Android Bitmap像素排列與JNI操作

圖像的數(shù)值表示

RGB

RGB顏色模型即紅綠藍(lán)顏色模型。由模仿生物視網(wǎng)膜三種視錐細(xì)胞產(chǎn)生,之后通過三原色疊加來進(jìn)行彩色圖像顯示。通過在黑色上不斷疊加三原色來顯示不同的顏色。在RGB顏色空間中,分別將RGB作為笛卡爾坐標(biāo)系中XYZ坐標(biāo)系產(chǎn)生。每一個顏色取值范圍為[0,256)

RGB是從顏色發(fā)光的原理來設(shè)計定的,通俗點(diǎn)說它的顏色混合方式就好像有紅、綠、藍(lán)三盞燈,當(dāng)它們的光相互疊合的時候,色彩相混,而亮度卻等于兩者亮度之總和,越混合亮度越高,即加法混合。

紅、綠、藍(lán)三個顏色通道每種色各分為256階亮度,在0時“燈”最弱——是關(guān)掉的,而在255時“燈”最亮。當(dāng)三色灰度數(shù)值相同時,產(chǎn)生不同灰度值的灰色調(diào),即三色灰度都為0時,是最暗的黑色調(diào);三色灰度都為255時,是最亮的白色調(diào)。

對一種顏色進(jìn)行編碼的方法統(tǒng)稱為顏色空間或色域。

用最簡單的話說,世界上任何一種顏色的“顏色空間”都可定義成一個固定的數(shù)字或變量。RGB(紅、綠、藍(lán))只是眾多顏色空間的一種。采用這種編碼方法,每種顏色都可用三個變量來表示-紅色綠色以及藍(lán)色的強(qiáng)度。記錄及顯示彩色圖像時,RGB是最常見的一種方案。

所以每一個圖像都可以由RGB組成,那么一個像素點(diǎn)的RGB該如何表示呢?音頻里面的每一個采樣(sample)均使用16個比特來表示,那么像素里面的子像素又該如何表示呢?常用的表示方式有以下幾種。

  • 浮點(diǎn)表示:取值范圍為0.0~1.0,比如,在OpenGL ES中對每一個子像素點(diǎn)的表示使用的就是這種表達(dá)方式。

  • 整數(shù)表示:取值范圍為0~255或者00~FF,8個比特表示一個子像素,32個比特表示一個像素

Android平臺上RGB_565的表示方法為16比特模式表示一個像素,R用5個比特來表示,G用6個比特來表示,B用5個比特來表示。

對于一幅圖像,一般使用整數(shù)表示方法來進(jìn)行描述,比如計算一張1280×720的RGBA_8888圖像的大小,可采用如下方式:

1280 * 720 * 4 = 3.516MB

這也是位圖(bitmap)在內(nèi)存中所占用的大小,所以每一張圖像的裸數(shù)據(jù)都是很大的。對于圖像的裸數(shù)據(jù)來講,直接在網(wǎng)絡(luò)上進(jìn)行傳輸也是不太可能的,所以就有了圖像的壓縮格式。

安卓圖像引擎解碼的規(guī)則,在JNI中解析出來的是ABGR順序,獲取RGB數(shù)據(jù)的時候要注意。

Android中的顏色值通常遵循RGB/ARGB標(biāo)準(zhǔn),使用時通常以“ # ”字符開頭的8位16進(jìn)制表示。前綴0x表示十六進(jìn)制(基數(shù)為16),其中ARGB 依次代表透明度(Alpha)、紅色(Red)、綠色(Green)、藍(lán)色(Blue),取值范圍為0 ~ 255(即16進(jìn)制的0x00 ~ 0xff)。
A0x000xff表示從透明到不透明,RGB0x000xff表示顏色從淺到深。當(dāng)RGB全取最小值(0或0x000000)時顏色為黑色,全取最大值(255或0xffffff)時顏色為白色。

  • 紅色:(255,0,0)或0x00FF0000
  • 綠色:(0,255,0)或0x0000FF00
  • 藍(lán)色:(255,255,255)或0x00FFFFFF

大小端字節(jié)序

這是在Android中使用RGB數(shù)據(jù)的時候面臨的問題。

以內(nèi)存中0x0A0B0C0D(0x前綴代表十六進(jìn)制)的存放方式為例,分別有以下幾種方式:

小端序

  • (little-endian)又稱小尾序

數(shù)據(jù)以8bit為單位:

地址增長方向
... 0x0D 0x0C 0x0B 0x0A ...

最低位字節(jié)是0x0D 存儲在最低的內(nèi)存地址處。后面字節(jié)依次存在后面的地址處。

大端序

  • (big-endian)又稱大尾序。

數(shù)據(jù)以8bit為單位:

地址增長方向
... 0x0A 0x0B 0x0C 0x0D ...

