OSDN Git Service

original
[gb-231r1-is01/Gingerbread_2.3.3_r1_IS01.git] / frameworks / base / media / java / android / media / ThumbnailUtils.java
1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package android.media;
18
19 import android.content.ContentResolver;
20 import android.content.ContentUris;
21 import android.content.ContentValues;
22 import android.database.Cursor;
23 import android.graphics.Bitmap;
24 import android.graphics.BitmapFactory;
25 import android.graphics.Canvas;
26 import android.graphics.Matrix;
27 import android.graphics.Rect;
28 import android.media.MediaMetadataRetriever;
29 import android.media.MediaFile.MediaFileType;
30 import android.net.Uri;
31 import android.os.ParcelFileDescriptor;
32 import android.provider.BaseColumns;
33 import android.provider.MediaStore.Images;
34 import android.provider.MediaStore.Images.Thumbnails;
35 import android.util.Log;
36
37 import java.io.FileInputStream;
38 import java.io.FileDescriptor;
39 import java.io.IOException;
40 import java.io.OutputStream;
41
42 /**
43  * Thumbnail generation routines for media provider.
44  */
45
46 public class ThumbnailUtils {
47     private static final String TAG = "ThumbnailUtils";
48
49     /* Maximum pixels size for created bitmap. */
50     private static final int MAX_NUM_PIXELS_THUMBNAIL = 512 * 384;
51     private static final int MAX_NUM_PIXELS_MICRO_THUMBNAIL = 128 * 128;
52     private static final int UNCONSTRAINED = -1;
53
54     /* Options used internally. */
55     private static final int OPTIONS_NONE = 0x0;
56     private static final int OPTIONS_SCALE_UP = 0x1;
57
58     /**
59      * Constant used to indicate we should recycle the input in
60      * {@link #extractThumbnail(Bitmap, int, int, int)} unless the output is the input.
61      */
62     public static final int OPTIONS_RECYCLE_INPUT = 0x2;
63
64     /**
65      * Constant used to indicate the dimension of mini thumbnail.
66      * @hide Only used by media framework and media provider internally.
67      */
68     public static final int TARGET_SIZE_MINI_THUMBNAIL = 320;
69
70     /**
71      * Constant used to indicate the dimension of micro thumbnail.
72      * @hide Only used by media framework and media provider internally.
73      */
74     public static final int TARGET_SIZE_MICRO_THUMBNAIL = 96;
75
76     /**
77      * This method first examines if the thumbnail embedded in EXIF is bigger than our target
78      * size. If not, then it'll create a thumbnail from original image. Due to efficiency
79      * consideration, we want to let MediaThumbRequest avoid calling this method twice for
80      * both kinds, so it only requests for MICRO_KIND and set saveImage to true.
81      *
82      * This method always returns a "square thumbnail" for MICRO_KIND thumbnail.
83      *
84      * @param filePath the path of image file
85      * @param kind could be MINI_KIND or MICRO_KIND
86      * @return Bitmap
87      *
88      * @hide This method is only used by media framework and media provider internally.
89      */
90     public static Bitmap createImageThumbnail(String filePath, int kind) {
91         boolean wantMini = (kind == Images.Thumbnails.MINI_KIND);
92         int targetSize = wantMini
93                 ? TARGET_SIZE_MINI_THUMBNAIL
94                 : TARGET_SIZE_MICRO_THUMBNAIL;
95         int maxPixels = wantMini
96                 ? MAX_NUM_PIXELS_THUMBNAIL
97                 : MAX_NUM_PIXELS_MICRO_THUMBNAIL;
98         SizedThumbnailBitmap sizedThumbnailBitmap = new SizedThumbnailBitmap();
99         Bitmap bitmap = null;
100         MediaFileType fileType = MediaFile.getFileType(filePath);
101         if (fileType != null && fileType.fileType == MediaFile.FILE_TYPE_JPEG) {
102             createThumbnailFromEXIF(filePath, targetSize, maxPixels, sizedThumbnailBitmap);
103             bitmap = sizedThumbnailBitmap.mBitmap;
104         }
105
106         if (bitmap == null) {
107             try {
108                 FileDescriptor fd = new FileInputStream(filePath).getFD();
109                 BitmapFactory.Options options = new BitmapFactory.Options();
110                 options.inSampleSize = 1;
111                 options.inJustDecodeBounds = true;
112                 BitmapFactory.decodeFileDescriptor(fd, null, options);
113                 if (options.mCancel || options.outWidth == -1
114                         || options.outHeight == -1) {
115                     return null;
116                 }
117                 options.inSampleSize = computeSampleSize(
118                         options, targetSize, maxPixels);
119                 options.inJustDecodeBounds = false;
120
121                 options.inDither = false;
122                 options.inPreferredConfig = Bitmap.Config.ARGB_8888;
123                 bitmap = BitmapFactory.decodeFileDescriptor(fd, null, options);
124             } catch (IOException ex) {
125                 Log.e(TAG, "", ex);
126             }
127         }
128
129         if (kind == Images.Thumbnails.MICRO_KIND) {
130             // now we make it a "square thumbnail" for MICRO_KIND thumbnail
131             bitmap = extractThumbnail(bitmap,
132                     TARGET_SIZE_MICRO_THUMBNAIL,
133                     TARGET_SIZE_MICRO_THUMBNAIL, OPTIONS_RECYCLE_INPUT);
134         }
135         return bitmap;
136     }
137
138     /**
139      * Create a video thumbnail for a video. May return null if the video is
140      * corrupt or the format is not supported.
141      *
142      * @param filePath the path of video file
143      * @param kind could be MINI_KIND or MICRO_KIND
144      */
145     public static Bitmap createVideoThumbnail(String filePath, int kind) {
146         Bitmap bitmap = null;
147         MediaMetadataRetriever retriever = new MediaMetadataRetriever();
148         try {
149             retriever.setDataSource(filePath);
150             bitmap = retriever.getFrameAtTime(-1);
151         } catch (IllegalArgumentException ex) {
152             // Assume this is a corrupt video file
153         } catch (RuntimeException ex) {
154             // Assume this is a corrupt video file.
155         } finally {
156             try {
157                 retriever.release();
158             } catch (RuntimeException ex) {
159                 // Ignore failures while cleaning up.
160             }
161         }
162         if (kind == Images.Thumbnails.MICRO_KIND && bitmap != null) {
163             bitmap = extractThumbnail(bitmap,
164                     TARGET_SIZE_MICRO_THUMBNAIL,
165                     TARGET_SIZE_MICRO_THUMBNAIL,
166                     OPTIONS_RECYCLE_INPUT);
167         }
168         return bitmap;
169     }
170
171     /**
172      * Creates a centered bitmap of the desired size.
173      *
174      * @param source original bitmap source
175      * @param width targeted width
176      * @param height targeted height
177      */
178     public static Bitmap extractThumbnail(
179             Bitmap source, int width, int height) {
180         return extractThumbnail(source, width, height, OPTIONS_NONE);
181     }
182
183     /**
184      * Creates a centered bitmap of the desired size.
185      *
186      * @param source original bitmap source
187      * @param width targeted width
188      * @param height targeted height
189      * @param options options used during thumbnail extraction
190      */
191     public static Bitmap extractThumbnail(
192             Bitmap source, int width, int height, int options) {
193         if (source == null) {
194             return null;
195         }
196
197         float scale;
198         if (source.getWidth() < source.getHeight()) {
199             scale = width / (float) source.getWidth();
200         } else {
201             scale = height / (float) source.getHeight();
202         }
203         Matrix matrix = new Matrix();
204         matrix.setScale(scale, scale);
205         Bitmap thumbnail = transform(matrix, source, width, height,
206                 OPTIONS_SCALE_UP | options);
207         return thumbnail;
208     }
209
210     /*
211      * Compute the sample size as a function of minSideLength
212      * and maxNumOfPixels.
213      * minSideLength is used to specify that minimal width or height of a
214      * bitmap.
215      * maxNumOfPixels is used to specify the maximal size in pixels that is
216      * tolerable in terms of memory usage.
217      *
218      * The function returns a sample size based on the constraints.
219      * Both size and minSideLength can be passed in as IImage.UNCONSTRAINED,
220      * which indicates no care of the corresponding constraint.
221      * The functions prefers returning a sample size that
222      * generates a smaller bitmap, unless minSideLength = IImage.UNCONSTRAINED.
223      *
224      * Also, the function rounds up the sample size to a power of 2 or multiple
225      * of 8 because BitmapFactory only honors sample size this way.
226      * For example, BitmapFactory downsamples an image by 2 even though the
227      * request is 3. So we round up the sample size to avoid OOM.
228      */
229     private static int computeSampleSize(BitmapFactory.Options options,
230             int minSideLength, int maxNumOfPixels) {
231         int initialSize = computeInitialSampleSize(options, minSideLength,
232                 maxNumOfPixels);
233
234         int roundedSize;
235         if (initialSize <= 8 ) {
236             roundedSize = 1;
237             while (roundedSize < initialSize) {
238                 roundedSize <<= 1;
239             }
240         } else {
241             roundedSize = (initialSize + 7) / 8 * 8;
242         }
243
244         return roundedSize;
245     }
246
247     private static int computeInitialSampleSize(BitmapFactory.Options options,
248             int minSideLength, int maxNumOfPixels) {
249         double w = options.outWidth;
250         double h = options.outHeight;
251
252         int lowerBound = (maxNumOfPixels == UNCONSTRAINED) ? 1 :
253                 (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
254         int upperBound = (minSideLength == UNCONSTRAINED) ? 128 :
255                 (int) Math.min(Math.floor(w / minSideLength),
256                 Math.floor(h / minSideLength));
257
258         if (upperBound < lowerBound) {
259             // return the larger one when there is no overlapping zone.
260             return lowerBound;
261         }
262
263         if ((maxNumOfPixels == UNCONSTRAINED) &&
264                 (minSideLength == UNCONSTRAINED)) {
265             return 1;
266         } else if (minSideLength == UNCONSTRAINED) {
267             return lowerBound;
268         } else {
269             return upperBound;
270         }
271     }
272
273     /**
274      * Make a bitmap from a given Uri, minimal side length, and maximum number of pixels.
275      * The image data will be read from specified pfd if it's not null, otherwise
276      * a new input stream will be created using specified ContentResolver.
277      *
278      * Clients are allowed to pass their own BitmapFactory.Options used for bitmap decoding. A
279      * new BitmapFactory.Options will be created if options is null.
280      */
281     private static Bitmap makeBitmap(int minSideLength, int maxNumOfPixels,
282             Uri uri, ContentResolver cr, ParcelFileDescriptor pfd,
283             BitmapFactory.Options options) {
284             Bitmap b = null;
285         try {
286             if (pfd == null) pfd = makeInputStream(uri, cr);
287             if (pfd == null) return null;
288             if (options == null) options = new BitmapFactory.Options();
289
290             FileDescriptor fd = pfd.getFileDescriptor();
291             options.inSampleSize = 1;
292             options.inJustDecodeBounds = true;
293             BitmapFactory.decodeFileDescriptor(fd, null, options);
294             if (options.mCancel || options.outWidth == -1
295                     || options.outHeight == -1) {
296                 return null;
297             }
298             options.inSampleSize = computeSampleSize(
299                     options, minSideLength, maxNumOfPixels);
300             options.inJustDecodeBounds = false;
301
302             options.inDither = false;
303             options.inPreferredConfig = Bitmap.Config.ARGB_8888;
304             b = BitmapFactory.decodeFileDescriptor(fd, null, options);
305         } catch (OutOfMemoryError ex) {
306             Log.e(TAG, "Got oom exception ", ex);
307             return null;
308         } finally {
309             closeSilently(pfd);
310         }
311         return b;
312     }
313
314     private static void closeSilently(ParcelFileDescriptor c) {
315       if (c == null) return;
316       try {
317           c.close();
318       } catch (Throwable t) {
319           // do nothing
320       }
321     }
322
323     private static ParcelFileDescriptor makeInputStream(
324             Uri uri, ContentResolver cr) {
325         try {
326             return cr.openFileDescriptor(uri, "r");
327         } catch (IOException ex) {
328             return null;
329         }
330     }
331
332     /**
333      * Transform source Bitmap to targeted width and height.
334      */
335     private static Bitmap transform(Matrix scaler,
336             Bitmap source,
337             int targetWidth,
338             int targetHeight,
339             int options) {
340         boolean scaleUp = (options & OPTIONS_SCALE_UP) != 0;
341         boolean recycle = (options & OPTIONS_RECYCLE_INPUT) != 0;
342
343         int deltaX = source.getWidth() - targetWidth;
344         int deltaY = source.getHeight() - targetHeight;
345         if (!scaleUp && (deltaX < 0 || deltaY < 0)) {
346             /*
347             * In this case the bitmap is smaller, at least in one dimension,
348             * than the target.  Transform it by placing as much of the image
349             * as possible into the target and leaving the top/bottom or
350             * left/right (or both) black.
351             */
352             Bitmap b2 = Bitmap.createBitmap(targetWidth, targetHeight,
353             Bitmap.Config.ARGB_8888);
354             Canvas c = new Canvas(b2);
355
356             int deltaXHalf = Math.max(0, deltaX / 2);
357             int deltaYHalf = Math.max(0, deltaY / 2);
358             Rect src = new Rect(
359             deltaXHalf,
360             deltaYHalf,
361             deltaXHalf + Math.min(targetWidth, source.getWidth()),
362             deltaYHalf + Math.min(targetHeight, source.getHeight()));
363             int dstX = (targetWidth  - src.width())  / 2;
364             int dstY = (targetHeight - src.height()) / 2;
365             Rect dst = new Rect(
366                     dstX,
367                     dstY,
368                     targetWidth - dstX,
369                     targetHeight - dstY);
370             c.drawBitmap(source, src, dst, null);
371             if (recycle) {
372                 source.recycle();
373             }
374             return b2;
375         }
376         float bitmapWidthF = source.getWidth();
377         float bitmapHeightF = source.getHeight();
378
379         float bitmapAspect = bitmapWidthF / bitmapHeightF;
380         float viewAspect   = (float) targetWidth / targetHeight;
381
382         if (bitmapAspect > viewAspect) {
383             float scale = targetHeight / bitmapHeightF;
384             if (scale < .9F || scale > 1F) {
385                 scaler.setScale(scale, scale);
386             } else {
387                 scaler = null;
388             }
389         } else {
390             float scale = targetWidth / bitmapWidthF;
391             if (scale < .9F || scale > 1F) {
392                 scaler.setScale(scale, scale);
393             } else {
394                 scaler = null;
395             }
396         }
397
398         Bitmap b1;
399         if (scaler != null) {
400             // this is used for minithumb and crop, so we want to filter here.
401             b1 = Bitmap.createBitmap(source, 0, 0,
402             source.getWidth(), source.getHeight(), scaler, true);
403         } else {
404             b1 = source;
405         }
406
407         if (recycle && b1 != source) {
408             source.recycle();
409         }
410
411         int dx1 = Math.max(0, b1.getWidth() - targetWidth);
412         int dy1 = Math.max(0, b1.getHeight() - targetHeight);
413
414         Bitmap b2 = Bitmap.createBitmap(
415                 b1,
416                 dx1 / 2,
417                 dy1 / 2,
418                 targetWidth,
419                 targetHeight);
420
421         if (b2 != b1) {
422             if (recycle || b1 != source) {
423                 b1.recycle();
424             }
425         }
426
427         return b2;
428     }
429
430     /**
431      * SizedThumbnailBitmap contains the bitmap, which is downsampled either from
432      * the thumbnail in exif or the full image.
433      * mThumbnailData, mThumbnailWidth and mThumbnailHeight are set together only if mThumbnail
434      * is not null.
435      *
436      * The width/height of the sized bitmap may be different from mThumbnailWidth/mThumbnailHeight.
437      */
438     private static class SizedThumbnailBitmap {
439         public byte[] mThumbnailData;
440         public Bitmap mBitmap;
441         public int mThumbnailWidth;
442         public int mThumbnailHeight;
443     }
444
445     /**
446      * Creates a bitmap by either downsampling from the thumbnail in EXIF or the full image.
447      * The functions returns a SizedThumbnailBitmap,
448      * which contains a downsampled bitmap and the thumbnail data in EXIF if exists.
449      */
450     private static void createThumbnailFromEXIF(String filePath, int targetSize,
451             int maxPixels, SizedThumbnailBitmap sizedThumbBitmap) {
452         if (filePath == null) return;
453
454         ExifInterface exif = null;
455         byte [] thumbData = null;
456         try {
457             exif = new ExifInterface(filePath);
458             if (exif != null) {
459                 thumbData = exif.getThumbnail();
460             }
461         } catch (IOException ex) {
462             Log.w(TAG, ex);
463         }
464
465         BitmapFactory.Options fullOptions = new BitmapFactory.Options();
466         BitmapFactory.Options exifOptions = new BitmapFactory.Options();
467         int exifThumbWidth = 0;
468         int fullThumbWidth = 0;
469
470         // Compute exifThumbWidth.
471         if (thumbData != null) {
472             exifOptions.inJustDecodeBounds = true;
473             BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length, exifOptions);
474             exifOptions.inSampleSize = computeSampleSize(exifOptions, targetSize, maxPixels);
475             exifThumbWidth = exifOptions.outWidth / exifOptions.inSampleSize;
476         }
477
478         // Compute fullThumbWidth.
479         fullOptions.inJustDecodeBounds = true;
480         BitmapFactory.decodeFile(filePath, fullOptions);
481         fullOptions.inSampleSize = computeSampleSize(fullOptions, targetSize, maxPixels);
482         fullThumbWidth = fullOptions.outWidth / fullOptions.inSampleSize;
483
484         // Choose the larger thumbnail as the returning sizedThumbBitmap.
485         if (thumbData != null && exifThumbWidth >= fullThumbWidth) {
486             int width = exifOptions.outWidth;
487             int height = exifOptions.outHeight;
488             exifOptions.inJustDecodeBounds = false;
489             sizedThumbBitmap.mBitmap = BitmapFactory.decodeByteArray(thumbData, 0,
490                     thumbData.length, exifOptions);
491             if (sizedThumbBitmap.mBitmap != null) {
492                 sizedThumbBitmap.mThumbnailData = thumbData;
493                 sizedThumbBitmap.mThumbnailWidth = width;
494                 sizedThumbBitmap.mThumbnailHeight = height;
495             }
496         } else {
497             fullOptions.inJustDecodeBounds = false;
498             sizedThumbBitmap.mBitmap = BitmapFactory.decodeFile(filePath, fullOptions);
499         }
500     }
501 }