RenderScript渲染利器

RenderScript

RenderScript是Android系统中能高效处理大量计算任务的框架,特别适用一些需要处理图片和加载图片以及计算机视觉的方面应用。

本文将先从如何使用出发,然后介绍关于 RenderScript的部分高级知识。

在阅读本文之前,需要知道的是Renderscript是用C99(C的一种标准)实现的。

First of all

在app的build.gradle文件加入下面两行:

 defaultConfig {
        applicationId "com.uniquestudio.renderscript"
        minSdkVersion 14
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"

        renderscriptTargetApi 18
        renderscriptSupportModeEnabled true
    }

考虑到兼容性,在使用RenderScript的类中,要引入:import android.support.v8.renderscript.*;

从实用性出发,下面介绍三种处理图片的方式。

Blur Image

现在很多APP都在使用 模糊 效果的图片,RenderScript提供了ScriptIntrinsicBlur帮助我们实现模糊效果。一个渲染的过程其实就是一个加工的过程,把送进来的原材料加工成想要的产品。

看一下这个加工过程:

 /**
     *
     * @param bitmap src
     * @param radius the radius of blur ,max is 25
     * @param context
     * @return a blur bitmap
     */
    public static Bitmap blurBitmap(Bitmap bitmap, float radius, Context context) {
        //Create renderscript
        RenderScript rs = RenderScript.create(context);

        //Create allocation from Bitmap
        Allocation allocation = Allocation.createFromBitmap(rs, bitmap);

        Type t = allocation.getType();

        //Create allocation with the same type
        Allocation blurredAllocation = Allocation.createTyped(rs, t);

        //Create blur script
        ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
        //Set blur radius (maximum 25.0)
        blurScript.setRadius(radius);
        //Set input for script
        blurScript.setInput(allocation);
        //Call script for output allocation
        blurScript.forEach(blurredAllocation);

        //Copy script result into bitmap
        blurredAllocation.copyTo(bitmap);

        //Destroy everything to free memory
        allocation.destroy();
        blurredAllocation.destroy();
        blurScript.destroy();
        t.destroy();
        rs.destroy();
        return bitmap;
    }

参数radius是模糊程度。虽然我们没有自己写脚本,但是抛开脚本,从上层java代码我们不难发现,使用RenderScript的几个关键步骤:

  • 将数据填充在Allocation对象中,一般来说需要两个Allocation,一个存放,原材料另一个是作为加工之后的数据存放地
  • 开启渲染
  • 将Allocation中的产品输出
  • 回收资源

代码很简单,不需要太多解释。在模糊效率上,是用java实现的fastblur速度的8倍,而且在ORM的问题上也有优化。
效果图:

原图

blur

Sketch Image

实现素描效果,处理的算法很简单:

求RGB平均值Avg = (R + G + B) / 3,如果Avg >= 100,则新的颜色值为R=G=B=255;如果Avg < 100,则新的颜色值为R=G=B=0;255就是白色,0就是黑色;至于为什么用100作比较,这是一个经验值吧,设置为128也可以,可以根据效果来调整。

STEP1:Writing a RenderScript Kernel

首先从渲染脚本文件写起,在main文件下新建一个包res,在res中新建sketch.rs注意文件的后缀是rs
我们先看一下官方文档的写法:

A simple kernel may look like the following:
uchar4 __attribute__((kernel)) invert(uchar4 in, uint32_t x, uint32_t y) {
  uchar4 out = in;
  out.r = 255 - in.r;
  out.g = 255 - in.g;
  out.b = 255 - in.b;
  return out;
}

文档对此的解释:

  • The first notable feature is the attribute((kernel)) applied to the function prototype. This denotes that the function is a RenderScript kernel instead of an invokable function.(attribute((kernel))用来区分kernel和invokable方法)
  • 函数参数的x,y,或者z是可选的,但是类型必须是uint32_t
  • pragma version(1)是指版本号,目前只能选择1,没有更高的版本了。
  • pragma rs java_package_name(com.hc.renderscript)是告诉编译器,包的名称。因为每个rs文件都会自动生成对应的Java代码。

我对in参数的理解是allocation中的Element

