最新剛好遇到個需求是要求做高斯模糊的,雖然現(xiàn)有已經(jīng)有一些框架可以提供調(diào)用,但關(guān)鍵還是要理解原理才行,思考的過程才是最重要的,高斯模糊的原理則與圖像卷積濾波有些關(guān)系。
目錄大綱
1.圖像卷積濾波與高斯模糊
2.高斯模糊實(shí)現(xiàn)與優(yōu)化
3.RenderScript的介紹與使用
一.圖像卷積濾波與高斯模糊
1.1 圖像卷積濾波
對于濾波來說,它可以說是圖像處理最基本的方法,可以產(chǎn)生很多不同的效果。以下圖來說
圖中矩陣分別為二維原圖像素矩陣,二維的圖像濾波矩陣(也叫做卷積核,下面講到濾波器和卷積核都是同個概念),以及最后濾波后的新像素圖。對于原圖像的每一個像素點(diǎn),計(jì)算它的領(lǐng)域像素和濾波器矩陣的對應(yīng)元素的成績,然后加起來,作為當(dāng)前中心像素位置的值,這樣就完成了濾波的過程了。
可以看到,一個原圖像通過一定的卷積核處理后就可以變換為另一個圖像了。而對于濾波器來說,也是有一定的規(guī)則要求的。
- ① 濾波器的大小應(yīng)該是奇數(shù),這樣它才有一個中心,例如3x3,5x5或者7x7。有中心了,也有了半徑的稱呼,例如5x5大小的核的半徑就是2。
- ② 濾波器矩陣所有的元素之和應(yīng)該要等于1,這是為了保證濾波前后圖像的亮度保持不變。當(dāng)然了,這不是硬性要求了。
- ③ 如果濾波器矩陣所有元素之和大于1,那么濾波后的圖像就會比原圖像更亮,反之,如果小于1,那么得到的圖像就會變暗。如果和為0,圖像不會變黑,但也會非常暗。
- ④ 對于濾波后的結(jié)構(gòu),可能會出現(xiàn)負(fù)數(shù)或者大于255的數(shù)值。對這種情況,我們將他們直接截?cái)嗟?和255之間即可。對于負(fù)數(shù),也可以取絕對值。
1.2 卷積核一些用法
既然知道濾波器可以用來對原圖進(jìn)行操作,那么,有沒有一些比較具體的例子。文中卷積核相關(guān)圖片來源于網(wǎng)絡(luò)
1.2.1 空卷積核
可以看到,這個濾波器啥也沒有做,得到的圖像和原圖是一樣的。因?yàn)橹挥兄行狞c(diǎn)的值是1。鄰域點(diǎn)的權(quán)值都是0,對濾波后的取值沒有任何影響。
1.2.2 圖像銳化濾波器
圖像的銳化和邊緣檢測很像,首先找到邊緣,然后把邊緣加到原來的圖像上面,這樣就強(qiáng)化了圖像的邊緣,使圖像看起來更加銳利了。這兩者操作統(tǒng)一起來就是銳化濾波器了,也就是在邊緣檢測濾波器的基礎(chǔ)上,再在中心的位置加1,這樣濾波后的圖像就會和原始的圖像具有同樣的亮度了,但是會更加銳利。
我們把核加大,就可以得到更加精細(xì)的銳化效果
1.2.3 浮雕
浮雕濾波器可以給圖像一種3D陰影的效果。只要將中心一邊的像素減去另一邊的像素就可以了。這時候,像素值有可能是負(fù)數(shù),我們將負(fù)數(shù)當(dāng)成陰影,將正數(shù)當(dāng)成光,然后我們對結(jié)果圖像加上128的偏移。這時候,圖像大部分就變成灰色了。
下面是45度的浮雕濾波器
我們只要加大濾波器,就可以得到更加夸張的效果了
1.2.4 均值模糊
我們可以將當(dāng)前像素和它的四鄰域的像素一起取平均,然后再除以5,或者直接在濾波器的5個地方取0.2的值即可,如下圖:
可以看到,這個模糊還是比較溫柔的,我們可以把濾波器變大,這樣就會變得粗暴了:注意要將和再除以13.
可以看到均值模糊也可以做到讓圖片模糊,但是它的模糊不是很平滑,不平滑主要在于距離中心點(diǎn)很遠(yuǎn)的點(diǎn)與距離中心點(diǎn)很近的所帶的權(quán)重值相同,產(chǎn)生的模糊效果一樣
而想要做到平滑,讓權(quán)重值跟隨中心點(diǎn)位置距離不同而不同,則可以利用正態(tài)分布(中間大,兩端?。┻@個特點(diǎn)來實(shí)現(xiàn)。
1.3 高斯模糊
有了前面的知識,我們知道如果要想實(shí)現(xiàn)高斯模糊的特點(diǎn),則需要通過構(gòu)建對應(yīng)的權(quán)重矩陣來進(jìn)行濾波。
1.3.1 正態(tài)分布

正態(tài)分布中,越接近中心點(diǎn),取值越大,越遠(yuǎn)離中心,取值越小。
計(jì)算平均值的時候,我們只需要將"中心點(diǎn)"作為原點(diǎn),其他點(diǎn)按照其在正態(tài)曲線上的位置,分配權(quán)重,就可以得到一個加權(quán)平均值。正態(tài)分布顯然是一種可取的權(quán)重分配模式。
1.3.2 高斯函數(shù)
如何反映出正態(tài)分布?則需要使用高函數(shù)來實(shí)現(xiàn)。
上面的正態(tài)分布是一維的,而對于圖像都是二維的,所以我們需要二維的正態(tài)分布。

正態(tài)分布的密度函數(shù)叫做"高斯函數(shù)"(Gaussian function)。它的一維形式是:

其中,μ是x的均值,σ是x的方差。因?yàn)橛?jì)算平均值的時候,中心點(diǎn)就是原點(diǎn),所以μ等于0。

