Tiny Jpeg Decoder (JPEG解码程序) 源代码分析 1:解码文件头

时间:2023-01-21 21:18:54

注:分析Tiny Jpeg Decoder源代码的文章:

Tiny Jpeg Decoder (JPEG解码程序) 源代码分析 1:解码文件头
Tiny Jpeg Decoder (JPEG解码程序) 源代码分析 2:解码数据

===================


Tiny Jpeg Decoder是一个可以用于嵌入式系统的JPEG解码器。也可以在Windows上编译通过。在此分析一下它部分的源代码,辅助学习JPEG解码知识。

通过TinyJpeg可以将JPEG(*.jpg)文件解码为YUV(*.yuv)或者RGB(*.tga)文件。

真正的解码开始于convert_one_image()函数:

/**
* Load one jpeg image, and decompress it, and save the result.
*/
int convert_one_image(LPVOID lparam,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];

/* Load the Jpeg into memory */
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();
//传入句柄--------------
jdec->dlg=(CSpecialVIJPGDlg *)lparam;

if (jdec == NULL)
exitmessage("Not enough memory to alloc the structure need for decompressing\n");
//解头部
if (tinyjpeg_parse_header(jdec, buf, length_of_file)<0)
exitmessage(tinyjpeg_get_errorstring(jdec));
/* Get the size of the image */
//获得图像长宽
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);

/* Save it */
switch (output_format)
{
case TINYJPEG_FMT_RGB24:
case TINYJPEG_FMT_BGR24:
write_tga(outfilename, output_format, width, height, components);
break;
case TINYJPEG_FMT_YUV420P:
//开始写入成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() */
//modify by lei! tinyjpeg_free(jdec);
/* else called just free(jdec); */

free(buf);
return 0;
}

tinyjpeg_init()用于初始化:

/**
* Allocate a new tinyjpeg decoder object.
*
* Before calling any other functions, an object need to be called.
*/
struct jdec_private *tinyjpeg_init(void)
{
struct jdec_private *priv;

priv = (struct jdec_private *)calloc(1, sizeof(struct jdec_private));
if (priv == NULL)
return NULL;
priv->DQT_table_num=0;
return priv;
}

tinyjpeg_parse_header()用于解码JPEG文件头,可见函数前几句主要验证文件是否为JPEG文件:

/**
* Initialize the tinyjpeg object and prepare the decoding of the stream.
*
* Check if the jpeg can be decoded with this jpeg decoder.
* Fill some table used for preprocessing.
*/
int tinyjpeg_parse_header(struct jdec_private *priv, const unsigned char *buf, unsigned int size)
{
int ret;

/* Identify the file */
//0x FF D8
//是否是JPEG格式文件?
if ((buf[0] != 0xFF) || (buf[1] != SOI))
snprintf(error_string, sizeof(error_string),"Not a JPG file ?\n");
//是
char temp_str[MAX_URL_LENGTH];
sprintf(temp_str,"0x %X %X",buf[0],buf[1]);
//JPEG格式文件固定的文件头
//begin指针前移2字节
priv->stream_begin = buf+2;
priv->stream_length = size-2;
priv->stream_end = priv->stream_begin + priv->stream_length;
//开始解析JFIF
ret = parse_JFIF(priv, priv->stream_begin);
return ret;
}

parse_JFIF()用于解析各种标签(SOF,SOS,DHT...):

//解各种不同的标签
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 */
//在Start of scan标签之前
while (!sos_marker_found)
{
if (*stream++ != 0xff)
goto bogus_jpeg_format;
/* Skip any padding ff byte (this is normal) */
//跳过0xff字节
while (*stream == 0xff)
stream++;
//marker是跳过0xff字节后1个字节的内容
marker = *stream++;
//chunk_len是marker后面2个字节的内容(大端模式需要转换)
//包含自身,但不包含0xff+marker2字节
chuck_len = be16_to_cpu(stream);
//指向下一个chunk的指针
next_chunck = stream + chuck_len;
//各种不同的标签
switch (marker)
{
case SOF:
//开始解析SOF
if (parse_SOF(priv, stream) < 0)
return -1;
break;
//Define quantization table
case DQT:
//开始解析DQT
if (parse_DQT(priv, stream) < 0)
return -1;
break;
case SOS:
//开始解析SOS
if (parse_SOS(priv, stream) < 0)
return -1;
sos_marker_found = 1;
break;
//Define Huffman table
case DHT:
//开始解析DHT
if (parse_DHT(priv, stream) < 0)
return -1;
dht_marker_found = 1;
break;
case DRI:
//开始解析DRI
if (parse_DRI(priv, stream) < 0)
return -1;
break;
default:
#if TRACE_PARAM
fprintf(param_trace,"> Unknown marker %2.2x\n", marker);
fflush(param_trace);
#endif
break;
}
//解下一个segment
stream = next_chunck;
}

if (!dht_marker_found) {
#if TRACE_PARAM
fprintf(param_trace,"No Huffman table loaded, using the default one\n");
fflush(param_trace);
#endif
build_default_huffman_tables(priv);
}

#ifdef SANITY_CHECK
if ( (priv->component_infos[cY].Hfactor < priv->component_infos[cCb].Hfactor)
|| (priv->component_infos[cY].Hfactor < priv->component_infos[cCr].Hfactor))
snprintf(error_string, sizeof(error_string),"Horizontal sampling factor for Y should be greater than horitontal sampling factor for Cb or Cr\n");
if ( (priv->component_infos[cY].Vfactor < priv->component_infos[cCb].Vfactor)
|| (priv->component_infos[cY].Vfactor < priv->component_infos[cCr].Vfactor))
snprintf(error_string, sizeof(error_string),"Vertical sampling factor for Y should be greater than vertical sampling factor for Cb or Cr\n");
if ( (priv->component_infos[cCb].Hfactor!=1)
|| (priv->component_infos[cCr].Hfactor!=1)
|| (priv->component_infos[cCb].Vfactor!=1)
|| (priv->component_infos[cCr].Vfactor!=1))
snprintf(error_string, sizeof(error_string),"Sampling other than 1x1 for Cr and Cb is not supported");
#endif

return 0;
bogus_jpeg_format:
#if TRACE_PARAM
fprintf(param_trace,"Bogus jpeg format\n");
fflush(param_trace);
#endif
return -1;
}

parse_SOF()用于解析SOF标签:

注意:其中包含了部分自己写的代码,形如:

itoa(width,temp_str1,10);
priv->dlg->AppendBInfo("SOF0","宽",temp_str1,"图像的宽度");

这些代码主要用于在解码过程中提取一些信息,比如图像宽,高,颜色分量数等等

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;
#if TRACE_PARAM
fprintf(param_trace,"> SOF marker\n");
fflush(param_trace);
#endif
print_SOF(stream);

height = be16_to_cpu(stream+3);
width = be16_to_cpu(stream+5);
nr_components = stream[7];
#if SANITY_CHECK
if (stream[2] != 8)
snprintf(error_string, sizeof(error_string),"Precision other than 8 is not supported\n");
if (width>JPEG_MAX_WIDTH || height>JPEG_MAX_HEIGHT)
snprintf(error_string, sizeof(error_string),"Width and Height (%dx%d) seems suspicious\n", width, height);
if (nr_components != 3)
snprintf(error_string, sizeof(error_string),"We only support YUV images\n");
if (height%16)
snprintf(error_string, sizeof(error_string),"Height need to be a multiple of 16 (current height is %d)\n", height);
if (width%16)
snprintf(error_string, sizeof(error_string),"Width need to be a multiple of 16 (current Width is %d)\n", width);
#endif
char temp_str1[MAX_URL_LENGTH]={0};
itoa(width,temp_str1,10);
priv->dlg->AppendBInfo("SOF0","宽",temp_str1,"图像的宽度");
itoa(height,temp_str1,10);
priv->dlg->AppendBInfo("SOF0","高",temp_str1,"图像的高度");
itoa(nr_components,temp_str1,10);
priv->dlg->AppendBInfo("SOF0","颜色分量数",temp_str1,"图像的颜色分量数。一个字节,例如03,代表有三个分量,YCrCb");
itoa(stream[2],temp_str1,10);
priv->dlg->AppendBInfo("SOF0","精度",temp_str1,"图像的精度。一个字节,例如08,即精度为一个字节。");
stream += 8;
for (i=0; i<nr_components; i++) {
cid = *stream++;
sampling_factor = *stream++;
Q_table = *stream++;
c = &priv->component_infos[i];
#if SANITY_CHECK
c->cid = cid;
if (Q_table >= COMPONENTS)
snprintf(error_string, sizeof(error_string),"Bad Quantization table index (got %d, max allowed %d)\n", Q_table, COMPONENTS-1);
#endif
c->Vfactor = sampling_factor&0xf;
c->Hfactor = sampling_factor>>4;
c->Q_table = priv->Q_tables[Q_table];
//------------
char temp_str2[MAX_URL_LENGTH]={0};
sprintf(temp_str2,"垂直采样因子【%d】",i);
itoa(c->Hfactor,temp_str1,10);
priv->dlg->AppendBInfo("SOF0",temp_str2,temp_str1,"颜色分量信息:每个分量有三个字节,第一个为分量的ID,01:Y 02:U 03:V;第二个字节高位为水平采样因子,低位为垂直采样因子。");
sprintf(temp_str2,"水平采样因子【%d】",i);
itoa(c->Hfactor,temp_str1,10);
priv->dlg->AppendBInfo("SOF0",temp_str2,temp_str1,"颜色分量信息:每个分量有三个字节,第一个为分量的ID,01:Y 02:U 03:V;第二个字节高位为水平采样因子,低位为垂直采样因子。");
sprintf(temp_str2,"对应量化表ID【%d】",i);
itoa((int)Q_table,temp_str1,10);
priv->dlg->AppendBInfo("SOF0",temp_str2,temp_str1,"颜色分量信息:第三个字节代表这个分量对应的量化表ID,例如,Y对应的量化表ID索引值为00,而UV对应的量化表ID都为01,即它们共用一张量化表。");
//-------------
#if TRACE_PARAM
fprintf(param_trace,"Component:%d factor:%dx%d Quantization table:%d\n",
cid, c->Hfactor, c->Hfactor, Q_table );
fflush(param_trace);
#endif

}


priv->width = width;
priv->height = height;
#if TRACE_PARAM
fprintf(param_trace,"< SOF marker\n");
fflush(param_trace);
#endif

return 0;
}

parse_DHT()用于解析DHT标签:

//解析DHT表
static int parse_DHT(struct jdec_private *priv, const unsigned char *stream)
{
unsigned int count, i,j;
unsigned char huff_bits[17];
int length, index;
//------------------------------------------
char *temp;
FILE *fp;
//------------------------------------------
length = be16_to_cpu(stream) - 2;
//跳过length字段
stream += 2;/* Skip length */
#if TRACE_PARAM
fprintf(param_trace,"> DHT marker (length=%d)\n", length);
fflush(param_trace);
#endif

while (length>0) {
//跳过第1字节:
//Huffman 表ID号和类型,高 4 位为表的类型,0:DC 直流;1:AC交流
//低四位为 Huffman 表 ID。
index = *stream++;

/* We need to calculate the number of bytes 'vals' will takes */
huff_bits[0] = 0;
count = 0;

//不同长度 Huffman 的码字数量:固定为 16 个字节,每个字节代表从长度为 1到长度为 16 的码字的个数
for (i=1; i<17; i++) {
huff_bits[i] = *stream++;
//count记录码字的个数
count += huff_bits[i];
}
#if SANITY_CHECK
if (count >= HUFFMAN_BITS_SIZE)
snprintf(error_string, sizeof(error_string),"No more than %d bytes is allowed to describe a huffman table", HUFFMAN_BITS_SIZE);
if ( (index &0xf) >= HUFFMAN_TABLES)
snprintf(error_string, sizeof(error_string),"No more than %d Huffman tables is supported (got %d)\n", HUFFMAN_TABLES, index&0xf);
#if TRACE_PARAM
fprintf(param_trace,"Huffman table %s[%d] length=%d\n", (index&0xf0)?"AC":"DC", index&0xf, count);
fflush(param_trace);
#endif
#endif

if (index & 0xf0 ){
//---------------------
char temp_str1[MAX_URL_LENGTH]={0};
char temp_str2[MAX_URL_LENGTH]={0};
temp=(char *)stream;
//fp = fopen("DHT.txt", "a+");
//fwrite(temp, 16, 1, fp);
for(j=0;j<16;j++){
//fprintf(fp,"%d ",temp[j]);
sprintf(temp_str2,"%d ",temp[j]);
strcat(temp_str1,temp_str2);
}
//fprintf(fp,"\n-----------------------\n");
//fclose(fp);
//-----------------------------------------------------
priv->dlg->AppendBInfo("DHT","定义霍夫曼表【交流系数表】",temp_str1,"Huffman表ID号和类型:1字节,高4位为表的类型,0:DC直流;1:AC交流 可以看出这里是直流表;低四位为Huffman表ID");
//-----------------------------------------------------
//交流霍夫曼表
build_huffman_table(huff_bits, stream, &priv->HTAC[index&0xf]);
}
else{
//---------------------
char temp_str1[MAX_URL_LENGTH]={0};
char temp_str2[MAX_URL_LENGTH]={0};
temp=(char *)stream;
//fp = fopen("DHT.txt", "a+");
//fwrite(temp, 16, 1, fp);
for(j=0;j<16;j++){
//fprintf(fp,"%d ",temp[j]);
sprintf(temp_str2,"%d ",temp[j]);
strcat(temp_str1,temp_str2);
}
//fprintf(fp,"\n-----------------------\n");
//fclose(fp);
//-----------------------------------------------------
priv->dlg->AppendBInfo("DHT","定义霍夫曼表【直流系数表】",temp_str1,"Huffman表ID号和类型:1字节,高4位为表的类型,0:DC直流;1:AC交流 可以看出这里是直流表;低四位为Huffman表ID");
//-----------------------------------------------------
//直流霍夫曼表
build_huffman_table(huff_bits, stream, &priv->HTDC[index&0xf]);
}

length -= 1;
length -= 16;
length -= count;
stream += count;
}
#if TRACE_PARAM
fprintf(param_trace,"< DHT marker\n");
fflush(param_trace);
#endif
return 0;
}

parse_DQT()用于解析DQT标签:

//解析Define quantization table
static int parse_DQT(struct jdec_private *priv, const unsigned char *stream)
{
int qi;
float *table;
const unsigned char *dqt_block_end;

//------------------------------------------------
int j,k;
char *temp;
FILE *fp;
//------------------------------------------------
#if TRACE_PARAM
fprintf(param_trace,"> DQT marker\n");
fflush(param_trace);
#endif
//该Segment末尾
dqt_block_end = stream + be16_to_cpu(stream);
//跳过标签length(2字节)
stream += 2;/* Skip length */
//没到末尾
while (stream < dqt_block_end)
{
//跳过该Segment的第1个字节,QT信息
//precision: 00 (Higher 4 bit)
//index: 00 (Lower 4 bit)
//qi取1,第1张量化表(例如,亮度表),取2,第2张量化表(例如,色度表)
qi = *stream++;
#if SANITY_CHECK
if (qi>>4)
snprintf(error_string, sizeof(error_string),"16 bits quantization table is not supported\n");
if (qi>4)
snprintf(error_string, sizeof(error_string),"No more 4 quantization table is supported (got %d)\n", qi);
#endif
//table指向jdec_private的Q_tables数组,为了在其中写入数据
table = priv->Q_tables[qi];
//注意:一次搞定整张!写入
//需要对数值进行变换!cos(k*PI/16) * sqrt(2)
//这样才能得到离散余弦变换的系数
build_quantization_table(table, stream);
//----------------------------------------------------------

temp=(char *)stream;
//fp = fopen("DQT.txt", "a+");
//fwrite(temp, 64, 1, fp);
char temp_str1[MAX_URL_LENGTH]={0};
char temp_str2[MAX_URL_LENGTH]={0};
for(j=0;j<64;j++){
sprintf(temp_str2,"%d ",temp[j]);
strcat(temp_str1,temp_str2);
//fprintf(fp,"%d ",temp[j]);
}
//计数
char temp_str3[MAX_URL_LENGTH]={0};
sprintf(temp_str3,"量化表【%d】",priv->DQT_table_num);
priv->dlg->AppendBInfo("DQT",temp_str3,temp_str1,"JPEG格式文件的量化表,一般来说第一张是亮度的,后面是色度的");
priv->DQT_table_num++;
//fprintf(fp,"\n-----------------------\n");
//fclose(fp);
#if TRACE_PARAM
for(j=0;j<8;j++){
for(k=0;k<8;k++){
fprintf(param_trace,"%d ",temp[j*8+k]);
}
fprintf(param_trace,"\n");
}
fprintf(fp,"\n-----------------------\n");
fflush(param_trace);
#endif
//----------------------------------------------------------
//完事了!
stream += 64;
}
#if TRACE_PARAM
fprintf(param_trace,"< DQT marker\n");
fflush(param_trace);
#endif
return 0;
}

其他标签的解析不一一列举。

待续未完。。。


主页:http://www.saillard.org/programs_and_patches/tinyjpegdecoder/

源代码下载:http://download.csdn.net/detail/leixiaohua1020/6383115