解決使用 libjpeg 保存圖片時因磁盤寫入失敗導致程序退出的問題,libjpeg寫入
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