封裝個 Android 的高斯模糊組件


本篇文章已授權微信公眾號 hongyangAndroid (鴻洋)獨家發布

最近基於 Android StackBlur 開源庫,根據自己碰到的需求場景,封裝了個高斯模糊組件,順便記錄一下。

為什么要自己重復造輪子?

其實也談不上重頭自己造輪子,畢竟是基於大神的開源庫,做了二次封裝。封裝的目的在於,方便外部使用。畢竟有着自己的編程習慣,大神的開源庫也只是提供了基礎的功能,現實編程中,產品的需求是各種各樣的。

導致每次使用時,都蠻麻煩的,需要額外自己處理蠻多東西。而一旦新的項目又需要接入高斯模糊了,又得重新寫一些代碼,復制粘貼也麻煩,經常由於各種業務耦合報錯。

既然如此,干脆花時間抽個基礎、公用的高斯模糊組件,需要時直接依賴即可。

基礎理論

高斯模糊

高斯模糊的原理和算法就不介紹了,我也不懂,沒深入,這里就大概講講我的粗坯理解:

我們知道,一張圖片,本質上其實是一個個像素點構成的,雖然經過計算機處理后,呈現在我們眼前的是具體的圖像。但在計算機中,其實就是一堆數組數據。

數組中每個單位就是一個個像素點,那么每個像素點是存儲什么內容呢,其實也就是 RGB 或者 ARGB 之類格式的數據。

高斯模糊,大體上就是對這張圖片中的每個像素點都重新進行計算,每個像素點的新值等於以它為中心,半徑為 r 的周圍區域內所有像素點各自按照不同權重相加之和的平均值。

可以粗坯的理解為,本來這個像素點是要呈現它自己本身的內容,但經過高斯模糊計算后,摻雜進它周圍區域像素點的內容了。就像加水稀釋類似的道理,既然都摻雜進周圍的內容了,那么它呈現的內容相比最初,肯定就不那么清晰了

而如果摻雜的半徑越大,混合進的內容也就越多,那么它本身的內容就越淡了,是不是這個理。所以,這就是為什么每個開源的高斯模糊組件庫,使用時基本都需要我們傳入一個 radius 半徑的參數。而且,半徑越大,越模糊。

這么一粗坯的解釋,就理解多了,是吧。

為什么需要大概掌握這個理論基礎呢?

想想,高斯模糊是遍歷所有像素點,對每個像素點都重新計算。那么,這自然是一個耗時的工作,掌握了理論基礎,我們要優化時也才有方向去優化。

性能對比

大神的開源庫中提供了三種高斯模糊方式,而我在 Blankj 的 AndroidUtilCode 開源庫中發現了另外一種,所以我將他們都整合起來,一共有四種:

  • Google 官方提供的 RenderScript 方式 (RSBlur)
  • C 編寫的高斯算法 blur.c 方式 (NativeBlur)
  • Java 編寫的高斯算法方式1(JavaBlur)
  • Java 編寫的高斯算法方式2(StackBlur)

其實,大體上就三種:Google 官方提供的,大神用 C 寫的高斯模糊算法,大神用 Java 寫的高斯模糊算法。至於后面兩種,看了下,算法的代碼不一樣,我就把它們當做是兩種不同的算法實現了。也許是一樣,但我沒深入去看,反正代碼不一樣,我就這么認為了。

下面我們來做個實驗,在如下相同條件下,不同高斯模糊方案的耗時比較:

  • radius=10, scale=1, bitmap=200*200(11.69KB)

ps: radius 表示高斯算法計算過程中的半徑取值,scale=1表示對 bitmap 原圖進行高斯模糊。這些前提條件需要了解一下,不然你在看網上其他類似性能比對的文章時,發現它們動不動就優化到幾 ms 級別的,然而你自己嘗試卻始終達不到。這是因為也許所使用的這些前提都不一致,不一致的前提下,耗時根本無從比較。

private void testBlur() {
	int sum = 0;
	for (int i = 0; i < 100; i++) {
		long time = SystemClock.uptimeMillis();
		DBlur.source(this, R.drawable.image).modeRs().radius(10).sampling(1).build().doBlurSync();
		long end = SystemClock.uptimeMillis();
		sum += (end - time);
	}
    Log.e("DBlur", "RSBlur cast " + (sum/100) + "ms");
}

代碼模板如上,分別運行 100 次后取平均值,四種不同方式的耗時如下表:

