編輯:關於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一下代碼來看一下,個人認為這段代碼是圖片模糊處理的較好實現例子。
詳解Android中實現ListView左右滑動刪除條目的方法
使用Scroller實現絢麗的ListView左右滑動刪除Item效果這裡來給大家帶來使用Scroller的小例子,同時也能用來幫助初步解除的讀者更加熟悉的掌握Scrol
HorizontalScrollView仿QQ側滑刪除
效果:需求:不論什麼領域,在模仿一個東西的時候,我們首先要對它進行需求提取,這樣才能保證做到”惟妙惟肖”。通過對QQ側滑功能的分析,提取出了以下需
Hook框架xposed的簡單demo
簡介:Xposed框架是一款可以在不修改APK的情況下影響程序運行的框架服務,通過替換/system/bin/app_process程序控制zygote進程,使得app
Android系統OTA升級流程
Android系統進行升級的時候,有兩種途徑,一種是通過接口傳遞升級包路徑自動升級,升級完之後系統自動重啟;另一種是手動進入recovery模式下,選擇升級包進行升級,升