JPEG圖像的解壓縮操作
解壓縮操作過程
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.