[Image_Codec]常見圖片格式的封裝及編解碼-Android平臺(二)PNG
PNG圖片格式
PNG(Portable Network Graphics) 是一種光柵化的,無失真壓縮的圖片檔案格式。其設計的目的是替換GIF,是目前網路中用得最廣的無失真壓縮圖片格式。我們可以用工具將前面的Bitmap轉換為PNG。
下面是從上一章所說的 BMP轉換過來的png_4x2_32bit.png的PNG圖片,圖片比較小,看仔細了:
PNG特點
- 體積小
網路通訊中因受頻寬制約,在保證圖片清晰、逼真的前提下,網頁中不可能大範圍的使用檔案較大的bmp格式檔案。 - 無失真壓縮 PNG檔案採用LZ77演算法的派生演算法進行壓縮,其結果是獲得高的壓縮比,不損失資料。它利用特殊的編碼方法標記重複出現的資料,因而對影象的顏色沒有影響,也不可能產生顏色的損失,這樣就可以重複儲存而不降低影象質量。
- 索引彩色模式
PNG-8格式與GIF影象類似,同樣採用8位調色盤將RGB彩色影象轉換為索引彩色影象。影象中儲存的不再是各個畫素的彩色資訊,而是從影象中挑選出來的具有代表性的顏色編號,每一編號對應一種顏色,影象的資料量也因此減少,這對彩色影象的傳播非常有利。 - 更優化的網路傳輸顯示
PNG影象在瀏覽器上採用流式瀏覽,即使經過交錯處理的影象會在完全下載之前提供瀏覽者一個基本的影象內容,然後再逐漸清晰起來。它允許連續讀出和寫入影象資料,這個特性很適合於在通訊過程中顯示和生成影象。 - 支援透明效果
PNG可以為原影象定義256個透明層次,使得彩色影象的邊緣能與任何背景平滑地融合,從而徹底地消除鋸齒邊緣。這種功能是GIF和JPEG沒有的。 - PNG同時還支援真彩和灰度級影象的Alpha通道透明度。
- 最高支援24位真彩色影象以及8位灰度影象。
- 支援Alpha通道的透明/半透明特性。
- 支援影象亮度的Gamma校準資訊。
- 支援儲存附加文字資訊,以保留影象名稱、作者、版權、創作時間、註釋等資訊。
- 漸近顯示和流式讀寫,適合在網路傳輸中快速顯示預覽效果後再展示全貌。
- 使用CRC防止檔案出錯。
- 最新的PNG標準允許在一個檔案記憶體儲多幅影象。
PNG檔案結構
PNG影象格式檔案(或者稱為資料流)由一個8位元組的PNG檔案署名(PNG file signature)域和按照特定結構組織的3個以上的資料塊(chunk)組成。
我們以二進位制的形式將PNG圖png_4x2_32bit.png開啟:
00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452 .PNG........IHDR
00000010: 0000 0004 0000 0002 0806 0000 007f a87d ...............}
00000020: 6300 0000 0173 5247 4200 aece 1ce9 0000 c....sRGB.......
00000030: 0004 6741 4d41 0000 b18f 0bfc 6105 0000 ..gAMA......a...
00000040: 0009 7048 5973 0000 0ec3 0000 0ec3 01c7 ..pHYs..........
00000050: 6fa8 6400 0000 1849 4441 5418 5763 0082 o.d....IDAT.Wc..
00000060: ffff 8118 4c81 2110 8029 b0e0 ffff 0005 ....L.!..)......
00000070: 2b10 f0fa b027 6800 0000 0049 454e 44ae +....'h....IEND.
00000080: 4260 82 B`.
- 檔案頭
PNG檔案包含8位元組的簽名:
8950 4e47 0d0a 1a0a
值Value | 作用Purpose |
---|---|
89 |
超出了ASCII字元的範圍,避免某些軟體將圖片當著文字來處理 |
50 4E 47 |
‘PNG’的ASCII值,PNG檔案的標識 |
0D 0A |
Dos風格的的回車,檢測DOS-Unix行結束資料的轉換 |
1A |
Dos風格的換行符號 |
0A |
Unix風格的回車 |
- 資料塊
PNG檔案中包含3個以上的資料塊,資料塊間以特定的順序組成。而資料塊又分為關鍵資料塊和輔助資料塊。基本每個資料塊都用下面的結構描述:
名稱 | 位元組數 | 說明 |
---|---|---|
Length (長度) | 4位元組 | 指定資料塊中資料域的長度,其長度不超過(231-1)位元組 |
Chunk Type Code (資料塊型別碼) | 4位元組 | 資料塊型別碼由ASCII字母(A-Z和a-z)組成 |
Chunk Data (資料塊資料) | 可變長度 | 儲存按照Chunk Type Code指定的資料 |
CRC (迴圈冗餘檢測) | 4位元組 | 儲存用來檢測是否有錯誤的迴圈冗餘碼 |
- 關鍵資料塊包括
資料塊符號 | 資料塊名稱 | 多資料塊 | 可選否 | 位置限制 |
---|---|---|---|---|
IHDR | 檔案頭資料塊 | 否 | 否 | 第一塊 |
IDAT | 影象資料塊 | 是 | 否 | 與其他IDAT連續 |
IEND | 影象結束資料 | 否 | 否 | 最後一個數據塊 |
和Bitmap相比,關鍵資料塊多了一個IEND塊。
- IHDR資料塊
IHDR也稱為檔案頭資料塊,它包含有PNG檔案中儲存的影象資料的基本資訊,並要作為第一個資料塊出現在PNG資料流中,而且一個PNG資料流(檔案)中只能有一個檔案頭資料塊。
IHDR檔案頭資料塊由13位元組組成
00000000: ---- ---- ---- ---- 0000 000d 4948 4452 .PNG........IHDR
00000010: 0000 0004 0000 0002 0806 0000 007f a87d ...............}
00000020: 63-- ---- ---- ---- ---- ---- ---- ---- c....sRGB.......
它的格式如下表所示:
offset | 值 Value | 域的名稱 | 位元組數 | 說明 |
---|---|---|---|---|
0000 000d |
13 | size | 4 | 資料塊大小 |
4948 4452 |
IHDR |
type | IHDR的ASCII | 資料塊的型別 |
0000 0004 |
4 | Width | 4 bytes | 影象寬度,以畫素為單位 |
0000 0002 |
2 | Height | 4 bytes | 影象高度,以畫素為單位 |
08 |
8 | Bit depth | 1 byte | 影象深度: 索引彩色影象:1,2,4或8 灰度影象:1,2,4,8或16 真彩色影象:8或16 |
06 |
6 | ColorType | 1 byte | 顏色型別: 0:灰度影象, 1,2,4,8或16 2:真彩色影象,8或16 3:索引彩色影象,1,2,4或8 4:帶α通道資料的灰度影象,8或16 6:帶α通道資料的真彩色影象,8或16 |
00 |
0 | Compression method | 1 byte | 壓縮方法(LZ77派生演算法) |
00 |
0 | Filter method | 1 byte | 濾波器方法 |
00 |
0 | Interlace method | 1 byte | 隔行掃描方法: 0:非隔行掃描 1: Adam7(由Adam M. Costello開發的7遍隔行掃描方法) |
7fa8 7d63 |
CRC | 4 | CRC校驗碼 |
按照我們的檔案來~
- sRGB資料塊
00000020: --00 0000 0173 5247 4200 aece 1ce9 ---- c....sRGB.......
offset | 值 Value | 域的名稱 | 位元組數 | 說明 |
---|---|---|---|---|
00 0000 01 |
1 | size | 4 | 資料塊大小 |
73 5247 42 |
sRGB |
type | sRGB的ASCII | 資料塊的型別 |
00 |
0 | sRGB模式 | 1 bytes | 有0 1 2 3 四種模式可選,可以參考W3對sRGB的介紹 |
aece 1ce9 |
CRC | 4 | CRC校驗碼 |
不是所有的解碼器都支援sRGB,所以有sRGB資料塊,就必須有gAMA 資料塊,也可以有cHRM 資料塊,以相容不支援sRGB的解碼器。
- gAMA 資料塊
00000020: ---- ---- ---- ---- ---- ---- ---- 0000 c....sRGB.......
00000030: 0004 6741 4d41 0000 b18f 0bfc 6105 0000 ..gAMA......a...
offset | 值 Value | 域的名稱 | 位元組數 | 說明 |
---|---|---|---|---|
0000 0004 |
4 | size | 4 | 資料塊大小 |
6741 4d41 |
gAMA |
type | gAMA 的ASCII | 資料塊的型別 |
0000 b18f |
0 | gama校驗 | 1 bytes | gama校驗 |
0bfc 6105 |
CRC | 4 | CRC校驗碼 |
如果有sRGB資料塊或者iCCP資料塊,gAMA資料塊就不用了,被override。
- pHYs 資料塊
期望的物理畫素的尺寸,或比例當顯示圖片時。
00000030: ---- ---- ---- ---- ---- ---- ---- 0000 ..gAMA......a...
00000040: 0009 7048 5973 0000 0ec3 0000 0ec3 01c7 ..pHYs..........
00000050: 6fa8 64-- ---- ---- ---- ---- ---- ---- o.d....IDAT.Wc..
offset | 值 Value | 域的名稱 | 位元組數 | 說明 |
---|---|---|---|---|
0000 0009 |
9 | size | 4 | 資料塊大小 |
7048 5973 |
pHYs |
type | pHYs 的ASCII | 資料塊的型別 |
0000 0ec3 |
3779 | x axis | 4 bytes | gama校驗 |
0000 0ec3 |
3779 | y axis | 4 bytes | gama校驗 |
01 |
1 | Unit specifier | 1 | 單位說明 |
c76f a8 64 |
CRC | 4 | CRC校驗碼 |
Unit specifier,有下列定義的值:
0:unit is unknown;只是定義了縮放比例
1:unit is the metre
接下來終於到我們的資料塊了
- IDAT 資料塊
關鍵資料塊,這裡就資料就是png圖片的資料
00000050: ---- --00 0000 1849 4441 5418 5763 0082 o.d....IDAT.Wc..
00000060: ffff 8118 4c81 2110 8029 b0e0 ffff 0005 ....L.!..)......
00000070: 2b10 f0fa b027 6800 0000 0049 454e 44ae +....'h....IEND.
00000080: 4260 82 B`.
offset | 值 Value | 域的名稱 | 位元組數 | 說明 |
---|---|---|---|---|
00 0000 18 |
24 | size | 4 | 資料塊大小 |
49 4441 54 |
IDAT |
type | IDAT 的ASCII | 資料塊的型別 |
1857 6300 82ff ff81 184c 8121 1080 29b0 e0ff ff00 052b 10f0 |
png編碼資料 | 24 | png編碼資料 | |
fab0 2768 |
CRC | 4 | CRC校驗碼 |
- IEND 資料塊
關鍵資料塊
00000070: ---- ---- ---- --00 0000 0049 454e 44ae +....'h....IEND.
00000080: 4260 82 B`.
offset | 值 Value | 域的名稱 | 位元組數 | 說明 |
---|---|---|---|---|
00 0000 00 |
4 | size | 4 | 資料塊大小 |
49 454e 44 |
IEND |
type | IEND 的ASCII | 資料塊的型別 |
ae 4260 82 |
CRC | 4 | CRC校驗碼 |
以上就是PNG檔案結構的描述~~~
PNG壓縮原理
PNG的壓縮過程是完全無損的,壓縮過的檔案可以準確的還原出原圖,這要分兩個階段完成:推斷(又稱過濾[filtering])和壓縮。
過濾
差分編碼(Delta encoding)是最強大的數字壓縮法之一。原理是根據前一個數據的值將後面的值替換成其他值,例如:
[2,3,4,5,6,7,8]可以變成[2,1,1,1,1,1,1],演算法是
[2, 3-2=1, 4-3=1, 5-4=1, 6-5=1, 7-6=1, 8-7=1]
這樣看就很明顯了,如果你的資料是線性相關的(線性相關的意思是,一組資料裡的前後值都差別不大,或者具有相關性),就可以把你的資料集轉換成一組重複的、低的值,這樣的值更容易被壓縮。
PNG格式使用了差分編碼(Delta encoding)裡的過濾。原理是,對於每一行的畫素,一個當前的畫素都跟它的左邊畫素、上邊的畫素和左上角的畫素有關係。
舉個例子,如果我們要編碼一個給定的畫素通過它與A和B平均值的差異(X-(A+B)/2),那麼我們將得到:
我們使用了ABC去推斷出X的值,然後我們將X替換成更小的值。
需要注意的是,每一行的畫素都有可能不同,PNG允許5種不同的推斷演算法,它們是:
* 不過濾
* X-A
* X-B
* X-(A+B)/2(又稱平均值)
Paeth推斷(A,B,C的線性方法,這種比較複雜可看W3C的規定)
這裡說明一下,每一行畫素應該選擇最適合的過濾演算法,這樣才能得到最少數量的特殊值。下面是我們關於不同模式的例子:
需要注意的是這些過濾器都是對每一行畫素起作用而不是單個畫素。也就是說過濾器會對每一行的紅色畫素起作用,再分別對藍色的畫素起作用。(儘管同一行的畫素會用同樣的過濾器)
現在PNG格式在選擇過濾器上有一些不錯的方法,開發人員根據對不同型別圖片的使用經驗摸索出一些不錯的規律。例如對於調色盤的影象(palette images)和8位的灰色圖就不要過濾。對於其他圖片,就選擇那種能最大限度地減少絕對差異總和的值的過濾器:將所有值的絕對值相加,然後對比不同過濾器得到的值,選擇那個相加起來得到最小值的過濾器。
壓縮
在一行畫素被過濾後,就會執行DEFLATE壓縮,這是LZ77延伸出來的一種演算法。該演算法結合了LZ77編碼和哈夫曼編碼,它跟PKWARE、PKZIP、GZIP等差不多相同。這種實現方式雖然是現成的,但用在壓縮圖片資料上,還是有一些需要注意的點:
* Deflate演算法只能匹配3到258個之間符號,所以最大的壓縮比只能到1035:1;
* 如果匹配到的符號小於3,那麼你會產生一些額外的開銷來表示這些符號;
上面的這兩點意味著你的圖片大小會受到每一行畫素的匹配程度影響。
你可以看一下面這兩張圖片,左邊那張270x90的圖只有20k,而右邊那張270x92的圖是左邊那張的2倍大。
這似乎不符合邏輯,一張圖片多540畫素在壓縮率上就少了一半。不過我們看仔細點,就能知道原因了,下面這張圖表示壓縮器怎麼去壓縮一個給定的畫素的。深藍色代表壓縮率很高的區域,黃色/紅色代表沒怎麼被壓縮的區域。
這是怎麼出現的呢,原因是小圖的每一行畫素的匹配度更高,那麼它的壓縮率就更高。你要是調整了大小,匹配度一變化,這種情況就有可能出現。一些潛在的匹配物件不在壓縮區域裡,它們沒有匹配到,這就又可能導致出現一張大圖。
如果你想知道你的PNG圖片的壓縮率如何,可以下個PNGThermal看一下。
PNG圖片編解碼 libpng
PNG圖片的編解碼,已經有很多開源的專案。libpng都比較成熟的官方的png編解碼器,以動態庫的形式提供,使用時load libpng的庫就可以使用。LodePNG是一個整合度比較高的編解碼器,沒有依賴,不用載入zlib或libpng等,直接可以使用, https://github.com/lvandeve/lodepng。 LodePNG建議大家去了解一下,通過LodePNG能夠很好理解PNG的編解碼實現。我們這裡側重用libpng使用。
Android平臺上,libpng的原始碼在external/libpng
目錄下;當前的版本應該是16.34。
https://sourceforge.net/projects/libpng/files/libpng16/1.6.34/libpng-1.6.34.tar.gz
png採用的壓縮演算法是分開的:
git clone https://github.com/madler/zlib.git
我們用Android Studio,建立一個純Native的應用,直接在 native中去編譯,使用libpng和zlib。
樣例程式碼請參考github Codec-PngCodec , 這是我們的PngCodec的檔案結構:
├── build.gradle
├── CMakeLists.txt
├── libs
├── png
│ └── libpng-1.6.34
├── src
│ ├── AndroidManifest.xml
│ ├── cpp
│ │ └── PngCodecNativeActivity.cpp
│ ├── java
│ └── res
└── zlib
└── zlib-1.2.11
將libpng和zlib,放到工程目錄下。將工程連到CMakeLists.txt。
zlib的CMake
add_library( zlib
STATIC
zlib/zlib-1.2.11/adler32.c
zlib/zlib-1.2.11/compress.c
zlib/zlib-1.2.11/crc32.c
zlib/zlib-1.2.11/deflate.c
zlib/zlib-1.2.11/gzclose.c
zlib/zlib-1.2.11/gzlib.c
zlib/zlib-1.2.11/gzread.c
zlib/zlib-1.2.11/gzwrite.c
zlib/zlib-1.2.11/infback.c
zlib/zlib-1.2.11/inflate.c
zlib/zlib-1.2.11/inftrees.c
zlib/zlib-1.2.11/inffast.c
zlib/zlib-1.2.11/trees.c
zlib/zlib-1.2.11/uncompr.c
zlib/zlib-1.2.11/zutil.c )
target_include_directories(zlib PRIVATE
zlib/zlib-1.2.11 )
libpng的CMake
add_library( png
STATIC
png/libpng-1.6.34/png.c
png/libpng-1.6.34/pngerror.c
png/libpng-1.6.34/pngget.c
png/libpng-1.6.34/pngmem.c
png/libpng-1.6.34/pngpread.c
png/libpng-1.6.34/pngread.c
png/libpng-1.6.34/pngrio.c
png/libpng-1.6.34/pngrtran.c
png/libpng-1.6.34/pngrutil.c
png/libpng-1.6.34/pngset.c
png/libpng-1.6.34/pngtrans.c
png/libpng-1.6.34/pngwio.c
png/libpng-1.6.34/pngwrite.c
png/libpng-1.6.34/pngwtran.c
png/libpng-1.6.34/pngwutil.c )
target_include_directories(png PRIVATE
png/libpng-1.6.34
zlib/zlib-1.2.11 )
native應用的CMake
add_library( png_codec
SHARED
src/main/cpp/PngCodecNativeActivity.cpp )
target_include_directories(png_codec PRIVATE
${ANDROID_NDK}/sources/android/native_app_glue
png/libpng-1.6.34
zlib/zlib-1.2.11 )
target_link_libraries( png_codec
native_activity_glue
android
zlib
png
log )
Native應用我們編譯為libpng_codec.so,在AndroidManifest中使用:
<application
...
android:hasCode="false">
<activity android:name="android.app.NativeActivity">
<meta-data
android:name="android.app.lib_name"
android:value="png_codec" />
編譯時,pnglibconf.h需要放在png/libpng-1.6.34目錄下,從scripts/pnglibconf.h.prebuilt拷貝。我們這邊註釋掉NEON
的支援。
下面是一個簡易的讀取png圖片的實現:
void readPngFile(char *name) {
ALOGE("readPngFile %s\n", name);
// 前邊幾句是扯淡,初始化各種結構
FILE *file = fopen(name, "rb");
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
png_infop info_ptr = png_create_info_struct(png_ptr);
setjmp(png_jmpbuf(png_ptr));
// 這句很重要
png_init_io(png_ptr, file);
// 讀檔案了
png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND, 0);
// 得到檔案的寬高色深
int m_width = png_get_image_width(png_ptr, info_ptr);
int m_height = png_get_image_height(png_ptr, info_ptr);
int color_type = png_get_color_type(png_ptr, info_ptr);
// 申請個記憶體玩玩,這裡用的是c++語法,甭想再c上編過
int size = m_height * m_width * 4;
char* head = static_cast<char*>(malloc(size));
int pos = 0;
// row_pointers裡邊就是傳說中的rgba資料了
png_bytep *row_pointers = png_get_rows(png_ptr, info_ptr);
// 拷貝!!注意,如果你讀取的png沒有A通道,就要3位3位的讀。還有就是注意位元組對其的問題,最簡單的就是別用不能被4整除的寬度就行了。讀過你實在想用,就要在這裡加上相關的對齊處理。
ALOGE("png file %s size(%dx%d) pixles:", name, m_width, m_height);
for (int i = 0; i < m_height; i++) {
for (int j = 0; j < (4 * m_width); j += 4) {
head[pos] = row_pointers[i][j + 2]; // blue
pos++;
head[pos] = row_pointers[i][j + 1]; // green
pos++;
head[pos] = row_pointers[i][j]; // red
pos++;
head[pos] = row_pointers[i][j + 3]; // alpha
pos++;
ALOGE("%02x %02x %02x %02x", head[pos-4], head[pos-3], head[pos-2], head[pos-1]);
}
}
free(head);
// 好了,你可以用這個資料作任何的事情了。。。把它顯示出來或者打印出來都行。
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
fclose(file);
return;
}
我們將我們的png_4x2_32bit.png
,讀出來,pixels為:
0000 00 ff 0000 ffff ff00 00ff 00ff 00ff
ffff ffff 00ff 00ff 0000 ffff ff00 00ff
還記得bmp的時候,你要轉為png之前,pixels是什麼樣的嗎?
ffff ff7f 00ff 007f 0000 ff7f ff00 007f 0000 0000 0000 ff00 ff00 0000 00ff 0000
對比一下:
- 轉換的時候,Alpha被去掉了,都設定為不透明的
- png的掃描方式和bmp吧一樣,png是從左上開始掃,bmp卻是從左下。
再回過頭看看測試程式碼:
png資訊用結構體png_inforp
描述,本體是png_info_def
,定義在pnginfo.h中。
struct png_info_def
{
/* The following are necessary for every PNG file */
png_uint_32 width; /* width of image in pixels (from IHDR) */
png_uint_32 height; /* height of image in pixels (from IHDR) */
png_uint_32 valid; /* valid chunk data (see PNG_INFO_ below) */
png_size_t rowbytes; /* bytes needed to hold an untransformed row */
png_colorp palette; /* array of color values (valid & PNG_INFO_PLTE) */
png_uint_16 num_palette; /* number of color entries in "palette" (PLTE) */
png_uint_16 num_trans; /* number of transparent palette color (tRNS) */
png_byte bit_depth; /* 1, 2, 4, 8, or 16 bits/channel (from IHDR) */
png_byte color_type; /* see PNG_COLOR_TYPE_ below (from IHDR) */
/* The following three should have been named *_method not *_type */
png_byte compression_type; /* must be PNG_COMPRESSION_TYPE_BASE (IHDR) */
png_byte filter_type; /* must be PNG_FILTER_TYPE_BASE (from IHDR) */
png_byte interlace_type; /* One of PNG_INTERLACE_NONE, PNG_INTERLACE_ADAM7 */
... ...
};
png檔案用結構體png_structp
描述,本體是png_struct_def
,定義在pngstruct.h中。
struct png_struct_def
{
#ifdef PNG_SETJMP_SUPPORTED
jmp_buf jmp_buf_local; /* New name in 1.6.0 for jmp_buf in png_struct */
png_longjmp_ptr longjmp_fn;/* setjmp non-local goto function. */
jmp_buf *jmp_buf_ptr; /* passed to longjmp_fn */
size_t jmp_buf_size; /* size of the above, if allocated */
#endif
png_error_ptr error_fn; /* function for printing errors and aborting */
#ifdef PNG_WARNINGS_SUPPORTED
png_error_ptr warning_fn; /* function for printing warnings */
#endif
png_voidp error_ptr; /* user supplied struct for error functions */
png_rw_ptr write_data_fn; /* function for writing output data */
png_rw_ptr read_data_fn; /* function for reading input data */
png_voidp io_ptr; /* ptr to application struct for I/O functions */
... ...
libpng都是用c寫的,用的結構體,這樣實現,C/C++中都可以用。
png資訊的讀取,用的png_read_png介面,定義在pngread.c中。在png_read_info函式中,採用迴圈的模式,將PNG的所有資料塊資料都讀出來。比如IHDR資料塊,採用png_handle_IHDR進行處理。
/* Read and check the IDHR chunk */
void /* PRIVATE */
png_handle_IHDR(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length)
{
png_byte buf[13];
png_uint_32 width, height;
int bit_depth, color_type, compression_type, filter_type;
int interlace_type;
png_debug(1, "in png_handle_IHDR");
if ((png_ptr->mode & PNG_HAVE_IHDR) != 0)
png_chunk_error(png_ptr, "out of place");
/* Check the length */
if (length != 13)
png_chunk_error(png_ptr, "invalid");
png_ptr->mode |= PNG_HAVE_IHDR;
png_crc_read(png_ptr, buf, 13);
png_crc_finish(png_ptr, 0);
width = png_get_uint_31(png_ptr, buf);
height = png_get_uint_31(png_ptr, buf + 4);
bit_depth = buf[8];
color_type = buf[9];
compression_type = buf[10];
filter_type = buf[11];
interlace_type = buf[12];
/* Set internal variables */
png_ptr->width = width;
png_ptr->height = height;
png_ptr->bit_depth = (png_byte)bit_depth;
png_ptr->interlaced = (png_byte)interlace_type;
png_ptr->color_type = (png_byte)color_type;
#ifdef PNG_MNG_FEATURES_SUPPORTED
png_ptr->filter_type = (png_byte)filter_type;
#endif
png_ptr->compression_type = (png_byte)compression_type;
/* Find number of channels */
switch (png_ptr->color_type)
{
default: /* invalid, png_set_IHDR calls png_error */
case PNG_COLOR_TYPE_GRAY:
case PNG_COLOR_TYPE_PALETTE:
png_ptr->channels = 1;
break;
case PNG_COLOR_TYPE_RGB:
png_ptr->channels = 3;
break;
case PNG_COLOR_TYPE_GRAY_ALPHA:
png_ptr->channels = 2;
break;
case PNG_COLOR_TYPE_RGB_ALPHA:
png_ptr->channels = 4;
break;
}
/* Set up other useful info */
png_ptr->pixel_depth = (png_byte)(png_ptr->bit_depth * png_ptr->channels);
png_ptr->rowbytes = PNG_ROWBYTES(png_ptr->pixel_depth, png_ptr->width);
png_debug1(3, "bit_depth = %d", png_ptr->bit_depth);
png_debug1(3, "channels = %d", png_ptr->channels);
png_debug1(3, "rowbytes = %lu", (unsigned long)png_ptr->rowbytes);
png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth,
color_type, interlace_type, compression_type, filter_type);
}
IHDR的大小為13,所以,直接申請13byte的buf。資訊儲存到info_ptr中。
圖片資料,通過png_read_image進行獲取。
void PNGAPI
png_read_image(png_structrp png_ptr, png_bytepp image)
{
png_uint_32 i, image_height;
int pass, j;
png_bytepp rp;
png_debug(1, "in png_read_image");
if (png_ptr == NULL)
return;
#ifdef PNG_READ_INTERLACING_SUPPORTED
if ((png_ptr->flags & PNG_FLAG_ROW_INIT) == 0)
{
pass = png_set_interlace_handling(png_ptr);
/* And make sure transforms are initialized. */
png_start_read_image(png_ptr);
}
else
{
if (png_ptr->interlaced != 0 &&
(png_ptr->transformations & PNG_INTERLACE) == 0)
{
/* Caller called png_start_read_image or png_read_update_info without
* first turning on the PNG_INTERLACE transform. We can fix this here,
* but the caller should do it!
*/
png_warning(png_ptr, "Interlace handling should be turned on when "
"using png_read_image");
/* Make sure this is set correctly */
png_ptr->num_rows = png_ptr->height;
}
/* Obtain the pass number, which also turns on the PNG_INTERLACE flag in
* the above error case.
*/
pass = png_set_interlace_handling(png_ptr);
}
#else
if (png_ptr->interlaced)
png_error(png_ptr,
"Cannot read interlaced image -- interlace handler disabled");
pass = 1;
#endif
image_height=png_ptr->height;
for (j = 0; j < pass; j++)
{
rp = image;
for (i = 0; i < image_height; i++)
{
png_read_row(png_ptr, *rp, NULL);
rp++;
}
}
}
在png_read_row中,通過png_read_IDAT_data去獲取pixels資料。這裡的資料是經過壓縮的,需要用到zlib進行解壓縮。解壓用的zlib的inflateInit() + inflate() + inflateEnd()
等函式。
解壓用到z_stream_s結構,用以讀寫zlib輸入輸出的資料。
typedef struct z_stream_s {
z_const Bytef *next_in; /* next input byte */
uInt avail_in; /* number of bytes available at next_in */
uLong total_in; /* total number of input bytes read so far */
Bytef *next_out; /* next output byte will go here */
uInt avail_out; /* remaining free space at next_out */
uLong total_out; /* total number of bytes output so far */
z_const char *msg; /* last error message, NULL if no error */
struct internal_state FAR *state; /* not visible by applications */
alloc_func zalloc; /* used to allocate the internal state */
free_func zfree; /* used to free the internal state */
voidpf opaque; /* private data object passed to zalloc and zfree */
int data_type; /* best guess about the data type: binary or text
for deflate, or the decoding state for inflate */
uLong adler; /* Adler-32 or CRC-32 value of the uncompressed data */
uLong reserved; /* reserved for future use */
} z_stream;
我們可以自己加log,將解壓的PNG圖片的畫素資料打印出來
PngCodec: png_show_byte bnext_in 1857630082FFFF81184C8121108029B0E0FFFF00052B10F0
這和我們用二進位制編輯器編輯png圖片時的檔案是不是一樣的?
- libpng編碼
編碼和解碼類似,編碼時提供png_write_info
和png_write_image
,一個寫Png檔案資訊,一個寫畫素點。我們來看例項。前面我們用png轉碼工具將bmp轉換為png時,丟了Alpha資訊,我們現在將Alpha資訊加上。
void writePngFile(char *fileName, png_byte* src , int width, int height)
{
png_structp png_ptr;
png_infop info_ptr;
png_colorp palette;
FILE *fp = fopen(fileName, "wb");
if (fp == NULL)
return ;
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (png_ptr == NULL)
{
fclose(fp);
return ;
}
/* Allocate/initialize the image information data. REQUIRED */
info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL)
{
fclose(fp);
png_destroy_write_struct(&png_ptr, NULL);
return ;
}
if (setjmp(png_jmpbuf(png_ptr)))
{
/* If we get here, we had a problem writing the file */
fclose(fp);
png_destroy_write_struct(&png_ptr, &info_ptr);
return ;
}
/* 接下來告訴 libpng 用 fwrite 來寫入 PNG 檔案,並傳給它已按二進位制方式開啟的 FILE* fp */
png_init_io(png_ptr, fp);
/* 設定png檔案的屬性 */
png_set_IHDR(png_ptr, info_ptr, width, height, 8,
PNG_COLOR_TYPE_RGB,
PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE,
PNG_FILTER_TYPE_BASE);
/* 分配調色盤空間。常數 PNG_MAX_PALETTE_LENGTH 的值是256 */
palette = (png_colorp)png_malloc(png_ptr, PNG_MAX_PALETTE_LENGTH * sizeof(png_color));
png_set_PLTE(png_ptr, info_ptr, palette, PNG_MAX_PALETTE_LENGTH)