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格式的压缩,具体的可执行代码在后面附上。


发布评论