Glide 的 transformation

Glide 里面内置了一套图片改造机制,名叫 Transformation ;利用这套机制,我们可以轻松实现以下的效果:

Glide 的 transformation_第1张图片

调用的方法也很简单,就拿其中一个来举例:

RequestOptions requestOptions = new RequestOptions()
   .transforms(new CenterCrop(), new CircleCrop());
Glide.with(this)
     .load("图片url")
     .apply(requestOptions)
     .into(imageViewRes);

我们调用的 api 非常简单,但是我们需要知其所以然,因此我们有必要去深入源码去看下

源码分析

我们先来看下 CenterCropRoundedCorners 到底是怎么回事,然后我们会发现它们都是 Transformation 的子类,具体关系如下:

Glide 的 transformation_第2张图片

我们先来前面的几个类吧

Key

这是 Glide 用于生成加密签名的接口类,供子类去实现生成签名的方法:

public interface Key {
    /**
     * 添加一个加密的签名
     *
     * @param messageDigest 用于提供信息加密算法的功能,如 MD5 或 SHA 算法
     */
    void updateDiskCacheKey(MessageDigest messageDigest);
   
    @Override
    boolean equals(Object o);
   
    @Override
    int hashCode();
}
Transformation

Transformation(转换器),这个类可以说是 Glide 压缩裁剪图片的核心类,因为该类功能就是依据要求输出的宽高对原始资源进行压缩裁剪的转换

public interface Transformation<T> extends Key {
  
  /**
   * 转换原始资源并返回转换后的资源对象
   */
  Resource<T> transform(@NonNull Context context, @NonNull Resource<T> resource,
                          int outWidth, int outHeight);
}
BitmapTransformation

这个是 Transformation 的一个子类,它的功能就和它的名字一样,主要负责将图片资源转换为 Bitmap ,Glide 在这里处理了一些 Bitmap 复用的逻辑:

public abstract class BitmapTransformation implements Transformation<Bitmap> {
  @Override
    public final Resource<Bitmap> transform(
            Context context,Resource<Bitmap> resource, int outWidth, int outHeight) {
        if (!Util.isValidDimensions(outWidth, outHeight)) {
            throw new IllegalArgumentException(
                    "Cannot apply transformation on width: " + outWidth + " or height: " + outHeight + " less than or equal to zero and not Target.SIZE_ORIGINAL");
        }
        //获取Glide的Bitmap复用池
        BitmapPool bitmapPool = Glide.get(context).getBitmapPool();
        //从包装类中取出Bitmap
        Bitmap toTransform = resource.get();
        //对比得知这次图片转换过程中的图片裁剪尺寸
        int targetWidth = outWidth == Target.SIZE_ORIGINAL ? toTransform.getWidth() : outWidth;
        int targetHeight = outHeight == Target.SIZE_ORIGINAL ? toTransform.getHeight() : outHeight;
        //得到图片转换后的Bitmap对象
        Bitmap transformed = transform(bitmapPool, toTransform, targetWidth, targetHeight);
        //将Bitmap对象包裹起来进行传递
        final Resource<Bitmap> result;
        if (toTransform.equals(transformed)) {
            result = resource;
        } else {
            result = BitmapResource.obtain(transformed, bitmapPool);
        }
        return result;
    }
  
  /**
   * 转换原始资源并返回转换后的Bitmap对象
   * 供子类去实现
   */
   protected abstract Bitmap transform(
            BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight);
}

前面几个类都介绍完了,其实到这里大概也可以猜到了,CenterCropRoundedCorners 等,都是 BitmapTransformation 的子类,都是某种图片资源的压缩裁剪的具体实现:

public class CircleCrop extends BitmapTransformation {

    @Override
    protected Bitmap transform(BitmapPool pool,Bitmap toTransform, int outWidth, int outHeight) {
        //核心方法
        return TransformationUtils.circleCrop(pool, toTransform, outWidth, outHeight);
    }

    @Override
    public void updateDiskCacheKey(MessageDigest messageDigest) {
        messageDigest.update(ID_BYTES);
    }
}

去看源码的话,可以发现 CenterCropFitCenterCenterInside 的源码里面,最终都是调用了 TransformationUtils 里面的对应压缩裁剪方法来生成指定的 Bitmap 对象,基本可以认定 TransformationUtils 就是 Glide 用来压缩裁剪图片的工具类

TransformationUtils

