圖像庫 libpng 編譯與實踐

在之前的文章中介紹了 stb_image 圖像庫暇唾,還順帶提到了 libpng 和 libjpeg ,這篇文章就是介紹如何在 Android 平臺上用 CMake 編譯 libpng 動態(tài)庫以及 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 的動態(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

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

相關文章:

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

Google Jetpack 新組件 CameraX 介紹與實踐

【抖音內推】校招提前批,研發(fā)崗專場!

廣告時間

最近新開了一個知識星球【圖形/圖像/音視頻交流】甸昏,如果有什么疑問顽分,歡迎在知識星球中討論。

image

星球除了探討施蜜,更主要是起到知識點沉淀的作用卒蘸,我會把【OpenGLES技術交流群】中大家的探討相應同步到星球中,作為內容沉淀翻默。

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

image
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市祠肥,隨后出現(xiàn)的幾起案子武氓,更是在濱河造成了極大的恐慌,老刑警劉巖仇箱,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件县恕,死亡現(xiàn)場離奇詭異,居然都是意外死亡剂桥,警方通過查閱死者的電腦和手機忠烛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來权逗,“玉大人美尸,你說我怎么就攤上這事≌遛保” “怎么了师坎?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長堪滨。 經常有香客問我胯陋,道長,這世上最難降的妖魔是什么袱箱? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任遏乔,我火速辦了婚禮,結果婚禮上发笔,老公的妹妹穿的比我還像新娘盟萨。我一直安慰自己,他們只是感情好了讨,可當我...
    茶點故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布捻激。 她就那樣靜靜地躺著制轰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪铺罢。 梳的紋絲不亂的頭發(fā)上艇挨,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天残炮,我揣著相機與錄音韭赘,去河邊找鬼。 笑死势就,一個胖子當著我的面吹牛泉瞻,可吹牛的內容都是我干的。 我是一名探鬼主播苞冯,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼袖牙,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了舅锄?” 一聲冷哼從身側響起鞭达,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎皇忿,沒想到半個月后畴蹭,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡鳍烁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年叨襟,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片幔荒。...
    茶點故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡糊闽,死狀恐怖,靈堂內的尸體忽然破棺而出爹梁,到底是詐尸還是另有隱情右犹,我是刑警寧澤,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布姚垃,位于F島的核電站傀履,受9級特大地震影響,放射性物質發(fā)生泄漏莉炉。R本人自食惡果不足惜钓账,卻給世界環(huán)境...
    茶點故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望絮宁。 院中可真熱鬧梆暮,春花似錦、人聲如沸绍昂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至唠椭,卻和暖如春跳纳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背贪嫂。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工寺庄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人力崇。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓斗塘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親亮靴。 傳聞我的和親對象是個殘疾皇子馍盟,可洞房花燭夜當晚...
    茶點故事閱讀 44,960評論 2 355

推薦閱讀更多精彩內容