现在许多app中都应用到了对图片的模糊处理,图片高斯模糊备受设计师的青睐,在各大知名APP中,如微信、手机QQ、网易云音乐等等都有对背景高斯图模糊的设计,还有今日头条、内涵段子等。


在Android 中,现在常用的图片高斯模糊技术的根本有两种:RenderScript 、fastBlur

1.RenderScript

RenderScript是在Android上的高性能运行密集型运算的框架。
是在Android3.0(API 11)引入的。而Android图片高斯模糊处理,通常也是用这个库来完成。它提供了我们Java层调用的API,实际上是在c/c++ 层来处理的,所以它的效率和性能通常是最高的。
缺点是:但是它只能在API 17或者更高的版本使用。当然也可以引用兼容包,但是会增加一点包的体积。并且模糊度有有限。

public class RSBlur {  @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)private static Bitmap rsBlur(Context context,Bitmap source,int radius){        Bitmap inputBmp = source;        //(1)初始化一个RenderScript Context:RenderScript 上下文环境通过create(Context)方法来创建,它保证RenderScript的使用并且提供一个控制后续所有RenderScript对象        RenderScript renderScript =  RenderScript.create(context);        //(2)通过Script至少创建一个Allocation:一个Allocation是提供存储大量可变数据的RenderScript 对象。        final Allocation input = Allocation.createFromBitmap(renderScript,inputBmp);        final Allocation output = Allocation.createTyped(renderScript,input.getType());        //(3)创建ScriptIntrinsic:它内置了RenderScript 的一些通用操作,如高斯模糊、扭曲变换、图像混合等等        ScriptIntrinsicBlur scriptIntrinsicBlur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript));        //(4)填充数据到Allocations:除了使用方法createFromBitmap创建的Allocation外,其它的第一次创建时都是填充的空数据。        scriptIntrinsicBlur.setInput(input);        //(5)设置模糊半径 (0---25)        scriptIntrinsicBlur.setRadius(radius);        //(6)启动内核,调用方法处理:调用forEach 方法模糊处理。        scriptIntrinsicBlur.forEach(output);        //(7)从Allocation 中拷贝数据:为了能在Java层访问Allocation的数据,用Allocation其中一个copy方法来拷贝数据。        output.copyTo(inputBmp);        //(8)销毁RenderScript对象        renderScript.destroy();        return inputBmp;    }}

2.fastBlur

fastBlur算法它直接在Java层做图片的模糊处理。对每个像素点应用高斯模糊计算、最后在合成Bitmap。就一个方法,使用这种方式不会有兼容性问题,也不会引入jar包导致APK变大。但是这种方法的效率是非常低的,想想也知道,因为是在Java 层处理,速度会更慢。将Bitmap全部加载到内存,较大图片容易OOM。

源码public class FastBlur {  public static Bitmap blur(Bitmap sentBitmap, int radius, boolean canReuseInBitmap) {    // 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     Bitmap bitmap;    if (canReuseInBitmap) {      bitmap = sentBitmap;    } else {      bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);    }    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);  }}
使用中需要考虑的问题:
  • sdk版本,如果是17或以下,就不能使用RenderScript
  • 模糊前应该对源bitmap进行缩放,图片缩小后再进行模糊处理,需要处理的像素点和半径都变小,从而使得模糊处理速度加快。(缩小的系数应该为2的整数次幂 ,即上面代码中的scale应该为1/2、1/4、1/8 ... 参考BitmapFactory.Options 对图片缩放 的inSample系数)

两者结合使用

public Bitmap blurBitmap(Context context,Bitmap source,int radius){//缩放       int width = source.getWidth();        int height = source.getHeight();        int scaledWidth = width / 4;        int scaledHeight = height / 4; Bitmap bitmap = Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888);   Canvas canvas = new Canvas(bitmap);        canvas.scale(1.0F / (float)this.mSampling, 1.0F / (float)this.mSampling);        Paint paint = new Paint();        paint.setFlags(2);        canvas.drawBitmap(source, 0.0F, 0.0F, paint);//模糊处理  Bitmap newBitmap;       if(Build.VERSION.SDK_INT >= 18) {            newBitmap = RSBlur.blur(context, bitmap, radius);        } else {            newBitmap = FastBlur.blur(bitmap,radius, true);        }        return newBitmap;}

【分割线】


大量加载and优化

当我们的需求只有对一张图片进行模糊当做背景这种情况,直接进行上面所述的模糊就好,不需要做缓存等处理。
然而当我们要在列表中加载大量的模糊图片时(就像内涵段子首页视频展示),就要考虑优化,缓存,不应该过多重复的进行模糊。否则对造成滑动卡顿。

通常我们用Glide加载一张图片,在Glide基础上也有了一个开源库glide-transformations,可以进行模糊处理,并增加缓存,对模糊类也做了封装,方便使用。

一个小demo

compile 'jp.wasabeef:glide-transformations:2.0.1'

xml<?xml version="1.0" encoding="utf-8"?>        
public class MainActivity extends AppCompatActivity {    private Context context;    private ImageView iv_blur;    private ImageView iv_origin;    private String url ="http://mvimg2.meitudata.com/583bc2f76f58a6875.jpg";    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        context=this;        setContentView(R.layout.activity_main);        this.iv_blur = (ImageView) findViewById(R.id.iv_blur);        this.iv_origin = (ImageView) findViewById(R.id.iv_origin);        Glide.with(context).load(url)                .bitmapTransform(new BlurTransformation(context, 23, 4)).into(new SimpleTarget() {            @Override            public void onResourceReady(final GlideDrawable glideDrawable, GlideAnimation<? super GlideDrawable> glideAnimation) {                Glide.with(context).load(url)                        .asBitmap().into(new SimpleTarget() {                    @Override                    public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {                        //resource为原图 glideDrawable为模糊处理后得到的                        //为了不改变原图的宽高,对原图resource进行等比例缩放                        Matrix matrix = new Matrix();                        float scale=resource.getHeight()/360 * context.getResources().getDisplayMetrics().density;                        matrix.postScale(scale, scale);                        Bitmap newResource=Bitmap.createBitmap(resource, 0, 0, resource.getWidth(), resource.getHeight(), matrix,                                true);                        iv_origin.setImageBitmap(resource);                        iv_blur.setImageDrawable(glideDrawable);                    }                });            }        });    }}

效果如下:


继续优化 可以看出我们进行了两次图片加载,一次用作背景模糊,一次用作原图显示,整体显得臃肿,还增加的请求的次数,所以我们可以重写自己的转换类,做我们自己的操作,把这两张图片进行合成。

如下:

public class MyBlurTransformation implements Transformation {    private static int MAX_RADIUS = 25;    private static int DEFAULT_DOWN_SAMPLING = 1;    private Context mContext;    private BitmapPool mBitmapPool;    private int mRadius;    private int mSampling;    private int bitmapWidth;    private int bitmapHeight;    public MyBlurTransformation(Context context, BitmapPool pool, int radius, int sampling                                ,int bitmapWidth,int bitmapHeight){        this.mContext = context.getApplicationContext();        this.mBitmapPool = pool;        this.mRadius = radius;        this.mSampling = sampling;        this.bitmapHeight=bitmapHeight;        this.bitmapWidth=bitmapWidth;    }    public MyBlurTransformation(Context context, int radius, int sampling            ,int bitmapWidth,int bitmapHeight) {        this(context, Glide.get(context).getBitmapPool(), radius, sampling,bitmapWidth,bitmapHeight);    }    @Override    public Resource transform(Resource resource, int outWidth, int outHeight) {        //source为原图 glide 的url        Bitmap source = (Bitmap)resource.get();        Bitmap composeBitmap= this.mBitmapPool.get(source.getWidth()/this.mSampling, source.getHeight()/this.mSampling, Bitmap.Config.ARGB_8888);        if (composeBitmap==null){            composeBitmap= composeBitmap(source);        }        return BitmapResource.obtain(composeBitmap, this.mBitmapPool);    }    private Bitmap composeBitmap(Bitmap source) {        int width = source.getWidth();        int height = source.getHeight();        int scaledWidth = width / this.mSampling;        int scaledHeight = height / this.mSampling;        Bitmap bitmap = this.mBitmapPool.get(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888);        if(bitmap == null) {            bitmap = Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888);        }        Canvas canvas = new Canvas(bitmap);        canvas.scale(1.0F / (float)this.mSampling, 1.0F / (float)this.mSampling);        Paint paint = new Paint();        paint.setFlags(2);        canvas.drawBitmap(source, 0.0F, 0.0F, paint);        if(Build.VERSION.SDK_INT >= 18) {            try {                bitmap = RSBlur.blur(this.mContext, bitmap, this.mRadius);            } catch (RSRuntimeException var13) {                bitmap = FastBlur.blur(bitmap, this.mRadius, true);            }        } else {            bitmap = FastBlur.blur(bitmap, this.mRadius, true);        }        Bitmap scaleGlideBitmap=scaleBlurBitmap(bitmap);        Bitmap scaleResourceBitmap=scaleOriginBitmap(source);        Bitmap newBitmap=Bitmap.createBitmap(bitmapWidth,bitmapHeight,scaleResourceBitmap.getConfig());        Canvas canvasnew=new Canvas(newBitmap);        canvasnew.drawBitmap(scaleGlideBitmap,0,0,null);        canvasnew.drawBitmap(scaleResourceBitmap,(bitmapWidth-scaleResourceBitmap.getWidth())/2,0,null);        canvasnew.save(Canvas.ALL_SAVE_FLAG);//保存        canvasnew.restore();//存储        return  newBitmap;    }    private Bitmap scaleOriginBitmap(Bitmap bkg) {        int originHeight = bkg.getHeight();        float scaleWidth = ((float) bitmapHeight) / originHeight;        Matrix matrix = new Matrix();        matrix.postScale(scaleWidth, scaleWidth);        return Bitmap.createBitmap(bkg, 0, 0, bkg.getWidth(), bkg.getHeight(), matrix,                true);    }    private Bitmap scaleBlurBitmap(Bitmap bitmap) {        float scaleWidth=(float) bitmapWidth/bitmap.getWidth();        float scaleHeight=(float) bitmapHeight/bitmap.getHeight();        Matrix matrix = new Matrix();        matrix.postScale(scaleWidth, scaleHeight);        return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix,                true);    }    @Override    public String getId() {        return "BlurTransformation(radius=" + this.mRadius + ", sampling=" + this.mSampling + ")";    }}

然后MainActivity使用就可以如下这么写 (去掉iv_origin,用一张图显示)

传入 也就是imageview的宽高,用作对原图的缩放bitmapWidth =bitmapHeight= 自己获取      Glide.with(mContext).load(url).bitmapTransform(new MyBlurTransformation(mContext,23, 4,bitmapWidth,bitmapHeight)).into(new SimpleTarget(){                                @Override                                public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {                                    if (url.equals(vertical_view.getTag())) {                                        blur_iv.setImageDrawable(resource);                                    }                                }                            });

以上例子只展示显示一张图,当然可以把它用在列表中,也已经做了缓存。

更多相关文章

  1. Android逐帧动画的简单使用-语音播放效果的实现
  2. Android(安卓)Studio—— 关于在Android(安卓)Studio中使用Asset
  3. Android图片压缩质量参数Bitmap.Config RGB_565、 ARGB_8888的含
  4. android中selector在java代码中使用无效的解决方案
  5. android HorizontalScrollView替代Gallery
  6. 【DiskLruCache完全解析】Android(安卓)AdapterView图片硬盘缓存
  7. Android裁剪图片总结
  8. Android开发之CheckedBox背景图片设置问题
  9. android 根据屏幕大小自行选择图片

随机推荐

  1. 最全C/C++教程 你需要的全都有!
  2. 使用Oracle Stream Analytics 21步搭建大
  3. 共同期待美好2020
  4. ES6 模块知识入门
  5. Java并发编程学习4-线程封闭和安全发布
  6. ISIS路由泄露,如何避免路由环路?
  7. 如何批量Ping N个IP地址,掌握一个命令让你
  8. H3C交换机命名规则
  9. 网工必知:用于监控企业网络的10款最佳工具
  10. 如何制作毛玻璃效果?