1. 程式人生 > >Android 下實現高效的模糊效果

Android 下實現高效的模糊效果

作者 | Dajavu

地址 | http://www.jianshu.com/p/4abce9d7b347

宣告 | 本文是 Dajavu 原創,已獲授權釋出,未經原作者允許請勿轉載

前言

其實有關 android 下實現圖片模糊的文章有很多,大多都是使用 renderscript 內建的 ScriptIntrinsicBlur 來實現的,這篇文章中的例子也不例外,但如果僅僅是呼叫一下 api 的話就沒必要去寫了。所以接下來會介紹均值模糊以及高斯模糊的原理、什麼是 renderscript 以及如何編寫 renderscript。最終的例子是將圖片高斯模糊處理後再呼叫自己編寫的 rs 對其增加一層蒙版效果(這裡會提到計算機是如何處理透明度以及顏色疊加的)。

具體實現

上面這張圖片是用於影象演算法測試的國際標準影象,使用這張圖片主要有兩個原因:

  1. 影象包含了各種細節、平滑區域、陰影和紋理,這些對測試各種影象處理演算法很有用。

  2. 影象裡是一個很迷人的女子。而影象處理領域裡的人大多為男性,可以吸引更多的人。

然而這張圖片其實出自 1972 年的 《花花公子》,所以上面給出的圖片並不完整,下面我們寫一個小 demo 來展示一下完整的圖片。

protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);

   imageView = (ImageView) findViewById(R.id.image);
   drag = (ImageView) findViewById(R.id.drag);
   SeekBar progressBar = (SeekBar) findViewById(R.id.seek);

   bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.rina);
   imageView.setImageBitmap(bitmap);
   imageView.post(new
Runnable() {
       @Override
       public void run() {
           float scale = bitmap.getWidth() * 1f / imageView.getWidth();
           mosaic = Bitmap.createBitmap(bitmap, (int) (drag.getX() * scale), (int) (drag.getY() * scale),
                   (int) (drag.getWidth() * scale), (int) (drag.getHeight() * scale));
           drag.setImageBitmap(BlurHelper.mosaic(mosaic, currentRadius));
       }
   });

   progressBar.setProgress(currentRadius * 5
);
   progressBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
       @Override
       public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
           if (progress % 5 == 0) {
               currentRadius = progress / 5;
               drag.setImageBitmap(BlurHelper.mosaic(mosaic, currentRadius));
           }
       }

       @Override
       public void onStartTrackingTouch(SeekBar seekBar) {

       }

       @Override
       public void onStopTrackingTouch(SeekBar seekBar) {

       }
   });

   drag.setOnTouchListener(new View.OnTouchListener() {
       @Override
       public boolean onTouch(View v, MotionEvent event) {
           switch (event.getAction()) {
               case MotionEvent.ACTION_DOWN:
                   xDown = event.getX();
                   yDown = event.getY();
                   break;
               case MotionEvent.ACTION_MOVE:
                   float targetX = event.getX() - xDown + v.getTranslationX();
                   float targetY = event.getY() - yDown + v.getTranslationY();
                   targetX = Math.min(Math.max(targetX, 0), imageView.getWidth() - drag.getWidth());
                   targetY = Math.min(Math.max(targetY, 0), imageView.getHeight() - drag.getHeight());
                   v.setTranslationX(targetX);
                   v.setTranslationY(targetY);
                   float scale = bitmap.getWidth() * 1f / imageView.getWidth();
                   b = Bitmap.createBitmap(bitmap, (int) (drag.getX() * scale), (int) (drag.getY() * scale),
                           (int) (drag.getWidth() * scale), (int) (drag.getHeight() * scale));
                   drag.setImageBitmap(BlurHelper.mosaic(mosaic, currentRadius));
                   break;
           }
           return true;
       }
   });
}

