JPEG影象的解壓縮操作

wzm10455發表於2013-05-19

 

解壓縮操作過程

1.        為JPEG物件分配空間並初始化

2.        指定解壓縮資料來源

3.        獲取檔案資訊

4.        為解壓縮設定引數,包括影象大小,顏色空間

5.        開始解壓縮

6.        取出資料

7.        解壓縮完畢

8.        釋放資源

 

JPEG物件分配空間並初始化

解壓縮過程中使用的JPEG物件是一個jpeg_decompress_struct的結構體。同時還需要定義一個用於錯誤處理的結構體物件,IJG中標準的錯誤結構體是jpeg_error_mgr。


     struct jpeg_decompress_struct cinfo;

     struct jpeg_error_mgr jerr;

       然後是將錯誤處理結構物件繫結在JPEG物件上。

       cinfo.err = jpeg_std_error(&jerr);

這個標準的錯誤處理結構將使程式在出現錯誤時呼叫exit()退出程式,如果不希望使用標準的錯誤處理方式,則可以通過自定義退出函式的方法自定義錯誤處理結構,詳情見文章後面的專門章節。

初始化cinfo結構。

     jpeg_create_decompress(&cinfo);

 

指定解壓縮資料來源

利用標準C中的檔案指標傳遞要開啟的jpg檔案。

     FILE * infile;

     if ((infile = fopen("sample.jpg", "rb")) == NULL)

     {

         return 0;

     }

     jpeg_stdio_src(&cinfo, infile);

 

獲取檔案資訊

 

IJG將影象的預設資訊填充到cinfo結構中以便程式使用。

     (void) jpeg_read_header(&cinfo, TRUE);

 

此時,常見的可用資訊包括影象的寬cinfo.image_width,高cinfo.image_height,色彩空間cinfo.jpeg_color_space,顏色通道數cinfo.num_components等。

 

為解壓縮設定引數

 

在完成jpeg_read_header呼叫後,開始解壓縮之前就可以進行解壓縮引數的設定,也就是為cinfo結構的成員賦值。

比如可以設定解出來的影象的大小,也就是與原圖的比例。使用scale_num和scale_denom兩個引數,解出來的影象大小就是scale_num/scale_denom,但是IJG當前僅支援1/1, 1/2, 1/4,和1/8這幾種縮小比例。

比如要取得1/2原圖的影象,需要如下設定:

     cinfo.scale_num=1;

     cinfo.scale_denom=2;

也可以設定輸出影象的色彩空間,即cinfo.out_color_space,可以把一個原本彩色的影象由真彩色JCS_RGB變為灰度JCS_GRAYSCALE。如:

     cinfo.out_color_space=JCS_GRAYSCALE;

 

開始解壓縮

 

根據設定的解壓縮引數進行影象解壓縮操作。

     (void) jpeg_start_decompress(&cinfo);

在完成解壓縮操作後,IJG就會將解壓後的影象資訊填充至cinfo結構中。比如,輸出影象寬度cinfo.output_width,輸出影象高度cinfo.output_height,每個畫素中的顏色通道數cinfo.output_components(比如灰度為1,全綵色為3)等。

一般情況下,這些引數是在jpeg_start_decompress後才被填充到cinfo中的,如果希望在呼叫jpeg_start_decompress之前就獲得這些引數,可以通過呼叫jpeg_calc_output_dimensions()的方法來實

 

取出資料

解開的資料是按照行取出的,資料畫素按照scanline來儲存,scanline是從左到右,從上到下的順序,每個畫素對應的各顏色或灰度通道資料是依次儲存,比如一個24-bitRGB真彩色的影象中,一個scanline中的資料儲存模式是R,G,B,R,G,B,R,G,B,...,每條scanline是一個JSAMPLE型別的陣列,一般來說就是unsigned char,定義於jmorecfg.h中。

除了JSAMPLE,IJG還定義了JSAMPROW和JSAMPARRAY,分別表示一行JSAMPLE和一個2D的JSAMPLE陣列。

 