最高位字節(jié)是0x0A`存儲在最低的內(nèi)存地址處。下一個字節(jié)0x0B存在后面的地址處。正類似于十六進(jìn)制字節(jié)從左到右的閱讀順序。

混合序

  • (middle-endian)具有更復(fù)雜的順序。

PDP-11為例,0x0A0B0C0D被存儲為:

32bit在PDP-11的存儲方式

地址增長方向
... 0x0B 0x0A 0x0D 0x0C ...

可以看作高16bit和低16bit以大端序存儲,但16bit內(nèi)部以小端存儲。

Bitmap像素排列

Android中Java/Kotlin默認(rèn)使用大端字節(jié)序,所見即所得,NDK 中C/C++默認(rèn)使用小端字節(jié)序。

這個很容易驗證:

import java.nio.ByteOrder
......
// 調(diào)用
ByteOrder.nativeOrder()
....
// 得到
LITTLE_ENDIAN

我們在Android平臺下創(chuàng)建Bitmap時:

Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)

Bitmap.config.ARGB_8888的注釋中就指明了:

int color = (A & 0xff) << 24 | (B & 0xff) << 16 | (G & 0xff) << 8 | (R & 0xff);

這里的字節(jié)順應(yīng)該為ABGR.

但是我們在Android中讀取bitmap中的像素值有兩種方式并不是按照這個順序取值的, 這是為什么?

getPixel() 取值順序

方法:

public void getPixels(@ColorInt int[] pixels, int offset, int stride,
                          int x, int y, int width, int height) {
  .......
nativeGetPixels(mNativePtr, pixels, offset, stride,
                        x, y, width, height);
}

最終調(diào)用native的方法nativeGetPixels,我們先不管Native是如何處理的。

這里將Bitmap中的像素數(shù)據(jù)將copy到pixels數(shù)組中,pixels數(shù)組是按照ColorSpace.Named#SRGB規(guī)則排列的。

即每一個pixel都是按ARGB四個分量8位排列壓縮而成的一個int值。

像素組裝:

int color = (A & 0xff) << 24 | (R & 0xff) << 16 | (G & 0xff) << 8 | (B & 0xff);

獲取單個像素值:

 int A = (color >> 24) & 0xff; // or color >>> 24
 int R = (color >> 16) & 0xff;
 int G = (color >>  8) & 0xff;
 int B = (color      ) & 0xff;

copyPixelsToBuffer() 取值順序

看下具體方法:

/**
     * <p>Copy the pixels from the buffer, beginning at the current position,
     * overwriting the bitmap's pixels. The data in the buffer is not changed
     * in any way (unlike setPixels(), which converts from unpremultipled 32bit
     * to whatever the bitmap's native format is. The pixels in the source
     * buffer are assumed to be in the bitmap's color space.</p>
     * <p>After this method returns, the current position of the buffer is
     * updated: the position is incremented by the number of elements read from
     * the buffer. If you need to read the bitmap from the buffer again you must
     * first rewind the buffer.</p>
     * @throws IllegalStateException if the bitmap's config is {@link Config#HARDWARE}
     */
public void copyPixelsFromBuffer(Buffer src) {
    .....
    
    nativeCopyPixelsFromBuffer(mNativePtr, src);
     
    .....
}

這里說The data in the buffer is not changed。

也就是說native層的操作將bitmap的排列就變成了RGBA ,buffer沒有改變順序

我們簡單驗證下:

 val tempBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
val canvas = Canvas(tempBitmap)
val paint = Paint()
paint.style = Paint.Style.FILL
paint.color = Color.rgb(0x11, 0x22, 0x33)
canvas.drawRect(0f, 0f, tempBitmap.width.toFloat(), tempBitmap.height.toFloat(), paint)

val byteSize = tempBitmap.allocationByteCount
val byteBuffer: ByteBuffer = ByteBuffer.allocateDirect(byteSize)
tempBitmap.copyPixelsToBuffer(byteBuffer)
byteBuffer.rewind()
val out = ByteArray(4)
byteBuffer[out, 0, out.size]
val pixel = tempBitmap.getPixel(0,0)
val a = Color.alpha(pixel)
val r = Color.red(pixel)
val g = Color.green(pixel)
val b = Color.blue(pixel)
Log.d("pixel = ", "${pixel}")
Log.d("pixel = ", "a= ${a},r= ${r},g=${g}, b=$")
Log.d("pixel 16 = ", "a= ${a.toString(16)},r= ${r.toString(16)},g=${g.toString(16)}, b=${b.toString(16)}")
for(element in out){
    Log.d("out = ", element.toString(16))
}

查看打印的的值

pixel =:        { -15654349 } 

pixel =:    { a= 255,r= 17,g=34, b=51 }
// ARGB
pixel 16=:  { a= ff,r= 11,g=22, b=33 }
// RGBA
out   =         { 11, 22 ,33 , -1 }

-1 取絕對值二進(jìn)制反碼+1后的16進(jìn)制即為FF。

JNI取值順序

之前說 Bitmap.config.ARGB_8888對應(yīng)的Bitmap字節(jié)序為ABRG.

那么JNI中ANDROID_BITMAP_FORMAT_RGBA_8888也是如此。

簡單驗證下:

同樣以上面的一個像素0X112233為例:

這里注意下我們使用paint.color = Color.rgb(0x11, 0x22, 0x33)alpha的值是默認(rèn)的。

0xff000000 | (red << 16) | (green << 8) | blue;

kotlin:

external fun handleBitmapForSinglePixel(bitmap: Bitmap)

定義宏,按照ABGR的順序取值:

#define MAKE_ABGR(a, b, g, r) (((a&0xff)<<24) | ((b & 0xff) << 16) | ((g & 0xff) << 8 ) | (r & 0xff))

#define BGR_8888_A(p) ((p & (0xff<<24))   >> 24 )
#define BGR_8888_B(p) ((p & (0xff << 16)) >> 16 )
#define BGR_8888_G(p) ((p & (0xff << 8))  >> 8 )
#define BGR_8888_R(p) (p & (0xff) )

對應(yīng)JNI方法:

extern "C"
JNIEXPORT void JNICALL
Java_tt_reducto_ndksample_BitmapOps_handleBitmapForSinglePixel(JNIEnv *env, jobject thiz,
                                                               jobject bitmap) {
    AndroidBitmapInfo bitmapInfo;
//    memset(&bitmapInfo , 0 , sizeof(bitmapInfo));
    int ret = AndroidBitmap_getInfo(env, bitmap, &bitmapInfo);
    if (ANDROID_BITMAP_RESULT_SUCCESS != ret) {
        LOGE("AndroidBitmap_getInfo() bitmap failed ! error=%d", ret)
    }
    // 獲得 Bitmap 的像素緩存指針:遍歷從 Bitmap 內(nèi)存 addrPtr 中讀取 BGRA 數(shù)據(jù)
    void *addrPtr;
    ret = AndroidBitmap_lockPixels(env, bitmap, &addrPtr);
    if (ANDROID_BITMAP_RESULT_SUCCESS != ret) {
        LOGE("AndroidBitmap_lockPixels() bitmap failed ! error=%d", ret)
    }

    // 執(zhí)行圖片操作的邏輯
    // 獲取寬高
    uint32_t mWidth = bitmapInfo.width;
    uint32_t mHeight = bitmapInfo.height;
    // 獲取原生數(shù)據(jù)
    auto pixelArr = ((uint32_t *) addrPtr);

    LOGE("bitmap width = %d", mWidth)
    LOGE("bitmap height = %d", mHeight)
    LOGE("bitmap format = %d", bitmapInfo.format)
    int a,r, g, b;
    for (int x = 0; x < mWidth; ++x) {

        for (int y = 0; y < mHeight; ++y) {
            LOGE("handleBitmapForSinglePixel %d", pixelArr[0])
            void *pixel = nullptr;
            // 移動像素指針
            pixel = pixelArr + y * mWidth + x;
            //按照ABGR存儲序列取值  獲取指針對應(yīng)的值
            uint32_t v = *((uint32_t *) pixel);
            // 
            a = RGB8888_A(v);
            r = RGB8888_R(v);
            g = RGB8888_G(v);
            b = RGB8888_B(v);
            //
            LOGD("bitmapInfo a %d", a)
            LOGD("bitmapInfo r %d", r)
            LOGD("bitmapInfo g %d", g)
            LOGD("bitmapInfo b %d", b)

        }
    }
    // 釋放緩存指針
    AndroidBitmap_unlockPixels(env, bitmap);
}

查看打印值:

2020-08-19 16:58:55.374 9562-9562/tt.reducto.ndksample E/TTNative: handleBitmapForSinglePixel -13426159
2020-08-19 16:58:55.374 9562-9562/tt.reducto.ndksample D/TTNative: bitmapInfo a 255
2020-08-19 16:58:55.374 9562-9562/tt.reducto.ndksample D/TTNative: bitmapInfo r 17
2020-08-19 16:58:55.374 9562-9562/tt.reducto.ndksample D/TTNative: bitmapInfo g 34
2020-08-19 16:58:55.374 9562-9562/tt.reducto.ndksample D/TTNative: bitmapInfo b 51

-13426159轉(zhuǎn)成二進(jìn)制:

1100 1100 1101 1101 1110 1111   
----------------------------- 取反
0011 0011 0010 0010 0001 0000
----------------------------- +1 
0011 0011 0010 0010 0001 0001  
        b                   g                   r

Skia處理

Android中bitmap的處理經(jīng)過:

Java層函數(shù)——Native層函數(shù)——Skia庫函數(shù)——對應(yīng)第三方庫函數(shù)(libjpeg)

所有Bitmap.createBitmap()對應(yīng)的native操作在..android/graphics/Bitmap.cpp中:

static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors,
                              jint offset, jint stride, jint width, jint height,
                              jint configHandle, jboolean isMutable,
                              jlong colorSpacePtr) {
    // 轉(zhuǎn)換色域
    SkColorType colorType = GraphicsJNI::legacyBitmapConfigToColorType(configHandle);
    if (NULL != jColors) {
        size_t n = env->GetArrayLength(jColors);
        if (n < SkAbs32(stride) * (size_t)height) {
            doThrowAIOOBE(env);
            return NULL;
        }
    }
    // ARGB_4444 is a deprecated format, convert automatically to 8888
    if (colorType == kARGB_4444_SkColorType) {
        // 將ARGB_4444強(qiáng)轉(zhuǎn)成kN32_SkColorType
        colorType = kN32_SkColorType;
    }
    sk_sp<SkColorSpace> colorSpace;
    if (colorType == kAlpha_8_SkColorType) {
        colorSpace = nullptr;
    } else {
        colorSpace = GraphicsJNI::getNativeColorSpace(colorSpacePtr);
    }
    // 
    SkBitmap bitmap;
    bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType,
                colorSpace));
    // 8.0以后bitmap的創(chuàng)建內(nèi)存分配都是在native上
    sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(&bitmap);
    if (!nativeBitmap) {
        ALOGE("OOM allocating Bitmap with dimensions %i x %i", width, height);
        doThrowOOME(env);
        return NULL;
    }
    // 填充色值
    if (jColors != NULL) {
        GraphicsJNI::SetPixels(env, jColors, offset, stride, 0, 0, width, height, &bitmap);
    }
    return createBitmap(env, nativeBitmap.release(), getPremulBitmapCreateFlags(isMutable));
}

這里第一步就是將Bitmap.Config.ARGB_8888轉(zhuǎn)成skia域的顏色類型:

 SkColorType colorType = GraphicsJNI::legacyBitmapConfigToColorType(configHandle);

看下GraphicsJNI.h中對應(yīng)的方法定義:

/*
 *  LegacyBitmapConfig is the old enum in Skia that matched the enum int values
 *  in Bitmap.Config. Skia no longer supports this config, but has replaced it
 *  with SkColorType. These routines convert between the two.
 */
static SkColorType legacyBitmapConfigToColorType(jint legacyConfig);

再去看下GraphicsJNI.cpp中看下實現(xiàn):

SkColorType GraphicsJNI::legacyBitmapConfigToColorType(jint legacyConfig) {
    const uint8_t gConfig2ColorType[] = {
        kUnknown_SkColorType,
        kAlpha_8_SkColorType,
        kUnknown_SkColorType, // Previously kIndex_8_SkColorType,
        kRGB_565_SkColorType,
        kARGB_4444_SkColorType,
        kN32_SkColorType,
        kRGBA_F16_SkColorType,
        kN32_SkColorType
    };
    if (legacyConfig < 0 || legacyConfig > kLastEnum_LegacyBitmapConfig) {
        legacyConfig = kNo_LegacyBitmapConfig;
    }
    return static_cast<SkColorType>(gConfig2ColorType[legacyConfig]);
}

因為我們在java層傳入的Bitmap.Config.ARGB_8888值為ARGB_8888(5)

與之對應(yīng)的就是kN32_SkColorType;

接下來我們在SkImageInfo.h中看下SkColorType:

/** \enum SkImageInfo::SkColorType
    Describes how pixel bits encode color. A pixel may be an alpha mask, a
    grayscale, RGB, or ARGB.
    kN32_SkColorType selects the native 32-bit ARGB format. On little endian
    processors, pixels containing 8-bit ARGB components pack into 32-bit
    kBGRA_8888_SkColorType. On big endian processors, pixels pack into 32-bit
    kRGBA_8888_SkColorType.
*/
enum SkColorType {
    kUnknown_SkColorType,      //!< uninitialized
    kAlpha_8_SkColorType,      //!< pixel with alpha in 8-bit byte
    kRGB_565_SkColorType,      //!< pixel with 5 bits red, 6 bits green, 5 bits blue, in 16-bit word
    kARGB_4444_SkColorType,    //!< pixel with 4 bits for alpha, red, green, blue; in 16-bit word
    kRGBA_8888_SkColorType,    //!< pixel with 8 bits for red, green, blue, alpha; in 32-bit word
    kRGB_888x_SkColorType,     //!< pixel with 8 bits each for red, green, blue; in 32-bit word
    kBGRA_8888_SkColorType,    //!< pixel with 8 bits for blue, green, red, alpha; in 32-bit word
    kRGBA_1010102_SkColorType, //!< 10 bits for red, green, blue; 2 bits for alpha; in 32-bit word
    kRGB_101010x_SkColorType,  //!< pixel with 10 bits each for red, green, blue; in 32-bit word
    kGray_8_SkColorType,       //!< pixel with grayscale level in 8-bit byte
    kRGBA_F16Norm_SkColorType, //!< pixel with half floats in [0,1] for red, green, blue, alpha; in 64-bit word
    kRGBA_F16_SkColorType,     //!< pixel with half floats for red, green, blue, alpha; in 64-bit word
    kRGBA_F32_SkColorType,     //!< pixel using C float for red, green, blue, alpha; in 128-bit word
    kLastEnum_SkColorType     = kRGBA_F32_SkColorType,//!< last valid value
#if SK_PMCOLOR_BYTE_ORDER(B,G,R,A)
    kN32_SkColorType          = kBGRA_8888_SkColorType,//!< native ARGB 32-bit encoding
#elif SK_PMCOLOR_BYTE_ORDER(R,G,B,A)
    kN32_SkColorType          = kRGBA_8888_SkColorType,//!< native ARGB 32-bit encoding
#else
    #error "SK_*32_SHIFT values must correspond to BGRA or RGBA byte order"
#endif
};

接著看下面

kN32_SkColorType根據(jù)字節(jié)序決定kN32_SkColorTypef的值,用到的宏SK_PMCOLOR_BYTE_ORDERSkPostConfig.h中定義:


/**
 * SK_PMCOLOR_BYTE_ORDER can be used to query the byte order of SkPMColor at compile time. The
 * relationship between the byte order and shift values depends on machine endianness. If the shift
 * order is R=0, G=8, B=16, A=24 then ((char*)&pmcolor)[0] will produce the R channel on a little
 * endian machine and the A channel on a big endian machine. Thus, given those shifts values,
 * SK_PMCOLOR_BYTE_ORDER(R,G,B,A) will be true on a little endian machine and
 * SK_PMCOLOR_BYTE_ORDER(A,B,G,R) will be true on a big endian machine.
 */
#ifdef SK_CPU_BENDIAN
#  define SK_PMCOLOR_BYTE_ORDER(C0, C1, C2, C3)     \
        (SK_ ## C3 ## 32_SHIFT == 0  &&             \
         SK_ ## C2 ## 32_SHIFT == 8  &&             \
         SK_ ## C1 ## 32_SHIFT == 16 &&             \
         SK_ ## C0 ## 32_SHIFT == 24)
#else
#  define SK_PMCOLOR_BYTE_ORDER(C0, C1, C2, C3)     \
        (SK_ ## C0 ## 32_SHIFT == 0  &&             \
         SK_ ## C1 ## 32_SHIFT == 8  &&             \
         SK_ ## C2 ## 32_SHIFT == 16 &&             \
         SK_ ## C3 ## 32_SHIFT == 24)
#endif

所以小端字節(jié)序?qū)?yīng)就是:

#  define SK_PMCOLOR_BYTE_ORDER(C0, C1, C2, C3)     \
        (SK_ ## C0 ## 32_SHIFT == 0  &&             \
         SK_ ## C1 ## 32_SHIFT == 8  &&             \
         SK_ ## C2 ## 32_SHIFT == 16 &&             \
         SK_ ## C3 ## 32_SHIFT == 24)

這里用到了SK_A32_SHIFT、SK_R32_SHIFT、SK_G32_SHIFT、SK_B32_SHIFT這幾個宏:

/**
 *  We check to see if the SHIFT value has already been defined.
 *  if not, we define it ourself to some default values. We default to OpenGL
 *  order (in memory: r,g,b,a)
 */
#ifndef SK_A32_SHIFT
#  ifdef SK_CPU_BENDIAN
#    define SK_R32_SHIFT    24
#    define SK_G32_SHIFT    16
#    define SK_B32_SHIFT    8
#    define SK_A32_SHIFT    0
#  else
#    define SK_R32_SHIFT    0
#    define SK_G32_SHIFT    8
#    define SK_B32_SHIFT    16
#    define SK_A32_SHIFT    24
#  endif
#endif

所以小端字節(jié)序處理:

#    define SK_R32_SHIFT    0
#    define SK_G32_SHIFT    8
#    define SK_B32_SHIFT    16
#    define SK_A32_SHIFT    24

回到SkColorType中:

#if SK_PMCOLOR_BYTE_ORDER(B,G,R,A)
    kN32_SkColorType = kBGRA_8888_SkColorType,
#elif SK_PMCOLOR_BYTE_ORDER(R,G,B,A)
    kN32_SkColorType = kRGBA_8888_SkColorType,
    
// SK_PMCOLOR_BYTE_ORDER(R,G,B,A) 展開后如下
SK_R32_SHIFT == 0 && SK_G32_SHIFT == 8 && SK_B32_SHIFT == 16 && SK_A32_SHIFT == 24
// 表達(dá)式返回
true 

綜上:

這意味著Bitmap.Config.ARGB_8888 會被轉(zhuǎn)成Skia域中的顏色類型 kRGBA_8888_SkColorType并以此格式內(nèi)部存儲。在將RGBA寫入到小端字節(jié)序的內(nèi)存中,就變成了ABGR.

ABGR也是我們在JNI中獲取bitmap像素值得順序。

接著往下看:

typedef uint32_t    SkPMColor
sk_sp<Bitmap> Bitmap::allocateHeapBitmap(size_t size, const SkImageInfo& info, size_t rowBytes) {
    void* addr = calloc(size, 1);
    if (!addr) {
        return nullptr;
    }
    return sk_sp<Bitmap>(new Bitmap(addr, size, info, rowBytes));
}

bool GraphicsJNI::SetPixels(JNIEnv* env, jintArray srcColors, int srcOffset, int srcStride,
        int x, int y, int width, int height, const SkBitmap& dstBitmap) {
    SkAutoLockPixels alp(dstBitmap);
    void* dst = dstBitmap.getPixels();
    FromColorProc proc = ChooseFromColorProc(dstBitmap);
    if (NULL == dst || NULL == proc) {
        return false;
    }
    const jint* array = env->GetIntArrayElements(srcColors, NULL);
    const SkColor* src = (const SkColor*)array + srcOffset;
    // reset to to actual choice from caller
    dst = dstBitmap.getAddr(x, y);
    // now copy/convert each scanline
    for (int y = 0; y < height; y++) {
        proc(dst, src, width, x, y);
        src += srcStride;
        dst = (char*)dst + dstBitmap.rowBytes();
    }
    dstBitmap.notifyPixelsChanged();
    env->ReleaseIntArrayElements(srcColors, const_cast<jint*>(array),
                                 JNI_ABORT);
    return true;
}

ChooseFromColorProc:

// can return NULL
static FromColorProc ChooseFromColorProc(const SkBitmap& bitmap) {
    switch (bitmap.colorType()) {
        case kN32_SkColorType:
            return bitmap.alphaType() == kPremul_SkAlphaType ? FromColor_D32 : FromColor_D32_Raw;
        case kARGB_4444_SkColorType:
            return bitmap.alphaType() == kPremul_SkAlphaType ? FromColor_D4444 :
                    FromColor_D4444_Raw;
        case kRGB_565_SkColorType:
            return FromColor_D565;
        default:
            break;
    }
    return NULL;
}

cpp代碼

#include <android/bitmap.h>
#include <android/graphics/Bitmap.h>
int AndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap,
                          AndroidBitmapInfo* info) {
    if (NULL == env || NULL == jbitmap) {
        return ANDROID_BITMAP_RESULT_BAD_PARAMETER;
    }
    if (info) {
        android::bitmap::imageInfo(env, jbitmap, info);
    }
    return ANDROID_BITMAP_RESULT_SUCCESS;
}
int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr) {
    if (NULL == env || NULL == jbitmap) {
        return ANDROID_BITMAP_RESULT_BAD_PARAMETER;
    }
    void* addr = android::bitmap::lockPixels(env, jbitmap);
    if (!addr) {
        return ANDROID_BITMAP_RESULT_JNI_EXCEPTION;
    }
    if (addrPtr) {
        *addrPtr = addr;
    }
    return ANDROID_BITMAP_RESULT_SUCCESS;
}
int AndroidBitmap_unlockPixels(JNIEnv* env, jobject jbitmap) {
    if (NULL == env || NULL == jbitmap) {
        return ANDROID_BITMAP_RESULT_BAD_PARAMETER;
    }
    bool unlocked = android::bitmap::unlockPixels(env, jbitmap);
    if (!unlocked) {
        return ANDROID_BITMAP_RESULT_JNI_EXCEPTION;
    }
    return ANDROID_BITMAP_RESULT_SUCCESS;
}

JNI操作Bitmap

準(zhǔn)備

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

target_link_libraries(
        #自己的需要生成的動態(tài)庫
        TTNative
        # 操作bitmap
        jnigraphics
        # 鏈接log 庫
        ${log-lib})

然后 導(dǎo)入頭文件:

#include <android/bitmap.h>

創(chuàng)建Bitmap

JNI創(chuàng)建bitmap只能調(diào)用Java或者kotlin的方法。

第一種,直接在Bitmap中

jclass bitmapCls;
jmethodID createBitmapFunction;
jmethodID getBitmapFunction;

// 創(chuàng)建bitmap public static Bitmap createBitmap (int width,int height,  Bitmap.Config config)

jobject createBitmap(JNIEnv *env, uint32_t width, uint32_t height) {
    bitmapCls = env->FindClass("android/graphics/Bitmap");
    createBitmapFunction = env->GetStaticMethodID(bitmapCls,
                                                            "createBitmap",
                                                            "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
    // 聲明 格式
    jstring configName = env->NewStringUTF("ARGB_8888");
    jclass bitmapConfigClass = env->FindClass("android/graphics/Bitmap$Config");
    getBitmapFunction = env->GetStaticMethodID(
            bitmapConfigClass, "valueOf",
            "(Ljava/lang/String;)Landroid/graphics/Bitmap$Config;");

    jobject bitmapConfig = env->CallStaticObjectMethod(bitmapConfigClass,
                                                       getBitmapFunction, configName);

    jobject newBitmap = env->CallStaticObjectMethod(bitmapCls, createBitmapFunction,
                                                    width, height, bitmapConfig);
    return newBitmap;
}

第二種:

檢索 Bitmap 對象信息

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

/**
 * Given a java bitmap object, fill out the {@link AndroidBitmapInfo} struct for it.
 * If the call fails, the info parameter will be ignored.
 */
int AndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap,
                          AndroidBitmapInfo* info);

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

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

/** Bitmap info, see AndroidBitmap_getInfo(). */
typedef struct {
    /** The bitmap width in pixels. */
    uint32_t    width;
    /** The bitmap height in pixels. */
    uint32_t    height;
    /** The number of byte per row. */
    uint32_t    stride;
    /** The bitmap pixel format. See {@link AndroidBitmapFormat} */
    int32_t     format;
    /** Bitfield containing information about the bitmap.
     *
     * <p>Two bits are used to encode alpha. Use {@link ANDROID_BITMAP_FLAGS_ALPHA_MASK}
     * and {@link ANDROID_BITMAP_FLAGS_ALPHA_SHIFT} to retrieve them.</p>
     *
     * <p>One bit is used to encode whether the Bitmap uses the HARDWARE Config. Use
     * {@link ANDROID_BITMAP_FLAGS_IS_HARDWARE} to know.</p>
     *
     * <p>These flags were introduced in API level 30.</p>
     */
    uint32_t    flags;
} AndroidBitmapInfo;

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

圖像的格式有如下支持:

/** Bitmap pixel format. */
enum AndroidBitmapFormat {
    /** No format. */
    ANDROID_BITMAP_FORMAT_NONE      = 0,
    /** Red: 8 bits, Green: 8 bits, Blue: 8 bits, Alpha: 8 bits. **/
    ANDROID_BITMAP_FORMAT_RGBA_8888 = 1,
    /** Red: 5 bits, Green: 6 bits, Blue: 5 bits. **/
    ANDROID_BITMAP_FORMAT_RGB_565   = 4,
    /** Deprecated in API level 13. Because of the poor quality of this configuration, it is advised to use ARGB_8888 instead. **/
    ANDROID_BITMAP_FORMAT_RGBA_4444 = 7,
    /** Alpha: 8 bits. */
    ANDROID_BITMAP_FORMAT_A_8       = 8,
    /** Each component is stored as a half float. **/
    ANDROID_BITMAP_FORMAT_RGBA_F16  = 9,
};

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

/** AndroidBitmap functions result code. */
enum {
    /** Operation was successful. */
    ANDROID_BITMAP_RESULT_SUCCESS           = 0,
    /** Bad parameter. */
    ANDROID_BITMAP_RESULT_BAD_PARAMETER     = -1,
    /** JNI exception occured. */
    ANDROID_BITMAP_RESULT_JNI_EXCEPTION     = -2,
    /** Allocation failed. */
    ANDROID_BITMAP_RESULT_ALLOCATION_FAILED = -3,
};

操作原生像素緩存

訪問

在頭文件中AndroidBitmap_lockPixels 函數(shù)對圖片進(jìn)行解碼并獲取解碼后像素保存在內(nèi)存中的地址指針addrPtr,鎖定了像素緩存以確保像素的內(nèi)存不會被移動。

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

/**
 * Given a java bitmap object, attempt to lock the pixel address.
 * Locking will ensure that the memory for the pixels will not move
 * until the unlockPixels call, and ensure that, if the pixels had been
 * previously purged, they will have been restored.
 *
 * If this call succeeds, it must be balanced by a call to
 * AndroidBitmap_unlockPixels, after which time the address of the pixels should
 * no longer be used.
 *
 * If this succeeds, *addrPtr will be set to the pixel address. If the call
 * fails, addrPtr will be ignored.
 */
int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr);

前兩個參數(shù)同上,第三個參數(shù)是指向像素緩存地址的二維指針。

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

函數(shù)執(zhí)行成功的話返回 0 ,否則返回一個負(fù)數(shù),錯誤碼列表同上。

釋放

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

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

/**
 * Call this to balance a successful call to AndroidBitmap_lockPixels.
 */
int AndroidBitmap_unlockPixels(JNIEnv* env, jobject jbitmap);

如果執(zhí)行成功返回 0,否則返回 1。

旋轉(zhuǎn)、鏡像

我們不管在kotlin還是在jni中定義 Bitmap 圖像時,都需要定義寬和高,這就相對于是一個二維的

圖像是二維數(shù)據(jù),但數(shù)據(jù)在內(nèi)存中只能一維存儲

二維轉(zhuǎn)一維有不同的對應(yīng)方式,比較常見的只有兩種方式:

按像素“行排列”從上往下或者從下往上;

Bitmap 在Android中的的像素是按照行進(jìn)行排列的,而且行的排列是從左往右,列的排列是從上往下。

起始點(diǎn)就和屏幕坐標(biāo)原點(diǎn)一樣,位于左上角。

舉個例子:

如果我們得到的原始bitmap像素信息展開為二位數(shù)組是這個樣子:

[
  [ 1, 2, 3]
  [ 4, 5, 6]
  [ 7, 8, 9]
]

那像素數(shù)據(jù)存儲即為:

123 456 789

我們要將 Bitmap 進(jìn)行旋轉(zhuǎn)可以創(chuàng)建一個新的 Bitmap 對象,然后將像素值填充到新的 Bitmap 對象中

根據(jù)上述的像素排列規(guī)則,如果我們需要順時針旋轉(zhuǎn)90度 的話,我們需要讓像素存儲的循序為:

[
  [ 7, 4, 1]
  [ 8, 5, 2]
  [ 9, 6, 3]
]

// 儲存順序
741 852 963

萬物基于矩陣。

但是我們這里只需要按照需要操作的順序去矩陣中取值再寫入就可以了。

通過 AndroidBitmap_lockPixels 方法,*addrPtr 指針就指向了 Bitmap 的像素地址,它的長度就是 Bitmap 的寬和高的乘積。

uint32_t mWidth = bitmapInfo.width;
uint32_t mHeight = bitmapInfo.height;
// 獲取原生數(shù)據(jù)
auto pixelArr =((uint32_t *) addrPtr);
// 創(chuàng)建一個新的數(shù)組指針填充像素值
auto *newBitmapPixels = new uint32_t[mWidth * mHeight];
LOGE("bitmap width = %d", (uint32_t)mWidth)
LOGE("bitmap height = %d", mHeight)
LOGE("bitmap format = %d", bitmapInfo.format)

我們這里處理RGBA_8888格式,A、R、G、B分量各占8位,8位是1個字節(jié),一個像素占4字節(jié)能存儲32位ARGB值

二進(jìn)制:2^32=16777216 (真彩色)

// 指針偏移
int tmp = 0;
// 按照順時針90度旋轉(zhuǎn)順序掃描
for (int x =0 ; x < mWidth; x++) {
        for (int y = mHeight-1; y >=0 ; --y) {
            // 從原左下角開始
            uint32_t pixel = pixelArr[mWidth * y+x];
            // 寫入
            newBitmapPixels[tmp++] =pixel;
        }
}

首先從原矩陣左下角開始依y軸從下向上掃描,再從左向右掃描x軸。以此類推

如果是旋轉(zhuǎn)90度注意需要在創(chuàng)建bitmap時候?qū)捀咝枰獡Q一下?

 jobject newBitmap = createBitmap(env, mHeight, mWidth);

完整代碼:

extern "C"
JNIEXPORT jobject JNICALL
Java_tt_reducto_ndksample_jni_BitmapOps_rotateBitmap(JNIEnv *env, jobject thiz, jobject bitmap,
                                                     jint ops) {
    if (bitmap == nullptr) {
        LOGD("rotateBitmap - the  bitmap is null ")
        return nullptr;
    }

    // 檢索獲取bitmap信息
    AndroidBitmapInfo bitmapInfo;
    int ret = AndroidBitmap_getInfo(env, bitmap, &bitmapInfo);
    if (ANDROID_BITMAP_RESULT_SUCCESS != ret) {
        LOGD("AndroidBitmap_getInfo() bitmap failed ! error=%d", ret)
        return nullptr;
    }
    // 獲得 Bitmap 的像素緩存指針:遍歷從 Bitmap 內(nèi)存 addrPtr 中讀取像素數(shù)據(jù)
    void *addrPtr;
    ret = AndroidBitmap_lockPixels(env, bitmap, &addrPtr);
    if (ANDROID_BITMAP_RESULT_SUCCESS != ret) {
        LOGD("AndroidBitmap_lockPixels() bitmap failed ! error=%d", ret)
        return nullptr;
    }

    // 執(zhí)行圖片操作的邏輯
    // 獲取寬高
    int mWidth = bitmapInfo.width;
    int mHeight = bitmapInfo.height;
    // 獲取原生數(shù)據(jù)
    auto pixelArr = ((uint32_t *) addrPtr);
    // 矩陣 創(chuàng)建一個新的數(shù)組指針填充像素值
    auto *newBitmapPixels = new uint32_t[mWidth * mHeight];
    LOGD("bitmap width = %d", mWidth)
    LOGD("bitmap height = %d", mHeight)
    LOGD("bitmap format = %d", bitmapInfo.format)
    int temp = 0;
    switch (ops) {
        case 0:
            // 遍歷矩陣,按照順時針90度順序掃描
            for (int x = 0; x < mWidth; x++) {
                for (int y = mHeight - 1; y >= 0; --y) {
                    newBitmapPixels[temp++] = pixelArr[mWidth * y + x];
                }
            }

            break;
        case 1:
            // 上下翻轉(zhuǎn)
            for (int y = 0; y < mHeight; ++y) {
                for (int x = 0; x < mWidth; x++) {
                    uint32_t pixel = pixelArr[temp++];
                    newBitmapPixels[mWidth * (mHeight - 1 - y) + x] = pixel;
                }
            }
            break;
        case 2:
            // 鏡像
            for (int y = 0; y < mHeight; ++y) {
                for (int x = mWidth - 1; x >= 0; x--) {
                    uint32_t pixel = pixelArr[temp++];
                    newBitmapPixels[mWidth * y + x] = pixel;
                }
            }
            break;
        default:
            break;
    }


    // 新建bitmap 注意這里 因為翻轉(zhuǎn)90度后,矩陣即bitmap的寬高也要改變
    jobject newBitmap;
    int size = mWidth * mHeight;
    if (ops == 0) {
        newBitmap = createBitmap(env, mHeight, mWidth);
        void *resultBitmapPixels;
        //
        ret = AndroidBitmap_lockPixels(env, newBitmap, &resultBitmapPixels);
        if (ANDROID_BITMAP_RESULT_SUCCESS != ret) {
            LOGD("AndroidBitmap_lockPixels() newBitmap failed ! error=%d", ret)
            return nullptr;
        }

        // 寫入新值
        memcpy((uint32_t *) resultBitmapPixels, newBitmapPixels, sizeof(uint32_t) * size);
        // 釋放緩存指針
        AndroidBitmap_unlockPixels(env, newBitmap);
        // 釋放內(nèi)存
        delete[] newBitmapPixels;

        return newBitmap;
    } else {
        memcpy((uint32_t *) addrPtr, newBitmapPixels, sizeof(uint32_t) * size);
        delete[] newBitmapPixels;
        // 釋放緩存指針
        AndroidBitmap_unlockPixels(env, bitmap);
        return bitmap;
    }


}

灰度、浮雕

平均值法:即新的顏色值

R=G=B=(R+G+B)/3

或者加權(quán)平均值法:

 (r * 0.3 + g * 0.59 + b * 0.11)

對應(yīng)jni函數(shù):

extern "C"
JNIEXPORT void JNICALL
Java_tt_reducto_ndksample_jni_BitmapOps_addBitmapFilter(JNIEnv *env, jobject thiz, jobject bitmap,
                                                        jint ops) {
    if (bitmap == nullptr) {
        LOGD("addBitmapFilter - the  bitmap is null ")
    }

    // 檢索獲取bitmap信息
    AndroidBitmapInfo bitmapInfo;
//    memset(&bitmapInfo , 0 , sizeof(bitmapInfo));
    int ret = AndroidBitmap_getInfo(env, bitmap, &bitmapInfo);
    if (ANDROID_BITMAP_RESULT_SUCCESS != ret) {
        LOGD("AndroidBitmap_getInfo() bitmap failed ! error=%d", ret)
    }
    // 獲得 Bitmap 的像素緩存指針:遍歷從 Bitmap 內(nèi)存 addrPtr 中讀取 BGRA 數(shù)據(jù)
    void *addrPtr;
    ret = AndroidBitmap_lockPixels(env, bitmap, &addrPtr);
    if (ANDROID_BITMAP_RESULT_SUCCESS != ret) {
        LOGD("AndroidBitmap_lockPixels() bitmap failed ! error=%d", ret)
    }

    // 執(zhí)行圖片操作的邏輯
    // 獲取寬高
    uint32_t mWidth = bitmapInfo.width;
    uint32_t mHeight = bitmapInfo.height;
    // 矩陣 創(chuàng)建一個新的數(shù)組指針填充像素值
    // auto *newBitmapPixels = new uint32_t[mWidth * mHeight];
    LOGD("bitmap width = %d", mWidth)
    LOGD("bitmap height = %d", mHeight)
    LOGD("bitmap format = %d", bitmapInfo.format)

    // 獲取原生數(shù)據(jù)
    auto pixelArr = ((uint32_t *) addrPtr);

    int a, r, g, b;
    // 不操作A
    // 遍歷從 Bitmap 內(nèi)存 addrPtr 中讀取 BGRA 數(shù)據(jù), 然后向 data 內(nèi)存存儲 BGR 數(shù)據(jù)


    switch (ops) {
        // 灰度圖
        case 1: {
            for (int y = 0; y < mHeight; ++y) {
                for (int x = 0; x < mWidth; ++x) {
                    // 這里定義成void,方便后續(xù)操作
                    void *pixel = nullptr;
                    // 24位
                    if (bitmapInfo.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
                        // 移動像素指針
                        pixel = pixelArr + y * mWidth + x;
                        //按照ABGR存儲序列取值  獲取指針對應(yīng)的值
                        uint32_t v = *((uint32_t *) pixel);
                        a = BGR_8888_A(v);
                        r = BGR_8888_R(v);
                        g = BGR_8888_G(v);
                        b = BGR_8888_B(v);
                        // 平均值法
                        // int sum = (r + g + b) / 3;
                        //或者加權(quán)平均值法
                        int sum = (int) (r * 0.3 + g * 0.59 + b * 0.11);
                        *((uint32_t *) pixel) = MAKE_ABGR(a, sum, sum, sum);
                    }
                }
            }
            break;
        }
            // 浮雕圖
        case 2: {
            // 
            // 用當(dāng)前點(diǎn)的RGB值減去相鄰點(diǎn)的RGB值并加上128作為新的RGB值
            void *pixel = nullptr;
            void *pixelBefore = nullptr;
            int  r1, g1, b1;
            for (int i = 1; i < mWidth * mHeight; ++i) {
                uint32_t color, colorBefore;

                pixel = pixelArr+i;
                pixelBefore = pixelArr+i - 1;
                color = *((uint32_t *) pixel);
                colorBefore =  *((uint32_t *) pixelBefore);
                a = BGR_8888_A(color);
                r = BGR_8888_R(color);
                g = BGR_8888_G(color);
                b = BGR_8888_B(color);

                r1 = BGR_8888_R(colorBefore);
                g1 = BGR_8888_G(colorBefore);
                b1 = BGR_8888_B(colorBefore);


                r = r - r1 + 128;
                g = g - g1+ 128;
                b = b - b1 + 128;
                // 再一次灰度處理
                int sum = (int) (r * 0.3 + g * 0.59 + b * 0.11);
                *((uint32_t *)pixelBefore) = MAKE_ABGR(a, sum, sum, sum);
            }
            break;
        }

        default:
            break;
    }

    // 釋放緩存指針
    AndroidBitmap_unlockPixels(env, bitmap);
}

以上,比較簡單的R、G、B濾鏡。

效果:

image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容