JPEG编解码原理及代码实现
文章目录
JPEG(Joint Photographic Experts Group)是联合图像专家组的英文缩写。 该组织从1986年正式开始制订静止数字图像的压缩编码标准,该标准于1992年正式通过,称为JPEG标准。JPEG是第一个数字图像压缩的国际标准,它不仅适于静止图像的压缩,对 于电视图像序列的帧内压缩也常采用JPEG算法,因此JPEG是一个适用范围广泛的通用标准。
一、JPEG编解码原理
编解码基本框图为:

编码原理
JPEG标准本身并没有规定具体的颜色空间,只是对各颜色的分量分别进行编码。为了减少各分量之间的相关性,减少数据的冗余,通常会把RGB颜色空间转换成YUV来进行各分量的编码。


1. Level offset 零偏置电平下移
该步骤的作用是,图像内容平均亮度较高,将0电平移到中间,平均亮度降低, 便于DCT变换量化后直流的系数大大降低,也就降低了数据量。
先对8×8的像块进行零偏置电平下移(Level Offset),即对于灰度级为
的像素,通过减去
,将无符号整数变为有符号数,使其值域变为
,以将绝对值大的数出现的概率大大减小,提高编码效率。
2. 8x8 DCT
该步骤主要是用于去除图像数据之间的相关性,便于量化过程去除图像数据的空间冗余。DCT是一种无损变换,也无法对图像进行压缩,这样做的目的是在为下一步的量化做准备。
将图像分为8×8的像块;对于宽(高)不是8的整数倍的图像,使用图像边缘像素填充,以不改变频谱分布。然后对每一个子块进行DCT(Discrete Cosine Transform,离散余弦变换),以实现能量集中和去相关,便于去除空间冗余,提高编码效率。需要特别强调的是,DCT是一种无损变换,也无法对图像进行压缩,这样做的目的是在为下一步的量化做准备。

其中,
是
的DCT变换二维核矩阵,
是原始的数据。
由于DCT变换是一个正交变换,故有
3. Uniform scalar quantization 均匀标称量化
量化器主要是利用人眼视觉特性设计而成的矩阵量化DCT系数,减少视觉冗余。将DCT变换后的临时结果,除以各自量化步长并四舍五入后取整,得到量化系数。JPEG系统分别规定了亮度分量和色度分量的量化表,色度分量相应的量化步长比亮度分量大。在量化步骤中,JPEG采用了中平型(Midtread)的均匀量化器。
实际上JPEG压缩编码算法中,真正可供调整的部分并不多:DCT、熵编码这两个主要步骤都是完全确定的了,实际上只有量化可以调整,量化也自然是JPEG压缩编码算法的核心。此外,量化是编码流程中唯一会引入误差也是唯一会带来压缩的步骤。

JPEG标准中采用中平型均匀量化,由于人眼对低频分量的敏感程度远高于高频分量,且对亮度的敏感程度远高于色度,因而标准中据此设计了2张量化表(亮度、色差各一张),使低频细量化,高频粗量化,亮度细量化,色差粗量化,以减少视觉冗余。
量化矩阵并不是固定的,可以根据要求的质量的不同而进行调整。
4. DC系数差分编码
我们注意到,8×8像块经过DCT后得到的DC系数有两个特点:一是系数的值较大;二是相邻像块的DC系数差值不大(即存在冗余)。根据这个特点,JPEG标准采用了DPCM(差分脉冲编码调制),以对相邻图像块之间量化DC系数的差值DIFF进行编码。

对DPCM后算出的DIFF差值使用Huffman编码,将其分成类别,类似于指数的Golomb编码(只不过Golomb是一元码+定长码),也就是类别ID使用规范哈夫曼编码,类内索引使用定长码(自然码)。
5. AC系数Zig-Zag扫描与游程编码
由于DCT后,系数大多数集中在左上角,即低频分量区,因此采用Zig-Zag(之字形)扫描,将系数按频率的高低顺序读出,这样可以出现很多连零的机会,便于进行RLE(Run Length Encoding,游程编码),尤其在最后,如果都是零,给出EOB (End of Block)即可。


