Android RenderScript 實現(xiàn) LowPoly 效果(三)

***本篇文章已授權(quán)微信公眾號 guolin_blog (郭霖)獨家發(fā)布 **

前言

拖了好久了嚎,終于到了這系列的主要部分——在 Android 中使用 RenderScript 實現(xiàn) LowPoly 的詳細(xì)過程。示例下面 Github 中捶朵,有興趣的同學(xué)可以參考酗昼,喜歡的可以 star 一下刁标,謝謝博助。

示例

Github-LowPoly

demo_with_different_accuracy

*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 operator

通過使用 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)用舶胀。

log

根據(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市根灯,隨后出現(xiàn)的幾起案子径缅,更是在濱河造成了極大的恐慌掺栅,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纳猪,死亡現(xiàn)場離奇詭異氧卧,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)氏堤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進(jìn)店門沙绝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人丽猬,你說我怎么就攤上這事宿饱。” “怎么了脚祟?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長强饮。 經(jīng)常有香客問我由桌,道長,這世上最難降的妖魔是什么邮丰? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任行您,我火速辦了婚禮,結(jié)果婚禮上剪廉,老公的妹妹穿的比我還像新娘娃循。我一直安慰自己,他們只是感情好斗蒋,可當(dāng)我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布捌斧。 她就那樣靜靜地躺著,像睡著了一般泉沾。 火紅的嫁衣襯著肌膚如雪捞蚂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天跷究,我揣著相機(jī)與錄音姓迅,去河邊找鬼。 笑死俊马,一個胖子當(dāng)著我的面吹牛丁存,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播柴我,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼解寝,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了屯换?” 一聲冷哼從身側(cè)響起编丘,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤与学,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后嘉抓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體索守,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年抑片,在試婚紗的時候發(fā)現(xiàn)自己被綠了卵佛。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡敞斋,死狀恐怖截汪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情植捎,我是刑警寧澤衙解,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站焰枢,受9級特大地震影響蚓峦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜济锄,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一暑椰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧荐绝,春花似錦一汽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至委造,卻和暖如春戳鹅,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背昏兆。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工枫虏, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人爬虱。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓隶债,卻偏偏與公主長得像,于是被迫代替她去往敵國和親跑筝。 傳聞我的和親對象是個殘疾皇子死讹,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法曲梗,內(nèi)部類的語法赞警,繼承相關(guān)的語法妓忍,異常的語法,線程的語...
    子非魚_t_閱讀 31,598評論 18 399
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理愧旦,服務(wù)發(fā)現(xiàn)世剖,斷路器,智...
    卡卡羅2017閱讀 134,629評論 18 139
  • 一. Java基礎(chǔ)部分.................................................
    wy_sure閱讀 3,805評論 0 11
  • ***本篇文章已授權(quán)微信公眾號 guolin_blog (郭霖)獨家發(fā)布 ** 前言 之前在知乎看到一篇關(guān)于 Lo...
    阪有桑閱讀 1,780評論 2 5
  • 一個徹底誠實的人是從不面對選擇的笤虫,那條路永遠(yuǎn)會清楚地呈現(xiàn)在你面前旁瘫,這和你的憧憬無關(guān),就像你是一棵蘋果樹琼蚯,你憧憬結(jié)出...
    田裡和樹閱讀 418評論 0 1