前提條件 RSBlur NativeBlur JavaBlur StackBlur
radius=10, scale=1, bitmap=200*200 51ms 13ms 162ms 384ms
radius=20, scale=1, bitmap=200*200 56ms 12ms 164ms 435ms
radius=10, scale=2, bitmap=200*200 48ms 11ms 75ms 110ms
radius=10, scale=8, bitmap=200*200 45ms 7ms 14ms 19ms
radius=10, scale=8, bitmap=1920*1180 183ms 143ms 346ms 460ms
radius=20, scale=8, bitmap=1920*1180 204ms 145ms 353ms 510ms
radius=20, scale=1, bitmap=1920*1180 474ms 444ms 8663ms 內存溢出

100 次樣本可能不多,但大體上我們也能比較出不同類型的高斯模糊之前的區別,及其適用場景:

  • 總體上,NativeBlur 和 RSBlur 的耗時會少於 JavaBlur 和 StackBlur
  • JavaBlur 和 StackBlur 方式,如果先對 Bitmap 進行縮小,再高斯模糊,最后再放大,耗時會大大縮短
  • radius 增大會增加耗時,但影響不大,但視圖呈現效果會越模糊
  • scale 對原圖縮小倍數越多,耗時越短,但視圖呈現效果同樣會越模糊
  • 分辨率越高的圖片,高斯模糊的就越耗時
  • 對於大圖而言,如果要使用 JavaBlur 或 StackBlur,最好設置 scale 先縮小再模糊,否則將非常耗時且容易內存溢出
  • 如果已經通過 scale 方式進行優化,那么最好 radius 值可以相對小一點,否則兩者的值都大會對圖片的模糊效果特別強烈,也許會過了頭

性能優化

高斯模糊的優化考慮點,其實就三個:

  • 選擇不同的高斯模糊方式
  • 通過 scale 對原圖先縮小,再模糊,最后再放大方式
  • 優化高斯模糊算法

最后一點就不考慮了,畢竟難度太大。那么,其實就剩下兩種,要么是從高斯模糊的方案上選擇,要么從待模糊的圖片上做手腳。

雖然有四種高斯模糊方案,但每種都有各自優缺點:

  • RSBlur 在低端機上可能無法兼容
  • NativeBlur 需要生成對應 CPU 架構的 so 文件支持
  • JavaBlur 和 StackBlur 耗時會較長

優化的考慮點大體上這幾種:

  • 大體上,使用 NativeBlur 或者 RSBlur 即可,如果出現一些問題,那么此時可考慮切到 JavaBlur 或 StackBlur 方案,但記得結合 scale 方式優化處理。
  • 如果高斯模糊的圖片有實時性要求,要求模糊得同步進行處理,主線程后續的工作需要等待高斯模糊后才能夠處理的話,那么盡量選擇 scale 方式進行優化,減少耗時。
  • 如果對實時性沒要求,但對圖片模糊程度有要求,那其實,只要后台異步去進行高斯模糊即可,此時 scale 可不用縮小太多,而利用 radius 來控制模糊效果,以達到理想的要求。
  • 如果兩者都有要求,那就自行嘗試尋找折中點吧。

最后說一點,因為已經封裝成組件庫了,RSBlur 也是引入的 support 包,so 文件也打包好了,那么使用這兩種方案足夠滿足絕大部分場景了,所以,沒有特意指定,組件默認的方案為 RSBlur。

二次封裝

需求場景

為什么要二次封裝?那肯定是因為有自己的各種需求場景的,我的需求如下:

  • 要能夠對當前頁面(Activity)截圖后,進行模糊
  • 要能夠對 drawable 資源圖片進行模糊、或者對指定 View 的視圖進行模糊
  • 模糊完成后,要能夠自動以淡入的動畫方式顯示在指定的控件上
  • 存在這種需求場景:對當前界面截圖、並且模糊,模糊后的圖片展示的時機可能在其他界面,因此需要支持緩存功能,可以根據指定 cacheKey 值獲取緩存
  • 當然,可以根據各種配置使用高斯模糊,當不指定配置時,有默認配置

總結一下,其實封裝要做的事也就是要實現:

  • 截圖、緩存、淡入動畫、默認配置
  • 可以的話,組件最好可以達到,其他人在不看文檔,不看源碼前提下,以最少的成本接入直接上手使用

實現

截圖、緩存、動畫這些都屬於純功能代碼的封裝了,具體就不說了。

這里想來講講,如何設計,可以讓其他人以最少的成本接入直接上手使用。

