图像解码之一——使用libjpeg解码jpeg图片
多媒體應用在現在電子產品中的地位越來越重要,尤其是在嵌入式設備中。本系列文章將會介紹利用libjpeg解碼jpeg文件,libpng解碼png文件,libgif解碼gif文件。本文為第一篇,介紹使用libjpeg解碼jpeg文件。
libjpeg簡介
??? libjpeg是一個完全用C語言編寫的庫,包含了被廣泛使用的JPEG解碼、JPEG編碼和其他的JPEG功能的實現。這個庫由獨立JPEG工作組維護。最新版本號是6b,于1998年發布。可以參考維基百科關于libjpeg的介紹。
libjpeg庫的數據結構
??? 用libjpeg庫解碼jpeg數據的時候,最重要的數據類型為struct jpeg_decompress_struct,一般變量定義成cinfo變量,該變量保存著jpeg數據的詳細信息,也保存著解碼之后輸出數據的詳細信 息。一般情況下,每次調用libjpeg庫API的時候都需要把這個變量作為第一個參數傳入。另外用戶也可以通過修改該變量來修改libjpeg行為,比 如輸出數據格式,libjpeg庫可用的最大內存等等。
libjpeg庫的使用
1、設置出錯處理函數
??? “天有不測風云”,我們使用libjpeg庫的時候難免會產生錯誤,所以我們在使用libjpeg解碼之前,首先要做好錯誤處理。在libjpeg庫中, 實現了默認錯誤處理函數,當錯誤發生時,比如如果內存不足(非常可能發生,后面會介紹)等,則默認錯誤處理函數將會調用exit函數結束整個進程,詳細內 容可以參考jerror.c文件。這個對于很多用戶來說這樣的特性是不合適的,不過libjpeg提供了接口讓我們注冊自定義錯誤處理函數。
??? 在C語言中沒有C++的異常處理機制,但是提供了setjmp和longjmp機制來實現類似的功能,如果你對這個機制不熟悉的話,請查閱C語言手冊。本文下面的代碼片段都是出自libjpeg的example.c文件,可以查閱之。
1: /* We set up the normal JPEG error routines, then override error_exit. */ 2: cinfo.err = jpeg_std_error(&jerr.pub); 3: jerr.pub.error_exit = my_error_exit; 4: /* Establish the setjmp return context for my_error_exit to use. */ 5: if (setjmp(jerr.setjmp_buffer)) { 6: /* If we get here, the JPEG code has signaled an error. 7: * We need to clean up the JPEG object, close the input file, and return. 8: */ 9: jpeg_destroy_decompress(&cinfo); 10: fclose(infile); 11: return 0; 12: }??? 上述代碼中的重點在于我們向libjpeg注冊了一個my_error_exit回調函數,當發生錯誤的時候,該回調函數將會被調用。然后我們調用 setjmp函數,設置一個返回點。這樣我們在my_error_exit回調函數處理完錯誤信息之后,就可以調用longjmp函數返回到這里,在這個 返回點進行資源的釋放(非常重要,否則將會內存泄漏)。我們再看看my_error_exit回調函數的實現:
1: /* 2: * Here's the routine that will replace the standard error_exit method: 3: */ 4: METHODDEF(void) 5: my_error_exit (j_common_ptr cinfo) 6: { 7: /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */ 8: my_error_ptr myerr = (my_error_ptr) cinfo->err; 9:? 10: /* Always display the message. */ 11: /* We could postpone this until after returning, if we chose. */ 12: (*cinfo->err->output_message) (cinfo); 13:? 14: /* Return control to the setjmp point */ 15: longjmp(myerr->setjmp_buffer, 1); 16: }??? 可以通過檢查cinfo->err->msg_code的值來判斷錯誤類型,進行相應的處理。本例中只是簡單的打印一個錯誤信息。最后調用longjmp跳轉到setjmp調用的地方。
2、初始化解碼對象
??? 要使用libjpeg解碼jpeg數據,這步是必須要做的。
1: /* Now we can initialize the JPEG decompression object. */ 2: jpeg_create_decompress(&cinfo);??? 這步之后,如果結束解碼或者出錯之后,需要調用jpeg_destroy_decompress銷毀解碼對象,否則將會內存泄漏。
3、初始化源數據
??? 在libjpeg庫中僅僅提供了文件作為輸入數據的接口,在example.c中代碼如下:
1: /* Step 2: specify data source (eg, a file) */ 2: jpeg_stdio_src(&cinfo, infile);??? 這個設計我個人覺得非常不合理,我覺得一個友好的庫,需要能夠接受各式各樣的輸入(內存數據,網絡數據等等)。比較友好的做法是提供幾種常用的輸入數據支 持(在libjpeg中如:文件輸入等)。然后還要提供一個用戶注冊自定義輸入函數(回調函數)的接口,這樣庫就可以適配現實生活中各式各樣的輸入數據類 型了。Simon也在以前的博文中寫過怎樣修改libjpeg庫,使之能夠解碼內存buffer中的jpeg數據,請參考《LibJpeg解碼內存中的Jpeg數據》。當然Simon沒有擴展libjpeg庫讓其支持用戶注冊自定義輸入函數(回調函數),有興趣的朋友可以自行實現。
4、讀取jpeg文件的頭信息
??? 這個和初始化解碼對象一樣,是必須要調用的,是約定,沒什么好說的。
1: /* Step 3: read file parameters with jpeg_read_header() */ 2: (void) jpeg_read_header(&cinfo, TRUE);5、設置解碼參數
??? 很多情況下,這步非常重要。比如設置輸出格式,設置scale(縮放)等等功能都是在這一步設置。參數設置通過修改上步得到cinfo的值來實現。這里簡單介紹一下一些常用的字段。
out_color_space:輸出的顏色格式,libjpeg定義如下:
1: /* Known color spaces. */ 2: typedef enum { 3: JCS_UNKNOWN, /* error/unspecified */ 4: JCS_GRAYSCALE, /* monochrome */ 5: JCS_RGB, /* red/green/blue */ 6: JCS_YCbCr, /* Y/Cb/Cr (also known as YUV) */ 7: JCS_CMYK, /* C/M/Y/K */ 8: JCS_YCCK, /* Y/Cb/Cr/K */ 9: #ifdef ANDROID_RGB 10: JCS_RGBA_8888, /* red/green/blue/alpha */ 11: JCS_RGB_565 /* red/green/blue in 565 format */ 12: #endif 13: } J_COLOR_SPACE;我們可以看出谷歌在Android擴展了幾種輸出格式,那么如果你需要的顏色格式輸出格式libjpeg不支持(比 如:YUYV等顏色格式),那么請參考Android對libjpeg的擴展自行修改,不用擔心復雜性,實現起來比較easy。請重點研究 jdcolor.c文件中的jinit_color_deconverter函數。
scale_num,scale_denom:因為實際的顯示設備千變萬化,我們可能需要根據實際情況對輸出數據進行一些縮放才能夠顯示。libjpeg支持對輸出數據進行縮放(scale),這個變量就是用來設置縮放的參數。目前libjpeg支持1/2,1/4,1/8三種縮放。
mem:可以指定內存管理相關的內容,比如分配和釋放內存,指定libjpeg 可以使用的最大內存。默認情況下不同的平臺下面都有一個libjpeg默認最大可用內存值,比如Android平臺上面該值為 10000000L(10M),請參考jmemxxxx.c文件中的DEFAULT_MAX_MEM,了解不同平臺的默認最大內存值。通過修改 mem->pub.max_memory_to_use的值,庫的使用者可以自定義libjpeg可以使用的最大內存值。
6、開始解碼
??? 經過前面的參數設置,我們可以開始解碼了,沒有什么好說的。
1: /* Step 5: Start decompressor */ 2: (void) jpeg_start_decompress(&cinfo);7、讀取解碼數據
1: /* Here we use the library's state variable cinfo.output_scanline as the 2: * loop counter, so that we don't have to keep track ourselves. 3: */ 4: while (cinfo.output_scanline < cinfo.output_height) { 5: /* jpeg_read_scanlines expects an array of pointers to scanlines. 6: * Here the array is only one element long, but you could ask for 7: * more than one scanline at a time if that's more convenient. 8: */ 9: (void) jpeg_read_scanlines(&cinfo, buffer, 1); 10: /* Assume put_scanline_someplace wants a pointer and sample count. */ 11: put_scanline_someplace(buffer[0], row_stride); 12: }??? 請注意雖然函數jpeg_read_scanlines可以指定一次讀多少行,但是目前該函數還是只能支持一次只讀1行。
8、結束解碼
1: /* Step 7: Finish decompression */ 2: (void) jpeg_finish_decompress(&cinfo);9、釋放解碼對象
1: /* Step 8: Release JPEG decompression object */ 2:? 3: /* This is an important step since it will release a good deal of memory. */ 4: jpeg_destroy_decompress(&cinfo);?? 至此一個jpeg數據已經解析完成了。雖然步驟不少但是對于常規的使用還是比較簡單的。
總結
??? libjpeg對于baseline的jpeg數據解碼比較好,但是解碼progressive的jpeg數據的時候,對內存的需求比較大(我測試過的 progressive的圖片曾經發現過消耗70M內存)。如果你的硬件能夠有硬件解碼jpeg的能力,請盡可能使用硬件解碼jpeg數據。
原文
轉載于:https://www.cnblogs.com/xiaoxiaoboke/archive/2012/02/13/2349763.html
總結
以上是生活随笔為你收集整理的图像解码之一——使用libjpeg解码jpeg图片的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: BBC News 2012-02-07
- 下一篇: 如何在UIAlertView中显示进度条