毛玻璃效果在Android的实现 - Go语言中文社区

毛玻璃效果在Android的实现


本文已授权「玉刚说」微信公众号独家发布

毛玻璃效果实际上是对原图片的严重劣化,突出朦胧感,一般都是通过图片的缩放+模糊算法来实现,从性能角度考虑,模糊半径不能大于25,所以要更高的模糊效果则需要进行缩放。具体实现方案有以下几种。

  • Java实现,一般都是采用Stack模糊算法
  • RenderScript实现
  • Native实现
  • OpenCV或者OpenGL实现,由于其复杂度,本文暂不讨论该方案

关于模糊算法及上面各种方案的性能分析可以参考Android动态模糊实现的研究这篇文章

1、Java实现

Java代码实现毛玻璃效果基本上都是采用的Stack模糊算法,该算法比高斯模糊均值模糊算法更高效,效果更好。实现代码如下。

    public static Bitmap doBlur(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);
    }

由于是Java实现的,所以该方案不存在兼容性问题,也正因为是Java实现的,所以性能不会很好。因此该方案一般作为降级方案使用

2、RenderScript实现

RenderScript是一个在Android上以高性能运行计算密集型任务的框架。它对执行图像处理,计算摄影或计算机视觉的应用程序尤其有用。RenderScript提供了一个实现高斯模糊的类ScriptIntrinsicBlur,代码如下。

    public Bitmap blurBitmap(Bitmap bitmap, int radius) {
        //创建一个空bitmap,其大小与我们想要模糊的bitmap大小相同
        Bitmap outBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
        //实例化一个新的Renderscript
        RenderScript rs = RenderScript.create(getApplicationContext());
        //创建Allocation对象
        Allocation allIn = Allocation.createFromBitmap(rs, bitmap);
        Allocation allOut = Allocation.createFromBitmap(rs, outBitmap);
        //创建ScriptIntrinsicBlur对象,该对象实现了高斯模糊算法
        ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));

        //设置模糊半径,0 <radius <= 25
        blurScript.setRadius(radius);

        //执行Renderscript
        blurScript.setInput(allIn);
        blurScript.forEach(allOut);
        //将allOut创建的Bitmap复制到outBitmap
        allOut.copyTo(outBitmap);
        //释放内存占用
        bitmap.recycle();

        //销毁Renderscript。
        rs.destroy();
        return outBitmap;
    }

由于RenderScript的最低支持版本是11,但很多方法都是在17及以后添加的,所以使用RenderScript的最低版本应该为17。但如果要向下兼容则需要使用谷歌提供的向下兼容库——android.support.v8.renderscript。由于该库会明显增加APK大小,所以慎重使用。

关于更多RenderScript内容可以去官网查看。

3、开源项目

3.1、Blurry的使用

Blurry是GitHub一个比较热门的毛玻璃效果实现库。首先导入该库。

    dependencies {
        compile 'jp.wasabeef:blurry:3.x.x'
    }

由于该库并没有使用RenderScript的向下兼容库,所以不会导入一些so文件,也就不会增加APK大小

Blurry在使用上是非常简单的,只要使用过Glide,基本上就能快速上手。使用方式如下。

  //for ViewGroup
  Blurry.with(context)
    .radius(10)//模糊半径
    .sampling(8)//缩放大小,先缩小再放大
    .color(Color.argb(66, 255, 255, 0))//颜色
    .async()//是否异步
    .animate(500)//显示动画,目前仅支持淡入淡出,默认时间是300毫秒,仅支持传入控件为ViewGroup
    .onto(viewGroup);
  //for view
  Blurry.with(context)
    .radius(10)//模糊半径
    .sampling(8)//缩放大小,先缩小再放大
    .color(Color.argb(66, 255, 255, 0))//颜色
    .async()//是否异步
    .capture(view)//传入View
    .into(view);//显示View
  //for bitmap
  Blurry.with(context)
    .radius(10)//模糊半径
    .sampling(8)//缩放大小,先缩小再放大
    .color(Color.argb(66, 255, 255, 0))//颜色
    .async()//是否异步
    .from(bitmap)//传入bitmap
    .into(view);//显示View

想必到这里就能很熟练的使用Blurry了吧。前面介绍过毛玻璃的实现原理,那么Blurry是怎么来实现毛玻璃效果的尼?其实它就是通过RenderScript+Java来实现的。来看它的Blur类,在该类的of方法中实现了毛玻璃效果。

  public static Bitmap of(Context context, Bitmap source, BlurFactor factor) {
    int width = factor.width / factor.sampling;
    int height = factor.height / factor.sampling;
    
    if (Helper.hasZero(width, height)) {
      return null;
    }

    Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);

    Canvas canvas = new Canvas(bitmap);
    //进行缩放
    canvas.scale(1 / (float) factor.sampling, 1 / (float) factor.sampling);
    Paint paint = new Paint();
    paint.setFlags(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
    PorterDuffColorFilter filter =
        new PorterDuffColorFilter(factor.color, PorterDuff.Mode.SRC_ATOP);
    //设置颜色
    paint.setColorFilter(filter);
    canvas.drawBitmap(source, 0, 0, paint);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
      //如果当前sdk版本大于17则采用RenderScript实现毛玻璃效果
      try {
        bitmap = Blur.rs(context, bitmap, factor.radius);
      } catch (RSRuntimeException e) {
        //当RenderScript出现意外时,采用Java代码来实现毛玻璃效果
        bitmap = Blur.stack(bitmap, factor.radius, true);
      }
    } else {//如果当前sdk版本小于等于17则采用Java来实现毛玻璃效果
      bitmap = Blur.stack(bitmap, factor.radius, true);
    }
    ...
  }

可以发现上面代码是在对Bitmap缩放后进行处理的,由于RenderScript的兼容性限制,所以采用了Java实现作为降级方案,因此该库不会存在兼容性问题。实现效果如下。

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/lmh_19941113/article/details/88683457
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

  • 发表于 2020-03-01 19:55:47
  • 阅读 ( 915 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