我的想法是,利用 AndroidStudio 的代碼提示功能,具體的說就是,你只需要了解組件的入口是 DBlur 即可,至於后續怎么使用,全靠 AndroidStudio 來提示,跟着 AndroidStudio 走就行。

例如:

DBlur入口.png

當敲完 DBlur. 時,會彈出代碼提示框,入口很少,是吧,就兩個,看命名也能猜到作用:getCacheBitmap() 明顯是用來取緩存的,那么要高斯模糊自然是另外一個入口 source(),這個方法有多個重載函數,看參數,其實也能知道,這就對應着要模糊的圖片的不同來源類型,如:

  • 直接傳入 Bitmap 對其進行模糊
  • 傳入 Activity/View,內部會對這個界面/控件進行截圖后再模糊
  • 傳入 resId,對 drawable 資源圖片進行模糊

那么,可能想問了,哪里進行高斯模糊配置,哪里設置同步或異步,哪里注冊回調等等。別急,既然只給你開了一個入口,那么就跟着入口走下去,自然會一步步引導你走到最后。如:

BlurConfigBuilder入口.png

第一步、第二步該做什么,我都給你規定好了,你也只能按照步驟一步步來。想要設置高斯模糊配置,你得先指定圖片來源,才能進入第二步,在這里,可以進行的配置也都給你列出來了,想要哪個,直接設置即可。如:

  • mode()modeRs()modeNative() 等等類似 mode 開頭的方法,用於指定要使用哪種高斯模糊方案,一共四種,每種內部都有提供對應的常量標志,但如果你不知道哪里找,那么直接調用 modeXXX 方法即可。
  • radius() 用於設置高斯模糊計算的半徑,內部默認為 4。
  • sampling() 用於設置對原圖的縮小比例,內部默認為 8,即默認先縮小 8 倍,再模糊,最后再放大。
  • cache() 用於設置緩存此次模糊后的圖片,沒有調用默認不緩存。
  • animAlpha() 用於設置使用淡入動畫,需要結合 intoTarget() 使用,否則不生效。
  • intoTarget() 用於設置模糊完成后,自動顯示到指定控件上。

另外,看每個方法返回的類名,其實這個過程都是在設置配置項,如果有對 Builder 模式了解的話,應該清楚,這個大多用來解決構造函數參數過多的場景,最后一般都會有一個 build() 或者 create() 類型的方法。參考的是 Android 源碼中 AlertDialog。

也就是說,要進入下一個步驟,需要調用 build() 方法,如:

doblur.png

顯然,已經到最后一個步驟了,這里就是發起高斯模糊工作的地方了。

  • doBlur() 會指定此次高斯模糊工作異步進行,所以需要注冊回調的在這里傳入。
  • doBlurSync() 指定高斯模糊同步進行,模糊后的 Bitmap 直接返回。

至此,接入結束。

使用這個高斯模糊的組件,只需要知道 DBlur 入口,其他都跟隨着 AndroidStudio 代碼提示一步步往下走即可,當然你也可以直接看源碼,注釋里也寫得蠻清楚的了。

這個就是我的想法,能力不足,只能想出這種方案,如果有哪里需要改進,哪里不合理,或者有其他思路,歡迎指點一下。

使用示例

compile 'com.dasu.image:blur:0.0.4'
//使用默認配置,最短調用鏈
Bitmap bitmap = DBlur.source(MainActivity.this).build().doBlurSync();

//同步模糊,將imageView控制的視圖進行模糊,完成后自動顯示到 imageView1 控件上,以淡入動畫方式
DBlur.source(imageView).intoTarget(imageView1).animAlpha().build().doBlurSync();

//異步模糊,將drawable資源文件中的圖片以 NATIVE 方式進行模糊,注冊回調,完成時手動顯示到 imageView1 控件上
DBlur.source(this, R.drawable.background).mode(BlurConfig.MODE_NATIVE).build()
      .doBlur(new OnBlurListener() {
            @Override
            public void onBlurSuccess(Bitmap bitmap) {
                imageView1.setImageBitmap(bitmap);
            }

            @Override
            public void onBlurFailed() {
                //do something
            }});

Github

DBlur 的 Github 鏈接:https://github.com/woshidasusu/base-module/tree/master/blur


大家好,我是 dasu,歡迎關注我的公眾號(dasuAndroidTv),如果你覺得本篇內容有幫助到你,可以轉載但記得要關注,要標明原文哦,謝謝支持~
dasuAndroidTv2.png


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM