前言
從 iOS 7 開始 Apple 從 擬物化 過渡到了 扁平化 的設(shè)計風(fēng)格,同時也搭配使用了 毛玻璃風(fēng)格 當(dāng)做背景效果,不得不說十分驚艷,頗有當(dāng)時pc上 Windows Vista 和 OS X Yosemite 的味道,在那之后,Google 也從 Android L(5.0)開始使用了 原質(zhì)化設(shè)計(Material Design) 設(shè)計語言,與 Microsoft 的 Metro 那種純扁平化風(fēng)格看似很相像,但實則因為引用了 Z軸 的概念,使其有了陰影和立體感,傳達(dá)了 響應(yīng)式交互 的設(shè)計理念。說到這里有一些跑題,因為筆者對設(shè)計美學(xué)很感興趣,所以對這些平臺都稍微了解一些皮毛。今天就來研究一下如何在 Android 上實現(xiàn)高斯模糊效果。
目前 Android 上實現(xiàn)高斯模糊效果的方式有:
Java : FastBlur.java ,應(yīng)用非常廣泛的 StackBlur 模糊算法實現(xiàn)代碼,效率最低
C++ :兩種實現(xiàn),1:標(biāo)準(zhǔn)高斯模糊算法 2:均值模糊,效率中等
Android : RenderScript ,用來在 Android 上編寫高性能代碼的一種語言(使用C99標(biāo)準(zhǔn),運(yùn)行時機(jī)器再次優(yōu)化編譯, 可以均衡的運(yùn)行在多個CPU 和 GPU上,有一個半徑限制小于25的限制),效率最高
簡單聊聊 FastBlur
因為效果的實現(xiàn)是基于 Java 的,所以有必要先來了解一下方法如何使用。
public static Bitmap doBlur(Bitmap sentBitmap, int radius, boolean canReuseInBitmap)
可以看出,使用方法非常簡單,傳入待虛化的bitmap、虛化程度(一般為8)、是否重用flag,最后返回模糊后的bitmap。
但如果直接把一張大圖傳入進(jìn)行虛化,很容易就會產(chǎn)生OOM內(nèi)存溢出,那就意味著我只能虛化小圖,這樣才能防止內(nèi)存溢出。但是我并不想換其他圖,那么,我們就應(yīng)該把這張圖縮小。
平時我們對圖片縮小,必然會帶來很明顯的清晰度的損失,但高斯模糊本身的目的就是要實現(xiàn)模糊的效果,因此實際上的效果差別不大,幾乎可以忽略。
同時由于圖片縮小后再進(jìn)行模糊處理,需要處理的像素點(diǎn)和半徑都變小,從而使得模糊處理速度加快。
ReScale
public static Bitmap createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter) {}
我們可以利用Bitmap的 createScaledBitmap() 方法來進(jìn)行bitmap的縮放。其中前三個參數(shù)很明顯,其中寬高我們可以選擇為原圖尺寸的1/5;第四個filter是指縮放的效果,filter為true則會得到一個邊緣平滑的bitmap,反之,則會得到邊緣鋸齒、pixelrelated的bitmap。這里我們要對縮放的圖片進(jìn)行虛化,所以無所謂邊緣效果,filter=false。
所以,我們要使用
int scaleRatio = 5;// 縮放比例 此處代表1/5
int blurRadius = 8;// 虛化程度
Bitmap scaledBitmap = Bitmap.createScaledBitmap(originBitmap,
originBitmap.getWidth() / scaleRatio,
originBitmap.getHeight() / scaleRatio,
false);
Bitmap blurBitmap = FastBlur.doBlur(scaledBitmap, blurRadius, true);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setImageBitmap(blurBitmap);
可以得到如下效果:

從圖中可以看出,首先可以確定思路是對的;然后,可以看出毛玻璃效果還不是特別的明顯。為了得到如iOS那樣的虛化效果,我們有兩種方法:
- 增大scaleRatio縮放比,使用更小的bitmap去虛化可以得到更好的模糊效果,而且有利于占用內(nèi)存的減??;
- 增大blurRadius,可以得到更高程度的虛化,不過會導(dǎo)致CPU更加intensive
這里筆者通過增大縮放比來實驗。
- scaleRatio = 10

- scaleRatio = 20

通過上面對比圖我們可以找出最適合自己的虛化效果。
再來聊聊 RenderScript
RenderScript 主要 在Android中的對圖形進(jìn)行處理,RenderScript 采用C99語法進(jìn)行編寫,主要優(yōu)勢在于性能較高。在 API 11 的時候被加入到 Android 中。同時,Google提供了
android.support.v8.renderscript兼容包,能夠?qū)崿F(xiàn)更低版本的兼容。
RenderScript 提供了一個用于實現(xiàn)高斯模糊的封裝類 ScriptIntrinsicBlur ,因為在 API 17 后才正式適配到 Android ,所以在不使用兼容包的情況下只能兼容到4.2的設(shè)備。但是,我們有兼容包啊向下兼容不是夢。
準(zhǔn)備階段
引入兼容包
方法很簡單,只需在build.gradle中加入:
defaultConfig {
....
// 就是這么簡單
renderscriptTargetApi 19
renderscriptSupportModeEnabled true
}
另外由于一些廠商會深度定制Android系統(tǒng),所以一些必要的依賴文件會被他們直接去掉,這導(dǎo)致一些型號的設(shè)備上調(diào)用RenderScriptd的部分方法時會報錯。遇到這種兼容問題的話,需要加上這些可能丟失的文件。
其實也簡單,打開android_sdk/build-tools/選擇19以上版本/renderscript/lib/packaged我們可以看見3個包含.so文件的文件夾。

直接復(fù)制這三個文件加到項目工程的 jniLibs 包下,沒有的話去建一個。

