summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorRuben Brunk <rubenbrunk@google.com>2013-07-12 17:23:39 -0700
committerRuben Brunk <rubenbrunk@google.com>2013-07-15 10:02:43 -0700
commit0dca1b590f4cae2fe42afc9245f1a8ea81f9e6d3 (patch)
treef67b95279ee27db61031bd06e391cbc386015c5c /src
parent2212065e2de9ec6a2a105218a693a48145e9bfb6 (diff)
downloadandroid_packages_apps_Snap-0dca1b590f4cae2fe42afc9245f1a8ea81f9e6d3.zip
android_packages_apps_Snap-0dca1b590f4cae2fe42afc9245f1a8ea81f9e6d3.tar.gz
android_packages_apps_Snap-0dca1b590f4cae2fe42afc9245f1a8ea81f9e6d3.tar.bz2
Combine ImageLoader and CropLoader utility classes.
Bug: 9356969 Bug: 9170644 - Refactor to eliminate code duplication in image loading classes. - Fix bugs caused by the inability to determine MIME types for images that aren't in Gallery's content provider (causes b/9356969). Change-Id: I30c2ba5c0cd37aac624681a2cec9abddaa78f9c2
Diffstat (limited to 'src')
-rw-r--r--src/com/android/gallery3d/filtershow/FilterShowActivity.java20
-rw-r--r--src/com/android/gallery3d/filtershow/cache/ImageLoader.java363
-rw-r--r--src/com/android/gallery3d/filtershow/crop/CropActivity.java8
-rw-r--r--src/com/android/gallery3d/filtershow/crop/CropLoader.java318
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java7
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ImageShow.java3
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/MasterImage.java28
-rw-r--r--src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java62
8 files changed, 291 insertions, 518 deletions
diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
index c29709b..bee764b 100644
--- a/src/com/android/gallery3d/filtershow/FilterShowActivity.java
+++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
@@ -506,9 +506,11 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL
if (highresPreviewSize > originalBounds.width()) {
highresPreviewSize = originalBounds.width();
}
- Bitmap originalHires = ImageLoader.loadOrientedScaledBitmap(master,
- master.getActivity(), master.getUri(), highresPreviewSize, false,
- master.getOrientation());
+ Rect bounds = new Rect();
+ Bitmap originalHires = ImageLoader.loadOrientedConstrainedBitmap(master.getUri(),
+ master.getActivity(), highresPreviewSize,
+ master.getOrientation(), bounds);
+ master.setOriginalBounds(bounds);
master.setOriginalBitmapHighres(originalHires);
master.warnListeners();
}
@@ -639,14 +641,12 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL
super.onDestroy();
}
+ // TODO: find a more robust way of handling image size selection
+ // for high screen densities.
private int getScreenImageSize() {
- DisplayMetrics metrics = new DisplayMetrics();
- Display display = getWindowManager().getDefaultDisplay();
- Point size = new Point();
- display.getSize(size);
- display.getMetrics(metrics);
- int msize = Math.min(size.x, size.y);
- return (133 * msize) / metrics.densityDpi;
+ DisplayMetrics outMetrics = new DisplayMetrics();
+ getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
+ return (int) Math.max(outMetrics.heightPixels, outMetrics.widthPixels);
}
private void showSavingProgress(String albumName) {
diff --git a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java
index ca8c7a3..7c594c6 100644
--- a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java
+++ b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java
@@ -29,18 +29,15 @@ import android.graphics.Rect;
import android.net.Uri;
import android.provider.MediaStore;
import android.util.Log;
+import android.webkit.MimeTypeMap;
import com.adobe.xmp.XMPException;
import com.adobe.xmp.XMPMeta;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.exif.ExifInterface;
-import com.android.gallery3d.filtershow.FilterShowActivity;
import com.android.gallery3d.filtershow.imageshow.MasterImage;
-import com.android.gallery3d.filtershow.pipeline.ImagePreset;
-import com.android.gallery3d.filtershow.tools.SaveCopyTask;
import com.android.gallery3d.util.XmpUtilHelper;
-import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
@@ -50,8 +47,6 @@ public final class ImageLoader {
private static final String LOGTAG = "ImageLoader";
public static final String JPEG_MIME_TYPE = "image/jpeg";
-
- public static final String DEFAULT_SAVE_DIRECTORY = "EditedOnlinePhotos";
public static final int DEFAULT_COMPRESS_QUALITY = 95;
public static final int ORI_NORMAL = ExifInterface.Orientation.TOP_LEFT;
@@ -65,42 +60,39 @@ public final class ImageLoader {
private static final int BITMAP_LOAD_BACKOUT_ATTEMPTS = 5;
- public static final int MAX_BITMAP_DIM = 900;
- public static final int SMALL_BITMAP_DIM = 160;
-
private ImageLoader() {}
- public static int getOrientation(Context context, Uri uri) {
- if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
- String mimeType = context.getContentResolver().getType(uri);
- if (mimeType != ImageLoader.JPEG_MIME_TYPE) {
- return -1;
- }
- String path = uri.getPath();
- int orientation = -1;
- ExifInterface exif = new ExifInterface();
- try {
- exif.readExif(path);
- orientation = ExifInterface.getRotationForOrientationValue(
- exif.getTagIntValue(ExifInterface.TAG_ORIENTATION).shortValue());
- } catch (IOException e) {
- Log.w(LOGTAG, "Failed to read EXIF orientation", e);
- }
- return orientation;
+ /**
+ * Returns the Mime type for a Url. Safe to use with Urls that do not
+ * come from Gallery's content provider.
+ */
+ public static String getMimeType(Uri src) {
+ String postfix = MimeTypeMap.getFileExtensionFromUrl(src.toString());
+ String ret = null;
+ if (postfix != null) {
+ ret = MimeTypeMap.getSingleton().getMimeTypeFromExtension(postfix);
}
+ return ret;
+ }
+
+ /**
+ * Returns the image's orientation flag. Defaults to ORI_NORMAL if no valid
+ * orientation was found.
+ */
+ public static int getMetadataOrientation(Context context, Uri uri) {
+ if (uri == null || context == null) {
+ throw new IllegalArgumentException("bad argument to getOrientation");
+ }
+
+ // First try to find orientation data in Gallery's ContentProvider.
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(uri,
- new String[] {
- MediaStore.Images.ImageColumns.ORIENTATION
- },
+ new String[] { MediaStore.Images.ImageColumns.ORIENTATION },
null, null, null);
- if (cursor.moveToNext()) {
+ if (cursor != null && cursor.moveToNext()) {
int ori = cursor.getInt(0);
-
switch (ori) {
- case 0:
- return ORI_NORMAL;
case 90:
return ORI_ROTATE_90;
case 270:
@@ -108,24 +100,73 @@ public final class ImageLoader {
case 180:
return ORI_ROTATE_180;
default:
- return -1;
+ return ORI_NORMAL;
}
- } else {
- return -1;
}
} catch (SQLiteException e) {
- return -1;
+ // Do nothing
} catch (IllegalArgumentException e) {
- return -1;
+ // Do nothing
} finally {
Utils.closeSilently(cursor);
}
+
+ // Fall back to checking EXIF tags in file.
+ if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
+ String mimeType = getMimeType(uri);
+ if (!JPEG_MIME_TYPE.equals(mimeType)) {
+ return ORI_NORMAL;
+ }
+ String path = uri.getPath();
+ ExifInterface exif = new ExifInterface();
+ try {
+ exif.readExif(path);
+ Integer tagval = exif.getTagIntValue(ExifInterface.TAG_ORIENTATION);
+ if (tagval != null) {
+ int orientation = tagval;
+ switch(orientation) {
+ case ORI_NORMAL:
+ case ORI_ROTATE_90:
+ case ORI_ROTATE_180:
+ case ORI_ROTATE_270:
+ case ORI_FLIP_HOR:
+ case ORI_FLIP_VERT:
+ case ORI_TRANSPOSE:
+ case ORI_TRANSVERSE:
+ return orientation;
+ default:
+ return ORI_NORMAL;
+ }
+ }
+ } catch (IOException e) {
+ Log.w(LOGTAG, "Failed to read EXIF orientation", e);
+ }
+ }
+ return ORI_NORMAL;
}
- public static Bitmap decodeImage(Context context, int id, BitmapFactory.Options options) {
- return BitmapFactory.decodeResource(context.getResources(), id, options);
+ /**
+ * Returns the rotation of image at the given URI as one of 0, 90, 180,
+ * 270. Defaults to 0.
+ */
+ public static int getMetadataRotation(Context context, Uri uri) {
+ int orientation = getMetadataOrientation(context, uri);
+ switch(orientation) {
+ case ORI_ROTATE_90:
+ return 90;
+ case ORI_ROTATE_180:
+ return 180;
+ case ORI_ROTATE_270:
+ return 270;
+ default:
+ return 0;
+ }
}
+ /**
+ * Takes an orientation and a bitmap, and returns the bitmap transformed
+ * to that orientation.
+ */
public static Bitmap orientBitmap(Bitmap bitmap, int ori) {
Matrix matrix = new Matrix();
int w = bitmap.getWidth();
@@ -166,12 +207,16 @@ public final class ImageLoader {
default:
return bitmap;
}
-
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
bitmap.getHeight(), matrix, true);
}
- private static Bitmap loadRegionBitmap(Context context, Uri uri, BitmapFactory.Options options,
+ /**
+ * Returns the bitmap for the rectangular region given by "bounds"
+ * if it is a subset of the bitmap stored at uri. Otherwise returns
+ * null.
+ */
+ public static Bitmap loadRegionBitmap(Context context, Uri uri, BitmapFactory.Options options,
Rect bounds) {
InputStream is = null;
try {
@@ -184,75 +229,120 @@ public final class ImageLoader {
}
return decoder.decodeRegion(bounds, options);
} catch (FileNotFoundException e) {
- Log.e(LOGTAG, "FileNotFoundException: " + uri);
- } catch (Exception e) {
- e.printStackTrace();
+ Log.e(LOGTAG, "FileNotFoundException for " + uri, e);
+ } catch (IOException e) {
+ Log.e(LOGTAG, "FileNotFoundException for " + uri, e);
} finally {
Utils.closeSilently(is);
}
return null;
}
- public static Bitmap loadOrientedScaledBitmap(MasterImage master, Context context, Uri uri,
- int size, boolean enforceSize, int orientation) {
- Bitmap bmap = loadScaledBitmap(master, context, uri, size, enforceSize);
- if (bmap != null) {
- bmap = orientBitmap(bmap, orientation);
- }
- return bmap;
+ /**
+ * Returns the bounds of the bitmap stored at a given Url.
+ */
+ public static Rect loadBitmapBounds(Context context, Uri uri) {
+ BitmapFactory.Options o = new BitmapFactory.Options();
+ loadBitmap(context, uri, o);
+ return new Rect(0, 0, o.outWidth, o.outHeight);
}
- public static Bitmap loadScaledBitmap(MasterImage master, Context context, Uri uri, int size,
- boolean enforceSize) {
- InputStream is = null;
- try {
- is = context.getContentResolver().openInputStream(uri);
- Log.v(LOGTAG, "loading uri " + uri.getPath() + " input stream: "
- + is);
- BitmapFactory.Options o = new BitmapFactory.Options();
- o.inJustDecodeBounds = true;
- BitmapFactory.decodeStream(is, null, o);
-
- int width_tmp = o.outWidth;
- int height_tmp = o.outHeight;
-
- master.setOriginalBounds(new Rect(0, 0, width_tmp, height_tmp));
-
- int scale = 1;
- while (true) {
- if (width_tmp <= 2 || height_tmp <= 2) {
- break;
- }
- if (!enforceSize
- || (width_tmp <= MAX_BITMAP_DIM
- && height_tmp <= MAX_BITMAP_DIM)) {
- if (width_tmp / 2 < size || height_tmp / 2 < size) {
- break;
- }
- }
- width_tmp /= 2;
- height_tmp /= 2;
- scale *= 2;
- }
+ /**
+ * Loads a bitmap that has been downsampled using sampleSize from a given url.
+ */
+ public static Bitmap loadDownsampledBitmap(Context context, Uri uri, int sampleSize) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inMutable = true;
+ options.inSampleSize = sampleSize;
+ return loadBitmap(context, uri, options);
+ }
- // decode with inSampleSize
- BitmapFactory.Options o2 = new BitmapFactory.Options();
- o2.inSampleSize = scale;
- o2.inMutable = true;
- Utils.closeSilently(is);
+ /**
+ * Returns the bitmap from the given uri loaded using the given options.
+ * Returns null on failure.
+ */
+ public static Bitmap loadBitmap(Context context, Uri uri, BitmapFactory.Options o) {
+ if (uri == null || context == null) {
+ throw new IllegalArgumentException("bad argument to loadBitmap");
+ }
+ InputStream is = null;
+ try {
is = context.getContentResolver().openInputStream(uri);
- return BitmapFactory.decodeStream(is, null, o2);
+ return BitmapFactory.decodeStream(is, null, o);
} catch (FileNotFoundException e) {
- Log.e(LOGTAG, "FileNotFoundException: " + uri);
- } catch (Exception e) {
- e.printStackTrace();
+ Log.e(LOGTAG, "FileNotFoundException for " + uri, e);
} finally {
Utils.closeSilently(is);
}
return null;
}
+ /**
+ * Loads a bitmap at a given URI that is downsampled so that both sides are
+ * smaller than maxSideLength. The Bitmap's original dimensions are stored
+ * in the rect originalBounds.
+ *
+ * @param uri URI of image to open.
+ * @param context context whose ContentResolver to use.
+ * @param maxSideLength max side length of returned bitmap.
+ * @param originalBounds set to the actual bounds of the stored bitmap.
+ * @return downsampled bitmap or null if this operation failed.
+ */
+ public static Bitmap loadConstrainedBitmap(Uri uri, Context context, int maxSideLength,
+ Rect originalBounds) {
+ if (maxSideLength <= 0 || originalBounds == null || uri == null || context == null) {
+ throw new IllegalArgumentException("bad argument to getScaledBitmap");
+ }
+ // Get width and height of stored bitmap
+ Rect storedBounds = loadBitmapBounds(context, uri);
+ originalBounds.set(storedBounds);
+ int w = storedBounds.width();
+ int h = storedBounds.height();
+
+ // If bitmap cannot be decoded, return null
+ if (w <= 0 || h <= 0) {
+ return null;
+ }
+
+ // Find best downsampling size
+ int imageSide = Math.max(w, h);
+ int sampleSize = 1;
+ while (imageSide > maxSideLength) {
+ imageSide >>>= 1;
+ sampleSize <<= 1;
+ }
+
+ // Make sure sample size is reasonable
+ if (sampleSize <= 0 ||
+ 0 >= (int) (Math.min(w, h) / sampleSize)) {
+ return null;
+ }
+ return loadDownsampledBitmap(context, uri, sampleSize);
+ }
+
+ /**
+ * Loads a bitmap at a given URI that is downsampled so that both sides are
+ * smaller than maxSideLength. The Bitmap's original dimensions are stored
+ * in the rect originalBounds. The output is also transformed to the given
+ * orientation.
+ *
+ * @param uri URI of image to open.
+ * @param context context whose ContentResolver to use.
+ * @param maxSideLength max side length of returned bitmap.
+ * @param orientation the orientation to transform the bitmap to.
+ * @param originalBounds set to the actual bounds of the stored bitmap.
+ * @return downsampled bitmap or null if this operation failed.
+ */
+ public static Bitmap loadOrientedConstrainedBitmap(Uri uri, Context context, int maxSideLength,
+ int orientation, Rect originalBounds) {
+ Bitmap bmap = loadConstrainedBitmap(uri, context, maxSideLength, originalBounds);
+ if (bmap != null) {
+ bmap = orientBitmap(bmap, orientation);
+ }
+ return bmap;
+ }
+
public static Bitmap getScaleOneImageForPreset(Context context, Uri uri, Rect bounds,
Rect destination) {
BitmapFactory.Options options = new BitmapFactory.Options();
@@ -272,88 +362,53 @@ public final class ImageLoader {
return bmp;
}
- public static void saveImage(ImagePreset preset, final FilterShowActivity filterShowActivity,
- File destination) {
- Uri selectedImageUri = filterShowActivity.getSelectedImageUri();
- new SaveCopyTask(filterShowActivity, MasterImage.getImage().getUri(), selectedImageUri,
- destination,
- new SaveCopyTask.Callback() {
-
- @Override
- public void onComplete(Uri result) {
- filterShowActivity.completeSaveImage(result);
- }
-
- }).execute(preset);
- }
-
- 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.orientBitmap(bitmap, orientation);
- return bitmap;
- }
-
- public static Bitmap decodeUriWithBackouts(Context context, Uri sourceUri,
- BitmapFactory.Options options) {
+ /**
+ * Loads a bitmap that is downsampled by at least the input sample size. In
+ * low-memory situations, the bitmap may be downsampled further.
+ */
+ public static Bitmap loadBitmapWithBackouts(Context context, Uri sourceUri, int sampleSize) {
boolean noBitmap = true;
int num_tries = 0;
- InputStream is = getInputStream(context, sourceUri);
-
- if (options.inSampleSize < 1) {
- options.inSampleSize = 1;
+ if (sampleSize <= 0) {
+ sampleSize = 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);
+ bmap = loadDownsampledBitmap(context, sourceUri, sampleSize);
noBitmap = false;
} catch (java.lang.OutOfMemoryError e) {
- // Try 5 times before failing for good.
+ // Try with more downsampling 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;
+ sampleSize *= 2;
}
}
- Utils.closeSilently(is);
return bmap;
}
- private static InputStream getInputStream(Context context, Uri sourceUri) {
- InputStream is = null;
- try {
- is = context.getContentResolver().openInputStream(sourceUri);
- } catch (FileNotFoundException e) {
- Log.w(LOGTAG, "could not load bitmap ", e);
- Utils.closeSilently(is);
- is = null;
+ /**
+ * Loads an oriented bitmap that is downsampled by at least the input sample
+ * size. In low-memory situations, the bitmap may be downsampled further.
+ */
+ public static Bitmap loadOrientedBitmapWithBackouts(Context context, Uri sourceUri,
+ int sampleSize) {
+ Bitmap bitmap = loadBitmapWithBackouts(context, sourceUri, sampleSize);
+ if (bitmap == null) {
+ return null;
}
- return is;
+ int orientation = getMetadataOrientation(context, sourceUri);
+ bitmap = orientBitmap(bitmap, orientation);
+ return bitmap;
}
+ /**
+ * Loads bitmap from a resource that may be downsampled in low-memory situations.
+ */
public static Bitmap decodeResourceWithBackouts(Resources res, BitmapFactory.Options options,
int id) {
boolean noBitmap = true;
@@ -370,7 +425,7 @@ public final class ImageLoader {
res, id, options);
noBitmap = false;
} catch (java.lang.OutOfMemoryError e) {
- // Try 5 times before failing for good.
+ // Retry before failing for good.
if (++num_tries >= BITMAP_LOAD_BACKOUT_ATTEMPTS) {
throw e;
}
diff --git a/src/com/android/gallery3d/filtershow/crop/CropActivity.java b/src/com/android/gallery3d/filtershow/crop/CropActivity.java
index d349d5d..d14c090 100644
--- a/src/com/android/gallery3d/filtershow/crop/CropActivity.java
+++ b/src/com/android/gallery3d/filtershow/crop/CropActivity.java
@@ -44,6 +44,8 @@ import android.widget.Toast;
import com.android.gallery3d.R;
import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.filtershow.cache.ImageLoader;
+import com.android.gallery3d.filtershow.tools.SaveCopyTask;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -261,9 +263,9 @@ public class CropActivity extends Activity {
@Override
protected Bitmap doInBackground(Uri... params) {
Uri uri = params[0];
- Bitmap bmap = CropLoader.getConstrainedBitmap(uri, mContext, mBitmapSize,
+ Bitmap bmap = ImageLoader.loadConstrainedBitmap(uri, mContext, mBitmapSize,
mOriginalBounds);
- mOrientation = CropLoader.getMetadataRotation(uri, mContext);
+ mOrientation = ImageLoader.getMetadataRotation(mContext, uri);
return bmap;
}
@@ -297,7 +299,7 @@ public class CropActivity extends Activity {
}
}
if (flags == 0) {
- destinationUri = CropLoader.makeAndInsertUri(this, mSourceUri);
+ destinationUri = SaveCopyTask.makeAndInsertUri(this, mSourceUri);
if (destinationUri != null) {
flags |= DO_EXTRA_OUTPUT;
}
diff --git a/src/com/android/gallery3d/filtershow/crop/CropLoader.java b/src/com/android/gallery3d/filtershow/crop/CropLoader.java
deleted file mode 100644
index 53a9ebc..0000000
--- a/src/com/android/gallery3d/filtershow/crop/CropLoader.java
+++ /dev/null
@@ -1,318 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.filtershow.crop;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteException;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.os.Environment;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Images;
-import android.provider.MediaStore.Images.ImageColumns;
-import android.util.Log;
-import android.webkit.MimeTypeMap;
-
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.exif.ExifInterface;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.sql.Date;
-import java.text.SimpleDateFormat;
-
-/**
- * This class contains static methods for loading a bitmap and
- * maintains no instance state.
- */
-public abstract class CropLoader {
- public static final String LOGTAG = "CropLoader";
- public static final String JPEG_MIME_TYPE = "image/jpeg";
-
- private static final String TIME_STAMP_NAME = "'IMG'_yyyyMMdd_HHmmss";
- public static final String DEFAULT_SAVE_DIRECTORY = "EditedOnlinePhotos";
-
- /**
- * Returns the orientation of image at the given URI as one of 0, 90, 180,
- * 270.
- *
- * @param uri URI of image to open.
- * @param context context whose ContentResolver to use.
- * @return the orientation of the image. Defaults to 0.
- */
- public static int getMetadataRotation(Uri uri, Context context) {
- if (uri == null || context == null) {
- throw new IllegalArgumentException("bad argument to getScaledBitmap");
- }
-
- // First try to find orientation data in Gallery's ContentProvider.
- Cursor cursor = null;
- try {
- cursor = context.getContentResolver().query(uri,
- new String[] { MediaStore.Images.ImageColumns.ORIENTATION },
- null, null, null);
- if (cursor != null && cursor.moveToNext()) {
- int ori = cursor.getInt(0);
- return (ori < 0) ? 0 : ori;
- }
- } catch (SQLiteException e) {
- // Do nothing
- } catch (IllegalArgumentException e) {
- // Do nothing
- } finally {
- Utils.closeSilently(cursor);
- }
-
- // Fall back to checking EXIF tags in file.
- if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
- String mimeType = getMimeType(uri);
- if (!JPEG_MIME_TYPE.equals(mimeType)) {
- return 0;
- }
- String path = uri.getPath();
- int orientation = 0;
- ExifInterface exif = new ExifInterface();
- try {
- exif.readExif(path);
- Integer tagval = exif.getTagIntValue(ExifInterface.TAG_ORIENTATION);
- if (tagval != null) {
- orientation = ExifInterface.getRotationForOrientationValue(tagval.shortValue());
- }
- } catch (IOException e) {
- Log.w(LOGTAG, "Failed to read EXIF orientation", e);
- }
- return orientation;
- }
- return 0;
- }
-
- /**
- * Gets a bitmap at a given URI that is downsampled so that both sides are
- * smaller than maxSideLength. The Bitmap's original dimensions are stored
- * in the rect originalBounds.
- *
- * @param uri URI of image to open.
- * @param context context whose ContentResolver to use.
- * @param maxSideLength max side length of returned bitmap.
- * @param originalBounds set to the actual bounds of the stored bitmap.
- * @return downsampled bitmap or null if this operation failed.
- */
- public static Bitmap getConstrainedBitmap(Uri uri, Context context, int maxSideLength,
- Rect originalBounds) {
- if (maxSideLength <= 0 || originalBounds == null || uri == null || context == null) {
- throw new IllegalArgumentException("bad argument to getScaledBitmap");
- }
- InputStream is = null;
- try {
- // Get width and height of stored bitmap
- is = context.getContentResolver().openInputStream(uri);
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeStream(is, null, options);
- int w = options.outWidth;
- int h = options.outHeight;
- originalBounds.set(0, 0, w, h);
-
- // If bitmap cannot be decoded, return null
- if (w <= 0 || h <= 0) {
- return null;
- }
-
- options = new BitmapFactory.Options();
-
- // Find best downsampling size
- int imageSide = Math.max(w, h);
- options.inSampleSize = 1;
- if (imageSide > maxSideLength) {
- int shifts = 1 + Integer.numberOfLeadingZeros(maxSideLength)
- - Integer.numberOfLeadingZeros(imageSide);
- options.inSampleSize <<= shifts;
- }
-
- // Make sure sample size is reasonable
- if (options.inSampleSize <= 0 ||
- 0 >= (int) (Math.min(w, h) / options.inSampleSize)) {
- return null;
- }
-
- // Decode actual bitmap.
- options.inMutable = true;
- is.close();
- is = context.getContentResolver().openInputStream(uri);
- return BitmapFactory.decodeStream(is, null, options);
- } catch (FileNotFoundException e) {
- Log.e(LOGTAG, "FileNotFoundException: " + uri, e);
- } catch (IOException e) {
- Log.e(LOGTAG, "IOException: " + uri, e);
- } finally {
- Utils.closeSilently(is);
- }
- return null;
- }
-
- /**
- * Gets a bitmap that has been downsampled using sampleSize.
- *
- * @param uri URI of image to open.
- * @param context context whose ContentResolver to use.
- * @param sampleSize downsampling amount.
- * @return downsampled bitmap.
- */
- public static Bitmap getBitmap(Uri uri, Context context, int sampleSize) {
- if (uri == null || context == null) {
- throw new IllegalArgumentException("bad argument to getScaledBitmap");
- }
- InputStream is = null;
- try {
- is = context.getContentResolver().openInputStream(uri);
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inMutable = true;
- options.inSampleSize = sampleSize;
- return BitmapFactory.decodeStream(is, null, options);
- } catch (FileNotFoundException e) {
- Log.e(LOGTAG, "FileNotFoundException: " + uri, e);
- } finally {
- Utils.closeSilently(is);
- }
- return null;
- }
-
- // TODO: Super gnarly (copied from SaveCopyTask.java), do cleanup.
-
- public static File getFinalSaveDirectory(Context context, Uri sourceUri) {
- File saveDirectory = getSaveDirectory(context, sourceUri);
- if ((saveDirectory == null) || !saveDirectory.canWrite()) {
- saveDirectory = new File(Environment.getExternalStorageDirectory(),
- DEFAULT_SAVE_DIRECTORY);
- }
- // Create the directory if it doesn't exist
- if (!saveDirectory.exists())
- saveDirectory.mkdirs();
- return saveDirectory;
- }
-
-
-
- public static String getNewFileName(long time) {
- return new SimpleDateFormat(TIME_STAMP_NAME).format(new Date(time));
- }
-
- public static File getNewFile(Context context, Uri sourceUri, String filename) {
- File saveDirectory = getFinalSaveDirectory(context, sourceUri);
- return new File(saveDirectory, filename + ".JPG");
- }
-
- private interface ContentResolverQueryCallback {
-
- void onCursorResult(Cursor cursor);
- }
-
- private static void querySource(Context context, Uri sourceUri, String[] projection,
- ContentResolverQueryCallback callback) {
- ContentResolver contentResolver = context.getContentResolver();
- Cursor cursor = null;
- try {
- cursor = contentResolver.query(sourceUri, projection, null, null,
- null);
- if ((cursor != null) && cursor.moveToNext()) {
- callback.onCursorResult(cursor);
- }
- } catch (Exception e) {
- // Ignore error for lacking the data column from the source.
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
-
- private static File getSaveDirectory(Context context, Uri sourceUri) {
- final File[] dir = new File[1];
- querySource(context, sourceUri, new String[] {
- ImageColumns.DATA }, new ContentResolverQueryCallback() {
- @Override
- public void onCursorResult(Cursor cursor) {
- dir[0] = new File(cursor.getString(0)).getParentFile();
- }
- });
- return dir[0];
- }
-
- private static String getMimeType(Uri src) {
- String postfix = MimeTypeMap.getFileExtensionFromUrl(src.toString());
- String ret = null;
- if (postfix != null) {
- ret = MimeTypeMap.getSingleton().getMimeTypeFromExtension(postfix);
- }
- return ret;
- }
-
- public static Uri insertContent(Context context, Uri sourceUri, File file, String saveFileName,
- long time) {
- time /= 1000;
-
- final ContentValues values = new ContentValues();
- values.put(Images.Media.TITLE, saveFileName);
- values.put(Images.Media.DISPLAY_NAME, file.getName());
- values.put(Images.Media.MIME_TYPE, "image/jpeg");
- values.put(Images.Media.DATE_TAKEN, time);
- values.put(Images.Media.DATE_MODIFIED, time);
- values.put(Images.Media.DATE_ADDED, time);
- values.put(Images.Media.ORIENTATION, 0);
- values.put(Images.Media.DATA, file.getAbsolutePath());
- values.put(Images.Media.SIZE, file.length());
-
- final String[] projection = new String[] {
- ImageColumns.DATE_TAKEN,
- ImageColumns.LATITUDE, ImageColumns.LONGITUDE,
- };
- querySource(context, sourceUri, projection,
- new ContentResolverQueryCallback() {
-
- @Override
- public void onCursorResult(Cursor cursor) {
- values.put(Images.Media.DATE_TAKEN, cursor.getLong(0));
-
- double latitude = cursor.getDouble(1);
- double longitude = cursor.getDouble(2);
- // TODO: Change || to && after the default location
- // issue is fixed.
- if ((latitude != 0f) || (longitude != 0f)) {
- values.put(Images.Media.LATITUDE, latitude);
- values.put(Images.Media.LONGITUDE, longitude);
- }
- }
- });
-
- return context.getContentResolver().insert(
- Images.Media.EXTERNAL_CONTENT_URI, values);
- }
-
- public static Uri makeAndInsertUri(Context context, Uri sourceUri) {
- long time = System.currentTimeMillis();
- String filename = getNewFileName(time);
- File file = getNewFile(context, sourceUri, filename);
- return insertContent(context, sourceUri, file, filename, time);
- }
-}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java
index 0458a22..7df5ffb 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java
@@ -133,15 +133,16 @@ public class ImageFilterDraw extends ImageFilter {
if (mBrush == null) {
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.ALPHA_8;
- mBrush = ImageLoader.decodeImage(MasterImage.getImage().getActivity(), mBrushID,
- opt);
+ mBrush = BitmapFactory.decodeResource(MasterImage.getImage().getActivity()
+ .getResources(), mBrushID, opt);
mBrush = mBrush.extractAlpha();
}
return mBrush;
}
@Override
- public void paint(FilterDrawRepresentation.StrokeData sd, Canvas canvas, Matrix toScrMatrix,
+ public void paint(FilterDrawRepresentation.StrokeData sd, Canvas canvas,
+ Matrix toScrMatrix,
int quality) {
if (sd == null || sd.mPath == null) {
return;
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java
index 8b9d57c..2da358c 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java
@@ -40,6 +40,7 @@ import com.android.gallery3d.filtershow.FilterShowActivity;
import com.android.gallery3d.filtershow.cache.ImageLoader;
import com.android.gallery3d.filtershow.filters.ImageFilter;
import com.android.gallery3d.filtershow.pipeline.ImagePreset;
+import com.android.gallery3d.filtershow.tools.SaveCopyTask;
import java.io.File;
@@ -394,7 +395,7 @@ public class ImageShow extends View implements OnGestureListener,
}
public void saveImage(FilterShowActivity filterShowActivity, File file) {
- ImageLoader.saveImage(getImagePreset(), filterShowActivity, file);
+ SaveCopyTask.saveImage(getImagePreset(), filterShowActivity, file);
}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java b/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java
index 7b11cf3..08d8c45 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java
@@ -26,29 +26,29 @@ import android.os.Handler;
import android.os.Message;
import com.android.gallery3d.filtershow.FilterShowActivity;
-import com.android.gallery3d.filtershow.history.HistoryManager;
-import com.android.gallery3d.filtershow.history.HistoryItem;
-import com.android.gallery3d.filtershow.pipeline.FilteringPipeline;
import com.android.gallery3d.filtershow.cache.ImageLoader;
-import com.android.gallery3d.filtershow.pipeline.RenderingRequest;
-import com.android.gallery3d.filtershow.pipeline.RenderingRequestCaller;
import com.android.gallery3d.filtershow.filters.FilterRepresentation;
import com.android.gallery3d.filtershow.filters.ImageFilter;
+import com.android.gallery3d.filtershow.history.HistoryItem;
+import com.android.gallery3d.filtershow.history.HistoryManager;
import com.android.gallery3d.filtershow.pipeline.Buffer;
+import com.android.gallery3d.filtershow.pipeline.FilteringPipeline;
+import com.android.gallery3d.filtershow.pipeline.ImagePreset;
+import com.android.gallery3d.filtershow.pipeline.RenderingRequest;
+import com.android.gallery3d.filtershow.pipeline.RenderingRequestCaller;
import com.android.gallery3d.filtershow.pipeline.SharedBuffer;
import com.android.gallery3d.filtershow.pipeline.SharedPreset;
-import com.android.gallery3d.filtershow.pipeline.ImagePreset;
import com.android.gallery3d.filtershow.state.StateAdapter;
import java.util.Vector;
public class MasterImage implements RenderingRequestCaller {
-
-
private static final String LOGTAG = "MasterImage";
private boolean DEBUG = false;
private static final boolean DISABLEZOOM = false;
+ public static final int SMALL_BITMAP_DIM = 160;
+ public static final int MAX_BITMAP_DIM = 900;
private static MasterImage sMasterImage = null;
private boolean mSupportsHighRes = false;
@@ -188,13 +188,16 @@ public class MasterImage implements RenderingRequestCaller {
public boolean loadBitmap(Uri uri, int size) {
setUri(uri);
- mOrientation = ImageLoader.getOrientation(mActivity, uri);
- mOriginalBitmapLarge = ImageLoader.loadOrientedScaledBitmap(this, mActivity, uri, size,
- true, mOrientation);
+ mOrientation = ImageLoader.getMetadataOrientation(mActivity, uri);
+ Rect originalBounds = new Rect();
+ mOriginalBitmapLarge = ImageLoader.loadOrientedConstrainedBitmap(uri, mActivity,
+ Math.min(MAX_BITMAP_DIM, size),
+ mOrientation, originalBounds);
+ setOriginalBounds(originalBounds);
if (mOriginalBitmapLarge == null) {
return false;
}
- int sw = ImageLoader.SMALL_BITMAP_DIM;
+ int sw = SMALL_BITMAP_DIM;
int sh = (int) (sw * (float) mOriginalBitmapLarge.getHeight() / (float) mOriginalBitmapLarge
.getWidth());
mOriginalBitmapSmall = Bitmap.createScaledBitmap(mOriginalBitmapLarge, sw, sh, true);
@@ -547,7 +550,6 @@ public class MasterImage implements RenderingRequestCaller {
}
}
-
public float getScaleFactor() {
return mScaleFactor;
}
diff --git a/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java b/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java
index 6c1b2d0..dcf0ae1 100644
--- a/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java
+++ b/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java
@@ -28,13 +28,16 @@ import android.os.Environment;
import android.provider.MediaStore;
import android.provider.MediaStore.Images;
import android.provider.MediaStore.Images.ImageColumns;
+import android.provider.MediaStore.Images.Media;
import android.util.Log;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.exif.ExifInterface;
+import com.android.gallery3d.filtershow.FilterShowActivity;
import com.android.gallery3d.filtershow.pipeline.CachingPipeline;
import com.android.gallery3d.filtershow.cache.ImageLoader;
import com.android.gallery3d.filtershow.filters.FiltersManager;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
import com.android.gallery3d.filtershow.pipeline.ImagePreset;
import com.android.gallery3d.util.UsageStatistics;
import com.android.gallery3d.util.XmpUtilHelper;
@@ -59,12 +62,10 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
* Callback for the completed asynchronous task.
*/
public interface Callback {
-
void onComplete(Uri result);
}
public interface ContentResolverQueryCallback {
-
void onCursorResult(Cursor cursor);
}
@@ -84,6 +85,7 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
private final Callback mCallback;
private final File mDestinationFile;
private final Uri mSelectedImageUri;
+ public static final String DEFAULT_SAVE_DIRECTORY = "EditedOnlinePhotos";
// In order to support the new edit-save behavior such that user won't see
// the edited image together with the original image, we are adding a new
@@ -142,10 +144,10 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
}
public static File getFinalSaveDirectory(Context context, Uri sourceUri) {
- File saveDirectory = getSaveDirectory(context, sourceUri);
+ File saveDirectory = SaveCopyTask.getSaveDirectory(context, sourceUri);
if ((saveDirectory == null) || !saveDirectory.canWrite()) {
saveDirectory = new File(Environment.getExternalStorageDirectory(),
- ImageLoader.DEFAULT_SAVE_DIRECTORY);
+ SaveCopyTask.DEFAULT_SAVE_DIRECTORY);
}
// Create the directory if it doesn't exist
if (!saveDirectory.exists())
@@ -241,6 +243,9 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
public ExifInterface getExifData(Uri source) {
ExifInterface exif = new ExifInterface();
String mimeType = mContext.getContentResolver().getType(mSelectedImageUri);
+ if (mimeType == null) {
+ mimeType = ImageLoader.getMimeType(mSelectedImageUri);
+ }
if (mimeType.equals(ImageLoader.JPEG_MIME_TYPE)) {
InputStream inStream = null;
try {
@@ -293,16 +298,16 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
// create a local copy as usual.
if (srcFile != null) {
srcFile.renameTo(mDestinationFile);
- uri = insertContent(mContext, mSelectedImageUri, mDestinationFile,
+ uri = SaveCopyTask.insertContent(mContext, mSelectedImageUri, mDestinationFile,
System.currentTimeMillis());
removeSelectedImage();
return uri;
}
}
- BitmapFactory.Options options = new BitmapFactory.Options();
boolean noBitmap = true;
int num_tries = 0;
+ int sampleSize = 1;
// If necessary, move the source file into the auxiliary directory,
// newSourceUri is then pointing to the new location.
@@ -317,11 +322,13 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
while (noBitmap) {
try {
// Try to do bitmap operations, downsample if low-memory
- Bitmap bitmap = ImageLoader.loadMutableBitmap(mContext, newSourceUri, options);
+ Bitmap bitmap = ImageLoader.loadOrientedBitmapWithBackouts(mContext, newSourceUri,
+ sampleSize);
if (bitmap == null) {
return null;
}
- CachingPipeline pipeline = new CachingPipeline(FiltersManager.getManager(), "Saving");
+ CachingPipeline pipeline = new CachingPipeline(FiltersManager.getManager(),
+ "Saving");
bitmap = pipeline.renderFinalImage(bitmap, preset);
Object xmp = getPanoramaXMPData(mSelectedImageUri, preset);
@@ -340,7 +347,7 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
// If we succeed in writing the bitmap as a jpeg, return a uri.
if (putExifData(mDestinationFile, exif, bitmap)) {
putPanoramaXMPData(mDestinationFile, xmp);
- uri = insertContent(mContext, mSelectedImageUri, mDestinationFile,
+ uri = SaveCopyTask.insertContent(mContext, mSelectedImageUri, mDestinationFile,
time);
}
@@ -361,7 +368,7 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
throw e;
}
System.gc();
- options.inSampleSize *= 2;
+ sampleSize *= 2;
}
}
return uri;
@@ -434,7 +441,31 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
}
}
- private static void querySource(Context context, Uri sourceUri, String[] projection,
+ public static Uri makeAndInsertUri(Context context, Uri sourceUri) {
+ long time = System.currentTimeMillis();
+ String filename = new SimpleDateFormat(TIME_STAMP_NAME).format(new Date(time));
+ File saveDirectory = getFinalSaveDirectory(context, sourceUri);
+ File file = new File(saveDirectory, filename + ".JPG");
+ return insertContent(context, sourceUri, file, time);
+ }
+
+ public static void saveImage(ImagePreset preset, final FilterShowActivity filterShowActivity,
+ File destination) {
+ Uri selectedImageUri = filterShowActivity.getSelectedImageUri();
+ new SaveCopyTask(filterShowActivity, MasterImage.getImage().getUri(), selectedImageUri,
+ destination,
+ new SaveCopyTask.Callback() {
+
+ @Override
+ public void onComplete(Uri result) {
+ filterShowActivity.completeSaveImage(result);
+ }
+
+ }).execute(preset);
+ }
+
+
+ public static void querySource(Context context, Uri sourceUri, String[] projection,
ContentResolverQueryCallback callback) {
ContentResolver contentResolver = context.getContentResolver();
querySourceFromContentResolver(contentResolver, sourceUri, projection, callback);
@@ -537,8 +568,7 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
/**
* Insert the content (saved file) with proper source photo properties.
*/
- private static Uri insertContent(Context context, Uri sourceUri, File file,
- long time) {
+ public static Uri insertContent(Context context, Uri sourceUri, File file, long time) {
time /= 1000;
final ContentValues values = new ContentValues();
@@ -546,7 +576,7 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
values.put(Images.Media.DISPLAY_NAME, file.getName());
values.put(Images.Media.MIME_TYPE, "image/jpeg");
values.put(Images.Media.DATE_TAKEN, time);
- values.put(Images.Media.DATE_MODIFIED, System.currentTimeMillis());
+ values.put(Images.Media.DATE_MODIFIED, time);
values.put(Images.Media.DATE_ADDED, time);
values.put(Images.Media.ORIENTATION, 0);
values.put(Images.Media.DATA, file.getAbsolutePath());
@@ -556,8 +586,8 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
ImageColumns.DATE_TAKEN,
ImageColumns.LATITUDE, ImageColumns.LONGITUDE,
};
- querySource(context, sourceUri, projection,
- new ContentResolverQueryCallback() {
+ SaveCopyTask.querySource(context, sourceUri, projection,
+ new SaveCopyTask.ContentResolverQueryCallback() {
@Override
public void onCursorResult(Cursor cursor) {