#pragma version(1)
#pragma rs_fp_relaxed
#pragma rs java_package_name(com.uniquestudio.renderscript)

// Debugging RenderScript
#include "rs_debug.rsh"

uchar4 __attribute__((kernel)) invert(uchar4 in, uint32_t x, uint32_t y) {
   //Convert input uchar4 to float4
   float4 f4 = rsUnpackColor8888(in);

   float r = f4.r;
   float g = f4.g;
   float b = f4.b;
   rsDebug("Red", r);

   if((r+g+b)*255/3>=100){
       return rsPackColorTo8888(1, 1, 1, f4.a);
   }else{
       return rsPackColorTo8888(0, 0, 0, f4.a);
   }
}

Tip:#include "rs_debug.rsh"可以打印log,但是在kernel method中打印log会造成io操作,使得整个处理过程的时间变长。

STEP2:Build project to generate .classes file

build之后会生成ScriptC_sketchjava文件,格式固定ScriptC_xxx,以此类推。

STEP3:Call scripts from Java code

RenderScript中用java代码控制脚本的生命周期。

public class SketchUtil {
    public static Bitmap sketchBitmap(Bitmap bitmap,Context context){
        RenderScript renderScript = RenderScript.create(context);
        ScriptC_sketch sketchScript = new ScriptC_sketch(renderScript);

        Allocation in = Allocation.createFromBitmap(renderScript,bitmap);
        Allocation out = Allocation.createTyped(renderScript,in.getType());

        // call kernel
        sketchScript.forEach_invert(in,out);

        out.copyTo(bitmap);

        renderScript.destroy();
        sketchScript.destroy();
        in.destroy();
        out.destroy();

        return bitmap;
    }
}

最后的效果图:


sketch

是不是很简单?之前我在kernel方法中打印过log,我发现,在java层中调用forEach_xx会遍历传入的Allocation参数,那么kernel方法中不要自己写循环了。

Magnifier Image

实现放大镜效果,类似这种:


example

假如我们定义放大镜的坐标为(atX,atY),半径为radius,而放大倍数为scale,那么其实就是将原图中的坐标为(atX,atY)、半径为radius/scale的区域的图像放大到放大镜覆盖的区域即可,算法其实很简单,对图片上的每一个点(X,Y),求其与(atX,atY)的距离Distance,若Distance < Radius,则取原图中坐标为(X/scale,Y/scale)的像素的颜色值作为新的颜色值。

这个问题的难点在于如何取回(X/scale,Y/scale)的像素,我们必然要存储所有的像素,以便我们取回。这里需要使用到脚本文件中与Allocation对应的rs_allocation以及rsGetElementAt_uchar4(allocation, X, Y)函数

具体的流程和上面的Sketch相同。源代码:

// Needed directive for RS to work
#pragma version(1)

// The java_package_name directive needs to use your Activity's package path
#pragma rs java_package_name(com.uniquestudio.renderscript)

// Store the input allocation
rs_allocation inputAllocation;

// Magnifying
// TODO: here, some checks should be performed to prevent atX and atY to be < 0, as well
//   as them to not be greater than width and height
int atX;
int atY;
float radius;
float scale; // The scale is >= 1

uchar4 __attribute__((kernel)) magnify(uchar4 in, int x, int y) {

    // Calculates the distance between the touched point and the current kernel
    // iteration pixel coordinated
    // Reference: http://math.stackexchange.com/a/198769
    float pointDistanceFromCircleCenter = sqrt(pow((float)(x - atX),2) + pow((float)(y - atY),2));


    // Is this pixel outside the magnify radius?
    if(radius < pointDistanceFromCircleCenter)
    {
        // In this case, just copy the original image
        return in;
    }


    // If the point is inside the magnifying inner radius, draw the magnified image

    // Calculates the current distance from the chosen magnifying center
    float diffX = x - atX;
    float diffY = y - atY;

    // Scales down the distance accordingly to scale and returns the original coordinates
    int originalX = atX + round(diffX / scale);
    int originalY = atY + round(diffY / scale);

    // Return the original image pixel at the calculated coordinates
    return rsGetElementAt_uchar4(inputAllocation, originalX, originalY);
}