如果首次創(chuàng)建 jniLibs 文件夾,還需要在 build.gradle 的 android{} 中加入:
sourceSets {
main {
jniLibs.srcDirs = ['jniLibs']
}
}
針對使用的混淆的同學(xué),需要在混淆中加入:
-keep class android.support.v8.renderscript.** { *; }
實現(xiàn)高斯模糊
- 將核心實現(xiàn)方法 ScriptIntrinsicBlur 封裝成工具類。
import android.support.v8.renderscript.*; // 需要導(dǎo)入v8包,否則無法向下兼容
public class BlurBitmapUtil {
/***
* 圖片縮放比例 (例如 1/10)
*/
private static int scaleRatio = 10;
/**
* 對圖片進(jìn)行高斯模糊
*
* @param context 上下文對象
* @param image 需要模糊的圖片
* @param blurRadius 模糊半徑,由于性能限制,這個值的取值區(qū)間為(0至25f)
* @return 模糊處理后的圖片
*/
public static Bitmap blurBitmap(Context context, Bitmap image, @FloatRange(from = 1, to = 25)
float blurRadius) {
// 計算圖片縮小后的長寬
int width = Math.round(image.getWidth() / scaleRatio);
int height = Math.round(image.getHeight() / scaleRatio);
// 創(chuàng)建一張縮小后的圖片做為渲染的圖片
Bitmap bitmap = Bitmap.createScaledBitmap(image, width, height, false);
// 創(chuàng)建RenderScript內(nèi)核對象
RenderScript rs = RenderScript.create(context);
// 創(chuàng)建一個模糊效果的RenderScript的工具對象,第二個參數(shù)Element相當(dāng)于一種像素處理的算法,高斯模糊的話用這個就好
ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
// 由于RenderScript并沒有使用VM來分配內(nèi)存,所以需要使用Allocation類來創(chuàng)建和分配內(nèi)存空間
// 創(chuàng)建Allocation對象的時候其實內(nèi)存是空的,需要使用copyTo()將數(shù)據(jù)填充進(jìn)去
Allocation input = Allocation.createFromBitmap(rs, bitmap);
// 創(chuàng)建相同類型的Allocation對象用來輸出
Type type = input.getType();
Allocation output = Allocation.createTyped(rs, type);
// 設(shè)置渲染的模糊程度, 25f是最大模糊度
blurScript.setRadius(blurRadius);
// 設(shè)置blurScript對象的輸入內(nèi)存
blurScript.setInput(input);
// 將輸出數(shù)據(jù)保存到輸出內(nèi)存中
blurScript.forEach(output);
// 將數(shù)據(jù)填充到bitmap中
output.copyTo(bitmap);
// 銷毀它們釋放內(nèi)存
input.destroy();
output.destroy();
blurScript.destroy();
rs.destroy();
type.destroy();
return bitmap;
}
使用 RenderScript 增加虛化程度的方法和 FastBlur 一樣,有兩種方法:
增大scaleRatio縮放比,使用更小的bitmap去虛化可以得到更好的模糊效果,而且有利于占用內(nèi)存的減??;
增大blurRadius,可以得到更高程度的虛化,不過會導(dǎo)致虛化時間變長
但因為 RenderScript 的天然優(yōu)勢(低級語言, 運(yùn)行時機(jī)器再次優(yōu)化編譯, 可以均衡的運(yùn)行在多個CPU 和 GPU上),所以這里筆者通過增大虛化程度來實驗,縮放比例為 1/10,實際運(yùn)用時可以根據(jù)需求在對虛化程度和縮放比例上采取一個合適的數(shù)值。
- blurRadius = 5

- blurRadius = 15

- blurRadius = 25

通過上面對比圖我們可以找出最適合自己的虛化效果。
目前來看,為何 Google 設(shè)置這個25的限制, 原因應(yīng)該有兩個 :
- 半徑大于25的話耗時就成為了一個瓶頸;
- 如果想實現(xiàn)大于25的模糊效果,可以通過縮小原圖,模糊,再放大來達(dá)到同樣的效果
總結(jié)
以上就是如何用 FastBlur 和 RenderScript 在 Android 上實現(xiàn)和 iOS 一樣的高斯模糊效果的簡單介紹,雖然在性能上毋庸置疑是 RenderScript 上最好,但是在一些使用場景上 FastBlur 耗時會更短,所以我們各取所需,根據(jù)實際需求去選擇使用。
另一種可能性
上面說的2種解決方案都是從性能和效率出發(fā)的產(chǎn)物,但如果我的需求就是從網(wǎng)絡(luò)上加載一張圖片(比如頭像),然后再高斯模糊化當(dāng)背景,走一遍轉(zhuǎn)換成bitma再將其轉(zhuǎn)換成高斯模糊的流程或許會有一點(diǎn)點(diǎn)麻煩,這里我再提供一種簡單快捷的解決方案 —— 基于Glid加載框架去實現(xiàn)一鍵 加載網(wǎng)絡(luò)圖片→高斯模糊化→展示。
引入兼容包
首先在build.gradle中加入圖片框架需要的庫和圖片工具庫:
defaultConfig {
....
compile 'com.yutianran.maven:super-adapter:1.0.0'
compile 'jp.wasabeef:glide-transformations:2.0.2'
}
然后就開寫,一行代碼即可
Glide.with(this).load(url).bitmapTransform(new BlurTransformation(this,25)).into(imageView);
需要的參數(shù)很分別是
- 上下文對象
- 圖片url
- 上下文對象,虛化數(shù)值
- imageView控件

效果如上,可以看出 glide-transformations 庫的虛化效果也是十分不錯的,但對圖片本身做的縮放應(yīng)該不是很多,所以在加載速度上會弱于 FastBlur 和 RenderScript ,但作為輕量級圖片而言足夠了。
Code
相關(guān)代碼已上傳至Github:BlurView,歡迎Star,F(xiàn)ork。