核心思路是找到图像数据部分,根据调色板,找到数据部分后解码,处理为rgb后利用先前的rgb2yuv子函数去处理。
png文件开头8个字节为固定字节。目的是区分于其他文件,表明本文件是,其中第一个字节0x89超出了ASCII字符的范围,这是为了避免某些软件将PNG文件当做文本文件来处理。
十进制 | 137 80 78 71 13 10 26 10 |
---|---|
十六进制数 | 89 50 4E 47 0D 0A 1A 0A |
开头读出文件前八个字节,验明正身。c语言以16进制输出时略去了高字节的0。
fread(head, 1, 8, pngFile);
for (int i = 0; i < 8; i++) {
printf("%x",*(head+i));//输出头八个字节判断是否为png文件,应为89504E470D0A1A0A
}
由之前的了解我们可以知道png文件格式定义了两种类型的数据块,一种是称为关键数据块(critical chunk),这是标准的数据块,另一种叫做辅助数据块(ancillary chunks),这是可选的数据块。这一步就是类似于ts包分析,由于所有的chunk都具有共同特征结构如下
chunk | 长度 | 数据块名称 | 数据 | CRC校验码 |
---|---|---|---|---|
字节 | 4 | 4 | 待定 | 4 |
由于结构已知,我们可以统计数据长度和名称,并将每个块数据部分存入对应的缓存区,使用指针数组实现。之前定义了chunkcontent数组用来存放指针
for (int i = 0; i < 1000; i++) {
unsigned length = 0;
unsigned char* crc = NULL;
crc = (unsigned char*)malloc(4);
length = assesschunk(pngFile);
if (length == 0) {
break;
}
chunkcontent[i]= (unsigned char*)malloc(length);
fread(chunkcontent[i], 1, length, pngFile);
fread(crc, 1, 4, pngFile);
}
在分析数据块部分调用了子函数,输出块的名称及长度,返回长度值,方便读入缓存区。
```c
unsigned int assesschunk(FILE*filename) {
unsigned char* chunklength = NULL;
unsigned int length;
chunklength = (unsigned char*)malloc(4);
fread(chunklength, 1, 4, filename);
length = unsigned int(*(chunklength)) * 16777216 + unsigned int(*(chunklength + 1)) * 65536 + unsigned int(*(chunklength + 2)) * 256 + unsigned int(*(chunklength + 3));
printf("\n");
printf("%u\n", length);//判断长度
char* chunktype = NULL;
chunktype = ( char*)malloc(4);
fread(chunktype, 1, 4, filename);
for (int i = 0; i < 4; i++) {
putchar(*(chunktype + i));
}//判断类型
//chunkcontent_inside = buf;
//chunkcontent_inside = (unsigned char*)malloc(length);
//fread(chunkcontent_inside, 1, length, filename);
//忽略crc,指针指向下一个chunk的头部
return(length);
}
名称 | 长度 | 功能 |
---|---|---|
Width | 4 bytes | 图像宽度,以像素为单位 |
Height | 4 bytes | 图像高度,以像素为单位 |
Bit depth | 1 byte | 图像深度: 索引彩色图像:1,2,4或8 灰度图像:1,2,4,8或16 真彩色图像:8或16 |
ColorType | 1 byte | 颜色类型: 0:灰度图像, 1,2,4,8或16 2:真彩色图像,8或16 3:索引彩色图像,1,2,4或8 4:带α通道数据的灰度图像,8或16 6:带α通道数据的真彩色图像,8或16 |
Compression method | 1 byte | 压缩方法(LZ77派生算法) |
Filter method | 1 byte | 滤波器方法 |
Interlace method | 1 byte | 隔行扫描方法: 0:非隔行扫描 1: Adam7(由Adam M. Costello开发的7遍隔行扫描方法) |
由于规定要求IDHR是第一个数据块,长度一致,正好可以测试我们的chunkassess函数是否正常。
读出本次使用的png文件有IHDR数据块长度13字节,还有一个phys数据块是物理像素尺寸数据块,我们这次实验用不到,先不管他。
后续输出显示本次实验还有27个IDAT数据块用于存放压缩后的图像数据,其中26个满长度,长度为8192个字节,最后一个不满长度为3758个字节。最后是标志着图像结束的IEND数据块。
读取数据后,进行图像数据的分析,采用结构体的方式。在结构体内写子函数负责结构体成员的赋值与输出。
struct ihdr {
unsigned int width, height;
unsigned char Bit_depth, ColorType, Compression_method, Filter_method, Interlace_method;
void ihdr_value(unsigned char* content) {
width = unsigned int(*(content)) * 16777216 + unsigned int(*(content + 1)) * 65536 + unsigned int(*(content + 2)) * 256 + unsigned int(*(content + 3));
height = unsigned int(*(content+4)) * 16777216 + unsigned int(*(content + 5)) * 65536 + unsigned int(*(content + 6)) * 256 + unsigned int(*(content + 7));
Bit_depth = *(content + 8);
ColorType = *(content + 9);
Compression_method = *(content + 10);
Filter_method = *(content + 11);
Interlace_method = *(content + 12);
}
void show() {
printf(" width, height, Bit_depth, ColorType, Compression_method, Filter_method, Interlace_method\n");
printf("%d,%d,%d,%d,%d,%d,%d\n", width, height, Bit_depth, ColorType, Compression_method, Filter_method, Interlace_method);
}
}ihdr1;
输出结果如下
表示2084*2084像素,8表示为真彩色图像,6表示此图片为带a数据通道的真彩色图像。这也是为什么没有找到调色板数据块的原因。000表示压缩方法,滤波方法皆为默认,隔行扫描为无。
理论上IDAT数据块的数据是按顺序排列在27个数据块中的,我们将它按顺序全部读入一个缓冲文件中,等待解压。
unsigned char* IDATdata= (unsigned char*)malloc(8192*26+3758);
FILE* idatFile = NULL;
idatFile = fopen("idat.txt", "wb");
if (!idatFile)
printf("open idat fail");
else
printf("create idat success\n");//暂时存放idat
for (int i = 2; i < 28; i++) {
fwrite(chunkcontent[i], 1, 8192, idatFile);
}
fwrite(chunkcontent[28], 1,3758, idatFile);
根据查询,png图像文件采用的LZ77压缩算法可以使用zlib库中的UNcompress函数进行解压,接下来进行库的编译和使用。
在官网下载完zlib库后,进行编译,得到lib文件。动态链接主要进行下面三步后就可以使用compress函数了。
fwrite(chunkcontent[28], 1,3758, idatFile);
fread(IDATdata, 1, 8192 * 26 + 3758, idatFile);
unsigned char* newdata = (unsigned char*)malloc(width*height *4);
uLongf newlength = width * height * 4;
uncompress(newdata, &(newlength), IDATdata, 8192 * 26 + 3758);
24位真彩色+a通道理论上是一个像素4个字节,我们解压缩的空间因此暂定为width * height * 4。
解压后是rgb+alpha的32位格式,我们要写一个a通道子函数处理得到不带透明度信息的rgb文件。如果直接舍弃a通道,输出的rgb图如下:
图片因为yuv查看器的原因是倒着的,说明解压缩的数据应该是无误的,但颜色完全没有了,可能与a通道数据丢失有关,考虑根据
output = alpha * foreground + (1-alpha) * background计算复合的rgb信号,未完…