例如0, -2, -1, -1, -1, 0, 0, -1, EOB表示为:(1, -2), (0, -1), (0, -1), (0, -1), (2, -1), EOB。
6. Huffman编码
对DC系数DPCM的结果和AC系数RLE的结果进行Huffman编码,类别ID采用一元码编码,类内索引采用定长码编码。以DC系数为例:

例如差值DIFF = 3 ,对应的类别ID = 2,类内索引 = 3,则码字为100 11。
共有亮度DC、亮度AC、色差DC、色差AC四张Huffman编码表。
解码原理
解码完全是编码的逆过程,解码系统框图:

二、JPEG文件格式分析
JPEG文件以segment的形式组织,其中每个segment以一个marker开始,而每个marker均以0xFF和一个marker的标识符开始,随后为2字节的marker长度(不包含marker的起始两字节)和对应的payload(SOI和EOI marker只有2字节的标识符)。
注意,连续的0xFF字节并不是marker的起始标志,而是用来填充的特殊字符。
此外,部分中,0xFF后若为0x00,则跳过此字节不予处理。
常见的marker如下:
Short name
| Bytes
| Payload
| Name
| Comments
|
SOI
| 0xFF, 0xD8
| none | Start Of Image
| |
SOF0
| 0xFF, 0xC0
| variable size | Start Of Frame (baseline DCT)
| Indicates that this is a baseline DCT-based JPEG, and specifies the width, height, number of components, and component subsampling (e.g., 4:2:0).
|
SOF2
| 0xFF, 0xC2
| variable size | Start Of Frame (progressive DCT)
| Indicates that this is a progressive DCT-based JPEG, and specifies the width, height, number of components, and component subsampling (e.g., 4:2:0).
|
DHT
| 0xFF, 0xC4
| variable size | Define Huffman Table(s)
| Specifies one or more Huffman tables.
|
DQT
| 0xFF, 0xDB
| variable size | Define Quantization Table(s)
| Specifies one or more quantization tables.
|
DRI
| 0xFF, 0xDD
| 4 bytes
| Define Restart Interval
| Specifies the interval between RSTn markers, in Minimum Coded Units (MCUs). This marker is followed by two bytes indicating the fixed size so it can be treated like any other variable size segment.
|
SOS
| 0xFF, 0xDA
| variable size | Start Of Scan
| Begins a top-to-bottom scan of the image. In baseline DCT JPEG images, there is generally a single scan. Progressive DCT JPEG images usually contain multiple scans. This marker specifies which slice of data it will contain, and is immediately followed by entropy-coded data.
|
RSTn
| 0xFF, 0xDn
| none
| Restart
| Inserted every r rr macroblocks, where r rr is the restart interval set by a DRI marker. Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7.
|
APPn
| 0xFF, 0xEn
| variable size | Application-specific
| For example, an Exif JPEG file uses an APP1 marker to store metadata, laid out in a structure based closely on TIFF.
|
COM
| 0xFF, 0xFE
| variable size | Comment
| Contains a text comment.
|
EOI
| 0xFF, 0xD9
| none | End Of Image
| |
下面使用Synalyze it! Pro App进行二进制分析,并对一些marker字段作一些简要说明。

实验图片test.jpg(1024x1024)
- SOI与EOI

- APP0

- DQT

- SOF0

- DHT

- SOS

- SOS

