PNG格式文件的分析

PNG格式文件分析

PNG格式简介

便携式网络图形(Portable Network Graphics,PNG)是一种采用无损压缩算法的位图格式,其设计目的是试图替代GIF和TIFF文件格式,同时增加一些GIF文件格式所不具备的特性。PNG使用从LZ77派生的无损数据压缩算法,一般应用于JAVA程序、网页或S60程序中,原因是它压缩比高,生成文件体积小。

来源:百度百科

PNG文件结构如何

PNG文件一般由两大部分组成:

  • 文件头:Filehead,用来标识这是一个PNG格式的文件。
  • 其他部分:Chunks,其他的部分则是由多个数据块组成的,存储了描述图像的相关信息。

一个标准的PNG文件结构应该如下:

FILEHEADPNG ChunksPNG Chunks

FILEHEAD的组成

根据PNG文件的定义来说,其文件头位置总是由位固定的字节来描述的:

十进制表示十六进制表示
137 80 78 71 13 10 26 1089 50 4E 47 0D 0A 1A 0A

实际上,第一个字节的数据0x89已经超过了ASCII码的范围,这是为了避免某些软件把PNG文件当作文本文件来处理

每一部分的作用如下表:

取值(HEX)含义
89表示不支持8bit的数据,并减小一个文本文件被错误地认为是PNG文件的可能性。
50 4E 47英文字符串“PNG”的ASCII码
0D 0ADOS风格的换行符(CRLF)。用于DOS-Unix数据的换行符转换。
1A在DOS命令行下,用于阻止文件显示的文件结束符。
0AUnix风格的换行符(LF)。用于Unix-DOS换行符的转换。

PNG Chunk

PNG定义了两种类型的数据块:

  • 一种是称为关键数据块(critical chunk),这是标准的数据块。
  • 一种叫做辅助数据块(ancillary chunks),这是可选的数据块。

关键数据块定义了4个标准数据块,每个PNG文件都必须包含它们,PNG读写软件也都必须要支持这些数据块。虽然PNG文件规范没有要求PNG编译码器对可选数据块进行编码和译码,但规范提倡支持可选数据块。

Critical Chunk

关键数据块有四种类型:

  • IHDR,header chunk,包含有图像基本信息,作为第一个出现的数据块并且只出现一次
  • PLTE,palette chunk,调色板数据块,必须存放在图像数据块之前
  • IDAT,image data chunk,存储实际的图像数据。PNG数据包允许包含多个连续的图像数据块
  • IEND,image trailer chunk,图像结束数据,表示PNG数据流结束。

除上述中的限制外,各类chunk的出现顺序没有严格要求。同时在文件中的数据是按照:高位在前低位在后的顺寻存储的,这一点和BMP格式并不一致。

Ancillary Chunk

辅助数据块的类型众多,PNG文件格式规范制定了10个辅助数据块:

  • 背景颜色数据块bKGD(background color)。
  • 基色和白色度数据块cHRM(primary chromaticities and white point)。
  • 图像γ数据块gAMA(image gamma)。
  • 图像直方图数据块hIST(image histogram)。
  • 物理像素尺寸数据块pHYs(physical pixel dimensions)。
  • 样本有效位数据块sBIT(significant bits)。
  • 文本信息数据块tEXt(textual data)。
  • 图像最后修改时间数据块tIME (image last-modification time)。
  • 图像透明数据块tRNS (transparency)。
  • 压缩文本数据块zTXt (compressed textual data)。

如何获取PNG的元数据信息

在PNG文件中,每个数据块都由下面四部分组成:

名称字节数说明
Length(长度)4 bytes指定数据块中数据域的长度,其长度不超过( 2 31 − 1 2^{31} - 1 2311)字节
Chunk Type Code(数据块类型码)4 bytesASCII码字母组成
Chunk Data(数据块数据)可变长度存储按照Chunk Type Code指定的数据
CRC(循环冗余检测)4 bytes检错

所以,我们每次只需要读取前8个字节,就可以大致判断出数据块的类型和长度。

下面我们具体分析一下PNG各个关键数据块的结构:

IHDR

文件头数据块IHDR(header chunk):它包含有PNG文件中存储的图像数据的基本信息,并要作为第一个数据块出现在PNG数据流中,而且一个PNG数据流中只能有一个文件头数据块。
文件头数据块由13字节组成,它的格式如下表所示。

名称字节数说明
width4 bytes图像宽度,以像素为单位
Height4 bytes图像高度,以像素为单位
Bit depth1 bytes图像深度:
索引彩色图像:1,2,4或8
灰度图像:1,2,4,8或16
真彩色图像:8或16
ColorType1 byte颜色类型:
0:灰度图像, 1,2,4,8或16
2:真彩色图像,8或16
3:索引彩色图像,1,2,4或8
4:带α通道数据的灰度图像,8或16
6:带α通道数据的真彩色图像,8或16
Compression method1 byte压缩方法(LZ77派生算法)
Filter method1 byte滤波器方法
Interlace method1 byte隔行扫描方法:
0:非隔行扫描
1: Adam7(由Adam M. Costello开发的7遍隔行扫描方法)

PLTE

调色板数据块PLTE(palette chunk)包含有与索引彩色图像(indexed-color image)相关的彩色变换数据,它仅与索引彩色图像有关,而且要放在图像数据块(image data chunk)之前。
PLTE数据块是定义图像的调色板信息,PLTE可以包含1~256个调色板信息,每一个调色板信息由3个字节组成:

颜色字节意义
R1 byte0 = 黑
255 = 红
G1 byte0 = 黑
255 = 绿
B1 byte0 = 黑
255 = 蓝

因此,调色板的长度应该是3的倍数,否则,这将是一个非法的调色板。
对于索引图像,调色板信息是必须的,调色板的颜色索引从0开始编号,然后是1、2……,调色板的颜色数不能超过色深中规定的颜色数(如图像色深为4的时候,调色板中的颜色数不可以超过 2 4 = 16 2^4=16 24=16),否则,这将导致PNG图像不合法。
真彩色图像和带 α \alpha α通道数据的真彩色图像也可以有调色板数据块,目的是便于非真彩色显示程序用它来量化图像数据,从而显示该图像。

IDAT

图像数据块IDAT(image data chunk):它存储实际的数据,在数据流中可包含多个连续顺序的图像数据块。
IDAT存放着图像真正的数据信息,因此,如果能够了解IDAT的结构,我们就可以很方便的生成PNG图像。

IEND

图像结束数据IEND(image trailer chunk):它用来标记PNG文件或者数据流已经结束,并且必须要放在文件的尾部。
十六进制下表示为:
00 00 00 00 49 45 4E 44 AE 42 60 82

实例分析

下面通过一张PNG图片为例进行分析:

图片中有哪些关键数据块?

File header

在这里插入图片描述
如前文所述,前8个字节为:89 50 4E 47 0D 0A 1A 0A

IHDR
名称实际数据说明
Length(长度)在这里插入图片描述数据块长度为13
Chunk Type Code在这里插入图片描述数据块类型为IHDR
width在这里插入图片描述图像宽度为981
Height在这里插入图片描述图像高度为1034
Bit depth在这里插入图片描述图像深度为8位
ColorType在这里插入图片描述颜色类型为3,是索引彩色图像
Compression method在这里插入图片描述无压缩
Filter method在这里插入图片描述滤波器方法0
Interlace method在这里插入图片描述0:非隔行扫描
CRC在这里插入图片描述CRC检错
PLTE

8位深度图,索引彩色图像,所以肯定存在调色板。

名称实际结果说明
Length在这里插入图片描述数据块长度为768
Chunk Type Code在这里插入图片描述数据块类型为PLTE
Chunk Data太多了不放了…实际数据
IDAT

我们以第一个IDAT块为例:

名称实际数据说明
Length在这里插入图片描述数据块长度为160695
Chunk Type Code在这里插入图片描述数据块类型为IDAT
Chunk data太多了也不放了…数据块中的实际数据
IEND

在这里插入图片描述
如图所示,和上面的描述是一致的。

图片中是否存在辅助数据块?

我们可以通过下面的代码来找出辅助数据块:

#include <iostream>
#include <fstream>
#include <cstdio>
#include <vector>
#include <map>
#include <set>
#define uchar unsigned char

using namespace std;

const string path = "test.png";
map<string, vector<int> > mp;
set <string> ancillary_chunk;

struct FileHeader
{
    uchar head[8];
    void GetHead(ifstream &in) {
        in.read((char *)head, 8);
    }
    void Print() {
        for (auto i : head) cout << (int) i << ' ';
        cout << endl;
    }
}file_header;

struct Chunks
{
    unsigned int length = 0;
    uchar type[4];
    string c_type = "";
    uchar *data;
    uchar CRC[4];
    void GetChunk(ifstream & in) {
        uchar * buffer = new uchar[4];
        in.read((char *)buffer, 4);
        for (int i = 0;i < 4;i ++) 
            length = (length << 8) + buffer[i];
        in.read((char *)type, 4);
        if (length) {
            data = new uchar[length];
            in.read((char*)data, length);
        }
        in.read((char *)CRC, 4);
        for (auto i : type) c_type += (int)i;
        return ;
    }
}chunk;

int main()
{
    ifstream in(path, ios :: binary);
    file_header.GetHead(in);
    int pos = 0;
    while (true) {
        Chunks tmp;
        tmp.GetChunk(in);
        mp[tmp.c_type].push_back(pos);
        pos ++;
        if (tmp.c_type == "IEND") break;
        if (tmp.c_type != "IDAT" && tmp.c_type != "IHDR" && tmp.c_type != "PLTE") ancillary_chunk.insert(tmp.c_type);
        delete(tmp.data);
    }
    cout << "All Chunks" << endl;
    for (auto &i : mp) {
        cout << "Chunk Type :" << i.first  << endl;
    }
    cout << "Ancillary Chunks" << endl;
    for (auto &i : ancillary_chunk) cout << i << endl;
}

最后的结果如下:

All Chunks
Chunk Type :IDAT
Chunk Type :IEND
Chunk Type :IHDR
Chunk Type :PLTE
Chunk Type :tRNS
Ancillary Chunks
tRNS

有一个辅助数据块tRNS,是图像透明数据块。
可以在二进制数据中找到他的位置和数据内容:
在这里插入图片描述

总结

为了便于数据的交换、管理、编辑和呈现,PNG文件使用了数据块这种形式。

数据块的结构简单,并且可以很方便的对每块信息进行修改、存储、编辑;同时为了方便显示,只要求解码器可以识别关键数据块,这对兼容性的实现有莫大帮助,即以前的版本的解码器依旧可以打开新版本的图片,只是损失部分新的特征,而核心内容的显示是完全没问题的;同时可以非常方便的对文件进行拓展,甚至可以自己定义一些信息,一些对图像的操作、存储一些其他数据——只要自己再编写出相应的解码器即可。

参考文献

[1] PNG文件分析
[2] W3C.Portable Network Graphics (PNG) Specification (Second Edition)

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CUCKyrie

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值