从上面得知,TransformationUtils 就是 Glide 用来压缩裁剪图片的工具类,Glide 关于图片的压缩裁剪方法都是在这个工具类里面,建议大家去看下这里的源码,可以知道 Glide 对图片压缩裁剪的一些套路,这里就简单分析一两个方法吧,先看 circleCrop 里面的裁剪圆形图片的方法:

 /**
     * 使用图片混合模式(PorterDuff.Mode.SRC_IN)将图片转为圆形并调整到指定的宽度/高度
     *
     * @param pool     一个{@link BitmapPool} 对象,用于存储或返回准备转换的{@link Bitmap}对象
     * @param inBitmap 准备进行裁剪压缩的图片资源 {@link Bitmap}
     * @param width    转换后的目标宽度
     * @param height   转换后的目标高度
     * @return 圆形的  {@link Bitmap} 或者null
     */
    public static Bitmap circleCrop(BitmapPool pool,Bitmap inBitmap,
                                    int destWidth, int destHeight) {
        //计算出圆形的半径
        int destMinEdge = Math.min(destWidth, destHeight);
        float radius = destMinEdge / 2f;

        int srcWidth = inBitmap.getWidth();
        int srcHeight = inBitmap.getHeight();
        //计算出最大缩小倍数
        float scaleXv = destMinEdge / (float) srcWidth;
        float scaleYv = destMinEdge / (float) srcHeight;
        float maxScale = Math.max(scaleXv, scaleYv);
        //计算出裁剪的中心区域
        float scaledWidth = maxScale * srcWidth;
        float scaledHeight = maxScale * srcHeight;
        float left = (destMinEdge - scaledWidth) / 2f;
        float top = (destMinEdge - scaledHeight) / 2f;
        RectF destRect = new RectF(left, top, left + scaledWidth, top + scaledHeight);
        //从池中取出可复用的Bitmap(使用inBitmap重新绘制该复用的Bitmap)
        Bitmap toTransform = getAlphaSafeBitmap(pool, inBitmap);
        //从池中取出一个可服用的Bitmap对象用于绘制toTransform
        //减少内存抖动
        Bitmap.Config outConfig = getAlphaSafeConfig(inBitmap);
        Bitmap result = pool.get(destMinEdge, destMinEdge, outConfig);
        result.setHasAlpha(true);
        //上锁
        BITMAP_DRAWABLE_LOCK.lock();
        try {
            Canvas canvas = new Canvas(result);
            //将Canvas转为圆形Canvas
            canvas.drawCircle(radius, radius, radius, CIRCLE_CROP_SHAPE_PAINT);
            //在圆形Canvas上面绘制图片
            canvas.drawBitmap(toTransform, null, destRect, CIRCLE_CROP_BITMAP_PAINT);
            //清空Canvas
            clear(canvas);
        } finally {
            BITMAP_DRAWABLE_LOCK.unlock();
        }
        if (!toTransform.equals(inBitmap)) {
            //存入进行复用
            pool.put(toTransform);
        }
        return result;
    }

核心地方都上面都写好注释了,应该蛮好理解的,因为这里就只是对图片进行区域抠图以及进行了一次图片混合模式,不过这里是不是有什么问题呢?

是不是少了一些操作?是的,这里并没有对原图进行压缩裁剪的操作!

这就意味着这里是针对传入的图片进行直接操作的,如果我们直接使用 circleCrop 的话,有相对大的 OOM 风险,因为很有可能是直接对下载的原图进行直接操作,没有经过任何的压缩裁剪处理!

这也是为什么一开始贴的代码里面,会有一个 CenterCrop 的原因:

RequestOptions requestOptions = new RequestOptions()
   .transforms(new CenterCrop(), new CircleCrop());