三、代码实现
1. 分层结构
JPEG压缩编码算法的一大特点就是采用了分层结构设计的思想,下面说明三个主要结构体的设计意图:
struct huffman_table:存储Huffman码表。
/* tinyjpeg-internal.h */
struct huffman_table
{
/* Fast look up table, using HUFFMAN_HASH_NBITS bits we can have directly the symbol,
* if the symbol is <0, then we need to look into the tree table */
short int lookup[HUFFMAN_HASH_SIZE];
/* code size: give the number of bits of a symbol is encoded */
unsigned char code_size[HUFFMAN_HASH_SIZE];
/* some place to store value that is not encoded in the lookup table
* FIXME: Calculate if 256 value is enough to store all values
*/
uint16_t slowtable[16-HUFFMAN_HASH_NBITS][256];
};
struct component:储存当前8×8像块中有关解码的信息。
/* tinyjpeg-internal.h */
struct component
{
unsigned int Hfactor; // 水平采样因子
unsigned int Vfactor; // 垂直采样因子
float* Q_table; // 指向该8×8块使用的量化表
struct huffman_table *AC_table; // 指向该块使用的AC Huffman表
struct huffman_table *DC_table; // 指向该块使用的DC Huffman表
short int previous_DC; // 前一个块的直流DCT系数
short int DCT[64]; // DCT系数数组
#if SANITY_CHECK
unsigned int cid;
#endif
};
struct jdec_private:JPEG数据流结构体,用于存储JPEG图像宽高、数据流指针、Huffman码表等内容,并包含struct huffman_table和struct component。
/* tinyjpeg-internal.h */
struct jdec_private
{
/* Public variables */
uint8_t *components[COMPONENTS]; /* 分别指向YUV三个分量的三个指针 */
unsigned int width, height; /* 图像宽高 */
unsigned int flags;
/* Private variables */
const unsigned char *stream_begin, *stream_end;
unsigned int stream_length;
const unsigned char *stream; /* 指向当前数据流的指针 */
unsigned int reservoir, nbits_in_reservoir;
struct component component_infos[COMPONENTS];
float Q_tables[COMPONENTS][64]; /* quantization tables */
struct huffman_table HTDC[HUFFMAN_TABLES]; /* DC huffman tables */
struct huffman_table HTAC[HUFFMAN_TABLES]; /* AC huffman tables */
int default_huffman_table_initialized;
int restart_interval;
int restarts_to_go; /* MCUs left in this restart interval */
int last_rst_marker_seen; /* Rst marker is incremented each time */
/* Temp space used after the IDCT to store each components */
uint8_t Y[64*4], Cr[64], Cb[64];
jmp_buf jump_state;
/* Internal Pointer use for colorspace conversion, do not modify it !!! */
uint8_t *plane[COMPONENTS];
};
2. 解码流程
/* 读取JPEG文件,进行解码,并存储结果 */
int convert_one_image(const char *infilename, const char *outfilename, int output_format)
{
FILE *fp;
unsigned int length_of_file; // 文件大小
unsigned int width, height; // 图像宽、高
unsigned char *buf; // 缓冲区
struct jdec_private *jdec;
unsigned char *components[3];
/* 将JPEG读入缓冲区 */
fp = fopen(infilename, "rb");
if (fp == NULL)
exitmessage("Cannot open filename\n");
length_of_file = filesize(fp);
buf = (unsigned char *)malloc(length_of_file + 4);
if (buf == NULL)
exitmessage("Not enough memory for loading file\n");
fread(buf, length_of_file, 1, fp);
fclose(fp);
/* Decompress it */
jdec = tinyjpeg_init(); // 初始化
if (jdec == NULL)
exitmessage("Not enough memory to alloc the structure need for decompressing\n");
/* 解析JPEG文件头 */
if (tinyjpeg_parse_header(jdec, buf, length_of_file)<0)
exitmessage(tinyjpeg_get_errorstring(jdec));
/* 计算图像宽高 */
tinyjpeg_get_size(jdec, &width, &height);
snprintf(error_string, sizeof(error_string),"Decoding JPEG image...\n");
if (tinyjpeg_decode(jdec, output_format) < 0) // 解码实际数据
exitmessage(tinyjpeg_get_errorstring(jdec));
/*
* Get address for each plane (not only max 3 planes is supported), and
* depending of the output mode, only some components will be filled
* RGB: 1 plane, YUV420P: 3 planes, GREY: 1 plane
*/
tinyjpeg_get_components(jdec, components);
/* 按照指定的输出格式保存输出文件 */
switch (output_format)
{
case TINYJPEG_FMT_RGB24:
case TINYJPEG_FMT_BGR24:
write_tga(outfilename, output_format, width, height, components);
break;
case TINYJPEG_FMT_YUV420P:
write_yuv(outfilename, width, height, components);
break;
case TINYJPEG_FMT_GREY:
write_pgm(outfilename, width, height, components);
break;
}
/* Only called this if the buffers were allocated by tinyjpeg_decode() */
tinyjpeg_free(jdec);
/* else called just free(jdec); */
free(buf);
return 0;
}
3. 核心模块
解析JPEG文件头
int tinyjpeg_parse_header(struct jdec_private *priv, const unsigned char *buf, unsigned int size)
{
int ret;
/* Identify the file */
if ((buf[0] != 0xFF) || (buf[1] != SOI)) // JPEG文件必须以SOI marker为起始,否则不是合法的JPEG文件
snprintf(error_string, sizeof(error_string),"Not a JPG file ?\n");
priv->stream_begin = buf+2; // 跳过标识符
priv->stream_length = size-2;
priv->stream_end = priv->stream_begin + priv->stream_length;
ret = parse_JFIF(priv, priv->stream_begin); // 开始解析JPEG
return ret;
}
解析marker标识符
/* 略去了trace部分 */
static int parse_JFIF(struct jdec_private *priv, const unsigned char *stream)
{
int chuck_len;
int marker;
int sos_marker_found = 0;
int dht_marker_found = 0;
const unsigned char *next_chunck;
/* Parse marker */
while (sos_marker_found == 0)
{
if (*stream++ != 0xff)
goto bogus_jpeg_format;
/* Skip any padding ff byte (this is normal) */
while (*stream == 0xff)
stream++;
marker = *stream++; // 获取0xFF后的一个字节(即为marker标识符)
chuck_len = be16_to_cpu(stream); // length字段
next_chunck = stream + chuck_len;
switch (marker) // 判断marker类型
{
case SOF:
if (parse_SOF(priv, stream) < 0)
return -1;
break;
case DQT:
if (parse_DQT(priv, stream) < 0)
return -1;
break;
case SOS:
if (parse_SOS(priv, stream) < 0)
return -1;
sos_marker_found = 1;
break;
case DHT:
if (parse_DHT(priv, stream) < 0)
return -1;
dht_marker_found = 1;
break;
case DRI:
if (parse_DRI(priv, stream) < 0)
return -1;
break;
default:
break;
}
stream = next_chunck; // 解析下一个marker
}
if (!dht_marker_found) {
build_default_huffman_tables(priv);
}
return 0;
bogus_jpeg_format:
return -1;
}
解析DQT
static int parse_DQT(struct jdec_private *priv, const unsigned char *stream)
{
int qi; // 量化表ID
float *table; // 指向量化表
const unsigned char *dqt_block_end; // 指向量化表结束位置
dqt_block_end = stream + be16_to_cpu(stream);
stream += 2; // 跳过长度字段
while (stream < dqt_block_end) // 检查是否还有量化表
{
qi = *stream++; // 将量化表中系数逐个赋给qi
table = priv->Q_tables[qi];
build_quantization_table(table, stream);
stream += 64;
}
return 0;
}
建立量化表
static void build_quantization_table(float *qtable, const unsigned char *ref_table)
{
int i, j;
static const double aanscalefactor[8] = {
1.0, 1.387039845, 1.306562965, 1.175875602,
1.0, 0.785694958, 0.541196100, 0.275899379
}; // 比例因子
const unsigned char *zz = zigzag;
for (i=0; i<8; i++) {
for (j=0; j<8; j++) {
*qtable++ = ref_table[*zz++] * aanscalefactor[i] * aanscalefactor[j];
}
}
}
解析DHT
static int parse_DHT(struct jdec_private *priv, const unsigned char *stream)
{
unsigned int count, i;
unsigned char huff_bits[17]; // 码长1~16
int length, index;
length = be16_to_cpu(stream) - 2;
stream += 2; // 跳过长度字段
while (length>0) { // 检查是否还有表
index = *stream++;
/* We need to calculate the number of bytes 'vals' will takes */
huff_bits[0] = 0;
count = 0;
for (i=1; i<17; i++) {
huff_bits[i] = *stream++;
count += huff_bits[i];
}
if (index & 0xf0 )
build_huffman_table(huff_bits, stream, &priv->HTAC[index&0xf]); // 建立交流表
else
build_huffman_table(huff_bits, stream, &priv->HTDC[index&0xf]); // 建立直流表
length -= 1;
length -= 16;
length -= count;
stream += count;
}
return 0;
}
建立Huffman码表
static void build_huffman_table(const unsigned char *bits, const unsigned char *vals, struct huffman_table *table) // bits为各个位数码字的数量,val为Huffval,table为要建立的Huffman表
{
unsigned int i, j, code, code_size, val, nbits;
unsigned char huffsize[HUFFMAN_BITS_SIZE + 1]; // 每个码字的长度
unsigned char* hz;
unsigned int huffcode[HUFFMAN_BITS_SIZE + 1]; // 每个码字
unsigned char* hc;
int next_free_entry;
/* 初始化 */
hz = huffsize;
for (i=1; i<=16; i++)
{
for (j=1; j<=bits[i]; j++)
*hz++ = i;
}
*hz = 0;
memset(table->lookup, 0xff, sizeof(table->lookup));
for (i=0; i<(16-HUFFMAN_HASH_NBITS); i++)
table->slowtable[i][0] = 0;
code = 0;
hc = huffcode;
hz = huffsize;
nbits = *hz;
while (*hz)
{
while (*hz == nbits)
{
*hc++ = code++;
hz++;
}
code <<= 1;
nbits++;
}
/*
* Build the lookup table, and the slowtable if needed.
*/
next_free_entry = -1;
for (i=0; huffsize[i] != 0; i++)
{
/* 得到Huffval、每个码字、每个码字的长度*/
val = vals[i];
code = huffcode[i];
code_size = huffsize[i];
table->code_size[val] = code_size; // Huffval(权值)
if (code_size <= HUFFMAN_HASH_NBITS)
{
/*
* Good: val can be put in the lookup table, so fill all value of this
* column with value val
*/
int repeat = 1UL<<(HUFFMAN_HASH_NBITS - code_size);
code <<= HUFFMAN_HASH_NBITS - code_size;
while ( repeat-- )
table->lookup[code++] = val; // 得到Huffval长度的查找表
}
else
{
/* Perhaps sorting the array will be an optimization */
uint16_t *slowtable = table->slowtable[code_size-HUFFMAN_HASH_NBITS-1];
while(slowtable[0])
slowtable+=2;
slowtable[0] = code;
slowtable[1] = val;
slowtable[2] = 0;
/* TODO: NEED TO CHECK FOR AN OVERFLOW OF THE TABLE */
}
}
}
解析SOS
static int parse_SOS(struct jdec_private *priv, const unsigned char *stream)
{
unsigned int i, cid, table;
unsigned int nr_components = stream[2]; // 颜色分量数
stream += 3;
for (i=0;i<nr_components;i++) {
/* 得到使用的Huffmann表号 */
cid = *stream++;
table = *stream++;
priv->component_infos[i].AC_table = &priv->HTAC[table&0xf];
priv->component_infos[i].DC_table = &priv->HTDC[table>>4];
}
priv->stream = stream+3;
return 0;
}
解析SOF
static int parse_SOF(struct jdec_private *priv, const unsigned char *stream)
{
int i, width, height, nr_components, cid, sampling_factor;
int Q_table;
struct component *c;
print_SOF(stream);
height = be16_to_cpu(stream+3); // 图像高度
width = be16_to_cpu(stream+5); // 图像宽度
nr_components = stream[7]; // 颜色分量数
stream += 8;
for (i=0; i<nr_components; i++) {
/* 分别解析各分量 */
cid = *stream++; // 分量ID
sampling_factor = *stream++; // 采样因子
Q_table = *stream++;
c = &priv->component_infos[i];
c->Vfactor = sampling_factor&0xf; // 垂直采样因子
c->Hfactor = sampling_factor>>4; // 水平采样因子
c->Q_table = priv->Q_tables[Q_table]; // 使用的量化表
}
priv->width = width;
priv->height = height;
return 0;
}
解析JPEG实际数据
int tinyjpeg_decode(struct jdec_private *priv, int pixfmt) // pixfmt为输出格式
{
unsigned int x, y, xstride_by_mcu, ystride_by_mcu;
unsigned int bytes_per_blocklines[3], bytes_per_mcu[3];
decode_MCU_fct decode_MCU;
const decode_MCU_fct *decode_mcu_table;
const convert_colorspace_fct *colorspace_array_conv;
convert_colorspace_fct convert_to_pixfmt;
if (setjmp(priv->jump_state))
return -1;
/* To keep gcc happy initialize some array */
bytes_per_mcu[1] = 0;
bytes_per_mcu[2] = 0;
bytes_per_blocklines[1] = 0;
bytes_per_blocklines[2] = 0;
decode_mcu_table = decode_mcu_3comp_table;
switch (pixfmt) {
/* 根据不同的输出格式确定MCU */
case TINYJPEG_FMT_YUV420P:
colorspace_array_conv = convert_colorspace_yuv420p;
if (priv->components[0] == NULL)
priv->components[0] = (uint8_t *)malloc(priv->width * priv->height);
if (priv->components[1] == NULL)
priv->components[1] = (uint8_t *)malloc(priv->width * priv->height/4);
if (priv->components[2] == NULL)
priv->components[2] = (uint8_t *)malloc(priv->width * priv->height/4);
bytes_per_blocklines[0] = priv->width;
bytes_per_blocklines[1] = priv->width/4;
bytes_per_blocklines[2] = priv->width/4;
bytes_per_mcu[0] = 8;
bytes_per_mcu[1] = 4;
bytes_per_mcu[2] = 4;
break;
case TINYJPEG_FMT_RGB24:
colorspace_array_conv = convert_colorspace_rgb24;
if (priv->components[0] == NULL)
priv->components[0] = (uint8_t *)malloc(priv->width * priv->height * 3);
bytes_per_blocklines[0] = priv->width * 3;
bytes_per_mcu[0] = 3*8;
break;
case TINYJPEG_FMT_BGR24:
colorspace_array_conv = convert_colorspace_bgr24;
if (priv->components[0] == NULL)
priv->components[0] = (uint8_t *)malloc(priv->width * priv->height * 3);
bytes_per_blocklines[0] = priv->width * 3;
bytes_per_mcu[0] = 3*8;
break;
case TINYJPEG_FMT_GREY:
decode_mcu_table = decode_mcu_1comp_table;
colorspace_array_conv = convert_colorspace_grey;
if (priv->components[0] == NULL)
priv->components[0] = (uint8_t *)malloc(priv->width * priv->height);
bytes_per_blocklines[0] = priv->width;
bytes_per_mcu[0] = 8;
break;
default:
return -1;
}
xstride_by_mcu = ystride_by_mcu = 8; // 初始化:MCU的宽高均为8px(4:4:4)
if ((priv->component_infos[cY].Hfactor | priv->component_infos[cY].Vfactor) == 1) {
/* 水平、垂直采样因子均为1 */
decode_MCU = decode_mcu_table[0]; // MCU包含1个Y
convert_to_pixfmt = colorspace_array_conv[0];
} else if (priv->component_infos[cY].Hfactor == 1) {
/* 水平采样因子为1,垂直采样因子为2 */
decode_MCU = decode_mcu_table[1]; // MCU包含2个Y
convert_to_pixfmt = colorspace_array_conv[1];
ystride_by_mcu = 16; // MCU高16px,宽8px
} else if (priv->component_infos[cY].Vfactor == 2) {
/* 水平、垂直采样因子均为2 */
decode_MCU = decode_mcu_table[3]; // MCU包含4个Y
convert_to_pixfmt = colorspace_array_conv[3];
xstride_by_mcu = 16; // MCU宽16px
ystride_by_mcu = 16; // MCU高16px
} else {
/* 水平采样因子为2,垂直采样因子为1 */
decode_MCU = decode_mcu_table[2]; // MCU包含2个Y
convert_to_pixfmt = colorspace_array_conv[2];
xstride_by_mcu = 16; // MCU宽16px,高8px
}
resync(priv);
/* Don't forget to that block can be either 8 or 16 lines */
bytes_per_blocklines[0] *= ystride_by_mcu;
bytes_per_blocklines[1] *= ystride_by_mcu;
bytes_per_blocklines[2] *= ystride_by_mcu;
bytes_per_mcu[0] *= xstride_by_mcu/8;
bytes_per_mcu[1] *= xstride_by_mcu/8;
bytes_per_mcu[2] *= xstride_by_mcu/8;
/* 对每个像块进行解码(8x8 / 8x16 / 16x16) */
for (y=0; y < priv->height/ystride_by_mcu; y++)
{
//trace("Decoding row %d\n", y);
priv->plane[0] = priv->components[0] + (y * bytes_per_blocklines[0]);
priv->plane[1] = priv->components[1] + (y * bytes_per_blocklines[1]);
priv->plane[2] = priv->components[2] + (y * bytes_per_blocklines[2]);
for (x=0; x < priv->width; x+=xstride_by_mcu)
{
decode_MCU(priv);
convert_to_pixfmt(priv);
priv->plane[0] += bytes_per_mcu[0];
priv->plane[1] += bytes_per_mcu[1];
priv->plane[2] += bytes_per_mcu[2];
if (priv->restarts_to_go>0)
{
priv->restarts_to_go--;
if (priv->restarts_to_go == 0)
{
priv->stream -= (priv->nbits_in_reservoir/8);
resync(priv);
if (find_next_rst_marker(priv) < 0)
return -1;
}
}
}
}
return 0;
}
解析MCU
/*
* Decode a 2x2
* .-------.
* | 1 | 2 |
* |---+---|
* | 3 | 4 |
* `-------'
*/
static void decode_MCU_2x2_3planes(struct jdec_private *priv)
{
// Y
process_Huffman_data_unit(priv, cY);
IDCT(&priv->component_infos[cY], priv->Y, 16);
process_Huffman_data_unit(priv, cY);
IDCT(&priv->component_infos[cY], priv->Y+8, 16);
process_Huffman_data_unit(priv, cY);
IDCT(&priv->component_infos[cY], priv->Y+64*2, 16);
process_Huffman_data_unit(priv, cY);
IDCT(&priv->component_infos[cY], priv->Y+64*2+8, 16);
// Cb
process_Huffman_data_unit(priv, cCb);
IDCT(&priv->component_infos[cCb], priv->Cb, 8);
// Cr
process_Huffman_data_unit(priv, cCr);
IDCT(&priv->component_infos[cCr], priv->Cr, 8);
}
将文件输出为.yuv格式
static void write_yuv(const char* filename, int width, int height, unsigned char** components) {
FILE* F;
char temp[1024];
snprintf(temp, 1024, "%s.Y", filename);
F = fopen(temp, "wb");
fwrite(components[0], width, height, F);
fclose(F);
snprintf(temp, 1024, "%s.U", filename);
F = fopen(temp, "wb");
fwrite(components[1], width * height / 4, 1, F);
fclose(F);
snprintf(temp, 1024, "%s.V", filename);
F = fopen(temp, "wb");
fwrite(components[2], width * height / 4, 1, F);
fclose(F);
snprintf(temp, 1024, "%s.YUV", filename);
F = fopen(temp, "wb");
fwrite(components[0], width, height, F);
fwrite(components[1], width * height / 4, 1, F);
fwrite(components[2], width * height / 4, 1, F);
fclose(F);
}
输出yuv文件如下:

