圖像庫 libpng 編譯與實踐

在以前的文章中介紹了 stb_image 圖像庫,還順帶提到了 libpng 和 libjpeg ,這篇文章就是介紹如何在 Android 平臺上用 CMake 編譯 libpng 動態庫以及 libpng 使用實踐。html

簡單易用的圖像解碼庫介紹 —— stb_imageandroid

https://glumes.com/post/android/stb-image-introduce/git

<!--more-->github

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 的動態庫。post

CMake 編譯 libpng 動態庫

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

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

相關文章:

簡單易用的圖像解碼庫介紹 —— stb_image

Google Jetpack 新組件 CameraX 介紹與實踐

【抖音內推】校招提早批,研發崗專場!

廣告時間

最近新開了一個知識星球【圖形/圖像/音視頻交流】,若是有什麼疑問,歡迎在知識星球中討論。

星球除了探討,更主要是起到知識點沉澱的做用,我會把【OpenGLES技術交流羣】中你們的探討相應同步到星球中,做爲內容沉澱。

另外,微信公衆號推廣的二維碼也更新了一波,【紙上淺談·多媒體開發札記】,會更加專一分享多媒體開發中的技術學習和實踐積累。

相關文章
相關標籤/搜索