Bitmap获取缩略图

前言

回顾了下以前写的调用相机和相册的功能,准备把它们整合下,想起曾经用魅族在获取大图时OOM的问题,决定重看一遍当初的解决方式。在获取缩略图步骤上发现了系统已经提供了工具类ThumbnailUtils,当然减少内存消耗不只有这一步。

先前获取缩略图的方法

public static Bitmap getThumbnail(Uri uri,int size, Context context) throws Exception {
    InputStream input = context.getContentResolver().openInputStream(uri);

    //配置BitmapFactory.Options,inJustDecodeBounds设为true,以获取图片的宽高
    BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options();
    onlyBoundsOptions.inJustDecodeBounds = true;
    onlyBoundsOptions.inDither=true;//optional
    onlyBoundsOptions.inPreferredConfig=Bitmap.Config.ARGB_8888;//optional

    //计算inSampleSize缩放比例
    BitmapFactory.decodeStream(input, null, onlyBoundsOptions);
    input.close();
    if ((onlyBoundsOptions.outWidth == -1) || (onlyBoundsOptions.outHeight == -1))
        return null;
    int originalSize = (onlyBoundsOptions.outHeight > onlyBoundsOptions.outWidth) ? onlyBoundsOptions.outHeight : onlyBoundsOptions.outWidth;
    double ratio = (originalSize > size) ? (originalSize / size) : 1.0;
    //获取到缩放比例后,再次设置BitmapFactory.Options,获取图片缩略图
    BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
    bitmapOptions.inSampleSize = getPowerOfTwoForSampleRatio(ratio);
    bitmapOptions.inDither=true;//optional
    bitmapOptions.inPreferredConfig=Bitmap.Config.ARGB_8888;//optional
    input = context.getContentResolver().openInputStream(uri);
    Bitmap bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions);
    input.close();
    return bitmap;
}

/**
 * 将double的比例采用近似值的方式转为int
 * @param ratio
 * @return
 */
private static int getPowerOfTwoForSampleRatio(double ratio){
    int k = Integer.highestOneBit((int)Math.floor(ratio));
    if(k==0) return 1;
    else return k;
}

总体思想是通过设置BitmapFactory.Options.inJustDecodeBounds设为true,先获取到图片的宽高而并不会生产Bitmap;再通过所需图片的最长边size来获取缩放比例inSampleSize的值;
然后获所需尺寸的图片。

获取缩放比例inSampleSize值的算法可以单独拉出一个方法,根据需求进行设置:

/**
 * 根据图片的Options和期望的图片大小来获取图片的缩小比例、
 * 如果图片的宽或高有一个大于目标值,就做处理;否则不做处理。
 * 关于inSampleSize需要注意,它只能是2的次方,否则它会取最接近2的次方的值。
 * @param options   目标图片的BitmapFactory.Options
 * @param expectationWidth 期望图片的宽
 * @param expectationHeight 期望图片的高
 * @return
 */
public static int sampleSize(BitmapFactory.Options options, int expectationWidth, int expectationHeight) {
    //首先获取图片的宽
    int rawWidth = options.outWidth;
    int rawHeight = options.outHeight;
    //在计算图片的sampleSize
    int inSampleSize  = 0;
    if (rawHeight > expectationHeight || rawWidth > expectationWidth) {
        float ratioHeight = rawHeight / expectationHeight;
        float rationWidth = rawWidth / expectationWidth;
        inSampleSize  = (int) Math.min(ratioHeight, rationWidth);
    }
    inSampleSize = Math.max(inSampleSize, 1);
    return inSampleSize ;
}

这种缩放是考虑短的边进行缩放控制,如果短边长度小于期望长度,不进行缩放。

通过上述方法成功获取到图片缩略图,但系统已经给我们提供获取方法,而且算法上考虑的情况更优秀,下面来看看。

ThumbnailUtils

获取图片缩略图,我们使用的方法是extractThumbnail,但主要实现方法是transform.我的是API23的源码。

 /**
 * Transform source Bitmap to targeted width and height.
 */