根據(jù)一維高斯函數(shù),可以推導(dǎo)得到二維高斯函數(shù):

有了這個函數(shù) ,就可以計(jì)算每個點(diǎn)的權(quán)重了。
1.3.3 獲取權(quán)重矩陣
假定中心點(diǎn)的坐標(biāo)是(0,0),那么距離它最近的8個點(diǎn)的坐標(biāo)如下:

更遠(yuǎn)的點(diǎn)以此類推。
為了計(jì)算權(quán)重矩陣,需要設(shè)定σ的值。假定σ=1.5,則模糊半徑為1的權(quán)重矩陣如下:

這9個點(diǎn)的權(quán)重總和等于0.4787147,如果只計(jì)算這9個點(diǎn)的加權(quán)平均,還必須讓它們的權(quán)重之和等于1,因此上面9個值還要分別除以0.4787147,得到最終的權(quán)重矩陣。

除以總值這個過程也叫做”歸一問題“
目的是讓濾鏡的權(quán)重總值等于1。否則的話,使用總值大于1的濾鏡會讓圖像偏亮,小于1的濾鏡會讓圖像偏暗。
1.3.4 計(jì)算模糊值
有了權(quán)重矩陣,就可以計(jì)算高斯模糊的值了。
假設(shè)現(xiàn)有9個像素點(diǎn),灰度值(0-255)如下:

每個點(diǎn)乘以自己的權(quán)重值:

得到

將這9個值加起來,就是中心點(diǎn)的高斯模糊的值。
對所有點(diǎn)重復(fù)這個過程,就得到了高斯模糊后的圖像。對于彩色圖片來說,則需要對RGB三個通道分別做高斯模糊。
1.3.5 邊界值問題
既然是根據(jù)權(quán)重矩陣來進(jìn)行處理的

