初窥图像处理利器RenderScript

1.RenderScript 简介


RenderScriptAndroid平台的一种类C的高性能编程语言,用于3D渲染和处理密集型计算。一直以来Android在绘图性能的表现一直差强人意,引入NDK之后才有所改善,而在Honeycomb中发布了RenderScript这一杀手锏,大大的增加了Android本地语言的执行能力和计算能力。


1.1移植性

虽然RenderScript是使用类C的语言编写,但是它的的移植性,目前还并不是很理想。RenderScript虽然无法从其他C应用程序直接移植过来,不过它在Android设备上比NDK更常见。相比于NDK的可移植性要弱一些,所以说RenderScript的移植性可以说有待改善。


1.2编译过程

扫描二维码关注公众号,回复: 10481283 查看本文章

RenderScript在机器上进行第一遍编译,然后在目标设备上进行最后一遍编译(Just-In-TimeCompiling),因而带来更高效的原生二进制代码。这也就是意味着,凡是支持RenderScript的设备都可以运行你的代码。不用管什么架构。

目前,RenderScript带来的代码只能在主处理器上运行,它会自动生成可利用多个核心的代码(如果设备上有多个核心)。就因此,编译出来的程序是针对该机器的最佳优化,这解决了DeviceFragmentation,也就是说开发者再也不必担心使用者的手机、平板够不够好、有没有GPU...等等问题,全都交给RenderScript去担心就好。没有GPU,RenderScript写好的程序就交由CPU来处理(背后的编译技术其实是使用的LLVM)。


1.3性能分析

RenderScript有类似于CUDAComputeAPI用于计算,配置和设置相对比较容易。最终的运行速度实际上要比胜过于NDK的实现方式,需要编写的代码比较少。而RenderScript最适合用于做3D的用户界面或高性能计算任务。而上面的编译和调试所说的一样,随着最后一遍的编译可以通过硬件直接或得性能的提升。



2.RenderScript 实现的机理与语法格式


RenderScript的运行机制可以用图2-1来概括。













2-1renderscript运行机制


RenderScript的应用主要包括三层,分别为NativeRenderScript 层、Reflected层以及AndroidFramework 层。


2.1Native RenderScript

NativeRenderScript层,主要是RenderScript的脚本代码,一般以.rs作为后缀,是编写处理数据的核心代码。

rs脚本有自己的一套格式规范,一般前面的格式固定不变的,格式如下:

#pragmaversion(1)

#pragmarsjava_package_name(com.example.myviewblur)

第一行申明脚本的版本,目前版本都使用version(1),第二行主要表明rs脚本,对应生成的java文件所在的包名。

其他的代码的编写和C语言的编写格式,没有太大的差别,需要注意的是就是有特殊的函数,init()函数和root()函数。他们都会被renderscript的编译系统去直接调用。就是说,即使不再代码中显示的去调用这两个函数,他们也会执行。



2.2Reflected

Reflected层,又叫做反射层,在这一层中,并不需要开发者做什么事情,应为该层是由编译器在编译RS脚本的时候自动产生的。反射层的作用主要是为了解决framework层的java代码调用rs代码而生成的中间层。这样framewrok就可以很容易的调用rs的函数,和向rs传递要处理的数据。


2.3Android Framework

AndroidFramework层,顾名思义,在这里就是RS脚本的调用层。framework层通过编译器产生的反射层,来轻松的向RS脚本传递要处理的数据,然后调用相应的函数去处理数据,最终将处理完的数据取回,做下一步的逻辑操作,比如将处理完的图像数据显示在屏幕上等等。



3.RenderScript 图像处理

在该章节将详细讲解一个使用RenderScript脚本实现的图像处理的示例。由于目前高斯模糊的应用十分广泛,所以在该章节中采用高斯模糊来作为RenderScript图像处理的示例。高斯模糊的原理在这里就不过多的赘述了,原理也比较简单,主要就是对每一个像素点做均值滤波,本文中为了提高高斯模糊的效率,主要采用对图像作水平和垂直的均值滤波来代替该点的领域均值滤波。为了降低篇幅和文章的重复,在对高斯模糊程序的讲解过程中只选用水平方向的均值滤波作为阐述的对象。

