Android Bitmap像素排列與JNI操做

圖像的數值表示

RGB

RGB顏色模型即紅綠藍顏色模型。由模仿生物視網膜三種視錐細胞產生,以後經過三原色疊加來進行彩色圖像顯示。經過在黑色上不斷疊加三原色來顯示不一樣的顏色。在RGB顏色空間中,分別將RGB做爲笛卡爾座標系中XYZ座標系產生。每個顏色取值範圍爲[0,256)java

RGB是從顏色發光的原理來設計定的,通俗點說它的顏色混合方式就好像有紅、綠、藍三盞燈,當它們的光相互疊合的時候,色彩相混,而亮度卻等於二者亮度之總和,越混合亮度越高,即加法混合。android

紅、綠、藍三個顏色通道每種色各分爲256階亮度,在0時「燈」最弱——是關掉的,而在255時「燈」最亮。當三色灰度數值相同時,產生不一樣灰度值的灰色調,即三色灰度都爲0時,是最暗的黑色調;三色灰度都爲255時,是最亮的白色調。git

對一種顏色進行編碼的方法統稱爲顏色空間或色域。canvas

用最簡單的話說,世界上任何一種顏色的「顏色空間」均可定義成一個固定的數字或變量。RGB(紅、綠、藍)只是衆多顏色空間的一種。採用這種編碼方法,每種顏色均可用三個變量來表示-紅色綠色以及藍色的強度。記錄及顯示彩色圖像時,RGB是最多見的一種方案。數組

因此每個圖像均可以由RGB組成,那麼一個像素點的RGB該如何表示呢?音頻裏面的每個採樣(sample)均使用16個比特來表示,那麼像素裏面的子像素又該如何表示呢?經常使用的表示方式有如下幾種。緩存

  • 浮點表示:取值範圍爲0.0~1.0,好比,在OpenGL ES中對每個子像素點的表示使用的就是這種表達方式。markdown

  • 整數表示:取值範圍爲0~255或者00~FF,8個比特表示一個子像素,32個比特表示一個像素網絡

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

對於一幅圖像,通常使用整數表示方法來進行描述,好比計算一張1280×720的RGBA_8888圖像的大小,可採用以下方式:函數

1280 * 720 * 4 = 3.516MB
複製代碼

這也是位圖(bitmap)在內存中所佔用的大小,因此每一張圖像的裸數據都是很大的。對於圖像的裸數據來說,直接在網絡上進行傳輸也是不太可能的,因此就有了圖像的壓縮格式。

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

Android中的顏色值一般遵循RGB/ARGB標準,使用時一般以**「 # 」字符開頭的8位16進製表示。前綴0x表示十六進制(基數爲16),其中ARGB** 依次表明透明度(Alpha)、紅色(Red)、綠色(Green)、藍色(Blue),取值範圍爲0 ~ 255(即16進制的0x00 ~ 0xff)。 A0x000xff表示從透明到不透明,RGB0x000xff表示顏色從淺到深。當RGB全取最小值(0或0x000000)時顏色爲黑色,全取最大值(255或0xffffff)時顏色爲白色。

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

大小端字節序

這是在Android中使用RGB數據的時候面臨的問題。

之內存中0x0A0B0C0D(0x前綴表明十六進制)的存放方式爲例,分別有如下幾種方式:

小端序

  • (little-endian)又稱小尾序

數據以8bit爲單位:

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

最低位字節是0x0D 存儲在最低的內存地址處。後面字節依次存在後面的地址處。

大端序

  • (big-endian)又稱大尾序

數據以8bit爲單位:

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

