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

kalf7426 7年前發布 | 7K 次閱讀 Android開發 移動開發 RenderScript

前言

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

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

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

實現方法對比

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

  • RenderScript
  • Java算法
  • NDK算法
  • openGL

處理一整張圖片這么大計算量的工作,openGL的性能最好,而用java實現肯定是最差的了。而RenderScript和NDK的性能相當,但是你懂得,NDK和openGL我無可奈何,綜合考慮,RenderScript應該是最適合的。

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

  1. 模糊半徑(radius)越大,性能要求越高,模糊半徑不能超過25,所以并不能得到模糊度非常高的圖片。
  2. 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的值,實現不同程度的模糊了。

package io.github.marktony.gaussianblur; 
 
import android.content.DialogInterface; 
import android.graphics.Bitmap; 
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)); 
            } 
 
            @Override 
            public void onStartTrackingTouch(SeekBar seekBar) { 
 
            } 
 
            @Override 
            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() { 
            @Override 
            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() { 
                    @Override 
                    public void onClick(DialogInterface dialog, int which) { 
 
                    } 
                }); 
                dialog.setOnCancelListener(new DialogInterface.OnCancelListener() { 
                    @Override 
                    public void onCancel(DialogInterface dialog) { 
 
                    } 
                }); 
 
                dialog.setOnCancelListener(new DialogInterface.OnCancelListener() { 
                    @Override 
                    public void onCancel(DialogInterface dialog) { 
                        container.setVisibility(View.GONE); 
                        layout.setVisibility(View.VISIBLE); 
                    } 
                }); 
 
                dialog.show(); 
            } 
        }); 
 
    } 
}  

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

是不是很簡單呢?

輪子

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

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

 

 

來自:http://mobile.51cto.com/android-528446.htm

 

 本文由用戶 kalf7426 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!