输出量化矩阵和Huffman码表
/* tinyjpeg.h中添加 */
/* 声明全局变量 by S.Z.Zheng */
FILE* qtabFilePtr; // 量化表文件指针
/* 声明结束 */
/* tinyjpeg.c中添加*/
static void build_quantization_table(float *qtable, const unsigned char *ref_table)
{
...
for (i=0; i<8; i++) {
for (j=0; j<8; j++) {
/* Added by S.Z.Zheng */
fprintf(qtabFilePtr, "%-6d", ref_table[*zz]);
if (j == 7) {
fprintf(qtabFilePtr, "\n");
}
/* Addition ended */
...
}
}
fprintf(qtabFilePtr, "\n\n"); // Added by S.Z.Zheng
}
static int parse_DQT(struct jdec_private *priv, const unsigned char *stream)
{
...
while (stream < dqt_block_end) // 检查是否还有量化表
{
...
fprintf(qtabFilePtr, "Quantisation table [%d]:\n", qi); // 量化表ID(added by S.Z.Zheng)
build_quantization_table(table, stream);
...
}
...
}
/* loadjpeg.c中添加 */
int main(int argc, char *argv[])
{
...
/* Added by S.Z.Zheng */
const char* qtabFileName = "q_table.txt"; // 量化表文件名
fopen_s(&qtabFilePtr, qtabFileName, "wb"); // 打开文件
/* Addition ended */
...
fclose(qtabFilePtr); // Added by S.Z.Zheng
return 0;
}

