天天看點

使用RenderScript實作高斯模糊(毛玻璃/磨砂)效果

前言

逛instagram的時候,偶然發現,instagram的對話框設計的很有意思,如下圖:  

使用RenderScript實作高斯模糊(毛玻璃/磨砂)效果

它的dialog的背景竟然是毛玻璃效果的,在我看來真漂亮,恩,對話框和迪麗熱巴都漂亮。看到這麼好的效果,當然就要開始搞事情了,自己動手實作差不多的效果。最終的實作效果如下圖:

使用RenderScript實作高斯模糊(毛玻璃/磨砂)效果
使用RenderScript實作高斯模糊(毛玻璃/磨砂)效果

分别實作了對話框背景的虛化和手動調節虛化程度。

實作方法對比

最開始想要實作毛玻璃效果時,我是一臉懵逼的,不知道如何下手。幸虧,有萬能的google。搜尋之後發現常見的實作方法有4種,分别是:

renderscript

java算法

ndk算法

opengl

處理一整張圖檔這麼大計算量的工作,opengl的性能最好,而用java實作肯定是最差的了。而renderscript和ndk的性能相當,但是你懂得,ndk和opengl我無可奈何,綜合考慮,renderscript應該是最适合的。

但并不是說renderscript就是完全沒有問題的:

模糊半徑(radius)越大,性能要求越高,模糊半徑不能超過25,是以并不能得到模糊度非常高的圖檔。

scriptintrinsicblur在api 17時才被引入,如果需要在android 4.2以下的裝置上實作,就需要引入renderscript support library,當然,安裝包體積會相應的增大。

renderscript實作

首先在app目錄下build.gradle檔案中添加如下代碼:

defaultconfig { 

        applicationid "io.github.marktony.gaussianblur" 

        minsdkversion 19 

        targetsdkversion 25 

        versioncode 1 

        versionname "1.0" 

        renderscripttargetapi 19 

        renderscriptsupportmodeenabled true 

    }  

renderscriptintrinsics提供了一些可以幫助我們快速實作各種圖檔處理的操作類,例如,scriptintrinsicblur,可以簡單高效實作 高斯模糊效果。

package io.github.marktony.gaussianblur; 

import android.content.context; 

import android.graphics.bitmap; 

import android.support.annotation.intrange; 

import android.support.annotation.nonnull; 

import android.support.v8.renderscript.allocation; 

import android.support.v8.renderscript.element; 

import android.support.v8.renderscript.renderscript; 

import android.support.v8.renderscript.scriptintrinsicblur; 

public class renderscriptgaussianblur { 

    private renderscript renderscript; 

    public renderscriptgaussianblur(@nonnull context context) { 

        this.renderscript = renderscript.create(context); 

    } 

    public bitmap gaussianblur(@intrange(from = 1, to = 25) int radius, bitmap original) { 

        allocation input = allocation.createfrombitmap(renderscript, original); 

        allocation output = allocation.createtyped(renderscript, input.gettype()); 

        scriptintrinsicblur scriptintrinsicblur = scriptintrinsicblur.create(renderscript, element.u8_4(renderscript)); 

        scriptintrinsicblur.setradius(radius); 

        scriptintrinsicblur.setinput(input); 

        scriptintrinsicblur.foreach(output); 

        output.copyto(original); 

        return original; 

}  

然後就可以直接使用renderscriptgaussianblur,愉快地根據seekbar的值,實作不同程度的模糊了。

import android.content.dialoginterface; 

import android.graphics.bitmapfactory; 

import android.support.v7.app.alertdialog; 

import android.support.v7.app.appcompatactivity; 

import android.os.bundle; 

import android.util.log; 

import android.view.view; 

import android.view.window; 

import android.view.windowmanager; 

import android.widget.framelayout; 

import android.widget.imageview; 

import android.widget.linearlayout; 

import android.widget.seekbar; 

import android.widget.textview; 

public class mainactivity extends appcompatactivity { 

    private imageview imageview; 

    private imageview container; 

    private linearlayout layout; 

    private textview textviewprogress; 

    private renderscriptgaussianblur blur; 

    @override 

    protected void oncreate(bundle savedinstancestate) { 

        super.oncreate(savedinstancestate); 

        setcontentview(r.layout.activity_main); 

        imageview = (imageview) findviewbyid(r.id.imageview); 

        container = (imageview) findviewbyid(r.id.container); 

        container.setvisibility(view.gone); 

        layout = (linearlayout) findviewbyid(r.id.layout); 

        layout.setvisibility(view.visible); 

        seekbar seekbar = (seekbar) findviewbyid(r.id.seekbar); 

        textviewprogress = (textview) findviewbyid(r.id.textviewprogress); 

        textview textviewdialog = (textview) findviewbyid(r.id.textviewdialog); 

        blur = new renderscriptgaussianblur(mainactivity.this); 

        seekbar.setmax(25); 

        seekbar.setonseekbarchangelistener(new seekbar.onseekbarchangelistener() { 

            @override 

            public void onprogresschanged(seekbar seekbar, int progress, boolean fromuser) { 

                textviewprogress.settext(string.valueof(progress)); 

            } 

            public void onstarttrackingtouch(seekbar seekbar) { 

            public void onstoptrackingtouch(seekbar seekbar) { 

                int radius = seekbar.getprogress(); 

                if (radius < 1) { 

                    radius = 1; 

                } 

                bitmap bitmap = bitmapfactory.decoderesource(getresources(), r.drawable.image); 

                imageview.setimagebitmap(blur.gaussianblur(radius, bitmap)); 

        }); 

        textviewdialog.setonclicklistener(new view.onclicklistener() { 

            public void onclick(view v) { 

                container.setvisibility(view.visible); 

                layout.setdrawingcacheenabled(true); 

                layout.setdrawingcachequality(view.drawing_cache_quality_low); 

                bitmap bitmap = layout.getdrawingcache(); 

                container.setimagebitmap(blur.gaussianblur(25, bitmap)); 

                layout.setvisibility(view.invisible); 

                alertdialog dialog = new alertdialog.builder(mainactivity.this).create(); 

                dialog.settitle("title"); 

                dialog.setmessage("message"); 

                dialog.setbutton(dialoginterface.button_positive, "ok", new dialoginterface.onclicklistener() { 

                    @override 

                    public void onclick(dialoginterface dialog, int which) { 

                        dialog.dismiss(); 

                    } 

                }); 

                dialog.setbutton(dialoginterface.button_negative, "cancel", new dialoginterface.onclicklistener() { 

                dialog.setoncancellistener(new dialoginterface.oncancellistener() { 

                    public void oncancel(dialoginterface dialog) { 

                        container.setvisibility(view.gone); 

                        layout.setvisibility(view.visible); 

                dialog.show(); 

在代碼裡做了一些view的可見性的操作,比較簡單,相信你能看懂的。和instagram中dialog的實作有一點不同的是,我沒有截取整個頁面的bitmap,隻是截取了actionbar下的内容,如果一定要實作一樣的效果,調整一下頁面的布局就可以了。這裡不多說了。

是不是很簡單呢?

輪子

除了renderscript外,還有一些優秀的輪子:

<a href="https://github.com/500px/500px-android-blur">500px-android-blur</a>

<a href="https://github.com/wasabeef/blurry">blurry</a>

<a href="https://github.com/kikoso/android-stackblur">android-stackblur</a>

blurtestandroid對不同類庫的實作方式、采取的算法和所耗費的時間做了統計和比較,你也可以下載下傳它的demo app,自行測試。

使用RenderScript實作高斯模糊(毛玻璃/磨砂)效果

作者:tonny

來源:51cto