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:
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user