最高位字節是0x0A`存儲在最低的內存地址處。下一個字節0x0B存在後面的地址處。正相似於十六進制字節從左到右的閱讀順序。

混合序

  • (middle-endian)具備更復雜的順序。

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

32bit在PDP-11的存儲方式

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

能夠看做高16bit和低16bit以大端序存儲,但16bit內部以小端存儲。

Bitmap像素排列

Android中Java/Kotlin默認使用大端字節序,所見即所得,NDK 中C/C++默認使用小端字節序。

這個很容易驗證:

import java.nio.ByteOrder
......
// 調用
ByteOrder.nativeOrder()
....
// 獲得
LITTLE_ENDIAN
複製代碼

咱們在Android平臺下建立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);
複製代碼

這裏的字節順應該爲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);
}
複製代碼

最終調用native的方法nativeGetPixels,咱們先無論Native是如何處理的。

這裏將Bitmap中的像素數據將copy到pixels數組中,pixels數組是按照ColorSpace.Named#SRGB規則排列的。

即每個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=${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 取絕對值二進制反碼+1後的16進制即爲FF。

JNI取值順序

以前說 Bitmap.config.ARGB_8888對應的Bitmap字節序爲ABRG.

那麼JNI中ANDROID_BITMAP_FORMAT_RGBA_8888也是如此。

簡單驗證下:

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

這裏注意下咱們使用paint.color = Color.rgb(0x11, 0x22, 0x33)alpha的值是默認的。

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) )
複製代碼

對應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 內存 addrPtr 中讀取 BGRA 數據
    void *addrPtr;
    ret = AndroidBitmap_lockPixels(env, bitmap, &addrPtr);
    if (ANDROID_BITMAP_RESULT_SUCCESS != ret) {
        LOGE("AndroidBitmap_lockPixels() bitmap failed ! error=%d", ret)
    }

    // 執行圖片操做的邏輯
    // 獲取寬高
    uint32_t mWidth = bitmapInfo.width;
    uint32_t mHeight = bitmapInfo.height;
    // 獲取原生數據
    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存儲序列取值 獲取指針對應的值
            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轉成二進制:

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的處理通過:

Java層函數——Native層函數——Skia庫函數——對應第三方庫函數(libjpeg)
複製代碼

全部Bitmap.createBitmap()對應的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) {
    // 轉換色域
    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強轉成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的建立內存分配都是在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轉成skia域的顏色類型:

SkColorType colorType = GraphicsJNI::legacyBitmapConfigToColorType(configHandle);
複製代碼

看下GraphicsJNI.h中對應的方法定義:

/*
 *  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中看下實現:

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)

與之對應的就是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根據字節序決定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

複製代碼

因此小端字節序對應就是:

# 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
複製代碼

因此小端字節序處理:

# 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
// 表達式返回
true 
複製代碼

綜上:

這意味着Bitmap.Config.ARGB_8888 會被轉成Skia域中的顏色類型 kRGBA_8888_SkColorType並以此格式內部存儲。在將RGBA寫入到小端字節序的內存中,就變成了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

準備

Android 經過 JNI 調用 Bitmap,經過 CMake 去編 so 動態連接庫的時候須要添加 jnigraphics 圖像庫。

target_link_libraries(
        #本身的須要生成的動態庫
        TTNative
        # 操做bitmap
        jnigraphics
        # 連接log 庫
        ${log-lib})
複製代碼

而後 導入頭文件:

#include <android/bitmap.h>
複製代碼

建立Bitmap

JNI建立bitmap只能調用Java或者kotlin的方法。

第一種,直接在Bitmap中

jclass bitmapCls;
jmethodID createBitmapFunction;
jmethodID getBitmapFunction;

// 建立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 對象信息

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

/** * 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);
複製代碼

第一個參數就是 JNI 接口指針,第二個參數是 Bitmap 對象的引用,第三個參數是指向 AndroidBitmapInfo 結構體的指針。

AndroidBitmapInfo 結構體以下:

/** 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 就是每一行的字節數。

圖像的格式有以下支持:

/** 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 執行成功的話,會返回 0 ,不然返回一個負數,表明執行的錯誤碼列表以下:

/** 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 函數對圖片進行解碼並獲取解碼後像素保存在內存中的地址指針addrPtr,鎖定了像素緩存以確保像素的內存不會被移動。

若是 Native 層想要訪問像素數據並操做它,該方法返回了像素緩存的一個原生指針:

/** * 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);
複製代碼

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

該函數拿到全部像素的緩存地址,而後對每一個像素值進行操做,從而更改 Bitmap 信息。

函數執行成功的話返回 0 ,不然返回一個負數,錯誤碼列表同上。

釋放

調用完 AndroidBitmap_lockPixels 以後都應該對應調用一次 AndroidBitmap_unlockPixels 用來釋放原生像素緩存。

當完成對原生像素緩存的讀寫以後,就應該釋放它,一旦釋放後,Bitmap 的Java 對象就能夠在 Java 層使用了,函數簽名:

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

若是執行成功返回 0,不然返回 1。

旋轉、鏡像

咱們無論在kotlin仍是在jni中定義 Bitmap 圖像時,都須要定義寬和高,這就相對因而一個二維的

圖像是二維數據,但數據在內存中只能一維存儲

二維轉一維有不一樣的對應方式,比較常見的只有兩種方式:

按像素「行排列」從上往下或者從下往上;

Bitmap 在Android中的的像素是按照行進行排列的,並且行的排列是從左往右,列的排列是從上往下。

起始點就和屏幕座標原點同樣,位於左上角。

舉個例子:

若是咱們獲得的原始bitmap像素信息展開爲二位數組是這個樣子:

[
  [ 1, 2, 3]
  [ 4, 5, 6]
  [ 7, 8, 9]
]
複製代碼

那像素數據存儲即爲:

123 456 789
複製代碼

咱們要將 Bitmap 進行旋轉能夠建立一個新的 Bitmap 對象,而後將像素值填充到新的 Bitmap 對象中

根據上述的像素排列規則,若是咱們須要順時針旋轉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;
// 獲取原生數據
auto pixelArr =((uint32_t *) addrPtr);
// 建立一個新的數組指針填充像素值
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個字節,一個像素佔4字節能存儲32位ARGB值

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

// 指針偏移
int tmp = 0;
// 按照順時針90度旋轉順序掃描
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軸。**以此類推

若是是旋轉90度注意須要在建立bitmap時候寬高須要換一下

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 內存 addrPtr 中讀取像素數據
    void *addrPtr;
    ret = AndroidBitmap_lockPixels(env, bitmap, &addrPtr);
    if (ANDROID_BITMAP_RESULT_SUCCESS != ret) {
        LOGD("AndroidBitmap_lockPixels() bitmap failed ! error=%d", ret)
        return nullptr;
    }

    // 執行圖片操做的邏輯
    // 獲取寬高
    int mWidth = bitmapInfo.width;
    int mHeight = bitmapInfo.height;
    // 獲取原生數據
    auto pixelArr = ((uint32_t *) addrPtr);
    // 矩陣 建立一個新的數組指針填充像素值
    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:
            // 上下翻轉
            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 注意這裏 由於翻轉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);
        // 釋放內存
        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
複製代碼

或者加權平均值法:

(r * 0.3 + g * 0.59 + b * 0.11)
複製代碼

對應jni函數:

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 內存 addrPtr 中讀取 BGRA 數據
    void *addrPtr;
    ret = AndroidBitmap_lockPixels(env, bitmap, &addrPtr);
    if (ANDROID_BITMAP_RESULT_SUCCESS != ret) {
        LOGD("AndroidBitmap_lockPixels() bitmap failed ! error=%d", ret)
    }

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

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

    int a, r, g, b;
    // 不操做A
    // 遍歷從 Bitmap 內存 addrPtr 中讀取 BGRA 數據, 而後向 data 內存存儲 BGR 數據


    switch (ops) {
        // 灰度圖
        case 1: {
            for (int y = 0; y < mHeight; ++y) {
                for (int x = 0; x < mWidth; ++x) {
                    // 這裏定義成void,方便後續操做
                    void *pixel = nullptr;
                    // 24位
                    if (bitmapInfo.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
                        // 移動像素指針
                        pixel = pixelArr + y * mWidth + x;
                        //按照ABGR存儲序列取值 獲取指針對應的值
                        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;
                        //或者加權平均值法
                        int sum = (int) (r * 0.3 + g * 0.59 + b * 0.11);
                        *((uint32_t *) pixel) = MAKE_ABGR(a, sum, sum, sum);
                    }
                }
            }
            break;
        }
            // 浮雕圖
        case 2: {
            // 
            // 用當前點的RGB值減去相鄰點的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濾鏡。

效果:

相關文章
相關標籤/搜索