如果一個點(diǎn)處于邊界,周邊沒有足夠的點(diǎn),怎么辦?
- ① 對稱處理,就是把已有的點(diǎn)拷貝到另一面的對應(yīng)位置,模擬出完整的矩陣。
- ② 賦0,想象圖像是無限長的圖像的一部分,除了我們給定值的部分,其他部分的像素值都是0
- ③ 賦邊界值,想象圖像是無限制長,但是默認(rèn)賦值的不是0而是對應(yīng)邊界點(diǎn)的值
二. 高斯模糊實(shí)現(xiàn)與優(yōu)化
理解原理之后則可以做進(jìn)一步的實(shí)現(xiàn),從Android上來說。
2.1 構(gòu)建權(quán)重矩陣
public static double[][] getMatrix(int radius){
//根據(jù)radius創(chuàng)建權(quán)重矩陣.
int size = 2 * radius + 1;
double[][] matrix = new double[size][size];
double sigama = (double) radius / 3;
double sigamaDouble = 2 * sigama * sigama;
double sigamaPi = Math.PI * sigamaDouble;
int row = 0;
double sum = 0;
for(int i = -radius ; i <= radius ; i++){
int line = 0;
for(int j = -radius ; j <= radius ; j++){
double x = i * i;
double y = j * j;
matrix[row][line] = Math.exp(-(x + y)/sigamaDouble)/sigamaPi;
sum += matrix[row][line];
line++;
}
row++;
}
//歸一
for(int i = 0 ; i < size ; i++){
for(int j = 0 ; j < size ; j++){
matrix[i][j] /= sum;
}
}
return matrix;
}
對于第5行sigama的計(jì)算,參考正態(tài)分布曲線圖,可以知道 3σ 距離以外的點(diǎn),權(quán)重已經(jīng)微不足道了。反推即可知道當(dāng)模糊半徑為r時,取σ為 r/3 是一個比較合適的取值。
2.2 計(jì)算
//獲取權(quán)重矩陣
double[][] matrix = getMatrix(radius);
int width = scaleBitmap.getWidth();
int height = scaleBitmap.getHeight();
int[] currentPixels = new int[width * height];
scaleBitmap.getPixels(currentPixels, 0, width, 0, 0, width, height)
for(int i = 0 ; i < width ; i++){
for(int j = 0; j < height ; j++){
int red = 0;
int green = 0;
int blue = 0;
int x = i - radius;
int y = j - radius;
//先不處理邊界值
if(x >0 && y > 0 && (i+radius < width && j+radius < height)) {
for (int tempI = -radius; tempI <= radius; tempI++) {
for (int tempJ = -radius; tempJ <= radius; tempJ++) {
int color = currentPixels[(j + tempJ) * width + i + tempI];
red += (int) (Color.red(color) * matrix[tempI + radius][tempJ + radius]);
green += (int) (Color.green(color) * matrix[tempI + radius][tempJ + radius]);
blue += (int) (Color.blue(color) * matrix[tempI + radius][tempJ + radius]);
}
}
int color = currentPixels[j * width + i];
currentPixels[j * width + i] = Color.rgb(red, green, blue);
}
}
}
}
這里currentPixels則是圖像的像素矩陣。獲取到的則是圖片的像素的color,而通過Color對應(yīng)的方法則可以轉(zhuǎn)成對應(yīng)的RGB形式,
也可以直接
red = (p & 0xff0000) >> 16;
green = (p & 0x00ff00) >> 8;
blue = (p & 0x0000ff);
當(dāng)你跑程序后,你會等到懷疑世界。手機(jī)一些低端手機(jī)來說,手機(jī)性能是硬傷。而且上面的算法是最最最菜的算法,跑起來差不多要幾十秒甚至幾分鐘。
2.3 優(yōu)化
優(yōu)化上可以分為很多種,一種是從圖片像素上的優(yōu)化,一種是算法的優(yōu)化,另一種是調(diào)用層的優(yōu)化,從java層改為jni層實(shí)現(xiàn)
2.3.1 圖片像素上的優(yōu)化
前面可以看到算法的主要循環(huán)在于width,height,radius,那么可以從降低像素點(diǎn),也就是壓縮圖片上入手。既然模糊后的圖片相比于原圖來說是不清晰的,那么我也可以先對圖片做壓縮,然后再高斯,最終再放大,得到的結(jié)果也與原圖直接模糊結(jié)果一樣,都是不清晰的。當(dāng)然如果對于清晰度來說的話,可以通過模糊半徑radius來做調(diào)整。壓縮太大就比較模糊,可以通過減小radius,相反,壓縮太小則通過增加radius即可。
Matrix matrix = new Matrix();
matrix.setScale(0.1f , 0.1f);
Bitmap scaleBitmap = Bitmap.createBitmap(srcBitmap , 0 , 0 , srcBitmap.getWidth() , srcBitmap.getHeight() , matrix , true);
2.3.2 算法上的優(yōu)化
前面可以看到,當(dāng)前算法的復(fù)雜度則是 O(width×height×(2×radius)2),radius為模糊半徑。
前面講到的處理方式都是建立在二維的情況下進(jìn)行的。高斯模糊也可以在二維圖像上對兩個獨(dú)立的一維空間分別進(jìn)行計(jì)算,這叫作線性可分。這也就是說,使用二維矩陣變換得到的效果也可以通過在水平方向進(jìn)行一維高斯矩陣變換加上豎直方向的一維高斯矩陣變換得到。
回到前面一維高斯的計(jì)算公式

跟前面一樣,不過這里的權(quán)重矩陣變?yōu)橐痪S的
//根據(jù)radius創(chuàng)建權(quán)重矩陣.
int size = 2 * radius + 1;
double[] matrix = new double[size];
double sigama = (double) radius / 3;
double sigamaDouble = 2 * sigama * sigama;
double sqlPi = Math.sqrt(2 * Math.PI);
double sigamaPi = sigama * sqlPi;
int row = 0;
double sum = 0;
for(int i = -radius ; i <= radius ; i++){
double x = i * i;
matrix[row] = Math.exp(-x/sigamaDouble)/sigamaPi;
sum += matrix[row];
row++;
}
//歸一處理目的是讓權(quán)重總值等于1。
//否則的話,使用總值大于1的濾鏡會讓圖像偏亮,小于1的濾鏡會讓圖像偏暗。
for(int i = 0 ; i < size ; i++){
matrix[i] /= sum;
}
分別對橫縱方向進(jìn)行處理
double[] matrix = getOneMatrix(radius);
int width = scaleBitmap.getWidth();
int height = scaleBitmap.getHeight();
int[] currentPixels = new int[width * height];
int red[] = new int[width * height];
int green[] = new int[width * height];
int blue[] = new int[width * height];
scaleBitmap.getPixels(currentPixels, 0, width, 0, 0, width, height);
for(int j = 0 ; j < height ; j++){
for(int i = 0 ; i < width ; i++){
int n = 0;
int x = i - radius;
int y = j - radius;
//先過濾邊界值
if(x >=0 && y >= 0 && (i+radius < width && j+radius < height)) {
for (int temp = -radius; temp <= radius; temp++) {
int point = temp + i;
int colorPoint = j * width + point;
int color = currentPixels[colorPoint];
red[colorPoint] += Color.red(color) * matrix[n];
green[colorPoint] += Color.green(color) * matrix[n];
blue[colorPoint] += Color.blue(color) * matrix[n];
n++;
}
}
}
}
for(int i = 0 ; i < width ; i++){
for(int j = 0 ; j < height ; j++){
int n = 0;
int r = 0 , b = 0 , g = 0;
int x = i - radius;
int y = j - radius;
//先過濾邊界值
if(x >=0 && y >= 0 && (i+radius < width && j+radius < height)) {
for (int temp = -radius; temp <= radius; temp++) {
int currentPoint = (j + temp) * width + i;
Log.e(TAG, "temp = " + temp + " i = " + i + " j : " + j + " currentPoint = " + currentPoint
);
r += red[currentPoint] * matrix[n];
g += green[currentPoint] * matrix[n];
b += blue[currentPoint] * matrix[n];
n++;
}
currentPixels[j*width + i] = Color.rgb(r, g, b);
}
}
}
此時的時間復(fù)雜度則為 O(width×height×2×radius×2)對比前面來說則少了radius倍。
當(dāng)然這里算法的優(yōu)化只是其中之一,網(wǎng)上有很多優(yōu)化后的高斯模糊,比如android-stackblur,FastBlur等,FastBlur則是參考Javascript來做個實(shí)現(xiàn)的,不過FastBlur的實(shí)現(xiàn)使用了很多額外的內(nèi)存(它會復(fù)制整個位圖到一個緩充區(qū)中),因此它適用于小位圖,對于大圖來說則比較容易造成OOM
2.3.3 轉(zhuǎn)換到JNI層
對于Java與JNI來說,同樣的代碼在JNI層調(diào)用所耗的時間要比Java的調(diào)用要少的多,特別是在一些圖像算法,或者游戲邏輯的時候。
JNI層面來說能夠帶來性能提升,它可以突破VM的內(nèi)存限制,由自己來管理內(nèi)存,而Java的內(nèi)存管理全部由虛擬機(jī)來管理的。
三.RenderScript
官網(wǎng)介紹:RenderScript是Android平臺上用于運(yùn)行計(jì)算密集任務(wù)的框架。RenderScript主要是面向數(shù)據(jù)并行計(jì)算,當(dāng)然了,RenderScript中使用串行計(jì)算效率也很好。RenderScript是充分利用手機(jī)GPU,CPU的計(jì)算能力,讓開發(fā)者專注于算法而不在于調(diào)度。我們編寫的代碼無需關(guān)心具體的硬件的不同,都能寫出高性能的代碼。
RenderScript是基于C99語言的,我們需要通過寫一個RenderScript腳本來控制。
結(jié)合官網(wǎng)上來做個入門。
3.1 編寫rs內(nèi)核腳本
在項(xiàng)目的代碼目錄下(即src根目錄下/src/)創(chuàng)建rs文件,表示是腳本文件。這里新建一個image.rs,輸入
#pragma version(1)
#pragma rs java_package_name(com.rdc.zzh.stackblurtest)
uchar4 __attribute__((kernel)) invert(uchar4 in)
{
uchar4 out = in;
out.r =255- in.r;
out.g = 255-in.g;
out.b = 255-in.b;
return out;
}
- 對于第一行來說,#pragma version(1)是指版本號,表示當(dāng)前腳本所使用的版本,不過這里只能是1才是有效的,#pragma是標(biāo)記給編譯器看的。
- 同樣第二行,可以看出這里是告訴編譯器當(dāng)前應(yīng)用的包名。因?yàn)槊總€rs文件都會自動生成對應(yīng)的Java代碼,比如,我們新建的hello.rs文件,會自動生成ScriptC_hello類,因此,我們需要在rs聲明包的名稱。
- 這里使用到了
uchar4 __attribute__((kernel)) invert(uchar4 in)可以知道這是一個調(diào)用方法。
首先對于RenderScript來說它有兩種計(jì)算內(nèi)核形式,分別是 映射(mapping)內(nèi)核 與 減少(reduction)內(nèi)核。 - 參數(shù)類型,uchar4表示的是一個4字節(jié)的類型,uchar4 in中可以直接用in.r,in.g,in.b,in.a 取出對應(yīng)像素點(diǎn)的色值信息,每個值各占一個字節(jié),取值范圍則是在(0`255)也比較說得通。上面函數(shù)則表示對一個像素點(diǎn)做處理。
3.1.1 映射內(nèi)核mapping kernel
映射內(nèi)核:它是一個對相同維度的Allocations集合進(jìn)行操作的并行函數(shù)。通常是將一個輸入Allocations分配集轉(zhuǎn)成另一個輸出Allocations分配集。比如
uchar4 RS_KERNEL invert(uchar4 in, uint32_t x, uint32_t y) {
uchar4 out = in;
out.r = 255 - in.r;
out.g = 255 - in.g;
out.b = 255 - in.b;
return out;
}
這里將輸入in(傳遞的是Allocations),輸出則是另一個out。
在多數(shù)情況下,這與C語言的標(biāo)準(zhǔn)函數(shù)語法一樣,在這里 RS_KERNEL 它則是一個宏定義常量,表示的是一個映射內(nèi)核而不是一個可調(diào)用函數(shù)。它的定義為
#define RS_KERNEL __attribute__((kernel))
由上面例子可知道我們用的是映射內(nèi)核的形式,會有一個Allocations做為輸入和輸出。
此外,一個映射內(nèi)核可有一個或者多個輸入Allocations,有一個或者兩個Allocation輸出
3.1.2 減少內(nèi)核reduction kernel
減少內(nèi)核則是在相同維數(shù)的對輸入Allocations進(jìn)行操作的函數(shù)族。它主要用于“降維”一個輸入的Allocation集合成一個單獨(dú)的值。
下面則是一個減少內(nèi)核將輸入元素累加起來的例子
#pragma rs reduce(addint) accumulator(addintAccum)
static void addintAccum(int *accum, int val) {
*accum += val;
}
在這里例子中,#pragma rs reduce則是用于定義內(nèi)核的名字(這里表示的是addint內(nèi)核),而后面的addintAccum則表示它是一個accumulator方法,所有這樣的方法都應(yīng)該是static的。而一個減少內(nèi)核通常需要一個accumulator方法,它的返回值必須是void。
此外,一個減少內(nèi)核擁有一個或者多個輸入Allocation,但是沒有輸出Allocation。詳細(xì)內(nèi)容還得參考官方API文檔。
3.2 Java代碼調(diào)用
mInBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
mOutBitmap = Bitmap.createBitmap(mInBitmap.getWidth(),mInBitmap.getHeight(), mInBitmap.getConfig());
mSrcImageView.setImageBitmap(mInBitmap);
RenderScript rs = RenderScript.create(this);
mScript = new ScriptC_image(rs);
Allocation aIn = Allocation.createFromBitmap(rs, mInBitmap);
Allocation aOut = Allocation.createFromBitmap(rs, mInBitmap);
mScript.forEach_invert(aIn, aOut);
aOut.copyTo(mOutBitmap);
mDstImageView.setImageBitmap(mOutBitmap);
rs.destroy();
首先這里先創(chuàng)建一個RenderScript對象,接著則是將編寫的rs文件對應(yīng)的自動生成的java類ScriptC_image初始化,接著則是創(chuàng)建兩個Allocation,從名字可以看出是用來分配內(nèi)存,作為映射內(nèi)核的輸入和輸出,createFromBitmap則是根據(jù)Bitmap分配內(nèi)存,把Bitmap的像素值傳遞到Allocation里面。
這兩個Allocation的Element類型必須相同,在函數(shù)調(diào)用時RenderScript會檢查,如果不想同會拋異常。這里提到了Element,Elemtent是指Allocation里的一項(xiàng)。比如我們要處理的是Bitmap,則Element表示的類型是像素。
接著調(diào)用到forEach_invert,后面的invert則是我們在rs里面編寫的方法名字,這里則是映射到了ScriptC_image里面了,RenderScript會自動將aIn里的每個元素(Element)并行的去執(zhí)行invert函數(shù).得到的結(jié)果放入aOut里。最后調(diào)用Allocation的copyTo函數(shù)把計(jì)算的結(jié)果轉(zhuǎn)入到Bitmap中。
當(dāng)然這里所列舉的都是比較簡單的例子,很多關(guān)于圖像間的操作都可以編寫對應(yīng)的rs文件來進(jìn)行處理,RenderScript所涉及到的知識點(diǎn)比較多,詳細(xì)的還得多參考官網(wǎng)API。
3.3 RenderScript高斯模糊
前面可以看到,實(shí)現(xiàn)的關(guān)鍵在于編寫對應(yīng)的rs文件生成響應(yīng)的Script類,以此來進(jìn)行調(diào)用。官方也已經(jīng)給出了對應(yīng)高斯模糊的實(shí)現(xiàn)類ScriptIntrinsicBlur,使用時的調(diào)用則為
//先對圖片進(jìn)行壓縮然后再blur
Bitmap inputBitmap = Bitmap.createScaledBitmap(bitmap, Math.round(bitmap.getWidth() * bitmap_scale),
Math.round(bitmap.getHeight() * bitmap_scale), false);
//創(chuàng)建空的Bitmap用于輸出
Bitmap outputBitmap = Bitmap.createBitmap(inputBitmap);
//①、初始化Renderscript
RenderScript rs = RenderScript.create(context);
//②、Create an Intrinsic Blur Script using the Renderscript
ScriptIntrinsicBlur theIntrinsic = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
//③、native層分配內(nèi)存空間
Allocation tmpIn = Allocation.createFromBitmap(rs, inputBitmap);
Allocation tmpOut = Allocation.createFromBitmap(rs, outputBitmap);
//④、設(shè)置blur的半徑然后進(jìn)行blur
theIntrinsic.setRadius(blur_radius);
theIntrinsic.setInput(tmpIn);
theIntrinsic.forEach(tmpOut);
//⑤、拷貝blur后的數(shù)據(jù)到j(luò)ava緩沖區(qū)中
tmpOut.copyTo(outputBitmap);
//⑥、銷毀Renderscript
rs.destroy();
bitmap.recycle();
不過這里的限制條件則是API要大于17才可以調(diào)用,也可以自己導(dǎo)入一些v8兼容包。
3.4 對比
對于大圖來說,F(xiàn)astBlur的實(shí)現(xiàn)效果是不如RenderScript好的,甚至?xí)l(fā)生OOM問題。
而對于RenderScript來說,無論大圖小圖耗時則都是1050mm左右,而FastBlur在小圖的情況下可以達(dá)到1050mm,甚至比RenderScript要好
四.參考鏈接
圖片資料來源