0. libjpeg 介紹
libjpeg 是一個完全用C語言編寫的庫,包含了被廣泛使用的JPEG解碼、JPEG編碼和其他的JPEG功能的實現。這個庫由獨立JPEG工作組維護。
參考:http://zh.wikipedia.org/wiki/Libjpeg
本文基於 libjpeg9 對使用 libjpeg 保存圖片時因磁盤寫入失敗導致程序退出的問題進行分析,文中的代碼和解決問題的方法均可結合 libjpeg9 編譯通過。
1.使用 libjpeg 保存圖片的方法。
不多說,直接上代碼:
/** * 將 rgb 數據保存到 jpeg 文件 */ int rgb_to_jpeg(LPRgbImage img, const char* filename) { FILE* f; struct jpeg_compress_struct jcs; // 聲明錯誤處理器,並賦值給jcs.err域 struct jpeg_error_mgr jem; unsigned char* pData; int error_flag = 0; jcs.err = jpeg_std_error(&jem); jpeg_create_compress(&jcs); f = fopen(filename, "wb"); if (f == NULL) { return -1; } // android 下使用以下方法,來解決使用 fwrite 寫文件時 sd 卡滿而不返回錯誤的問題 setbuf(f, NULL); jpeg_stdio_dest(&jcs, f); jcs.image_width = img->width; // 圖像尺寸 jcs.image_height = img->height; // 圖像尺寸 jcs.input_components = 3; // 在此為1,表示灰度圖, 如果是彩色位圖,則為3 jcs.in_color_space = JCS_RGB; // JCS_GRAYSCALE表示灰度圖,JCS_RGB表示彩色圖像 jpeg_set_defaults(&jcs); jpeg_set_quality(&jcs, 100, 1); // 圖像質量,100 最高 jpeg_start_compress(&jcs, TRUE); while (jcs.next_scanline < jcs.image_height) { pData = img->rgb + jcs.image_width * jcs.next_scanline * 3; jpeg_write_scanlines(&jcs, &pData, 1); } jpeg_finish_compress(&jcs); jpeg_destroy_compress(&jcs); fclose (f); return error_flag; }
libjpeg 也可已用來解碼(讀取 jpeg)文件:
/** * 從 jpeg 文件讀取數據,並保存到 RgbImage 中返回 */ LPRgbImage jpeg_to_rgb(const char* filename) { struct jpeg_decompress_struct cinfo; struct jpeg_error_mgr jerr; FILE* f; LPRgbImage pRgbImage; JSAMPROW row_pointer[1]; f = fopen(filename, "rb"); if (f == NULL) { return NULL; } // 將 jpeg 錯誤處理中的異常退出回調修改為我們自己的回調函數,保證程序不異常退出 cinfo.err = jpeg_std_error(&jerr); jpeg_create_decompress(&cinfo); jpeg_stdio_src(&cinfo, f); jpeg_read_header(&cinfo, TRUE); pRgbImage = (LPRgbImage) malloc(sizeof (RgbImage)); if (pRgbImage == NULL) { fclose(f); return NULL; } pRgbImage->width = cinfo.image_width; pRgbImage->height = cinfo.image_height; pRgbImage->linesize = libcfc_align_size(cinfo.image_width * 3); pRgbImage->rgb = (unsigned char*) malloc(pRgbImage->linesize * cinfo.image_height); if (pRgbImage->rgb == NULL) { free(pRgbImage); fclose(f); return NULL; } jpeg_start_decompress(&cinfo); row_pointer[0] = pRgbImage->rgb; while (cinfo.output_scanline < cinfo.output_height) { row_pointer[0] = pRgbImage->rgb + (cinfo.image_height - cinfo.output_scanline - 1) * pRgbImage->linesize; jpeg_read_scanlines(&cinfo, row_pointer, 1); } jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); fclose(f); return pRgbImage; }
代碼中使用了 LPRgbImage ,這是我自定義的一個結構體的指針類型,用來表示一個 rgb 的圖像,本文後面會給出完整的代碼。
2. 問題描述
使用以上方法保存 rgb 數據到 jpeg 文件時,如果磁盤空間滿或其他原因導致不能寫文件失敗,整個進程會被結束。但我們的期望往往是磁盤空間滿時給出友好提示,而不是程序直接掛掉。
3. 問題分析
1)在開發環境上重現此問題,程序會在控制台上打印“Output file write error --- out of disk space?”,然後退出。
2)在 libjpeg 的源代碼中搜索 “Output file write error --- out of disk space?”,找到 jerror.h 文件,內容對應
JMESSAGE(JERR_FILE_WRITE, "Output file write error --- out of disk space?")。
3)可以看出,JERR_FILE_WRITE 是 libjpeg 給這個問題描述信息定義的一個編號。
4)查找 JERR_FILE_WRITE 這個編號被引用過的地方,發現有六個文件使用過這個符號(我使用的是 libjpeg9,其他版本應該也不會影響本文的分析過程)。
5)JERR_FILE_WRITE 被引用的形式為:
ERREXIT(cinfo, JERR_FILE_WRITE);
ERREXIT 是一個宏,轉到這個宏的定義,這個宏同樣的被定義在 jerror.h 中,其定義如下:
#define ERREXIT(cinfo,code) \ ((cinfo)->err->msg_code = (code), \ (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo)))
可以看出來,ERREXIT 宏做了兩件事:
a)將編號(本文中討論的問題編號對應 JERR_FILE_WRITE)賦值給 (cinfo)->err->msg_code
b)調用 (cinfo)->err->error_exit) 回調。
cinfo 就是 初始化 libjpeg 時指定的 struct jpeg_decompress_struct。
(cinfo)->err->msg_code 是 libjpeg 處理錯誤的錯誤代碼。
(cinfo)->err->error_exit 是 libjpeg 在出現錯誤時,用來退出的回調函數。我們的程序就是這樣被退出的。
4. 解決辦法
通過分析,知道了程序退出是(cinfo)->err->error_exit 實現的,因此我們可以讓這個回調函數指針指向我們自己的函數。
並且,因為 libjpeg 會在出錯的時候給 (cinfo)->err->msg_code 一個值,之個值就是 libjpeg 定義的錯誤描述編號,非零的,所以可以在調用 libjpeg 的函數之前將這個值設置為 0,調用完成後在檢查這個時是否為 0,這樣來判斷 libjpeg 的函數調用是否成功。
5. 在 android 下的問題
遇到這個問題是因為要做一個 android 的播放器,其中解碼使用了 ffmpeg,截圖保存使用 libjpeg。本文上面描述的方法並不能完全奏效。
在 android 下保存截圖,如果使用的 sd 卡滿,導致保存圖片失敗,並不會回調我們使用 (cinfo)->err->error_exit 指定的函數。每次都會生成一個大小為 0 的文件。
通過分析,認為這事 android 寫文件時緩存機制的問題:sd 卡滿了,但寫文件是是先寫到緩存的,因此每次寫入文件都會先寫到緩存中,不會返回失敗。並且每次調用 fwrite 返回已經寫入的數據數是正確的,等到關閉文件或刷新緩存的時候,才會出錯。
為了解決這個問題,在打開文件時,將文件的緩存關閉,這樣,就能使用本文提到的方法來解決問題了。
setbuf(f, NULL);
6. 結束語
下面提供本文源代碼的完整版,代碼中使用的位圖必須是 24 位位圖,且掃描順序是自下而上的。
/* * jpeg_sample.c * * Created on: 2013-5-27 * Author: chenf * QQ: 99951468 */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <jpeglib.h> ////////////////////////////////////////////////////////////////////////////////////////////////// // 用於存取 bmp 文件的結構和函數的定義 #define TAG_TO_UINT16(l, h) ( (uint16_t) ( (l) | (h << 8) ) ) /** * 表示一個位圖的文件頭 */ #pragma pack(push, 1) // 修改字節對齊方式 typedef struct _libcfc_bitmap_file_header_t { uint16_t bfType; uint32_t bfSize; uint16_t bfReserved1; uint16_t bfReserved2; uint32_t bfOffBits; } libcfc_bitmap_file_header_t; #pragma pack(pop) /** * 表示一個位圖信息頭 */ #pragma pack(push, 1) // 修改字節對齊方式 typedef struct _libcfc_bitmap_info_header_t { uint32_t biSize; int32_t biWidth; int32_t biHeight; uint16_t biPlanes; uint16_t biBitCount; uint32_t biCompression; uint32_t biSizeImage; int32_t biXPelsPerMeter; int32_t biYPelsPerMeter; uint32_t biClrUsed; uint32_t biClrImportant; } libcfc_bitmap_info_header_t; #pragma pack(pop) /** * 表示一個位圖的頭部 */ #pragma pack(push, 1) // 修改字節對齊方式 typedef struct _libcfc_bitmap_header_t { libcfc_bitmap_file_header_t file_header; libcfc_bitmap_info_header_t info_header; } libcfc_bitmap_header_t; #pragma pack(pop) /** * 初始化位圖文件頭 */ void libcfc_bitmap_init_header(libcfc_bitmap_header_t* p_bitmap_header) { // 固定值 p_bitmap_header->file_header.bfType = TAG_TO_UINT16('B', 'M'); // 固定值 p_bitmap_header->file_header.bfReserved1 = 0; // 固定值 p_bitmap_header->file_header.bfReserved2 = 0; // 固定值 p_bitmap_header->file_header.bfOffBits = sizeof(libcfc_bitmap_header_t); // 需指定 * p_bitmap_header->file_header.bfSize = 0; //bmpheader.bfOffBits + width*height*bpp/8; // 固定值 p_bitmap_header->info_header.biSize = sizeof(libcfc_bitmap_info_header_t); // 需指定 * p_bitmap_header->info_header.biWidth = 0; // 需指定 * p_bitmap_header->info_header.biHeight = 0; // 固定值 p_bitmap_header->info_header.biPlanes = 1; // 需指定 * p_bitmap_header->info_header.biBitCount = 24; // 視情況指定 # p_bitmap_header->info_header.biCompression = 0; // 視情況指定 # p_bitmap_header->info_header.biSizeImage = 0; // 選填 - p_bitmap_header->info_header.biXPelsPerMeter = 100; // 選填 - p_bitmap_header->info_header.biYPelsPerMeter = 100; // 選填 - p_bitmap_header->info_header.biClrUsed = 0; // 選填 - p_bitmap_header->info_header.biClrImportant = 0; } // 用於存取 bmp 文件的結構和函數的定義 ////////////////////////////////////////////////////////////////////////////////////////////////// /** * 用於獲取對齊大小的宏,即得到不小於輸入數字的最小的 4 的倍數 */ #define libcfc_align_size(size) ( ( ( size ) + sizeof( int ) - 1 ) & ~( sizeof( int ) - 1 ) ) /** * 使用 rgb 數據表示的一個圖像 */ typedef struct tagRgbImage { unsigned char* rgb; int width; int height; int linesize; } RgbImage, *LPRgbImage; /** * 處理 jpeg 類庫中出錯退出的邏輯 */ static void jpeg_error_exit_handler(j_common_ptr cinfo) { // 什麼也不做 } /** * 將 rgb 數據保存到 jpeg 文件 */ int rgb_to_jpeg(LPRgbImage img, const char* filename) { FILE* f; struct jpeg_compress_struct jcs; // 聲明錯誤處理器,並賦值給jcs.err域 struct jpeg_error_mgr jem; unsigned char* pData; int error_flag = 0; jcs.err = jpeg_std_error(&jem); jpeg_create_compress(&jcs); // 將 jpeg 錯誤處理中的異常退出回調修改為我們自己的回調函數,保證程序不異常退出 jcs.err->error_exit = jpeg_error_exit_handler; f = fopen(filename, "wb"); if (f == NULL) { return -1; } // android 下使用以下方法,來解決使用 fwrite 寫文件時 sd 卡滿而不返回錯誤的問題 setbuf(f, NULL); jpeg_stdio_dest(&jcs, f); jcs.image_width = img->width; // 圖像尺寸 jcs.image_height = img->height; // 圖像尺寸 jcs.input_components = 3; // 在此為1,表示灰度圖, 如果是彩色位圖,則為3 jcs.in_color_space = JCS_RGB; // JCS_GRAYSCALE表示灰度圖,JCS_RGB表示彩色圖像 jpeg_set_defaults(&jcs); jpeg_set_quality(&jcs, 100, 1); // 圖像質量,100 最高 jpeg_start_compress(&jcs, TRUE); while (jcs.next_scanline < jcs.image_height) { pData = img->rgb + jcs.image_width * jcs.next_scanline * 3; // 調用前,先將 jcs.err->msg_code 設置為 0 jcs.err->msg_code = 0; jpeg_write_scanlines(&jcs, &pData, 1); // 調用完成後,檢查 jcs.err->msg_code 是否為 0 if (jcs.err->msg_code != 0) { error_flag = -1; break; } } jpeg_finish_compress(&jcs); jpeg_destroy_compress(&jcs); fclose (f); return error_flag; } /** * 從 jpeg 文件讀取數據,並保存到 RgbImage 中返回 */ LPRgbImage jpeg_to_rgb(const char* filename) { struct jpeg_decompress_struct cinfo; struct jpeg_error_mgr jerr; FILE* f; LPRgbImage pRgbImage; JSAMPROW row_pointer[1]; f = fopen(filename, "rb"); if (f == NULL) { return NULL; } // 將 jpeg 錯誤處理中的異常退出回調修改為我們自己的回調函數,保證程序不異常退出 cinfo.err = jpeg_std_error(&jerr); cinfo.err->error_exit = jpeg_error_exit_handler; jpeg_create_decompress(&cinfo); jpeg_stdio_src(&cinfo, f); jpeg_read_header(&cinfo, TRUE); pRgbImage = (LPRgbImage) malloc(sizeof (RgbImage)); if (pRgbImage == NULL) { fclose(f); return NULL; } pRgbImage->width = cinfo.image_width; pRgbImage->height = cinfo.image_height; pRgbImage->linesize = libcfc_align_size(cinfo.image_width * 3); pRgbImage->rgb = (unsigned char*) malloc(pRgbImage->linesize * cinfo.image_height); if (pRgbImage->rgb == NULL) { free(pRgbImage); fclose(f); return NULL; } jpeg_start_decompress(&cinfo); row_pointer[0] = pRgbImage->rgb; while (cinfo.output_scanline < cinfo.output_height) { row_pointer[0] = pRgbImage->rgb + (cinfo.image_height - cinfo.output_scanline - 1) * pRgbImage->linesize; jpeg_read_scanlines(&cinfo, row_pointer, 1); } jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); fclose(f); return pRgbImage; } /** * 將 rgb 數據保存成 bmp 文件 */ int rgb_to_bmp(LPRgbImage img, const char* filename) { libcfc_bitmap_header_t header; FILE* f; int size; f = fopen(filename, "wb"); if (f == NULL) { return -1; } libcfc_bitmap_init_header(&header); size = img->linesize * img->height; header.file_header.bfSize = sizeof(header) + size; header.info_header.biWidth = img->width; header.info_header.biHeight = img->height; if (1 != fwrite(&header, sizeof(header), 1, f)) { fclose (f); return -1; } if (size != fwrite(img->rgb, 1, size, f)) { fclose (f); return -1; } fclose (f); return 0; } /** * 從 bmp 文件讀取 rgb 數據,並保存到 RgbImage 中返回 */ LPRgbImage bmp_to_rgb(const char* filename) { libcfc_bitmap_header_t header; FILE* f; LPRgbImage pRgbImage; int size; f = fopen(filename, "rb"); if (f == NULL) { return NULL; } if (1 != fread(&header, sizeof(header), 1, f)) { fclose (f); return NULL; } pRgbImage = (LPRgbImage) malloc(sizeof (RgbImage)); if (pRgbImage == NULL) { fclose (f); return NULL; } pRgbImage->width = header.info_header.biWidth; pRgbImage->height = header.info_header.biHeight; if (pRgbImage->height < 0) { pRgbImage->height = -pRgbImage->height; } pRgbImage->linesize = libcfc_align_size(header.info_header.biWidth * 3); size = pRgbImage->linesize * pRgbImage->height; pRgbImage->rgb = (unsigned char*) malloc(size); if (pRgbImage->rgb == NULL) { free(pRgbImage); fclose (f); return NULL; } if (size != fread(pRgbImage->rgb, 1, size, f)) { free (pRgbImage->rgb); free (pRgbImage); fclose (f); return NULL; } fclose(f); return pRgbImage; } int main () { LPRgbImage pRgbImage = bmp_to_rgb("d:\\gamerev.bmp"); if (pRgbImage == NULL ) { return -1; } rgb_to_bmp(pRgbImage, "d:\\gamerev2.bmp"); free (pRgbImage->rgb); free (pRgbImage); } View Code
源代碼下載鏈接:http://files.cnblogs.com/baiynui1983/jpeg_sample.rar