在此,我們定義一個JSAMPARRAY型別的緩衝區變數來存放影象資料。

     JSAMPARRAY buffer;

然後是計算每行需要的空間大小,比如RGB影象就是寬度×3,灰度圖就是寬度×1

     row_stride = cinfo.output_width * cinfo.output_components;

為緩衝區分配空間,這裡使用了IJG的記憶體管理器來完成分配。

JPOOL_IMAGE表示分配的記憶體空間將在呼叫jpeg_finish_compress,jpeg_finish_decompress,jpeg_abort後被釋放,而如果此引數改為JPOOL_PERMANENT則表示記憶體將一直到JPEG物件被銷燬時才被釋放。

row_stride如上所說,是每行資料的實際大小。

最後一個引數是要分配多少行資料。此處只分配了一行。

     buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);

 

output_scanline表示當前已經讀取的行數,如此即可依次讀出影象的所有資料,並填充到緩衝區中,引數1表示的是每次讀取的行數。

     while (cinfo.output_scanline < cinfo.output_height)

     {

         (void) jpeg_read_scanlines(&cinfo, buffer, 1);

         //do something

     }

解壓縮完畢

     (void) jpeg_finish_decompress(&cinfo);

釋放資源

     jpeg_destroy_decompress(&cinfo);

     fclose(infile);

退出程式

如果不再需要JPEG物件,則使用

     jpeg_destroy_decompress(&cinfo);

     jpeg_destroy(&cinfo);

 

而如果還希望繼續使用JPEG物件,則可使用

     jpeg_abort_decompress(&cinfo);

     jpeg_abort(&cinfo);

完整例程

       //變數定義

     struct jpeg_decompress_struct cinfo;

     struct jpeg_error_mgr jerr;

     FILE * infile;

     JSAMPARRAY buffer;

     int row_stride;        

     //繫結標準錯誤處理結構

     cinfo.err = jpeg_std_error(&jerr);  

     //初始化JPEG物件

     jpeg_create_decompress(&cinfo);

     //指定影象檔案

     if ((infile = fopen("sample.jpg", "rb")) == NULL)

     {

         return;

     }

     jpeg_stdio_src(&cinfo, infile);

     //讀取影象資訊

     (void) jpeg_read_header(&cinfo, TRUE);

     //設定解壓縮引數,此處我們將影象長寬縮小為原圖的1/2

     cinfo.scale_num=1;

     cinfo.scale_denom=2;

     //開始解壓縮影象

     (void) jpeg_start_decompress(&cinfo);

 

     //本程式功能是應用GDI+在客戶區繪製影象

     CClientDC dc(this);

     Bitmap bm( cinfo.output_width , cinfo.output_height); 

     Graphics graphics(dc.GetSafeHdc());

     Graphics gdc(&bm);

     //分配緩衝區空間

     row_stride = cinfo.output_width * cinfo.output_components;

     buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);

     //讀取資料

     while (cinfo.output_scanline < cinfo.output_height)

     {

         (void) jpeg_read_scanlines(&cinfo, buffer, 1);

         //output_scanline是從1開始,所以需要減1

         int line=cinfo.output_scanline-1;

         for(int i=0;i<cinfo.output_width;i++)

         {

     //繪製點陣圖,本例中假設讀取的sample.jpg影象為RGB真彩色影象

     //因此,實際上cinfo.output_components就等於3,灰度圖則需另作處理

     bm.SetPixel(i,line,Color(255,(BYTE)buffer[0][i*3],(BYTE)buffer[0][i*3+1],(BYTE)buffer[0][i*3+2]));

         }

     }

     //結束解壓縮操作

     (void) jpeg_finish_decompress(&cinfo);

     //釋放資源

     jpeg_destroy_decompress(&cinfo);

     fclose(infile);

     //在客戶區繪製點陣圖

     graphics.DrawImage(&bm,0,0);

JPEG影象的壓縮操作

壓縮操作過程

1.         JPEG物件分配空間並初始化

2.         指定影象輸出目標

