import android.content.ContentResolver;
import android.content.Context;
-import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Matrix;
import android.graphics.Rect;
-import android.graphics.Bitmap.CompressFormat;
import android.media.ExifInterface;
import android.net.Uri;
import android.provider.MediaStore;
import com.adobe.xmp.XMPException;
import com.adobe.xmp.XMPMeta;
-
import com.android.gallery3d.R;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.exif.ExifInvalidFormatException;
import com.android.gallery3d.exif.ExifParser;
import com.android.gallery3d.exif.ExifTag;
-import com.android.gallery3d.filtershow.CropExtras;
import com.android.gallery3d.filtershow.FilterShowActivity;
import com.android.gallery3d.filtershow.HistoryAdapter;
-import com.android.gallery3d.filtershow.imageshow.ImageCrop;
import com.android.gallery3d.filtershow.imageshow.ImageShow;
import com.android.gallery3d.filtershow.presets.ImagePreset;
import com.android.gallery3d.filtershow.tools.BitmapTask;
import com.android.gallery3d.util.InterruptableOutputStream;
import com.android.gallery3d.util.XmpUtilHelper;
-import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Vector;
import java.util.concurrent.locks.ReentrantLock;
+
+// TODO: this class has waaaay to much bitmap copying. Cleanup.
public class ImageLoader {
private static final String LOGTAG = "ImageLoader";
public static final int ORI_TRANSPOSE = ExifInterface.ORIENTATION_TRANSPOSE;
public static final int ORI_TRANSVERSE = ExifInterface.ORIENTATION_TRANSVERSE;
+ private static final int BITMAP_LOAD_BACKOUT_ATTEMPTS = 5;
private Context mContext = null;
private Uri mUri = null;
bitmap.getHeight(), matrix, true);
}
- private Bitmap loadRegionBitmap(Uri uri, Rect bounds) {
+ private Bitmap loadRegionBitmap(Uri uri, BitmapFactory.Options options, Rect bounds) {
InputStream is = null;
try {
is = mContext.getContentResolver().openInputStream(uri);
BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
- return decoder.decodeRegion(bounds, null);
+ return decoder.decodeRegion(bounds, options);
} catch (FileNotFoundException e) {
Log.e(LOGTAG, "FileNotFoundException: " + uri);
} catch (Exception e) {
// decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
+ o2.inMutable = true;
Utils.closeSilently(is);
is = mContext.getContentResolver().openInputStream(uri);
// FIXME: this currently does the loading + filtering on the UI thread --
// need to move this to a background thread.
public Bitmap getScaleOneImageForPreset(ImageShow caller, ImagePreset imagePreset, Rect bounds,
- boolean force) {
+ Rect destination, boolean force) {
mLoadingLock.lock();
Bitmap bmp = mZoomCache.getImage(imagePreset, bounds);
if (force || bmp == null) {
- bmp = loadRegionBitmap(mUri, bounds);
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inMutable = true;
+ if (destination != null) {
+ if (bounds.width() > destination.width()) {
+ int sampleSize = 1;
+ int w = bounds.width();
+ while (w > destination.width()) {
+ sampleSize *= 2;
+ w /= sampleSize;
+ }
+ options.inSampleSize = sampleSize;
+ }
+ }
+ bmp = loadRegionBitmap(mUri, options, bounds);
+ if (destination != null) {
+ mLoadingLock.unlock();
+ return bmp;
+ }
if (bmp != null) {
- // TODO: this workaround for RS might not be needed ultimately
- Bitmap bmp2 = bmp.copy(Bitmap.Config.ARGB_8888, true);
float scaleFactor = imagePreset.getScaleFactor();
- imagePreset.setScaleFactor(1.0f);
- bmp2 = imagePreset.apply(bmp2);
+ float scale = (float) bmp.getWidth() / (float) getOriginalBounds().width();
+ imagePreset.setScaleFactor(scale);
+ imagePreset.setupEnvironment();
+ bmp = imagePreset.apply(bmp);
imagePreset.setScaleFactor(scaleFactor);
- mZoomCache.setImage(imagePreset, bounds, bmp2);
+ mZoomCache.setImage(imagePreset, bounds, bmp);
mLoadingLock.unlock();
- return bmp2;
+ return bmp;
}
}
mLoadingLock.unlock();
public static Bitmap loadMutableBitmap(Context context, Uri sourceUri) {
BitmapFactory.Options options = new BitmapFactory.Options();
+ return loadMutableBitmap(context, sourceUri, options);
+ }
+
+ public static Bitmap loadMutableBitmap(Context context, Uri sourceUri,
+ BitmapFactory.Options options) {
// TODO: on <3.x we need a copy of the bitmap (inMutable doesn't
// exist)
options.inMutable = true;
+ Bitmap bitmap = decodeUriWithBackouts(context, sourceUri, options);
+ if (bitmap == null) {
+ return null;
+ }
+ int orientation = ImageLoader.getOrientation(context, sourceUri);
+ bitmap = ImageLoader.rotateToPortrait(bitmap, orientation);
+ return bitmap;
+ }
+
+ public static Bitmap decodeUriWithBackouts(Context context, Uri sourceUri,
+ BitmapFactory.Options options) {
+ boolean noBitmap = true;
+ int num_tries = 0;
+ InputStream is = getInputStream(context, sourceUri);
+
+ if (options.inSampleSize < 1) {
+ options.inSampleSize = 1;
+ }
+ // Stopgap fix for low-memory devices.
+ Bitmap bmap = null;
+ while (noBitmap) {
+ if (is == null) {
+ return null;
+ }
+ try {
+ // Try to decode, downsample if low-memory.
+ bmap = BitmapFactory.decodeStream(is, null, options);
+ noBitmap = false;
+ } catch (java.lang.OutOfMemoryError e) {
+ // Try 5 times before failing for good.
+ if (++num_tries >= BITMAP_LOAD_BACKOUT_ATTEMPTS) {
+ throw e;
+ }
+ is = null;
+ bmap = null;
+ System.gc();
+ is = getInputStream(context, sourceUri);
+ options.inSampleSize *= 2;
+ }
+ }
+ Utils.closeSilently(is);
+ return bmap;
+ }
+
+ private static InputStream getInputStream(Context context, Uri sourceUri) {
InputStream is = null;
- Bitmap bitmap = null;
try {
is = context.getContentResolver().openInputStream(sourceUri);
- bitmap = BitmapFactory.decodeStream(is, null, options);
} catch (FileNotFoundException e) {
Log.w(LOGTAG, "could not load bitmap ", e);
- is = null;
- bitmap = null;
- } finally {
Utils.closeSilently(is);
+ is = null;
}
- if (bitmap == null) {
- return null;
+ return is;
+ }
+
+ public static Bitmap decodeResourceWithBackouts(Resources res, BitmapFactory.Options options,
+ int id) {
+ boolean noBitmap = true;
+ int num_tries = 0;
+ if (options.inSampleSize < 1) {
+ options.inSampleSize = 1;
}
- int orientation = ImageLoader.getOrientation(context, sourceUri);
- bitmap = ImageLoader.rotateToPortrait(bitmap, orientation);
- return bitmap;
+ // Stopgap fix for low-memory devices.
+ Bitmap bmap = null;
+ while (noBitmap) {
+ try {
+ // Try to decode, downsample if low-memory.
+ bmap = BitmapFactory.decodeResource(
+ res, id, options);
+ noBitmap = false;
+ } catch (java.lang.OutOfMemoryError e) {
+ // Try 5 times before failing for good.
+ if (++num_tries >= BITMAP_LOAD_BACKOUT_ATTEMPTS) {
+ throw e;
+ }
+ bmap = null;
+ System.gc();
+ options.inSampleSize *= 2;
+ }
+ }
+ return bmap;
}
public void returnFilteredResult(ImagePreset preset,
preset.setQuality(ImagePreset.QUALITY_FINAL);
preset.setScaleFactor(1.0f);
- BitmapTask.Callbacks<ImagePreset> cb = new BitmapTask.Callbacks<ImagePreset>() {
+ BitmapTask.Callbacks<ImagePreset, Bitmap> cb = new BitmapTask.Callbacks<ImagePreset, Bitmap>() {
@Override
public void onComplete(Bitmap result) {
if (param == null || mUri == null) {
return null;
}
- Bitmap bitmap = loadMutableBitmap(mContext, mUri);
- if (bitmap == null) {
- Log.w(LOGTAG, "Failed to save image!");
- return null;
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ boolean noBitmap = true;
+ int num_tries = 0;
+ if (options.inSampleSize < 1) {
+ options.inSampleSize = 1;
+ }
+ Bitmap bitmap = null;
+ // Stopgap fix for low-memory devices.
+ while (noBitmap) {
+ try {
+ // Try to do bitmap operations, downsample if low-memory
+ bitmap = loadMutableBitmap(mContext, mUri, options);
+ if (bitmap == null) {
+ Log.w(LOGTAG, "Failed to save image!");
+ return null;
+ }
+ param.setupEnvironment();
+ bitmap = param.applyGeometry(bitmap);
+ bitmap = param.apply(bitmap);
+ noBitmap = false;
+ } catch (java.lang.OutOfMemoryError e) {
+ // Try 5 times before failing for good.
+ if (++num_tries >= 5) {
+ throw e;
+ }
+ bitmap = null;
+ System.gc();
+ options.inSampleSize *= 2;
+ }
}
- return param.apply(bitmap);
+ return bitmap;
}
};
- (new BitmapTask<ImagePreset>(cb)).execute(preset);
+ (new BitmapTask<ImagePreset, Bitmap>(cb)).execute(preset);
}
private String getFileExtension(String requestFormat) {
final InterruptableOutputStream ios = new InterruptableOutputStream(out);
- BitmapTask.Callbacks<Bitmap> cb = new BitmapTask.Callbacks<Bitmap>() {
+ BitmapTask.Callbacks<Bitmap, Bitmap> cb = new BitmapTask.Callbacks<Bitmap, Bitmap>() {
@Override
public void onComplete(Bitmap result) {
@Override
public Bitmap onExecute(Bitmap param) {
+ if (param == null) {
+ return null;
+ }
CompressFormat cf = convertExtensionToCompressFormat(getFileExtension(outputFormat));
param.compress(cf, DEFAULT_COMPRESS_QUALITY, ios);
Utils.closeSilently(ios);
}
};
- (new BitmapTask<Bitmap>(cb)).execute(bmap);
+ (new BitmapTask<Bitmap, Bitmap>(cb)).execute(bmap);
}
public void setAdapter(HistoryAdapter adapter) {