libjpeg-turbo解压与压缩JPEG图像原理

宦瀚
2023-12-01

libjpeg-turbo 相对于 libjpeg 性能有很大的提升,可以参考 https://bbs.archlinux.org/viewtopic.php?id=99147

libjpeg-turbo 官方网站 http://www.libjpeg-turbo.org/Main/HomePage

注:本文部分参考其他网页资源:http://www.cnblogs.com/hzhida/archive/2012/05/30/2524989.html


1  解压缩操作过程

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;                                                  //  JSAMPARRAY 等价于 unsigned char * *  类型, 在jpeglib.h中定义

 

然后是计算每行需要的空间大小,比如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);

 

 

1.1  完整例程

       //变量定义

     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);

 

 


 

2 压缩操作过程

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);

 

 

2.1  完整例程

       //变量定义

     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);

 

 

3  错误处理

在使用默认错误处理结构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;

     }

     //以下的代码与上文解压缩操作章节中相同,不再赘述

 类似资料: