2024年6月7日发(作者:)

一、JPEG文件格式介绍

JPEG文件使用的数据存储方式有多种。最常用的格式称为JPEG文件交换格式(JPEG File

Interchange Format,JFIF)。而JPEG文件大体上可以分成两个部分:标记码(Tag)和压缩数据。

标记码由两个字节构成,其前一个字节是固定值0xFF,后一个字节则根据不同意义有不同

数值。在每个标记码之前还可以添加数目不限的无意义的0xFF填充,也就说连续的多个0xFF

可以被理解为一个0xFF,并表示一个标记码的开始。而在一个完整的两字节的标记码后,

就是该标记码对应的压缩数据流,记录了关于文件的诸种信息。

常用的标记有SOI、APP0、DQT、SOF0、DHT、DRI、SOS、EOI。

SOI,Start of Image,图像开始

标记代码 2字节 固定值0xFFD8

APP0,Application,应用程序保留标记0

标记代码 2字节 固定值0xFFE0

包含9个具体字段:

① 数据长度 2字节 ①~⑨9个字段的总长度

② 标识符 5字节 固定值0x4A46494600,即字符串“JFIF0”

③ 版本号 2字节 一般是0x0102,表示JFIF的版本号1.2

④ X和Y的密度单位 1字节 只有三个值可选

i. 0:无单位;1:点数/英寸;2:点数/厘米

⑤ X方向像素密度 2字节 取值范围未知

⑥ Y方向像素密度 2字节 取值范围未知

⑦ 缩略图水平像素数目 1字节 取值范围未知

⑧ 缩略图垂直像素数目 1字节 取值范围未知

⑨ 缩略图RGB位图 长度可能是3的倍数 缩略图RGB位图数据

本标记段可以包含图像的一个微缩版本,存为24位的RGB像素。如果没有微缩图像(这种

情况更常见),则字段⑦“缩略图水平像素数目”和字段⑧“缩略图垂直像素数目”的值均

为0。

APPn,Application,应用程序保留标记n,其中n=1~15(任选)

标记代码 2字节 固定值0xFFE1~0xFFF

包含2个具体字段:

① 数据长度 2字节 ①~②2个字段的总长度

i. 即不包括标记代码,但包括本字段

② 详细信息 数据长度-2字节 内容不定

例如,Adobe Photoshop生成的JPEG图像中就用了APP1和APP13两个标记段分别存储了一

幅图像的副本。

DQT,Define Quantization Table,定义量化表

标记代码 2字节 固定值0xFFDB

包含9个具体字段:

① 数据长度 2字节 字段①和多个字段②的总长度

i. 即不包括标记代码,但包括本字段

② 量化表 数据长度-2字节

a) 精度及量化表ID 1字节 高4位:精度,只有两个可选值

0:8位;1:16位

低4位:量化表ID,取值范围为0~3

b) 表项 (64×(精度+1))字节 例如8位精度的量化表

其表项长度为64×(0+1)=64字节

本标记段中,字段②可以重复出现,表示多个量化表,但最多只能出现4次。

SOF0,Start of Frame,帧图像开始

① 标记代码 2字节 固定值0xFFC0

② 包含9个具体字段:

③ 数据长度 2字节 ①~⑥六个字段的总长度

i. 即不包括标记代码,但包括本字段

④ 精度 1字节 每个数据样本的位数

i. 通常是8位,一般软件都不支持 12位和16位

⑤ 图像高度 2字节 图像高度(单位:像素),如果不支持 DNL 就必须 >0

⑥ 图像宽度 2字节 图像宽度(单位:像素),如果不支持 DNL 就必须 >0

⑦ 颜色分量数 1字节 只有3个数值可选

i. 1:灰度图;3:YCrCb或YIQ;4:CMYK

ii. 而JFIF中使用YCrCb,故这里颜色分量数恒为3

⑧ 颜色分量信息 颜色分量数×3字节(通常为9字节)

a) 颜色分量ID 1字节

b) 水平/垂直采样因子 1字节 高4位:水平采样因子

低4位:垂直采样因子

c) 量化表 1字节 当前分量使用的量化表的ID

本标记段中,字段⑥应该重复出现,有多少个颜色分量(字段⑤),就出现多少次(一般为

3次)。

DHT,Difine Huffman Table,定义哈夫曼表

标记代码 2字节 固定值0xFFC4

包含2个具体字段:

① 数据长度 2字节 字段①和多个字段②的总长度

i. 即不包括标记代码,但包括本字段

② 哈夫曼表 数据长度-2字节

a) 表ID和表类型 1字节 高4位:类型,只有两个值可选

0:DC直流;1:AC交流

低4位:哈夫曼表ID,注意,DC表和AC表分开编码

b) 不同位数的码字数量 16字节

c) 编码内容 16个不同位数的码字数量之和(字节)

本标记段中,字段②可以重复出现(一般4次),也可以致出现1次。例如,Adobe Photoshop

生成的JPEG图片文件中只有1个DHT标记段,里边包含了4个哈夫曼表;而Macromedia

Fireworks生成的JPEG图片文件则有4个DHT标记段,每个DHT标记段只有一个哈夫曼表。

DRI,Define Restart Interval,定义差分编码累计复位的间隔

标记代码 2字节 固定值0xFFDD

包含2个具体字段:

数据长度 2字节 固定值0x0004,①~②两个字段的总

长度

即不包括标记代码,但

包括本字段

MCU块的单元中的重新开始间隔

2字节 设其值为n,则表示每n个MCU块就有一个

RSTn标记。第一个标记是RST0,第二个是

RST1等,RST7后再从RST0重复。

如果没有本标记段,或间隔值为0时,就表示不存在重开始间隔和标记RST

SOS,Start of Scan,扫描开始 12字节

标记代码 2字节 固定值0xFFDA

包含2个具体字段:

①数据长度 2字节 ①~④两个字段的总长度

即不包括标记代码,但包括本字段

②颜色分量数 1字节 应该和SOF中的字段⑤的值相同,即:

1:灰度图是;3: YCrCb或YIQ;4:CMYK。

而JFIF中使用YCrCb,故这里颜色分量数恒为3

③颜色分量信息

a) 颜色分量ID 1字节

b) 直流/交流系数表号 1字节 高4位:直流分量使用的哈夫曼树编号

低4位:交流分量使用的哈夫曼树编号

④ 压缩图像数据

a)谱选择开始 1字节 固定值0x00

b)谱选择结束 1字节 固定值0x3F

c)谱选择 1字节 在基本JPEG中总为00

本标记段中,字段③应该重复出现,有多少个颜色分量(字段②),就出现多少次(一般为

3次)。本段结束后,紧接着就是真正的图像信息了。图像信息直至遇到一个标记代码就自

动结束,一般就是以EOI标记表示结束。

EOI,End of Image,图像结束 2字节

标记代码 2字节 固定值0xFFD9

这里补充说明一下,由于在JPEG文件中0xFF具有标志性的意思,所以在压缩数据流(真正

的图像信息)中出现0xFF,就需要作特别处理。具体方法是,在数据0xFF后添加一个没有意

义的0x00。换句话说,如果在图像数据流中遇到0xFF,应该检测其紧接着的字符,如果是

1)0x00,则表示0xFF是图像流的组成部分,需要进行译码;

2)0xD9,则与0xFF组成标记EOI,则图像流结束,同时图像文件结束;

3)0xD0~0xD7,则组成RSTn标记,则要忽视整个RSTn标记,即不对当前0xFF和紧接的0xDn

两个字节进行译码,并按RST标记的规则调整译码变量;

3)0xFF,则忽视当前0xFF,对后一个0xFF再作判断;

4)其他数值,则忽视当前0xFF,并保留紧接的此数值用于译码。

二、JPEG图像的编码

一、bmp文件的读取

1:这里首先把bmp文件读取到一个数组中,并且进行预处理,预处理包括把行和列全部变

成以8为单位的数据,因为进行DCT变化时,需要以8*8的数据单元为最小的单元。由于

要变成以8为单元,所以可能会多余或者少一部分,对于新增的部分,需要进行对其的数据

进行填充,这里选择填充就是最后一行或者列的数据。

2:还有就是对数据单元上下倒置,就是把第一行和最后一行交换,第二行和倒数第二行交

换,目的是翻转图像,这样做的目的是因为bmp文件在保存时,保存的形式是BGR的形式,

但是实际上需要转化成为RGB的格式,所以整个把图像倒转过来,就实现了BGR和RGB的

转化。

3:转化的主要的代码如下:

void load_bitmap(char *bitmap_name, WORD *width_original, WORD *height_original)

{

WORD widthDiv8, heightDiv8; //最近的8的整数位

BYTE nr_fillingbytes;//在bmp文件中的填充字节

colorRGB lastcolor;

WORD column;

BYTE TMPBUF[256];

WORD nrline_up, nrline_dn, nrline;

WORD dimline;

colorRGB *tmpline;

FILE *fp_bitmap = fopen(bitmap_name,"rb");

width = (WORD)TMPBUF[19]*256+TMPBUF[18];//确定位图的宽度和高度

height = (WORD)TMPBUF[23]*256+TMPBUF[22];

// 保存原始图像的数据

*width_original = width;

*height_original = height;

if (width%8 != 0) //把高度和宽度数据变成8的整数关系数

widthDiv8 = (width/8)*8+8;

else

widthDiv8 = width;

if (height%8 != 0)

heightDiv8 = (height/8)*8+8;

else

heightDiv8 = height;

//分配相应的存储空间,用于保存所有的数据。

RGB_buffer = (colorRGB *)(malloc(3*widthDiv8*heightDiv8));

if (RGB_buffer == NULL)

exitmessage("Not enough memory for the bitmap image.");

//Windows默认的扫描的最小单位是4字节,所以这里判断剩余的要扫描的数量。

if ( (width*3)%4 != 0)

nr_fillingbytes = 4 - ( (width*3)%4);

else

nr_fillingbytes = 0;

for (nrline=0; nrline

{

fread(RGB_buffer + nrline*widthDiv8, 1, width*3, fp_bitmap);

fread(TMPBUF, 1, nr_fillingbytes, fp_bitmap);

// 填充X多出来的部分

memcpy(&lastcolor, RGB_buffer + nrline*widthDiv8 + width-1, 3);

for (column=width; column

memcpy(RGB_buffer+nrline*widthDiv8+column, &lastcolor, 3);

}

width = widthDiv8;

dimline = width*3;

tmpline = (colorRGB *)malloc(dimline);

// 把图像翻转

for (nrline_up=height-1,nrline_dn=0; nrline_up>nrline_dn; nrline_up--,nrline_dn++)

{

memcpy(tmpline, RGB_buffer+nrline_up*width, dimline);

memcpy(RGB_buffer+nrline_up*width, RGB_buffer+nrline_dn*width, dimline);

memcpy(RGB_buffer+nrline_dn*width, tmpline, dimline);

}

//填充X多出来的部分

memcpy(tmpline, RGB_buffer+(height-1)*width, dimline);

for (nrline=height; nrline

memcpy(RGB_buffer+nrline*width, tmpline, dimline);

height = heightDiv8;

free(tmpline);

fclose(fp_bitmap);

}

二、压缩算法的核心数据结构和初始化

1:核心数据结构

首先就是两个量化表,色度量化表和亮度量化表,分别用std_luminance_qt[64] 和

std_chrominance_qt[64]表示:这两个数组中的值都是给定的,一般就是这样的压缩比。

std_luminance_qt[64]= {16, 11, 10, 16, 24, 40, 51, 61,

12, 12, 14, 19, 26, 58, 60, 55,

14, 13, 16, 24, 40, 57, 69, 56,

14, 17, 22, 29, 51, 87, 80, 62,

18, 22, 37, 56, 68, 109, 103, 77,

24, 35, 55, 64, 81, 104, 113, 92,

49, 64, 78, 87, 103, 121, 120, 101,

72, 92, 95, 98, 112, 100, 103, 99};

static BYTE std_chrominance_qt[64] = {

17, 18, 24, 47, 99, 99, 99, 99,

18, 21, 26, 66, 99, 99, 99, 99,

24, 26, 56, 99, 99, 99, 99, 99,

47, 66, 99, 99, 99, 99, 99, 99,

99, 99, 99, 99, 99, 99, 99, 99,

99, 99, 99, 99, 99, 99, 99, 99,

99, 99, 99, 99, 99, 99, 99, 99,

99, 99, 99, 99, 99, 99, 99, 99};

直流亮度的码字数量和直流亮度的码字内容,std_dc_luminance_nrcodes[17]和

std_dc_luminance_values[12],std_dc_luminance_nrcodes[17]中2~17的16个值对应的是,这

16个数值实际意义为:没有1位的哈夫曼码字;2位的码字各有1个;3位码字有5个;4

位和5,6,7,8,9位码字各有1个;std_dc_luminance_values[12]各个数对应的权值,通

过他们两个表从而确定;另一个重要的数据结构,就是Huffman表。

std_dc_luminance_nrcodes[17]={0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0};

std_dc_luminance_values[12]={0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};

Huffman表的建立方法:

1)第一个码字必定为0。

如果第一个码字位数为1,则码字为0;

如果第一个码字位数为2,则码字为00;

如此类推。

2)从第二个码字开始,

如果它和它前面的码字位数相同,则当前码字为它前面的码字加1;

如果它的位数比它前面的码字位数大,则当前码字是前面的码字加1后再在后边添若干个0,

直至满足位数长度为止。

static BYTE std_dc_chrominance_nrcodes[17]={0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0};

static BYTE std_dc_chrominance_values[12]={0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};

色度表的Huffman的建立是对应的方法。

交流亮度和色度表的创建方法也是使用相同的方法:

std_ac_luminance_nrcodes[17]={0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d };

std_ac_luminance_values[162]= {

0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12,

0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,

0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08,

0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0,

0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16,

0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,

0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,

0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,

0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,

0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,

0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,

0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,

0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,

0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,

0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,

0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5,

0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4,

0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,

0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,

0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,

0xf9, 0xfa };

std_ac_chrominance_nrcodes[17]={0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77};

std_ac_chrominance_values[162]={

0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21,

0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71,

0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91,

0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0,

0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34,

0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26,

0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38,

0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,

0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,

0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,

0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,

0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,

0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96,

0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5,

0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4,

0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3,

0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2,

0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,

0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,

0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,

0xf9, 0xfa };

2、核心数据结构的初始化

主要是通过码字数量表和编码值建立Huffman表,算法如上面的描述,具体的代码实现如下:

首先由于要定义长短不同的码字,所以需要定义一个数据结构,用于保存这些信息,定义的

结构如下:

typedef struct { BYTE length; WORD value;} bitstring;

length主要是保存数据的位数,value主要是保存数据的值,当需要往文件中书写数据时,

首先比较value的值,当写满一个字节之后写入文件,具体的代码如下:

定义两个全局变量bytepos和bytenew,bytepos判断数据有没有写完一个字节,如果写完,

就写入文件,bytenew=0,bytepos=7,然后接着写入剩下的数据,从而把所有的数据写入到

文件中,实现长短不一的编码格式。

void writebits(bitstring bs)

{

WORD value;.//具体的数据的值

SBYTE posval;

value = ;

posval = - 1;//数据码字的长度

while (posval >= 0)

{

if (value & mask[posval]) //判断当前位是否为1

bytenew |= mask[bytepos];//写入到bytenew中。

posval--;

bytepos--;

if (bytepos < 0)

{

if (bytenew == 0xFF)

{

writebyte(0xFF);

writebyte(0);

}

else

writebyte(bytenew);

bytepos = 7;

bytenew = 0;

}

}

}

继续初始化Huffman表,代码的如下:nrcodes是码字数量,std_table是码字内容。

void compute_Huffman_table(BYTE *nrcodes, BYTE *std_table, bitstring *HT)//这里通过码字数

量和编码内容构建Huffman树

{

BYTE k,j;

BYTE pos_in_table;

WORD codevalue;

codevalue = 0;

pos_in_table = 0;

for (k=1; k<=16; k++)

{

for (j=1; j<=nrcodes[k]; j++) //判断每一个码字的数量

{

HT[std_table[pos_in_table]].value = codevalue;//如果是相同的码字,直接是+1

HT[std_table[pos_in_table]].length = k;//如果不是,就是K+1,

pos_in_table++;

codevalue++;

}

codevalue <<= 1;//并且codevalue向左平移1位。

}

}

通过这样的方法,在HT表中构建出需要编码所需要的Huffman树。

三、整个算法的流程

1:首先需要往文件写入标志位,tag位,代码如下:

writeword(0xFFD8); // 往文件中写入标志位

write_APP0info();

write_DQTinfo();

write_SOF0info();

write_DHTinfo();

write_SOSinfo();

//初始化码字位和中间值,进行主编码。

bytenew = 0; // 当前的位

bytepos = 7; // 这个字节中位的位置

main_encoder();

//写入剩余部分的编码

if (bytepos >= 0)

{

= bytepos + 1;

= (1<<(bytepos+1)) - 1;

writebits(fillbits);

}

writeword(0xFFD9); //写入结束位

前面的写入文件比较简单,这里主要介绍编码的主过程,首先是把图像数据分割成为小的

8*8的数据单元,从RGB空间转化到YCbCr空间。

void main_encoder()

{

SWORD DCY = 0, DCCb = 0, DCCr = 0; //DC coefficients used for differential encoding

WORD xpos, ypos;

for (ypos=0; ypos

{

for (xpos=0; xpos

{

load_data_units_from_RGB_buffer(xpos, ypos);// 从RGB空间转化到YCbCr空

间。

process_DU(YDU, fdtbl_Y, &DCY, YDC_HT, YAC_HT);//编码Y分量

process_DU(CbDU, fdtbl_Cb, &DCCb, CbDC_HT, CbAC_HT); //编码Cb分量

process_DU(CrDU, fdtbl_Cb, &DCCr, CbDC_HT, CbAC_HT);//编码Cr分量

}

}

}

//把数据从RGB空间转化为VCbCr空间。这里在转化时,主要是使用构建的RGB和YCbCr

的空间转化表实现,空间转化表的构建如下:

void precalculate_YCbCr_tables()//确定一个RGB和YCbCr的一个索引的关系。

{

WORD R,G,B;

for (R=0; R<256; R++)

{

YRtab[R] = (SDWORD)(65536*0.299+0.5)*R;

CbRtab[R] = (SDWORD)(65536*-0.16874+0.5)*R;

CrRtab[R] = (SDWORD)(32768)*R;

}

for (G=0; G<256; G++)

{

YGtab[G] = (SDWORD)(65536*0.587+0.5)*G;

CbGtab[G] = (SDWORD)(65536*-0.33126+0.5)*G;

CrGtab[G] = (SDWORD)(65536*-0.41869+0.5)*G;

}

for (B=0; B<256; B++)

{

YBtab[B] = (SDWORD)(65536*0.114+0.5)*B;

CbBtab[B] = (SDWORD)(32768)*B;

CrBtab[B] = (SDWORD)(65536*-0.08131+0.5)*B;

}

}

通过这个中间转化表,可以直接从RGB空间索引出YCbCr的值,从而相加平移得到对应得

值。

定义的转化宏如下:

#define Y(R,G,B) ((BYTE)( (YRtab[(R)]+YGtab[(G)]+YBtab[(B)])>>16 ) - 128)

#define Cb(R,G,B) ((BYTE)( (CbRtab[(R)]+CbGtab[(G)]+CbBtab[(B)])>>16 ) )

#define Cr(R,G,B) ((BYTE)( (CrRtab[(R)]+CrGtab[(G)]+CrBtab[(B)])>>16 ) )

void load_data_units_from_RGB_buffer(WORD xpos, WORD ypos) {

BYTE x, y;

BYTE pos = 0;

DWORD location;

BYTE R, G, B;

location = ypos * width + xpos;

for (y=0; y<8; y++)

{

for (x=0; x<8; x++)

{

R = RGB_buffer[location].R;

G = RGB_buffer[location].G;

B = RGB_buffer[location].B;

// convert to YCbCr

YDU[pos] = Y(R,G,B);

CbDU[pos] = Cb(R,G,B);

CrDU[pos] = Cr(R,G,B);

location++;

pos++;

}

location += width - 8;

}

}

完成转化之后就要处理模块单元,首先要对这个单元进行DCT变化,然后进行z形扫描,处

理直流编码和交流编码,完成数据的编码。

首先进行DCT变化,由于DCT变化本身很复杂,并且不是jpeg编码的核心,所以这里不对

其进行详细描述,然后是进行Z形扫描,Z形扫描时,使用hash的方法,首先定义一个数

据结构,通过这个数据结构查到所以值,在进行扫描。

zigzag[64]={ 0, 1, 5, 6,14,15,27,28,//类似于hash表,直接把数组转化为Z行扫描结果。

2, 4, 7,13,16,26,29,42,

3, 8,12,17,25,30,41,43,

9,11,18,24,31,40,44,53,

10,19,23,32,39,45,52,54,

20,22,33,38,46,51,55,60,

21,34,37,47,50,56,59,61,

35,36,48,49,57,58,62,63 };

在进行编码过程中SSSS的求解也是很重要的一步,这里通过构建一个对应的关系,来构建

SSSS的索引表。

void set_numbers_category_and_bitcode()

{

SDWORD nr;

SDWORD nrlower, nrupper;

BYTE cat;

category_alloc = (BYTE *)malloc(65535*sizeof(BYTE));//分配存储空间

category = category_alloc + 32767; //分配负数存储空间

bitcode_alloc=(bitstring *)malloc(65535*sizeof(bitstring));

bitcode = bitcode_alloc + 32767;

nrlower = 1;

nrupper = 2;

for (cat=1; cat<=15; cat++)

{

for (nr=nrlower; nr

{

category[nr] = cat;//保存的是前缀码索引

bitcode[nr].length = cat;

bitcode[nr].value = (WORD)nr;//保存的是后缀码

}

for (nr=-(nrupper-1); nr<=-nrlower; nr++)

{

category[nr] = cat;

bitcode[nr].length = cat;

bitcode[nr].value = (WORD)(nrupper-1+nr);

}

nrlower <<= 1;//进入下一级空间

nrupper <<= 1;

}

}

通过这些,从而扫描编码内容表,从而确定最终的编码内容表。

void process_DU(SBYTE *ComponentDU,float *fdtbl,SWORD *DC,bitstring *HTDC,bitstring

*HTAC)

{

bitstring EOB = HTAC[0x00];

bitstring M16zeroes = HTAC[0xF0];

BYTE i;

BYTE startpos;

BYTE end0pos;

BYTE nrzeroes;

BYTE nrmarker;

SWORD Diff;

fdct_and_quantization(ComponentDU, fdtbl, DU_DCT);//DU_DCT里面保存的就是DCT变

化之后的数据

for (i=0; i<64; i++)

DU[zigzag[i]]=DU_DCT[i];//然后经过Z扫描转化为扫描后的结果,

Diff = DU[0] - *DC;//先是对直流进行量化,*DC里面保存的就是上一个直流的数据。

*DC = DU[0];

if (Diff == 0)//HTDC保存的是直流的Huffman表

writebits(HTDC[0]); //Diff might be 0

else

{

writebits(HTDC[category[Diff]]);//写入前缀码

writebits(bitcode[Diff]);//写入后缀码

}

for (end0pos=63; (end0pos>0)&&(DU[end0pos]==0); end0pos--) ;//找到最后不是0的元

素,后面的数据就可以不进行处理

i = 1;

while (i <= end0pos)

{

startpos = i;

for (; (DU[i]==0) && (i<=end0pos); i++) ;//从前扫描找到第一个不是0的元素

nrzeroes = i - startpos;//这里面就是0的数量

if (nrzeroes >= 16)

{

for (nrmarker=1; nrmarker<=nrzeroes/16; nrmarker++) //就要分成两个进行处理

writebits(M16zeroes);

nrzeroes = nrzeroes%16;

}

writebits(HTAC[nrzeroes*16+category[DU[i]]]);//扫描交流Huffman表,确定编码内

容,前面的就是NNNN的值,后面的是SSSS的值

writebits(bitcode[DU[i]]);//写入后缀码的值,振幅值。

i++;

}

if (end0pos != 63)

writebits(EOB);

}

到这里就完成了从Bmp到jpeg格式的压缩,具体的可执行代码在后面附上。