图像的数值示意
RGB
RGB色彩模型即红绿蓝色彩模型。由模拟生物视网膜三种视锥细胞产生,之后通过三原色叠加来进行彩色图像显示。通过在彩色上一直叠加三原色来显示不同的色彩。在RGB色彩空间中,别离将RGB作为笛卡尔坐标系中XYZ坐标系产生。每一个色彩取值范畴为[0,256)
RGB是从色彩发光的原理来设计定的,艰深点说它的颜色混合形式就如同有红、绿、蓝三盏灯,当它们的光互相叠合的时候,色调相混,而亮度却等于两者亮度之总和,越混合亮度越高,即加法混合。
红、绿、蓝三个色彩通道每种色各分为256阶亮度,在0时“灯”最弱——是关掉的,而在255时“灯”最亮。当三色灰度数值雷同时,产生不同灰度值的灰色调,即三色灰度都为0时,是最暗的彩色调;三色灰度都为255时,是最亮的红色调。
对一种色彩进行编码的办法统称为色彩空间或色域。
用最简略的话说,世界上任何一种色彩的“色彩空间”都可定义成一个固定的数字或变量。RGB(红、绿、蓝)只是泛滥色彩空间的一种。采纳这种编码方法,每种色彩都可用三个变量来示意-红色绿色以及蓝色的强度。记录及显示彩色图像时,RGB是最常见的一种计划。
所以每一个图像都能够由RGB组成,那么一个像素点的RGB该如何示意呢?音频外面的每一个采样(sample)均应用16个比特来示意,那么像素外面的子像素又该如何示意呢?罕用的示意形式有以下几种。
- 浮点示意:取值范畴为0.0~1.0,比方,在OpenGL ES中对每一个子像素点的示意应用的就是这种表达方式。
- 整数示意:取值范畴为0~255或者00~FF,8个比特示意一个子像素,32个比特示意一个像素
Android平台上RGB_565的示意办法为16比特模式示意一个像素,R用5个比特来示意,G用6个比特来示意,B用5个比特来示意。
对于一幅图像,个别应用整数示意办法来进行形容,比方计算一张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)。
A 从0x00到0xff示意从通明到不通明,RGB 从0x00到0xff示意色彩从浅到深。当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.FILLpaint.color = Color.rgb(0x11, 0x22, 0x33)canvas.drawRect(0f, 0f, tempBitmap.width.toFloat(), tempBitmap.height.toFloat(), paint)val byteSize = tempBitmap.allocationByteCountval 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 }// ARGBpixel 16=: { a= ff,r= 11,g=22, b=33 }// RGBAout = { 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 RGB8888_A(p) ((p & (0xff<<24)) >> 24 )#define RGB8888_B(p) ((p & (0xff << 16)) >> 16 )#define RGB8888_G(p) ((p & (0xff << 8)) >> 8 )#define RGB8888_R(p) (p & (0xff) )
对应JNI办法:
extern "C"JNIEXPORT void JNICALLJava_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 -134261592020-08-19 16:58:55.374 9562-9562/tt.reducto.ndksample D/TTNative: bitmapInfo a 2552020-08-19 16:58:55.374 9562-9562/tt.reducto.ndksample D/TTNative: bitmapInfo r 172020-08-19 16:58:55.374 9562-9562/tt.reducto.ndksample D/TTNative: bitmapInfo g 342020-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_ORDER
在SkPostConfig.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 NULLstatic 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 JNICALLJava_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 JNICALLJava_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滤镜。
成果: