編輯:關於Android編程
這篇文章將給大家介紹android圖片處理的高效做法,大家有需求的時候可以參考一下。
首先我要說明一下本實例中實現的效果(我還不會制作gif圖,如果誰會的話,希望可以教一下我):通過手指對圖片的上下滑動,實現圖片的逐漸模糊效果。
找網上找了一張效果圖如下(侵權請通知刪除):
下面我來講解一下效果制作的思路。
首先是對圖像的模糊處理,最常見的模糊處理方式是高斯模糊,高斯模糊指定一個半徑radius,對於圖片上的每個像素點,以其為中心,有一個radius長的正方形(邊界點除外,但是可以使用對稱的方式計算),對於這個正方形上的每一個點,和權值(權值是根據正態分布函數計算出來的)相乘以後相加,再求平均,用該平均值代替中心點的值。
高斯模糊效率比較低,處理時間很長,github上有一個快速模糊的算法,接下來我們也會用到。
另外,android其實提供了一個高效的圖片處理庫RenderScript,使用這個庫我們也可以快速的進行圖片模糊。
下面來看我寫的,一個圖片模糊處理的類
public class BitmapBlurHelper { //縮放系數 public final static int SCALE = 8; /** * 模糊函數 * @param context * @param sentBitmap * @param radius * @return */ public static Bitmap doBlur(Context context, Bitmap sentBitmap, float radius) { if(sentBitmap==null) return null; if (radius <= 0 || radius > 25) radius = 25f;//范圍在1-25之間 if (radius<=6&&VERSION.SDK_INT > 16) {//經測試,radius大於6後,fastBlur效率更高,並且RenderScript在api11以上使用 Bitmap bitmap = Bitmap.createScaledBitmap(sentBitmap, sentBitmap.getWidth()/SCALE,sentBitmap.getHeight()/SCALE,false);//先縮放圖片,增加模糊速度 final RenderScript rs = RenderScript.create(context); final Allocation input = Allocation.createFromBitmap(rs, bitmap, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); final Allocation output = Allocation.createTyped(rs, input.getType()); final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); script.setRadius(radius); script.setInput(input); script.forEach(output); output.copyTo(bitmap); rs.destroy(); return bitmap; }else{//快速模糊 return fastBlur(sentBitmap,radius); } } /** * 快速模糊算法 * @param sbitmap * @param radiusf * @return * Stack Blur v1.0 from * http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html * Java Author: Mario Klingemann* http://incubator.quasimondo.com * created Feburary 29, 2004 * Android port : Yahel Bouaziz * http://www.kayenko.com * ported april 5th, 2012 * This is a compromise between Gaussian Blur and Box blur * It creates much better looking blurs than Box Blur, but is * 7x faster than my Gaussian Blur implementation. * I called it Stack Blur because this describes best how this * filter works internally: it creates a kind of moving stack * of colors whilst scanning through the image. Thereby it * just has to add one new block of color to the right side * of the stack and remove the leftmost color. The remaining * colors on the topmost layer of the stack are either added on * or reduced by one, depending on if they are on the right or * on the left side of the stack. * If you are using this algorithm in your code please add * the following line: * * Stack Blur Algorithm by Mario Klingemann */ public static Bitmap fastBlur(Bitmap sbitmap, float radiusf){ Bitmap bitmap = Bitmap.createScaledBitmap(sbitmap, sbitmap.getWidth()/SCALE,sbitmap.getHeight()/SCALE,false);//先縮放圖片,增加模糊速度 int radius = (int)radiusf; if (radius < 1) { return (null); } int w = bitmap.getWidth(); int h = bitmap.getHeight(); int[] pix = new int[w * h]; bitmap.getPixels(pix, 0, w, 0, 0, w, h); int wm = w - 1; int hm = h - 1; int wh = w * h; int div = radius + radius + 1; int r[] = new int[wh]; int g[] = new int[wh]; int b[] = new int[wh]; int rsum, gsum, bsum, x, y, i, p, yp, yi, yw; int vmin[] = new int[Math.max(w, h)]; int divsum = (div + 1) >> 1; divsum *= divsum; int dv[] = new int[256 * divsum]; for (i = 0; i < 256 * divsum; i++) { dv[i] = (i / divsum); } yw = yi = 0; int[][] stack = new int[div][3]; int stackpointer; int stackstart; int[] sir; int rbs; int r1 = radius + 1; int routsum, goutsum, boutsum; int rinsum, ginsum, binsum; for (y = 0; y < h; y++) { rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; for (i = -radius; i <= radius; i++) { p = pix[yi + Math.min(wm, Math.max(i, 0))]; sir = stack[i + radius]; sir[0] = (p & 0xff0000) >> 16; sir[1] = (p & 0x00ff00) >> 8; sir[2] = (p & 0x0000ff); rbs = r1 - Math.abs(i); rsum += sir[0] * rbs; gsum += sir[1] * rbs; bsum += sir[2] * rbs; if (i > 0) { rinsum += sir[0]; ginsum += sir[1]; binsum += sir[2]; } else { routsum += sir[0]; goutsum += sir[1]; boutsum += sir[2]; } } stackpointer = radius; for (x = 0; x < w; x++) { r[yi] = dv[rsum]; g[yi] = dv[gsum]; b[yi] = dv[bsum]; rsum -= routsum; gsum -= goutsum; bsum -= boutsum; stackstart = stackpointer - radius + div; sir = stack[stackstart % div]; routsum -= sir[0]; goutsum -= sir[1]; boutsum -= sir[2]; if (y == 0) { vmin[x] = Math.min(x + radius + 1, wm); } p = pix[yw + vmin[x]]; sir[0] = (p & 0xff0000) >> 16; sir[1] = (p & 0x00ff00) >> 8; sir[2] = (p & 0x0000ff); rinsum += sir[0]; ginsum += sir[1]; binsum += sir[2]; rsum += rinsum; gsum += ginsum; bsum += binsum; stackpointer = (stackpointer + 1) % div; sir = stack[(stackpointer) % div]; routsum += sir[0]; goutsum += sir[1]; boutsum += sir[2]; rinsum -= sir[0]; ginsum -= sir[1]; binsum -= sir[2]; yi++; } yw += w; } for (x = 0; x < w; x++) { rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; yp = -radius * w; for (i = -radius; i <= radius; i++) { yi = Math.max(0, yp) + x; sir = stack[i + radius]; sir[0] = r[yi]; sir[1] = g[yi]; sir[2] = b[yi]; rbs = r1 - Math.abs(i); rsum += r[yi] * rbs; gsum += g[yi] * rbs; bsum += b[yi] * rbs; if (i > 0) { rinsum += sir[0]; ginsum += sir[1]; binsum += sir[2]; } else { routsum += sir[0]; goutsum += sir[1]; boutsum += sir[2]; } if (i < hm) { yp += w; } } yi = x; stackpointer = radius; for (y = 0; y < h; y++) { // Preserve alpha channel: ( 0xff000000 & pix[yi] ) pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum]; rsum -= routsum; gsum -= goutsum; bsum -= boutsum; stackstart = stackpointer - radius + div; sir = stack[stackstart % div]; routsum -= sir[0]; goutsum -= sir[1]; boutsum -= sir[2]; if (x == 0) { vmin[y] = Math.min(y + r1, hm) * w; } p = x + vmin[y]; sir[0] = r[p]; sir[1] = g[p]; sir[2] = b[p]; rinsum += sir[0]; ginsum += sir[1]; binsum += sir[2]; rsum += rinsum; gsum += ginsum; bsum += binsum; stackpointer = (stackpointer + 1) % div; sir = stack[stackpointer]; routsum += sir[0]; goutsum += sir[1]; boutsum += sir[2]; rinsum -= sir[0]; ginsum -= sir[1]; binsum -= sir[2]; yi += w; } } bitmap.setPixels(pix, 0, w, 0, 0, w, h); return (bitmap); } }
如果我們不事先對圖片進行縮放,無論是調用快速模糊還是RenderScript,都會需要較長的計算時間,對於最大模糊效果,前者需要2000ms以上,後者需要需要500ms以上,這個效率顯然是不能接受的。
我們的模糊系數范圍是1-25,因為RenderScript的系數要求就是這個范圍(原因不得而知,但是超過了就會拋異常)。
對於圖片進行縮放以後,我發現了一個神奇的地方,就是快速模糊的效率居然趕上了RenderScript的效率,在radius為6以下,RenderScript較高,在20ms之內,而快速模糊需要200ms以內,但是在6以後,快速模糊只在20ms以內,而RenderScript則超過20ms,並且隨著radius的增大,兩者的差距也拉大。
所以在代碼中,我們根據6為邊界,分別使用兩者,另外RenderScript還要求在API 11以上才能使用。
OK,由來圖片模糊的處理方法,我們現在想實現圖片上的動態效果,簡單的思路就是監聽手指的移動,然後每次都講圖片進行模糊處理。
這種思路面臨一個困難,就是GPU繪制的速度超過了模糊算法的速度,也就是說模糊計算需要較長時間,這樣會造成程序的卡頓。
我的解決思路是,首先將圖片進行一次最大的模糊處理,得到一張最模糊的圖片,然後將清晰圖片(在下方)和模糊圖片(在上方)疊加,在手指移動過程中,修改模糊圖片的透明度,從而實現從清晰到透明的過渡效果。
怎麼實現圖片疊加呢?我使用了LayerDrawable這個類,並且構造了一個BlurDrawable類
/** * 模糊drawable */ public class BlurDrawable{ //上下兩層圖片 private Drawable[] array = new Drawable[2]; //層疊圖片 private LayerDrawable la; /** * @param context * @param res * @param bitmap */ public BlurDrawable(Context context,Resources res, Bitmap bitmap) { array[0] = new BitmapDrawable(res,bitmap); array[1] = new BitmapDrawable(res,BitmapBlurHelper.doBlur(context,bitmap,25));//生產模糊圖片 array[1].setAlpha(0); la = new LayerDrawable(array); la.setLayerInset(0, 0, 0, 0, 0);//層疊 la.setLayerInset(1, 0, 0, 0, 0); } /** * 返回層疊以後的圖片 * @return */ public LayerDrawable getBlurDrawable() { return la; } /** * 獲得模糊系數,本質上是透明度 * @return */ public int getBlur(){ return array[1].getAlpha(); } /** * 設置模糊系數 * @param alpha */ public void setBlur(int alpha){ array[1].setAlpha(alpha); } }
上面的代碼很簡單,相信大家也看得懂,最後就是為ImageView設置drawable,然後添加一個onClickListener
mBlurImage = (ImageView)findViewById(R.id.img); final Bitmap bp = BitmapFactory.decodeResource(getResources(), R.drawable.ssd); final BlurDrawable blurDrawable = new BlurDrawable(this, getResources(),bp); mBlurImage.setImageDrawable(blurDrawable.getBlurDrawable()); mBlurImage.setOnTouchListener(new View.OnTouchListener() { private float mLastY; @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mLastY = event.getY(); break; case MotionEvent.ACTION_MOVE: float y = event.getY(); float alphaDelt = (y - mLastY) / 50; int alpha = (int) (blurDrawable.getBlur() + alphaDelt); Log.i(time, alpha + ); if (alpha > 255) { alpha = 255; } else if (alpha < 0.0) { alpha = 0; } blurDrawable.setBlur(alpha); break; case MotionEvent.ACTION_UP: break; } return true; } });
只有在action_move過程最後,不斷修改blurDrawable的透明度就可以了,而且透明度改變方法我也提供了
Ok,到此為止,透明效果就實現了,大家看copy一下代碼來看一下,個人認為這段代碼是圖片模糊處理的較好實現例子。
使用Scroller實現絢麗的ListView左右滑動刪除Item效果這裡來給大家帶來使用Scroller的小例子,同時也能用來幫助初步解除的讀者更加熟悉的掌握Scrol
效果:需求:不論什麼領域,在模仿一個東西的時候,我們首先要對它進行需求提取,這樣才能保證做到”惟妙惟肖”。通過對QQ側滑功能的分析,提取出了以下需
簡介:Xposed框架是一款可以在不修改APK的情況下影響程序運行的框架服務,通過替換/system/bin/app_process程序控制zygote進程,使得app
Android系統進行升級的時候,有兩種途徑,一種是通過接口傳遞升級包路徑自動升級,升級完之後系統自動重啟;另一種是手動進入recovery模式下,選擇升級包進行升級,升