Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ef093cd6 authored by Ray Chen's avatar Ray Chen
Browse files

Fix issue 2152541 thumbnail images stretched.

parent 7e1af37c
Loading
Loading
Loading
Loading
+17 −13
Original line number Diff line number Diff line
@@ -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 (c == null || !c.moveToFirst()) {
                            return null;
                        }
                        filePath = c.getString(1);
                    }
                    if (isVideo) {
                        bitmap = ThumbnailUtil.createVideoThumbnail(filePath);
                        if (kind == MICRO_KIND) {
                            bitmap = ThumbnailUtil.extractMiniThumb(bitmap,
                                        targetSize, targetSize, ThumbnailUtil.RECYCLE_INPUT);
                            }
                                    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) {
+6 −9
Original line number Diff line number Diff line
@@ -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;

+169 −26
Original line number Diff line number Diff line
@@ -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;
    }
}