diff options
author | Android (Google) Code Review <android-gerrit@google.com> | 2009-10-01 01:12:35 -0400 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2009-10-01 01:12:35 -0400 |
commit | f09edd60376f6ad755ebaaf0c1f89f561f78468c (patch) | |
tree | 6b1b05cb2405879f228fa6dd04a01f26446bb283 /media/java | |
parent | 59a2c2958d0bcc00402ebb961b02368004117e3b (diff) | |
parent | ef093cd6c4ab4d3c8a1c8be5ed7147d5f06d7027 (diff) | |
download | frameworks_base-f09edd60376f6ad755ebaaf0c1f89f561f78468c.zip frameworks_base-f09edd60376f6ad755ebaaf0c1f89f561f78468c.tar.gz frameworks_base-f09edd60376f6ad755ebaaf0c1f89f561f78468c.tar.bz2 |
Merge change Id60fa26a into eclair
* changes:
Fix issue 2152541 thumbnail images stretched.
Diffstat (limited to 'media/java')
-rw-r--r-- | media/java/android/media/MiniThumbFile.java | 15 | ||||
-rw-r--r-- | media/java/android/media/ThumbnailUtil.java | 195 |
2 files changed, 175 insertions, 35 deletions
diff --git a/media/java/android/media/MiniThumbFile.java b/media/java/android/media/MiniThumbFile.java index 22c6459..f6e6317 100644 --- a/media/java/android/media/MiniThumbFile.java +++ b/media/java/android/media/MiniThumbFile.java @@ -61,6 +61,9 @@ public class MiniThumbFile { * we should hashcode of content://media/external/images/media remains the same. */ public static synchronized void reset() { + for (MiniThumbFile file : sThumbFiles.values()) { + file.deactivate(); + } sThumbFiles.clear(); } @@ -144,7 +147,7 @@ public class MiniThumbFile { // Get the magic number for the specified id in the mini-thumb file. // Returns 0 if the magic is not available. - public long getMagic(long id) { + public synchronized long getMagic(long id) { // check the mini thumb file for the right data. Right is // defined as having the right magic number at the offset // reserved for this "id". @@ -183,13 +186,7 @@ public class MiniThumbFile { return 0; } - public void saveMiniThumbToFile(Bitmap bitmap, long id, long magic) - throws IOException { - byte[] data = ThumbnailUtil.miniThumbData(bitmap); - saveMiniThumbToFile(data, id, magic); - } - - public void saveMiniThumbToFile(byte[] data, long id, long magic) + public synchronized void saveMiniThumbToFile(byte[] data, long id, long magic) throws IOException { RandomAccessFile r = miniThumbDataFile(); if (r == null) return; @@ -237,7 +234,7 @@ public class MiniThumbFile { * @param id the ID of the image (same of full size image). * @param data the buffer to store mini-thumbnail. */ - public byte [] getMiniThumbFromFile(long id, byte [] data) { + public synchronized byte [] getMiniThumbFromFile(long id, byte [] data) { RandomAccessFile r = miniThumbDataFile(); if (r == null) return null; diff --git a/media/java/android/media/ThumbnailUtil.java b/media/java/android/media/ThumbnailUtil.java index eeee266..f9d69fb 100644 --- a/media/java/android/media/ThumbnailUtil.java +++ b/media/java/android/media/ThumbnailUtil.java @@ -18,9 +18,15 @@ package android.media; import android.net.Uri; import android.os.ParcelFileDescriptor; +import android.provider.BaseColumns; +import android.provider.MediaStore.Images; +import android.provider.MediaStore.Images.Thumbnails; import android.util.Log; import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; @@ -250,32 +256,6 @@ public class ThumbnailUtil { } /** - * Creates a byte[] for a given bitmap of the desired size. Recycles the - * input bitmap. - */ - public static byte[] miniThumbData(Bitmap source) { - if (source == null) return null; - - Bitmap miniThumbnail = extractMiniThumb( - source, MINI_THUMB_TARGET_SIZE, - MINI_THUMB_TARGET_SIZE, - RECYCLE_INPUT); - - ByteArrayOutputStream miniOutStream = new ByteArrayOutputStream(); - miniThumbnail.compress(Bitmap.CompressFormat.JPEG, 75, miniOutStream); - miniThumbnail.recycle(); - - try { - miniOutStream.close(); - byte [] data = miniOutStream.toByteArray(); - return data; - } catch (java.io.IOException ex) { - Log.e(TAG, "got exception ex " + ex); - } - return null; - } - - /** * Create a video thumbnail for a video. May return null if the video is * corrupt. * @@ -302,6 +282,67 @@ public class ThumbnailUtil { return bitmap; } + /** + * This method first examines if the thumbnail embedded in EXIF is bigger than our target + * size. If not, then it'll create a thumbnail from original image. Due to efficiency + * consideration, we want to let MediaThumbRequest avoid calling this method twice for + * both kinds, so it only requests for MICRO_KIND and set saveImage to true. + * + * This method always returns a "square thumbnail" for MICRO_KIND thumbnail. + * + * @param cr ContentResolver + * @param filePath file path needed by EXIF interface + * @param uri URI of original image + * @param origId image id + * @param kind either MINI_KIND or MICRO_KIND + * @param saveImage Whether to save MINI_KIND thumbnail obtained in this method. + * @return Bitmap + */ + public static Bitmap createImageThumbnail(ContentResolver cr, String filePath, Uri uri, + long origId, int kind, boolean saveMini) { + boolean wantMini = (kind == Images.Thumbnails.MINI_KIND || saveMini); + int targetSize = wantMini ? + ThumbnailUtil.THUMBNAIL_TARGET_SIZE : ThumbnailUtil.MINI_THUMB_TARGET_SIZE; + int maxPixels = wantMini ? + ThumbnailUtil.THUMBNAIL_MAX_NUM_PIXELS : ThumbnailUtil.MINI_THUMB_MAX_NUM_PIXELS; + byte[] thumbData = createThumbnailFromEXIF(filePath, targetSize); + Bitmap bitmap = null; + + if (thumbData != null) { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inSampleSize = computeSampleSize(options, targetSize, maxPixels); + options.inDither = false; + options.inPreferredConfig = Bitmap.Config.ARGB_8888; + options.inJustDecodeBounds = false; + bitmap = BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length, options); + } + + if (bitmap == null) { + bitmap = ThumbnailUtil.makeBitmap(targetSize, maxPixels, uri, cr); + } + + if (bitmap == null) { + return null; + } + + if (saveMini) { + if (thumbData != null) { + ThumbnailUtil.storeThumbnail(cr, origId, thumbData, bitmap.getWidth(), + bitmap.getHeight()); + } else { + ThumbnailUtil.storeThumbnail(cr, origId, bitmap); + } + } + + if (kind == Images.Thumbnails.MICRO_KIND) { + // now we make it a "square thumbnail" for MICRO_KIND thumbnail + bitmap = ThumbnailUtil.extractMiniThumb(bitmap, + ThumbnailUtil.MINI_THUMB_TARGET_SIZE, + ThumbnailUtil.MINI_THUMB_TARGET_SIZE, ThumbnailUtil.RECYCLE_INPUT); + } + return bitmap; + } + public static Bitmap transform(Matrix scaler, Bitmap source, int targetWidth, @@ -396,6 +437,108 @@ public class ThumbnailUtil { return b2; } + private static final String[] THUMB_PROJECTION = new String[] { + BaseColumns._ID // 0 + }; + /** + * Look up thumbnail uri by given imageId, it will be automatically created if it's not created + * yet. Most of the time imageId is identical to thumbId, but it's not always true. + * @param req + * @param width + * @param height + * @return Uri Thumbnail uri + */ + private static Uri getImageThumbnailUri(ContentResolver cr, long origId, int width, int height) { + Uri thumbUri = Images.Thumbnails.EXTERNAL_CONTENT_URI; + Cursor c = cr.query(thumbUri, THUMB_PROJECTION, + Thumbnails.IMAGE_ID + "=?", + new String[]{String.valueOf(origId)}, null); + try { + if (c.moveToNext()) { + return ContentUris.withAppendedId(thumbUri, c.getLong(0)); + } + } finally { + if (c != null) c.close(); + } + + ContentValues values = new ContentValues(4); + values.put(Thumbnails.KIND, Thumbnails.MINI_KIND); + values.put(Thumbnails.IMAGE_ID, origId); + values.put(Thumbnails.HEIGHT, height); + values.put(Thumbnails.WIDTH, width); + try { + return cr.insert(thumbUri, values); + } catch (Exception ex) { + Log.w(TAG, ex); + return null; + } + } + + /** + * Store a given thumbnail in the database. (Bitmap) + */ + private static boolean storeThumbnail(ContentResolver cr, long origId, Bitmap thumb) { + if (thumb == null) return false; + try { + Uri uri = getImageThumbnailUri(cr, origId, thumb.getWidth(), thumb.getHeight()); + OutputStream thumbOut = cr.openOutputStream(uri); + thumb.compress(Bitmap.CompressFormat.JPEG, 85, thumbOut); + thumbOut.close(); + return true; + } catch (Throwable t) { + Log.e(TAG, "Unable to store thumbnail", t); + return false; + } + } + + /** + * Store a given thumbnail in the database. (byte array) + */ + private static boolean storeThumbnail(ContentResolver cr, long origId, byte[] jpegThumbnail, + int width, int height) { + if (jpegThumbnail == null) return false; + Uri uri = getImageThumbnailUri(cr, origId, width, height); + if (uri == null) { + return false; + } + try { + OutputStream thumbOut = cr.openOutputStream(uri); + thumbOut.write(jpegThumbnail); + thumbOut.close(); + return true; + } catch (Throwable t) { + Log.e(TAG, "Unable to store thumbnail", t); + return false; + } + } + + // Extract thumbnail in image that meets the targetSize criteria. + static byte[] createThumbnailFromEXIF(String filePath, int targetSize) { + if (filePath == null) return null; + + try { + ExifInterface exif = new ExifInterface(filePath); + if (exif == null) return null; + byte [] thumbData = exif.getThumbnail(); + if (thumbData == null) return null; + // Sniff the size of the EXIF thumbnail before decoding it. Photos + // from the device will pass, but images that are side loaded from + // other cameras may not. + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length, options); + + int width = options.outWidth; + int height = options.outHeight; + + if (width >= targetSize && height >= targetSize) { + return thumbData; + } + } catch (IOException ex) { + Log.w(TAG, ex); + } + return null; + } } |