PNG。可移植网络图形格式(Portable Network Graphic Format,PNG)名称来源于非官方的“PNG’s Not GIF”,是一种位图文件(bitmap file)存储格式。PNG用来存储灰度图像时,灰度图像的深度可多到16位,存储彩色图像时,彩色图像的深度可多到48位,并且还可存储多到16位的α通道数据。
PNG格式有8位、24位、32位三种形式。当中8位PNG支持两种不同的透明形式(索引透明和alpha透明),24位PNG不支持透明,32位PNG在24位基础上添加了8位透明通道,因此可展现256级透明程度。
PNG8和PNG24后面的数字则是代表这样的PNG格式最多能够索引和存储的颜色值。”8″代表2的8次方也就是256色。而24则代表2的24次方大概有1600多万色。
格式 | 最高支持色彩通道 | 索引色编辑支持 | 透明支持 |
PNG8 | 256索引色 | 支持 |
支持设定特定索引色为透明色(布尔透明) 支持为索引色附加8位透明度(256阶alpha透明) |
PNG24 | 约1600万色 | 不支持 | 不支持 |
PNG32 | 约1600万色 | 不支持 | 支持8位透明度(256阶alpha透明) |
PNG文件格式保留GIF文件格式的下列特性:
- 使用彩色查找表或者叫做调色板可支持256种颜色的彩色图像;
- 流式读/写性能(streamability):图像文件格式同意连续读出和写入图像数据。这个特性非常适合于在通信过程中生成和显示图像;
- 逐次逼近显示(progressive display):这样的特性可使在通信链路上传输图像文件的同一时候就在终端上显示图像,把整个轮廓显示出来之后逐步显示图像的细节,也就是先用低分辨率显示图像,然后逐步提高它的分辨率;
- 透明性(transparency):这个性能可使图像中某些部分不显示出来,用来创建一些有特色的图像。
- 辅助信息(ancillary information):这个特性可用来在图像文件里存储一些文本凝视信息。
- 独立于计算机软硬件环境;
- 使用无损压缩。
PNG文件格式中要添加下列GIF文件格式所没有的特性:
- 每一个像素为48位的真彩色图像;
- 每一个像素为16位的灰度图像;
- 可为灰度图和真彩色图加入α通道;
- 加入图像的γ信息;
- 使用循环冗余码(cyclic redundancy code,CRC)检測损害的文件;
- 加快图像显示的逐次逼近显示方式;
- 标准的读/写工具包;
- 可在一个文件里存储多幅图像。
文件结构
PNG图像格式文件(或者称为数据流)由一个8字节的PNG文件署名(PNG file signature)域和依照特定结构组织的3个以上的数据块(chunk)组成。
PNG定义了两种类型的数据块。一种是称为重要数据块(critical chunk),这是标准的数据块,还有一种叫做辅助数据块(ancillary chunks),这是可选的数据块。重要数据块定义了4个标准数据块,每一个PNG文件都必须包括它们。PNG读写软件也都必需要支持这些数据块。尽管PNG文件规范没有要求PNG编译码器对可选数据块进行编码和译码,但规范提倡支持可选数据块。
十进制数 | 137 | 80 | 78 | 71 | 13 | 10 | 26 | 10 |
十六进制数 | 89 | 50 | 4e | 47 | 0d | 0a | 1a | 0a |
当中第一个字节0x89超出了ASCII字符的范围,这是为了避免某些软件将PNG文件当做文本文件来处理。文件里剩余的部分由3个以上的PNG的数据块(Chunk)依照特定的顺序组成,因此,一个标准的PNG文件结构应该例如以下:
PNG文件标志 | PNG数据块 | … | PNG数据块 |
所以我们能够看到-x里面png格式的推断函数:
bool Image::isPng(const unsigned char * data, ssize_t dataLen)
{
if (dataLen <= 8)
{
return false;
}
static const unsigned char PNG_SIGNATURE[] = {0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a};
return memcmp(PNG_SIGNATURE, data, sizeof(PNG_SIGNATURE)) == 0;
}
PNG文件格式中的数据块 | ||||
数据块符号 | 数据块名称 | 多数据块 | 可选否 | 位置限制 |
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 | 图像结束数据 | 否 | 否 | 最后一个数据块 |
数据块结构
名称 | 字节数 | 说明 |
Length (长度) | 4字节 | 指定数据块中数据域的长度,其长度不超过(231-1)字节 |
Chunk Type Code (数据块类型码) | 4字节 | 数据块类型码由ASCII字母(A-Z和a-z)组成 |
Chunk Data (数据块数据) | 可变长度 | 存储依照Chunk Type Code指定的数据 |
CRC (循环冗余检測) | 4字节 | 存储用来检測是否有错误的循环冗余码 |
IHDR
文件头数据块IHDR(header chunk):它包括有PNG文件里存储的图像数据的基本信息,并要作为第一个数据块出如今PNG数据流中,并且一个PNG数据流中仅仅能有一个文件头数据块。文件头数据块由13字节组成,它的格式例如以下表所看到的。
域的名称 | 字节数 | 说明 |
Length (长度) | 4字节 | 图像宽度,以像素为单位 |
Height | 4字节 | 图像高度,以像素为单位 |
Bit depth | 1字节 | 图像深度: 索引彩色图像:1,2。4或8 灰度图像:1,2,4,8或16 真彩色图像:8或16 |
ColorType | 1字节 | 颜色类型: 0:灰度图像, 1,2。4。8或16 2:真彩色图像,8或16 3:索引彩色图像,1,2,4或8 4:带α通道数据的灰度图像,8或16 6:带α通道数据的真彩色图像,8或16 |
Compression method | 1字节 | 压缩方法(LZ77派生算法) |
Filter method | 1字节 | 滤波器方法 |
Interlace method | 1字节 | 隔行扫描方法: 0:非隔行扫描 1: Adam7(由Adam M. Costello开发的7遍隔行扫描方法) |
PLTE
调色板数据块PLTE(palette chunk)包括有与索引彩色图像(indexed-color image)相关的彩色变换数据,它仅与索引彩色图像有关。并且要放在图像数据块(image data chunk)之前。
PLTE数据块是定义图像的调色板信息。PLTE能够包括1~256个调色板信息,每一个调色板信息由3个字节RGB组成。因此,调色板的长度应该是3的倍数,否则。这将是一个非法的调色板。同理调色板数据块所包括的最大字节数为768。
对于索引图像。调色板信息是必须的,调色板的颜色索引从0開始编号,然后是1、2……。调色板的颜色数不能超过色深中规定的颜色数(如图像色深为4的时候,调色板中的颜色数不能够超过2^4=16),否则,这将导致PNG图像不合法。
真彩色图像和带α通道数据的真彩色图像也能够有调色板数据块。目的是便于非真彩色显示程序用它来量化图像数据,从而显示该图像。
IDAT
图像数据块IDAT(image data chunk):它存储实际的数据,在数据流中可包括多个连续顺序的图像数据块。
IDAT存放着图像真正的数据信息,因此,假设能够了解IDAT的结构,我们就能够非常方便的生成PNG图像。
IEND
图像结束数据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。
cocos2dx libpng的解码
bool Image::initWithPngData(const unsigned char * data, ssize_t dataLen)
{
// length of bytes to check if it is a valid png file
#define PNGSIGSIZE 8
bool ret = false;
png_byte header[PNGSIGSIZE] = {0};
png_structp png_ptr = 0;
png_infop info_ptr = 0;
do
{
// png header len is 8 bytes
CC_BREAK_IF(dataLen < PNGSIGSIZE);
//文件头校验
// check the data is png or not
memcpy(header, data, PNGSIGSIZE);
CC_BREAK_IF(png_sig_cmp(header, 0, PNGSIGSIZE));
//初始化png_structp类型结构体,libpng内部使用
// init png_struct
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
CC_BREAK_IF(! png_ptr);
//创建图像信息
// init png_info
info_ptr = png_create_info_struct(png_ptr);
CC_BREAK_IF(!info_ptr);
#if (CC_TARGET_PLATFORM != CC_PLATFORM_BADA && CC_TARGET_PLATFORM != CC_PLATFORM_NACL)
//设置异常处理
CC_BREAK_IF(setjmp(png_jmpbuf(png_ptr)));
#endif
//使用自己定义的回调函数来设置libpng的数据源
// set the read call back function
tImageSource imageSource;
imageSource.data = (unsigned char*)data;
imageSource.size = dataLen;
imageSource.offset = 0;
png_set_read_fn(png_ptr, &imageSource, pngReadCallback);
// read png header info
//使用底层处理来读取png数据
// read png file info
png_read_info(png_ptr, info_ptr);
//查询图像信息
_width = png_get_image_width(png_ptr, info_ptr);
_height = png_get_image_height(png_ptr, info_ptr);
png_byte bit_depth = png_get_bit_depth(png_ptr, info_ptr);
png_uint_32 color_type = png_get_color_type(png_ptr, info_ptr);
//CCLOG("color type %u", color_type);
//调色板格式的png图片,转化为RGB888的像素格式
// force palette images to be expanded to 24-bit RGB
// it may include alpha channel
if (color_type == PNG_COLOR_TYPE_PALETTE)
{
png_set_palette_to_rgb(png_ptr);
}
//像素格式少于1字节长度的灰度图,将其转为每像素占1字节的像素格式
// low-bit-depth grayscale images are to be expanded to 8 bits
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
{
bit_depth = 8;
png_set_expand_gray_1_2_4_to_8(png_ptr);
}
//将tRNS块数据信息扩展为完整的ALPHA通道信息
// expand any tRNS chunk data into a full alpha channel
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
{
png_set_tRNS_to_alpha(png_ptr);
}
//将16位输入降为8位
// reduce images with 16-bit samples to 8 bits
if (bit_depth == 16)
{
png_set_strip_16(png_ptr);
}
// Expanded earlier for grayscale, now take care of palette and rgb
if (bit_depth < 8)
{
png_set_packing(png_ptr);
}
//更新png数据的具体信息
// update info
png_read_update_info(png_ptr, info_ptr);
bit_depth = png_get_bit_depth(png_ptr, info_ptr);
color_type = png_get_color_type(png_ptr, info_ptr);
switch (color_type)
{
case PNG_COLOR_TYPE_GRAY:
_renderFormat = Texture2D::PixelFormat::I8;
break;
case PNG_COLOR_TYPE_GRAY_ALPHA:
_renderFormat = Texture2D::PixelFormat::AI88;
break;
case PNG_COLOR_TYPE_RGB:
_renderFormat = Texture2D::PixelFormat::RGB888;
break;
case PNG_COLOR_TYPE_RGB_ALPHA:
_renderFormat = Texture2D::PixelFormat::RGBA8888;
break;
default:
break;
}
//按行读取png信息,
// read png data
png_size_t rowbytes;
png_bytep* row_pointers = (png_bytep*)malloc( sizeof(png_bytep) * _height );
//获取每一行像素的字节数量
rowbytes = png_get_rowbytes(png_ptr, info_ptr);
//申请内存地址
_dataLen = rowbytes * _height;
_data = static_cast<unsigned char*>(malloc(_dataLen * sizeof(unsigned char)));
if (!_data)
{
if (row_pointers != nullptr)
{
free(row_pointers);
}
break;
}
for (unsigned short i = 0; i < _height; ++i)
{
row_pointers[i] = _data + i*rowbytes;
}
//读取png数据
png_read_image(png_ptr, row_pointers);
//结束读取数据
png_read_end(png_ptr, nullptr);
// premultiplied alpha for RGBA8888
if (color_type == PNG_COLOR_TYPE_RGB_ALPHA)
{
//预乘Alpha。使用渐变Alpha
premultipliedAlpha();
}
else
{
_hasPremultipliedAlpha = false;
}
if (row_pointers != nullptr)
{
//释放图片数据的内存
free(row_pointers);
}
ret = true;
} while (0);
if (png_ptr)
{
//释放png_ptr
png_destroy_read_struct(&png_ptr, (info_ptr) ? &info_ptr : 0, 0);
}
return ret;
}