<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Android JNI 之 Bitmap 操作

          共 6081字,需瀏覽 13分鐘

           ·

          2021-10-15 16:32


          在 Android 中通過 JNI 去操作 Bitmap。

          在 Android 通過 JNI 去調(diào)用 Bitmap,通過 CMake 去編 so 動態(tài)鏈接庫的話,需要添加 jnigraphics 圖像庫。

          1target_link_libraries(?#?Specifies?the?target?library.
          2???????????????????????native-operation
          3???????????????????????jnigraphics
          4???????????????????????${log-lib}?)

          在 Android 中關(guān)于 JNI Bitmap 的操作,都定義在 bitmap.h 的頭文件里面了,主要就三個函數(shù),明白它們的含義之后就可以去實踐體會了。

          檢索 Bitmap 對象信息

          AndroidBitmap_getInfo 函數(shù)允許原生代碼檢索 Bitmap 對象信息,如它的大小、像素格式等,函數(shù)簽名如下:

          1/**
          2?*?Given?a?java?bitmap?object,?fill?out?the?AndroidBitmapInfo?struct?for?it.
          3?*?If?the?call?fails,?the?info?parameter?will?be?ignored.
          4?*/

          5int?AndroidBitmap_getInfo(JNIEnv*?env,?jobject?jbitmap,
          6??????????????????????????AndroidBitmapInfo*?info)
          ;

          其中,第一個參數(shù)就是 JNI 接口指針,第二個參數(shù)就是 Bitmap 對象的引用,第三個參數(shù)是指向 AndroidBitmapInfo 結(jié)構(gòu)體的指針。

          AndroidBitmapInfo 結(jié)構(gòu)體如下:

           1/**?Bitmap?info,?see?AndroidBitmap_getInfo().?*/
          2typedef?struct?{
          3????/**?The?bitmap?width?in?pixels.?*/
          4????uint32_t????width;
          5????/**?The?bitmap?height?in?pixels.?*/
          6????uint32_t????height;
          7????/**?The?number?of?byte?per?row.?*/
          8????uint32_t????stride;
          9????/**?The?bitmap?pixel?format.?See?{@link?AndroidBitmapFormat}?*/
          10????int32_t?????format;
          11????/**?Unused.?*/
          12????uint32_t????flags;??????//?0?for?now
          13}?AndroidBitmapInfo;

          其中,width 就是 Bitmap 的寬,height 就是高,format 就是圖像的格式,而 stride 就是每一行的字節(jié)數(shù)。

          圖像的格式有如下支持:

           1/**?Bitmap?pixel?format.?*/
          2enum?AndroidBitmapFormat?{
          3????/**?No?format.?*/
          4????ANDROID_BITMAP_FORMAT_NONE??????=?0,
          5????/**?Red:?8?bits,?Green:?8?bits,?Blue:?8?bits,?Alpha:?8?bits.?**/
          6????ANDROID_BITMAP_FORMAT_RGBA_8888?=?1,
          7????/**?Red:?5?bits,?Green:?6?bits,?Blue:?5?bits.?**/
          8????ANDROID_BITMAP_FORMAT_RGB_565???=?4,
          9????/**?Deprecated?in?API?level?13.?Because?of?the?poor?quality?of?this?configuration,?it?is?advised?to?use?ARGB_8888?instead.?**/
          10????ANDROID_BITMAP_FORMAT_RGBA_4444?=?7,
          11????/**?Alpha:?8?bits.?*/
          12????ANDROID_BITMAP_FORMAT_A_8???????=?8,
          13};

          如果 AndroidBitmap_getInfo 執(zhí)行成功的話,會返回 0 ,否則返回一個負(fù)數(shù),代表執(zhí)行的錯誤碼列表如下:

           1/**?AndroidBitmap?functions?result?code.?*/
          2enum?{
          3????/**?Operation?was?successful.?*/
          4????ANDROID_BITMAP_RESULT_SUCCESS???????????=?0,
          5????/**?Bad?parameter.?*/
          6????ANDROID_BITMAP_RESULT_BAD_PARAMETER?????=?-1,
          7????/**?JNI?exception?occured.?*/
          8????ANDROID_BITMAP_RESULT_JNI_EXCEPTION?????=?-2,
          9????/**?Allocation?failed.?*/
          10????ANDROID_BITMAP_RESULT_ALLOCATION_FAILED?=?-3,
          11};

          訪問原生像素緩存

          AndroidBitmap_lockPixels 函數(shù)鎖定了像素緩存以確保像素的內(nèi)存不會被移動。

          如果 Native 層想要訪問像素數(shù)據(jù)并操作它,該方法返回了像素緩存的一個原生指針,

           1/**
          2?*?Given?a?java?bitmap?object,?attempt?to?lock?the?pixel?address.
          3?*?Locking?will?ensure?that?the?memory?for?the?pixels?will?not?move
          4?*?until?the?unlockPixels?call,?and?ensure?that,?if?the?pixels?had?been
          5?*?previously?purged,?they?will?have?been?restored.
          6?*
          7?*?If?this?call?succeeds,?it?must?be?balanced?by?a?call?to
          8?*?AndroidBitmap_unlockPixels,?after?which?time?the?address?of?the?pixels?should
          9?*?no?longer?be?used.
          10?*
          11?*?If?this?succeeds,?*addrPtr?will?be?set?to?the?pixel?address.?If?the?call
          12?*?fails,?addrPtr?will?be?ignored.
          13?*/

          14int?AndroidBitmap_lockPixels(JNIEnv*?env,?jobject?jbitmap,?void**?addrPtr);

          其中,第一個參數(shù)就是 JNI 接口指針,第二個參數(shù)就是 Bitmap 對象的引用,第三個參數(shù)是指向像素緩存地址的指針。

          AndroidBitmap_lockPixels 執(zhí)行成功的話返回 0 ,否則返回一個負(fù)數(shù),錯誤碼列表就是上面提到的。

          釋放原生像素緩存

          對 Bitmap 調(diào)用完 AndroidBitmap_lockPixels 之后都應(yīng)該對應(yīng)調(diào)用一次 AndroidBitmap_unlockPixels ?用來釋放原生像素緩存。

          當(dāng)完成對原生像素緩存的讀寫之后,就應(yīng)該釋放它,一旦釋放后,Bitmap Java 對象又可以在 Java 層使用了,函數(shù)簽名如下:

          1/**
          2?*?Call?this?to?balance?a?successful?call?to?AndroidBitmap_lockPixels.
          3?*/

          4int?AndroidBitmap_unlockPixels(JNIEnv*?env,?jobject?jbitmap);

          其中,第一個參數(shù)就是 JNI 接口指針,第二個參數(shù)就是 Bitmap 對象的引用,如果執(zhí)行成功返回 0,否則返回 1。

          對 Bitmap 的操作,最重要的就是 AndroidBitmap_lockPixels 函數(shù)拿到所有像素的緩存地址,然后對每個像素值進(jìn)行操作,從而更改 Bitmap 。

          實踐

          通過對 Bitmap 進(jìn)行旋轉(zhuǎn),上下翻轉(zhuǎn),左右鏡像來體驗 JNI 的開發(fā)。

          效果如下:

          具體代碼可以參考我的 Github 項目,歡迎 Star。

          https://github.com/glumes/AndroidDevWithCpp

          通過 JNI 將 Bitmap 旋轉(zhuǎn)

          首先定義一個這樣的 native 函數(shù):

          1????//?順時針旋轉(zhuǎn)?90°?的操作
          2????public?native?Bitmap?rotateBitmap(Bitmap?bitmap);

          傳入一個 Bitmap 對象,然后返回一個 Bitmap 對象。

          然后在 C++ 代碼中,首先檢索 Bitmap 的信息,看看是否成功。

          1????AndroidBitmapInfo?bitmapInfo;
          2????int?ret;
          3????if?((ret?=?AndroidBitmap_getInfo(env,?bitmap,?&bitmapInfo))?0)?{
          4????????LOGE("AndroidBitmap_getInfo()?failed?!?error=%d",?ret);
          5????????return?NULL;
          6????}

          接下來就是獲得 Bitmap 的像素緩存指針:

          1????//?讀取?bitmap?的像素內(nèi)容到?native?內(nèi)存
          2????void?*bitmapPixels;
          3????if?((ret?=?AndroidBitmap_lockPixels(env,?bitmap,?&bitmapPixels))?0)?{
          4????????LOGE("AndroidBitmap_lockPixels()?failed?!?error=%d",?ret);
          5????????return?NULL;
          6????}

          這個指針指向的就是 Bitmap 像素內(nèi)容,它是一個以一維數(shù)組的形式保存所有的像素點(diǎn)的值,但是我們在定義 Bitmap 圖像時,都會定義寬和高,這就相對于是一個二維的了,那么就存在 Bitmap 的像素內(nèi)容如何轉(zhuǎn)成指針指向的一維內(nèi)容,是按照行排列還是按照列排列呢?

          在這里是按照行進(jìn)行排列的,而且行的排列是從左往右,列的排列是從上往下,起始點(diǎn)就和屏幕坐標(biāo)原點(diǎn)一樣,位于左上角。

          通過 AndroidBitmap_lockPixels 方法,bitmapPixels 指針就指向了 Bitmap 的像素內(nèi)容,它的長度就是 Bitmap 的寬和高的乘積。

          要將 Bitmap 進(jìn)行旋轉(zhuǎn),可以通過直接更改 bitmapPixels 指針指向的像素點(diǎn)的值,也可以通過創(chuàng)建一個新的 Bitmap 對象,然后將像素值填充到 Bitmap 對象中,這里選擇后者的實現(xiàn)方式。

          首先創(chuàng)建一個新的 Bitmap 對象,參考之前文章中提到的方式:Android 通過 JNI 訪問 Java 字段和方法調(diào)用

          在 Java 代碼中,通過 ?createBitmap 方法可以創(chuàng)建一個 Bitmap,如下所示:

          1?Bitmap.createBitmap(int?width,?int?height,?@NonNull?Config?config)`

          所以在 JNI 中就需要調(diào)用 Bitmap 的靜態(tài)方法來創(chuàng)建一個 Bitmap 對象。

           1jobject?generateBitmap(JNIEnv?*env,?uint32_t?width,?uint32_t?height)?{
          2
          3????jclass?bitmapCls?=?env->FindClass("android/graphics/Bitmap");
          4????jmethodID?createBitmapFunction?=?env->GetStaticMethodID(bitmapCls,
          5????????????????????????????????????????????????????????????"createBitmap",
          6????????????????????????????????????????????????????????????"(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
          7????jstring?configName?=?env->NewStringUTF("ARGB_8888");
          8????jclass?bitmapConfigClass?=?env->FindClass("android/graphics/Bitmap$Config");
          9????jmethodID?valueOfBitmapConfigFunction?=?env->GetStaticMethodID(
          10????????????bitmapConfigClass,?"valueOf",
          11????????????"(Ljava/lang/String;)Landroid/graphics/Bitmap$Config;");
          12
          13????jobject?bitmapConfig?=?env->CallStaticObjectMethod(bitmapConfigClass,
          14???????????????????????????????????????????????????????valueOfBitmapConfigFunction,?configName);
          15
          16????jobject?newBitmap?=?env->CallStaticObjectMethod(bitmapCls,
          17????????????????????????????????????????????????????createBitmapFunction,
          18????????????????????????????????????????????????????width,
          19????????????????????????????????????????????????????height,?bitmapConfig);
          20????return?newBitmap;
          21}

          首先通過 FindClass 方法找到 Config 類,得到一個 ARGB_8888 的配置,然后得到 Bitmap 類,調(diào)用它的靜態(tài)方法 createBitmap 創(chuàng)建一個新的 Bitmap 對象,具體可以參考之前的文章。

          在這里要傳入新 Bitmap 的寬高,這個寬高也是通過 AndroidBitmap_getInfo 方法得到原來的寬高之后,根據(jù)不同的操作計算后得到的。

          1????//?旋轉(zhuǎn)操作,新?Bitmap?的寬等于原來的高,新?Bitmap?的高等于原來的寬
          2???uint32_t?newWidth?=?bitmapInfo.height;
          3????uint32_t?newHeight?=?bitmapInfo.width;

          有了新的 Bitmap 對象,又有了原有的 Bitmap 像素指針,接下來就是創(chuàng)建新的像素指針,并填充像素內(nèi)容,然后把這個像素內(nèi)容再填充到 Bitmap 上。

          1????//?創(chuàng)建一個新的數(shù)組指針,把這個新的數(shù)組指針填充像素值
          2????uint32_t?*newBitmapPixels?=?new?uint32_t[newWidth?*?newHeight];
          3????int?whereToGet?=?0;
          4????for?(int?y?=?0;?y?5????????for?(int?x?=?newWidth?-?1;?x?>=?0;?x--)?{
          6????????????uint32_t?pixel?=?((uint32_t?*)?bitmapPixels)[whereToGet++];
          7????????????newBitmapPixels[newWidth?*?y?+?x]?=?pixel;
          8????????}
          9????}

          在這兩個 for循環(huán)里面就是從原來的像素指針中取出像素值,然后把它按照特定的排列順序填充到新的像素指針中對應(yīng)位置的值,這里也就是前面強(qiáng)調(diào)的像素指針是按照行進(jìn)行排列的,起點(diǎn)是 Bitmap 的左上角。

          1????void?*resultBitmapPixels;
          2????if?((ret?=?AndroidBitmap_lockPixels(env,?newBitmap,?&resultBitmapPixels))?0)?{
          3????????LOGE("AndroidBitmap_lockPixels()?failed?!?error=%d",?ret);
          4????????return?NULL;
          5????}
          6????int?pixelsCount?=?newWidth?*?newHeight;
          7????memcpy((uint32_t?*)?resultBitmapPixels,?newBitmapPixels,?sizeof(uint32_t)?*?pixelsCount);
          8????AndroidBitmap_unlockPixels(env,?newBitmap);

          再次創(chuàng)建一個 resultBitmapPixels 指針,并調(diào)用 AndroidBitmap_lockPixels 方法獲取新的 Bitmap 的像素指針緩存,然后調(diào)用 memcpy 方法,將待填充的像素指針填充到 resultBitmapPixels 上,這樣就完成了像素的賦值,最后調(diào)用 AndroidBitmap_unlockPixels 方法釋放像素指針緩存,完成整個賦值過程。

          就這樣通過讀取原有 Bitmap 的像素內(nèi)容然后進(jìn)行操作后再賦值給新的 Bitmap 對象就完成了 JNI 操作 Bitmap 。

          通過 JNI 將 Bitmap 上下翻轉(zhuǎn)和左右鏡像

          將 Bitmap 進(jìn)行上下翻轉(zhuǎn)以及左右鏡像和旋轉(zhuǎn)操作類似了,只是針對像素指針的操作方式不同。

          上下翻轉(zhuǎn)的操作:

          1????int?whereToGet?=?0;
          2????for?(int?y?=?0;?y?3????????for?(int?x?=?0;?x?4????????????uint32_t?pixel?=?((uint32_t?*)?bitmapPixels)[whereToGet++];
          5????????????newBitmapPixels[newWidth?*?(newHeight?-?1?-?y)?+?x]?=?pixel;
          6????????}
          7????}

          左右鏡像的操作:

          1????int?whereToGet?=?0;
          2????for?(int?y?=?0;?y?3????????for?(int?x?=?newWidth?-?1;?x?>=?0;?x--)?{
          4????????????uint32_t?pixel?=?((uint32_t?*)?bitmapPixels)[whereToGet++];
          5????????????newBitmapPixels[newWidth?*?y?+?x]?=?pixel;
          6????????}
          7????}

          其他的操作都相同了,具體還是看項目代碼吧。


          技術(shù)交流,歡迎加我微信:ezglumes ,拉你入技術(shù)交流群。

          推薦閱讀:

          音視頻面試基礎(chǔ)題

          OpenGL ES 學(xué)習(xí)資源分享

          開通專輯 | 細(xì)數(shù)那些年寫過的技術(shù)文章專輯

          NDK 學(xué)習(xí)進(jìn)階免費(fèi)視頻來了

          推薦幾個堪稱教科書級別的 Android 音視頻入門項目

          覺得不錯,點(diǎn)個在看唄~

          瀏覽 131
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本18禁黄网站 | 成人免费精品 | 内射国产视频 | 日本不卡视屏 | 成人欧美一区二区三区牛牛影视 |