3.1RS 脚本的编写


uint32_tradiusX2; // 高斯模糊的半径

uint32_tdstlen;

uint32_tsrclen; //两个变量都是代表了图像的宽度

uchar4*gDst; //处理完的结果数据

constuchar4 *gSrc; //源数据

float*gGuass; //高斯权值因子


staticint16_t edge(uint16_t i, uint16_t x, uint16_t w)

{

int16_th_offset = x + i;

if (h_offset < 0) h_offset = -x;

else if(h_offset >= w) h_offset = w - 1 - x;

else h_offset = i;

returnh_offset;

}


voidroot(const uint16_t *eachline){

int16_tx,n,h_offset,i = *eachline,

size =radiusX2 + 1, radius = radiusX2 / 2;

uint32_tc_in = i * srclen, c_rt = i * dstlen,end;

//end =c_rt + dstlen - radius;

end =c_rt + dstlen;


float4color_in, color;


for(x = 0; c_rt < end; c_rt++, c_in++, x++, color = 0.0f) {

for(n = 0, i = -radius; i <= radius; i++, n++) {

h_offset= edge(i, x, dstlen);


color_in= rsUnpackColor8888(gSrc[c_in + h_offset]);

color+= color_in * gGuass[n];

}

gDst[c_rt]= rsPackColorTo8888(color);

}


}


代码比较简单,就是对图像像素点的每一行中的每一点做水平方向的均值滤波操作。root()函数的参数eachline代表了当前的行数。edge()函数的作用主要是对每个像素点做边缘的检测看是否超出了边缘,并进行相应的调整。






3.2反射层代码

这个地方,不妨对大家说一下,虽然说反射层一般是由android编译器编译生成的。但是我们依然可以自己编写反射层的代码,并人为的和RS脚本进行绑定就ok了,后面的操作中我们依然可以通过调用自己编写的反射层代码来操作RS的代码,而不用系统自动生成的反射层代码。

自己编写反射层代码的最大的优点就是很灵活,但是也存在着很明显的缺陷,就是编写的代码可能并没有编译器自动生成的代码规范,而且也可能存在错误。


privatefinal static class RSCBlurH extends ScriptC {

privatefinal static int mExportVarIdx_radiusX2 = 0;

privatefinal static int mExportVarIdx_dstlen = 1;

privatefinal static int mExportVarIdx_srclen = 2;

privatefinal static int mExportVarIdx_gDst = 3;

privatefinal static int mExportVarIdx_gSrc = 4;

privatefinal static int mExportVarIdx_gGuass = 5;

privatefinal static int mExportForEachIdx_root = 0;


publicRSCBlurH(RenderScript rs, int radiusX2, int width,

Allocationkernel, Allocation input, Allocation output) {

super(rs,rs.getApplicationContext().getResources(),

rs.getApplicationContext().getResources().getIdentifier("rsc_blur_h",

"raw",rs.getApplicationContext().getPackageName()));

//主要实现和rs脚本进行绑定

//On Android4.4 and later, The size is 16 byte-aligned per line.

if(Build.VERSION.SDK_INT > 18) {

//PR:701779 20140612 xiaoqing.he modified start

//setVar(mExportVarIdx_dstlen, getAlignedPixn(width - radiusX2 / 2));

setVar(mExportVarIdx_dstlen,getAlignedPixn(width));

//PR:701779 20140612 xiaoqing.he modified end

setVar(mExportVarIdx_srclen,getAlignedPixn(width));

}else {

//PR:701779 20140612 xiaoqing.he modified start

//setVar(mExportVarIdx_dstlen, width - radiusX2 / 2);

setVar(mExportVarIdx_dstlen,width);

//PR:701779 20140612 xiaoqing.he modified end

setVar(mExportVarIdx_srclen,width);

}

setVar(mExportVarIdx_radiusX2,radiusX2);

bindAllocation(kernel,mExportVarIdx_gGuass);

bindAllocation(input,mExportVarIdx_gSrc);

bindAllocation(output,mExportVarIdx_gDst);

}

publicvoid process(Allocation eachline) {

forEach(mExportForEachIdx_root,eachline, null, null);

}

}

