summaryrefslogtreecommitdiffstats
path: root/media/java
diff options
context:
space:
mode:
authorAndroid (Google) Code Review <android-gerrit@google.com>2009-10-01 01:12:35 -0400
committerAndroid (Google) Code Review <android-gerrit@google.com>2009-10-01 01:12:35 -0400
commitf09edd60376f6ad755ebaaf0c1f89f561f78468c (patch)
tree6b1b05cb2405879f228fa6dd04a01f26446bb283 /media/java
parent59a2c2958d0bcc00402ebb961b02368004117e3b (diff)
parentef093cd6c4ab4d3c8a1c8be5ed7147d5f06d7027 (diff)
downloadframeworks_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.java15
-rw-r--r--media/java/android/media/ThumbnailUtil.java195
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;
+ }
}