***本篇文章已授權(quán)微信公眾號 guolin_blog (郭霖)獨家發(fā)布 **
前言
拖了好久了嚎,終于到了這系列的主要部分——在 Android 中使用 RenderScript 實現(xiàn) LowPoly 的詳細(xì)過程。示例下面 Github 中捶朵,有興趣的同學(xué)可以參考酗昼,喜歡的可以 star 一下刁标,謝謝博助。
示例
*Gif 圖片加載有點慢,加載完后顯示的點擊再次渲染的速度還是挺快的
使用
MainActivity.java
...
{
...
LowPoly.createLowPoly(this, bitmapOriginal, accuracy, RENDERED_FLAG);
}
static Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case RENDERED_FLAG:
ivOut.setImageBitmap(LowPoly.bmpRendered);
Log.e(TAG, "Render FINISH in==" + (System.currentTimeMillis() - time) + " ms");
System.gc();
break;
}
}
};
...
由于 LowPoly 處理結(jié)果不在 UI 線程中猪瞬,所以使用 Handler 設(shè)置渲染后的 Bitmap 到 ImageView 中憎瘸。
lowpoly.rs
#include "pragma.rsh"
#define STATUS_GRAY 1
#define STATUS_SOBEL 2
int status = 0;
const static float3 gMonoMult = {0.299f, 0.587f, 0.114f};
rs_script gScript;
rs_allocation gOriginal;
rs_allocation gGrayed;
rs_allocation gSobel;
int width,height;
int rand;
int accuracy = 10;
int2 points[15000];
int count =0;
static void setColor(int px,int py){
float4 l = {1.0f,1.0f,1.0f,1.0f};
rsSetElementAt(gSobel,&l, px, py);
}
void root(const uchar4 *v_in, uchar4 *v_out, uint32_t x, uint32_t y){
if(status == STATUS_GRAY){
float4 f4 = rsUnpackColor8888(*(v_in));
float3 mono = dot(f4.rgb, gMonoMult);
*v_out = rsPackColorTo8888(mono);
}else if(status == STATUS_SOBEL){
float4 lt = rsUnpackColor8888(*(v_in-width-1));
float4 ct = rsUnpackColor8888(*(v_in-width));
float4 rt = rsUnpackColor8888(*(v_in-width+1));
float4 l = rsUnpackColor8888(*(v_in-1));
float4 c = rsUnpackColor8888(*(v_in));
float4 r = rsUnpackColor8888(*(v_in+1));
float4 lb = rsUnpackColor8888(*(v_in+width-1));
float4 cb = rsUnpackColor8888(*(v_in+width));
float4 rb = rsUnpackColor8888(*(v_in+width+1));
float gx = lt.x*(-1)+l.x*(-2)+lb.x*(-1)+
rt.x*(1)+r.x*(2)+rb.x*(1);
float gy = lt.x*(-1)+ct.x*(-2)+rt.x*(-1)+
lb.x*(1)+cb.x*(2)+rb.x*(1);
float G = sqrt(gx*gx+gy*gy);
rand = rsRand(1.0f) * 10 * accuracy;
if(G > 0.1f && rand == 1){
setColor(x,y);
int2 i2 = {x,y};
points[count] = i2;
count++;
}else{
float3 black = { 0.0f,0.0f,0.0f};
*v_out = rsPackColorTo8888(black);
}
}
}
void process(int stat){
status = stat;
rsDebug("process==",status);
if(status == STATUS_GRAY){
rsForEach(gScript,gOriginal,gGrayed);
rsDebug("process GRAY finish==",stat);
rsSendToClient(101,&count,101);
}else if(status == STATUS_SOBEL){
count=0;
rsForEach(gScript,gGrayed,gSobel);
rsDebug("process SOBEL finish==",stat);
rsSendToClient(102,&count,102);
}
}
void send_points(){
// to client
int group = (count-1)/625+1;
rsDebug("points group==",group);
rsDebug("points size==",count);
rsSendToClient(0,&count,group);
for(int i=1;i<=group;i++){
int index = 625 *(i-1);
rsSendToClient(i,&(points[index]),4999);
}
}
在 rs 腳本中,實現(xiàn)的方法主要為 root
撑螺、process
含思、 send_points
三個:
root
中,分兩步分別處理 灰度化 和 查找邊緣同時采樣甘晤,第一篇提到的含潘,灰度處理并不是必要步驟,所以可以省略线婚。但是出于兩點原因遏弱,依舊保留了這個步驟:1.灰度化處理在 rs 耗費的時間經(jīng)過測試,一般只占 幾ms ~ 10+ ms塞弊,在不是很苛刻的要求下還是可以接受的漱逸;2.圖片的分步處理是比較普遍的場景,這樣可以熟悉編寫復(fù)雜 rs 腳本的過程游沿,當(dāng)前這些步驟也可以通過編寫不同的 rs 文件實現(xiàn)饰抒。
這個方法的第一個步驟灰度化不做詳細(xì)介紹了,代碼比較直觀诀黍。
第二個步驟處理了邊緣查找和采樣:
通過使用 Sobel 算子袋坑, 計算 {x ,y}點的橫向與縱向的亮度差異眯勾;同時枣宫,在
if(G > 0.1f && rand == 1)
一行,設(shè)置 0.1f 為進(jìn)入總樣本集的臨界值吃环,當(dāng)然也可以根據(jù)需要在 java 層設(shè)置這個值也颤,這個值越小,總樣本集的元素越多郁轻;rand = rsRand(1.0f) * 10 * accuracy;
這一行翅娶,生成一個在 [0,10 * accuracy) 中隨機(jī)整數(shù),accuracy
值越低故觅,采樣精度越高厂庇,當(dāng) accuracy =1 時渠啊,采樣率為 1/ (10 * 1) 输吏,即期望上,邊緣上每十個亮度值大于 0.1f 的點就會被選為構(gòu)建三角形的一個元素點替蛉。因為采樣方法是完全隨機(jī)的贯溅,所以最后的效果有時出現(xiàn)一些不理想的三角形,因此這個步驟的調(diào)整對輸出結(jié)果優(yōu)化還有很大的提升空間躲查,不過在時間上必然有一定的開銷它浅,這里就不作詳細(xì)討論。
process
這個方法作為 java 層調(diào)用入口镣煮,傳入處理的步驟姐霍,當(dāng)前步驟處理結(jié)束后通過 rsSendToClient
方法給 java 層發(fā)送通知,類似 Handler 的 sendMessge() 方法典唇,傳入三個參數(shù):mID镊折、pointer、dataLength——消息的 ID介衔,發(fā)送數(shù)組數(shù)據(jù)的指針地址恨胚,數(shù)據(jù)的長度。在這里調(diào)用炎咖,除了 mID 在 java 會被用到赃泡,另外兩個參數(shù)并沒有什么意義。
send_points
當(dāng) java 層收到 process
方法中 SOBEL 處理結(jié)束的消息后會被調(diào)用乘盼,這個方法將 root
第二個步驟選取的采樣點發(fā)送到 java 層升熊。有一點值得注意的是,rsSendToClient 這個方法第二參數(shù)的數(shù)組的長度上限為 1250绸栅,所以當(dāng)采樣點數(shù)量大于 625(一個點有兩個數(shù)值組成)時级野,須要分批發(fā)送數(shù)據(jù)。rsSendToClient(0,&count,group);
以 0 為信息 ID阴幌,把采樣點數(shù)勺阐,批數(shù)(包括當(dāng)前信息批次),發(fā)送給 java 層矛双,rsSendToClient(i,&(points[index]),4999);
分批次把采樣點數(shù)據(jù)發(fā)給 java 層渊抽,第三個參數(shù)沒有意義。
以上就是實現(xiàn) LowPoly 效果 rs 腳本的所有代碼议忽,并不復(fù)雜懒闷,然后是 java 的調(diào)用。
LowPoly.java
final static String TAG = "==LowPoly==";
private static Allocation allocationOriginal;
private static Allocation allocationGrayed;
private static Allocation allocationSobel;
private static RenderScript mRs;
private static ScriptC_lowpoly scriptLowPoly;
private static int width, height;
private static Bitmap mBitmapIn;
public static Bitmap bmpRendered;
private static int pointCount;
private static int groupCount = 100;
private static Int2[] points = new Int2[10000];
private static List<Int2> pointz = new ArrayList<Int2>();
private static int RENDERED_FLAG;
public static void createLowPoly(Context context, Bitmap bitmapIn, int accuracy, int flag) {
mBitmapIn = bitmapIn;
RENDERED_FLAG = flag;
Bitmap bitmapOut = Bitmap.createBitmap(bitmapIn.getWidth(), bitmapIn.getHeight(),
bitmapIn.getConfig());
width = bitmapIn.getWidth();
height = bitmapIn.getHeight();
Log.e(TAG, "Width==" + width + "==Height==" + height + "==accuracy==" + accuracy);
createLowPolyScript(context, accuracy, bitmapIn, bitmapOut);
Log.e(TAG, "Start GRAYED");
scriptLowPoly.invoke_process(1);
}
private static void createLowPolyScript(Context context, int accuracy, final Bitmap bitmapIn, final Bitmap bitmapOut) {
mRs = RenderScript.create(context);
allocationOriginal = Allocation.createFromBitmap(mRs, bitmapIn,
Allocation.MipmapControl.MIPMAP_NONE,
Allocation.USAGE_SCRIPT);
allocationGrayed = Allocation.createFromBitmap(mRs, bitmapOut,
Allocation.MipmapControl.MIPMAP_NONE,
Allocation.USAGE_SCRIPT);
allocationSobel = Allocation.createFromBitmap(mRs, bitmapOut,
Allocation.MipmapControl.MIPMAP_NONE,
Allocation.USAGE_SCRIPT);
scriptLowPoly = new ScriptC_lowpoly(mRs);
scriptLowPoly.set_gScript(scriptLowPoly);
scriptLowPoly.set_gOriginal(allocationOriginal);
scriptLowPoly.set_gGrayed(allocationGrayed);
scriptLowPoly.set_gSobel(allocationSobel);
scriptLowPoly.set_accuracy(accuracy);
scriptLowPoly.set_width(width);
scriptLowPoly.set_height(height);
mRs.setMessageHandler(new RenderScript.RSMessageHandler() {
@Override
public void run() {
super.run();
if (mID == 101) {
Log.e(TAG, "GRAYED finish");
// allocationGrayed.copyTo(bitmapOut);
Log.e(TAG, "Start SOBEL");
scriptLowPoly.invoke_process(2);
return;
}
if (mID == 102) {
Log.e(TAG, "SOBEL finish");
// allocationSobel.copyTo(bitmapOut);
scriptLowPoly.invoke_send_points();
points = new Int2[10000];
pointz.clear();
return;
}
if (mID == 0) {
pointCount = mData[0];
groupCount = mLength;
Log.e(TAG, "Receive points==" + pointCount + "==by group==" + groupCount);
} else if (mID == groupCount) {
for (int i = 0; i < mData.length; i += 2) {
points[i / 2 + 625 * (mID - 1)] = new Int2(mData[i], mData[i + 1]);
}
for (int i = 0; i < pointCount; i++) {
Int2 int2 = points[i];
pointz.add(int2);
}
for (int i = 0; i < 200; i++) {
Int2 int2 = new Int2((int) (Math.random() * width), (int) (Math.random() * height));
pointz.add(int2);
}
pointz.add(new Int2(0, 0));
pointz.add(new Int2(0, height));
pointz.add(new Int2(width, 0));
pointz.add(new Int2(width, height));
Log.e(TAG, "Points size==" + pointz.size() + "");
List<Integer> tris = Delaunay.triangulate(pointz);
Log.e(TAG, "Triangle size== " + tris.size() / 3 + "");
bmpRendered = Bitmap.createBitmap((int) (width), (int) (height), Bitmap.Config.ARGB_8888);
long t = System.currentTimeMillis();
Canvas canvas = new Canvas(bmpRendered);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
float x1, x2, x3, y1, y2, y3, cx, cy;
for (int i = 0; i < tris.size(); i += 3) {
x1 = pointz.get(tris.get(i)).x;
x2 = pointz.get(tris.get(i + 1)).x;
x3 = pointz.get(tris.get(i + 2)).x;
y1 = pointz.get(tris.get(i)).y;
y2 = pointz.get(tris.get(i + 1)).y;
y3 = pointz.get(tris.get(i + 2)).y;
cx = (x1 + x2 + x3) / 3;
cy = (y1 + y2 + y3) / 3;
Path path = new Path();
path.moveTo(x1, y1);
path.lineTo(x2, y2);
path.lineTo(x3, y3);
path.close();
paint.setColor(mBitmapIn.getPixel((int) cx, (int) cy));
canvas.drawPath(path, paint);
}
Log.e(TAG, "Canvas cost === " + (System.currentTimeMillis() - t) + " ms");
MainActivity.mHandler.sendEmptyMessageAtTime(RENDERED_FLAG, 0);
System.gc();
} else {
Log.e(TAG, "Receive group==" + mID);
for (int i = 0; i < mData.length; i += 2) {
points[i / 2 + 625 * (mID - 1)] = new Int2(mData[i], mData[i + 1]);
}
}
}
});
}
java 層與 rs 層的交互從 scriptLowPoly.invoke_process(1);
開始,在 RSMessageHandler
中處理 rs 發(fā)來的數(shù)據(jù)消息愤估,交互步驟為:
java 層調(diào)用 rs 層 process(STATUS_GRAY)
處理灰度化帮辟;
rs 層 process(STATUS_GRAY)
處理結(jié)束通知 java 層;
java 接到通知后玩焰,調(diào)用 rs 層 process(STATUS_SOBEL)
處理查找邊緣及采樣由驹;
rs 層 process(STATUS_SOBEL)
處理結(jié)束通知 java 層;
java 層接到采樣結(jié)束通知后昔园,調(diào)用 rs 層 send_points
蔓榄,把采樣數(shù)據(jù)分批發(fā)到 java 層;
最后在 java 層完成 Delaunay 三角化默刚,與繪圖的過程甥郑,RSMessageHandler 不在 UI 線程中,所以使用 Handler 通知 UI 線程設(shè)置處理后的 Bitmap荤西。
以上就是澜搅,實現(xiàn) LowPoly 效果的完整過程。
有一點值得注意的是邪锌,為什么不在 java 層直接調(diào)用 forEach_root
方法處理各個步驟呢勉躺?
因為,在 java 層直接調(diào)用該方法結(jié)束的時間是不能確定的秃流,但是在 rs 中 process
中赂蕴,rsForEach
執(zhí)行前后的 rsDebug 與 java 的 log 信息都是按順序輸出的,是可控的調(diào)用舶胀。
根據(jù)一次處理的 log 信息概说,可以看到,處理一個 600 * 600 的圖片嚣伐,采樣率為 1/20 糖赔,總耗時為 479 ms,其中 GRAYED 步驟耗時 6ms 轩端,SOBEL 步驟耗時 18ms放典, Canvas 繪圖耗時 250 ms,其余時間用于數(shù)據(jù)傳輸與處理基茵。
至此奋构,關(guān)于使用 RenderScript 實現(xiàn) LowPoly 的介紹就到這里了,有什么疑問或者文章有不對的地方請留言拱层,感謝看到這里的同學(xué)弥臼。
最后再附上 Github 地址:https://github.com/ReikyZ/LowPoly