在该类的构造函数中主要工作就是为RS中的变量设置初始的数据,以供RS脚本进行数据的操作。







3.3java代码部分

java部分代码因为本身很长,所以在此并不打算全部copy过来,只挑选了其中核心的代码来讲解一下,整个调用的过程。

1oncreate()函数

privatevoid create(float sigma, int radiusX2) {

intusage = Allocation.USAGE_SCRIPT;

Type.Builderbuilder = new Type.Builder(mRS, Element.RGBA_8888(mRS));

Typetp_in = builder.setX(mWidth).setY(mHeight).create();

Typetp_hb = builder.setX(mWidth).setY(mHeight).create();

Typetp_vb = builder.setX(mWidth).setY(mHeight).create();


mAllocInput= Allocation.createTyped(mRS, tp_in, usage);

mAllocHBlur= Allocation.createTyped(mRS, tp_hb, usage);

mAllocVBlur= Allocation.createTyped(mRS, tp_vb, usage);


mAllocEachLineH= Allocation.createSized(mRS, Element.U16(mRS), mHeight, usage);

mAllocEachLineV= Allocation.createSized(mRS, Element.U16(mRS), mWidth, usage);

if(mAllocGuassKernel == null) {

mAllocGuassKernel =generateGaussKernel(sigma, radiusX2);

}


mRSCBlurH= new RSCBlurH(mRS, radiusX2, mWidth, mAllocGuassKernel, mAllocInput,mAllocHBlur);

mRSCBlurV= new RSCBlurV(mRS, radiusX2, mWidth, mHeight, mAllocGuassKernel,mAllocHBlur,

mAllocVBlur);


shortmax = (short) (mWidth > mHeight ? mWidth : mHeight);

short[]ids = new short[max];

for(short i = 0; i < max; i++)

ids[i]= i;

mAllocEachLineH.copyFrom(ids);

mAllocEachLineV.copyFrom(ids);

}

该函数是主要为调用RS函数做准备,申请内存空间以及对相应的内存空间赋值。



2generate()函数

publicBitmap generate(Bitmap input) throws Exception {

//image no scale

if(input.getWidth() == mWidth && input.getHeight() == mHeight){

mAllocInput.copyFrom(input);


mRSCBlurH.process(mAllocEachLineH);


mRSCBlurV.process(mAllocEachLineV);


BitmapretBmp = Bitmap.createBitmap(mWidth, mHeight,Bitmap.Config.ARGB_8888);


mAllocVBlur.copyTo(retBmp);


returnretBmp;

//image scale

}

else

...

}


该函数主要通过调用高斯模糊函数,并返回高斯模糊图像的函数。

4. RenderScript 图层的渲染

RenderScript的强大之处并不仅仅局限于与上面所说的对图像数据的处理。在实际应用中RenderScript常常被用作图层的3D或者2D的图形渲染。比如像现在Android中的动态壁纸大部分使用的方法都是使用RS来进行图形的渲染达到的。

在该章节中,将详细的探讨一下,RenderScriptgraphics编程。在作进一步的探讨之前,必须要先了解一下,在图形编程中几个最基本的概念。

着色器,这个概念很重要,否则在进一步的RenderScript的实际编程中可能会不知所云。着色器一般分为定点着色器、片段着色器和网格着色器。着色器(Shader)是用来实现图像渲染的用来代替固定渲染管线的可编辑程序。着色器一般也会有对应的着色语言(ShadingLanguageSpecification),着色器程序看起来确实和C语言非常相似。它们从入口点main函数开始,并且使用同样的字符集和注释约定,以及很多相同的处理命令。RenderScript虽然没有明确给出它的着色器标准,但是从官方提供的例子程序中,还是可以很容易确定,它遵循像OpengGL的着色语言规范。我们将在下面的例子中来见识一下RS的着色语言。过多的关于着色器的内容,就不再次赘述了,毕竟本文的主要内容不在这里。

在介绍实际的例子之前,有必要需要知道RenderScriptJava层的一个重要的类,RSSurfaceViewRSSurfaceView是一个特殊的类,主要是用作RenderScript绘制的View,也就是说RenderScript的所有绘图操作的结果都是通过该SurfaceView类来显示的。在该类中主要有两个函数需要用户在实际的代码编写中去重写,它们分别是surfaceChanged(SurfaceHolderholder, int format, int w, int h)surfaceCreated(SurfaceHolderholder)。从函数的字面上不难看出它们的作用,surfaceChanged主要是在SurfaceView发生改变时被调用的方法,surfaceCreated主要是在SurfaceView创建的时候被调用。

下面主要通过RenderScript的实际的例子来介绍一下,下面主要结合Google提供的一个RenderScript的例子来展开讲解一下RenderScript具体是如何工作的。首先来看一下实际的效果图如图4-1所示。

4-1Balls 的效果图

下面主要分两部份来讲解整个程序,分别涉及到Java层的代码以及rs脚本的内容。

首先,来分析一下Java层的代码都做那些工作。


publicvoid init(RenderScriptGL rs, Resources res, int width, int height) {

mRS= rs;

mRes= res;


ProgramFragmentFixedFunction.Builderpfb = new ProgramFragmentFixedFunction.Builder(rs);

pfb.setPointSpriteTexCoordinateReplacement(true);

pfb.setTexture(ProgramFragmentFixedFunction.Builder.EnvMode.MODULATE,

ProgramFragmentFixedFunction.Builder.Format.RGBA,0);

pfb.setVaryingColor(true);

mPFPoints= pfb.create();


pfb= new ProgramFragmentFixedFunction.Builder(rs);

pfb.setVaryingColor(true);

mPFLines= pfb.create();


android.util.Log.e("rs","Load texture");

mPFPoints.bindTexture(loadTexture(R.drawable.flares),0);


mPoints= new ScriptField_Point(mRS, PART_COUNT, Allocation.USAGE_SCRIPT);


Mesh.AllocationBuildersmb = new Mesh.AllocationBuilder(mRS);

smb.addVertexAllocation(mPoints.getAllocation());

smb.addIndexSetType(Mesh.Primitive.POINT);

MeshsmP = smb.create();


mPhysicsScript= new ScriptC_ball_physics(mRS, mRes, R.raw.ball_physics);


mScript= new ScriptC_balls(mRS, mRes, R.raw.balls);

mScript.set_partMesh(smP);

mScript.set_physics_script(mPhysicsScript);

mScript.bind_point(mPoints);

mScript.bind_balls1(newScriptField_Ball(mRS, PART_COUNT, Allocation.USAGE_SCRIPT));

mScript.bind_balls2(newScriptField_Ball(mRS, PART_COUNT, Allocation.USAGE_SCRIPT));


mScript.set_gPFLines(mPFLines);

mScript.set_gPFPoints(mPFPoints);

createProgramVertex();


mRS.bindProgramStore(BLEND_ADD_DEPTH_NONE(mRS));


mPhysicsScript.set_gMinPos(newFloat2(5, 5));

mPhysicsScript.set_gMaxPos(newFloat2(width - 5, height - 5));


mScript.invoke_initParts(width,height);


mRS.bindRootScript(mScript);

}

init函数是Java层代码中的核心代码了,主要的工作是建立Mesh层,建立mesh层的主要作用,就像建立一个画布一样,RS的所有渲染结果都是通过绘制到mesh层上然后显示到屏幕上的,所以建立mesh层是十分必要的。建立顶点着色器,和片段着色器,在RS做图形渲染时,这两个着色器是必须的,其实RS的做法也是和主流的opengl3D方面的渲染是一致的。

定点着色器程序:

Stringt = "varying vec4 varColor;\n" +

"voidmain() {\n" +

" vec4 pos = vec4(0.0, 0.0, 0.0, 1.0);\n" +

" pos.xy = ATTRIB_position;\n" +

" gl_Position = UNI_MVP * pos;\n" +

" varColor = vec4(1.0, 1.0, 1.0, 1.0);\n" +

" gl_PointSize = ATTRIB_size;\n" +

"}\n";

这个地方这个字符串就是,顶点着色器的代码,它是的作用主要是告诉RS的底层解释器如何来操作RS脚本的定点数据。这段代码是定点着色器的核心程序。它的主要作用就是向RS底层传递定点的位置、颜色以及绘制的点的大小。

