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

Commit 52cf405c authored by Owen Lin's avatar Owen Lin Committed by Android (Google) Code Review
Browse files

Merge "Reuse bitmap for all micro thumb images to prevent GC."

parents 5cac6d5d 4bb5912e
Loading
Loading
Loading
Loading
+17 −32
Original line number Diff line number Diff line
@@ -32,7 +32,7 @@ import java.lang.reflect.Method;

public class BitmapUtils {
    private static final String TAG = "BitmapUtils";
    private static final int COMPRESS_JPEG_QUALITY = 90;
    private static final int DEFAULT_JPEG_QUALITY = 90;
    public static final int UNCONSTRAINED = -1;

    private BitmapUtils(){}
@@ -94,7 +94,7 @@ public class BitmapUtils {
                : initialSize / 8 * 8;
    }

    // Fin the min x that 1 / x <= scale
    // Find the min x that 1 / x >= scale
    public static int computeSampleSizeLarger(float scale) {
        int initialSize = (int) FloatMath.floor(1f / scale);
        if (initialSize <= 1) return 1;
@@ -104,7 +104,7 @@ public class BitmapUtils {
                : initialSize / 8 * 8;
    }

    // Find the max x that 1 / x >= scale.
    // Find the max x that 1 / x <= scale.
    public static int computeSampleSize(float scale) {
        Utils.assertTrue(scale > 0);
        int initialSize = Math.max(1, (int) FloatMath.ceil(1 / scale));
@@ -146,27 +146,15 @@ public class BitmapUtils {
        return resizeBitmapByScale(bitmap, scale, recycle);
    }

    // Resize the bitmap if each side is >= targetSize * 2
    public static Bitmap resizeDownIfTooBig(
            Bitmap bitmap, int targetSize, boolean recycle) {
        int srcWidth = bitmap.getWidth();
        int srcHeight = bitmap.getHeight();
        float scale = Math.max(
                (float) targetSize / srcWidth, (float) targetSize / srcHeight);
        if (scale > 0.5f) return bitmap;
        return resizeBitmapByScale(bitmap, scale, recycle);
    }

    public static Bitmap resizeDownAndCropCenter(Bitmap bitmap, int size,
            boolean recycle) {
    public static Bitmap resizeAndCropCenter(Bitmap bitmap, int size, boolean recycle) {
        int w = bitmap.getWidth();
        int h = bitmap.getHeight();
        int minSide = Math.min(w, h);
        if (w == h && minSide <= size) return bitmap;
        size = Math.min(size, minSide);
        if (w == size && h == size) return bitmap;

        // scale the image so that the shorter side equals to the target;
        // the longer side will be center-cropped.
        float scale = (float) size / Math.min(w,  h);

        float scale = Math.max((float) size / bitmap.getWidth(),
                (float) size / bitmap.getHeight());
        Bitmap target = Bitmap.createBitmap(size, size, getConfig(bitmap));
        int width = Math.round(scale * bitmap.getWidth());
        int height = Math.round(scale * bitmap.getHeight());
@@ -247,11 +235,14 @@ public class BitmapUtils {
        return null;
    }

    public static byte[] compressBitmap(Bitmap bitmap) {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG,
                COMPRESS_JPEG_QUALITY, os);
        return os.toByteArray();
    public static byte[] compressToBytes(Bitmap bitmap) {
        return compressToBytes(bitmap, DEFAULT_JPEG_QUALITY);
    }

    public static byte[] compressToBytes(Bitmap bitmap, int quality) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream(65536);
        bitmap.compress(CompressFormat.JPEG, quality, baos);
        return baos.toByteArray();
    }

    public static boolean isSupportedByRegionDecoder(String mimeType) {
@@ -266,10 +257,4 @@ public class BitmapUtils {
        mimeType = mimeType.toLowerCase();
        return mimeType.equals("image/jpeg");
    }

    public static byte[] compressToBytes(Bitmap bitmap, int quality) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream(65536);
        bitmap.compress(CompressFormat.JPEG, quality, baos);
        return baos.toByteArray();
    }
}
+5 −5
Original line number Diff line number Diff line
@@ -16,22 +16,17 @@

package com.android.gallery3d.common;

import android.app.PendingIntent;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.Cursor;
import android.os.Build;
import android.os.Environment;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.StatFs;
import android.text.TextUtils;
import android.util.Log;

import java.io.Closeable;
import java.io.InterruptedIOException;
import java.util.Random;

public class Utils {
    private static final String TAG = "Utils";
@@ -336,4 +331,9 @@ public class Utils {
        int length = Math.min(s.length(), MASK_STRING.length());
        return IS_DEBUG_BUILD ? s : MASK_STRING.substring(0, length);
    }

    // This method should be ONLY used for debugging.
    public static void debug(String message, Object ... args) {
        Log.v(DEBUG_TAG, String.format(message, args));
    }
}
+8 −9
Original line number Diff line number Diff line
@@ -16,15 +16,6 @@

package com.android.gallery3d.app;

import com.android.gallery3d.R;
import com.android.gallery3d.data.DataManager;
import com.android.gallery3d.data.ImageCacheService;
import com.android.gallery3d.ui.GLRoot;
import com.android.gallery3d.ui.GLRootView;
import com.android.gallery3d.ui.PositionRepository;
import com.android.gallery3d.util.ThreadPool;

import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
@@ -39,6 +30,13 @@ import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;

import com.android.gallery3d.R;
import com.android.gallery3d.data.DataManager;
import com.android.gallery3d.ui.BitmapPool;
import com.android.gallery3d.ui.GLRoot;
import com.android.gallery3d.ui.GLRootView;
import com.android.gallery3d.util.ThreadPool;

public class AbstractGalleryActivity extends Activity implements GalleryActivity {
    @SuppressWarnings("unused")
    private static final String TAG = "AbstractGalleryActivity";
@@ -177,6 +175,7 @@ public class AbstractGalleryActivity extends Activity implements GalleryActivity
        } finally {
            mGLRootView.unlockRenderThread();
        }
        BitmapPool.clear();
    }

    @Override
+54 −29
Original line number Diff line number Diff line
@@ -16,20 +16,17 @@

package com.android.gallery3d.data;

import com.android.gallery3d.common.BitmapUtils;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.util.ThreadPool.CancelListener;
import com.android.gallery3d.util.ThreadPool.JobContext;

import android.content.ContentResolver;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Rect;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.util.FloatMath;

import com.android.gallery3d.common.BitmapUtils;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.util.ThreadPool.CancelListener;
import com.android.gallery3d.util.ThreadPool.JobContext;

import java.io.FileDescriptor;
import java.io.FileInputStream;
@@ -40,27 +37,29 @@ public class DecodeUtils {

    private static class DecodeCanceller implements CancelListener {
        Options mOptions;

        public DecodeCanceller(Options options) {
            mOptions = options;
        }

        @Override
        public void onCancel() {
            mOptions.requestCancelDecode();
        }
    }

    public static Bitmap requestDecode(JobContext jc, FileDescriptor fd, Options options) {
    public static Bitmap decode(JobContext jc, FileDescriptor fd, Options options) {
        if (options == null) options = new Options();
        jc.setCancelListener(new DecodeCanceller(options));
        return ensureGLCompatibleBitmap(
                BitmapFactory.decodeFileDescriptor(fd, null, options));
    }

    public static Bitmap requestDecode(JobContext jc, byte[] bytes,
            Options options) {
        return requestDecode(jc, bytes, 0, bytes.length, options);
    public static Bitmap decode(JobContext jc, byte[] bytes, Options options) {
        return decode(jc, bytes, 0, bytes.length, options);
    }

    public static Bitmap requestDecode(JobContext jc, byte[] bytes, int offset,
    public static Bitmap decode(JobContext jc, byte[] bytes, int offset,
            int length, Options options) {
        if (options == null) options = new Options();
        jc.setCancelListener(new DecodeCanceller(options));
@@ -68,13 +67,13 @@ public class DecodeUtils {
                BitmapFactory.decodeByteArray(bytes, offset, length, options));
    }

    public static Bitmap requestDecode(JobContext jc, final String filePath,
            Options options, int targetSize) {
    public static Bitmap decodeThumbnail(
            JobContext jc, String filePath, Options options, int targetSize, int type) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(filePath);
            FileDescriptor fd = fis.getFD();
            return requestDecode(jc, fd, options, targetSize);
            return decodeThumbnail(jc, fd, options, targetSize, type);
        } catch (Exception ex) {
            Log.w(TAG, ex);
            return null;
@@ -83,8 +82,8 @@ public class DecodeUtils {
        }
    }

    public static Bitmap requestDecode(JobContext jc, FileDescriptor fd,
            Options options, int targetSize) {
    public static Bitmap decodeThumbnail(
            JobContext jc, FileDescriptor fd, Options options, int targetSize, int type) {
        if (options == null) options = new Options();
        jc.setCancelListener(new DecodeCanceller(options));

@@ -92,14 +91,40 @@ public class DecodeUtils {
        BitmapFactory.decodeFileDescriptor(fd, null, options);
        if (jc.isCancelled()) return null;

        options.inSampleSize = BitmapUtils.computeSampleSizeLarger(
                options.outWidth, options.outHeight, targetSize);
        int w = options.outWidth;
        int h = options.outHeight;

        if (type == MediaItem.TYPE_MICROTHUMBNAIL) {
            // We center-crop the original image as it's micro thumbnail. In this case,
            // we want to make sure the shorter side >= "targetSize".
            float scale = (float) targetSize / Math.min(w, h);
            options.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);

            // For an extremely wide image, e.g. 300x30000, we may got OOM when decoding
            // it for TYPE_MICROTHUMBNAIL. So we add a max number of pixels limit here.
            final int MAX_PIXEL_COUNT = 640000; // 400 x 1600
            if ((w / options.inSampleSize) * (h / options.inSampleSize) > MAX_PIXEL_COUNT) {
                options.inSampleSize = BitmapUtils.computeSampleSize(
                        FloatMath.sqrt((float) MAX_PIXEL_COUNT / (w * h)));
            }
        } else {
            // For screen nail, we only want to keep the longer side >= targetSize.
            float scale = (float) targetSize / Math.max(w, h);
            options.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);
        }

        options.inJustDecodeBounds = false;

        Bitmap result = BitmapFactory.decodeFileDescriptor(fd, null, options);
        // We need to resize down if the decoder does not support inSampleSize.
        // (For example, GIF images.)
        result = BitmapUtils.resizeDownIfTooBig(result, targetSize, true);
        if (result == null) return null;

        // We need to resize down if the decoder does not support inSampleSize
        // (For example, GIF images)
        float scale = (float) targetSize / (type == MediaItem.TYPE_MICROTHUMBNAIL
                ? Math.min(result.getWidth(), result.getHeight())
                : Math.max(result.getWidth(), result.getHeight()));

        if (scale <= 0.5) result = BitmapUtils.resizeBitmapByScale(result, scale, true);
        return ensureGLCompatibleBitmap(result);
    }

@@ -110,7 +135,7 @@ public class DecodeUtils {
     * Note: The returned image may be resized down. However, both width and height must be
     * larger than the <code>targetSize</code>.
     */
    public static Bitmap requestDecodeIfBigEnough(JobContext jc, byte[] data,
    public static Bitmap decodeIfBigEnough(JobContext jc, byte[] data,
            Options options, int targetSize) {
        if (options == null) options = new Options();
        jc.setCancelListener(new DecodeCanceller(options));
@@ -138,7 +163,7 @@ public class DecodeUtils {
        return newBitmap;
    }

    public static BitmapRegionDecoder requestCreateBitmapRegionDecoder(
    public static BitmapRegionDecoder createBitmapRegionDecoder(
            JobContext jc, byte[] bytes, int offset, int length,
            boolean shareable) {
        if (offset < 0 || length <= 0 || offset + length > bytes.length) {
@@ -156,7 +181,7 @@ public class DecodeUtils {
        }
    }

    public static BitmapRegionDecoder requestCreateBitmapRegionDecoder(
    public static BitmapRegionDecoder createBitmapRegionDecoder(
            JobContext jc, String filePath, boolean shareable) {
        try {
            return BitmapRegionDecoder.newInstance(filePath, shareable);
@@ -166,7 +191,7 @@ public class DecodeUtils {
        }
    }

    public static BitmapRegionDecoder requestCreateBitmapRegionDecoder(
    public static BitmapRegionDecoder createBitmapRegionDecoder(
            JobContext jc, FileDescriptor fd, boolean shareable) {
        try {
            return BitmapRegionDecoder.newInstance(fd, shareable);
@@ -176,7 +201,7 @@ public class DecodeUtils {
        }
    }

    public static BitmapRegionDecoder requestCreateBitmapRegionDecoder(
    public static BitmapRegionDecoder createBitmapRegionDecoder(
            JobContext jc, InputStream is, boolean shareable) {
        try {
            return BitmapRegionDecoder.newInstance(is, shareable);
+15 −10
Original line number Diff line number Diff line
@@ -16,15 +16,16 @@

package com.android.gallery3d.data;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

import com.android.gallery3d.app.GalleryApp;
import com.android.gallery3d.common.BitmapUtils;
import com.android.gallery3d.data.ImageCacheService.ImageData;
import com.android.gallery3d.ui.BitmapPool;
import com.android.gallery3d.util.ThreadPool.Job;
import com.android.gallery3d.util.ThreadPool.JobContext;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

abstract class ImageCacheRequest implements Job<Bitmap> {
    private static final String TAG = "ImageCacheRequest";

@@ -53,8 +54,14 @@ abstract class ImageCacheRequest implements Job<Bitmap> {
        if (data != null) {
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
            Bitmap bitmap = DecodeUtils.requestDecode(jc, data.mData,
                    data.mOffset, data.mData.length - data.mOffset, options);
            Bitmap bitmap;
            if (mType == MediaItem.TYPE_MICROTHUMBNAIL) {
                bitmap = BitmapPool.decode(jc, BitmapPool.TYPE_MICRO_THUMB,
                        data.mData, data.mOffset, data.mData.length - data.mOffset, options);
            } else {
                bitmap = DecodeUtils.decode(jc,
                        data.mData, data.mOffset, data.mData.length - data.mOffset, options);
            }
            if (bitmap == null && !jc.isCancelled()) {
                Log.w(TAG, "decode cached failed " + debugTag);
            }
@@ -69,15 +76,13 @@ abstract class ImageCacheRequest implements Job<Bitmap> {
            }

            if (mType == MediaItem.TYPE_MICROTHUMBNAIL) {
                bitmap = BitmapUtils.resizeDownAndCropCenter(bitmap,
                        mTargetSize, true);
                bitmap = BitmapUtils.resizeAndCropCenter(bitmap, mTargetSize, true);
            } else {
                bitmap = BitmapUtils.resizeDownBySideLength(bitmap,
                        mTargetSize, true);
                bitmap = BitmapUtils.resizeDownBySideLength(bitmap, mTargetSize, true);
            }
            if (jc.isCancelled()) return null;

            byte[] array = BitmapUtils.compressBitmap(bitmap);
            byte[] array = BitmapUtils.compressToBytes(bitmap);
            if (jc.isCancelled()) return null;

            cacheService.putImageData(mPath, mType, array);
Loading