输出DC、AC图像
/* tinyjpeg.h中添加 */
/* 声明全局变量 by S.Z.Zheng */
...
FILE* dcImgFilePtr; // DC图像文件指针
FILE* acImgFilePtr; // AC图像文件指针
/* 声明结束 */
/* tinyjpeg.c中添加 */
int tinyjpeg_decode(struct jdec_private* priv, int pixfmt)
{
...
/* Added by S.Z.Zheng */
unsigned char* dcImgBuff;
unsigned char* acImgBuff;
unsigned char* uvBuff = 128;
int count = 0;
/* Addition ended*/
/* 对每个像块进行解码(8x8 / 8x16 / 16x16) */
for (y = 0; y < priv->height / ystride_by_mcu; y++) {
...
for (x = 0; x < priv->width; x += xstride_by_mcu) {
decode_MCU(priv);
dcImgBuff = (unsigned char)((priv->component_infos->DCT[0] + 512.0) / 4 + 0.5); // DCT[0]为DC系数;DC系数范围-512~512;变换到0~255
acImgBuff = (unsigned char)(priv->component_infos->DCT[1] + 128); // 选取DCT[1]作为AC的observation;+128便于观察
fwrite(&dcImgBuff, 1, 1, dcImgFilePtr);
fwrite(&acImgBuff, 1, 1, acImgFilePtr);
count++;
...
}
}
}
}
...
/* Added by S.Z.Zheng */
for (int i = 0; i < count / 4 * 2; i++) {
fwrite(&uvBuff, sizeof(unsigned char), 1, dcImgFilePtr);
fwrite(&uvBuff, sizeof(unsigned char), 1, acImgFilePtr);
}
/* Addition ended */
return 0;
}
/* loadjpeg.c中添加 */
int main(int argc, char *argv[]) {
...
/* Added by S.Z.Zheng */
...
const char* dcImgFileName = "test_decoded_dc.yuv"; // DC图像文件名
const char* acImgFileName = "test_decoded_ac.yuv"; // AC图像文件名
...
fopen_s(&dcImgFilePtr, dcImgFileName, "wb"); // 打开DC图像文件
fopen_s(&acImgFilePtr, acImgFileName, "wb"); // 打开AC图像文件
/* Addition ended */
...
/* Added by S.Z.Zheng */
...
fclose(dcImgFilePtr);
fclose(acImgFilePtr);
/* Addition Ended */
return 0;
}