片段着色器,在这里片段并没有像顶点着色器的着色程序,这里只是简单的将一个纹理图片和片段着色器进行的绑定,就是说对于顶点的绘制都采用这个纹理的图片的样式来进行绘制。该纹理图像是一个圆点的图像,这就是为什么最终绘制在屏幕上的点无论是大的还是小的外观都是圆形,如果去掉这个纹理最终的绘制效果如图4-2所示。

4-2没有使用纹理的结果图

当然对于这个程序来说完全可以不用纹理,而是简单的绘制圆就好了。但是在实际的使用过程中纹理的使用还是至关重要的。它可以简化很多的操作同时也增添了渲染的图像的真实感,在实际的3D场景的渲染中,纹理的使用是很有效的。

Java方面的代码介绍差不多了,先面将视角转移到主角,图像渲染正真的操纵者。

RS部分代码:

typedefstruct __attribute__((packed, aligned(4))) Ball {

float2delta;

float2position;

//float3color;

float size;

//int arcID;

//float arcStr;

}Ball_t;

Ball_t*balls;



typedefstruct BallControl {

uint32_t dimX;

rs_allocation ain;

rs_allocation aout;

float dt;

}BallControl_t;


rs_program_fragmentgPFPoints;

rs_program_fragmentgPFLines;

rs_meshpartMesh;


typedefstruct __attribute__((packed, aligned(4))) Point {

float2 position;

float size;

}Point_t;

Point_t*point;


typedefstruct VpConsts {

rs_matrix4x4 MVP;

}VpConsts_t;

VpConsts_t*vpConstants;


rs_scriptphysics_script;


Ball_t*balls1;

Ball_t*balls2;


staticint frame = 0;


voidinitParts(int w, int h)

{

uint32_t dimX = rsAllocationGetDimX(rsGetAllocation(balls1));


for (uint32_t ct=0; ct < dimX; ct++) {

balls1[ct].position.x = rsRand(0.f, (float)w);

balls1[ct].position.y = rsRand(0.f, (float)h);

balls1[ct].delta.x = 0.f;

balls1[ct].delta.y = 0.f;

balls1[ct].size = 1.f;


float r = rsRand(100.f);

if(r > 90.f) {

balls1[ct].size += pow(10.f,rsRand(0.f, 2.f)) * 0.07;

}

}

}



introot() {

rsgClearColor(0.f,0.f, 0.f, 1.f);


BallControl_tbc;

Ball_t*bout;


if(frame & 1) {

bc.ain= rsGetAllocation(balls2);

bc.aout= rsGetAllocation(balls1);

bout= balls2;

}else {

bc.ain= rsGetAllocation(balls1);

bc.aout= rsGetAllocation(balls2);

bout= balls1;

}


bc.dimX= rsAllocationGetDimX(bc.ain);

bc.dt= 1.f / 30.f;


rsForEach(physics_script,bc.ain, bc.aout, &bc, sizeof(bc));


for(uint32_t ct=0; ct < bc.dimX; ct++) {

point[ct].position= bout[ct].position;

point[ct].size= 6.f /*+ bout[ct].color.g * 6.f*/ * bout[ct].size;

}


frame++;

//rsgBindProgramFragment(gPFPoints);

rsgDrawMesh(partMesh);

return1;

}


RS部分的代码主要都在了,我们来分析一下它执行的整个过程。首先有一个函数initParts它主要功能是给定义的900个点进行初始化,随机的给予它们位置和大小信息。下面root函数是我们要讨论的重点,rs脚本定义的root函数是默认肯定会被调用的,这个是rs的内部调用的机制,而且如果他的返回值不是void而是一个int型的整数的话,那么他会被不停的调用调用周期就是返回值对应的毫秒数。这个地方就是每1毫秒就会执行一次。每一次调用都会physics_script函数来改变每一个点的信息,所以我们会看到,屏幕上的点是不断的移动的。







发布了35 篇原创文章 · 获赞 9 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/ZHOUYONGXYZ/article/details/79066678
今日推荐