在之前的文章中介紹了 stb_image
圖像庫暇唾,還順帶提到了 libpng 和 libjpeg ,這篇文章就是介紹如何在 Android 平臺上用 CMake 編譯 libpng 動態(tài)庫以及 libpng 使用實踐辰斋。
https://glumes.com/post/android/stb-image-introduce/
libpng 介紹
libpng 的官方介紹網站如下:
下載地址網站如下:
博客中使用的版本是 1.6.37 策州,也是目前最新的版本了。
關于 libpng 的編譯網上已經有不少博客教程了宫仗,但有的是基于 Linux够挂,有的是基于 Android.mk 的,本文會介紹如何在 Android Studio 上通過 CMake 來編譯 Android 的動態(tài)庫藕夫。
CMake 編譯 libpng 動態(tài)庫
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 ()
這段代碼就是判斷系統(tǒng)處理器平臺病蛉,不同平臺所需要編譯的代碼不一樣。而 libpng 會有這樣的適配,主要是因為它用到了 neon 相關優(yōu)化铺然,該優(yōu)化主要是用在 filter
操作方面俗孝。
// libpng 使用 neon 優(yōu)化加速的方法
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 優(yōu)化魄健,還必須通過 add_definitions 方法定義 DPNG_ARM_NEON_OPT 宏的值為 2 驹针,否則在源碼中會認為不需要使用 neon 。
要使用 neon 編譯诀艰,還需要指定編譯器相關參數:
set_property(SOURCE ${libpng_arm_sources}
APPEND_STRING PROPERTY COMPILE_FLAGS " -mfpu=neon")
不過看到網上一些 libpng 編譯文章柬甥,基本沒提到 neon 相關東西,估計這個優(yōu)化加速功能用不上吧其垄。
但是苛蒲,可以在我的 Demo 上看到如何啟用 neon 去編譯,以后也會寫專門的文章來介紹 neon 的使用~~
zlip 庫依賴
libpng 動態(tài)庫編譯還依賴 zlip 庫绿满,要是在其他平臺上需要單獨下載這個庫臂外,但是 Android 上就不需要了,因為 Android 編譯環(huán)境本身就提供了這個庫喇颁,就像我們使用 log 庫一樣漏健。
// 指定要編譯的 so 依賴哪些其他的 so , z 就是 zlib 庫
target_link_libraries(png z log )
Android 編譯環(huán)境中 z
就是 zlip 庫了。
源碼編譯
其他的就是源碼編譯了橘霎,主要是 add_library 方法的使用蔫浆,要指定好需要編譯的源文件。
具體有哪些源文件需要添加到編譯中姐叁,還是請參考如下鏈接瓦盛,就不貼具體代碼了,減少文章篇幅外潜。
https://github.com/glumes/InstantGLSL/blob/master/instantglsl/src/main/cpp/libpng/CMakeLists.txt
完成上述三個過程后原环,就能夠編譯出 libpng 的動態(tài)庫了,實際編譯過程還是參考項目代碼吧处窥。
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 方法了沽瞭。
設置錯誤返回點
由于在創(chuàng)建 png 變量時,用來自定義錯誤處理的參數都傳了 nullptr剩瓶,所以需要設置錯誤返回點驹溃,這樣當 libpng 發(fā)生錯誤時城丧,程序將回到這個調用點,這時候可以做一些清理工作:
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 該跳過相應的數據,否則會出現(xiàn)黑屏布疙,或者通過 rewind 方法重置文件指針蚊惯。
獲取圖像信息
首先創(chuàng)建 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;
// 獲取每行的字節(jié)數量
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 中,具體的實現(xiàn)如下:
通過 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()
// 關聯(lián)數據源锄码,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 創(chuàng)建滋捶。
infop 變量還是 png_create_info_struct 方法創(chuàng)建。
接下來就是設置圖片信息余黎,寫入圖片信息重窟,寫入像素內容,具體的代碼實踐可以參考我的代碼示例惧财。
參考
最后巡扇,在 libpng 的源代碼中,也提供了豐富的示例垮衷,一般這種開源庫都會提供相應的 test 代碼厅翔,通過 test 代碼基本都能找到相應的函數調用。
libpng 的官網示例地址如下:
http://www.libpng.org/pub/png/libpng-manual.txt
有疑問的話搀突,基本都可以在這個上面找到答案刀闷。
相關文章:
Google Jetpack 新組件 CameraX 介紹與實踐
廣告時間
最近新開了一個知識星球【圖形/圖像/音視頻交流】甸昏,如果有什么疑問顽分,歡迎在知識星球中討論。
星球除了探討施蜜,更主要是起到知識點沉淀的作用卒蘸,我會把【OpenGLES技術交流群】中大家的探討相應同步到星球中,作為內容沉淀翻默。
另外缸沃,微信公眾號推廣的二維碼也更新了一波,【紙上淺談·多媒體開發(fā)札記】修械,會更加專注分享多媒體開發(fā)中的技術學習和實踐積累和泌。