最近,有人问我一个问题,如何使用彩图转为黑白,又如何将黑白图片转换为彩图?对于这个问题,我能想到的最直接的方法是:调用Android的系统Api获取图片生成bitmap文件,然后再使用Android中的二值化技术即可实现;除此之外,还可以使用FFpeg等库的方式实现。不过,我们今天要讲的是另外一种方案,即使用RenderScript方式。
RenderScript 是用于在 Android 上以高性能运行计算密集型任务的框架。RenderScript 专为数据并行计算而设计,不过串行工作负载也可以从中受益。RenderScript 运行时可以并行安排设备上可用的多个处理器(如多核 CPU 和 GPU)上的工作负载,使开发者能够专注于表达算法而不是调度工作。RenderScript 对于专注于图像处理、计算摄影或计算机视觉的应用来说尤其有用。
RenderScript使用的是一种类似于C/C++的rs 脚本语法,且是在运行时编译、跨平台的。性能比 Java 好,比 Native 略差。下图是RenderScript在Android 8.0 及更高版本的设备上的一个框架示意图。
与 Android 7.x 及更低版本中的 RenderScript 之间的区别如下:
关于RenderScript的说明,可以参考RenderScript架构组成
RenderScript的使用分为两个步骤:
RenderScript 内核通常位于 <project_root>/src/
目录下,由类C语言的.rs语法编写,每个.rs 文件就是一个脚本,每个脚本由一组内核、函数和变量构成。首先,创建一个rs脚本文件代码。
在这里插入图片描述
然后,打开 app 的 build.gradle 文件,在 android 的 defaultConfig 结点添加两句:
renderscriptTargetApi 18
renderscriptSupportModeEnabled true
接下来,下面以【将图片置灰】为例来说明如何编写内核脚本文件,新建一个 Gray.rs 文件,如下所示。
#pragma version(1)
#pragma rs java_package_name(com.avatar.rs)
void root(const uchar4 *in, uchar4 *out, uint32_t x, uint32_t y) {
// a 是透明度,这里不修改透明度。
out->a = in->a;
// 快,但并不是真正意义的去色
out->r = out->g = out->b = (in->r + in->g + in->b) / 3;
// 慢,但是是真正的去色
// out->r = out->g = out->b = (in->r * 299 + in->g * 587 + in->b * 114 + 500) / 1000;
}
void init() {
}
其中,第1行声明 RenderScript 的版本;第2行是申明该脚本所在的Java包的包名;root 函数是脚本文件的入口函数,对于图片来说,root函数负责对每一个像素做处理。参数 in 是输入像素点的指针; out 是输出像素点的指针。并且,init 函数是可选的,主要用于做一些初始化的工作。
使用前,需要先引入RenderScript脚本文件,如下所示。
import com.avatar.rs.ScriptC_greyscale;
这里的类名是 ScriptC_ 加上 .rs 的文件名,包名就是在创建 rs 文件时声明的包名。
import com.avatar.rs.ScriptC_greyscale;
public class MainActivity extends AppCompatActivity {
private ImageView mImageView;
private ImageView mRSImageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mImageView = findViewById(R.id.image_view);
mRSImageView = findViewById(R.id.rs_image_view);
Bitmap mInBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test);
Bitmap mOutBitmap = Bitmap.createBitmap(mInBitmap.getWidth(), mInBitmap.getHeight(), mInBitmap.getConfig());
mImageView.setImageBitmap(mInBitmap);
Bitmap bitmap=transGray(this,mInBitmap);
mRSImageView.setImageBitmap(bitmap);
}
public static Bitmap transGray(@NonNull Context context, @NonNull Bitmap bitmap) {
// 创建输出 bitmap
int width = bitmap.getWidth();
int height = bitmap.getHeight();
Bitmap outBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
// 创建 RenderScript 对象
RenderScript rs = RenderScript.create(context);
// 创建输入、输出 Allocation
Allocation allIn = Allocation.createFromBitmap(rs, bitmap);
Allocation allOut = Allocation.createFromBitmap(rs, outBitmap);
// 创建我们在上面定义的 script
ScriptC_greyscale script = new ScriptC_greyscale(rs);
// 对每一个像素执行 root 方法
script.forEach_root(allIn, allOut);
// 将执行结果复制到输出 bitmap 上
// 释放资源
rs.destroy();
return outBitmap;
}
}
然后,我们运行下代码,看看前后的对比效果。
除了 root 函数,我们还可以在 .rs 中定义其他的 kernal 函数,比如:
/**
* 黑金色转换
*/
uchar4 __attribute__((kernel)) blackGold(uchar4 in, uint32_t x, uint32_t y) {
uchar4 out = in;
if ((in.r < in.b) && (in.g < in.b)) {
out.r = out.g = out.b = (in.r*299 + in.g*587 + in.b*114 + 500) / 1000;
}
return out;
}
uchar4 __attribute__((kernel)) root(uchar4 v_in) {
float4 f4 = rsUnpackColor8888(v_in);
float3 mono = dot(f4.rgb, gMonoMult);
return rsPackColorTo8888(mono);
}
uchar __attribute__((kernel)) toU8(uchar4 v_in) {
float4 f4 = convert_float4(v_in);
return (uchar)dot(f4.rgb, gMonoMult);
}
uchar4 __attribute__((kernel)) toU8_4(uchar v_in) {
return (uchar4)v_in;
}
在定义kernal 函数时,函数返回值必须是 uchar4, 并且用 __attribute__((kernel))
标记该函数是个 kernal 函数。然后,我们在Java代码中就可以使用下面的方式进行调用了。
script.forEach_blackGold(allIn, allOut);