3.         為壓縮設定引數,包括影象大小,顏色空間

4.        開始壓縮

5.        寫入資料

6.        壓縮完畢

7.        釋放資源

JPEG物件分配空間並初始化

壓縮過程中使用的JPEG物件是一個jpeg_compress_struct的結構體。同時還需要定義一個用於錯誤處理的結構體物件,IJG中標準的錯誤結構體是jpeg_error_mgr。

 

     struct jpeg_compress_struct cinfo;

     struct jpeg_error_mgr jerr;

      

然後是將錯誤處理結構物件繫結在JPEG物件上。

       cinfo.err = jpeg_std_error(&jerr);

這個標準的錯誤處理結構將使程式在出現錯誤時呼叫exit()退出程式,如果不希望使用標準的錯誤處理方式,則可以通過自定義退出函式的方法自定義錯誤處理結構,詳情見文章後面的專門章節。

 

初始化cinfo結構。

     jpeg_create_compress(&cinfo);

 

指定影象輸出目標

 

利用標準C中的檔案指標傳遞要輸出的jpg檔案。

     FILE * outfile;   

     if ((outfile = fopen(filename, "wb")) == NULL)

     {

         return 0;

     }

     jpeg_stdio_dest(&cinfo, outfile);

 

為壓縮設定引數

 

在開始壓縮資料之前需要為壓縮指定幾個引數和預設引數。

設定預設引數之前需要指定的幾個引數是:影象寬度cinfo.image_width,影象高度cinfo.image_height,影象的顏色通道數cinfo.input_components(比如RGB影象為3,灰度圖為1),影象顏色空間cinfo.in_color_space(比如真彩色JCS_RGB,灰度圖JCS_GRAYSCALE)。

如:

     cinfo.image_width = 800;

     cinfo.image_height = 600;

     cinfo.input_components = 3;

     cinfo.in_color_space = JCS_RGB;

然後是設定預設設定

     jpeg_set_defaults(&cinfo);

注意此處,在set default之前,必須設定in_color_space,因為某些預設引數的設定需要正確的color space值。

在此之後還可以對其他的一些引數進行設定。具體有哪些引數可以查詢libjpeg.doc文件。

比如最常用的一個引數就是壓縮比。

jpeg_set_quality(&cinfo, quality, TRUE);

quality是個0~100之間的整數,表示壓縮比率。

 

開始壓縮

 

根據設定的壓縮引數進行影象壓縮操作。

     jpeg_start_compress(&cinfo, TRUE);

開始壓縮過程後就不可以修改cinfo物件引數。

 

 

寫入資料

     row_stride = image_width * 3;    //假設用到的圖示RGB真彩色三通道

同上文介紹的解壓縮操作中介紹的,要寫入的資料是按照行寫入的,資料畫素按照scanline來儲存,與讀取資料的不同是使用jpeg_write_scanlines。

類似於解壓縮操作中的cinfo.output_scanline < cinfo.output_height機制,壓縮過程使用的cinfo.next_scanline < cinfo.image_height來判斷是否完成寫入資料。

在此,假設image_buffer是個JSAMPARRAY型別變數,其中儲存的是要輸出的影象資料,比如可以是用上文中的解壓縮操作從某JPEG檔案中獲得的資料。

     JSAMPROW row_pointer;

     while (cinfo.next_scanline < cinfo.image_height)

     {

         //找到影象中的某一行,寫入目標檔案

         row_pointer = image_buffer[cinfo.next_scanline];

         (void) jpeg_write_scanlines(&cinfo, &row_pointer, 1);

     }

 

壓縮完畢

 

     jpeg_finish_compress(&cinfo);

 

釋放資源

 

     fclose(outfile);

     jpeg_destroy_compress(&cinfo);

 

退出程式

 

如果不再需要JPEG物件,則使用

     jpeg_destroy_compress(&cinfo);

     jpeg_destroy(&cinfo);

 

而如果還希望繼續使用JPEG物件,則可使用

     jpeg_abort_compress(&cinfo);

     jpeg_abort(&cinfo);

