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; }