解决使用 libjpeg 保存图片时因磁盘写入失败导致程序退出的问题
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了解决使用 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); }
源代码下载链接:http://files.cnblogs.com/baiynui1983/jpeg_sample.rar
以上是关于解决使用 libjpeg 保存图片时因磁盘写入失败导致程序退出的问题的主要内容,如果未能解决你的问题,请参考以下文章
spring boot kafka 在将 testcontainers 与 kafka、zookeeper、模式注册表一起使用时因“代理可能不可用”而失败