【原创】继续我的项目研究,现在采用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_row到first_undef_row,最内层的范围是blocksperrow。就是最多访问rows_in_array行,即最多访问图片高度数的行数,但是内存中可能无法一次存下整张图片,因此还有内存中存储的行数,即rows_in_mem,或者是从cur_start_row到first_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系数也都全部访问结束。