简介
RenderScript是一个Google出品的,在Android平台上的并行计算框架,官方的简介是说RenderScript运行时可在设备上提供的多个处理器(如多核 CPU 和 GPU)间并行调度工作。在日常Android开发中,RenderScript主要用于图像处理。比如对图片做高斯模糊等,都可以用RenderScript处理。
问题
我在项目中有这样一个需求:用一个二维数组生成一张Bitmap图片。二维数组中,每一个元素都是代表对于Bitmap中某一个像素点的像素值,比如数组 array[x][y]的元素,就代表了原Bitmap中第x列y行的像素点的像素值颜色。做这个需求,一般可以直接简单粗暴的通过遍历二维数组来实现,举个例子:
fun <T> createMask(array: Array<T>,
width: Int,
height: Int,
pixelsOp: (Int, Int, T) -> Int): Bitmap {
val conf = Bitmap.Config.ARGB_8888
val bitmap = Bitmap.createBitmap(width, height, conf)
for (y in 0 until height) {
for (x in 0 until width) {
val value = array[x + width * y]
val color = pixelsOp(x, y, value)
bitmap(x, y, color)
}
}
return bitmap
}
上面这个方法,接受一个数组、指定图片的宽度和高度、一个具体执行每个像素点的生成过程的函数。在方法内部,是先创建一张空的Bitmap,然后遍历数组,在遍历过程中,对Bitmap做逐像素点的设置。这样处理是简单粗暴,在图片大小不大的时候,比如宽高200左右,倒也能接受;但是如果图片较大,那耗时就会非常严重。处理一张1000 * 1000大小的图片,至少有3000ms时间,这显然是非常恐怖的。为了追求快速高效,就可以考虑使用RenderScript了。用RenderScript的话,生成一张1000 * 1000大小的图片,耗时仅仅几毫秒!!!这是因为RenderScipt是并行处理的,所以速度是相当的快。
基本用法
RenderScript的用法,简单来说是可以概括为:编写内核文件,用Control API控制RenderScript内核的导入并运行,接收以图片为基础的数据输入,再以图片数据为输出。所以他的使用分为两步:
-
编写内核文件
-
调用API处理内核
接下来我以上面那个例子作为场景,简单演示下RenderScript是如何用来做“生成Bitmap”的。
编写内核文件
RenderScript 内核通常位于 <project_root>/src/ 目录下的 .rs 文件中,由类C语言编写;每个 .rs 文件就是一个脚本。每个脚本都包含其自己的一组内核、函数和变量。在内核中,主要进行的就是对每个像素点的操作,对应到前面的例子中,就是 pixelsOp
方法。我们在自己姓名的src目录下创建一个文件,main.rs,文件内容如下:
#pragma version(1)
#pragma rs java_package_name(自己的包名)
#pragma rs_fp_relaxed
float array[1000*1000];
/*
* RenderScript kernel
*/
uchar4 RS_KERNEL gen_bitmap(uchar4 in,uint32_t x, uint32_t y)
{
uchar4 out = in;
float value = array[y * 1000 + x];
out.r = value;
out.g = value;
out.b = value;
out.a = 255;
return out;
}
main.rs就是内核文件,在内核文件中,array数组就是我们需要输入的图像数据,即一个二维数组(这里以一维数组来表示,但实际使用是通过 y * 1000 + x
的方式相当于是转换为了一个二维数组)。RS_KERNEL 是由 RenderScript 自动定义的宏,目的在于方便使用,也可以换成原始一点的 __attribute__((kernel))
,主要是用来标识接下来的这个函数 gen_bitmap
是一个内核映射函数,说白了就是具体执行“逐像素点设置颜色值”的这么一个函数,函数接受三个参数:in代表输入的bitmap数据,out是输出数据,x和y表示坐标。对一个Bitmap操作时,实际上是对Bitmap中的每个像素点执行一次 gen_bitmap
函数,所以in这里指的就是Bitmap中当前点的数据,out是相对应的输出数据,x和y也就是当前点的位置信息。函数内做的操作很简单:把array数组中对应元素的值赋予该像素点out,也即分别对像素点的r g b a 四个通道进行赋值。最后 return
把结果返回即可。
调用API处理内核
创建好内核文件之后,就可以调用提供的API来处理内核了,这里不用引入什么依赖库,只需要注意一下,暂时先不要用androidx包目录下的RenderScript API,会有问题。调用API代码如下:
fun genDeptBitmap(floatArray: FloatArray,width:Int,height:Int):Bitmap{
val renderScript:RenderScript = RenderScript.create(context)
val inputBitmap = Bitmap.createBitmap(width,height,Bitmap.Config.ARGB_8888)
val inputAllocation = Allocation.createFromBitmap(renderScript,inputBitmap)
val outputType = Type.Builder(renderScript, Element.RGBA_8888(renderScript))
.setX(inputBitmap.width)
.setY(inputBitmap.height)
val outputAllocation = Allocation.createTyped(renderScript,outputType.create())
val script = ScriptC_main(renderScript)
script._array = floatArray
script.forEach_gen_bitmap(inputAllocation,outputAllocation)
val outputBitmap = Bitmap.createBitmap(inputBitmap.width,inputBitmap.height,inputBitmap.config)
outputAllocation.copyTo(outputBitmap)
return outputBitmap
}
首先用一个上下文对象Context创建RenderScript对象。因为我们需要生成一张新的Bitmap,所以需要先创建一张空白的Bitmap作为输入参数 inputBitmap
。RenderScript不能直接接受Bitmap输入,是需要先转换为一个 Allocation
对象,转换方式比较简单,直接调用 Allocation
类的静态方法 Allocation.createFromBitmap()
即可。同样的,输出数据也是一个 Allocation
对象,所以也要先创建一个 Allocation
对象用来承载输出。这里跟输入不一样的是,输入的 Allocation
是已知Bitmap,再用对应方法转换的;输出 Allocation
因为没有数据,只知道数据类型是一个Bitmap,所以是用 Allocation.createTyped()
方法来创建,这个方法接受的第二个参数是一个 Type
对象,包含了Bitmap的相关信息。之后,创建一个 ScriptC_main
的对象。 ScriptC_main
这个类是编写玩内核文件后,先 build 一次自动生成的,我们无需关心他怎么创建,直接使用就好。因为内核文件是 main.rs
,所以内核文件对应的脚本对象类叫 ScriptC_main
。到这里,我们内核文件就通过 script对象承载了,其中的 _array
属性就对应着内核文件中的 array
数组,forEach_gen_bitmap()
函数则是由 gen_bitmap()
方法生成的,forEach嘛,很简单,也就说在API中调用一次 forEach_gen_bitmap()
函数,就等于是对每个像素点分别调用一次 gen_bitmap()
函数了。forEach_gen_bitmap()
接受输入和输出数据,在调用了 forEach_gen_bitmap()
方法后,图像就处理完了,直接从前面准备好的 outputAllocation
中获取,即先创建一张空白的Bitmap,然后调用 copyTo()
方法,把 Allocation
中的数据就拷贝到了Bitmap中,这样一次图像处理流程就结束了。
最后
其实RenderScript可以做的事情远不止这些,本文的示例只是简单的生成一张Bitmap,也就是相当于没有进行图像处理的图像处理,实际上还可做很多比如高斯模糊、直方图均衡化等处理,SDK也已经内置了这些处理需要的内核,我们可以直接跳过创建内核文件这一步骤,直接去调用API处理即可。
网友评论