Android RenderScript实现LowPoly效果

来源:互联网 发布:数据图表 编辑:程序博客网 时间:2024/06/02 01:06

今日科技快讯

近日,利用银行新规的诈骗手法已出现,大家要注意了!根据银行新规,在ATM机上转账时,除同行同户名的卡,其他均在24小时后才能到账,且24小时内可撤销转账!不法分子以“先转账、再取现”骗取受害者信任,在拿到现金后,便会前往柜台撤销转账!

有网友爆料称,前天一个陌生电话打来订货,不说要什么就说配5000块的货,发了ATM转账凭证过来,然后就不停催发货,打银行客服查询确实有这交易,不过还是多了心眼没发货,到今天了24小时钱也没到账,银行查询是该笔交易已撤销。

作者简介

新的一周开始了,又到了跟大家见面的时候!本篇是 reikyZ 的第二篇投稿,本文包含基本概念的讲解以及RenderScript的基本使用。如果看了本文对这个知识感兴趣的朋友可以访问作者博客,进行更深入的了解。

reikyZ 的博客地址:

http://www.jianshu.com/users/5bf423bcdb88

前言

之前在知乎看到一篇关于 LowPoly  js 实现的贴:

https://www.zhihu.com/question/29856775

觉得效果很棒,就打算在 Android 上实现看看。本来以为一两天就可以搞定,没想到不知不觉耗了一周多,踏坑不少。鉴于内容可能会比较多,所以暂时打算分篇幅介绍实现过程。

LowPoly (低多边形) 这种风格在前两年十分风靡,比较有名的《纪念碑谷》就是采用这种美术风格的游戏。

纪念碑谷

效果

LowPolyAndroid

https://github.com/zzhoujay/LowPolyAndroid

java cost time

这个是由 纯 java 实现的版本,在查找资料时找到的,应该是国内某人写的。用 Nexus5 测试,处理尺寸为 900 * 900 的 bitmap 图片耗时基本在 60~90 s 左右,可见效率确实比较不能忍受。从 Log 的计时器看,在处理两千不到的取样点的情况下,三角化和绘图耗时都在 1s 以内,所以估计主要耗时应该在 Sobel 边缘处理和点列采样的过程中,没有验证,有心的同学可以试试看。需要说明的是,我的实现使用了这位同学的 Delaunay 算法和 canvas 部分的代码(表示感谢!),所以在取样点差不多时,我的效果应该和这个差不多。

LowPoly

https://github.com/CoXier/LowPoly

jni cost time

这个是国外某位使用 jni 实现的版本,是在着手写该篇时才找到的,看到其介绍由 jni 实现,想必肯定会比 java 快,担心如果比 rs 实现快很多,也没必要写出来了。根据测试效果,明显比 java 实现快很多,处理同样的图片,用了7秒多,比 java 快了十倍左右。从效果图上来看,其边缘更加清晰,尖锐三角形也很多,风格与 java 实现的稍有不同,不过这些应该是因为使用不同的三角化算法导致,对耗时影响应该不大。


rs process(微信大小限制,图片压缩了)


rs cost time

最后是使用 RenderScript 实现的,为了方便描述实现过程 Demo 分步骤展示了主要的过程。处理同样的图片,通过计时器可以看到,总耗时大概在 2s以内,效率应该说还是比较能够让人接受的。正如前文提到的,使用了前文同学的 Delaunay 算法和 canvas 部分代码,所以风格应该差不多。不同的是,用 rs 处理了灰度、边缘化和采样的过程,大大节省了时间。

实现原理

根据知乎那个答案,这种效果主要有那么几个步骤:

 1. 首先将原图转为灰度图(不是必须);

 2. 使用灰度图(或原图)查找边缘,获得边缘线图;

 3. 从边缘线上采样,采样率决定后期三角的数量与图片精度;

 4. 使用上步骤得到的采样点列,生成三角化序列;

5. 遍历三角形,使用原图得到的色值填充三角形。

其中,在边缘检测上一般采用 Sobel 算法,即通过 sobel 算子计算某一点的梯度,可以简单理解为某一点与周围点灰度差异度;然后,在三角化处理上,一般采用 Delaunay 算法。

在实现方面,首先 jni 并不是一定会比 java 快的,使用 jni 一般是为了某些特定场景,其中图形处理所需要的大量数学运算就是其一,在 LowPoly 的代码中:

LowPoly.java


可见,除了取色、上色两个步骤,其他步骤都通过调用 native 的 getTriangles 方法处理,其中应该就是负责图形处理的大量运算。

对于 RenderScript,Google 这样描述:

RenderScript

https://developer.android.com/guide/topics/renderscript/compute.html

RenderScript is a framework for running computationally intensive tasks at high performance on Android. RenderScript is primarily oriented for use with data-parallel computation, although serial workloads can benefit as well. The RenderScript runtime parallelizes work across processors available on a device, such as multi-core CPUs and GPUs. This allows you to focus on expressing algorithms rather than scheduling work. RenderScript is especially useful for applications performing image processing, computational photography, or computer vision.

介绍了 RenderScript 主要基于多核 CPU 或者 GPU,主要是为了处理图形运算。早些时候,在手机处理器和图形处理器的性能普遍不高的情况下, rs 似乎并不太引人注意,从网络上少得可怜的资料就可以看出。不过,时至今日,各大手机厂商在硬件的堆砌上似乎一点也没有节制,以及 VR、 AR 等视觉技术的兴起,相信 rs 技术会有更多应用场景。

示例

Github-LowPoly

https://github.com/ReikyZ/LowPoly

基础
配置

app.gradle


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

语言

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

使用流程

编写脚本

grayed.rs


pragma.rsh

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

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

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

调用

MainActivity.java


在编译后,我们就可以通过自动生成的 ScriptC_grayed.java  反射类对 rs 进行初始化和调用。

在 createRS 方法中,首先,获得一个 RenderScript 对象;

根据 bitmap 构建输入、输出的 Allocation 对象,对应 rs 脚本中的两个rs_allocation 对象,这两个对象的尺寸必须相同;

初始化 Grayed 脚本,至此都是一些准备工作;

调用 scriptGrayed.forEach_root(mInAllocation, mOutAllocation) 将分配的 allocation 传入, rs 执行 root 方法;

最后将生成的 输出 allocation 中的图像写入 bitmap 。

在 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


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

MainActivity.java


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


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

更多

每天学习累了,看些搞笑的段子放松一下吧。关注最具娱乐精神的公众号,每天都有好心情。

如果你有好的技术文章想和大家分享,欢迎向我的公众号投稿,投稿具体细节请在公众号主页点击“投稿”菜单查看。

欢迎长按下图 -> 识别图中二维码或者扫一扫关注我的公众号:

阅读全文
0 0