PNG格式详解:https://blog.mythsman.com/post/5d2d62b4a2005d74040ef7eb/
LibPNG的使用:https://blog.csdn.net/dreamInTheWorld/article/details/55805901
PNG是20世纪90年代中期开始开发的图像文件存储格式,其目的是替代GIF和TIFF文件格式,同时增加一些GIF文件格式所不具备的特性。流式网络图形格式(Portable Network Graphic Format,PNG)名称来源于非官方的“PNG’s Not GIF”,是一种位图文件(bitmap file)存储格式,读成“ping”。PNG用来存储灰度图像时,灰度图像的深度可多到16位,存储彩色图像时,彩色图像的深度可多到48位,并且还可存储多到16位的α通道数据。PNG使用从LZ77派生的无损数据压缩算法。(说白了这就是一种方便的、适于网络传播的轻便图片文件格式)
PNG图像格式文件由文件署名和数据块(chunk)组成。
8字节的PNG文件署名域用来识别该文件是不是PNG文件。该域的值是:
十进制数 | 十六进制数 |
---|---|
137 | 89 |
80 | 50 |
78 | 4e |
71 | 47 |
13 | 0d |
10 | 0a |
26 | 1a |
10 | 0a |
这个文件署名就是在《利用文件头标志判断文件类型》中提到的文件头标志了,很简单。
这里有两种类型的数据块,一种是称为关键数据块(critical chunk),就是必须要有的块;另一种叫做辅助数据块(ancillary chunks)。
每个数据块都由下表所示的的4个域组成。
名称 | 字节数 | 说明 |
---|---|---|
Length(长度) | 4字节 | 指定数据块中数据域的长度,其长度不超过(231−1)(231−1)字节 |
Chunk Type Code(数据块类型码) | 4字节 | 数据块类型码由ASCII字母(A-Z和a-z)组成 |
Chunk Data(数据块实际内容 | 可变长度 | 存储按照Chunk Type Code指定的数据 |
CRC(循环冗余检测 | 4字节 | 存储用来检测是否有错误的循环冗余码 |
其中CRC(cyclic redundancy check)域中的值是对Chunk Type Code域和Chunk Data域中的数据进行计算得到的,可以看做一种校验码。
关键数据块中的4个标准数据块是:
(1) 文件头数据块IHDR(header chunk):
它包含有PNG文件中存储的图像数据的基本信息,并要作为第一个数据块出现在PNG数据流中,而且一个PNG数据流中只能有一个文件头数据块。
文件头数据块由13字节,组成结构如下:
域的名称 | 字节数 | 说明 |
---|---|---|
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或84:带α通道数据的灰度图像,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遍隔行扫描方法) |
(2) 调色板数据块PLTE(palette chunk):
它包含有与索引彩色图像((indexed-color image))相关的彩色变换数据,它仅与索引彩色图像有关,而且要放在图像数据块(image data chunk)之前。真彩色的PNG数据流也可以有调色板数据块,目的是便于非真彩色显示程序用它来量化图像数据,从而显示该图像。结构如下:
|颜色|字节|意义|
|Red|1 byte||0 = 黑色, 255 = 红|
|Green|1 byte||0 = 黑色, 255 = 绿色|
|Blue|1 byte||0 = 黑色, 255 = 蓝色|
PLTE数据块是定义图像的调色板信息,PLTE可以包含1~256个调色板信息,每一个调色板信息由3个字节组成,因此调色板数据块所包含的最大字节数为768,调色板的长度应该是3的倍数,否则,这将是一个非法的调色板。
对于索引图像,调色板信息是必须的,调色板的颜色索引从0开始编号,然后是1、2……,调色板的颜色数不能超过色深中规定的颜色数(如图像色深为4的时候,调色板中的颜色数不可以超过2^4=16),否则,这将导致PNG图像不合法。
(3) 图像数据块IDAT(image data chunk):
它存储实际的数据,在数据流中可包含多个连续顺序的图像数据块。
IDAT存放着图像真正的数据信息,因此,如果能够了解IDAT的结构,我们就可以很方便的生成PNG图像。
(4) 图像结束数据IEND(image trailer chunk):
它用来标记PNG文件或者数据流已经结束,并且必须要放在文件的尾部。
如果我们仔细观察PNG文件,我们会发现,文件的结尾12个字符看起来总应该是这样的:
00 00 00 00 49 45 4E 44 AE 42 60 82
不难明白,由于数据块结构的定义,IEND数据块的长度总是0(00 00 00 00,除非人为加入信息),数据标识总是IEND(49 45 4E 44),因此,CRC码也总是AE 42 60 82。
最后,除了表示数据块开始的IHDR必须放在最前面, 表示PNG文件结束的IEND数据块放在最后面之外,其他数据块的存放顺序没有限制。
(比较杂,不需要全部了解透)
PNG文件格式规范制定的10个辅助数据块是:
关键数据块、辅助数据块和专用公共数据块(special-purpose public chunks)综合下表中:
数据块符号 | 数据块名称 | 多数据块 | 可选否 | 位置限制 |
---|---|---|---|---|
IHDR | 文件头数据块 | 否 | 否 | 第一块 |
cHRM | 基色和白色点数据块 | 否 | 是 | 在PLTE和IDAT之前 |
gAMA | 图像γ数据块 | 否 | 是 | 在PLTE和IDAT之前 |
sBIT | 样本有效位数据块 | 否 | 是 | 在PLTE和IDAT之前 |
PLTE | 调色板数据块 | 否 | 是 | 在IDAT之前 |
bKGD | 背景颜色数据块 | 否 | 是 | 在PLTE之后IDAT之前 |
hIST | 图像直方图数据块 | 否 | 是 | 在PLTE之后IDAT之前 |
tRNS | 图像透明数据块 | 否 | 是 | 在PLTE之后IDAT之前 |
oFFs | (专用公共数据块) | 否 | 是 | 在IDAT之前 |
pHYs | 物理像素尺寸数据块 | 否 | 是 | 在IDAT之前 |
sCAL | (专用公共数据块) | 否 | 是 | 在IDAT之前 |
IDAT | 图像数据块 | 是 | 否 | 与其他IDAT连续 |
tIME | 图像最后修改时间数据块 | 否 | 是 | 无限制 |
tEXt | 文本信息数据块 | 是 | 是 | 无限制 |
zTXt | 压缩文本数据块 | 是 | 是 | 无限制 |
fRAc | (专用公共数据块) | 是 | 是 | 无限制 |
gIFg | (专用公共数据块) | 是 | 是 | 无限制 |
gIFt | (专用公共数据块) | 是 | 是 | 无限制 |
gIFx | (专用公共数据块) | 是 | 是 | 无限制 |
IEND | 图像结束数据 | 否 | 否 | 最后一个数据块 |
tEXt和zTXt数据块中的标准关键字:
关键字 | 说明 |
---|---|
Title | 图像名称或者标题 |
Author | 图像作者名 |
Description | 图像说明 |
Copyright | 版权声明 |
CreationTime | 原图创作时间 |
Software | 创作图像使用的软件 |
Disclaimer | 弃权 |
Warning | 图像内容警告 |
Source | 创作图像使用的设备 |
Comment | 各种注释 |
为了便于研究,我在本地找了个24x24像素的图片:
用十六进制打开后是这样的:
|
|
接下来我们试着分析一下:
首先是八个字节的文件头标志,标识着png文件:
|
|
接下来的地方就是IHDR数据块了:
0000 000d
说明IHDR头块长为13
4948 4452
IHDR标识(ascii码为IHDR)
下面是IHDR数据块的实际内容
0000 0018
图像的宽,24像素
0000 0018
图像的高,24像素
08
表示色深,这里是2^8=256,即这是一个256色的图像
06
颜色类型,查表可知这是带α通道数据的真彩色图像
00
PNG Spec规定此处总为0(非0值为将来使用更好的压缩方法预留),表示使压缩方法(LZ77派生算法)
00
同上
00
非隔行扫描
e0 773d f8
CRC校验
以上分析了第一个IHDR块的内容,其他块的分析方法类似,比如接下来的就是tEXt块了,很简单,不做分析了。(当然这里还有重要的IDAT块,这是图像的实际内容)
最后得有个IEND数据块,这部分正如上所说,通常都应该是
00 00 00 00 49 45 4E 44 AE 42 60 82
LibPNG是一款C语言编写的比较底层的读写PNG文件的跨平台的库。借助它,你可以轻松读写PNG文件的每一行像素。
因为PNG文件是经过压缩而且格式复杂的图形文件(有的PNG文件甚至像GIF文件一样带动画效果)
而且PNG可以是带透明通道的真彩色图像、不带透明通道的真彩色图像、索引颜色、灰度颜色等各种格式,如果大家都自己写程序分析PNG文件就会显得很麻烦、很累。因此,通过使用libpng你就能直接使用现成的函数、程序来读写PNG文件了。
使用方法:到libPNG的官方网站下载源代码,使用cmake配置之后,使用VS或者make工具编译成库;
之前需要使用合适的zlib源代码在相应的开发平台上进行编译。
参考:https://blog.csdn.net/dreamInTheWorld/article/details/55805901
两个图片的大小不一样;
左边的比右边的多几十k的骨骼信息。
读取png代码:
#include
#include
#include
#include "../png.h"
#include "../pngstruct.h"
#include "../pnginfo.h"
#define PNG_BYTES_TO_CHECK 8
#define HAVE_ALPHA 1
#define NOT_HAVE_ALPHA 0
#define PNG_TEXT_SUPPORTED
typedef struct _pic_data pic_data;
struct _pic_data {
int width, height; //长宽
int bit_depth; //位深度
int alpha_flag; //是否有透明通道
unsigned char *rgba;//实际rgb数据
};
int check_is_png(FILE **fp, const char *filename) //检查是否png文件
{
char checkheader[PNG_BYTES_TO_CHECK]; //查询是否png头
*fp = fopen(filename, "rb");
if (*fp == NULL) {
printf("open failed ...1\n");
return -1;
}
if (fread(checkheader, 1, PNG_BYTES_TO_CHECK, *fp) != PNG_BYTES_TO_CHECK) //读取png文件长度错误直接退出
return 0;
return png_sig_cmp(checkheader, 0, PNG_BYTES_TO_CHECK); //0正确, 非0错误
//return 0;//wishchin!!!
}
int decode_png(const char *filename, pic_data *out) //取出png文件中的rgb数据
{
png_structp png_ptr; //png文件句柄
png_infop info_ptr;//png图像信息句柄
int ret;
FILE *fp;
if (check_is_png(&fp, filename) != 0) {
printf("file is not png ...\n");
return -1;
}
printf("launcher[%s] ...\n", PNG_LIBPNG_VER_STRING); //打印当前libpng版本号
//1: 初始化libpng的数据结构 :png_ptr, info_ptr
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
info_ptr = png_create_info_struct(png_ptr);
//2: 设置错误的返回点
setjmp(png_jmpbuf(png_ptr));
rewind(fp); //等价fseek(fp, 0, SEEK_SET);
//3: 把png结构体和文件流io进行绑定
png_init_io(png_ptr, fp);
//4:读取png文件信息以及强转转换成RGBA:8888数据格式
//png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND, 0); //读取文件信息
png_read_info(png_ptr, info_ptr);
int channels, color_type;
channels = png_get_channels(png_ptr, info_ptr); //通道数量
color_type = png_get_color_type(png_ptr, info_ptr);//颜色类型
out->bit_depth = png_get_bit_depth(png_ptr, info_ptr);//位深度
out->width = png_get_image_width(png_ptr, info_ptr);//宽
out->height = png_get_image_height(png_ptr, info_ptr);//高
//png_const_structrp png_ptr,
// png_inforp info_ptr,
png_textp *text_ptr = NULL;// int *num_text;
int num_text= png_get_text(png_ptr, info_ptr, text_ptr, NULL);//额外文本信息?
//if(color_type == PNG_COLOR_TYPE_PALETTE)
// png_set_palette_to_rgb(png_ptr);//要求转换索引颜色到RGB
//if(color_type == PNG_COLOR_TYPE_GRAY && out->bit_depth < 8)
// png_set_expand_gray_1_2_4_to_8(png_ptr);//要求位深度强制8bit
//if(out->bit_depth == 16)
// png_set_strip_16(png_ptr);//要求位深度强制8bit
//if(png_get_valid(png_ptr,info_ptr,PNG_INFO_tRNS))
// png_set_tRNS_to_alpha(png_ptr);
//if(color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
// png_set_gray_to_rgb(png_ptr);//灰度必须转换成RG
printf("channels = %d color_type = %d bit_depth = %d width = %d height = %d ...\n",
channels, color_type, out->bit_depth, out->width, out->height);
//info_ptr->text->//读不出数据,是函数用错了???
int i, j, k;
int size, pos = 0;
int temp;
//5: 读取实际的rgb数据
png_bytepp row_pointers; //实际存储rgb数据的buf
row_pointers = png_get_rows(png_ptr, info_ptr); //也可以分别每一行获取png_get_rowbytes();
size = out->width * out->height; //申请内存先计算空间
if (channels == 4 || color_type == PNG_COLOR_TYPE_RGB_ALPHA) { //判断是24位还是32位
out->alpha_flag = HAVE_ALPHA; //记录是否有透明通道
size *= (sizeof(unsigned char) * 4); //size = out->width * out->height * channel
out->rgba = (png_bytep)malloc(size);
if (NULL == out->rgba) {
printf("malloc rgba faile ...\n");
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
fclose(fp);
return -1;
}
//从row_pointers里读出实际的rgb数据出来
temp = channels - 1;
for (i = 0; i < out->height; i++)
for (j = 0; j < out->width * 4; j += 4)
for (k = temp; k >= 0; k--)
out->rgba[pos++] = row_pointers[i][j + k];
}
else if (channels == 3 || color_type == PNG_COLOR_TYPE_RGB) { //判断颜色深度是24位还是32位
out->alpha_flag = NOT_HAVE_ALPHA;
size *= (sizeof(unsigned char) * 3);
out->rgba = (png_bytep)malloc(size);
if (NULL == out->rgba) {
printf("malloc rgba faile ...\n");
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
fclose(fp);
return -1;
}
//从row_pointers里读出实际的rgb数据
temp = (3 * out->width);
for (i = 0; i < out->height; i++) {
for (j = 0; j < temp; j += 3) {
out->rgba[pos++] = row_pointers[i][j + 2];
out->rgba[pos++] = row_pointers[i][j + 1];
out->rgba[pos++] = row_pointers[i][j + 0];
}
}
}
else return -1;
//6:销毁内存
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
fclose(fp);
//此时, 我们的out->rgba里面已经存储有实际的rgb数据了
//处理完成以后free(out->rgba)
return 0;
}
结果,依然未能读取PNG的额外信息....!!!悲剧!
使用方式:
int CPngWish::readpng(int argc, char* pngfilename, char* filename2)
{
pic_data out;
//if (argc == 3) {
//test_one_file2(pngfilename, filename2);
decode_png(pngfilename, &out);
write_png_file(filename2, &out);
free(out.rgba);
//}
//else {
// puts("please input two file, \nargv[1]:source.png argv[2]:dest.png");
//}
return 1;
};
有待研究...