OSDN Git Service

Combine ImageLoader and CropLoader utility classes.
authorRuben Brunk <rubenbrunk@google.com>
Sat, 13 Jul 2013 00:23:39 +0000 (17:23 -0700)
committerRuben Brunk <rubenbrunk@google.com>
Mon, 15 Jul 2013 17:02:43 +0000 (10:02 -0700)
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

src/com/android/gallery3d/filtershow/FilterShowActivity.java
src/com/android/gallery3d/filtershow/cache/ImageLoader.java
src/com/android/gallery3d/filtershow/crop/CropActivity.java
src/com/android/gallery3d/filtershow/crop/CropLoader.java [deleted file]
src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java
src/com/android/gallery3d/filtershow/imageshow/ImageShow.java
src/com/android/gallery3d/filtershow/imageshow/MasterImage.java
src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java

index c29709b..bee764b 100644 (file)
@@ -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) {
index ca8c7a3..7c594c6 100644 (file)
@@ -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;
                 }
index d349d5d..d14c090 100644 (file)
@@ -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 (file)
index 53a9ebc..0000000
+++ /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);
-    }
-}
index 0458a22..7df5ffb 100644 (file)
@@ -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;
index 8b9d57c..2da358c 100644 (file)
@@ -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);
     }
 
 
index 7b11cf3..08d8c45 100644 (file)
@@ -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;
     }
index 6c1b2d0..dcf0ae1 100644 (file)
@@ -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) {