首页 > 代码库 > 【原创】Libjpeg 库使用心得(一) JPEG图像DCT系数的获取和访问

【原创】Libjpeg 库使用心得(一) JPEG图像DCT系数的获取和访问

【原创】继续我的项目研究,现在采用Libjpeg库函数来进行处理,看了库函数之后发现C语言被这些人用的太牛了,五体投地啊。。。废话不多说,下面就进入正题。

 

Libjpeg库在网上下载还是挺方便的,这里就不附上来了,当然如果找不到的话,也可以发邮件给我,我的邮箱是gungnir2011@gmail.com。

打开库函数会看到有很多很多的文件,里面有两个解决方案,一个是apps,一个是jpeg。apps里面有5个工程,分别是用于压缩,解压,转换,读取JPEG中COM段,写入JPEG中COM段,COM段可以看做是JPEG中的注释。要想获取到DCT系数,比较好用的工程是压缩,解压和转换,要是想直接可以操作DCT系数的话还是使用转换的那个工程比较好,即jpegtran。

这里就需要提到库中对于DCT系数操作的函数,主要用上的就是jpeg_read_coefficients()jpeg_write_coefficients()函数。这是两个非常好用的函数,从名字就可以看出,分别是读取DCT系数和写入DCT系数。因为jpegtran工程中可以提供无损转换,因此就用上了直接对于DCT系数操作的函数。这两个函数的源码就不贴了,在jpeg的解决方案中全方案查找一下就能找到源码,这里也不需要关注他内部如何实现的,只要知道需要什么参数,返回什么就可以了。还有一点要补充的是,读取出的DCT系数是量化后的DCT系数。

1 jvirt_barray_ptr *  jpeg_read_coefficients (j_decompress_ptr cinfo);
2 
3 void  jpeg_write_coefficients (j_compress_ptr cinfo, jvirt_barray_ptr * coef_arrays);

上面jvirt_barray_ptr *就是读取DCT系数所返回的值的类型,我们再来看看这个类型是怎么定义的:

1 typedef struct jvirt_barray_control * jvirt_barray_ptr;

可以看出是指向一个结构体的指针,再来看看结构体是什么样子:

 1 struct jvirt_barray_control {
 2   JBLOCKARRAY mem_buffer;    /* => the in-memory buffer */   指向内存中的内容的指针
 3   JDIMENSION rows_in_array;    /* total virtual array height */   总的虚拟数组的高度,即图片的高度
 4   JDIMENSION blocksperrow;    /* width of array (and of memory buffer) */  数组的宽度,也就是图片的宽度
 5   JDIMENSION maxaccess;        /* max rows accessed by access_virt_barray */  access_virt_barry所能访问到的最多的行数,这里不太明白是用来做什么的
 6   JDIMENSION rows_in_mem;    /* height of memory buffer */       在内存中内容的高度
 7   JDIMENSION rowsperchunk;    /* allocation chunk size in mem_buffer */  这里不大清楚,我也没有用上
 8   JDIMENSION cur_start_row;    /* first logical row # in the buffer */   内存中内容的第一行的行号,一般都为0
 9   JDIMENSION first_undef_row;    /* row # of first uninitialized row */    第一个没有初始化的行的行号,可以看作是含数据的最后一行的后一行
10   boolean pre_zero;        /* pre-zero mode requested? */  预设0模式的判断
11   boolean dirty;        /* do current buffer contents need written? */  现在的内容是否需要写入
12   boolean b_s_open;        /* is backing-store data valid? */   不大清楚
13   jvirt_barray_ptr next;    /* link to next virtual barray control block */  下一个结构体的指针
14   backing_store_info b_s_info;    /* System-dependent control info */    不清楚
15 };

这下就明朗了,这里作者写的很好,每个都给了注释说明作用,后面的中文是我添加以便更好理解的。注释中红色突出的是需要用上的内容,mem_buffer是用来找到内存中的内容的指针,我们也是通过它来访问DCT系数的,这个指针类型会在下面介绍访问的时候再细说,这里先略过,next指针同样略过。其他的内容主要是用来控制循环的范围的,最外层的循环范围可以是rows_in_array,再次内层的范围是rows_in_mem或者从cur_start_rowfirst_undef_row,最内层的范围是blocksperrow。就是最多访问rows_in_array行,即最多访问图片高度数的行数,但是内存中可能无法一次存下整张图片,因此还有内存中存储的行数,即rows_in_mem,或者是从cur_start_rowfirst_undef_row,这两个范围都可以表示内存中存储的行数,而每一行又有blocksperrow个block,一个block是一个8*8的矩阵。每个block中存储的就是量化后的DCT系数,也就达到了获取DCT系数的目的。

 

 