private static Bitmap transform(Matrix scaler,
        Bitmap source,
        int targetWidth,
        int targetHeight,
        int options) {
    //是否可以进行图片放大操作
    boolean scaleUp = (options & OPTIONS_SCALE_UP) != 0;
    //是否可以进行原图资源回收操作
    boolean recycle = (options & OPTIONS_RECYCLE_INPUT) != 0;

    int deltaX = source.getWidth() - targetWidth;
    int deltaY = source.getHeight() - targetHeight;
    
    /*
    *图片如果小于目标值,进行放大处理
    */
    if (!scaleUp && (deltaX < 0 || deltaY < 0)) {
        /*
        * In this case the bitmap is smaller, at least in one dimension,
        * than the target.  Transform it by placing as much of the image
        * as possible into the target and leaving the top/bottom or
        * left/right (or both) black.
        */
        Bitmap b2 = Bitmap.createBitmap(targetWidth, targetHeight,
        Bitmap.Config.ARGB_8888);
        Canvas c = new Canvas(b2);

        int deltaXHalf = Math.max(0, deltaX / 2);
        int deltaYHalf = Math.max(0, deltaY / 2);
        Rect src = new Rect(
        deltaXHalf,
        deltaYHalf,
        deltaXHalf + Math.min(targetWidth, source.getWidth()),
        deltaYHalf + Math.min(targetHeight, source.getHeight()));
        int dstX = (targetWidth  - src.width())  / 2;
        int dstY = (targetHeight - src.height()) / 2;
        Rect dst = new Rect(
                dstX,
                dstY,
                targetWidth - dstX,
                targetHeight - dstY);
        c.drawBitmap(source, src, dst, null);
        if (recycle) {
            source.recycle();
        }
        c.setBitmap(null);
        return b2;
    }
    
    /*
    *图片如果大于目标值,进行缩小处理
    */
    float bitmapWidthF = source.getWidth();
    float bitmapHeightF = source.getHeight();

    float bitmapAspect = bitmapWidthF / bitmapHeightF;
    float viewAspect   = (float) targetWidth / targetHeight;
    
    //获取缩放比例,如果原图宽高比大于目标宽高比,也就是原图变得更“窄”了
    //就用高度比例进行缩放,否则用宽度比例进行缩放。
    //效果上看就是将图片完全展示。
    if (bitmapAspect > viewAspect) {
        float scale = targetHeight / bitmapHeightF;
        if (scale < .9F || scale > 1F) {
            scaler.setScale(scale, scale);
        } else {
            scaler = null;
        }
    } else {
        float scale = targetWidth / bitmapWidthF;
        if (scale < .9F || scale > 1F) {
            scaler.setScale(scale, scale);
        } else {
            scaler = null;
        }
    }
    
    //根据缩放比例创建缩略图
    Bitmap b1;
    if (scaler != null) {
        // this is used for minithumb and crop, so we want to filter here.
        b1 = Bitmap.createBitmap(source, 0, 0,
        source.getWidth(), source.getHeight(), scaler, true);
    } else {
        b1 = source;
    }

    if (recycle && b1 != source) {
        source.recycle();
    }

    int dx1 = Math.max(0, b1.getWidth() - targetWidth);
    int dy1 = Math.max(0, b1.getHeight() - targetHeight);

    Bitmap b2 = Bitmap.createBitmap(
            b1,
            dx1 / 2,
            dy1 / 2,
            targetWidth,
            targetHeight);

    if (b2 != b1) {
        if (recycle || b1 != source) {
            b1.recycle();
        }
    }

    return b2;
}

可以看出,系统在效果上看就是将图片完全展示,算法上考虑的情况也更为丰富。原图的回收,是否放大,目标图片的宽高等考虑进去了。

总结

获取缩略图总的步骤就是:


graph TB
A{开始}-->B(获取原始图片宽高)
B --> C[根据算法获取缩放比例]
C --> D[根据缩放比例创建缩略图]


ThumbnailUtils的行数只有521行,还有些方法并有被使用到,感兴趣的可以去看看。
虽然ThumbnailUtils的代码不长,但学到了不同的实现方式和写作方式,了解了自己的不足。多看源码对自己的提升还是很有用的。

    原文作者:求闲居士
    原文地址: https://www.jianshu.com/p/2e74383441ba
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