BMP 和 JPEG
來源:程序員人生 發布時間:2016-06-22 08:45:04 閱讀次數:2428次
1. 甚么是 BMP
BMP 格式是最簡單,最直觀的位圖數據格式.它的思想非常樸素:
用若干個位來保存1個像素的信息,由若干個像素組成1個像素流來表達1張圖片.
通常我們會用1位(可以保存2種色彩,0 表示1種色彩,1表示另外一種,不1定是黑白,也能夠是藍綠,總之是2種),4位 - 16種色彩,8位 - 256種色彩,16位 - 65535種色彩,24位 - 2^24種色彩,32位 - 前24位和24位位圖1樣可以保存2^24種色彩,最后8位用來保存這個像素的灰度(也就是這個像素的明暗程度),可以表示256種灰度.注意,目前的顯示器在硬件上通常只能支持24位色,而且 libjpeg 也只能處理最多24位色的像素流.
對24位以下的BMP,可以引入1個"調色板"來增強圖片的表達能力,以8位,256色位圖作為例子:
在8位位圖中,每一個像素的信息用1個字節存儲,那末 DIB 數據就是1個 BYTE dibBuffer[] 數組, dibBuffer[0] 表示第1個像素的色彩,以此類推. 我們知道色彩是由RGB 3個份量組合而成的,編程中用 RGBQUAD 結構表示,那末8位除以3,每一個份量只能用2位來存儲(不使用編碼緊縮RLE的條件下),實際能夠表現的色彩非常有限.現在我們引入1個長度為256的 RGBQUAD 類型的數組: RGBQUAD colorTable[256],我們在 DBI 數組 dibBuffer[] 中不再直接寄存的每一個像素的色彩值,而是寄存該色彩值在
colorTable 中的索引,這樣就能夠充分利用 dibBuffer 中的每位的存儲空間.這個 "colorTable" 就是 Windows 中調色板的概念. 知道了這些,就能夠理解為何24位及以上色深的位圖不需要調色板了.
2.1. BMP 文件格式和DIB
DIB就是"裝備無關位圖"的意思,我們可以理解為1個像素數組,這是編程時我們需要處理的數據,非常簡單,就是1個定長數組,如果是1個24位的DIB數據,那末在編程時就能夠認為是1個 BYTE dibBuffer[], dibBuffer[0],dibBuffer[2],dibBuffer[2]表示第1個像素的 RGB 值(實際上是 BGR), dibBuffer[3],[4],[5] 表示第2個像素的 RGB 值,以此類推.固然我們不能直接把這個 dibBuffer 數組寫到磁盤作為 BMP 文件,缺少圖片的調色板,寬,高等信息,所以我們需要1個特定的格式來存儲
DIB 像素流.
BMP文件格式就是把DIB像素流存儲到磁盤是需要遵守的相干約定. 關于BMP文件格式的詳細說明在網上可以找到很多,比如這篇說的就很清楚: http://blog.csdn.net/lanbing510/article/details/8176231
從編程的角度來看,1個BMP文件是可以表述為以下結構:
typedef struct tagBITMAP_FILE
{
BITMAPFILEHEADER bitmapheader;
BITMAPINFOHEADER bitmapinfoheader;
PALETTEENTRY palette[n]; // 調色板數據(可選,由BITMAPFILEHEADER::bOffBits計算 n 的值)
UCHAR *dibBuffer; // DIB 數據數組
} BITMAP_FILE;
BITMAPFILEHEADER, BITMAPINFOHEADER, PALETTEENTRY 結構的詳細信息可以在 MSDN 中查到.
用自然語言簡單描寫1下:
文件頭 - 固定長度,表示這個文件是1個 BMP 文件,版本號,文件長度等,最重要的時文件頭結構中的 bfOffBits 字段,它表示 DIB 像素流數據在文件中的偏移位置,編程時,我們打開1個 BMP 文件,先讀取固定長度的文件頭,在根據這個字段就能夠構造前面說的調色板數組 RGBQUAD colorTable[] 和 DIB 數組 BYTE dibBuffer[] 了.
BMP信息頭 - 固定長度,存儲位圖的寬高等信息,需要注意的字段 biHeight, 如果它是正數則表示像素流的信息是倒序存儲的,即位圖的底下1行的像素存儲在前;如果它是負數則表示像素流的信息是正序存儲的,位圖的第1行像素存儲在 dibBuffer 開頭.
調色板數組 - 可選,用文件頭中的偏移地址減去文件頭和信息頭的長度就是調色板數組的長度.
DIB像素流 - 就是 dibBuffer[] 數組.
特別要注意的1點是,在實際編程中, 24位 DIB 數據的寄存順序是 BGR 即 dibBuffer[0] 寄存的是最后1行的第1個像素的 B 份量, dibBuffer[1] 是 G 份量, dibBuffer[2] 是 R 份量, 而 JPG 緊縮時要求輸入順序是 RGB, 所以把 dibBuffer 提供給 JPEG 緊縮器前需要處理1下, dibBuffer[i] 和 dibBuffer[i + 2] 交換,否則得到的 JPG 圖象色彩是不對的.
2.2. DDB
DDB 是"裝備相干位圖"的意思,把 DIB 數據寫入裝備以后,裝備在內部會把 DIB 數據處理為內部數據格式, Windows GDI 中用 HBITMAP 表述1個 DDB,我們只需要調用相干的 API 就能夠了,具體細節不用理睬.
3. 甚么是 JPEG
JPEG是 DIB 數據的1種編碼規則,前面我們提到 BMP 文件,直接把 DIB 數組 dibBuffer[] 直接寫到文件中,所以BMP文件是原始的,無損失的保存了內存中的圖象數據.如果用某種算法把 dibBuffer 數組編碼緊縮,那末我們或許就沒必要把全部 dibBuffer (通常是1個很大的數組) 直接寫入文件中,從而大大節省磁盤空間. JPEG 就是這樣1種算法.
4. libjpeg
C語言實現的 JPEG 庫,官網地址: http://www.ijg.org/
4.1 編譯
我寫這篇文章的時候 JPEG 庫的版本是 jpeg⑼b,從官網上下載源碼 jpegsr9b.zip 解壓后,啟動Visual Studio,進入命令行模式,切換到 jpeg 源碼目錄,輸入: nmake /f makefile.vc 就會看到 jpeg.sln - VS工程文件出現了,用Visual Studio 打開編譯便可.
如果履行 nmake 命令時提示找不到 win32.mak,就編輯1下 makefile.vc 把第12行 !include 注釋掉就能夠,其實 nmake /f makefile.vc 其實不是真正編譯,這是重命名了幾個文件而已.
編譯完成后得到: jpeg.lib 這就是你需要的庫文件了,再把 jconfig.h, jerror.h, jinclude.h, jmorecfg.h, jpeglib.h 復制到你的工程中就算配置完成了.
4.2 example.c
libjpeg 的使用實例在源碼包中的 example.c 文件里, 我們只要把 write_JPEG_file / read_JPEG_file 兩個函數看明白就能夠應付大多數利用了.
4.3 內存 JPG 緊縮解緊縮及其它
example.c 中的實例是使用文件io的, 用 jpeg_mem_src / jpeg_mem_dest 函數代替 jpeg_stdio_src / jpeg_stdio_dest 就能夠實現內存io了.
JPEG庫是不知道調色板之類的東西的,它只是很單純的把輸入的 DIB 像素流緊縮輸出為1個更短的輸出數據流.所以對包括了調色板的 BMP 文件,由于 DIB 數組內保存的是調色板的索引號而其實不是色彩值,在提交給 JPEG 庫之前需要根據調色板查表構造1個真實的包括色彩信息的 DIB 像素流,這樣 JPEG 庫才能正常工作.
====================================================================================================
附錄: 截取windows桌面,并保存為 .jpg 文件
int save_screen_to_jpeg(const char* filename, int quality)
{
/*
* 把屏幕內容保存為1個 HBITMAP DDB
*/
HDC hScrnDC = CreateDC(_T("DISPLAY"), NULL, NULL, NULL);
HDC hMemDC = CreateCompatibleDC(hScrnDC);
// 獲得屏幕分辨率
int xScrn = GetDeviceCaps(hScrnDC, HORZRES);
int yScrn = GetDeviceCaps(hScrnDC, VERTRES);
// 創建位圖,并選中
HBITMAP hScrnBmp = CreateCompatibleBitmap(hScrnDC, xScrn, yScrn);
SelectObject(hMemDC, hScrnBmp);
// 復制屏幕內容
BitBlt(hMemDC, 0, 0, xScrn, yScrn, hScrnDC, 0, 0, SRCCOPY);
// 現在得到了1個 HBITMAP DDB - hScrnBmp
/*
* 通過 hScrnBmp DDB 獲得 DIB 數據
*/
// 獲得色深 JPG 只能處理 24 位色,所以不管當前系統設置的色深是多少,我們都要求 GetDIBits 函數返回 24 位的 DIB 數據,同時也不需要調色板
//int colorDeepBits = GetDeviceCaps(hScrnBmp, BITSPIXEL);
//if(colorDeepBits > 24) colorDeepBits = 24;
int colorDeepBits = 24;
// 每行像素占用的字節數,每行要對齊4字節.
int imageRowSize = (xScrn * colorDeepBits + 31) / 32 * 4;
// 分配 DIB 數組
unsigned char* dibBuffer = new unsigned char[imageRowSize * yScrn];
assert(dibBuffer);
memset(dibBuffer, 0, imageRowSize * yScrn); // 清零是個好習慣
// 填充 BMP 信息頭
BITMAPINFO bmi = {0};
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = xScrn;
bmi.bmiHeader.biHeight = yScrn * ⑴; // JPG 緊縮需要正序的 DIB 像素流,所以要負數.
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = colorDeepBits;
bmi.bmiHeader.biCompression = BI_RGB;
// 獲得 DIB 像素數組(DIB_RGB_COLORS 表示獲得 RGB 值而不是調色板索引,固然24位位圖也沒有調色板)
int gdiRet = GetDIBits(hMemDC, hScrnBmp, 0, yScrn, dibBuffer, &bmi, DIB_RGB_COLORS);
assert(gdiRet == yScrn);
assert(bmi.bmiHeader.biSizeImage == imageRowSize * yScrn);
// DIB 數據已獲得,所有的 GDI 對象可以釋放了.
DeleteDC(hScrnDC);
DeleteDC(hMemDC);
DeleteObject(hScrnBmp);
/*
* 把 DIB 數據緊縮為 JPG 數據,用 example.c 中的代碼
*/
// DIB 中色彩的寄存順序是 BGR, 而 JPG 要求的順序是 RGB, 所以要交換 R 和 B.
// 由于有行對齊因素,所以逐行處理
for(int row = 0; row < yScrn; ++row) { unsigned char* rowData = dibBuffer + imageRowSize * row; for(int col = 0; col < xScrn * 3; col += 3) { unsigned char swap = rowData[col]; rowData[col] = rowData[col + 2]; rowData[col + 2] = swap; } } //把位圖數據緊縮為 jpeg struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; FILE * outfile; /* target file */ JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */ int row_stride; /* physical row width in image buffer */ int image_width = xScrn; int image_height = yScrn; JSAMPLE* image_buffer = dibBuffer; // DIB buffer int image_buffer_len = imageRowSize * image_height; // DIB buffer 長度 if(fopen_s(&outfile, filename, "wb")) //if ((outfile = fopen_s(filename, "wb")) == NULL) { fprintf(stderr, "can't open %s\n", filename); assert(0); } else { /* Step 1: allocate and initialize JPEG compression object */ cinfo.err = jpeg_std_error(&jerr); /* Now we can initialize the JPEG compression object. */ jpeg_create_compress(&cinfo); /* Step 2: specify data destination (eg, a file) */ /* Note: steps 2 and 3 can be done in either order. */ jpeg_stdio_dest(&cinfo, outfile); /* Step 3: set parameters for compression */ /* First we supply a description of the input image. * Four fields of the cinfo struct must be filled in: */ cinfo.image_width = image_width; /* image width and height, in pixels */ cinfo.image_height = image_height; cinfo.input_components = 3; /* # of color components per pixel */ // 由于DIB數據是24位的,所以每一個像素占用3個字節 cinfo.in_color_space = JCS_RGB; /* colorspace of input image */ /* Now use the library's routine to set default compression parameters. * (You must set at least cinfo.in_color_space before calling this, * since the defaults depend on the source color space.) */ jpeg_set_defaults(&cinfo); /* Now you can set any non-default parameters you wish to. * Here we just illustrate the use of quality (quantization table) scaling: */ jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */); /* Step 4: Start compressor */ /* TRUE ensures that we will write a complete interchange-JPEG file. * Pass TRUE unless you are very sure of what you're doing. */ jpeg_start_compress(&cinfo, TRUE); /* Step 5: while (scan lines remain to be written) */ /* jpeg_write_scanlines(...); */ /* Here we use the library's state variable cinfo.next_scanline as the * loop counter, so that we don't have to keep track ourselves. * To keep things simple, we pass one scanline per call; you can pass * more if you wish, though. */ row_stride = imageRowSize; while (cinfo.next_scanline < cinfo.image_height) { /* jpeg_write_scanlines expects an array of pointers to scanlines. * Here the array is only one element long, but you could pass * more than one scanline at a time if that's more convenient. */ row_pointer[0] = &image_buffer[cinfo.next_scanline * row_stride]; //row_pointer[0] = &image_buffer[image_buffer_len - (cinfo.next_scanline + 1) * row_stride]; (void)jpeg_write_scanlines(&cinfo, row_pointer, 1); } /* Step 6: Finish compression */ jpeg_finish_compress(&cinfo); /* After finish_compress, we can close the output file. */ fclose(outfile); /* Step 7: release JPEG compression object */ /* This is an important step since it will release a good deal of memory. */ jpeg_destroy_compress(&cinfo); } // 釋放 DIB 數組 delete []dibBuffer; return 0; }
PS: 最近1年半在忙1個項目,1直沒時間更新博客,也沒有回答網友們的發問,非常抱歉.大多數問題都是百度1下就能夠處理的,只能說抱歉了.
截屏保存為JPG其實也是我在項目中用到的1個小功能,由于不觸及甚么商業上的東西,就貼出來分享1下.
以后有機會我會把項目中的1些功能分解成可以復用的代碼,大家交換1下.
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