作者:星隕
來源:
音視訊開發進階 【 簡單易用的圖像解碼庫介紹 —— 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有疑問的話,基本都可以在這個上面找到答案。
安卓以及音視訊雜談「視訊雲技術」你最值得關注的音視訊技術公衆号,每周推送來自阿裡雲一線的實踐技術文章,在這裡與音視訊領域一流工程師交流切磋。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5CZhFDMkBTZyUDZ4kzN3EWOkNTO0cTZjNDM2EWMlFzNh9CX5d2bs92Yl1iclB3bsVmdlR2LcNWaw9CXt92Yu4GZjlGbh5yYjV3Lc9CX6MHc0RHaiojIsJye.png)