解决使用 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

时间: 2024-10-14 12:56:32

解决使用 libjpeg 保存图片时因磁盘写入失败导致程序退出的问题的相关文章

删除合并快照时因磁盘空间不够导致快照管理器看不到快照无法完成的解决方法

VMWare Workstation合并快照时因磁盘空间不够导致快照管理器看不到快照无法完成的解决方法 ?Lander Zhang 专注外企按需IT基础架构运维服务,IT Helpdesk 实战培训践行者博客:https://blog.51cto.com/lander IT Helpdesk实战培训视频课程:https://edu.51cto.com/lecturer/733218.html 为什么要创建及删除合并快照? 搭建试验测试环境时,为了减少搭建时产生的未知错误导致已经完成的部分前功尽弃

解决键盘弹出时,webview被挤压导致背景图片被挤压出空白

第一种方法: 1.给外层div容器设置背景图片 style="height: 100%;background: url(../../images/10.jpg);background-size: 100% 100%;" 2.监听键盘弹出事件,弹出时调整容器的高度以适应背景图片 <body onresize="windowSizeChange();"> var oglHeight = document.querySelector("body&qu

[转]解决STM32开启定时器时立即进入一次中断程序问题

整理:MilerShao 在用到STM32定时器的更新中断时,发现有些情形下只要开启定时器就立即进入一次中断.准确说,只要使能更新中断允许位就立即响应一次更新中断[当然前提是相关NVIC也已经配置好].换言之,只要使能了相关定时器更新中断,不管你定时间隔多长甚至不在乎你是否启动了相关定时器,它都会立即进入一次定时器更新中断服务程序. 以STM32F051芯片为例,做了几种不同顺序的组合测试.根据测试发现,的确有些情况下一运行TIM_ITConfig(TIM1, TIM_IT_Update, EN

android4.2 高用zing拍照后,返回其它页面操作时,主线程关掉或程序退出的问题解决

产生错误的代码: @Override protected void onCreate(Bundle savedInstanceState) { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectDiskReads().detectDiskWrites().detectNetwork().penaltyLog().build()); StrictMode.setVmPolicy(new StrictMode

避免火狐浏览器产生巨大的磁盘写入量

我把电脑硬盘换成了一个SSD,新安装了系统.不久后,在系统监视器里发现,浏览一下午的火狐往磁盘写了1.5G的数据量,打开一个网页时要写2M的数据,这也太大了!我这SSD得少活多少年?我去试了试谷歌,一连打开七八个网页,磁盘写入达到2M/s,赶忙关掉了,吓人. 这怎么行?百度找方案......乱七八糟,只找到磁盘优化的内容,都与很地层的东西有关,不怎么敢弄,但是也改了一些东西. 如果下面这些解决方案并没有生效,可能是因为我做的一些其他改动,评论或私信我改过来. 历史纪录 偶然间,我想到隐私模式会不

VMware虚拟机Mac OS X无法调整扩展硬盘大小,更新xcode时出现磁盘空间不足

使用VMware虚拟机搭建的MacOSX,安装xcode时出现磁盘空间不足的错误. 因为很多朋友在初次安装MacOSX的时候都默认选择40G的磁盘大小,结果用了没两天之后就发现磁盘不够用了. 这时,百度一下你会找到很多相关文章,大体上是正确的,但针对于OS10.10以上的版本就有可能会出现 PCI 外置磁盘大小通过 磁盘工具 无法扩展的问题. 呈现出来的效果,是可以在磁盘工具中看到对应的磁盘已经扩展到目标大小,但是不能进行分区,其中唯一一个MacOSX分区也不能进行抹掉和扩展操作,原因很简单,这

解决Python读取文件时出现UnicodeDecodeError: &#39;gbk&#39; codec can&#39;t decode byte...

用Python在读取某个html文件时会遇到下面问题: 出问题的代码: 1 if __name__ == '__main__': 2 fileHandler = open('../report.html', mode='r') 3 4 report_lines = fileHandler.readlines() 5 for line in report_lines: 6 print(line.rstrip()) 修改方式是在open方法指定参数encoding='UTF-8': if __nam

使用Kindeditor的多文件(图片)上传时出现上传失败的解决办法/使用Flash上传多文件(图片)上传时上传失败的解决办法

近来用户反映希望我们把在线编辑器中的多图片上传功能实现,因为他们在编辑商品描述时经常会有一次上传多张图片的需求,如果要逐张选择的话效率很低,客户的需求就是我们的追求,很快我们就把完善功能排到了日程表中,要求尽快实现. 我们在项目中使用的在线编辑器是Kindeditor4.1.10,它们的多文件上传插件是使用Flash实现的,原本应该就是能使用的,但为什么老是显示上传失败的,百度了一下前人的经验和教训,出现这种情况,有两种可能:1)上传的目标文件夹没有写权限,导致上传的文件无法进行写操作,所以上传

vs项目,点击.sln文件时出错:“项目所需的应用程序未安装,确保已安装项目类型(.csproj)的应用程序”解决办法

关键词:VS2005程序用VS2008打开 程序无法使用 项目所需的应用程序未安装,确保已安装项目类型(.csproj)的应用程序 在要打开的项目sln文件上右键,打开方式,不要用Micrisoft visual studio version selector,用D:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\devenv.exe打开. vs项目,点击.sln文件时出错:"项目所需的应用程序未安装,确保已安装项目类型(.cspro