高斯模糊效果實現方案及性能對比

如今愈來愈多的app在背景圖中使用高斯模糊效果,如yahoo天氣,效果作得很炫。 這裏就用一個demo來談談它的不一樣實現方式及各自的優缺點。html


1. RenderScript

談到高斯模糊,第一個想到的就是RenderScript。RenderScript是由Android3.0引入,用來在Android上編寫高性能代碼的一種語言(使用C99標準)。 引用官方文檔的描述:java

RenderScript runtime will parallelize work across all processors available on a device, such as multi-core CPUs, GPUs, or DSPs, allowing you to focus on expressing algorithms rather than scheduling work or load balancing.android

爲了在Android中使用RenderScript,咱們須要(直接貼官方文檔,比直譯更通俗易懂):git

  • High-performance compute kernels are written in a C99-derived language.程序員

  • A Java API is used for managing the lifetime of RenderScript resources and controlling kernel execution.github

學習文檔:http://developer.android.com/guide/topics/renderscript/compute.htmlexpress

上面兩點總結成一句話爲:咱們須要一組compute kernels(.rs文件中編寫),及一組用於控制renderScript相關的java api(.rs文件自動生成爲java類)。 因爲compute kernels的編寫須要必定的學習成本,從JELLY_BEAN_MR1開始,Androied內置了一些compute kernels用於經常使用的操做,其中就包括了Gaussian blurcanvas

下面,經過實操來說解一下RenderScript來實現高斯模糊,最終實現效果(講文字背景進行模糊處理):api

佈局:app

[html] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. <?xml version="1.0" encoding="utf-8"?>  

  2. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"  

  3. android:layout_width="match_parent"  

  4. android:layout_height="match_parent" >  

  5.   

  6.     <ImageView   

  7.         android:id="@+id/picture"   

  8.         android:layout_width="match_parent"   

  9.         android:layout_height="match_parent"   

  10.         android:src="@drawable/splash"   

  11.         android:scaleType="centerCrop" />  

  12.   

  13.     <TextView   

  14.         android:id="@+id/text"  

  15.         android:gravity="center_horizontal"   

  16.         android:layout_width="match_parent"  

  17.         android:layout_height="wrap_content"  

  18.         android:text="Gaussian Blur"  

  19.         android:textColor="@android :color/black"  

  20.         android:layout_gravity="center_vertical"  

  21.         android:textStyle="bold"  

  22.         android:textSize="48sp" />  

  23.   

  24.     <LinearLayout   

  25.         android:id="@+id/controls"   

  26.         android:layout_width="match_parent"   

  27.         android:layout_height="wrap_content"   

  28.         android:background="#7f000000"   

  29.         android:orientation="vertical"  

  30.         android:layout_gravity="bottom" />  

  31. </FrameLayout>  

核心代碼:

[java] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. private void applyBlur() {  

  2.     image.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {  

  3.   

  4.         @Override  

  5.         public boolean onPreDraw() {  

  6.             image.getViewTreeObserver().removeOnPreDrawListener(this);  

  7.             image.buildDrawingCache();  

  8.             Bitmap bmp = image.getDrawingCache();  

  9.             blur(bmp, text, true);  

  10.             return true;  

  11.         }  

  12.     });  

  13. }  

  14.   

  15. @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)  

  16. private void blur(Bitmap bkg, View view) {  

  17.     long startMs = System.currentTimeMillis();  

  18.     float radius = 20;  

  19.   

  20.     Bitmap overlay = Bitmap.createBitmap((int)(view.getMeasuredWidth()), (int)(view.getMeasuredHeight()), Bitmap.Config.ARGB_8888);  

  21.     Canvas canvas = new Canvas(overlay);  

  22.     canvas.translate(-view.getLeft(), -view.getTop());  

  23.     canvas.drawBitmap(bkg, 00null);  

  24.   

  25.     RenderScript rs = RenderScript.create(SecondActivity.this);  

  26.   

  27.     Allocation overlayAlloc = Allocation.createFromBitmap(rs, overlay);  

  28.     ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create(rs, overlayAlloc.getElement());  

  29.     blur.setInput(overlayAlloc);  

  30.     blur.setRadius(radius);  

  31.     blur.forEach(overlayAlloc);  

  32.     overlayAlloc.copyTo(overlay);  

  33.     view.setBackground(new BitmapDrawable(getResources(), overlay));  

  34.     rs.destroy();  

  35.   

  36.     statusText.setText("cost " + (System.currentTimeMillis() - startMs) + "ms");  

  37. }  

當ImageView開始加載背景圖時,取出它的drawableCache,進行blur處理,Gaussian blur的主要邏輯在blur函數中。對於在Java中使用RenderScript,文檔中也有詳細描述,對應到咱們的代碼,步驟爲:

  • 初始化一個RenderScript Context.

  • 至少建立一個Allocation對象用於存儲須要處理的數據.

  • 建立compute kernel的實例,本例中是內置的ScriptIntrinsicBlur對象.

  • 設置ScriptIntrinsicBlur實例的相關屬性,包括Allocation, radius等.

  • 開始blur操做,對應(forEach).

  • 將blur後的結果拷貝回bitmap中。

