am f09edd60: Merge change Id60fa26a into eclair

Merge commit 'f09edd60376f6ad755ebaaf0c1f89f561f78468c' into eclair-plus-aosp

* commit 'f09edd60376f6ad755ebaaf0c1f89f561f78468c':
  Fix issue 2152541	thumbnail images stretched.
This commit is contained in:
Ray Chen
2009-09-30 22:18:41 -07:00
committed by Android Git Automerger
3 changed files with 192 additions and 48 deletions

View File

@@ -245,7 +245,7 @@ public final class MediaStore {
* requests can cancel their own requests.
*
* @param cr ContentResolver
* @param origId original image or video id
* @param origId original image or video id. use -1 to cancel all requests.
* @param baseUri the base URI of requested thumbnails
*/
static void cancelThumbnailRequest(ContentResolver cr, long origId, Uri baseUri) {
@@ -340,24 +340,28 @@ public final class MediaStore {
// We probably run out of space, so create the thumbnail in memory.
if (bitmap == null) {
Log.v(TAG, "We probably run out of space, so create the thumbnail in memory.");
int targetSize = kind == MINI_KIND ? ThumbnailUtil.THUMBNAIL_TARGET_SIZE :
ThumbnailUtil.MINI_THUMB_TARGET_SIZE;
int maxPixelNum = kind == MINI_KIND ? ThumbnailUtil.THUMBNAIL_MAX_NUM_PIXELS :
ThumbnailUtil.MINI_THUMB_MAX_NUM_PIXELS;
Uri uri = Uri.parse(
baseUri.buildUpon().appendPath(String.valueOf(origId))
.toString().replaceFirst("thumbnails", "media"));
if (isVideo) {
if (filePath == null) {
c = cr.query(uri, PROJECTION, null, null, null);
if (c != null && c.moveToFirst()) {
bitmap = ThumbnailUtil.createVideoThumbnail(c.getString(1));
if (kind == MICRO_KIND) {
bitmap = ThumbnailUtil.extractMiniThumb(bitmap,
targetSize, targetSize, ThumbnailUtil.RECYCLE_INPUT);
}
if (c == null || !c.moveToFirst()) {
return null;
}
filePath = c.getString(1);
}
if (isVideo) {
bitmap = ThumbnailUtil.createVideoThumbnail(filePath);
if (kind == MICRO_KIND) {
bitmap = ThumbnailUtil.extractMiniThumb(bitmap,
ThumbnailUtil.MINI_THUMB_TARGET_SIZE,
ThumbnailUtil.MINI_THUMB_TARGET_SIZE,
ThumbnailUtil.RECYCLE_INPUT);
}
} else {
bitmap = ThumbnailUtil.makeBitmap(targetSize, maxPixelNum, uri, cr);
bitmap = ThumbnailUtil.createImageThumbnail(cr, filePath, uri, origId,
kind, false);
}
}
} catch (SQLiteException ex) {

View File

@@ -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;

View File

@@ -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;
@@ -249,32 +255,6 @@ public class ThumbnailUtil {
return miniThumbnail;
}
/**
* 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;
}
}