在以前的文章中介紹了 stb_image
圖像庫,還順帶提到了 libpng 和 libjpeg ,這篇文章就是介紹如何在 Android 平臺上用 CMake 編譯 libpng 動態庫以及 libpng 使用實踐。html
【簡單易用的圖像解碼庫介紹 —— stb_image】android
https://glumes.com/post/android/stb-image-introduce/git
<!--more-->github
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
在 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 的使用~~
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 的動態庫了,實際編譯過程仍是參考項目代碼吧。
編譯是小事,重點在使用~~~
以解碼 png 圖片獲取像素內容爲例:
首先是初始化 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; }
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
有疑問的話,基本均可以在這個上面找到答案。
Google Jetpack 新組件 CameraX 介紹與實踐
最近新開了一個知識星球【圖形/圖像/音視頻交流】,若是有什麼疑問,歡迎在知識星球中討論。
星球除了探討,更主要是起到知識點沉澱的做用,我會把【OpenGLES技術交流羣】中你們的探討相應同步到星球中,做爲內容沉澱。
另外,微信公衆號推廣的二維碼也更新了一波,【紙上淺談·多媒體開發札記】,會更加專一分享多媒體開發中的技術學習和實踐積累。