Who are you?

JPEG实例简析

主要介绍如何利用IJG code作为一个子例函数库来读写JPEG图像文件,在阅读该段代码时建议结合文档libjpeg.txt

source code:example.c

reference:USING THE IJG JPEG LIBRARY

Part A :子程序说明

- 压缩部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#include <stdio.h>
/* 用于JPEG函数库的头文件,也许还需要 "jerror.h" */
#include "jpeglib.h"
/* <setjmp.h>用于可选的错误回复机制,这将在该例子的第二部分介绍 */
#include <setjmp.h>
/**********************************************/
/* JPEG压缩示例 */
/**********************************************/
/* 这部分例子将会展示如何将数据读入到JPEG压缩器中 */
/*
* 图像数据格式:
* 标准的输入图像数据格式是一个矩形像素阵列,其中每
* 一个像素值有着相同数量的组成(颜色通道),每一
* 个像素行是一个JSAMPLEs的数组(通常是无符号字符型)
* 对于颜色数据而言,每一个像素的颜色值在行中必须是
* 相邻的,例如对于24位的RGB数据,其排列为R,G,B,
* R,G,B,...除了JSAMPLE,IJG还定义了JSAMPROW和
* JSAMPARRAY,分别表示一行JSAMPLE和一个2D的JSAMPLE数组。
*
* 对于本例:图像数据在内存中
* 我们假定数据结构与我们存储在内存中的图形数据的方
* 式相同,因此我们只需要传递指向图像内存的指针即可。
* 特别的,我们假定图像是RGB颜色的,同时其按以下方式
* 描述:
*/
extern JSAMPLE * image_buffer; /* 指向图像数据的指针 */
extern int imagine_height; /* 图像的行数 */
extern int imagine_width; /* 图像的列数 */
/*
* JPEG 压缩的示例函数。我们假定目标文件名和压缩的比
* 例系数已经传递进去
*/
GLOBAL(void)
write_JPEG_file(char *filename, int quality)
{
/* 这个结构体是JPEG库所分配的,它包含了JPEG的压
* 缩参数以及指向指向工作空间的指针,这个结构体可
* 以有多个,进而表示有多个压缩/解压缩要处理,我
* 们任何一个结构体(以及相应所需要处理的数据)为
* "JPEG object"
*/
struct jpeg_compress_struct cinfo;
/* 这个结构体表示JPEG错误处理。它通常是单独声明的
* 因为程序经常需要提供不同的错误处理方式。在这里
* 我们仅仅采用最简单的处理方式,即标准处理方式,
* 当压缩失败时,它会在标准错误输出设备中打印错误
* 信息并调用退出函数。
* 注意:这个结构体必须与主要的JPEG参数同时存在,
* 从而避免悬空指针问题 */
struct jpeg_error_mgr jerr;
/* More stuff */
FILE * outfile; /* 目标文件 */
JSAMPROW row_pointer[1]; /* 指向第s行 JSAMPLES数据的指针 */
int row_stride; /* 图像数据的行数 */
/* Step 1: 分配并初始化JPEG压缩对象 */
/* 我们首先必须建立一个错误处理方式,以防初始化步
* 骤失败。(一般是不可能的,但是如果内存溢出还是
* 出错的)
* 绑定标准错误处理结构 */
cinfo.err=jpeg_std_error(&jerr);
/* 现在我们可以初始化JPEG压缩对象 */
jpeg_create_compress(&cinfo);
/* Step 2: 指定压缩后数据目的地(例如某个文件)*/
/* 注意:Step 2和Step 3次序可任意 */
/* 这里我们使用函数库工的代码将压缩后的数据传递到
* 标准输出流中。同时我们也可以自定义输出方式。
* 非常重要:如果所使用的机器要求顺序写二进制文件
* 我们需要在fopen()中使用选项"b" */
if((outfile = fopen(filename,"wb"))==NULL){
fprintf(stderr,"can't open %s\n",filename);
exit(1);
}
/* 关联JPEG对象与输出文件 */
jpeg_stdio_dest(&cinfo,outfile);
/* Step 3: 设置压缩参数 */
/* 首先对输入的图像数据进行描述。
* cinfo中的四个变量必须赋值: */
cinfo.image_width=imagine_width; /* 图像的高度和宽度,以像素点记 */
cinfo.image_height=imagine_height;
cinfo.input_components=3; /* 每一个像素点的图像成分,RGB为3,灰度图为1 */
cinfo.in_color_space=JCS_RGB; /* 输入图像的颜色空间,JCS_RGB表示真彩图,灰度图为JCS_GRAYSCALE */
/* 现在使用库函数的子例程序设置默认压缩参数。我们
* 在调用它之前至少需要设置cinfo.in_color_space,
* 因为某些默认设置依赖于源的颜色空间。 */
jpeg_set_defaults(&cinfo);
/* 现在我们可以按照自己的意愿设置任意一个非默认参数。
* 在这我们只是说明如何设置压缩比(量化表)
*
* 基线JPEG数值的限制,quality是0~100之间的整数,表示压缩比率 */
jpeg_set_quality(&cinfo, quality, TRUE);
/
/* Step 4: 开始压缩 */
/* 参数TRUE确保我们将会写入一个完全可以互换的JPEG文
* 件。 */
jpeg_start_compress(&cinfo,TRUE);
/* 开始压缩后就不可以修改cinfo对象的参数 */
/* Step 5: while(scan lines remain to be written)
* jpeg_write_scanlines(...); */
/* 在这里我们采用库的一个状态变量作为循环参数。为了
* 简单起见,我们一次调用只传递一个scanline,我们也
* 可以一次传递多行。 */
row_stride=imagine_width * 3; /* 在图像内存中JSAMPLEs的每行数据,RGB */
while(cinfo.next_scanline < cinfo.image_height){
/* jpeg_write_scanlines 需要有一个指向scanlines指
* 针的数组。在这里,数组只有一个元素长度,但是我们
* 可以一次传入多条scanline,如果这样更方便的话。*/
row_pointer[0]=&image_buffer[cinfo.next_scanline*row_stride];
(void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
}
/* Step 6: 完成压缩 */
jpeg_finish_compress(&cinfo);
/* 在压缩完毕后,关闭文件 */
fclose(outfile);
/* Step 7: 释放JPEG压缩对象 */
/* 这一步十分重要,因为它会释放大量的内存 */
jpeg_destroy_compress(&cinfo);
/* 上述函数将会把JPEG压缩对象释放掉,如果
* 需要重复使用该对象,可以使用jpeg_abort()
* 来将JPEG对象置为空闲状态 */
}
/*
* SOME FINE POINTS:
* 1、jpeg_write_scanlines的返回值是写入的scanline的行号
* 2、当压缩需要全部的图像内容时,程序会产生一些临时文件,
* 我们需要确保当程序终止时这些文件会被删除,详情libjpeg.txt。
* 3、从兼容性考虑,scanlines必须是从上到下的,

- 解压缩部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
#include <stdio.h>
/* 用于JPEG函数库的头文件,也许还需要 "jerror.h" */
#include "jpeglib.h"
/* <setjmp.h>用于可选的错误回复机制,这将在该例子的第二部分介绍 */
#include <setjmp.h>
/**********************************************/
/* JPEG解压缩示例 */
/**********************************************/
/* 这部分例子将会说明如何从JPEG解压缩器中读入数据,
* 与之前相比,这部分内容会更细分一些,具体体现在:
* (a) 如何修改JPEG库中标准错误报告的行为;
* (b) 如何使用函数库的内存管理分配工作区间;
*
* 为了使这部分的程序与前面的有所不同,我们假定不将所
* 有的图像数据都传送到内存中,而是将其一行一行的发送
* 到其他地方。我们需要一个one-scanline-high的JSAMPLE
* 类型的数组作为一个工作区缓存,并且将利用JPEG的内存
* 管理器为我们分配空间。这种方法实际上是相当有效的,
* 因为我们不必特地去释放缓存空间:它会自动释放当JPEG
* 对象被清理后。 */
/*
* Error Handling
* JPEG函数库的标准错误处理(jerror.c)被分为几个不同
* 的方法,它们可以单独被重载。这将使我们可以调整相应的
* 行为而不用重复一大段代码。
*
* 这里我们将展示如何重载"error_exit"方法,因而当有关键
* 错误产生时,程序控制将会返回到函数的调用着,而不是像
* 标准error_exit那样,调用exit()。
*
* 我们使用C的 setjmp/longjmp 工具来返回控制。这就意味着
* 调用了JPEG库的子例程序必须首先执行一次setjmp()调用进而
* 建立返回点。我们想让error_exit替换为调用一次longjmp(),
* 但是我们需要使setjmp缓存对error_exit子例程序是可获取的。
* 因此,我们制定了标准JPEG错误处理对象的私有扩展。(在C++
* 中,这个称之为子类)
*
* From:http://www.cnblogs.com/hzhida/archive/2012/05/30/2524989.html
* 在使用默认错误处理结构jpeg_error_mgr的情况下,程序在遇到错误后将调用exit直接退出程序,用户如果不
* 希望使用这种直接退出的方式处理错误的话就需要自定义错误处理结构。依照example.c中的例子,IJG推荐使
* 用C语言的setjmp和longjmp机制来重写错误处理结构。需要头文件setjmp.h
*
* 扩展的错误处理结构体如下:
* 首先,需要定义一个包含标准错误处理结构类型变量的自定义结构。
*/
struct my_error_mgr{
struct jpeg_error_mgr pub; /* “公共”部分 */
jmp_buf setjmp_buffer; /* 用于返回给调用的对象 */
};
typedef struct my_error_mgr * my_error_ptr;
/* 这是将要替代标准error_exit方法的子例程序
* 在出现错误时程序将跳转到本函数中,而本函
* 数将跳转到setjmp设定的程序位置。*/
METHODDEF(void) my_error_exit(j_common_ptr cinfo){
/* cinfo->err实际指向一个my_error_mgr结构体 */
my_error_ptr myerr=(my_error_ptr)cinfo->err;
/* 总是显示信息 */
(*cinfo->err->output_message)(cinfo);
/* 返回控制到setjmp point */
longjmp(myerr->setjmp_buffer,1);
}
/* 示例子程序用于JPEG解压缩。我们假定源文件名字已经传递进去,
* 并且成功返回1,出错返回0 */
GLOBAL(int)
read_JPEG_file(char * filename){
/* 这个结构体包含了JPEG解压缩的参数以及指向工作空间的指针 */
struct jpeg_decompress_struct cinfo;
/* 我们将使用我们扩展后的JPEG错误处理方法。注意到这个结构
* 体必须与主要的JPEG参数同时存在,从而避免悬空指针问题. */
struct my_error_mgr jerr;
/* More stuff */
FILE *infile; /* JPEG源文件 */
JSAMPARRAY buffer; /* 输出的行缓存内容 */
int row_stride; /* 在输出缓存中的物理行宽 */
/* 在这个例子中,我们在做任何其他操作钱将先打开输入文件,
* 进而下面的setjmp()的error recovery可以假定文件是打开
* 的。
* 非常重要:如果所使用的机器要求顺序写二进制文件我们需要
* 在fopen()中使用选项"b" */
if((infile=fopen(filename,"rb"))==NULL){
fprintf(stderr,"can't open %s\n",filename);
return 0;
}
/* Step 1: 分配并初始化JPEG解压缩对象 */
/* 我们建立了一般的JPEG错误子例程序,然后重载了error_exit */
/* 错误处理修改1 */
cinfo.err=jpeg_std_error(&jerr.pub);
jerr.pub.error_exit=my_error_exit;
/* 为了my_error_exit使用建立setjmp的返回上下文 */
if(setjmp(jerr.setjmp_buffer)){
/* 如果程序运行到了这,说明JPEG代码中出现了一个错误,
* 即调用my_error_exit,然后程序将再次跳转于此,同时
* setjmp将返回在my_error_exit中由longjmp第二个参数
* 设定的值1
* 我们需要释放JPEG对象,关闭输入文件,并且返回 */
jpeg_destroy_decompress(&cinfo);
fclose(infile);
return 0;
}
/* 现在我们可以初始化JPEG解压缩对象 */
jpeg_create_decompress(&cinfo);
/* Step 2: 指定数据源(例如一个文件) */
jpeg_stdio_src(&cinfo, infile);
/* Step 3:使用jpeg_read_header读入文件参数 */
(void)jpeg_read_header(&cinfo,TRUE);
/* IJG将图像的缺省信息填充到cinfo结构中以便程序使用
*
*我们可以忽略jpeg_read_header函数的返回值,因为
* (a)对于标准输入输出数据源,暂停是不可能的,并且
* (b)我们传递TRUE进而拒绝只有表格的JPEG文件作为
* 一个错误。
* 更多信息可以参见libjpeg.txt。 */
/* Step 4: 设置用于解压缩的参数 */
/* 比如可以设定解出来的图像的大小,也就是与原图的比
* 例。使用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;
/* 也可以设定图像的彩色空间,可以把一个原本彩色的图像
* 由真彩色JCS_RGB变为灰度JCS_GRAYSCALE */
cinfo.out_color_space=JCS_GRAYSCALE;
/* 如果我们使用默认参数,那么在这我们不必做任何事。 */
/* Step 5: 开始解压缩 */
(void)jpeg_start_decompress(&cinfo);
/* 我们可以忽略函数返回值。
*
* 在完成解压缩操作后,IJG就会将解压后的图像信息填充
* 至cinfo结构中。比如,输出图像宽度cinfo.output_width,
* 输出图像高度cinfo.output_height,每个像素中的颜色通
* 道数cinfo.output_components(比如灰度为1,全彩色为3)等
*
* 在这里我们可能需要做一些自己的设置在读入数据之前,在
* jpeg_start_compress()之后,我们得到了正确的被放大后
* 的输出图像维度,以及输出的颜色映射表(如果我们要颜色
* 量化的话)。
* 在这个例子中,我们需要创建一个正确大小的输出文件缓存 */
/* 计算每行需要的空间大小 */
row_stride=cinfo.output_width*cinfo.output_components;
/* Make a one-row-high sample array that will go away when done with image */
/* 为缓冲区分配空间,这里使用了IJG的内存管理器来完成分配。
*
* JPOOL_IMAGE表示分配的内存空间将在调用jpeg_finish_compress,
* jpeg_finish_decompress,jpeg_abort后被释放,而如果此参数改
* 为JPOOL_PERMANENT则表示内存将一直到JPEG对象被销毁时才被释放。
* 最后一个参数是要分配多少行数据。此处只分配了一行。 */
buffer=(*cinfo.mem->alloc_sarray)((j_commom_ptr)&cinfo, JPOOL_IMAGE,row_stride,1);
/* Step 6:while (scan lines remain to be read)
* jpeg_read_scanlines(...); */
/* 在这里我们采用库的一个状态变量作为循环参数。 */
while(cinfo.output_scanline<cinfo.output_height){
/* jpeg_read_scanlines 需要有一个指向scanlines指
* 针的数组。在这里,数组只有一个元素长度,但是我们
* 可以一次传入多条scanline,如果这样更方便的话。
* output_scanline表示当前已经读取的行数,如此即可
* 依次读出图像的所有数据,并填充到缓冲区中,参数1表
* 示的是每次读取的行数。 */
(void)jpeg_read_scanlines(&cinfo,buffer,1);
/* 假定put_scanline_someplace需要一个指针和样点数 */
put_scanline_someplace(buffer[0],row_stride);
}
/* Step 7: 完成解压缩 */
(void)jpeg_finish_decompress(&cinfo);
/* 我们可以忽略返回值 */
/* Step 8: 释放JPEG解压缩对象 */
/* 这一步十分重要,因为它会释放大量的内存 */
jpeg_destroy_decompress(&cinfo);
/* 在完成解压缩后,我们可以关闭输入文件。
* 在这里我们推迟关闭文件直到没有更多的JPEG错误出现,
* 以便简化上述的setjmp错误逻辑。 */
fclose(infile);
/* 在这时,如果我们想检查是否有任何损坏数据警告产生,
* 我们可以通过测试jerr.pub.num_warning 是否位零来
* 确认 。 */
return 1;
}
/*
* SOME FINE POINTS:
* 1、在上述代码中,我们可以忽略jpeg_read_scanlines的返回值;
* 2、我们在jpeg_start_compress()后做了一点弊,直接调用了alloc_array(),一般我们应该先确认
* 输出图像的数据大小,否则会发生内存溢出错误;
* 3、scanlines的顺序是从上到下的;
* 4、和压缩操作一样,解压缩的一些操作也会产生临时文件,当程序中止时我们需要清除这些临时文件。

Part B:必备数据类型及函数说明

数据类型

  • JSAMPLE:一种自定义数据类型,通常是”unsigned char”,除非修改了头文件”jmorecfg.h”。详情请参见头文件jmorecfg.h

  • JSAMPROW:同上,表示一行JSAMPLE类型的数组

  • JSAMPARRAY:同上,表示一个2DJSAMPLE类型的数组

  • struct jpeg_compress_struct:jpeg压缩对象结构体

  • struct jpeg_decompress_struct:jpeg解压缩对象结构体

  • struct jpeg_error_mgr:jpeg错误处理结构体

函数定义

  • jpeg_std_error(&jerr):建立错误处理方式
  • jpeg_create_compress(&cinfo):初始化JPEG压缩对象
  • jpeg_stdio_dest(&cinfo, outfile):关联jpeg对象与输出文件
  • jpeg_set_default(&cinfo):设置默认压缩参数
  • jpeg_set_quality(&cinfo, quality, TRUE):设置图片的压缩率,数值越大,压缩越小
  • jpeg_start_compress(&cinfo, TRUE):开始压缩
  • jpeg_write_scanlines(&cinfo, row_pointer, 1):将row_pointer指向的1行scanline写入文件中
  • jpeg_finish_compress(&cinfo):完成压缩
  • jpeg_destroy_compress(&cinfo):释放JPEG压缩对象
  • setjmp:具体参阅原文档
  • longjmp:具体参阅原文档
  • jpeg_create_decompress(&cinfo):初始化JPEG解压缩对象
  • jpeg_stdio_src(&cinfo, infile):指定数据源
  • jpeg_read_header(&cinfo, TRUE):读入文件的头部信息
  • jpeg_start_decompress(&cinfo):开始解压缩
  • jpeg_read_scanlines(&cinfo, buffer, 1):读入数据并将其存储到缓存区中,1表示每次读出的行数
  • put_scanline_someplace:自定义函数
  • jpeg_finish_decompress(&cinfo):完成解压缩
  • jpeg_destroy_decompress(&cinfo):释放JPEG解压缩对象

更多详细内容,请参考原文档:

source code:example.c

reference:USING THE IJG JPEG LIBRARY