Java Code:

 public static Bitmap magnifierBitmap(Bitmap bitmap, int x, int y, int radius,int scale, Context context){
        RenderScript rs = RenderScript.create(context);

        Allocation in = Allocation.createFromBitmap(rs, bitmap);
        Allocation out = Allocation.createTyped(rs,in.getType());


        ScriptC_magnifier magnifier = new ScriptC_magnifier(rs);

        magnifier.set_inputAllocation(in);
        magnifier.set_atX(x);
        magnifier.set_atY(y);
        magnifier.set_radius(radius);
        magnifier.set_scale(scale);


        magnifier.forEach_magnify(in,out);

        out.copyTo(bitmap);

        rs.destroy();
        magnifier.destroy();
        in.destroy();
        out.destroy();

        return bitmap;
}

效果图:


Magnifier

Advanced RenderScript

这一部分介绍两个layer(runtime和reflected),了解关于renderscript编译和执行。

RenderScript Runtime Layer

RenderScript的编译和执行都发生在这个层。

编译的过程:共进行两次编译。

  • 第一次.rs文件被llvm compiler编译为字节码,
  • 第二次将字节码由设备上的llvm compiler编译为机器码,而且机器码会在设备上存储起来,这样之后RenderScript的执行就不再需要编译字节码了。

这样就不难解释为什么RenderScript的移植性比较高了。

RenderScript运行的库中有几个关键的功能:

  • 内存分配功能
  • 大量的数学计算函数
  • 数据类型的转化
  • log函数

Reflected Layer

为了能从Android Framwork层访问RenderScript Runtime层,Android Build Tools生成了反射层。

.rs脚本文件被反射成位于project_root/gen/package/name/ScriptC_*renderscript_filename*的类,也就是.rs的java版本文件,然后我们就能从Android Framework层调用了。

.rs文件的反射中,无非是要把文件的变量和函数进行反射。

Variables

如果在RenderScript中有以下声明:

uint32_t unsignedInteger = 1;

那么反射之后会产生:

private long mExportVar_unsignedInteger;
public void set_unsignedInteger(long v){
    mExportVar_unsignedInteger = v;
    setVar(mExportVarIdx_unsignedInteger, v);
}

public long get_unsignedInteger(){
    return mExportVar_unsignedInteger;
}

所以我们在Java层就可以使用get或者set对RenderScript中的变量进行操作了。

但是如果在RenderScript中有const修饰变量时,就会不会产生set方法。

Functions

如果在RenderScript中定义了这样一个函数:

void touch(float x, float y, float pressure, int id) {
    if (id >= 10) {
        return;
    }

    touchPos[id].x = x;
    touchPos[id].y = y;
    touchPressure[id] = pressure;
}

反射之后产生:

public void invoke_touch(float x, float y, float pressure, int id) {
    FieldPacker touch_fp = new FieldPacker(16);
    touch_fp.addF32(x);
    touch_fp.addF32(y);
    touch_fp.addF32(pressure);
    touch_fp.addI32(id);
    invoke(mExportFuncIdx_touch, touch_fp);
}

RenderScript中函数最好不要带返回值,RenderScript本身就是异步的,如果函数有返回值,那么Android Framework调用时就会一直阻塞,直到有值返回,这样在处理大量计算任务的时候会直接影响效率。

上面的代码已经上传到github,传送门

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • [译]原文链接 RenderScript是运行在Android系统上的一个高性能密集计算框架。RenderScri...
    stefanli阅读 8,300评论 0 13
  • 一、入口 https://developer.android.google.cn -> API指南 -> 库 ->...
    捡之阅读 8,939评论 4 8
  • 我的CSDN博客同步发布:RenderScript 让你的Android计算速度快的飞上天! 在上一篇文章Andr...
    huachao1001阅读 5,149评论 8 38
  • 1.python 中类型 (1) type 判断类型 python是一种动态类型语言,换句话说每个变量可以在程序里...
    lmem阅读 363评论 0 0
  • 今天我们继续跑着,每个人都是奔跑的人,时钟无法暂停,时间往前不停流逝,这是一场不能回头的马拉松比赛,边跟对手竞争着...
    高恩泽阅读 211评论 0 0