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

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

  • 相关阅读:
    人生应该接受的教育
    【转】俞军给淘宝产品经理的分享
    【转】伪O2O已死?2016年实体零售将迎来真正的O2O
    【转】一个测试工程师的2015总结和2016年小展望
    【转】移动App测试中的最佳做法
    Net作业调度(一) -Quartz.Net入门
    Quartz学习
    Newtonsoft.Json.dll
    用C#实现Base64处理,加密解密,编码解码
    mysql 连接数的最大数
  • 原文地址:https://www.cnblogs.com/baiynui/p/c201305271411.html
Copyright © 2020-2023  润新知