【数据压缩】PNG格式文件分析

  • Post author:
  • Post category:其他




一、PNG格式基础

PNG格式文件结构很清晰,仅由两部分构成:(1)文件头 (2)文件数据块(chunk)



(1)文件头

PNG的文件头为8个字节:

89 50 4E 47 0D 0A 1A 0A (16进制)

文件头是固定的,用以标明该文件是一个PNG格式文件。



(2)Chunk数据块

PNG格式文件中,除了文件头都是chunk数据块,不同的数据块在文件中起到不同的作用。



数据块结构

每个数据块由以下结构组成:

名称 字节数 备注
Length 4 Chunk Data字段的长度
Chunk Type Code 4 标志数据块的类型(ASCII码)
Chunk Data 由Length决定 数据
CRC 4 循环冗余检测

因此,一个数据块的长度等于



12

+

L

e

n

g

t

h

12+Length






1


2




+








L


e


n


g


t


h






数据块分类:

按照标准分为

关键数据块



辅助数据块

两种。只有

关键数据块

是必要的。

下表列出了关键数据块:

数据块符号 数据块名称
IHDR 文件头数据块
IDAT 图像数据块
IEND 图像结束数据块
PLTE 调色板数据块

一个真彩色PNG文件必须包含以上关键数据块的前三种,而伪彩色PNG图片还必须包含PLTE数据块,以携带调色板信息。

其它非关键数据块写到备忘里。



文件头数据块

IHDR是文件头数据块,描述了图片的一些元数据,13字节的数据分布如下:

域名 字节数 备注
Width 4 图像宽度
Height 4 图像高度
Bit depth 1 比特深度
ColorType 1 表明图像的颜色类型:

0:灰度图像;2:真彩色图像;3:索引彩色图像

Compression method 1 压缩方法
Filter method 1 滤波器方法
Interlace method 1 非隔行扫描为0、隔行为1



图像数据块

IDAT是图像数据块,存放着图像真正的数据信息。



图像结束数据块

IEND是图像结束数据块,它标志着图像的结束。

没有真正的数据内容,因此Length字段为0,所以,IEND数据块总是由以下字符组成:

00 00 00 00 49 45 4E 44 AE 42 60 82



调色板数据块

一个8bit伪彩色图像必须携带调色板数据块,它的数据部分由



2

8

2^8







2










8












个以下结构构成:

颜色 字节
Red 1 byte
Green 1 byte
Blue 1 byte

根据调色板信息,图像数据可以索引得到对应的24bit RGB颜色信息。



二、案例分析

使用opencv-python得到一张10*10的PNG图像以便于分析:

import cv2
img=cv2.imread("input.bmp")
img1=cv2.resize(img,(10,10))
cv2.imwrite("output.png",img1)



(1)二进制文件分析

使用FlexHEX软件分析其组成,如图:

在这里插入图片描述

可以看到该png文件由四部分构成:

  • 文件头——红色背景
  • Chunk1——绿色背景
  • Chunk2——白色背景
  • Chunk3——蓝色背景

根据png数据块的知识,可以判断出三个chunk分别对应着IHDR(文件头数据块)、IDAT(图片信息数据块)和IEND(图片结束数据块),且本图为真彩色图片,没有调色板数据块。



(2)编程验证和元数据读取

使用C++编写程序,读上面的PNG文件。



主函数读取文件头和数据块

int main()
{
	//打开文件、获取文件长度
    FILE* file1;
    fopen_s(&file1, "2.png", "rb");
    fseek(file1, 0, SEEK_END);
    int len = ftell(file1);
    fseek(file1, 0, SEEK_SET);

    //读入buffer
    unsigned char* buffer = new unsigned char[len];
    fread(buffer, sizeof(unsigned char),len , file1);
    fclose(file1);

    unsigned char* p = buffer;
    //读文件头

    cout << "文件头:" << endl;
    unsigned char* head = new unsigned char[8];
    for (int i = 0; i < 8; i++)
    {
        head[i] = *(p + i);
        cout << int(head[i]) << " ";
    }
    cout << endl;
    p += 8;

    //读chunks
    int count = 0;
    
    while (p < len+buffer)
    {
        //Length字段:
        cout << "------------------" << endl;
        count += 1;
        cout << "Chunk" << count<<":"<<endl;
        int chunkLen = 0;
        for (int i = 0; i < 4; i++)
        {
            chunkLen += *(p + i) << ((3 - i) * 8);
        }
        cout << "Chunk Data Length:" << chunkLen<<endl;
        p += 4;

        //Chunk Type Code字段:
        string chunkType;
        for (int i = 0; i < 4; i++)
        {
            char ch = *(p + i);
            string st="";
            st = ch;
            chunkType.append(st);
        }
        cout << "Chunk Type:"<< chunkType << endl;
        p += 4;

        //Chunk Data字段
        unsigned char* chunkData = new unsigned char[chunkLen];
        for (int i = 0; i < chunkLen; i++) 
        {
           chunkData[i] = *(p + i);
        }
        if (chunkType == "IHDR")
        {
            readMetaData(chunkData);
        }
        p += chunkLen;

        //CRC字段
        //没有写
        p += 4;
    }
    return 0;
}

如果数据块类型是”IHDR“则读进入

readMetaData

函数读元数据



元数据读取readMetaData()

根据第一部分中png格式IHDR数据块的规则读取图片信息并输出。

void readMetaData(unsigned char *buf) {
    cout << "=========Meta Data======="<<endl;
    unsigned char *p = buf;
    int width = 0;
    for (int i = 0; i < 4; i++)
    {
        width += *(p + i) << ((3 - i) * 8);
    }
    p += 4;
    int height = 0;
    for (int i = 0; i < 4; i++)
    {
        height += *(p + i) << ((3 - i) * 8);
    }
    p += 4;
    int bitDepth = *p;
    p += 1;
    int colorType = *p;
    p += 1;
    string colorT="";
    switch (colorType)
    {
    case 0:
        colorT = "灰度图像";
        break;
    case 2:
        colorT = "真彩图像";
        break;
    case 3:
        colorT = "索引图像";
        break;
    } 
    int compressionMethod = *p;
    p += 1;
    int filterMethod = *p;
    p += 1;
    int interlaceMethod = *p;
    string interM = "";
    switch (interlaceMethod)
    {
    case 0:
        interM = "非隔行扫描";
        break;
    case 1:
        interM = "隔行扫描";
        break;
    }
    cout << "宽度(Width):" << width << endl;
    cout << "高度(Height):" << height << endl;
    cout << "比特位深(Bit Depth):" << bitDepth << endl;
    cout << "色彩类型(Color Type):" << colorType <<" "<< colorT<< endl;
    cout << "压缩类型(Compression Method):" << compressionMethod << endl;
    cout << "滤波方法(Filter Method):" << filterMethod << endl;
    cout << "隔行方法(Interlace Method):" << interlaceMethod <<" "<<interM<< endl;
    cout << "======================="<<endl;
}



验证结果:

执行程序,得到结果如下:

在这里插入图片描述

根据结果,该PNG文件由文件头和三个chunk数据块构成,分别是IHDR、IDAT、IEND。

没有调色板数据块,元数据中色彩类型为2,即真彩色图像。

宽度为10,高度为10,位深为8,非隔行扫描。

实验结果与二进制文件的分析一致。



三、作业总结:

  • png文件的数据结构比较简单有条理,方便读取信息
  • 元数据中还有压缩类型和滤波方法这些暂时还没弄清楚



备忘:PNG其它可能数据块的信息

在这里插入图片描述

来源:

PNG文件格式详解



版权声明:本文为hdkks原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。