1.PNG格式简介
png(便携式网络图形)
是一种采用无损压缩算法的位图格式,其设计目的是试图替代GIF和TIFF文件格式,同时增加一些GIF文件格式所不具备的特性。PNG使用从LZ77派生的无损数据压缩算法,一般应用于JAVA程序、网页或S60程序中,原因是它压缩比高,生成文件体积小。
数据块结构
PNG图像格式文件(或者称为数据流)由一个8字节的PNG文件署名(PNG file signature)域和按照特定结构组织的3个以上的数据块(chunk)组成。PNG定义了两种类型的数据块,一种是称为关键数据块(critical chunk),这是必需的数据块,另一种叫做辅助数据块(ancillary chunks),这是可选的数据块。
Critical Chunk(关键数据块),有四种类型:
IHDR,header chunk,包含有图像基本信息,作为第一个出现的数据块并且只出现一次。
PLTE,palette chunk,调色板数据块,必须存放在图像数据块之前。
IDAT,image data chunk,存储实际的图像数据。PNG数据包允许包含多个连续的图像数据块。
IEND,image trailer chunk,图像结束数据,表示PNG数据流结束。
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)。
关键数据块定义了4个标准数据块,每个PNG文件都必须包含它们,PNG读写软件也都必须要支持这些数据块。虽然PNG文件规范没有要求PNG编译码器对可选数据块进行编码和译码,但规范提倡支持可选数据块。
除上述中的限制外,各类chunk的出现顺序没有严格要求。同时在文件中的数据是按照:高位在前低位在后的顺寻存储的,这一点和BMP格式并不一致。
2.PNG文件分析
具体实例分析如图:
1.使用visual studio,用二进制编辑器,得到以十六进制表示的二进制文件数据流。
PNG文件头共8个字节,用来识别该文件是否是PNG文件:
十进制表示 | 十六进制表示 |
---|---|
137 80 78 71 13 10 26 10 | 89 50 4E 47 0D 0A 1A 0A |
2.在PNG文件中,每个数据块都由下面四部分组成,所以,每次只需要读取前8个字节,就可以大致判断出数据块的类型和长度。
(1) IHDR(文件头数据块),由13字节组成,格式如下图所示:
· 0x0D表示长度,13字节,后面四字节表明该数据块是HIDR数据块:
· 图像宽度为0x400,即1024像素;长度为0x0358,即856像素:
· 图像深度为8位,颜色类型为0x06,即图像为带a通道数据的真彩色图像,压缩方法,滤波器方法,隔行扫描方法:0x00,循环校验码 CRC:5A 67 9F 97:
(2)PLTE(调色版数据块),包含有与索引彩色图像相关的彩色变换数据,它仅与索引彩色图像有关,而且要放在图像数据块之前。PLTE可以包含1~256个调色板信息,每一个调色板信息由3个字节组成,因此,调色板的长度应该是3的倍数,否则,这将是一个非法的调色板。
这个案例是带有a通道数据的真彩色图像,没有调色板数据块。
(3)IDAT(图像数据块),存储实际的数据,在数据流中可包含多个连续顺序的图像数据块。
(4)IEND(图像结束数据),用来标记PNG文件或者数据流已经结束,并且必须要放在文件的尾部。
十六进制下表示为:00 00 00 00 49 45 4E 44 AE 42 60 82
辅助数据块:
代码:
#include <iostream>
#include <fstream>
#include <vector>
#include <map>
#include <set>
using namespace std;
struct fileHeader
{
unsigned char 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;
char type[4];
string s_type = "";
unsigned char *data;
unsigned char CRC[4];
void GetChunk( ifstream &in )
{
unsigned char* buffer = new unsigned char[4];
in.read((char*)buffer,4);
for( int i = 0; i < 4; ++ i ) { length = (length << 8) + buffer[i]; }
in.read(type,4);
if( length != 0 ) {
data = new unsigned char[length];
in.read((char *) data, length);
}
in.read((char*)CRC,4);
for( auto i : type ) s_type += (int)i;
return;
}
};
const string path = "0.png";
map< string, vector<int> > mp;
set<string> ancillary_types;
int main()
{
ifstream in(path,ios::binary);
file_header.GetHead(in);
vector<chunks> v;
int pos = -1;
while(1)
{
chunks c_tmp;
c_tmp.GetChunk(in);
v.push_back(c_tmp); pos ++;
mp[c_tmp.s_type].push_back(pos);
if(c_tmp.s_type=="IEND") break;
if(c_tmp.s_type != "IDAT"&& c_tmp.s_type != "IHDR" && c_tmp.s_type != "PLTE")
{
ancillary_types.insert(c_tmp.s_type);
}
}
cout << "All Chunks information:" << endl;
for( auto &i : mp )
{
cout << "Chunk type: " << i.first << ", Chunk position: ";
auto &v = i.second;
for( auto &j : v ) cout << j << " ";
cout << endl;
}
cout << "ancillary chunks:" << endl;
for( auto i : ancillary_types )
cout << i << endl;
}
运行结果如下: