长文【Android模糊图片处理】

现在许多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 <mario at quasimondo.com>
    // http://incubator.quasimondo.com
    // created Feburary 29, 2004
    // Android port : Yahel Bouaziz <yahel at kayenko.com>
    // 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 <mario@quasimondo.com>

    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"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:layout_marginTop="20dp"
    tools:context="com.lihongxin.blog.test.testproject.MainActivity">

    <ImageView
        android:id="@+id/iv_blur"
        android:layout_width="match_parent"
        android:layout_height="360dp"
        android:scaleType="centerCrop"/>

    <ImageView
        android:id="@+id/iv_origin"
        android:layout_width="wrap_content"
        android:layout_height="360dp"
        android:layout_gravity="center_horizontal"
        android:scaleType="centerCrop" />
</FrameLayout>

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<GlideDrawable>() {
            @Override
            public void onResourceReady(final GlideDrawable glideDrawable, GlideAnimation<? super GlideDrawable> glideAnimation) {
                Glide.with(context).load(url)
                        .asBitmap().into(new SimpleTarget<Bitmap>() {
                    @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<Bitmap> {
    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<Bitmap> transform(Resource<Bitmap> 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<GlideDrawable>(){
                                @Override
                                public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {
                                    if (url.equals(vertical_view.getTag())) {
                                        blur_iv.setImageDrawable(resource);
                                    }

                                }
                            });

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容