解决使用 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);
}
View Code

 

   源代码下载链接:http://files.cnblogs.com/baiynui1983/jpeg_sample.rar

 

以上是关于解决使用 libjpeg 保存图片时因磁盘写入失败导致程序退出的问题的主要内容,如果未能解决你的问题,请参考以下文章

libJPEG-turbo库使用示例代码

npm install时因权限失败的解决方法

Android压缩图片和libjpeg库

php为啥上传图片会失败

spring boot kafka 在将 testcontainers 与 kafka、zookeeper、模式注册表一起使用时因“代理可能不可用”而失败

ZuulProxy 在应该进行故障转移时因“RibbonCommand 超时且没有可用的回退”而失败