从Android 2.2开始系统新增了一个缩略图ThumbnailUtils类,位于framework的android.media.ThumbnailUtils位置,可以帮助我们从mediaprovider中获取系统中的视频或图片文件的缩略图,该类提供了三种静态方法可以直接调用获取。
static Bitmap createVideoThumbnail(String filePath, int kind) //获取视频文件的缩略图,第一个参数为视频文件的位置,比如/sdcard/android123.3gp,而第二个参数可以为MINI_KIND或 MICRO_KIND最终和分辨率有关
static Bitmap extractThumbnail(Bitmap source, int width, int height, int options) //直接对Bitmap进行缩略操作,最后一个参数定义为OPTIONS_RECYCLE_INPUT ,来回收资源
static Bitmap extractThumbnail(Bitmap source, int width, int height) // 这个和上面的方法一样,无options选项
最后Android开发网再次提醒大家,ThumbnailUtils类是API Level从8或更高才开始支持的。
Android缩略图类源代码
Android 2.2开始新增的缩略图类ThumbnailUtils的主要方法是静态的,对于Android 2.2或API Level8以下的工程可以直接使用,本类相对于我们常规的缩略图类考虑更周全,除了尺寸比例优化外,针对OOM的内存管理方面有更周全的处理方式,完整代码如下:
1: public class ThumbnailUtils {
2: private static final String TAG = "ThumbnailUtils";
3:
4: /* Maximum pixels size for created bitmap. */
5: private static final int MAX_NUM_PIXELS_THUMBNAIL = 512 * 384;
6: private static final int MAX_NUM_PIXELS_MICRO_THUMBNAIL = 128 * 128;
7: private static final int UNCONSTRAINED = -1;
8:
9: /* Options used internally. */
10: private static final int OPTIONS_NONE = 0x0;
11: private static final int OPTIONS_SCALE_UP = 0x1;
12:
13: /**
14: * Constant used to indicate we should recycle the input in
15: * {@link #extractThumbnail(Bitmap, int, int, int)} unless the output is the input.
16: */
17: public static final int OPTIONS_RECYCLE_INPUT = 0x2;
18:
19: /**
20: * Constant used to indicate the dimension of mini thumbnail.
21: * @hide Only used by media framework and media provider internally.
22: */
23: public static final int TARGET_SIZE_MINI_THUMBNAIL = 320;
24:
25: /**
26: * Constant used to indicate the dimension of micro thumbnail.
27: * @hide Only used by media framework and media provider internally.
28: */
29: public static final int TARGET_SIZE_MICRO_THUMBNAIL = 96;
30:
31: /**
32: * This method first examines if the thumbnail embedded in EXIF is bigger than our target
33: * size. If not, then it'll create a thumbnail from original image. Due to efficiency
34: * consideration, we want to let MediaThumbRequest avoid calling this method twice for
35: * both kinds, so it only requests for MICRO_KIND and set saveImage to true.
36: *
37: * This method always returns a "square thumbnail" for MICRO_KIND thumbnail.
38: *
39: * @param filePath the path of image file
40: * @param kind could be MINI_KIND or MICRO_KIND
41: * @return Bitmap
42: *
43: * @hide This method is only used by media framework and media provider internally.
44: */
45: public static Bitmap createImageThumbnail(String filePath, int kind) {
46: boolean wantMini = (kind == Images.Thumbnails.MINI_KIND);
47: int targetSize = wantMini
48: ? TARGET_SIZE_MINI_THUMBNAIL
49: : TARGET_SIZE_MICRO_THUMBNAIL;
50: int maxPixels = wantMini
51: ? MAX_NUM_PIXELS_THUMBNAIL
52: : MAX_NUM_PIXELS_MICRO_THUMBNAIL;
53: SizedThumbnailBitmap sizedThumbnailBitmap = new SizedThumbnailBitmap();
54: Bitmap bitmap = null;
55: MediaFileType fileType = MediaFile.getFileType(filePath);
56: if (fileType != null && fileType.fileType == MediaFile.FILE_TYPE_JPEG) {
57: createThumbnailFromEXIF(filePath, targetSize, maxPixels, sizedThumbnailBitmap);
58: bitmap = sizedThumbnailBitmap.mBitmap;
59: }
60:
61: if (bitmap == null) {
62: try {
63: FileDescriptor fd = new FileInputStream(filePath).getFD();
64: BitmapFactory.Options options = new BitmapFactory.Options();
65: options.inSampleSize = 1;
66: options.inJustDecodeBounds = true;
67: BitmapFactory.decodeFileDescriptor(fd, null, options);
68: if (options.mCancel || options.outWidth == -1
69: || options.outHeight == -1) {
70: return null;
71: }
72: options.inSampleSize = computeSampleSize(
73: options, targetSize, maxPixels);
74: options.inJustDecodeBounds = false;
75:
76: options.inDither = false;
77: options.inPreferredConfig = Bitmap.Config.ARGB_8888;
78: bitmap = BitmapFactory.decodeFileDescriptor(fd, null, options);
79: } catch (IOException ex) {
80: Log.e(TAG, "", ex);
81: }
82: }
83:
84: if (kind == Images.Thumbnails.MICRO_KIND) {
85: // now we make it a "square thumbnail" for MICRO_KIND thumbnail
86: bitmap = extractThumbnail(bitmap,
87: TARGET_SIZE_MICRO_THUMBNAIL,
88: TARGET_SIZE_MICRO_THUMBNAIL, OPTIONS_RECYCLE_INPUT);
89: }
90: return bitmap;
91: }
92:
93: /**
94: * Create a video thumbnail for a video. May return null if the video is
95: * corrupt or the format is not supported.
96: *
97: * @param filePath the path of video file
98: * @param kind could be MINI_KIND or MICRO_KIND
99: */
100: public static Bitmap createVideoThumbnail(String filePath, int kind) {
101: Bitmap bitmap = null;
102: MediaMetadataRetriever retriever = new MediaMetadataRetriever();
103: try {
104: retriever.setMode(MediaMetadataRetriever.MODE_CAPTURE_FRAME_ONLY);
105: retriever.setDataSource(filePath);
106: bitmap = retriever.captureFrame();
107: } catch (IllegalArgumentException ex) {
108: // Assume this is a corrupt video file
109: } catch (RuntimeException ex) {
110: // Assume this is a corrupt video file.
111: } finally {
112: try {
113: retriever.release();
114: } catch (RuntimeException ex) {
115: // Ignore failures while cleaning up.
116: }
117: }
118: if (kind == Images.Thumbnails.MICRO_KIND && bitmap != null) {
119: bitmap = extractThumbnail(bitmap,
120: TARGET_SIZE_MICRO_THUMBNAIL,
121: TARGET_SIZE_MICRO_THUMBNAIL,
122: OPTIONS_RECYCLE_INPUT);
123: }
124: return bitmap;
125: }
126:
127: /**
128: * Creates a centered bitmap of the desired size.
129: *
130: * @param source original bitmap source
131: * @param width targeted width
132: * @param height targeted height
133: */
134: public static Bitmap extractThumbnail(
135: Bitmap source, int width, int height) {
136: return extractThumbnail(source, width, height, OPTIONS_NONE);
137: }
138:
139: /**
140: * Creates a centered bitmap of the desired size.
141: *
142: * @param source original bitmap source
143: * @param width targeted width
144: * @param height targeted height
145: * @param options options used during thumbnail extraction
146: */
147: public static Bitmap extractThumbnail(
148: Bitmap source, int width, int height, int options) {
149: if (source == null) {
150: return null;
151: }
152:
153: float scale;
154: if (source.getWidth() < source.getHeight()) {
155: scale = width / (float) source.getWidth();
156: } else {
157: scale = height / (float) source.getHeight();
158: }
159: Matrix matrix = new Matrix();
160: matrix.setScale(scale, scale);
161: Bitmap thumbnail = transform(matrix, source, width, height,
162: OPTIONS_SCALE_UP | options);
163: return thumbnail;
164: }
165:
166: /*
167: * Compute the sample size as a function of minSideLength
168: * and maxNumOfPixels.
169: * minSideLength is used to specify that minimal width or height of a
170: * bitmap.
171: * maxNumOfPixels is used to specify the maximal size in pixels that is
172: * tolerable in terms of memory usage.
173: *
174: * The function returns a sample size based on the constraints.
175: * Both size and minSideLength can be passed in as IImage.UNCONSTRAINED,
176: * which indicates no care of the corresponding constraint.
177: * The functions prefers returning a sample size that
178: * generates a smaller bitmap, unless minSideLength = IImage.UNCONSTRAINED.
179: *
180: * Also, the function rounds up the sample size to a power of 2 or multiple
181: * of 8 because BitmapFactory only honors sample size this way.
182: * For example, BitmapFactory downsamples an image by 2 even though the
183: * request is 3. So we round up the sample size to avoid OOM.
184: */
185: private static int computeSampleSize(BitmapFactory.Options options,
186: int minSideLength, int maxNumOfPixels) {
187: int initialSize = computeInitialSampleSize(options, minSideLength,
188: maxNumOfPixels);
189:
190: int roundedSize;
191: if (initialSize <= 8 ) {
192: roundedSize = 1;
193: while (roundedSize < initialSize) {
194: roundedSize <<= 1;
195: }
196: } else {
197: roundedSize = (initialSize + 7) / 8 * 8;
198: }
199:
200: return roundedSize;
201: }
202:
203: private static int computeInitialSampleSize(BitmapFactory.Options options,
204: int minSideLength, int maxNumOfPixels) {
205: double w = options.outWidth;
206: double h = options.outHeight;
207:
208: int lowerBound = (maxNumOfPixels == UNCONSTRAINED) ? 1 :
209: (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
210: int upperBound = (minSideLength == UNCONSTRAINED) ? 128 :
211: (int) Math.min(Math.floor(w / minSideLength),
212: Math.floor(h / minSideLength));
213:
214: if (upperBound < lowerBound) {
215: // return the larger one when there is no overlapping zone.
216: return lowerBound;
217: }
218:
219: if ((maxNumOfPixels == UNCONSTRAINED) &&
220: (minSideLength == UNCONSTRAINED)) {
221: return 1;
222: } else if (minSideLength == UNCONSTRAINED) {
223: return lowerBound;
224: } else {
225: return upperBound;
226: }
227: }
228:
229: /**
230: * Make a bitmap from a given Uri, minimal side length, and maximum number of pixels.
231: * The image data will be read from specified pfd if it's not null, otherwise
232: * a new input stream will be created using specified ContentResolver.
233: *
234: * Clients are allowed to pass their own BitmapFactory.Options used for bitmap decoding. A
235: * new BitmapFactory.Options will be created if options is null.
236: */
237: private static Bitmap makeBitmap(int minSideLength, int maxNumOfPixels,
238: Uri uri, ContentResolver cr, ParcelFileDescriptor pfd,
239: BitmapFactory.Options options) {
240: Bitmap b = null;
241: try {
242: if (pfd == null) pfd = makeInputStream(uri, cr);
243: if (pfd == null) return null;
244: if (options == null) options = new BitmapFactory.Options();
245:
246: FileDescriptor fd = pfd.getFileDescriptor();
247: options.inSampleSize = 1;
248: options.inJustDecodeBounds = true;
249: BitmapFactory.decodeFileDescriptor(fd, null, options);
250: if (options.mCancel || options.outWidth == -1
251: || options.outHeight == -1) {
252: return null;
253: }
254: options.inSampleSize = computeSampleSize(
255: options, minSideLength, maxNumOfPixels);
256: options.inJustDecodeBounds = false;
257:
258: options.inDither = false;
259: options.inPreferredConfig = Bitmap.Config.ARGB_8888;
260: b = BitmapFactory.decodeFileDescriptor(fd, null, options);
261: } catch (OutOfMemoryError ex) {
262: Log.e(TAG, "Got oom exception ", ex);
263: return null;
264: } finally {
265: closeSilently(pfd);
266: }
267: return b;
268: }
269:
270: private static void closeSilently(ParcelFileDescriptor c) {
271: if (c == null) return;
272: try {
273: c.close();
274: } catch (Throwable t) {
275: // do nothing
276: }
277: }
278:
279: private static ParcelFileDescriptor makeInputStream(
280: Uri uri, ContentResolver cr) {
281: try {
282: return cr.openFileDescriptor(uri, "r");
283: } catch (IOException ex) {
284: return null;
285: }
286: }
287:
288: /**
289: * Transform source Bitmap to targeted width and height.
290: */
291: private static Bitmap transform(Matrix scaler,
292: Bitmap source,
293: int targetWidth,
294: int targetHeight,
295: int options) {
296: boolean scaleUp = (options & OPTIONS_SCALE_UP) != 0;
297: boolean recycle = (options & OPTIONS_RECYCLE_INPUT) != 0;
298:
299: int deltaX = source.getWidth() - targetWidth;
300: int deltaY = source.getHeight() - targetHeight;
301: if (!scaleUp && (deltaX < 0 || deltaY < 0)) {
302: /*
303: * In this case the bitmap is smaller, at least in one dimension,
304: * than the target. Transform it by placing as much of the image
305: * as possible into the target and leaving the top/bottom or
306: * left/right (or both) black.
307: */
308: Bitmap b2 = Bitmap.createBitmap(targetWidth, targetHeight,
309: Bitmap.Config.ARGB_8888);
310: Canvas c = new Canvas(b2);
311:
312: int deltaXHalf = Math.max(0, deltaX / 2);
313: int deltaYHalf = Math.max(0, deltaY / 2);
314: Rect src = new Rect(
315: deltaXHalf,
316: deltaYHalf,
317: deltaXHalf + Math.min(targetWidth, source.getWidth()),
318: deltaYHalf + Math.min(targetHeight, source.getHeight()));
319: int dstX = (targetWidth - src.width()) / 2;
320: int dstY = (targetHeight - src.height()) / 2;
321: Rect dst = new Rect(
322: dstX,
323: dstY,
324: targetWidth - dstX,
325: targetHeight - dstY);
326: c.drawBitmap(source, src, dst, null);
327: if (recycle) {
328: source.recycle();
329: }
330: return b2;
331: }
332: float bitmapWidthF = source.getWidth();
333: float bitmapHeightF = source.getHeight();
334:
335: float bitmapAspect = bitmapWidthF / bitmapHeightF;
336: float viewAspect = (float) targetWidth / targetHeight;
337:
338: if (bitmapAspect > viewAspect) {
339: float scale = targetHeight / bitmapHeightF;
340: if (scale < .9F || scale > 1F) {
341: scaler.setScale(scale, scale);
342: } else {
343: scaler = null;
344: }
345: } else {
346: float scale = targetWidth / bitmapWidthF;
347: if (scale < .9F || scale > 1F) {
348: scaler.setScale(scale, scale);
349: } else {
350: scaler = null;
351: }
352: }
353:
354: Bitmap b1;
355: if (scaler != null) {
356: // this is used for minithumb and crop, so we want to filter here.
357: b1 = Bitmap.createBitmap(source, 0, 0,
358: source.getWidth(), source.getHeight(), scaler, true);
359: } else {
360: b1 = source;
361: }
362:
363: if (recycle && b1 != source) {
364: source.recycle();
365: }
366:
367: int dx1 = Math.max(0, b1.getWidth() - targetWidth);
368: int dy1 = Math.max(0, b1.getHeight() - targetHeight);
369:
370: Bitmap b2 = Bitmap.createBitmap(
371: b1,
372: dx1 / 2,
373: dy1 / 2,
374: targetWidth,
375: targetHeight);
376:
377: if (b2 != b1) {
378: if (recycle || b1 != source) {
379: b1.recycle();
380: }
381: }
382:
383: return b2;
384: }
385:
386: /**
387: * SizedThumbnailBitmap contains the bitmap, which is downsampled either from
388: * the thumbnail in exif or the full image.
389: * mThumbnailData, mThumbnailWidth and mThumbnailHeight are set together only if mThumbnail
390: * is not null.
391: *
392: * The width/height of the sized bitmap may be different from mThumbnailWidth/mThumbnailHeight.
393: */
394: private static class SizedThumbnailBitmap {
395: public byte[] mThumbnailData;
396: public Bitmap mBitmap;
397: public int mThumbnailWidth;
398: public int mThumbnailHeight;
399: }
400:
401: /**
402: * Creates a bitmap by either downsampling from the thumbnail in EXIF or the full image.
403: * The functions returns a SizedThumbnailBitmap,
404: * which contains a downsampled bitmap and the thumbnail data in EXIF if exists.
405: */
406: private static void createThumbnailFromEXIF(String filePath, int targetSize,
407: int maxPixels, SizedThumbnailBitmap sizedThumbBitmap) {
408: if (filePath == null) return;
409:
410: ExifInterface exif = null;
411: byte [] thumbData = null;
412: try {
413: exif = new ExifInterface(filePath);
414: if (exif != null) {
415: thumbData = exif.getThumbnail();
416: }
417: } catch (IOException ex) {
418: Log.w(TAG, ex);
419: }
420:
421: BitmapFactory.Options fullOptions = new BitmapFactory.Options();
422: BitmapFactory.Options exifOptions = new BitmapFactory.Options();
423: int exifThumbWidth = 0;
424: int fullThumbWidth = 0;
425:
426: // Compute exifThumbWidth.
427: if (thumbData != null) {
428: exifOptions.inJustDecodeBounds = true;
429: BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length, exifOptions);
430: exifOptions.inSampleSize = computeSampleSize(exifOptions, targetSize, maxPixels);
431: exifThumbWidth = exifOptions.outWidth / exifOptions.inSampleSize;
432: }
433:
434: // Compute fullThumbWidth.
435: fullOptions.inJustDecodeBounds = true;
436: BitmapFactory.decodeFile(filePath, fullOptions);
437: fullOptions.inSampleSize = computeSampleSize(fullOptions, targetSize, maxPixels);
438: fullThumbWidth = fullOptions.outWidth / fullOptions.inSampleSize;
439:
440: // Choose the larger thumbnail as the returning sizedThumbBitmap.
441: if (thumbData != null && exifThumbWidth >= fullThumbWidth) {
442: int width = exifOptions.outWidth;
443: int height = exifOptions.outHeight;
444: exifOptions.inJustDecodeBounds = false;
445: sizedThumbBitmap.mBitmap = BitmapFactory.decodeByteArray(thumbData, 0,
446: thumbData.length, exifOptions);
447: if (sizedThumbBitmap.mBitmap != null) {
448: sizedThumbBitmap.mThumbnailData = thumbData;
449: sizedThumbBitmap.mThumbnailWidth = width;
450: sizedThumbBitmap.mThumbnailHeight = height;
451: }
452: } else {
453: fullOptions.inJustDecodeBounds = false;
454: sizedThumbBitmap.mBitmap = BitmapFactory.decodeFile(filePath, fullOptions);
455: }
456: }
457: }