public static Bitmap mosaic(Bitmap bitmap, int radius) {
   if (radius == 0) return bitmap;
   final int width = bitmap.getWidth();
   final int height = bitmap.getHeight();
   final Bitmap outBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
   final int[] pixels = new int[width * height];
   bitmap.getPixels(pixels, 0, width, 0, 0, width, height);

   for (int i = 0; i < height; i++) {
       for (int j = 0; j < width; j++) {
           int x = j % radius;
           int y = i % radius;
           pixels[i * width + j] = pixels[(i - y) * width + j - x];
       }
   }
   outBitmap.setPixels(pixels, 0, width, 0, 0, width, height);
   return outBitmap;
}

因為原圖有點少兒不宜,所以我這邊手動給打了個碼,在原圖上蓋了一層馬賽克後的圖片,每次拖動後都會重新計算。其實馬賽克演算法也是一種模糊演算法,首先圖片其實是由很多畫素點組成的一個二維陣列(或者矩陣)。上面的馬賽克演算法只是遍歷了圖片的每一個畫素,然後在這個過程中對於給定的半徑將所有的畫素都設定成第一個畫素的值。

我們由此拋磚引玉引出均值模糊(box blur),和馬賽克演算法差不多,他是每一個畫素都取周圍畫素的平均值。演算法也比較簡單,如下:

public static Bitmap boxBlur(Bitmap bitmap, int radius) {
   final int width = bitmap.getWidth();
   final int height = bitmap.getHeight();
   final int[] pixels = new int[width * height];
   final int[] outPixels = new int[width * height];
   final Bitmap outBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());

   bitmap.getPixels(pixels, 0, width, 0, 0, width, height);

   //遍歷bitmap每一個畫素
   for (int i = 0; i < width; i++) {
       for (int j = 0; j < height; j++) {
           //取半徑為radius的矩形區域,並處理邊界情況
           final int left = i - radius < 0 ? 0 : i - radius;
           final int top = j - radius < 0 ? 0 : j - radius;
           final int right = i + radius > width ? width : i + radius;
           final int bottom = j + radius > height ? height : j + radius;

           //矩形區域總畫素
           final int count = (right - left) * (bottom - top);

           //分別求出矩形區域內rgb的總值
           int r = 0, g = 0, b = 0;
           for (int m = left; m < right; m++) {
               for (int n = top; n < bottom; n++) {
                   final int pixel = pixels[n * width + m];
                   r += Color.red(pixel);
                   g += Color.green(pixel);
                   b += Color.blue(pixel);
               }
           }
           //設定新的畫素為矩形區域內畫素的均值
           outPixels[j * width + i] = Color.rgb(r / count, g / count, b / count);
       }
   }
   outBitmap.setPixels(outPixels, 0, width, 0, 0, width, height);
   bitmap.recycle();
   return outBitmap;
}

上面這麼寫是為了看起來更清楚,他的時間複雜度為 O(n^2 * m^2)效率是極低的。anyway 進行均值模糊之後的效果如下圖(影象大小 300 * 260,模糊半徑 5 ):

可以看到模糊的效果並不是很平滑,仔細看可以看到一個個小格子。顯然取平均的方式並不是特別好,對於影象而言我們可以認為越靠近中心點與其關係越密切,而離中心點越遠的畫素相關程度也就越低,採用加權平均的方式似乎更合理一些。如果你是理科生的話,不知道你是否還記得高中數學書上提到過的正態分佈(高斯分佈)。下圖是正態分佈的函式曲線:

距離中心點越近,值就越大,完全符合我們的需求,可以作為計算平均時的權值,使用正態分佈曲線來進行模糊計算的方式就叫做高斯模糊(gaussian blur)。u = 0 時的二維高斯曲線的函式如下:

其中 sigma 決定了資料的離散程度,sigma 越大麴線越扁,反之依然。

下面我們用程式碼簡單實現一下,首先生成一個高斯模糊的概率矩陣:

