写点什么

Android NDK 之旅——图片高斯模糊,30 岁以后搞 Android 已经没有前途

发布于: 2021 年 11 月 08 日

数据类型

JNI 的数据类型包含两种,分别是基本类型和引用类型,它们和 Java 中的数据类型对应关系如下两表所示。



《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


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);

评论

发布
暂无评论
Android NDK之旅——图片高斯模糊,30岁以后搞Android已经没有前途