Bitmap获取缩略图

作者: 求闲居士 | 来源:发表于2016-06-12 21:42 被阅读252次

    前言

    回顾了下以前写的调用相机和相册的功能,准备把它们整合下,想起曾经用魅族在获取大图时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的代码不长,但学到了不同的实现方式和写作方式,了解了自己的不足。多看源码对自己的提升还是很有用的。

    相关文章

      网友评论

        本文标题:Bitmap获取缩略图

        本文链接:https://www.haomeiwen.com/subject/nbtydttx.html