此時,咱們便獲得了一個通過高斯模糊的bitmap。 

從上圖能夠看到,模糊處理花費了38ms(測試機爲小米2s),因爲Android假設每一幀的處理時間不能超過16ms(屏幕刷新頻率60fps),所以,若在主線程裏執行RenderScript操做,可能會形成卡頓現象。最好的方式是將其放入AsyncTask中執行。

此外,RenderScript在3.0引入,而一些內置的compute kernelJELLY_BEAN_MR1中引入,爲了在低版本手機中使用這些特性,咱們不得不引入renderscript_v8兼容包,對於手Q安裝包增量的硬性指標,貌似只能放棄JELLY_BEAN_MR1如下的用戶?

有點不甘心,想一想別的解決方案吧。

2. FastBlur

因爲高斯模糊歸根結底是像素點的操做,也許在java層能夠直接操做像素點來進行模糊化處理。google一下,果不其然,一個名爲stackblur的開源項目提供了名爲fastBlur的方法在java層直接進行高斯模糊處理。

項目地址請猛戳: stackblur

ok,如今來改造咱們的程序.

[java] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. private void blur(Bitmap bkg, View view) {  

  2.     long startMs = System.currentTimeMillis();  

  3.     float radius = 20;  

  4.   

  5.     Bitmap overlay = Bitmap.createBitmap((int)(view.getMeasuredWidth()), (int)(view.getMeasuredHeight()), Bitmap.Config.ARGB_8888);  

  6.     Canvas canvas = new Canvas(overlay);  

  7.     canvas.translate(-view.getLeft(), -view.getTop());  

  8.     canvas.drawBitmap(bkg, 00null);  

  9.     overlay = FastBlur.doBlur(overlay, (int)radius, true);  

  10.     view.setBackground(new BitmapDrawable(getResources(), overlay));  

  11.     statusText.setText("cost " + (System.currentTimeMillis() - startMs) + "ms");  

  12. }    

這裏,僅僅是把RenderScript相關的操做換成了FastBlur提供的api。效果圖以下: 

效果還不錯,與RenderScript的實現差很少,但花費的時間卻整整多了2倍多,這徹底是沒法接受的。好吧,只能繼續探究。

3. AdvancedFastBlur

stackOverflow對於程序員來講永遠是最大的寶藏。http://stackoverflow.com/questions/2067955/fast-bitmap-blur-for-android-sdk這篇提問帖終於提供了新的解決思路:

This is a shot in the dark, but you might try shrinking the image and then enlarging it again. This can be done with Bitmap.createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter). Make sure and set the filter parameter to true. It'll run in native code so it might be faster.

它所表述的原理爲先經過縮小圖片,使其丟失一些像素點,接着進行模糊化處理,而後再放大到原來尺寸。因爲圖片縮小後再進行模糊處理,須要處理的像素點和半徑都變小,從而使得模糊處理速度加快。 瞭解原理,繼續改善:

[java] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. private void blur(Bitmap bkg, View view) {  

  2.     long startMs = System.currentTimeMillis();  

  3.     float radius = 2;  

  4.     float scaleFactor = 8;  

  5.   

  6.     Bitmap overlay = Bitmap.createBitmap((int)(view.getMeasuredWidth()/scaleFactor), (int)(view.getMeasuredHeight()/scaleFactor), Bitmap.Config.ARGB_8888);  

  7.     Canvas canvas = new Canvas(overlay);  

  8.     canvas.translate(-view.getLeft()/scaleFactor, -view.getTop()/scaleFactor);  

  9.     canvas.scale(1 / scaleFactor, 1 / scaleFactor);  

  10.     Paint paint = new Paint();  

  11.     paint.setFlags(Paint.FILTER_BITMAP_FLAG);  

  12.     canvas.drawBitmap(bkg, 00, paint);  

  13.     overlay = FastBlur.doBlur(overlay, (int)radius, true);  

  14.     view.setBackground(new BitmapDrawable(getResources(), overlay));  

  15.     statusText.setText("cost " + (System.currentTimeMillis() - startMs) + "ms");  

  16. }   

最新的代碼所建立的bitmap爲原圖的1/8大小,接着,一樣使用fastBlur來進行模糊化處理,最後再爲textview設置背景,此時,背景圖會自動放大到初始大小。注意,因爲這裏進行了縮放,radius的取值也要比以前小得多(這裏將原始取值除以8獲得近似值2)。下面是效果圖:

驚呆了有木有!!效果同樣,處理速度卻快得驚人。它相對於renderScript方案來講,節省了拷貝bitmap到Allocation中,處理完後再拷貝回來的時間開銷。

4. Warning

因爲FastBlur是將整個bitmap拷貝到一個臨時的buffer中進行像素點操做,所以,它不適合處理一些過大的背景圖(很容致使OOM有木有~)。對於開發者來講,RenderScript方案和FastBlur方案的選擇,須要你根據具體業務來衡量!

相關文章
相關標籤/搜索