Galley、Camera中缩略图的生成

一,图库中缩略图的生成

图库中对图片、video 的解码会通过ImageCacheRequest.java来完成,具体在run方法中:

snapdragonGallery/src/com/android/gallery3d/data/ImageCacheRequest.java

public Bitmap run(JobContext jc) {
        ImageCacheService cacheService = mApplication.getImageCacheService();
        BytesBuffer buffer = MediaItem.getBytesBufferPool().get();
        try {
            boolean found = cacheService.getImageData(mPath, mTimeModified, mType, buffer);
            if (jc.isCancelled()) return null;
            if (found) {
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inPreferredConfig = Bitmap.Config.ARGB_8888;
                Bitmap bitmap;
                if (mType == MediaItem.TYPE_MICROTHUMBNAIL) {
                    bitmap = DecodeUtils.decodeUsingPool(jc,
                            buffer.data, buffer.offset, buffer.length, options);
                } else {
                    bitmap = DecodeUtils.decodeUsingPool(jc,
                            buffer.data, buffer.offset, buffer.length, options);
                }
                return bitmap;
            }
        } finally {
            MediaItem.getBytesBufferPool().recycle(buffer);
        }

        Bitmap bitmap = onDecodeOriginal(jc, mType);
        if (jc.isCancelled()) return null;

        if (mType == MediaItem.TYPE_MICROTHUMBNAIL) {
            bitmap = BitmapUtils.resizeAndCropCenter(bitmap, mTargetSize, true);
        } else {
            bitmap = BitmapUtils.resizeDownBySideLength(bitmap, mTargetSize, true);
        }
        if (jc.isCancelled()) return null;

        byte[] array = BitmapUtils.compressToBytes(bitmap);
        if (jc.isCancelled()) return null;

        cacheService.putImageData(mPath, mTimeModified, mType, array);
        return bitmap;
    }

首先是查缓存,如果没查询到才执行onDecodeOriginal,具体的数据源在继承ImageCacheRequest时,都会重写这个方法,如LocalImage.java,LocalVideo.java都有重写。

解除Bitmap后,根据缩略图的类型对其做裁剪,具体调用的是BitmapUtils.java中的方法resizeAndCropCenter或者resizeDownBySideLength。

最后把缩略图存入Cache。

1)图片缩略图的生成

LocalImage.java

public Bitmap onDecodeOriginal(JobContext jc, final int type) {
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inPreferredConfig = Bitmap.Config.ARGB_8888;

            int targetSize = MediaItem.getTargetSize(type);

            // try to decode from JPEG EXIF
            if (type == MediaItem.TYPE_MICROTHUMBNAIL) {
                ExifInterface exif = new ExifInterface();
                byte[] thumbData = null;
                try {
                    exif.readExif(mLocalFilePath);
                    thumbData = exif.getThumbnail();
                } catch (FileNotFoundException e) {
                    Log.w(TAG, "failed to find file to read thumbnail: " + mLocalFilePath);
                } catch (IOException e) {
                    Log.w(TAG, "failed to get thumbnail from: " + mLocalFilePath);
                }
                if (thumbData != null) {
                    Bitmap bitmap = DecodeUtils.decodeIfBigEnough(
                            jc, thumbData, options, targetSize);
                    if (bitmap != null) return bitmap;
                }
            }

            return DecodeUtils.decodeThumbnail(jc, mLocalFilePath, options, targetSize, type);
        }
    }

首选从Exif头中读取,没有读取到调用DecodeUtils的decodeThumbnail来生成Bitmap数据。

DecodeUtils.java

public static Bitmap decodeThumbnail(
            JobContext jc, FileDescriptor fd, Options options, int targetSize, int type) {
        if (options == null) options = new Options();
 
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFileDescriptor(fd, null, options);
        if (jc.isCancelled()) return null;

        int w = options.outWidth;
        int h = options.outHeight;

        if (type == MediaItem.TYPE_MICROTHUMBNAIL) {
            // We center-crop the original image as it's micro thumbnail. In this case,
            // we want to make sure the shorter side >= "targetSize".
            float scale = (float) targetSize / Math.min(w, h);
            options.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);

            // For an extremely wide image, e.g. 300x30000, we may got OOM when decoding
            // it for TYPE_MICROTHUMBNAIL. So we add a max number of pixels limit here.
            final int MAX_PIXEL_COUNT = 640000; // 400 x 1600
            if ((w / options.inSampleSize) * (h / options.inSampleSize) > MAX_PIXEL_COUNT) {
                options.inSampleSize = BitmapUtils.computeSampleSize(
                        (float) Math.sqrt((double) MAX_PIXEL_COUNT / (w * h)));
            }
        } else {
            // For screen nail, we only want to keep the longer side >= targetSize.
            float scale = (float) targetSize / Math.max(w, h);
            if (scale < 0.2) scale *= 2;
            Log.d(TAG, "decodeThumbnail scale=" + scale);
            options.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);
        }

        options.inJustDecodeBounds = false;
        setOptionsMutable(options);

        Bitmap result = BitmapFactory.decodeFileDescriptor(fd, null, options);
        if (result == null) return null;

        // We need to resize down if the decoder does not support inSampleSize
        // (For example, GIF images)
        float scale = (float) targetSize / (type == MediaItem.TYPE_MICROTHUMBNAIL
                ? Math.min(result.getWidth(), result.getHeight())
                : Math.max(result.getWidth(), result.getHeight()));

        if (scale <= 0.5) result = BitmapUtils.resizeBitmapByScale(result, scale, true);
        return ensureGLCompatibleBitmap(result);
    }

从文件路径或者文件输入流中获取到bitmap,然后对其做缩放处理,调用的是BitmapUtils.java中的resizeBitmapByScale方法。同时确保格式跟GL兼容。

BitmapUtils.java

    public static Bitmap resizeBitmapByScale(
            Bitmap bitmap, float scale, boolean recycle) {
        int width = Math.round(bitmap.getWidth() * scale);
        int height = Math.round(bitmap.getHeight() * scale);
        if (width == bitmap.getWidth()
                && height == bitmap.getHeight()) return bitmap;
        Bitmap target = Bitmap.createBitmap(width, height, getConfig(bitmap));
        Canvas canvas = new Canvas(target);
        canvas.scale(scale, scale);
        Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
        canvas.drawBitmap(bitmap, 0, 0, paint);
        if (recycle) bitmap.recycle();
        return target;
    }

2)视频的缩略图的生成

Video缩略图的生成跟图片类似,Video通常获取其第一帧作为缩略图,当然可以获取任意一帧。具体的是调用的BitmapUtils.java中的createVideoThumbnail方法。

public static Bitmap createVideoThumbnail(String filePath) {
        Class<?> clazz = null;
        Object instance = null;
        try {
            clazz = Class.forName("android.media.MediaMetadataRetriever");
            instance = clazz.newInstance();

            Method method = clazz.getMethod("setDataSource", String.class);
            method.invoke(instance, filePath);

            // The method name changes between API Level 9 and 10.
            if (Build.VERSION.SDK_INT <= 9) {
                return (Bitmap) clazz.getMethod("captureFrame").invoke(instance);
            } else {
                byte[] data = (byte[]) clazz.getMethod("getEmbeddedPicture").invoke(instance);
                if (data != null) {
                    Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
                    if (bitmap != null) return bitmap;
                }
                return (Bitmap) clazz.getMethod("getFrameAtTime").invoke(instance);
            }
        } catch (IllegalArgumentException ex) {
            // Assume this is a corrupt video file
        } 
        } finally {
            try {
                if (instance != null) {
                    clazz.getMethod("release").invoke(instance);
                }
            } catch (Exception ignored) {
            }
        }
        return null;
    }

二,Camera中缩略图的生成

