音視訊入門-12-手動生成一張PNG圖片
阿新 • • 發佈:2019-10-31
* 音視訊入門文章目錄 *
預熱
上一篇 【PNG檔案格式詳解】詳細介紹了 PNG 檔案的格式。
PNG 影象格式檔案由一個 8 位元組的 PNG 檔案署名域和 3 個以上的後續資料塊(IHDR、IDAT、IEND)組成。
PNG 檔案包括 8 位元組檔案署名(89 50 4E 47 0D 0A 1A 0A,十六進位制),用來識別 PNG 格式。
資料塊中有 4 個關鍵資料塊:
- 檔案頭資料塊 IHDR(header chunk):包含有影象基本資訊,作為第一個資料塊出現並只出現一次。
- 調色盤資料塊 PLTE(palette chunk):必須放在影象資料塊之前。
- 影象資料塊 IDAT(image data chunk):儲存實際影象資料。PNG 資料允許包含多個連續的影象資料塊。
- 影象結束資料 IEND(image trailer chunk):放在檔案尾部,表示 PNG 資料流結束。
資料塊連起來,大概這個樣子:
PNG 識別符號 | PNG 資料塊(IHDR) | PNG 資料塊(其他型別資料塊) | … | PNG 結尾資料塊(IEND) |
---|
目標圖:
生成真彩 PNG 圖片
真彩 PNG 圖片不需要 PLTE 調色盤資料塊,IDAT 資料塊中存放的是影象的 RGB 資料。
分析 - 真彩 PNG IDAT 資料塊
以 7X7 解析度為例:
程式碼 - 生成真彩 PNG IDAT 資料塊
// 彩虹的七種顏色 uint32_t rainbowColors[] = { 0XFF0000, // 紅 0XFFA500, // 橙 0XFFFF00, // 黃 0X00FF00, // 綠 0X007FFF, // 青 0X0000FF, // 藍 0X8B00FF // 紫 }; // 生成真彩 PNG 圖片的影象資料塊 IDAT void genRGB24Data(uint8_t *rgbData, int width, int height) { for (int i = 0; i < height; ++i) { // 當前顏色 uint32_t currentColor = rainbowColors[0]; if(i < 100) { currentColor = rainbowColors[0]; } else if(i < 200) { currentColor = rainbowColors[1]; } else if(i < 300) { currentColor = rainbowColors[2]; } else if(i < 400) { currentColor = rainbowColors[3]; } else if(i < 500) { currentColor = rainbowColors[4]; } else if(i < 600) { currentColor = rainbowColors[5]; } else if(i < 700) { currentColor = rainbowColors[6]; } // 當前顏色 R 分量 uint8_t R = (currentColor & 0xFF0000) >> 16; // 當前顏色 G 分量 uint8_t G = (currentColor & 0x00FF00) >> 8; // 當前顏色 B 分量 uint8_t B = currentColor & 0x0000FF; // 每個掃描行前第一個位元組是過濾器型別 rgbData[3*(i*width)+i] = 0x00; for (int j = 0; j < width; ++j) { int currentIndex = 3*(i*width+j)+(i+1); rgbData[currentIndex] = R; rgbData[currentIndex+1] = G; rgbData[currentIndex+2] = B; } } }
生成真彩 PNG 完整程式碼
#include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <stdbool.h> #include "zlib.h" // ***** functions in util.c ***** bool isBigEndianOrder(); void genRGB24Data(uint8_t *rgbData, int width, int height); uint32_t switchUint32(uint32_t i); uint32_t calcCrc32(uint32_t dataASCII, uint8_t *data, uint32_t length); typedef struct { uint32_t width; uint32_t height; uint8_t bitDepth; uint8_t colorType; uint8_t compressionMethod; uint8_t filterMethod; uint8_t interlaceMethod; } PNG_IHDR_DATA; int main() { // PNG 圖片尺寸 int width = 700, height = 700; // IDAT 中資料部分長度 uint32_t IDAT_RGB_DATA_LENGTH = width*height*3+height; // PNG 檔案包括 8 位元組檔案署名(89 50 4E 47 0D 0A 1A 0A,十六進位制),用來識別 PNG 格式。 uint8_t PNG_FILE_SIGNATURE[] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}; // IHDR 每個字母對應的 ASCII uint32_t IHDR_ASCII = switchUint32(0x49484452); // IDAT 每個字母對應的ASCII uint32_t IDAT_ASCII = switchUint32(0x49444154); // PNG 檔案的結尾 12 個位元組看起來總應該是這樣的:(00 00 00 00 49 45 4E 44 AE 42 60 82,十六進位制) uint8_t PNG_IEND_DATA[] = {0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82}; FILE *file = fopen("/Users/staff/Desktop/0-true-color.png", "wb"); // FILE *file = fopen("C:\\Users\\Administrator\\Desktop\\0-true-color.png", "wb+"); if (!file) { printf("Could not write file\n"); return -1; } // 真彩 PNG 圖片 儲存的是 RGB 數 uint8_t *rgb24Data = (uint8_t *)malloc(IDAT_RGB_DATA_LENGTH); // 填充 IDAT 的 RGB 資料 genRGB24Data(rgb24Data, width, height); // 寫 PNG 檔案署名 fwrite(PNG_FILE_SIGNATURE, 1, sizeof(PNG_FILE_SIGNATURE), file); // 準備 IHDR 資料 PNG_IHDR_DATA pngIhdrData; pngIhdrData.width = switchUint32(width); pngIhdrData.height = switchUint32(height); pngIhdrData.bitDepth = 8; pngIhdrData.colorType = 2;// 2:真彩色影象,8或16 6:帶α通道資料的真彩色影象,8或16 pngIhdrData.compressionMethod = 0; pngIhdrData.filterMethod = 0; pngIhdrData.interlaceMethod = 0; // IHDR 資料長度 uint32_t IHDR_DATA_LENGTH = 13; // IHDR 資料長度 轉換成大端位元組序 uint32_t pngIhdrDataSize = switchUint32(IHDR_DATA_LENGTH); // 計算 IHDR CRC32 uint32_t ihdrDataCrc32 = calcCrc32(IHDR_ASCII, (uint8_t *) &pngIhdrData, IHDR_DATA_LENGTH); // 寫 IHDR 資料長度 fwrite(&pngIhdrDataSize, 1, sizeof(pngIhdrDataSize), file); // 寫 IHDR ASCII fwrite(&IHDR_ASCII, 1, sizeof(IHDR_ASCII), file); // 寫 IHDR 資料 fwrite(&pngIhdrData, 1, IHDR_DATA_LENGTH, file); // 寫 IHDR CRC32 fwrite(&ihdrDataCrc32, 1, sizeof(ihdrDataCrc32), file); // zlib 壓縮資料 uint8_t buf[IDAT_RGB_DATA_LENGTH]; // 壓縮後 buf 的資料長度 壓縮完成後就是實際大小了 uint32_t buflen = IDAT_RGB_DATA_LENGTH; // 執行 zlib 的壓縮方法 compress(buf, (uLongf *) &buflen, rgb24Data, IDAT_RGB_DATA_LENGTH); printf("\n壓縮前資料長度:%d \n壓縮後資料長度為:%d \n", IDAT_RGB_DATA_LENGTH, buflen); // 計算 IDAT CRC32 uint32_t idatDataCrc32 = calcCrc32(IDAT_ASCII, buf, buflen); // IDAT 資料長度 轉換成大端位元組序 uint32_t tmpBuflen = switchUint32(buflen); // 寫 IDAT 資料長度 fwrite(&tmpBuflen, 1, sizeof(tmpBuflen), file); // 寫 IDAT ASCII fwrite(&IDAT_ASCII, 1, sizeof(IDAT_ASCII), file); // 寫 IDAT 資料 fwrite(buf, 1, buflen, file); // 寫 IDAT CRC32 fwrite(&idatDataCrc32, 1, sizeof(idatDataCrc32), file); // 寫 IEND 資訊 fwrite(PNG_IEND_DATA, 1, sizeof(PNG_IEND_DATA), file); // 檢視位元組序 if(isBigEndianOrder()) { printf("大端位元組序"); } else { printf("小端位元組序"); } // 收尾工作 fflush(file); free(rgb24Data); fclose(file); return 0; }
生成索引 PNG 圖片
索引 PNG 圖片必須有 PLTE 調色盤資料塊,IDAT 資料塊中存放的是影象的 PLTE 調色盤顏色索引資料。
分析 - 索引 PNG IDAT 資料塊
以 7X7 解析度為例:
程式碼 - 生成索引 PNG PLTE 調色盤
// 彩虹的七種顏色
uint32_t rainbowColors[] = {
0XFF0000, // 紅
0XFFA500, // 橙
0XFFFF00, // 黃
0X00FF00, // 綠
0X007FFF, // 青
0X0000FF, // 藍
0X8B00FF // 紫
};
/**
* 生成索引 PNG 圖片的調色盤 PLTE
* @param rgbPLTEData
*/
void genRGBPLTE(uint8_t *rgbPLTEData) {
for (int i = 0; i < 7; ++i) {
uint32_t currentColor = rainbowColors[i];
// 當前顏色 R 分量
uint8_t R = (currentColor & 0xFF0000) >> 16;
// 當前顏色 G 分量
uint8_t G = (currentColor & 0x00FF00) >> 8;
// 當前顏色 B 分量
uint8_t B = currentColor & 0x0000FF;
int currentIndex = 3*i;
rgbPLTEData[currentIndex] = R;
rgbPLTEData[currentIndex+1] = G;
rgbPLTEData[currentIndex+2] = B;
}
}
程式碼 - 生成索引 PNG IDAT 資料塊
/**
* 生成索引 PNG 圖片的影象資料塊 IDAT
* @param rgbIndexData
* @param width
* @param height
*/
void genRGBIndexData(uint8_t *rgbIndexData, int width, int height) {
for (int i = 0; i < height; ++i) {
uint8_t currentColorIndex = 0;
if(i < 100) {
currentColorIndex = 0;
} else if(i < 200) {
currentColorIndex = 1;
} else if(i < 300) {
currentColorIndex = 2;
} else if(i < 400) {
currentColorIndex = 3;
} else if(i < 500) {
currentColorIndex = 4;
} else if(i < 600) {
currentColorIndex = 5;
} else if(i < 700) {
currentColorIndex = 6;
}
// 每個掃描行前第一個位元組是過濾器型別
rgbIndexData[(i*width)/2+i] = 0x00;
for (int j = 0; j < width; ++j) {
int currentIndex = (i*width+j)/2+(i+1);
int positionInByte = j%2;
if(positionInByte == 0) {
rgbIndexData[currentIndex] = currentColorIndex << 4;
} else {
rgbIndexData[currentIndex] += currentColorIndex;
}
}
}
}
生成索引 PNG 完整程式碼
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include "zlib.h"
// ***** functions in util.c *****
bool isBigEndianOrder();
void genRGBPLTE(uint8_t *rgbData);
void genRGBIndexData(uint8_t *rgbIndexData, int width, int height);
uint32_t switchUint32(uint32_t i);
uint32_t calcCrc32(uint32_t dataASCII, uint8_t *data, uint32_t length);
typedef struct {
uint32_t width;
uint32_t height;
uint8_t bitDepth;
uint8_t colorType;
uint8_t compressionMethod;
uint8_t filterMethod;
uint8_t interlaceMethod;
} PNG_IHDR_DATA;
int main() {
// PNG 圖片尺寸
int width = 700, height = 700;
// IDAT 中資料部分長度
uint32_t IDAT_INDEX_DATA_LENGTH = width*height/2+height;
// PNG 檔案包括 8 位元組檔案署名(89 50 4E 47 0D 0A 1A 0A,十六進位制),用來識別 PNG 格式。
uint8_t PNG_FILE_SIGNATURE[] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
// IHDR 每個字母對應的 ASCII
uint32_t IHDR_ASCII = switchUint32(0x49484452);
// PLTE 每個字母對應的ASCII
uint32_t PLTE_ASCII = switchUint32(0x504C5445);
// IDAT 每個字母對應的ASCII
uint32_t IDAT_ASCII = switchUint32(0x49444154);
// PNG 檔案的結尾 12 個位元組看起來總應該是這樣的:(00 00 00 00 49 45 4E 44 AE 42 60 82,十六進位制)
uint8_t PNG_IEND_DATA[] = {0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82};
FILE *file = fopen("/Users/staff/Desktop/0-indexed-color.png", "wb");
// FILE *file = fopen("C:\\Users\\Administrator\\Desktop\\0-indexed-color.png", "wb+");
if (!file) {
printf("Could not write file\n");
return -1;
}
// 紅橙黃綠青藍紫-七種顏色的調色盤 7 種顏色 * 每種顏色佔 3 位元組
uint8_t *rgbPLTEData = (uint8_t *)malloc(7*3);
// 索引 PNG 圖片,IDAT 儲存的是 PLTE 中的圖片索引
uint8_t *rgbIndexData = (uint8_t *)malloc(IDAT_INDEX_DATA_LENGTH);
// 填充 紅橙黃綠青藍紫-七種顏色的調色盤
genRGBPLTE(rgbPLTEData);
// 填充 IDAT 的 PLTE 索引
genRGBIndexData(rgbIndexData, width, height);
// 寫 PNG 檔案署名
fwrite(PNG_FILE_SIGNATURE, 1, sizeof(PNG_FILE_SIGNATURE), file);
// 準備 IHDR 資料
PNG_IHDR_DATA pngIhdrData;
pngIhdrData.width = switchUint32(width);
pngIhdrData.height = switchUint32(height);
pngIhdrData.bitDepth = 4;
pngIhdrData.colorType = 3; // 3:索引彩色影象,1,2,4或8
pngIhdrData.compressionMethod = 0;
pngIhdrData.filterMethod = 0;
pngIhdrData.interlaceMethod = 0;
// IHDR 資料長度
uint32_t IHDR_DATA_LENGTH = 13;
// IHDR 資料長度 轉換成大端位元組序
uint32_t pngIhdrDataSize = switchUint32(IHDR_DATA_LENGTH);
// 計算 IHDR CRC32
uint32_t ihdrDataCrc32 = calcCrc32(IHDR_ASCII, (uint8_t *) &pngIhdrData, IHDR_DATA_LENGTH);
// 寫 IHDR 資料長度
fwrite(&pngIhdrDataSize, 1, sizeof(pngIhdrDataSize), file);
// 寫 IHDR ASCII
fwrite(&IHDR_ASCII, 1, sizeof(IHDR_ASCII), file);
// 寫 IHDR 資料
fwrite(&pngIhdrData, 1, IHDR_DATA_LENGTH, file);
// 寫 IHDR CRC32
fwrite(&ihdrDataCrc32, 1, sizeof(ihdrDataCrc32), file);
// 準備 PLTE 調色盤資訊
// PLTE 資料長度
uint32_t PLTE_DATA_LENGTH = 21;
// PLTE 資料長度 轉換成大端位元組序
uint32_t pngPlteDataLength = switchUint32(PLTE_DATA_LENGTH);
// 計算 PLTE CRC32
uint32_t plteDataCrc32 = calcCrc32(PLTE_ASCII, rgbPLTEData, PLTE_DATA_LENGTH);
// 寫 PLTE 資料長度
fwrite(&pngPlteDataLength, 1, sizeof(pngPlteDataLength), file);
// 寫 PLTE ASCII
fwrite(&PLTE_ASCII, 1, sizeof(PLTE_ASCII), file);
// 寫 PLTE 資料
fwrite(rgbPLTEData, 1, PLTE_DATA_LENGTH, file);
// 寫 PLTE CRC32
fwrite(&plteDataCrc32, 1, sizeof(plteDataCrc32), file);
// zlib 壓縮資料
// buf 用於存放壓縮後的資料
uint8_t buf[IDAT_INDEX_DATA_LENGTH];
// 壓縮後 buf 的資料長度 壓縮完成後就是實際大小了
uint32_t buflen = IDAT_INDEX_DATA_LENGTH;
// 執行 zlib 的壓縮方法
compress(buf, (uLongf *) &buflen, rgbIndexData, IDAT_INDEX_DATA_LENGTH);
printf("\n壓縮前資料長度:%d \n壓縮後資料長度為:%d \n", IDAT_INDEX_DATA_LENGTH, buflen);
// 計算 IDAT CRC32
uint32_t idatDataCrc32 = calcCrc32(IDAT_ASCII, buf, buflen);
// IDAT 資料長度 轉換成大端位元組序
uint32_t tmpBuflen = switchUint32(buflen);
// 寫 IDAT 資料長度
fwrite(&tmpBuflen, 1, sizeof(tmpBuflen), file);
// 寫 IDAT ASCII
fwrite(&IDAT_ASCII, 1, sizeof(IDAT_ASCII), file);
// 寫 IDAT 資料
fwrite(buf, 1, buflen, file);
// 寫 IDAT CRC32
fwrite(&idatDataCrc32, 1, sizeof(idatDataCrc32), file);
// 寫 IEND 資訊
fwrite(PNG_IEND_DATA, 1, sizeof(PNG_IEND_DATA), file);
// 檢視位元組序
if(isBigEndianOrder()) {
printf("大端位元組序");
} else {
printf("小端位元組序");
}
// 收尾工作
fflush(file);
free(rgbPLTEData);
free(rgbIndexData);
fclose(file);
return 0;
}
總結 & 檢視
生成真彩 PNG、索引 PNG 圖片之間的區別:
- IHDR 檔案頭資料塊中的顏色型別,索引 PNG 顏色型別是
3:索引彩色影象
,真彩 PNG 顏色型別是2:真彩色影象
。 - PLTE 調色盤資料塊,索引 PNG 必須有調色盤,真彩 PNG 不需要調色盤。
- IDAT 資料塊儲存的資料,索引 PNG 儲存的是調色盤顏色的索引,真彩 PNG 儲存的是 RGB 資料。
來看一看純手工打造的 PNG 圖片:
Congratulations!
程式碼:
11-rgb-to-png
參考資料:
Portable Network Graphics (PNG) Specification and Extensions
gzip,deflate,zlib辨析
Zlib庫的安裝與使用
內容有誤?聯絡作者:
本文由部落格一文多發平臺 OpenWrite 釋出!