如果无法确保操作图片的尺寸大小的情况下,建议配合具备压缩裁剪功能的工具类来使用,来看下 TransformationUtilscenterCrop 方法吧,CenterCrop 里面最终调用的核心方法就是这个:

 public static Bitmap centerCrop(BitmapPool pool,Bitmap inBitmap, int width,int height) {
        if (inBitmap.getWidth() == width && inBitmap.getHeight() == height) {
            return inBitmap;
        }
        //扩大或缩小的倍数
        final float scale;
        //x轴的平移距离
        final float dx;
        //y轴的平移距离
        final float dy;
        Matrix matrix = new Matrix();
        if (inBitmap.getWidth() * height > inBitmap.getHeight() * width) {
            scale = (float) height / (float) inBitmap.getHeight();
            dx = (width - inBitmap.getWidth() * scale) * 0.5f;
            dy = 0;
        } else {
            scale = (float) width / (float) inBitmap.getWidth();
            dx = 0;
            dy = (height - inBitmap.getHeight() * scale) * 0.5f;
        }
        //设置缩放(扩大)倍数
        matrix.setScale(scale, scale);
        //设置平移,确保绘制的是图片的中央部分
        matrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
        //取出复用的bitmap对象
        Bitmap result = pool.get(width, height, getNonNullConfig(inBitmap));
        //保持透明度一致
        TransformationUtils.setAlpha(inBitmap, result);
        //进行矩阵转换(也就是对图片进行压缩裁剪)
        applyMatrix(inBitmap, result, matrix);
        return result;
    }

    private static void applyMatrix(Bitmap inBitmap, Bitmap targetBitmap, Matrix matrix) {
        //上锁
        BITMAP_DRAWABLE_LOCK.lock();
        try {
            //新的画布
            Canvas canvas = new Canvas(targetBitmap);
            //在画布上绘制新的位图并使用matrix进行转换
            canvas.drawBitmap(inBitmap, matrix, DEFAULT_PAINT);
            //清空画布
            clear(canvas);
        } finally {
            BITMAP_DRAWABLE_LOCK.unlock();
        }
    }

可以看到 Glide 对图片进行压缩裁剪是通过 Matrix 来进行线性压缩的,这种压缩方法相对耗时,不过压缩过程一般都是在子线程里面,所以影响不怎么大

自定义Transformation

或者有人会觉得同时使用 CenterCropCircleCrop 会比较麻烦,那么可以自己去定义一个 Transformation , 针对静态图片,通过继承 BitmapTransformation 来实现自己的变换就可以了,比分说给圆形图片加个外边框:

public class CircleCropWithBorderCorp extends BitmapTransformation {
   private final Paint mPaint;

   public CircleCropWithBorderCorp(int dp) {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(dp);
        mPaint.setColor(Color.RED);
   }
  
  @Override
   protected Bitmap transform(BitmapPool pool,  Bitmap toTransform
     , int outWidth, int outHeight) {
        Bitmap bitmap = TransformationUtils.centerCrop(pool, toTransform, outWidth, outHeight);
        Bitmap result = TransformationUtils.circleCrop(pool, bitmap, outWidth, outHeight);
        int destMinEdge = Math.min(outWidth, outHeight);
        int radius = (int) (destMinEdge / 2f);
        Canvas canvas = new Canvas(result);
        canvas.drawCircle(radius, radius, radius - mPaint.getStrokeWidth() / 2, mPaint);
        return result;
    }
}

这样子就可以了,然后使用一下:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ImageView show = findViewById(R.id.iv_show);
        RequestOptions options = new RequestOptions().transform(
          new CircleCropWithBorderCorp(10));
        Glide.with(this)
          .load("图片url")
          .apply(options)
          .into(show);
    }

运行效果如下:

Glide 的 transformation_第3张图片

多个Transformation

这里还有一个疑问,通过自定义 transformation 可以将 CenterCropCircleCrop 的效果合并在一起,那么我们一开始配置:

RequestOptions requestOptions = new RequestOptions()
   .transforms(new CenterCrop(), new CircleCrop());

它们是怎么合并到一起的?再去源码看下,可以发现:

public RequestOptions transforms(@NonNull Transformation<Bitmap>... transformations) {
  return transform(new MultiTransformation<>(transformations), /*isRequired=*/ true);
}

它们都是传给 MultiTransformation 来做构建参数,然后去查看 MultiTransformation 的源码的话,就可以看到下面的代码:

public class MultiTransformation<T> implements Transformation<T> {
  //省去部分代码
   public Resource<T> transform(Context context, Resource<T> resource
                                , int outWidth, int outHeight) {
    Resource<T> previous = resource;
    //遍历全部到Transformation并不断迭代转换
    for (Transformation<T> transformation : transformations) {
      Resource<T> transformed = transformation.transform(context, previous, outWidth, outHeight);
      if (previous != null && !previous.equals(resource) && !previous.equals(transformed)) {
        previous.recycle();
      }
      previous = transformed;
    }
    return previous;
  }
  //省去部分代码
}

现在就很清楚了, MultiTransformation 是通过 for 循环对全部到 Transformation 进行迭代转换,从而将不同的 Transformation 的效果进行合并

你可能感兴趣的:(拆Glide系列,Android,WebView)