Android RenderScript 实现 LowPoly 效果(二)

***本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 **

示例

Github-LowPoly

基础

配置

app.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 24
    buildToolsVersion "24.0.0"

    defaultConfig {
        applicationId "com.reikyz.renderscript"
        minSdkVersion 14
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"

        
        
    }
}

RenderScript 在 Android 3.0(API 11)时首次被引入,包名为 android.renderscript;之后又添加了 Support Library android.support.v8.renderscript,最低可以兼容 Android 2.3 (API 9),SDK Tools Version 须要 22.2 及以上, Build Tools Version 须要 18.1 及以上。在应用的 .gradle 配置中需要加上蓝色的两行,以配置支持库。支持库与早先版本的 api 由较大的不同,所以下文都是以使用 Support Library 为基础。

文件目录
Android RenderScript 实现 LowPoly 效果(二)_第1张图片
files

在 Android Studio 的 Project 视图中,在 main 目录下创建 rs 目录,用来存放 rs 脚本,在编写好脚本,经过编译后,会在 build 文件目录下分别生成 .bc 格式与 .java 文件,其中 .java 文件就是 .java 让我们可以对 rs 脚本进行反射调用。

语言

RenderScript 以 C99 作为开发语言,不支持调用 NDK 或 C 标准库的 API,但是提供了能够基本满足开发需要的 RenderScript API,包涵了丰富的基本数据类型,例如向量,矩阵,各种数学函数,具体可以参考官方文档。

使用流程

编写脚本

grayed.rs

#include "pragma.rsh"

rs_allocation gIn;// 输入图像的 allocation 对象
rs_allocation gOut;// 输出图像的 allocation 对象
rs_script gScript;// RenderScript 对象

// 一种常用的加权平均灰度化的权重值
const static float3 gMonoMult = {0.299f, 0.587f, 0.114f};

// rs 的初始化函数,只会被调用一次,可以不实现
void init(){
    rsDebug("===init ======",0);
}

// rs 的核心函数,每一个像素都会执行这个函数
void root(const uchar4 *v_in, uchar4 *v_out) {
    rsDebug("===root wiz 2 params",0);// debug info
    float4 f4 = rsUnpackColor8888(*(v_in));// 获得 输入allocation 中当前点的 RGBA 值
    float3 mono = dot(f4.rgb, gMonoMult);// 灰度运算
    *v_out = rsPackColorTo8888(mono);// 将灰度值写入 输入allocation 的当前对应位置点
}

pragma.rsh

#pragma version(1)
#pragma rs java_package_name(com.reikyz.renderscript)

对于 grayed.rs 这个脚本,首先需要添加两行包信息,这两行信息放在了 pragme.rsh 文件中引入,在有较多的脚本时,也可以把一些自定义的结构体或者函数,通过这样的方式引入,方便管理与复用。

在添加了包信息之后,我们就可以使用 rs 提供的一系列数据类型和函数,grayed.rs 这个脚本的效果是将一个图像灰度化。

调用

MainActivity.java


    private RenderScript mRs;
    private ScriptC_grayed scriptGrayed;
    private Allocation mInAllocation;
    private Allocation mOutAllocation;

    private Bitmap mBitmapIn;
    private Bitmap mBitmapOut;

    ImageView ivIn, ivOut;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mBitmapIn = zoomImage(loadBitmap(R.mipmap.data), 720, 600);
        mBitmapOut = Bitmap.createBitmap(mBitmapIn.getWidth(), mBitmapIn.getHeight(),
                mBitmapIn.getConfig());

        ivIn = (ImageView) findViewById(R.id.iv_in);
        ivOut = (ImageView) findViewById(R.id.iv_out);

        ivIn.setImageBitmap(mBitmapIn);
        ivOut.setImageBitmap(mBitmapOut);

        createRS(mBitmapOut);
    }

    private void createRS(Bitmap bmpOut) {
        mRs = RenderScript.create(this);

        mInAllocation = Allocation.createFromBitmap(mRs, mBitmapIn,
                Allocation.MipmapControl.MIPMAP_NONE,
                Allocation.USAGE_SCRIPT);
        mOutAllocation = Allocation.createFromBitmap(mRs, bmpOut,
                Allocation.MipmapControl.MIPMAP_NONE,
                Allocation.USAGE_SCRIPT);

        Log.e(TAG, "===Sccrit new===");
        scriptGrayed = new ScriptC_grayed(mRs);
        Log.e(TAG, "===Sccrit invoke root===");
        scriptGrayed.forEach_root(mInAllocation, mOutAllocation);
        Log.e(TAG, "===Sccrit root end===");

        mOutAllocation.copyTo(bmpOut);
    }

在编译后,我们就可以通过自动生成的 ScriptC_grayed.java 反射类对 rs 进行初始化和调用,在 createRS 方法中,
首先,获得一个 RenderScript 对象;
根据 bitmap 构建输入、输出的 Allocation 对象,对应 rs 脚本中的两个 rs_allocation 对象,这两个对象的尺寸必须相同;
初始化 Grayed 脚本,至此都是一些准备工作;
调用 scriptGrayed.forEach_root(mInAllocation, mOutAllocation) 将分配的 allocation 传入, rs 执行 root 方法;
最后将生成的 输出allocation 中的图像写入 bitmap 。

Android RenderScript 实现 LowPoly 效果(二)_第2张图片
Grayed

Android RenderScript 实现 LowPoly 效果(二)_第3张图片
log

在 log 中可以注意到,在调用时 scriptGrayed.forEach_root 这个函数应该早于 “===Script root end===”这行 log 信息,实际却不是这样,因此,可以看出 rs 脚本的 root 方法并不在主线程中。

另外,java 中调用脚本的方式 scriptGrayed.forEach_root,为什么是 each_root 呢?
因为,root 还有一个重载方法 root(const uchar4 *v_in, uchar4 *v_out, uint32_t x, uint32_t y)

grayed.rs

void root(const uchar4 *v_in, uchar4 *v_out, uint32_t x, uint32_t y){
    int x_c = (x/5) * 5 +5/2;
    int y_c = (y/20) * 20 +20/2;
    float4 pixel_center = rsGetElementAt_float4(gIn, x_c,y_c);
    rsSetElementAt_float4(gOut,pixel_center, x, y);
}

在 grayed.rs 脚本中重载这个方法,需注意的是一个脚本只能重载一个 root 方法,否则编译时就会报错。

MainActivity.java

scriptGrayed = new ScriptC_grayed(mRs);
scriptGrayed.set_gIn(mInAllocation);
scriptGrayed.set_gOut(mOutAllocation);
scriptGrayed.forEach_root(mInAllocation, mOutAllocation);

mOutAllocation.copyTo(bmpOut);

由于,重载包含坐标的 root 方法对 rs 脚本中的输入、输出 rs_allocation 进行操作,如果在 java 层不进行 set 操作的话,程序运行后会直接崩溃,且不会输出崩溃原因,所以编写 rs 脚本时,须要格外注意代码逻辑。

Android RenderScript 实现 LowPoly 效果(二)_第4张图片
mosaic

如图所示,重载的 root 方法很简单地实现了一个马赛克的图像效果。

这篇就到这里,下篇正式开始介绍 LowPoly 效果的具体实现。

示例

Github-LowPoly

你可能感兴趣的:(Android RenderScript 实现 LowPoly 效果(二))