完整例程

       //變數定義

     struct jpeg_compress_struct cinfo;

     struct jpeg_error_mgr jerr;

     FILE * outfile;       

     JSAMPROW row_pointer; 

     int row_stride;       

     //繫結標準錯誤處理結構

     cinfo.err = jpeg_std_error(&jerr);

     //初始化JPEG物件

     jpeg_create_compress(&cinfo);

     //指定目標影象檔案

     if ((outfile = fopen("dest.jpg", "wb")) == NULL)

     {

         return;

     }

     jpeg_stdio_dest(&cinfo, outfile);

     //設定壓縮引數

     cinfo.image_width = image_width;

     cinfo.image_height = image_height;

     cinfo.input_components = 3;

     cinfo.in_color_space = JCS_RGB;

     jpeg_set_defaults(&cinfo);

     //此處設壓縮比為90%

     jpeg_set_quality(&cinfo, 90, TRUE);

     //開始壓縮

     jpeg_start_compress(&cinfo, TRUE);

     //假設使用的是RGB影象

     row_stride = image_width * 3;   

     //寫入資料

     while (cinfo.next_scanline < cinfo.image_height)

     {

         row_pointer = image_buffer[cinfo.next_scanline];

         (void) jpeg_write_scanlines(&cinfo, &row_pointer, 1);

     }

     //壓縮完畢

     jpeg_finish_compress(&cinfo);

     //釋放資源

     fclose(outfile);

     jpeg_destroy_compress(&cinfo);

 

 

錯誤處理

在使用預設錯誤處理結構jpeg_error_mgr的情況下,程式在遇到錯誤後將呼叫exit直接退出程式,使用者如果不希望使用這種直接退出的方式處理錯誤的話就需要自定義錯誤處理結構。

依照example.c中的例子,IJG推薦使用C語言的setjmp和longjmp機制來重寫錯誤處理結構。

首先,需要定義一個包含標準錯誤處理結構型別變數的自定義結構。

同時,程式將需要引入標頭檔案setjmp.h。

#include <setjmp.h>

     struct my_error_mgr

     {

         struct jpeg_error_mgr pub;

         jmp_buf setjmp_buffer;

     };

     typedef struct my_error_mgr * my_error_ptr;

 

以及一個錯誤處理函式。在出現錯誤時程式將跳轉到本函式中,而本函式將跳轉到setjmp設定的程式位置。

 

     METHODDEF(void) my_error_exit (j_common_ptr cinfo)

     {

         my_error_ptr myerr = (my_error_ptr) cinfo->err;

         (*cinfo->err->output_message) (cinfo);

         longjmp(myerr->setjmp_buffer, 1);

     }

 

以解壓縮過程為例,原程式將被修改為如下形式。

 

     struct jpeg_decompress_struct cinfo;

     struct jpeg_error_mgr jerr;

     //此處做了修改    

     //struct jpeg_error_mgr jerr;   

     struct my_error_mgr jerr;

     ////////////////////////////////////////////////////

     FILE * infile;

     JSAMPARRAY buffer;

     int row_stride;       

     //此處做了修改    

     //cinfo.err = jpeg_std_error(&jerr);

     cinfo.err = jpeg_std_error(&jerr.pub);

     jerr.pub.error_exit = my_error_exit;

     if (setjmp(jerr.setjmp_buffer))

     {

     //在正常情況下,setjmp將返回0,而如果程式出現錯誤,即呼叫my_error_exit

     //然後程式將再次跳轉於此,同時setjmp將返回在my_error_exit中由longjmp第二個引數設定的值1

     //並執行以下程式碼

         jpeg_destroy_decompress(&cinfo);

         fclose(infile);

         return;

     }

     ////////////////////////////////////////////////////

     jpeg_create_decompress(&cinfo);

     if ((infile = fopen("sample.jpg", "rb")) == NULL)

     {

         return;

     }

     //以下的程式碼與上文解壓縮操作章節中相同,不再贅述

 

THE END.

 

相關文章