Android NDK 之旅——图片高斯模糊,30 岁以后搞 Android 已经没有前途
数据类型
JNI 的数据类型包含两种,分别是基本类型和引用类型,它们和 Java 中的数据类型对应关系如下两表所示。
nt | int | 32 位整型 || jlong | long | 64 位整型 || jfloat | float | 32 位浮点型 || jdouble | double | 64 位浮点型 || void | void | 无类型 |
JNI 的类型签名
JNI 的类型签名标识了一个特定的 Java 类型,这个类型既可以是类也可以是方法,也可以是数据类型。
类的签名比较简单,它采用 L+包名+类型+; 的形式,只需要将其中的.替换为/即可。例如 java.lang.String, 它的签名为 Ljava/lang/String; ,注意末尾的;也是签名一部分。
基本数据类型的签名采用一系列大写字母来表示, 如下表所示
JNI C/C++函数编写
先来看看 Android Studio 为我们生成的示例
JNIEXPORT jstring JNICALLJava_com_glee_myapplication_MainActivity_stringFromJNI(JNIEnv* env,jobject /* this */) {std::string hello = "Hello from C++";return env->NewStringUTF(hello.c_str());}
JNIEXPORT & JNICALL
JNIEXPORT 和 JNICALL 这两个宏(被定义在 jni.h)确保这个函数在本地库外可见,并且编译器会进行正确的调用转换。
函数规范
在 JNI 中 C/C++的函数名是有规范要求的,由以下几部分串接而成
Java_前缀
完全限定的类名,并用下划线“_”作为分隔符
第一参数JNIEnv* env
第二个参数jobject或jclass
其他参数按类型映射
返回参数按类型映射
JNI 层操作 Bitmap 对象
原理
Android 中 JNI 层处理 Bitmap 通常有两种方法
获取到 Bitmap 中的 byte 数组并传入 native 方法,JNI 层处理得到的 byte 数组后返回一个新的 byte 数组,Java 层重建 Bitmap 对象。(不推荐)
Java 层直接向 JNI 层传入 Bitmap 的引用,JNI 层得到 Bitmap 对象的图像数据的地址,直接修改 Bitmap 的 byte 数组。
阅读了很多篇博客,很多开发者都会采用第一种方法,本人是极不推荐的。这种方法会在内存中重建一个 byte 数组,会造成内存的浪费,性能低下。
第二种方法是性能最优的,JNI 层充分利用的 C/C++指针的特性,直接获取到 Bitmap 中 byte 数组在内存中的地址,通过指针直接修改图像数据,所以用到了 NDK 中的 android/bitmap.h。
android/bitmap.h
android/bitmap.h 这个头文件用于在 JNI 层操作 Bitmap 对象的,其包含于 jnigraphics 库中,所以要在 CMakeLists.txt 中的 target_link_libraries 加入-ljnigraphics,如下
target_link_libraries(native-lib -ljnigraphics ${log-lib})
三个常用函数
AndroidBitmap_getInfo() 从位图句柄获得信息(宽度、高度、像素格式)
AndroidBitmap_lockPixels() 对像素缓存上锁,即获得该缓存的指针。
AndroidBitmap_unlockPixels() 解锁
JNI 接口函数
请看注释
JNIEXPORT void JNICALLJava_com_glee_ndkroad1006_MainActivity_gaussBlur(JNIEnv env, jobject / this /, jobject bmp) {AndroidBitmapInfo info = {0};//初始化 BitmapInfo 结构体 int data=NULL;//初始化 Bitmap 图像数据指针 AndroidBitmap_getInfo(env, bmp, &info);AndroidBitmap_lockPixels(env, bmp, (void *) &data);//锁定 Bitmap,并且获得指针/高斯模糊算法作对 int 数组进行处理///调用 gaussBlur 函数,把图像数据指针、图片长宽和模糊半径传入 gaussBlur(data,info.width,info.height,80);/**************************************************/AndroidBitmap_unlockPixels(env,bmp);//解锁}
这里用到的 gaussBlur 函数代码将在文章最后列出。
这里用到的 gaussBlur 函数代码将在文章最后列出。
这里用到的 gaussBlur 函数代码将在文章最后列出。
Java 层代码
请看注释
public class MainActivity extends AppCompatActivity {
static {//通过静态代码块加载 so 库 System.loadLibrary("native-lib");}
@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//初始化两个 ImageViewImageView iv1 = (ImageView) findViewById(R.id.img1);ImageView iv2 = (ImageView) findViewById(R.id.img2);//iv1 设置图片 iv1.setImageResource(R.drawable.test);//生成 bitmap 对象 Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);//调用 native 方法,传入 Bitmap 对象,对 Bitmap 进行高斯迷糊处理 gaussBlur(bitmap);//把 Bitmap 对象设置给 iv2iv2.setImageBitmap(bitmap);}//native 方法声明 public native void gaussBlur(Bitmap bitmap);}
运行效果
上方的 ImageView 是没有进行高斯模糊处理的,下方的 ImageView 调用了 JNI 方法进行高斯模糊处理。
高斯模糊算法
void gaussBlur1(int* pix, int w, int h, int radius){float sigma = (float) (1.0 * radius / 2.57);float deno = (float) (1.0 / (sigma * sqrt(2.0 * PI)));float nume = (float) (-1.0 / (2.0 * sigma * sigma));float* gaussMatrix = (float*)malloc(sizeof(float)* (radius + radius + 1));float gaussSum = 0.0;for (int i = 0, x = -radius; x <= radius; ++x, ++i){float g = (float) (deno * exp(1.0 * nume * x * x));gaussMatrix[i] = g;gaussSum += g;}int len = radius + radius + 1;for (int i = 0; i < len; ++i)gaussMatrix[i] /= gaussSum;int* rowData = (int*)malloc(w * sizeof(int));int* listData = (int*)malloc(h * sizeof(int));for (int y = 0; y < h; ++y){memcpy(rowData, pix + y * w, sizeof(int) * w);for (int x = 0; x < w; ++x){float r = 0, g = 0, b = 0;gaussSum = 0;for (int i = -radius; i <= radius; ++i){int k = x + i;if (0 <= k && k <= w){//得到像素点的 rgb 值 int color = rowData[k];int cr = (color & 0x00ff0000) >> 16;int cg = (color & 0x0000ff00) >> 8;int cb = (color & 0x000000ff);r += cr * gaussMatrix[i + radius];g += cg * gaussMatrix[i + radius];b += cb * gaussMatrix[i + radius];gaussSum += gaussMatrix[i + radius];}}int cr = (int)(r / gaussSum);int cg = (int)(g / gaussSum);int cb = (int)(b / gaussSum);pix[y * w + x] = cr << 16 | cg << 8 | cb | 0xff000000;}}for (int x = 0; x < w; ++x){for (int y = 0; y < h; ++y)listData[y] = pix[y * w + x];for (int y = 0; y < h; ++y){float r = 0, g = 0, b = 0;gaussSum = 0;for (int j = -radius; j <= radius; ++j){int k = y + j;if (0 <= k && k <= h){int color = listData[k];int cr = (color & 0x00ff0000) >> 16;int cg = (color & 0x0000ff00) >> 8;int cb = (color & 0x000000ff);r += cr * gaussMatrix[j + radius];g += cg * gaussMatrix[j + radius];b += cb * gaussMatrix[j + radius];gaussSum += gaussMatrix[j + radius];}}int cr = (int)(r / gaussSum);int cg = (int)(g / gaussSum);int cb = (int)(b / gaussSum);
评论