天天看點

圖像庫 libpng 編譯與實踐

作者:星隕

來源:

音視訊開發進階 簡單易用的圖像解碼庫介紹 —— stb_image https://glumes.com/post/android/stb-image-introduce/

libpng 介紹

libpng 的官方介紹網站如下:

http://www.libpng.org/pub/png/libpng.html

下載下傳位址網站如下:

https://sourceforge.net/projects/libpng/files/

部落格中使用的版本是 1.6.37 ,也是目前最新的版本了。

關于 libpng 的編譯網上已經有不少部落格教程了,但有的是基于 Linux,有的是基于 Android.mk 的,本文會介紹如何在 Android Studio 上通過 CMake 來編譯 Android 的動态庫。

CMake 編譯 libpng 動态庫neon 相關編譯

neon 相關編譯

在 libpng 的源代碼中,就提供了 CMakeLists.txt 檔案用以說明如何編譯,但是卻不能直接用在 Android 平台上,不過可以借鑒其源碼作為參考。

由于 CMake 跨平台編譯的特性,一般大型項目代碼編譯都會針對平台做适配,常見代碼結構如下:

if (CMAKE_SYSTEM_PROCESSOR MATCHES "^arm" OR
            CMAKE_SYSTEM_PROCESSOR MATCHES "^aarch64")
        set(libpng_arm_sources
                arm/arm_init.c
                arm/filter_neon.S
                arm/filter_neon_intrinsics.c
                arm/palette_neon_intrinsics.c)
        // 定義宏
        add_definitions(-DPNG_ARM_NEON_OPT=2)
    endif ()      

這段代碼就是判斷系統處理器平台,不同平台所需要編譯的代碼不一樣。而 libpng 會有這樣的适配,主要是因為它用到了 neon 相關優化,該優化主要是用在

filter

操作方面。

// libpng 使用 neon 優化加速的方法
void
png_read_filter_row_up_neon(png_row_infop row_info, png_bytep row,
   png_const_bytep prev_row)
{
   png_bytep rp = row;
   png_bytep rp_stop = row + row_info->rowbytes;
   png_const_bytep pp = prev_row;
   png_debug(1, "in png_read_filter_row_up_neon");
   for (; rp < rp_stop; rp += 16, pp += 16)
   {
      uint8x16_t qrp, qpp;
      qrp = vld1q_u8(rp);
      qpp = vld1q_u8(pp);
      qrp = vaddq_u8(qrp, qpp);
      vst1q_u8(rp, qrp);
   }
}      

通過檢視 libpng 源代碼,要啟用 neon 優化,還必須通過 add_definitions 方法定義 DPNG_ARM_NEON_OPT 宏的值為 2 ,否則在源碼中會認為不需要使用 neon 。

要使用 neon 編譯,還需要指定編譯器相關參數:

set_property(SOURCE ${libpng_arm_sources}
        APPEND_STRING PROPERTY COMPILE_FLAGS " -mfpu=neon")      

不過看到網上一些 libpng 編譯文章,基本沒提到 neon 相關東西,估計這個優化加速功能用不上吧。

但是,可以在我的 Demo 上看到如何啟用 neon 去編譯,以後也會寫專門的文章來介紹 neon 的使用~~

zlip 庫依賴

libpng 動态庫編譯還依賴 zlip 庫,要是在其他平台上需要單獨下載下傳這個庫,但是 Android 上就不需要了,因為 Android 編譯環境本身就提供了這個庫,就像我們使用 log 庫一樣。

// 指定要編譯的 so 依賴哪些其他的 so , z 就是 zlib 庫
target_link_libraries(png z log )      

Android 編譯環境中

z

就是 zlip 庫了。

源碼編譯源碼編譯

其他的就是源碼編譯了,主要是 add_library 方法的使用,要指定好需要編譯的源檔案。

具體有哪些源檔案需要添加到編譯中,還是請參考如下連結,就不貼具體代碼了,減少文章篇幅。

https://github.com/glumes/InstantGLSL/blob/master/instantglsl/src/main/cpp/libpng/CMakeLists.txt

完成上述三個過程後,就能夠編譯出 libpng 的動态庫了,實際編譯過程還是參考項目代碼吧。

libpng 的使用實踐

編譯是小事,重點在使用~~~

以解碼 png 圖檔擷取像素内容為例:

linpng 初始

首先是初始化 libpng ,得到

png_structp

結構體。它可以說是代表了 libpng 上下文,在方法調用時都需要把它作為第一個參數傳入。

// 傳 nullptr 的參數是用來自定義錯誤處理的,這裡不需要
    png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);      

由于是讀取,方法名中帶有 read ,如果是寫入,那就是 png_create_write_struct 方法了。

設定錯誤傳回點

由于在建立 png 變量時,用來自定義錯誤處理的參數都傳了 nullptr,是以需要設定錯誤傳回點,這樣當 libpng 發生錯誤時,程式将回到這個調用點,這時候可以做一些清理工作:

if (setjmp(png_jmpbuf(png))) {
        png_destroy_read_struct(&png, nullptr, nullptr);
        fclose(fp);
        return;
    }      

判斷檔案是否是 png 格式

libpng 提供了 png_sig_cmp 方法來檢查檔案是否 png 格式。

#define PNG_BYTES_TO_CHECK 4
    char buf[PNG_BYTES_TO_CHECK];
    // 讀取 buffer
    if (fread(buf, 1, PNG_BYTES_TO_CHECK, fp) != PNG_BYTES_TO_CHECK) {
        return;
    }
    // 判斷
    if (!png_sig_cmp(reinterpret_cast<png_const_bytep>(buf), 0, PNG_BYTES_TO_CHECK)) {
        // 傳回值不等于 0 則是 png 檔案格式
    }      

如果調用了該方法,需要通過 png_set_sig_bytes 方法告訴 libpng 該跳過相應的資料,否則會出現黑屏,或者通過 rewind 方法重置檔案指針。

擷取圖像資訊

首先建立 png_infop 結構體來代表圖像資訊:

png_infop infop = png_create_info_struct(png);      

然後是設定圖像的資料源,前提是要得到檔案路徑:

// 根據檔案路徑打開檔案
    FILE *fp = fopen(mFileName.c_str(), "rb");
    // 設定圖像資料源
    png_init_io(png, fp);      

接下來是讀取資訊:

png_read_png(png, infop,
                 (PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_PACKING | PNG_TRANSFORM_EXPAND),      

通過如下方法,能得到圖像具體某方面資訊:

mWidth = png_get_image_width(png, info);
    mHeight = png_get_image_height(png, info);
    mColorType = png_get_color_type(png, info);
    mBitDepth = png_get_bit_depth(png, info);      

當然也可以通過 png_get_IHDR 方法去獲得資訊

png_get_IHDR(png, infop, &mWidth, &mHeight, &mBitDepth, &mColorType, &mInterlaceType,
                 &mCompressionType, &mFilterType);      

擷取像素内容

通過 png_get_rows 方法按行擷取所有的資料,然後指派到像素指針上去。

// 代表像素内容的指針
    unsigned char *mPixelData;
    // 擷取每行的位元組數量
    unsigned int row_bytes = png_get_rowbytes(png, infop);
    mPixelData = new unsigned char[row_bytes * mHeight];
    png_bytepp rows = png_get_rows(png, infop);
    // 逐行讀取,并填充到像素指針上去
    for (int i = 0; i < mHeight; ++i) {
        memcpy(mPixelData + (row_bytes * i), rows[i], row_bytes);
    }      

其實在前面的 png_read_png 方法中就已經得到了所有的像素内容,儲存在 infop 變量的 row_pointers 中,具體的實作如下:

通過 png_read_image 方式讀取像素内容:

// row_pointers 當成了一維指針數組
    png_bytep *row_pointers;
    row_pointers = (png_bytep *) malloc(sizeof(png_bytep) * mHeight);
    for (int y = 0; y < mHeight; y++) {
        row_pointers[y] = (png_byte *) malloc(png_get_rowbytes(png, info));
    }
    png_read_image(png, row_pointers);      

最後,别忘了調用 png_read_end 方法結束讀取。

有了像素内容,就可以做一個常見的渲染操作了,将像素内容渲染繪制到紋理上。

儲存圖檔

最後介紹如何根據像素内容去儲存圖檔,在 libpng 中也提供了相應的方法調用,流程就是如下方法:

png = png_create_write_struct()
    infop = png_create_info_struct()
    // 關聯資料源,png 和要寫入的檔案
    png_init_io(png,fp)
    // 設定 infop 相關參數,代表最好要生成的圖檔檔案相關資訊
    png_set_IHDR()
    // 寫入圖檔資訊
    png_write_info(png, infop);
    // 寫入圖檔像素内容
    png_write_image(png, row_pointers);
    // 結束寫入
    png_write_end(png, NULL);      

流程和讀取像素内容恰好相反。

其中 png 變量要通過 png_create_write_struct 建立。

infop 變量還是 png_create_info_struct 方法建立。

接下來就是設定圖檔資訊,寫入圖檔資訊,寫入像素内容,具體的代碼實踐可以參考我的代碼示例。

參考

最後,在 libpng 的源代碼中,也提供了豐富的示例,一般這種開源庫都會提供相應的 test 代碼,通過 test 代碼基本都能找到相應的函數調用。

libpng 的官網示例位址如下:

http://www.libpng.org/pub/png/libpng-manual.txt

有疑問的話,基本都可以在這個上面找到答案。

安卓以及音視訊雜談
「視訊雲技術」你最值得關注的音視訊技術公衆号,每周推送來自阿裡雲一線的實踐技術文章,在這裡與音視訊領域一流工程師交流切磋。  
圖像庫 libpng 編譯與實踐

繼續閱讀