==================================================分割线=====================================================

上面说完了DCT系数的获取,都已经能得到一个block了,就可以直接访问了,为什么还要单独拿出来说一下DCT系数的访问。只能说是因为那些库函数的作者太牛了,指针用的出神入化,当然更多的原因应该是我太菜了。。。求轻喷。。。在理解了他指针的使用之后其实挺简单的,下面就来介绍介绍:

还记得上面提到的JBLOCKARRAY指针类型么,就是这个,我们就从这里开始,这是指向内存中内容的指针,也是我们访问DCT系数的头,我们先来看看它以及其他相关指针类型的定义:

1 typedef JCOEF JBLOCK[DCTSIZE2];    /* one block of coefficients */     一个系数的block
2 typedef JBLOCK FAR *JBLOCKROW;    /* pointer to one row of coefficient blocks */ 指向一行系数block的指针
3 typedef JBLOCKROW *JBLOCKARRAY;        /* a 2-D array of coefficient blocks */    一个系数block的2维数组指针

JCOEF就是short类型,这是头文件里typedef的,就不贴出了。可以看出,JBLOCK其实是一个大小为DCTSIZE2的数组,DCTSIZE2就是64,同样是头文件里typedef的,而JBLOCKROW是一个指向JBLOCK型的指针,我们的头JBLOCKARRAY是指向JBLOCKROW型的指针,也就是指向JBLOCK型的一个二级指针(不知道有没有这个说法,是我自创的~)。

作者为什么要用这么麻烦的指针型呢,别急,我们再来看看注释。JBLOCK是一个系数的block,这好理解,一个block有64个系数,JBLOCK是大小为64的short型数组,很明显,每个元素就是一个系数。JBLOCKROW是指向一行系数block的指针,这什么意思呢?上面说到的那个结构体中很多成员变量都与行有关,因为库函数中对于图片是一行一行的处理的,我们可以把图片看成由许多行组成的一个2维数组,每个数组元素是一个block,这样就好理解多了,JBLOCKROW就是指向这个2维数组一行的一个指针,那么JBLOCKARRAY也容易理解了,就是一个指向这个2维数组的指针。

具体的结构关系请看下图(图片也是本人手绘的~):

也就是说在内存中的buffer其实保存的是一个个JBLOCKROW型的指针,每个JBLOCKROW型指针都指向了一行系数,每一行最多能访问图片的宽度,即blocksperrow个block,要不然再往后访问的数据就不对了,同样也只能访问图片的高度数那么多个的JBLOCKROW,否则也会访问出错!!

 

主要结构关系都已经说明白了,下面就直接贴上我的代码,也可以更直观的看到是如何访问的,加深理解:

 1 void func(jvirt_barray_ptr* coef_arrays)                       
 2 {
 3     jvirt_barray_ptr temp_src_coef_arrays; 5     temp_src_coef_arrays = *coef_arrays;
 6     while (temp_src_coef_arrays)
 7     {
 8         JBLOCKARRAY jbarray = (temp_src_coef_arrays)->mem_buffer;
 9         JBLOCKROW jbrow = NULL;
10         for (int i = 0; i < (temp_src_coef_arrays)->rows_in_mem; i++)
11         {
12             jbrow = *jbarray;
13             for (int j = 0; j < (temp_src_coef_arrays)->blocksperrow; j++)
14             {
15                 for (int k = 1; k <64; k++)
16                 {
17                     if ((*(jbrow + j))[k] != 0)
18                     {
19                        ......20                     }                        
21                 }
22             }
23             jbarray++;
24         }
25         temp_src_coef_arrays = (temp_src_coef_arrays)->next;
26     }28 }

函数中的参数就是由jpeg_read_coefficients()函数返回的值,通过它找到访问的头,在访问完一个block之后,JBLOCKROW型指针向后移动访问下一个block,直到一行block访问结束,跳出一行的循环,通过JBLOCKARRAY取到下一个JBLOCKROW型指针,继续循环一行的访问,直到所有的行都访问结束,DCT系数也都全部访问结束。