private static float[][] makeGaussianBlurKernel(int radius, float sigma) {
   //根據公式先計算一下2 * sigma ^ 2 便於之後計算
   final float sigmaSquare2 = sigma * sigma * 2;

   //半徑是指中心點距離邊界的距離,所以如果半徑為1,則需要一個3 * 3的矩陣
   final int size = radius * 2 + 1;
   final float[][] matrix = new float[size][size];

   float sum = 0;
   int row = 0;
   for (int i = -radius; i <= radius; i++) {
       int column = 0;
       for (int j = -radius; j <= radius; j++) {
           //根據公式計算出值
           matrix[row][column] = (float) (1 / (Math.PI * sigmaSquare2)
                   * Math.exp(-(i * i + j * j) / sigmaSquare2));

           sum += matrix[row][column];
           column++;
       }
       row++;

   //算出均值,使總概率為1
   for (int i = 0; i < size; i++) {
       for (int j = 0; j < size; j++) {
           matrix[i][j] /= sum;
       }
   }
   return matrix;
}

接下來就是根據上面算出的矩陣對影象進行加權平均了,在這裡處理邊界情況的時候偷了個懶,假設越界後的畫素對於邊界是鏡面的。

public static Bitmap gaussianBlur(Bitmap bitmap, int radius) {
   final int width = bitmap.getWidth();
   final int height = bitmap.getHeight();
   final int[] pixels = new int[width * height];
   final int[] outPixels = new int[width * height];
   final Bitmap outBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
   final float[][] blurMatrix = makeGaussianBlurKernel(radius, radius / 2.5f);

   bitmap.getPixels(pixels, 0, width, 0, 0, width, height);

   for (int i = 0; i < width; i++) {
       for (int j = 0; j < height; j++) {
           final int left = i - radius;
           final int top = j - radius;
           final int right = i + radius;
           final int bottom = j + radius;

           int row = 0;
           float r = 0, g = 0, b = 0;

           for (int n = top; n <= bottom; n++) {
               int column = 0;
               int y = n;
               if (y >= height) y = height - 1 - (y - height);
               if (y < 0) y = -y;
               for (int m = left; m <= right; m++) {
                   

相關推薦

Android 實現高效模糊效果

作者 | Dajavu 地址 | http://www.jianshu.com/p/4abce9d7b347 宣告 | 本文是 Dajavu 原創,已獲授權釋出,未經原作者允許請勿轉載 前言 其實有關 android 下實現圖片模糊的文章有很多,大多都是使用

Android快速實現動態模糊效果

寫在前面現在,越來越多的App裡面使用了模糊效果,這種模糊效果稱之為高斯模糊。大家都知道,在Android平臺上進行模糊渲染是一個相當耗CPU也相當耗時的操作,一旦處理不好,卡頓是在所難免的。一般來說,考慮到效率,渲染一張圖片最好的方法是使用OpenGL,其次是使用C++/C

【iOS】iOS高斯模糊效果實現

其實有很多種實現方式,但是沒必要了解那麼多,簡單實用就行,選取一種效能相對來說比較好的方式 效果圖如下(高斯0.1): 程式碼: 需要匯入 #import <Accelerate/Accelerate.h> UIImage *ima

Android學習之BlurImageView實現影象模糊效果

今天看了jj大神的部落格,裡面寫了一篇關於基於universalImageLoader實現圖片載入控制元件BlurImageView,看了不甚欣喜,就寫篇博文記錄以記之,望大家共勉之。 第一件事,上效果圖,這樣大家才能真正瞭解這個控制元件的效果,大家看了之後一

Android實現一個簡單的計算器原始碼

下面的內容是關於Android下實現一個簡單的計算器的內容。 import android.app.Activity; import android.os.Bundle;import android.view.View;import android.widget.Button;import android.w

android TextView實現滾動顯示效果(列表中多個同時滾動,親測可用)

 在android中,如果設定了TextView控制元件為單行顯示,且顯示的文字太長的話,預設情況下會造成顯示不全的情況,這種情況下我們需要設定該控制元件屬性如下: <TextView android:id="@+id/tv1"

Android TextView 實現跑馬燈效果

自定義一個TextView控制元件 public class MarqueeTextView extends AppCompatTextView { public MarqueeTextView(Context context) { s

android:TextView實現文字走馬燈效果(欺騙系統獲取持久的焦點)

通常情況下我們想實現文字的走馬燈效果需要在xml檔案中這樣設定 <TextView android:layout_width="wrap_content"

AndroidAndroid開發實現帶有反彈效果,仿IOS反彈scrollview詳解教程

作者:程式設計師小冰,GitHub主頁:https://github.com/QQ986945193 新浪微博:http://weibo.com/mcxiaobing 首先給大家看一下我們今天這個最終實現的效果圖: 這個是ios中的反彈效果。當然我

IOS 使用CoreImage實現圖片模糊效果

//原始圖片 UIImage*image = [UIImage imageNamed:@"sourceImage.jpg"]; /*.....coreImage部分....*/ //CIImage(圖片輸入源類似於UIImage) C

Android -- RecyclerView實現頂部吸附效果

package com.qianmo.stickyitemdecoration.bean; import java.util.List; /** * Created by Administrator on 2017/3/3 0003. * E-Mail:[email protected]

android實現ListView高亮顯示

使用者點選ListView中的item時,有的時候我們想要選中的item高亮顯示,以便提醒使用者。原本以為只需要簡單設定就能實現,但是並非如此。下面我們來介紹如何讓選中的item高亮顯示。 (Sam注:方法一使用是簡單方便,但在使用過程中,如果列表的條目超過一屏,會出現多條

android View實現變暗效果

android專案中做一個預設圖片變暗,有焦點時變亮的效果。相信大家都能各種辦法,各種手段很容易的實現這個效果。這裡記錄下作者實現這個效果的過程及遇到的問題,僅供參考。 見下圖(注:因為是eclipse截圖,所以有點色差了,黃色變成藍色,不過暗亮的效果還是明顯的O(

Android探索之旅(第十四篇)Android實現炫酷效果的Demo(持續收錄中......)

浪起來!使用 drawBitmapMesh 實現模擬水波紋效果 簡書傳送門 三十秒實現QQ首頁動畫特效 BMoveView為RadioGroup新增移動的特

CSS實現背景模糊效果(高斯模糊

px值越大,越模糊,可以按照不同的類名給不同的模糊值,背景看起來會更好看一點 重點實現為: -webkit-filter: blur(10px); /* Chrome, Opera */ -moz-filter: blur(10px); -ms-

Android實現波浪球效果

波浪球的效果一直都是想模仿的物件,在最近一段時間裡模仿了這一介面,其實所用知識並不多。 1>波浪的效果是利用三角函式來實現的,在自定義view中建立容量為width的陣列,由y=Asin(Kx+T)+H得到每個x相對應的y值,然後存入數組裡面。 2>利用A

Android實現延遲載入效果

主要是分兩步實現,一是在xml佈局中使用ViewStub控制元件,再在java程式碼中實現。 1) 首先,只要是要將Android頁面中要進行延遲載入,那麼都要將該部分佈局載入到ViewStub中。 <FrameLayoutandroid:layout_width="

Android -- SpannableString 實現富文字效果用法全解析

先給你們看一下執行效果: 文末有Demo地址,感興趣的可以下載執行一下。 SpannableString 實現了CharSequence 和 Spannable 類 ,是字串的一種,所以其例項物件可以作為textvie.setText()的實參。

初學者---Android TextView實現跑馬燈效果

TextView實現跑馬燈效果 <TextView android:layout_width="100dip" android:layout_height="wrap_content"

一分鐘實現動態模糊效果(毛玻璃)

本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家釋出 本文轉載自 http://wl9739.github.io/   湫水長天的部落格 現在,越來越多的App裡面使用了模糊效果,我尤其喜歡雅虎天氣的介面,上滑的時候背景圖片會跟著移動,最重要的是背景圖片