
舊時『游戲紋理』再用『.bmp格式』, 冇壓缩即使256色盤依然佔用吉間.
『.PNG格式』利LZ77且冇失真壓縮. 且壓縮極高, 配合256色盤,适宜作『游戲紋理』.
| 數據塊 | 數值 |
| PNG圖檔標誌 | 0x89 0x50 0x4E 0x47 0x0D 0x0A 0x1A 0x0A |
| 圖檔頭 | IHDR |
| 调色板 | PLTE |
| 图像数据 | IDAT |
| 图像结束 | IEND |
PNG數據塊基本結构.
| 數據塊結构 | size | 簡介 |
| LENGTH | 整數4字節 | 數據長度 |
| CHUNK TYPE | 整數4字節 | 類型標記 |
| CHUNK DATA | 0~2^32字節 | 數據 |
| CRC32 | 整數4字節 | CRC32校驗 |
两字節肆字節數據,冚辦闌用『網络字節』存儲,需轉『主機字節』.
| 『網络字節』轉『主機字節』 |
| #define PNG_VALUE32(v) ((v & 0xff000000) >> 24 | (v & 0x00ff0000) >> 8 | (v & 0x0000ff00) << 8 | (v & 0x000000ff) << 24) |
| #define PNG_VALUE16(v) ((v & 0xff00) >> 8 | (v & 0x00ff) << 8) |
游戲引擎需讀PNG 伍個數據塊
| PNG數據塊 | 功能簡介 |
| IHDR | 文檔頭 |
| PLTE | 色盤 |
| IDAT | 圖像數值 |
| IEND | 結束 |
| tRNS | 圖像透明 |
| bKGD | 背景色 |
PNG圖檔頭捌字節標記,愛蒞識别PNG
| 0x89 | 0x50 | 0x4E | 0x47 | 0x0D | 0x0A | 0x1A | 0x0A |
| 137 | P | N | G | \r | \n | 26 | \n |
對比PNG標識
| if(memcmp(data, 0x0A1A0A0D474E5089, 8) != 0)
return false;// 非png圖檔 |
CRC32計算必需計『CHUNK TYPE + CHUNK DATA』, 長度『length + 4』而得.
| DWORD length = PNG_VALUE32(chunk->length) + 4; | 長 |
| DWORD64 crc = CRC32((PBYTE)(chunk) + 4,length); | CRC32計算 |
| DWORD64 crc_ = PNG_VALUE32(chunk->crc_); | 轉主機字節 |
| if(crc_ == crc)
return true;
|
比較crc值 |
首先定義PNG結构
| typedef struct PNG_TAG{ | |
| int width; | 圖宽 |
| int height; | 圖高 |
| int bitCount; | 位圖像素bits 8位,16位,24位,32位 |
| PNG_RGBA palette[256]; | 调色板 |
| PBYTE buffer; | 圖像数据 |
| int buffer_size; | 圖像数据長度 |
| int length; | 临時變量 |
| z_stream stream; | ZLIB解壓 |
| } PNG, *PNG_PTR; |
文檔頭結构 IHDR
| typedef struct PNG_IHDR_TAG{ | 文檔頭 IHDR |
| int length; | Data長度 |
| DWORD type; | 標記’IHDR’ |
| DWORD width; | 像素寬 |
| DWORD height; | 像素高 |
| BYTE BitDepth; | 圖像深度 |
| BYTE ColorType; | 顏色類型 |
| BYTE Compression; | LZ77派生演算法 ,壹定係0 |
| BYTE Filter; | 濾波,止值定0. 事實『0,1,2,3,4』伍種濾波 |
| BYTE Interlace; | 隔行掃描0=冇掃描,1=(Adam7 interlace) |
| DWORD CRC32; | CRC32校驗 |
| }PNG_IHDR,*PNG_IHDR_PTR; |
PNG支緩伍款顏色類型
| ColorType | 顏色類型 | 像素 |
| 0=Greyscale | 灰度圖像 | 1,2,4,8,16bit |
| 2=Truecolour | 真彩色圖像 | 8,16bit |
| 3=Indexed-colour | 索引彩色圖像 | 1,2,4,8bit |
| 4=Greyscale with alpha | 帶α通道資料灰度圖像 | 8,16bit |
| 6=Truecolour with alpha | 帶α通道資料真彩色圖像 | 8,16bit |
像素排列
| ColorType | 像素排列 |
| Greyscale | Y |
| Truecolour | RGB |
| Indexed-colour | i |
| Greyscale with alpha | RGBA |
| Truecolour with alpha | YA |
解析文檔頭 IHDR, 分配圖像時分, 每行像素多壹字節,記錄『Filter』濾波值,『0,1,2,3,4』伍種濾波;
| if(IHDR->ColorType == 0)
png->bitCount = IHDR->BitDepth; |
0:Greyscale:灰度圖像,1,2,4,8,16bit |
| if(IHDR->ColorType == 2)
png->bitCount = IHDR->BitDepth * 3; |
2:Truecolour:真彩色圖像,8,16bit |
| if(IHDR->ColorType == 3)
png->bitCount = IHDR->BitDepth; |
3:Indexed-colour:索引彩色圖像,1,2,4,8bit |
| if(IHDR->ColorType == 4)
png->bitCount = IHDR->BitDepth * 2; |
4:Greyscale with alpha:帶α通道資料灰度圖像,8,16bit |
| if(IHDR->ColorType == 6)
png->bitCount = IHDR->BitDepth * 4; |
6:Truecolour with alpha:帶α通道資料真彩色圖像,8,16bit |
| png->width = PNG_VALUE32(IHDR->width); | 像素寬 |
| png->height = PNG_VALUE32(IHDR->height); | 像素高 |
| int byte_count = (float)png->bitCount / 8.0f; | 像素大小 BIT to BYTE |
| png->buffer_size = (png->width * png->height * byte_count) + (png->height * 1); | 圖像数据長度,每行多壹字節 |
| png->buffer = (PBYTE)malloc(png->buffer_size); | 圖像数据 |
定義色盤RGB
| typedef struct PNG_RGB_TYP { | 色盤3字節 |
| BYTE red; | 紅 |
| BYTE green; | 錄 |
| BYTE blue; | 蓝 |
| } PNG_RGB, *PNG_RGB_PTR; |
定義色盤RGBA
| typedef struct PNG_RGBA_TYP { | 色盤4字節 |
| BYTE red; | 紅 |
| BYTE green; | 錄 |
| BYTE blue; | 蓝 |
| BYTE alpha; | 透明混合,透明0x00~實體0xFF |
| } PNG_RGBA, *PNG_RGBA_PTR; |
定義PNG色盤結构 ‘PLTE’
| typedef struct PNG_PLTE_TYP { | |
| int length; | Data長度 |
| DWORD type; | 標記’PLTE’ |
| PNG_RGB palette[256]; | 至多256色盤 |
| DWORD CRC32; | CRC32校驗 |
| } PNG_PLTE, *PNG_PLTE_PTR; |
分析PNG色盤 ‘PLTE’
| int length = PNG_VALUE32(chunk->length); | 長度 |
| int count = length / 3; | 色盤量 |
| for(int index = 0; index < count; ++index){ | |
| png->palette[index].red = PLTE->palette[index].red ; | 紅 |
| png->palette[index].green = PLTE->palette[index].green ; | 錄 |
| png->palette[index].blue = PLTE->palette[index].blue ; | 蓝 |
| png->palette[index].alpha = 0xff; } | 透明0x00~實體0xFF |
定義透明像素結构’tRNS’
| typedef struct PNG_tRNS_TYP{ | |
| int length; | 數據長度 |
| DWORD type; | 標記’tRNS’ |
| union { | |
| WORD grey; | 顏色類型 0 |
| struct {WORD Red,Green,Blue;}; | 顏色類型 2 |
| BYTE palette[256]; | 顏色類型 3 |
| DWORD CRC32; | CRC32校驗 |
| }PNG_tRNS,*PNG_tRNS_PTR; |
分析透明像素
| PNG_tRNS_PTR tRNS; | 透明像素 |
| tRNS->length = PNG_VALUE32(tRNS->length); | 數據長度 |
| if(IHDR->ColorType == 0 )
tRNS->grey = PNG_VALUE16(tRNS->grey); |
0:Greyscale:灰度圖像,1,2,4,8,16bit |
| if(IHDR->ColorType == 2 ) {
tRNS->Red = PNG_VALUE16(tRNS->Red); tRNS->Green = PNG_VALUE16(tRNS->Green); tRNS->Blue = PNG_VALUE16(tRNS->Blue); } |
2:Truecolour:真彩色圖像,8,16bit |
| if(IHDR->ColorType == 3) {
length = tRNS->length; for(int i = 0; i < length; ++i) { index = tRNS->palette[i]; png->palette[index].alpha = 0x00; } |
3:Indexed-colour:索引彩色圖像,1,2,4,8bit
透明0x00~實體0xFF |
定義背景色結构 ‘bKGD’
| typedef struct PNG_bKGD_TYP{ | |
| int length; | Data長度 |
| DWORD type; | 標記’bKGD’ |
| union { | |
| WORD Greyscale; | 顏色類型0 and 4灰度 |
| struct{WORD Red,Green,Blue;}; | 顏色類型2 and 6 -RGB三色 |
| BYTE Palette_Index;}; | 顏色類型3色盤索引 |
| DWORD CRC32; | CRC32校驗 |
| }PNG_bKGD,*PNG_bKGD_PTR; |
分析背景色’bKGD’
| int length = PNG_VALUE32(bKGD->length); | 長度 |
| if(IHDR->ColorType == 0 || IHDR->ColorType == 4)
bKGD->Greyscale = PNG_VALUE16(bKGD->Greyscale); |
灰度圖像 – 帶α通道資料灰度圖像 |
| if(IHDR->ColorType == 2 || IHDR->ColorType == 6) {
bKGD->Red = PNG_VALUE16(bKGD->Red); bKGD->Green = PNG_VALUE16(bKGD->Green); bKGD->Blue = PNG_VALUE16(bKGD->Blue); } |
真彩色圖像 – 帶α通道資料真彩色圖像 |
| if(IHDR->ColorType == 3)
bKGD->Palette_Index = bKGD->Palette_Index; |
索引彩色圖像 |
定義圖像數值結构 ‘IDAT’, 止數據塊可能有多個.
| typedef struct PNG_IDAT_TYP { | |
| int length; | Data長度 |
| DWORD type; | 標記’PLTE’ |
| BYTE data[1]; | 經壓縮圖像數據 |
| DWORD CRC32; | CRC32校驗 |
| } PNG_IDAT, *PNG_IDAT_PTR; |
每行像素多壹字節,記錄『Filter』濾波值,『0,1,2,3,4』伍種濾波;
| int length = PNG_VALUE32(IDAT->length); | 長度 |
| int size = png->buffer_size – png->length; | 剩余記憶體 |
| Uncompress_Data_gZip(png->buffer + png->length, &size,
IDAT->data,length, &png->stream); |
解压 |
| png->length = png->length + size; | 累積積數據長度 |
結束 IEND,檢測到’IEND’數據塊,己到文檔未端.
| typedef struct PNG_IEND_TAG{ | |
| int length; | Data長度0 |
| DWORD type; | 標記’IEND’ |
| DWORD CRC32; | CRC32校驗 |
| }PNG_IEND, *PNG_IEND_PTR; |
以顏色類型分別進行『反濾波』, 係每行像素首字節『濾波』值,有『0,1,2,3,4』伍款濾波, 以每粒像素單獨『復位』,帶α通道真彩色圖像8bit為例.
以cbax像素排列支缓伍款濾波
| c | b |
| a | x |
濾波算法
| filter濾波 | 濾波 | 復位 |
| 0= None | Filt(x) = Orig(x) | Recon(x) = Filt(x) |
| 1= Sub | Filt(x) = Orig(x) – Orig(a) | Recon(x) = Filt(x) + Recon(a) |
| 2= Up | Filt(x) = Orig(x) – Orig(b) | Recon(x) = Filt(x) + Recon(b) |
| 3= Average | Filt(x) = Orig(x) – floor((Orig(a) + Orig(b)) / 2) | Recon(x) = Filt(x) + floor((Recon(a) + Recon(b)) / 2) |
| 4= Paeth | Filt(x) = Orig(x) – PaethPredictor(Orig(a), Orig(b), Orig(c)) | Recon(x) = Filt(x) + PaethPredictor(Recon(a), Recon(b), Recon(c)) |
復位算法
| int byte_count = 4; | 每粒像素4字節 |
| int pixel_size = png->width * png->height; | 總像素量 |
| int buffer_size = png->width * png->height * byte_count; | 總字節量 |
| PBYTE buffer = (PBYTE)malloc(buffer_size); | 反濾波影像數據 |
| int col_size_recon = (png->width * 4) ; | 復位後每行字節量 |
| int col_size_filt = (png->width * 4) + 1; | 濾波後每行字節量 |
| for(int j = 0; j < png->height ; ++j) | 每行像素 |
| int filter = png->buffer[col_size_filt * j]; | 行首濾波=1 byte |
處理filter濾波0= None
| if(filter == 0) { | None |
| for(int i = 0; i < png->width; ++i){ | 逐像素復位 |
| Red = png->buffer[(col_size_filt * j) + 1 + (i * 4) + 0]; | x像素紅色 |
| Green = png->buffer[(col_size_filt * j) + 1 + (i * 4) + 1]; | x像素藍色 |
| Blue = png->buffer[(col_size_filt * j) + 1 + (i * 4) + 2]; | x像素錄色 |
| Alpha = png->buffer[(col_size_filt * j) + 1 + (i * 4) + 3]; | x像素透明 |
| buffer[(col_size_recon * j) + (i * 4) + 0] = Red; | 復位像素紅色 |
| buffer[(col_size_recon * j) + (i * 4) + 1] = Green; | 復位像素藍色 |
| buffer[(col_size_recon * j) + (i * 4) + 2] = Blue; | 復位像素綠色 |
| buffer[(col_size_recon * j) + (i * 4) + 3] = Alpha; }} | 復位像素透明 |
處理filter濾波1 =Sub
| if(filter == 1) { | Sub |
| for(int i = 0; i < png->width; ++i){ | 逐像素復位 |
| Red = png->buffer[(col_size_filt * j) + 1 + (i * 4) + 0]; | x像素紅色 |
| Green = png->buffer[(col_size_filt * j) + 1 + (i * 4) + 1]; | x像素藍色 |
| Blue = png->buffer[(col_size_filt * j) + 1 + (i * 4) + 2]; | x像素錄色 |
| Alpha = png->buffer[(col_size_filt * j) + 1 + (i * 4) + 3]; | x像素透明 |
| aRed = aGreen = aBlue = aAlpha = 0; | 清零 |
| if(i > 0) { | |
| aRed = buffer[(col_size_recon * j) + ((i-1) * 4) + 0]; | a像素紅色 |
| aGreen = buffer[(col_size_recon * j) + ((i-1) * 4) + 1]; | a像素藍色 |
| aBlue = buffer[(col_size_recon * j) + ((i-1) * 4) + 2]; | a像素綠色 |
| aAlpha = buffer[(col_size_recon * j) + ((i-1) * 4) + 3]; } | a像素透明 |
| buffer[(col_size_recon * j) + (i * 4) + 0] = Red + aRed; | 復位像素紅色 |
| buffer[(col_size_recon * j) + (i * 4) + 1] = Green + aGreen; | 復位像素藍色 |
| buffer[(col_size_recon * j) + (i * 4) + 2] = Blue + aBlue; | 復位像素綠色 |
| buffer[(col_size_recon * j) + (i * 4) + 3] = Alpha + aAlpha; }} | 復位像素透明 |
處理filter濾波2 =Up
| if(filter == 2){ | Up |
| for( i = 0; i < png->width; ++i){ | 逐像素復位 |
| Red = png->buffer[(col_size_filt * j) + 1 + (i * 4) + 0]; | x像素紅色 |
| Green = png->buffer[(col_size_filt * j) + 1 + (i * 4) + 1]; | x像素藍色 |
| Blue = png->buffer[(col_size_filt * j) + 1 + (i * 4) + 2]; | x像素錄色 |
| Alpha = png->buffer[(col_size_filt * j) + 1 + (i * 4) + 3]; | x像素透明 |
| bRed = bGreen = bBlue = bAlpha = 0; | 清零 |
| if(j > 0) { | |
| bRed = buffer[(col_size_recon * (j-1)) + (i * 4) + 0]; | b像素紅色 |
| bGreen = buffer[(col_size_recon * (j-1)) + (i * 4) + 1]; | b像素藍色 |
| bBlue = buffer[(col_size_recon * (j-1)) + (i * 4) + 2]; | b像素綠色 |
| bAlpha = buffer[(col_size_recon * (j-1)) + (i * 4) + 3];} | b像素透明 |
| buffer[(col_size_recon * j) + (i * 4) + 0] = Red + bRed; | 復位像素紅色 |
| buffer[(col_size_recon * j) + (i * 4) + 1] = Green + bGreen; | 復位像素藍色 |
| buffer[(col_size_recon * j) + (i * 4) + 2] = Blue + bBlue; | 復位像素綠色 |
| buffer[(col_size_recon * j) + (i * 4) + 3] = Alpha + bAlpha; }} | 復位像素透明 |
處理filter濾波3 = Average
| if(filter == 3) { | Average |
| for( i = 0; i < png->width; ++i){ | 逐像素復位 |
| Red = png->buffer[(col_size_filt * j) + 1 + (i * 4) + 0]; | x像素紅色 |
| Green = png->buffer[(col_size_filt * j) + 1 + (i * 4) + 1]; | x像素藍色 |
| Blue = png->buffer[(col_size_filt * j) + 1 + (i * 4) + 2]; | x像素錄色 |
| Alpha = png->buffer[(col_size_filt * j) + 1 + (i * 4) + 3]; | x像素透明 |
| aRed = aGreen = aBlue = aAlpha = 0; | 清零 |
| bRed = bGreen = bBlue = bAlpha = 0; | 清零 |
| if(i > 0) { | |
| aRed = buffer[(col_size_recon * j) + ((i-1) * 4) + 0]; | a像素紅色 |
| aGreen = buffer[(col_size_recon * j) + ((i-1) * 4) + 1]; | a像素藍色 |
| aBlue = buffer[(col_size_recon * j) + ((i-1) * 4) + 2]; | a像素綠色 |
| aAlpha = buffer[(col_size_recon * j) + ((i-1) * 4) + 3]; } | a像素透明 |
| if(j > 0) { | |
| bRed = buffer[(col_size_recon * (j-1)) + (i * 4) + 0]; | b像素紅色 |
| bGreen = buffer[(col_size_recon * (j-1)) + (i * 4) + 1]; | b像素藍色 |
| bBlue = buffer[(col_size_recon * (j-1)) + (i * 4) + 2]; | b像素綠色 |
| bAlpha = buffer[(col_size_recon * (j-1)) + (i * 4) + 3];} | b像素透明 |
| buffer[(col_size_recon * j) + (i * 4) + 0] = Red + ((aRed+bRed)/2); | 復位像素紅色 |
| buffer[(col_size_recon * j) + (i * 4) + 1] = Green + ((aGreen+bGreen)/2); | 復位像素藍色 |
| buffer[(col_size_recon * j) + (i * 4) + 2] = Blue + ((aBlue+bBlue)/2); | 復位像素綠色 |
| buffer[(col_size_recon * j) + (i * 4) + 3] = Alpha + ((aAlpha+bAlpha)/2); }} | 復位像素透明 |
Paeth預測函式
| BYTE PaethPredictor_PNG(BYTE a,BYTE b,BYTE c){ | 預測函式 |
| int p; | |
| int pa,pb,pc; | |
| p = a + b – c; | |
| pa = abs(p – a); | |
| pb = abs(p – b); | |
| pc = abs(p – c); | |
| if (pa <= pb && pa <= pc ) return a; | |
| else if (pb <= pc ) return b; | |
| else return c;} |
處理filter濾波4 =Paeth預測
| if(filter == 3) { | Average |
| for( i = 0; i < png->width; ++i){ | 逐像素復位 |
| Red = png->buffer[(col_size_filt * j) + 1 + (i * 4) + 0]; | x像素紅色 |
| Green = png->buffer[(col_size_filt * j) + 1 + (i * 4) + 1]; | x像素藍色 |
| Blue = png->buffer[(col_size_filt * j) + 1 + (i * 4) + 2]; | x像素錄色 |
| Alpha = png->buffer[(col_size_filt * j) + 1 + (i * 4) + 3]; | x像素透明 |
| aRed = aGreen = aBlue = aAlpha = 0; | 清零 |
| bRed = bGreen = bBlue = bAlpha = 0; | 清零 |
| cRed = cGreen = cBlue = cAlpha = 0; | 清零 |
| if(i > 0) { | |
| aRed = buffer[(col_size_recon * j) + ((i-1) * 4) + 0]; | a像素紅色 |
| aGreen = buffer[(col_size_recon * j) + ((i-1) * 4) + 1]; | a像素藍色 |
| aBlue = buffer[(col_size_recon * j) + ((i-1) * 4) + 2]; | a像素綠色 |
| aAlpha = buffer[(col_size_recon * j) + ((i-1) * 4) + 3]; } | a像素透明 |
| if(j > 0) { | |
| bRed = buffer[(col_size_recon * (j-1)) + (i * 4) + 0]; | b像素紅色 |
| bGreen = buffer[(col_size_recon * (j-1)) + (i * 4) + 1]; | b像素藍色 |
| bBlue = buffer[(col_size_recon * (j-1)) + (i * 4) + 2]; | b像素綠色 |
| bAlpha = buffer[(col_size_recon * (j-1)) + (i * 4) + 3];} | b像素透明 |
| if(i > 0 && j > 0) { | |
| cRed = buffer[(col_size_recon * (j-1)) + ((i-1) * 4) + 0]; | c像素紅色 |
| cGreen = buffer[(col_size_recon * (j-1)) + ((i-1) * 4) + 1]; | c像素藍色 |
| cBlue = buffer[(col_size_recon * (j-1)) + ((i-1) * 4) + 2]; | c像素綠色 |
| cAlpha = buffer[(col_size_recon * (j-1)) + ((i-1) * 4) + 3];} | c像素透明 |
| buffer[(col_size_recon * j) + (i * 4) + 0] = Red + PaethPredictor_PNG(aRed,bRed,cRed); | 復位像素紅色 |
| buffer[(col_size_recon * j) + (i * 4) + 1] = Green + PaethPredictor_PNG(aGreen,bGreen,cGreen); | 復位像素藍色 |
| buffer[(col_size_recon * j) + (i * 4) + 2] = Blue + PaethPredictor_PNG(aBlue,bBlue,cBlue); | 復位像素綠色 |
| buffer[(col_size_recon * j) + (i * 4) + 3] = Alpha + PaethPredictor_PNG(aAlpha,bAlpha,cAlpha);}} | 復位像素透明 |
释放圖像
| free(png->buffer); | 释放 |
| png->buffer = buffer; | 替换復位圖像 |
| png->bitCount = 32; | rgba |
| png->buffer_size = buffer_size; |
