BMP 和 JPEG
作者: 闕榮文
時(shí)間: 2016.5.29
1. 什么是 BMP
BMP 格式是最簡單,最直觀的位圖數(shù)據(jù)格式.它的思想非常樸素:
用若干個(gè)位來保存一個(gè)像素的信息,由若干個(gè)像素組成一個(gè)像素流來表達(dá)一張圖片.
通常我們會(huì)用1位(可以保存2種顏色,0 表示一種顏色,1表示另一種,不一定是黑白,也可以是藍(lán)綠,總之是2種),4位 - 16種顏色,8位 - 256種顏色,16位 - 65535種顏色,24位 - 2^24種顏色,32位 - 前24位和24位位圖一樣可以保存2^24種顏色,最后8位用來保存這個(gè)像素的灰度(也就是這個(gè)像素的明暗程度),可以表示256種灰度.注意,目前的顯示器在硬件上通常只能支持24位色,而且 libjpeg 也只能處理最多24位色的像素流.
對(duì)于24位以下的BMP,可以引入一個(gè)"調(diào)色板"來增強(qiáng)圖片的表達(dá)能力,以8位,256色位圖作為例子:
在8位位圖中,每個(gè)像素的信息用一個(gè)字節(jié)存儲(chǔ),那么 DIB 數(shù)據(jù)就是一個(gè) BYTE dibBuffer[] 數(shù)組, dibBuffer[0] 表示第一個(gè)像素的顏色,以此類推. 我們知道顏色是由RGB 3個(gè)分量組合而成的,編程中用 RGBQUAD 結(jié)構(gòu)表示,那么8位除以3,每個(gè)分量只能用2位來存儲(chǔ)(不使用編碼壓縮RLE的前提下),實(shí)際能夠表現(xiàn)的顏色非常有限.現(xiàn)在我們引入一個(gè)長度為256的 RGBQUAD 類型的數(shù)組: RGBQUAD colorTable[256],我們?cè)?DBI 數(shù)組 dibBuffer[] 中不再直接存放的每個(gè)像素的顏色值,而是存放該顏色值在 colorTable 中的索引,這樣就可以充分利用 dibBuffer 中的每一位的存儲(chǔ)空間.這個(gè) "colorTable" 就是 Windows 中調(diào)色板的概念. 知道了這些,就可以理解為什么24位及以上色深的位圖不需要調(diào)色板了.
2.1. BMP 文件格式和DIB
DIB就是"設(shè)備無關(guān)位圖"的意思,我們可以理解為一個(gè)像素?cái)?shù)組,這是編程時(shí)我們需要處理的數(shù)據(jù),非常簡單,就是一個(gè)定長數(shù)組,如果是一個(gè)24位的DIB數(shù)據(jù),那么在編程時(shí)就可以認(rèn)為是一個(gè) BYTE dibBuffer[], dibBuffer[0],dibBuffer[1],dibBuffer[2]表示第一個(gè)像素的 RGB 值(實(shí)際上是 BGR), dibBuffer[3],[4],[5] 表示第二個(gè)像素的 RGB 值,以此類推.當(dāng)然我們不能直接把這個(gè) dibBuffer 數(shù)組寫到磁盤作為 BMP 文件,缺少圖片的調(diào)色板,寬,高等信息,所以我們需要一個(gè)特定的格式來存儲(chǔ) DIB 像素流.
BMP文件格式就是把DIB像素流存儲(chǔ)到磁盤是需要遵循的相關(guān)約定. 關(guān)于BMP文件格式的詳細(xì)說明在網(wǎng)上可以找到很多,比如這篇說的就很清楚: http://blog.csdn.net/lanbing510/article/details/8176231
從編程的角度來看,一個(gè)BMP文件是可以表述為以下結(jié)構(gòu):
typedef struct tagBITMAP_FILE
{
BITMAPFILEHEADER bitmapheader;
BITMAPINFOHEADER bitmapinfoheader;
PALETTEENTRY palette[n]; // 調(diào)色板數(shù)據(jù)(可選,由BITMAPFILEHEADER::bOffBits計(jì)算 n 的值)
UCHAR *dibBuffer; // DIB 數(shù)據(jù)數(shù)組
} BITMAP_FILE;
BITMAPFILEHEADER, BITMAPINFOHEADER, PALETTEENTRY 結(jié)構(gòu)的詳細(xì)信息可以在 MSDN 中查到.
用自然語言簡單描述一下:
文件頭 - 固定長度,表示這個(gè)文件是一個(gè) BMP 文件,版本號(hào),文件長度等,最重要的時(shí)文件頭結(jié)構(gòu)中的 bfOffBits 字段,它表示 DIB 像素流數(shù)據(jù)在文件中的偏移位置,編程時(shí),我們打開一個(gè) BMP 文件,先讀取固定長度的文件頭,在根據(jù)這個(gè)字段就可以構(gòu)造前面說的調(diào)色板數(shù)組 RGBQUAD colorTable[] 和 DIB 數(shù)組 BYTE dibBuffer[] 了.
BMP信息頭 - 固定長度,存儲(chǔ)位圖的寬高等信息,需要注意的字段 biHeight, 如果它是正數(shù)則表示像素流的信息是倒序存儲(chǔ)的,即位圖的底下一行的像素存儲(chǔ)在前;如果它是負(fù)數(shù)則表示像素流的信息是正序存儲(chǔ)的,位圖的第一行像素存儲(chǔ)在 dibBuffer 開頭.
調(diào)色板數(shù)組 - 可選,用文件頭中的偏移地址減去文件頭和信息頭的長度就是調(diào)色板數(shù)組的長度.
DIB像素流 - 就是 dibBuffer[] 數(shù)組.
特別要注意的一點(diǎn)是,在實(shí)際編程中, 24位 DIB 數(shù)據(jù)的存放順序是 BGR 即 dibBuffer[0] 存放的是最后一行的第一個(gè)像素的 B 分量, dibBuffer[1] 是 G 分量, dibBuffer[2] 是 R 分量, 而 JPG 壓縮時(shí)要求輸入順序是 RGB, 所以把 dibBuffer 提供給 JPEG 壓縮器前需要處理一下, dibBuffer[i] 和 dibBuffer[i + 2] 交換,否則得到的 JPG 圖像顏色是不對(duì)的.
2.2. DDB
DDB 是"設(shè)備相關(guān)位圖"的意思,把 DIB 數(shù)據(jù)寫入設(shè)備之后,設(shè)備在內(nèi)部會(huì)把 DIB 數(shù)據(jù)處理為內(nèi)部數(shù)據(jù)格式, Windows GDI 中用 HBITMAP 表述一個(gè) DDB,我們只需要調(diào)用相關(guān)的 API 就可以了,具體細(xì)節(jié)不用理會(huì).
3. 什么是 JPEG
JPEG是 DIB 數(shù)據(jù)的一種編碼規(guī)則,前面我們提到 BMP 文件,直接把 DIB 數(shù)組 dibBuffer[] 直接寫到文件中,所以BMP文件是原始的,無損失的保存了內(nèi)存中的圖像數(shù)據(jù).如果用某種算法把 dibBuffer 數(shù)組編碼壓縮,那么我們也許就沒必要把整個(gè) dibBuffer (通常是一個(gè)很大的數(shù)組) 直接寫入文件中,從而大大節(jié)省磁盤空間. JPEG 就是這樣一種算法.
4. libjpeg
C語言實(shí)現(xiàn)的 JPEG 庫,官網(wǎng)地址: http://www.ijg.org/
4.1 編譯
我寫這篇文章的時(shí)候 JPEG 庫的版本是 jpeg-9b,從官網(wǎng)上下載源碼 jpegsr9b.zip 解壓后,啟動(dòng)Visual Studio,進(jìn)入命令行模式,切換到 jpeg 源碼目錄,輸入: nmake /f makefile.vc 就會(huì)看到 jpeg.sln - VS工程文件出現(xiàn)了,用Visual Studio 打開編譯即可.
如果執(zhí)行 nmake 命令時(shí)提示找不到 win32.mak,就編輯一下 makefile.vc 把第12行 !include <win32.mak> 注釋掉就可以,其實(shí) nmake /f makefile.vc 并不是真正編譯,這是重命名了幾個(gè)文件而已.
編譯完成后得到: jpeg.lib 這就是你需要的庫文件了,再把 jconfig.h, jerror.h, jinclude.h, jmorecfg.h, jpeglib.h 復(fù)制到你的工程中就算配置完成了.
4.2 example.c
libjpeg 的使用實(shí)例在源碼包中的 example.c 文件里, 我們只要把 write_JPEG_file / read_JPEG_file 兩個(gè)函數(shù)看明白就可以應(yīng)付大多數(shù)應(yīng)用.
4.3 內(nèi)存 JPG 壓縮解壓縮及其它
example.c 中的實(shí)例是使用文件io的, 用 jpeg_mem_src / jpeg_mem_dest 函數(shù)代替 jpeg_stdio_src / jpeg_stdio_dest 就可以實(shí)現(xiàn)內(nèi)存io.
JPEG庫是不知道調(diào)色板之類的東西的,它只是很單純的把輸入的 DIB 像素流壓縮輸出為一個(gè)更短的輸出數(shù)據(jù)流.所以對(duì)于包含了調(diào)色板的 BMP 文件,由于 DIB 數(shù)組內(nèi)保存的是調(diào)色板的索引號(hào)而并不是顏色值,在提交給 JPEG 庫之前需要根據(jù)調(diào)色板查表構(gòu)造一個(gè)真正的包含顏色信息的 DIB 像素流,這樣 JPEG 庫才能正常工作.
=======================================
我之前的的博客地址是 http://blog.csdn.net/querw 還有一些我以前寫的文章.實(shí)在受不了他們網(wǎng)站的各種問題,決定改變陣地.
====================================================================================================
附錄: 截取windows桌面,并保存為 .jpg 文件
1 #include "stdafx.h"
2 #include <tchar.h>
3
4 extern "C"
5 {
6 #include "jpeglib.h"
7 }
8
9 int save_screen_to_jpeg(const char* filename, int quality)
10 {
11 /*
12 * 把屏幕內(nèi)容保存為一個(gè) HBITMAP DDB
13 */
14 HDC hScrnDC = CreateDC(_T("DISPLAY"), NULL, NULL, NULL);
15 HDC hMemDC = CreateCompatibleDC(hScrnDC);
16
17 // 獲取屏幕分辨率
18 int xScrn = GetDeviceCaps(hScrnDC, HORZRES);
19 int yScrn = GetDeviceCaps(hScrnDC, VERTRES);
20
21 // 創(chuàng)建位圖,并選中
22 HBITMAP hScrnBmp = CreateCompatibleBitmap(hScrnDC, xScrn, yScrn);
23 SelectObject(hMemDC, hScrnBmp);
24
25 // 復(fù)制屏幕內(nèi)容
26 BitBlt(hMemDC, 0, 0, xScrn, yScrn, hScrnDC, 0, 0, SRCCOPY);
27
28 // 現(xiàn)在得到了一個(gè) HBITMAP DDB - hScrnBmp
29
30 /*
31 * 通過 hScrnBmp DDB 取得 DIB 數(shù)據(jù)
32 */
33 // 獲取色深 JPG 只能處理 24 位色,所以不管當(dāng)前系統(tǒng)設(shè)置的色深是多少,我們都要求 GetDIBits 函數(shù)返回 24 位的 DIB 數(shù)據(jù),同時(shí)也不需要調(diào)色板
34 //int colorDeepBits = GetDeviceCaps(hScrnBmp, BITSPIXEL);
35 //if(colorDeepBits > 24) colorDeepBits = 24;
36 int colorDeepBits = 24;
37
38 // 每行像素占用的字節(jié)數(shù),每行要對(duì)齊4字節(jié).
39 int imageRowSize = (xScrn * colorDeepBits + 31) / 32 * 4;
40
41 // 分配 DIB 數(shù)組
42 unsigned char* dibBuffer = new unsigned char[imageRowSize * yScrn];
43 assert(dibBuffer);
44 memset(dibBuffer, 0, imageRowSize * yScrn); // 清零是個(gè)好習(xí)慣
45
46 // 填充 BMP 信息頭
47 BITMAPINFO bmi = {0};
48 bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
49 bmi.bmiHeader.biWidth = xScrn;
50 bmi.bmiHeader.biHeight = yScrn * -1; // JPG 壓縮需要正序的 DIB 像素流,所以要負(fù)數(shù).
51 bmi.bmiHeader.biPlanes = 1;
52 bmi.bmiHeader.biBitCount = colorDeepBits;
53 bmi.bmiHeader.biCompression = BI_RGB;
54
55 // 獲取 DIB 像素?cái)?shù)組(DIB_RGB_COLORS 表示獲取 RGB 值而不是調(diào)色板索引,當(dāng)然24位位圖也沒有調(diào)色板)
56 int gdiRet = GetDIBits(hMemDC, hScrnBmp, 0, yScrn, dibBuffer, &bmi, DIB_RGB_COLORS);
57 assert(gdiRet == yScrn);
58 assert(bmi.bmiHeader.biSizeImage == imageRowSize * yScrn);
59
60 // DIB 數(shù)據(jù)已經(jīng)獲取,所有的 GDI 對(duì)象可以釋放了.
61 DeleteDC(hScrnDC);
62 DeleteDC(hMemDC);
63 DeleteObject(hScrnBmp);
64
65 /*
66 * 把 DIB 數(shù)據(jù)壓縮為 JPG 數(shù)據(jù),用 example.c 中的代碼
67 */
68
69 // DIB 中顏色的存放順序是 BGR, 而 JPG 要求的順序是 RGB, 所以要交換 R 和 B.
70 // 由于有行對(duì)齊因素,所以逐行處理
71 for(int row = 0; row < yScrn; ++row)
72 {
73 unsigned char* rowData = dibBuffer + imageRowSize * row;
74 for(int col = 0; col < xScrn * 3; col += 3)
75 {
76 unsigned char swap = rowData[col];
77 rowData[col] = rowData[col + 2];
78 rowData[col + 2] = swap;
79 }
80 }
81
82 //把位圖數(shù)據(jù)壓縮為 jpeg
83 struct jpeg_compress_struct cinfo;
84 struct jpeg_error_mgr jerr;
85 FILE * outfile; /* target file */
86 JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */
87 int row_stride; /* physical row width in image buffer */
88 int image_width = xScrn;
89 int image_height = yScrn;
90 JSAMPLE* image_buffer = dibBuffer; // DIB buffer
91 int image_buffer_len = imageRowSize * image_height; // DIB buffer 長度
92
93 if(fopen_s(&outfile, filename, "wb"))
94 //if ((outfile = fopen_s(filename, "wb")) == NULL)
95 {
96 fprintf(stderr, "can't open %s\n", filename);
97 assert(0);
98 }
99 else
100 {
101 /* Step 1: allocate and initialize JPEG compression object */
102 cinfo.err = jpeg_std_error(&jerr);
103
104 /* Now we can initialize the JPEG compression object. */
105 jpeg_create_compress(&cinfo);
106
107 /* Step 2: specify data destination (eg, a file) */
108 /* Note: steps 2 and 3 can be done in either order. */
109 jpeg_stdio_dest(&cinfo, outfile);
110
111 /* Step 3: set parameters for compression */
112
113 /* First we supply a description of the input image.
114 * Four fields of the cinfo struct must be filled in:
115 */
116 cinfo.image_width = image_width; /* image width and height, in pixels */
117 cinfo.image_height = image_height;
118 cinfo.input_components = 3; /* # of color components per pixel */ // 因?yàn)镈IB數(shù)據(jù)是24位的,所以每個(gè)像素占用3個(gè)字節(jié)
119 cinfo.in_color_space = JCS_RGB; /* colorspace of input image */
120 /* Now use the library's routine to set default compression parameters.
121 * (You must set at least cinfo.in_color_space before calling this,
122 * since the defaults depend on the source color space.)
123 */
124 jpeg_set_defaults(&cinfo);
125 /* Now you can set any non-default parameters you wish to.
126 * Here we just illustrate the use of quality (quantization table) scaling:
127 */
128 jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);
129
130 /* Step 4: Start compressor */
131
132 /* TRUE ensures that we will write a complete interchange-JPEG file.
133 * Pass TRUE unless you are very sure of what you're doing.
134 */
135 jpeg_start_compress(&cinfo, TRUE);
136
137 /* Step 5: while (scan lines remain to be written) */
138 /* jpeg_write_scanlines(
); */
139
140 /* Here we use the library's state variable cinfo.next_scanline as the
141 * loop counter, so that we don't have to keep track ourselves.
142 * To keep things simple, we pass one scanline per call; you can pass
143 * more if you wish, though.
144 */
145 row_stride = imageRowSize;
146 while (cinfo.next_scanline < cinfo.image_height)
147 {
148 /* jpeg_write_scanlines expects an array of pointers to scanlines.
149 * Here the array is only one element long, but you could pass
150 * more than one scanline at a time if that's more convenient.
151 */
152 row_pointer[0] = &image_buffer[cinfo.next_scanline * row_stride];
153 //row_pointer[0] = &image_buffer[image_buffer_len - (cinfo.next_scanline + 1) * row_stride];
154 (void)jpeg_write_scanlines(&cinfo, row_pointer, 1);
155 }
156
157 /* Step 6: Finish compression */
158 jpeg_finish_compress(&cinfo);
159
160 /* After finish_compress, we can close the output file. */
161 fclose(outfile);
162
163 /* Step 7: release JPEG compression object */
164 /* This is an important step since it will release a good deal of memory. */
165 jpeg_destroy_compress(&cinfo);
166 }
167
168 // 釋放 DIB 數(shù)組
169 delete []dibBuffer;
170 return 0;
171 }
172