程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 解決使用 libjpeg 保存圖片時因磁盤寫入失敗導致程序退出的問題,libjpeg寫入

解決使用 libjpeg 保存圖片時因磁盤寫入失敗導致程序退出的問題,libjpeg寫入

編輯:C++入門知識

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

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved