summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorOwen Lin <owenlin@google.com>2012-03-07 17:39:56 +0800
committerOwen Lin <owenlin@google.com>2012-03-14 15:48:54 +0800
commit504dd40f27893b120c7a978b5b01b73bd23559bb (patch)
tree5cd02a32256653631da6af42df47d270d417a9fd
parent2fe19c950eb46c425d36279923e1fa0b541ce2c8 (diff)
downloadandroid_packages_apps_Snap-504dd40f27893b120c7a978b5b01b73bd23559bb.tar.gz
android_packages_apps_Snap-504dd40f27893b120c7a978b5b01b73bd23559bb.tar.bz2
android_packages_apps_Snap-504dd40f27893b120c7a978b5b01b73bd23559bb.zip
Reuse bitmap for all micro thumb images to prevent GC.
Change-Id: I27d3002e5bb745a597f52962fe24744c8329441c
-rw-r--r--gallerycommon/src/com/android/gallery3d/common/BitmapUtils.java49
-rw-r--r--gallerycommon/src/com/android/gallery3d/common/Utils.java10
-rw-r--r--src/com/android/gallery3d/app/AbstractGalleryActivity.java17
-rw-r--r--src/com/android/gallery3d/data/DecodeUtils.java83
-rw-r--r--src/com/android/gallery3d/data/ImageCacheRequest.java25
-rw-r--r--src/com/android/gallery3d/data/ImageCacheService.java6
-rw-r--r--src/com/android/gallery3d/data/LocalImage.java40
-rw-r--r--src/com/android/gallery3d/data/LocalVideo.java2
-rw-r--r--src/com/android/gallery3d/data/MediaItem.java16
-rw-r--r--src/com/android/gallery3d/data/MtpImage.java4
-rw-r--r--src/com/android/gallery3d/data/UriImage.java31
-rw-r--r--src/com/android/gallery3d/ui/AbstractDisplayItem.java11
-rw-r--r--src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java5
-rw-r--r--src/com/android/gallery3d/ui/AlbumSlidingWindow.java9
-rw-r--r--src/com/android/gallery3d/ui/BitmapPool.java113
15 files changed, 283 insertions, 138 deletions
diff --git a/gallerycommon/src/com/android/gallery3d/common/BitmapUtils.java b/gallerycommon/src/com/android/gallery3d/common/BitmapUtils.java
index 75ae25fc1..a671ed2b9 100644
--- a/gallerycommon/src/com/android/gallery3d/common/BitmapUtils.java
+++ b/gallerycommon/src/com/android/gallery3d/common/BitmapUtils.java
@@ -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();
- }
}
diff --git a/gallerycommon/src/com/android/gallery3d/common/Utils.java b/gallerycommon/src/com/android/gallery3d/common/Utils.java
index bbe2a5d55..391b22535 100644
--- a/gallerycommon/src/com/android/gallery3d/common/Utils.java
+++ b/gallerycommon/src/com/android/gallery3d/common/Utils.java
@@ -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));
+ }
}
diff --git a/src/com/android/gallery3d/app/AbstractGalleryActivity.java b/src/com/android/gallery3d/app/AbstractGalleryActivity.java
index f3e60cee9..7d9d72b13 100644
--- a/src/com/android/gallery3d/app/AbstractGalleryActivity.java
+++ b/src/com/android/gallery3d/app/AbstractGalleryActivity.java
@@ -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
diff --git a/src/com/android/gallery3d/data/DecodeUtils.java b/src/com/android/gallery3d/data/DecodeUtils.java
index 969fd9854..319458a14 100644
--- a/src/com/android/gallery3d/data/DecodeUtils.java
+++ b/src/com/android/gallery3d/data/DecodeUtils.java
@@ -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);
diff --git a/src/com/android/gallery3d/data/ImageCacheRequest.java b/src/com/android/gallery3d/data/ImageCacheRequest.java
index 104ff4839..64dfc9f45 100644
--- a/src/com/android/gallery3d/data/ImageCacheRequest.java
+++ b/src/com/android/gallery3d/data/ImageCacheRequest.java
@@ -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);
diff --git a/src/com/android/gallery3d/data/ImageCacheService.java b/src/com/android/gallery3d/data/ImageCacheService.java
index 3adce1332..fdc27749b 100644
--- a/src/com/android/gallery3d/data/ImageCacheService.java
+++ b/src/com/android/gallery3d/data/ImageCacheService.java
@@ -16,13 +16,13 @@
package com.android.gallery3d.data;
+import android.content.Context;
+
import com.android.gallery3d.common.BlobCache;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.util.CacheManager;
import com.android.gallery3d.util.GalleryUtils;
-import android.content.Context;
-
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -33,7 +33,7 @@ public class ImageCacheService {
private static final String IMAGE_CACHE_FILE = "imgcache";
private static final int IMAGE_CACHE_MAX_ENTRIES = 5000;
private static final int IMAGE_CACHE_MAX_BYTES = 200 * 1024 * 1024;
- private static final int IMAGE_CACHE_VERSION = 3;
+ private static final int IMAGE_CACHE_VERSION = 4;
private BlobCache mCache;
diff --git a/src/com/android/gallery3d/data/LocalImage.java b/src/com/android/gallery3d/data/LocalImage.java
index fa3ece3b6..4f2797e02 100644
--- a/src/com/android/gallery3d/data/LocalImage.java
+++ b/src/com/android/gallery3d/data/LocalImage.java
@@ -16,13 +16,6 @@
package com.android.gallery3d.data;
-import com.android.gallery3d.app.GalleryApp;
-import com.android.gallery3d.common.BitmapUtils;
-import com.android.gallery3d.util.GalleryUtils;
-import com.android.gallery3d.util.ThreadPool.Job;
-import com.android.gallery3d.util.ThreadPool.JobContext;
-import com.android.gallery3d.util.UpdateHelper;
-
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
@@ -35,6 +28,13 @@ import android.provider.MediaStore.Images;
import android.provider.MediaStore.Images.ImageColumns;
import android.util.Log;
+import com.android.gallery3d.app.GalleryApp;
+import com.android.gallery3d.common.BitmapUtils;
+import com.android.gallery3d.util.GalleryUtils;
+import com.android.gallery3d.util.ThreadPool.Job;
+import com.android.gallery3d.util.ThreadPool.JobContext;
+import com.android.gallery3d.util.UpdateHelper;
+
import java.io.File;
import java.io.IOException;
@@ -159,14 +159,15 @@ public class LocalImage extends LocalMediaItem {
LocalImageRequest(GalleryApp application, Path path, int type,
String localFilePath) {
- super(application, path, type, getTargetSize(type));
+ super(application, path, type, MediaItem.getTargetSize(type));
mLocalFilePath = localFilePath;
}
@Override
- public Bitmap onDecodeOriginal(JobContext jc, int type) {
+ public Bitmap onDecodeOriginal(JobContext jc, final int type) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ int targetSize = MediaItem.getTargetSize(type);
// try to decode from JPEG EXIF
if (type == MediaItem.TYPE_MICROTHUMBNAIL) {
@@ -181,25 +182,13 @@ public class LocalImage extends LocalMediaItem {
Log.w(TAG, "fail to get exif thumb", t);
}
if (thumbData != null) {
- Bitmap bitmap = DecodeUtils.requestDecodeIfBigEnough(
- jc, thumbData, options, getTargetSize(type));
+ Bitmap bitmap = DecodeUtils.decodeIfBigEnough(
+ jc, thumbData, options, targetSize);
if (bitmap != null) return bitmap;
}
}
- return DecodeUtils.requestDecode(
- jc, mLocalFilePath, options, getTargetSize(type));
- }
- }
- static int getTargetSize(int type) {
- switch (type) {
- case TYPE_THUMBNAIL:
- return THUMBNAIL_TARGET_SIZE;
- case TYPE_MICROTHUMBNAIL:
- return MICROTHUMBNAIL_TARGET_SIZE;
- default:
- throw new RuntimeException(
- "should only request thumb/microthumb from cache");
+ return DecodeUtils.decodeThumbnail(jc, mLocalFilePath, options, targetSize, type);
}
}
@@ -217,8 +206,7 @@ public class LocalImage extends LocalMediaItem {
}
public BitmapRegionDecoder run(JobContext jc) {
- return DecodeUtils.requestCreateBitmapRegionDecoder(
- jc, mLocalFilePath, false);
+ return DecodeUtils.createBitmapRegionDecoder(jc, mLocalFilePath, false);
}
}
diff --git a/src/com/android/gallery3d/data/LocalVideo.java b/src/com/android/gallery3d/data/LocalVideo.java
index d68072bec..c45949ea5 100644
--- a/src/com/android/gallery3d/data/LocalVideo.java
+++ b/src/com/android/gallery3d/data/LocalVideo.java
@@ -147,7 +147,7 @@ public class LocalVideo extends LocalMediaItem {
LocalVideoRequest(GalleryApp application, Path path, int type,
String localFilePath) {
- super(application, path, type, LocalImage.getTargetSize(type));
+ super(application, path, type, MediaItem.getTargetSize(type));
mLocalFilePath = localFilePath;
}
diff --git a/src/com/android/gallery3d/data/MediaItem.java b/src/com/android/gallery3d/data/MediaItem.java
index 13612321c..b682c2d1b 100644
--- a/src/com/android/gallery3d/data/MediaItem.java
+++ b/src/com/android/gallery3d/data/MediaItem.java
@@ -16,11 +16,11 @@
package com.android.gallery3d.data;
-import com.android.gallery3d.util.ThreadPool.Job;
-
import android.graphics.Bitmap;
import android.graphics.BitmapRegionDecoder;
+import com.android.gallery3d.util.ThreadPool.Job;
+
// MediaItem represents an image or a video item.
public abstract class MediaItem extends MediaObject {
// NOTE: These type numbers are stored in the image cache, so it should not
@@ -89,4 +89,16 @@ public abstract class MediaItem extends MediaObject {
// Returns 0, 0 if the information is not available.
public abstract int getWidth();
public abstract int getHeight();
+
+ public static int getTargetSize(int type) {
+ switch (type) {
+ case TYPE_THUMBNAIL:
+ return THUMBNAIL_TARGET_SIZE;
+ case TYPE_MICROTHUMBNAIL:
+ return MICROTHUMBNAIL_TARGET_SIZE;
+ default:
+ throw new RuntimeException(
+ "should only request thumb/microthumb from cache");
+ }
+ }
}
diff --git a/src/com/android/gallery3d/data/MtpImage.java b/src/com/android/gallery3d/data/MtpImage.java
index 211b2f2ee..bdbaecd22 100644
--- a/src/com/android/gallery3d/data/MtpImage.java
+++ b/src/com/android/gallery3d/data/MtpImage.java
@@ -84,7 +84,7 @@ public class MtpImage extends MediaItem {
Log.w(TAG, "decoding thumbnail failed");
return null;
}
- return DecodeUtils.requestDecode(jc, thumbnail, null);
+ return DecodeUtils.decode(jc, thumbnail, null);
}
};
}
@@ -95,7 +95,7 @@ public class MtpImage extends MediaItem {
public BitmapRegionDecoder run(JobContext jc) {
byte[] bytes = mMtpContext.getMtpClient().getObject(
UsbDevice.getDeviceName(mDeviceId), mObjectId, mObjectSize);
- return DecodeUtils.requestCreateBitmapRegionDecoder(
+ return DecodeUtils.createBitmapRegionDecoder(
jc, bytes, 0, bytes.length, false);
}
};
diff --git a/src/com/android/gallery3d/data/UriImage.java b/src/com/android/gallery3d/data/UriImage.java
index 8f91cc036..05850bbb3 100644
--- a/src/com/android/gallery3d/data/UriImage.java
+++ b/src/com/android/gallery3d/data/UriImage.java
@@ -16,13 +16,6 @@
package com.android.gallery3d.data;
-import com.android.gallery3d.app.GalleryApp;
-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.Job;
-import com.android.gallery3d.util.ThreadPool.JobContext;
-
import android.content.ContentResolver;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
@@ -32,6 +25,13 @@ import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.webkit.MimeTypeMap;
+import com.android.gallery3d.app.GalleryApp;
+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.Job;
+import com.android.gallery3d.util.ThreadPool.JobContext;
+
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
@@ -180,7 +180,7 @@ public class UriImage extends MediaItem {
private class RegionDecoderJob implements Job<BitmapRegionDecoder> {
public BitmapRegionDecoder run(JobContext jc) {
if (!prepareInputFile(jc)) return null;
- BitmapRegionDecoder decoder = DecodeUtils.requestCreateBitmapRegionDecoder(
+ BitmapRegionDecoder decoder = DecodeUtils.createBitmapRegionDecoder(
jc, mFileDescriptor.getFileDescriptor(), false);
mWidth = decoder.getWidth();
mHeight = decoder.getHeight();
@@ -195,25 +195,24 @@ public class UriImage extends MediaItem {
mType = type;
}
+ @Override
public Bitmap run(JobContext jc) {
if (!prepareInputFile(jc)) return null;
- int targetSize = LocalImage.getTargetSize(mType);
+ int targetSize = MediaItem.getTargetSize(mType);
Options options = new Options();
options.inPreferredConfig = Config.ARGB_8888;
- Bitmap bitmap = DecodeUtils.requestDecode(jc,
- mFileDescriptor.getFileDescriptor(), options, targetSize);
+ Bitmap bitmap = DecodeUtils.decodeThumbnail(jc,
+ mFileDescriptor.getFileDescriptor(), options, targetSize, mType);
+
if (jc.isCancelled() || bitmap == null) {
return null;
}
if (mType == MediaItem.TYPE_MICROTHUMBNAIL) {
- bitmap = BitmapUtils.resizeDownAndCropCenter(bitmap,
- targetSize, true);
+ bitmap = BitmapUtils.resizeAndCropCenter(bitmap, targetSize, true);
} else {
- bitmap = BitmapUtils.resizeDownBySideLength(bitmap,
- targetSize, true);
+ bitmap = BitmapUtils.resizeDownBySideLength(bitmap, targetSize, true);
}
-
return bitmap;
}
}
diff --git a/src/com/android/gallery3d/ui/AbstractDisplayItem.java b/src/com/android/gallery3d/ui/AbstractDisplayItem.java
index 1f8165fb4..2547ebf31 100644
--- a/src/com/android/gallery3d/ui/AbstractDisplayItem.java
+++ b/src/com/android/gallery3d/ui/AbstractDisplayItem.java
@@ -16,10 +16,10 @@
package com.android.gallery3d.ui;
-import com.android.gallery3d.data.MediaItem;
-
import android.graphics.Bitmap;
+import com.android.gallery3d.data.MediaItem;
+
public abstract class AbstractDisplayItem extends DisplayItem {
private static final String TAG = "AbstractDisplayItem";
@@ -44,6 +44,7 @@ public abstract class AbstractDisplayItem extends DisplayItem {
protected void updateImage(Bitmap bitmap, boolean isCancelled) {
if (mRecycling) {
+ recycleBitmap(bitmap);
return;
}
@@ -96,7 +97,10 @@ public abstract class AbstractDisplayItem extends DisplayItem {
public void recycle() {
if (!inState(STATE_UPDATING | STATE_CANCELING)) {
- if (mBitmap != null) mBitmap = null;
+ if (mBitmap != null) {
+ recycleBitmap(mBitmap);
+ mBitmap = null;
+ }
} else {
mRecycling = true;
cancelImageRequest();
@@ -110,4 +114,5 @@ public abstract class AbstractDisplayItem extends DisplayItem {
abstract protected void startLoadBitmap();
abstract protected void cancelLoadBitmap();
abstract protected void onBitmapAvailable(Bitmap bitmap);
+ abstract protected void recycleBitmap(Bitmap bitmap);
}
diff --git a/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java b/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java
index b02f19e90..5bead6297 100644
--- a/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java
+++ b/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java
@@ -352,6 +352,11 @@ public class AlbumSetSlidingWindow implements AlbumSetView.ModelListener {
}
@Override
+ protected void recycleBitmap(Bitmap bitmap) {
+ BitmapPool.recycle(BitmapPool.TYPE_MICRO_THUMB, bitmap);
+ }
+
+ @Override
protected void onBitmapAvailable(Bitmap bitmap) {
if (isActiveSlot(mSlotIndex)) {
--mActiveRequestCount;
diff --git a/src/com/android/gallery3d/ui/AlbumSlidingWindow.java b/src/com/android/gallery3d/ui/AlbumSlidingWindow.java
index 8d61f934d..142a309b2 100644
--- a/src/com/android/gallery3d/ui/AlbumSlidingWindow.java
+++ b/src/com/android/gallery3d/ui/AlbumSlidingWindow.java
@@ -293,6 +293,15 @@ public class AlbumSlidingWindow implements AlbumView.ModelListener {
}
@Override
+ protected void recycleBitmap(Bitmap bitmap) {
+ // if mCacheThumbSize > 0, we will keep images in cache so that
+ // we cannot recycle the bitmap
+ if (mCacheThumbSize == 0) {
+ BitmapPool.recycle(BitmapPool.TYPE_MICRO_THUMB, bitmap);
+ }
+ }
+
+ @Override
protected void onBitmapAvailable(Bitmap bitmap) {
boolean isActiveSlot = isActiveSlot(mSlotIndex);
if (isActiveSlot) {
diff --git a/src/com/android/gallery3d/ui/BitmapPool.java b/src/com/android/gallery3d/ui/BitmapPool.java
new file mode 100644
index 000000000..e910aece1
--- /dev/null
+++ b/src/com/android/gallery3d/ui/BitmapPool.java
@@ -0,0 +1,113 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+
+package com.android.gallery3d.ui;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapFactory.Options;
+
+import com.android.gallery3d.data.DecodeUtils;
+import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.util.ThreadPool.JobContext;
+
+import java.io.FileDescriptor;
+import java.util.ArrayList;
+
+public class BitmapPool {
+ private static final String TAG = "BitmapPool";
+
+ public static final int TYPE_MICRO_THUMB = 0;
+ private static final int TYPE_COUNT = 1;
+ private static final int POOL_SIZE = 16;
+ private static final int EXPECTED_WIDTH[] = {MediaItem.MICROTHUMBNAIL_TARGET_SIZE};
+ private static final int EXPECTED_HEIGHT[] = {MediaItem.MICROTHUMBNAIL_TARGET_SIZE};
+
+ @SuppressWarnings("unchecked")
+ private static final ArrayList<Bitmap> sPools[] = new ArrayList[TYPE_COUNT];
+ static {
+ for (int i = 0; i < TYPE_COUNT; ++i) {
+ sPools[i] = new ArrayList<Bitmap>();
+ }
+ }
+
+ private BitmapPool() {
+ }
+
+ public static Bitmap getBitmap(int type) {
+ ArrayList<Bitmap> list = sPools[type];
+ synchronized (list) {
+ int size = list.size();
+ return size > 0 ? list.remove(size - 1) : null;
+ }
+ }
+
+ public static void recycle(int type, Bitmap bitmap) {
+ if (bitmap == null) return;
+ if ((bitmap.getWidth() != EXPECTED_WIDTH[type])
+ || (bitmap.getHeight() != EXPECTED_HEIGHT[type])) {
+ bitmap.recycle();
+ return;
+ }
+ ArrayList<Bitmap> list = sPools[type];
+ synchronized (list) {
+ if (list.size() < POOL_SIZE) list.add(bitmap);
+ }
+ }
+
+ public static void clear() {
+ for (int i = 0; i < TYPE_COUNT; ++i) {
+ ArrayList<Bitmap> list = sPools[i];
+ synchronized (list) {
+ list.clear();
+ }
+ }
+ }
+
+ public static Bitmap decode(JobContext jc, int type,
+ byte[] data, int offset, int length, BitmapFactory.Options options) {
+ if (options == null) options = new BitmapFactory.Options();
+ if (options.inSampleSize < 1) options.inSampleSize = 1;
+ options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ options.inBitmap = (options.inSampleSize == 1) ? getBitmap(type) : null;
+ try {
+ Bitmap bitmap = DecodeUtils.decode(jc, data, offset, length, options);
+ if (options.inBitmap != null && options.inBitmap != bitmap) {
+ recycle(type, bitmap);
+ options.inBitmap = null;
+ }
+ return bitmap;
+ } catch (IllegalArgumentException e) {
+ if (options.inBitmap == null) throw e;
+
+ Log.w(TAG, "decode fail with a given bitmap, try decode to a new bitmap");
+ recycle(type, options.inBitmap);
+ options.inBitmap = null;
+ return DecodeUtils.decode(jc, data, offset, length, options);
+ }
+ }
+
+ // This is the same as the method above except the source data comes
+ // from a file descriptor instead of a byte array.
+ public static Bitmap decode(int type,
+ JobContext jc, FileDescriptor fileDescriptor, Options options) {
+ if (options == null) options = new BitmapFactory.Options();
+ if (options.inSampleSize < 1) options.inSampleSize = 1;
+ options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ options.inBitmap = (options.inSampleSize == 1) ? getBitmap(type) : null;
+ try {
+ Bitmap bitmap = DecodeUtils.decode(jc, fileDescriptor, options);
+ if (options.inBitmap != null&& options.inBitmap != bitmap) {
+ recycle(type, bitmap);
+ options.inBitmap = null;
+ }
+ return bitmap;
+ } catch (IllegalArgumentException e) {
+ if (options.inBitmap == null) throw e;
+
+ Log.w(TAG, "decode fail with a given bitmap, try decode to a new bitmap");
+ recycle(type, options.inBitmap);
+ options.inBitmap = null;
+ return DecodeUtils.decode(jc, fileDescriptor, options);
+ }
+ }
+}