snapdragoncamera/src/com/android/camera/CameraActivity.java

 private class UpdateThumbnailTask extends AsyncTask<Void, Void, Bitmap> {
        private final byte[] mJpegData;
        private final boolean mCheckOrientation;

        @Override
        protected Bitmap doInBackground(Void... params) {
            if (mJpegData != null)
                return decodeImageCenter(null);

            LocalDataAdapter adapter = getDataAdapter();
            ImageData img = adapter.getImageData(1);
            if (img == null) {
                return null;
            }
            Uri uri = img.getContentUri();
            if(mUri != null) uri = mUri;
            String path = getPathFromUri(uri);
            if (path == null) {
                return null;
            }
            Bitmap bitmap = null;
            if (CacheUtil.instance() != null) {
                bitmap = CacheUtil.instance().getBitmap(uri.toString());
            }
            if (bitmap != null) {
                return bitmap;
            } else if (path.contains("VID")) {
                return decodeIamgeFromVideo(uri.toString(),path);
            } else {
                return decodeImageCenter(path);
            }
        }

如果是图片调用decodeImageCenter,如果是video调用decodeIamgeFromVideo。

跟图库的做法大同小异:

        private synchronized Bitmap decodeImageCenter(final String path) {
            int orientation = 0;
            if (mCheckOrientation) {
                ExifInterface exif = new ExifInterface();
                try {
                    if (mJpegData != null) {
                        exif.readExif(mJpegData);
                    } else {
                        exif.readExif(path);
                    }
                    orientation = Exif.getOrientation(exif);
                } catch (IOException e) {
                    // ignore
                }
            }

            final BitmapFactory.Options opt = new BitmapFactory.Options();
            opt.inJustDecodeBounds = true;
            if (mJpegData != null) {
                BitmapFactory.decodeByteArray(mJpegData, 0, mJpegData.length, opt);
            } else {
                BitmapFactory.decodeFile(path, opt);
            }

            int w = opt.outWidth;
            int h = opt.outHeight;
            int d = w > h ? h : w;
            final Rect rect = w > h ? new Rect((w - h) / 2, 0, (w + h) / 2, h)
                    : new Rect(0, (h - w) / 2, w, (h + w) / 2);

            final int target = getResources().getDimensionPixelSize(R.dimen.capture_size);
            int sample = 1;
            if (d > target) {
                while (d / sample / 2 > target) {
                    sample *= 2;
                }
            }

            opt.inJustDecodeBounds = false;
            opt.inSampleSize = sample;
            final BitmapRegionDecoder decoder;
            try {
                if (mJpegData == null) {
                    decoder = BitmapRegionDecoder.newInstance(path, true);
                } else {
                    decoder = BitmapRegionDecoder.newInstance(mJpegData, 0, mJpegData.length, true);
                }
            } catch (IOException e) {
                return null;
            }
            Bitmap bitmap = decoder.decodeRegion(rect, opt);
            if (orientation != 0) {
                Matrix matrix = new Matrix();
                matrix.setRotate(orientation);
                bitmap = Bitmap.createBitmap(bitmap, 0, 0,
                        bitmap.getWidth(), bitmap.getHeight(), matrix, false);
            }
            return bitmap;
        }

video缩略图的生成,直接调用了系统api中的方法:

frameworks/base/media/java/android/media/ThumbnailUtils.java

        private synchronized Bitmap decodeIamgeFromVideo(final String uri, final String path) {
            Bitmap bitmap = ThumbnailUtils
                            .createVideoThumbnail(path, MediaStore.Video.Thumbnails.MINI_KIND);
            CacheUtil cacheUtil = CacheUtil.instance();
            if (bitmap != null && cacheUtil != null) {
                cacheUtil.put(uri, bitmap);
            }
            return bitmap;
        }

三,缩略图相关的概念

1)缩略图的类别

frameworks/base/core/java/android/provider/MediaStore.java

    private static class InternalThumbnails implements BaseColumns {
        private static final int MINI_KIND = 1;
        private static final int MICRO_KIND = 3;
}

InternalThumbnails这个内部类是供Images.Thumbnails 和 Video.Thumbnails使用的,外部无法访问,Images.Thumbnails 和 Video.Thumbnails都继承了InternalThumbnails。常用的类型是MINI_KIND,MICRO_KIND,还有一种int FULL_SCREEN_KIND = 2;没看到在哪里使用。

对应的最大像素值定义:

frameworks/base/media/java/android/media/ThumbnailUtils.java

public class ThumbnailUtils {
    /* Maximum pixels size for created bitmap. */
    private static final int MAX_NUM_PIXELS_THUMBNAIL = 512 * 384;
    private static final int MAX_NUM_PIXELS_MICRO_THUMBNAIL = 160 * 120;
    private static final int UNCONSTRAINED = -1;

    /* Options used internally. */
    private static final int OPTIONS_NONE = 0x0;
    private static final int OPTIONS_SCALE_UP = 0x1;

    /**
     * Constant used to indicate we should recycle the input in
     * {@link #extractThumbnail(Bitmap, int, int, int)} unless the output is the input.
     */
    public static final int OPTIONS_RECYCLE_INPUT = 0x2;

    /**
     * Constant used to indicate the dimension of mini thumbnail.
     * @hide Only used by media framework and media provider internally.
     */
    public static final int TARGET_SIZE_MINI_THUMBNAIL = 320;

    /**
     * Constant used to indicate the dimension of micro thumbnail.
     * @hide Only used by media framework and media provider internally.
     */
    public static final int TARGET_SIZE_MICRO_THUMBNAIL = 96;
}

mini_kind最大size是512*384,micro_kind最大size是160*120,但是实际看到的缩略图通常并不是这个尺寸,这是因为系统对缩略图做了处理,将他们做成了正方形的thumbnail。

如createImageThumbnail方法中:

frameworks/base/media/java/android/media/ThumbnailUtils.java

  public static Bitmap createImageThumbnail(String filePath, int kind) {
        boolean wantMini = (kind == Images.Thumbnails.MINI_KIND);
        int targetSize = wantMini
                ? TARGET_SIZE_MINI_THUMBNAIL
                : TARGET_SIZE_MICRO_THUMBNAIL;
        int maxPixels = wantMini
                ? MAX_NUM_PIXELS_THUMBNAIL
                : MAX_NUM_PIXELS_MICRO_THUMBNAIL;
        SizedThumbnailBitmap sizedThumbnailBitmap = new SizedThumbnailBitmap();
        Bitmap bitmap = null;
        MediaFileType fileType = MediaFile.getFileType(filePath);
        if (fileType != null && (fileType.fileType == MediaFile.FILE_TYPE_JPEG
                || MediaFile.isRawImageFileType(fileType.fileType))) {
            createThumbnailFromEXIF(filePath, targetSize, maxPixels, sizedThumbnailBitmap);
            bitmap = sizedThumbnailBitmap.mBitmap;
        }

        if (bitmap == null) {
            FileInputStream stream = null;
            try {
                stream = new FileInputStream(filePath);
                FileDescriptor fd = stream.getFD();
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inSampleSize = 1;
                options.inJustDecodeBounds = true;
                BitmapFactory.decodeFileDescriptor(fd, null, options);
                if (options.mCancel || options.outWidth == -1
                        || options.outHeight == -1) {
                    return null;
                }
                options.inSampleSize = computeSampleSize(
                        options, targetSize, maxPixels);
                options.inJustDecodeBounds = false;

                options.inDither = false;
                options.inPreferredConfig = Bitmap.Config.ARGB_8888;
                bitmap = BitmapFactory.decodeFileDescriptor(fd, null, options);
            } catch (IOException ex) {
                Log.e(TAG, "", ex);
            } catch (OutOfMemoryError oom) {
                Log.e(TAG, "Unable to decode file " + filePath + ". OutOfMemoryError.", oom);
            } finally {
                try {
                    if (stream != null) {
                        stream.close();
                    }
                } catch (IOException ex) {
                    Log.e(TAG, "", ex);
                }
            }

        }

        if (kind == Images.Thumbnails.MICRO_KIND) {
            // now we make it a "square thumbnail" for MICRO_KIND thumbnail
            bitmap = extractThumbnail(bitmap,
                    TARGET_SIZE_MICRO_THUMBNAIL,
                    TARGET_SIZE_MICRO_THUMBNAIL, OPTIONS_RECYCLE_INPUT);
        }
        return bitmap;
    }

默认正方形的大小是TARGET_SIZE_MINI_THUMBNAIL*TARGET_SIZE_MINI_THUMBNAIL(3208320),

如果是MICRO_KIND类型,正方形的大小是TARGET_SIZE_MICRO_THUMBNAIL*TARGET_SIZE_MICRO_THUMBNAIL(96*96),


对于Video缩略图,如果是MINI_KIND,最大是512,

 public static Bitmap createVideoThumbnail(String filePath, int kind) {
        Bitmap bitmap = null;
        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
        try {
            retriever.setDataSource(filePath);
            bitmap = retriever.getFrameAtTime(-1);
        }

        if (kind == Images.Thumbnails.MINI_KIND) {
            // Scale down the bitmap if it's too large.
            int width = bitmap.getWidth();
            int height = bitmap.getHeight();
            int max = Math.max(width, height);
            if (max > 512) {
                float scale = 512f / max;
                int w = Math.round(scale * width);
                int h = Math.round(scale * height);
                bitmap = Bitmap.createScaledBitmap(bitmap, w, h, true);
            }
        } else if (kind == Images.Thumbnails.MICRO_KIND) {
            bitmap = extractThumbnail(bitmap,
                    TARGET_SIZE_MICRO_THUMBNAIL,
                    TARGET_SIZE_MICRO_THUMBNAIL,
                    OPTIONS_RECYCLE_INPUT);
        }
        return bitmap;
    }

类型是MINI_KIND,如果宽、高的最大值超过512,将按512计算缩放比例。


2)在获取jpeg图片的缩略图时,有时会先从其exif中读取,其实exif就是jpeg格式文件的头部的信息内容,包含了拍摄时的参数,常见的有拍摄时间,宽高,缩略图等很多tag。

对应的类是frameworks/base/media/java/android/meida/ExifInterface.java,

图库和camera中都自定义这个类。

具体代码:

ExifInterface.java

    public byte[] getThumbnail() {
        return mData.getCompressedThumbnail();
    }

ExifData.java

    protected byte[] getCompressedThumbnail() {
        return mThumbnail;
    }


3)在生成缩略图的过程中,必然涉及的一个类是Bitmap,Bitmap是一个位图,扩展名可以是.bmp或者.dib。位图是Windows标准格式图形文件,它将图像定义为由点(像素)组成,每个点可以由多种色彩表示,包括2、4、8、16、24和32位色彩。例如,一幅1024×768分辨率的32位真彩图片,其所占存储字节数为:1024×768×32/(8*1024)=3072KB

位图文件图像效果好,但是非 压缩格式的,需要占用较大存储空间,不利于在网络上传送。jpg格式则恰好弥补了位图文件这个缺点。
Android中Bitmap是一个中转类,用它获取图像文件信息,然后借助Matrix进行缩放、旋转等处理后,在转成想要保存的格式。

创建一个Bitmap对象,通常是调用其CreateBitmap方法,对应的实现类:

frameworks/base/graphics/java/android/graphics/Bitmap.java

复制位图:

    public static Bitmap createBitmap(@NonNull Bitmap src) {
        return createBitmap(src, 0, 0, src.getWidth(), src.getHeight());
    }

复制位图,从源位图的x,y位置,截取w、h部分,创建新的位图

    public static Bitmap createBitmap(@NonNull Bitmap source, int x, int y, int width, int height) {
        return createBitmap(source, x, y, width, height, null, false);
    }

以指定宽、高,第三个参数是来自public enum Config {}@Bitmap.java 的枚举项,表示一个像素点要几位来存储。

    public static Bitmap createBitmap(int width, int height, @NonNull Config config) {
        return createBitmap(width, height, config, true);
    }

将源位图缩放为指定宽高

    public static Bitmap createScaledBitmap(@NonNull Bitmap src, int dstWidth, int dstHeight,
            boolean filter) {
        Matrix m = new Matrix();

        final int width = src.getWidth();
        final int height = src.getHeight();
        if (width != dstWidth || height != dstHeight) {
            final float sx = dstWidth / (float) width;
            final float sy = dstHeight / (float) height;
            m.setScale(sx, sy);
        }
        return Bitmap.createBitmap(src, 0, 0, width, height, m, filter);
    }


还有一种常用的创建Bitmap的方法是通过BitmapFactory类。常用接口:

/frameworks/base/graphics/java/android/graphics/BitmapFactory.java

public static Bitmap decodeFile(String pathName, Options opts) 
public static Bitmap decodeStream(InputStream is) 
 public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts) 
public static Bitmap decodeResource(Resources res, int id, Options opts)
public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts)

支持从文件路径,输入流,文件描述符,给定资源ID,字节数组来创建Bitmap,在使用这些接口时,通常要提供一个参数Options,这是BitmapFactory.java的内部类.

public static class Options {

这个值为true,表示在解码时不返回Bitmap,只返回bitmap 的尺寸,在只需要得到其宽高的情况下,这么做不用将其加载到内存,提高了效率。

    /**
         * If set to true, the decoder will return null (no bitmap), but
         * the <code>out...</code> fields will still be set, allowing the caller to
         * query the bitmap without having to allocate the memory for its pixels.
         */
        public boolean inJustDecodeBounds;

还有很多参数,都有注释。

......

}


4)ThumbnailUtils.java类,生成缩略图一种方式是前面提高的图库中的做法,生成bitmap,然后做缩放、裁剪得到。还有一种方式是就是调用ThumbnailUtils.java中的方法,这通常用在MediaProvider中,如内部类:MediaStore$Images$Thumbnails中:

MediaStore$Images$Thumbnails{

            public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
                    BitmapFactory.Options options) {
                return InternalThumbnails.getThumbnail(cr, origId,
                        InternalThumbnails.DEFAULT_GROUP_ID, kind, options,
                        EXTERNAL_CONTENT_URI, false);
            }

}

MediaStore$InternalThumbnails{

static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId, int kind,
                BitmapFactory.Options options, Uri baseUri, boolean isVideo) {
            Bitmap bitmap = null;
            
                // We probably run out of space, so create the thumbnail in memory.
                if (bitmap == null) {
                    
                    String filePath = c.getString(1);
                    if (filePath != null) {
                        if (isVideo) {
                            bitmap = ThumbnailUtils.createVideoThumbnail(filePath, kind);
                        } else {
                            bitmap = ThumbnailUtils.createImageThumbnail(filePath, kind);
                        }
                    }
                }
            } 
            return bitmap;
        }
    }
这里调用了ThumbnailUtils.java中的方法:createVideoThumbnail,createImageThumbnail

}

在计算图片缩略图时用到了一个方法computeSampleSize计算样本大小。

    private static int computeSampleSize(BitmapFactory.Options options,
            int minSideLength, int maxNumOfPixels) {
        int initialSize = computeInitialSampleSize(options, minSideLength,
                maxNumOfPixels);

        int roundedSize;
        if (initialSize <= 8 ) {
            roundedSize = 1;
            while (roundedSize < initialSize) {
                roundedSize <<= 1;
            }
        } else {
            roundedSize = (initialSize + 7) / 8 * 8;
        }

        return roundedSize;
    }

首先通过computeInitialSampleSize计算缩放比例,然后将样本大小转成BitmapFactory要求的格式(2的幂次方,或者8的倍数),这是函数注释的描述。

其中通过往左移位得到最接近initialSize数值的2的幂次方;通过 (initialSize + 7) / 8 * 8;得到最接近 initialSize数值的8的倍数,也就是先得到最接近的8的最小整数倍再乘以8。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值