1. 程式人生 > >[Image_Codec]常見圖片格式的封裝及編解碼-Android平臺(二)PNG

[Image_Codec]常見圖片格式的封裝及編解碼-Android平臺(二)PNG

PNG圖片格式

PNG(Portable Network Graphics) 是一種光柵化的,無失真壓縮的圖片檔案格式。其設計的目的是替換GIF,是目前網路中用得最廣的無失真壓縮圖片格式。我們可以用工具將前面的Bitmap轉換為PNG。

下面是從上一章所說的 BMP轉換過來的png_4x2_32bit.png的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_infopng_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)