OSDN Git Service

am c3178ca4: Gallery crop uses new activity. Fixed image quality.
authorRuben Brunk <rubenbrunk@google.com>
Thu, 18 Apr 2013 18:40:49 +0000 (11:40 -0700)
committerThe Android Automerger <android-build@android.com>
Fri, 19 Apr 2013 17:39:00 +0000 (10:39 -0700)
* commit 'c3178ca4daf7ed570f3432f3433959c208ecc105':
  Gallery crop uses new activity.  Fixed image quality.

res/drawable-nodpi/geometry_shadow.9.png [new file with mode: 0644]
res/values/crop_colors.xml [new file with mode: 0644]
res/values/crop_dimens.xml [new file with mode: 0644]
src/com/android/gallery3d/app/PhotoPage.java
src/com/android/gallery3d/filtershow/crop/CropActivity.java
src/com/android/gallery3d/filtershow/crop/CropDrawingUtils.java
src/com/android/gallery3d/filtershow/crop/CropLoader.java
src/com/android/gallery3d/filtershow/crop/CropMath.java
src/com/android/gallery3d/filtershow/crop/CropView.java

diff --git a/res/drawable-nodpi/geometry_shadow.9.png b/res/drawable-nodpi/geometry_shadow.9.png
new file mode 100644 (file)
index 0000000..2f7abdc
Binary files /dev/null and b/res/drawable-nodpi/geometry_shadow.9.png differ
diff --git a/res/values/crop_colors.xml b/res/values/crop_colors.xml
new file mode 100644 (file)
index 0000000..37582ef
--- /dev/null
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<resources>
+    <color name="crop_shadow_color">#CF000000</color>
+</resources>
diff --git a/res/values/crop_dimens.xml b/res/values/crop_dimens.xml
new file mode 100644 (file)
index 0000000..a79c2c9
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<resources>
+    <dimen name="preview_margin">2dp</dimen>
+    <dimen name="shadow_margin">5dp</dimen>
+    <dimen name="crop_min_side">45dp</dimen>
+    <dimen name="crop_touch_tolerance">20dp</dimen>
+</resources>
index 9b6f2b9..364cb79 100644 (file)
@@ -59,6 +59,7 @@ import com.android.gallery3d.data.SnailAlbum;
 import com.android.gallery3d.data.SnailItem;
 import com.android.gallery3d.data.SnailSource;
 import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.crop.CropActivity;
 import com.android.gallery3d.picasasource.PicasaSource;
 import com.android.gallery3d.ui.DetailsHelper;
 import com.android.gallery3d.ui.DetailsHelper.CloseListener;
@@ -1081,8 +1082,8 @@ public abstract class PhotoPage extends ActivityState implements
             }
             case R.id.action_crop: {
                 Activity activity = mActivity;
-                Intent intent = new Intent(FilterShowActivity.CROP_ACTION);
-                intent.setClass(activity, FilterShowActivity.class);
+                Intent intent = new Intent(CropActivity.CROP_ACTION);
+                intent.setClass(activity, CropActivity.class);
                 intent.setDataAndType(manager.getContentUri(path), current.getMimeType())
                     .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                 activity.startActivityForResult(intent, PicasaSource.isPicasaImage(current)
index 26659a6..878b82f 100644 (file)
@@ -21,11 +21,10 @@ import android.app.Activity;
 import android.app.WallpaperManager;
 import android.content.Context;
 import android.content.Intent;
-import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.CompressFormat;
 import android.graphics.BitmapFactory;
-import android.graphics.Point;
+import android.graphics.BitmapRegionDecoder;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.net.Uri;
@@ -34,8 +33,6 @@ import android.os.Bundle;
 import android.provider.MediaStore;
 import android.util.DisplayMetrics;
 import android.util.Log;
-import android.util.TypedValue;
-import android.view.Display;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.WindowManager;
@@ -43,8 +40,11 @@ import android.widget.Toast;
 
 import com.android.gallery3d.R;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.OutputStream;
 
 /**
@@ -52,19 +52,30 @@ import java.io.OutputStream;
  */
 public class CropActivity extends Activity {
     private static final String LOGTAG = "CropActivity";
+    public static final String CROP_ACTION = "com.android.camera.action.CROP";
     private CropExtras mCropExtras = null;
     private LoadBitmapTask mLoadBitmapTask = null;
-    private SaveBitmapTask mSaveBitmapTask = null;
-    private SetWallpaperTask mSetWallpaperTask = null;
+
     private Bitmap mOriginalBitmap = null;
+    private RectF mOriginalBounds = null;
+    private Uri mSourceUri = null;
     private CropView mCropView = null;
-    private int mActiveBackgroundIO = 0;
+    private View mSaveButton = null;
+    private boolean finalIOGuard = false;
+
     private Intent mResultIntent = null;
     private static final int SELECT_PICTURE = 1; // request code for picker
-    private static final int DEFAULT_DENSITY = 133;
+
     private static final int DEFAULT_COMPRESS_QUALITY = 90;
     public static final int MAX_BMAP_IN_INTENT = 990000;
 
+    // Flags
+    private static final int DO_SET_WALLPAPER = 1;
+    private static final int DO_RETURN_DATA = 1 << 1;
+    private static final int DO_EXTRA_OUTPUT = 1 << 2;
+
+    private static final int FLAG_CHECK = DO_SET_WALLPAPER | DO_RETURN_DATA | DO_EXTRA_OUTPUT;
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -79,22 +90,30 @@ public class CropActivity extends Activity {
         setContentView(R.layout.crop_activity);
         mCropView = (CropView) findViewById(R.id.cropView);
 
-        if (intent.getData() != null) {
-            startLoadBitmap(intent.getData());
-        } else {
-            pickImage();
-        }
         ActionBar actionBar = getActionBar();
         actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
         actionBar.setCustomView(R.layout.filtershow_actionbar);
 
-        View saveButton = actionBar.getCustomView();
-        saveButton.setOnClickListener(new OnClickListener() {
+        View mSaveButton = actionBar.getCustomView();
+        mSaveButton.setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(View view) {
                 startFinishOutput();
             }
         });
+
+        if (intent.getData() != null) {
+            mSourceUri = intent.getData();
+            startLoadBitmap(mSourceUri);
+        } else {
+            pickImage();
+        }
+    }
+
+    private void enableSave(boolean enable) {
+        if (mSaveButton != null) {
+            mSaveButton.setEnabled(enable);
+        }
     }
 
     @Override
@@ -109,7 +128,7 @@ public class CropActivity extends Activity {
      * Opens a selector in Gallery to chose an image for use when none was given
      * in the CROP intent.
      */
-    public void pickImage() {
+    private void pickImage() {
         Intent intent = new Intent();
         intent.setType("image/*");
         intent.setAction(Intent.ACTION_GET_CONTENT);
@@ -123,53 +142,25 @@ public class CropActivity extends Activity {
     @Override
     public void onActivityResult(int requestCode, int resultCode, Intent data) {
         if (resultCode == RESULT_OK && requestCode == SELECT_PICTURE) {
-            Uri selectedImageUri = data.getData();
-            startLoadBitmap(selectedImageUri);
+            mSourceUri = data.getData();
+            startLoadBitmap(mSourceUri);
         }
     }
 
     /**
-     * Gets the crop extras from the intent, or null if none exist.
-     */
-    public static CropExtras getExtrasFromIntent(Intent intent) {
-        Bundle extras = intent.getExtras();
-        if (extras != null) {
-            return new CropExtras(extras.getInt(CropExtras.KEY_OUTPUT_X, 0),
-                    extras.getInt(CropExtras.KEY_OUTPUT_Y, 0),
-                    extras.getBoolean(CropExtras.KEY_SCALE, true) &&
-                            extras.getBoolean(CropExtras.KEY_SCALE_UP_IF_NEEDED, false),
-                    extras.getInt(CropExtras.KEY_ASPECT_X, 0),
-                    extras.getInt(CropExtras.KEY_ASPECT_Y, 0),
-                    extras.getBoolean(CropExtras.KEY_SET_AS_WALLPAPER, false),
-                    extras.getBoolean(CropExtras.KEY_RETURN_DATA, false),
-                    (Uri) extras.getParcelable(MediaStore.EXTRA_OUTPUT),
-                    extras.getString(CropExtras.KEY_OUTPUT_FORMAT),
-                    extras.getBoolean(CropExtras.KEY_SHOW_WHEN_LOCKED, false),
-                    extras.getFloat(CropExtras.KEY_SPOTLIGHT_X),
-                    extras.getFloat(CropExtras.KEY_SPOTLIGHT_Y));
-        }
-        return null;
-    }
-
-    /**
      * Gets screen size metric.
      */
     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);
-        // TODO: WTF
-        return (DEFAULT_DENSITY * msize) / metrics.densityDpi + 512;
+        DisplayMetrics outMetrics = new DisplayMetrics();
+        getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
+        return (int) Math.max(outMetrics.heightPixels, outMetrics.widthPixels);
     }
 
     /**
      * Method that loads a bitmap in an async task.
      */
     private void startLoadBitmap(Uri uri) {
-        mActiveBackgroundIO++;
+        enableSave(false);
         final View loading = findViewById(R.id.loading);
         loading.setVisibility(View.VISIBLE);
         mLoadBitmapTask = new LoadBitmapTask();
@@ -179,18 +170,19 @@ public class CropActivity extends Activity {
     /**
      * Method called on UI thread with loaded bitmap.
      */
-    private void doneLoadBitmap(Bitmap bitmap) {
-        mActiveBackgroundIO--;
+    private void doneLoadBitmap(Bitmap bitmap, RectF bounds) {
         final View loading = findViewById(R.id.loading);
         loading.setVisibility(View.GONE);
         mOriginalBitmap = bitmap;
-        // TODO: move these to dimens folder
-        if (bitmap != null) {
-            mCropView.setup(bitmap, (int) getPixelsFromDip(55), (int) getPixelsFromDip(25));
+        mOriginalBounds = bounds;
+        if (bitmap != null && bitmap.getWidth() != 0 && bitmap.getHeight() != 0) {
+            RectF imgBounds = new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight());
+            mCropView.initialize(bitmap, imgBounds, imgBounds, 0);
+            enableSave(true);
         } else {
             Log.w(LOGTAG, "could not load image for cropping");
             cannotLoadImage();
-            setResult(RESULT_CANCELED, mResultIntent);
+            setResult(RESULT_CANCELED, new Intent());
             done();
         }
     }
@@ -217,7 +209,6 @@ public class CropActivity extends Activity {
 
         public LoadBitmapTask() {
             mBitmapSize = getScreenImageSize();
-            Log.v(LOGTAG, "bitmap size: " + mBitmapSize);
             mContext = getApplicationContext();
             mOriginalBounds = new Rect();
         }
@@ -231,155 +222,279 @@ public class CropActivity extends Activity {
 
         @Override
         protected void onPostExecute(Bitmap result) {
-            doneLoadBitmap(result);
-            // super.onPostExecute(result);
+            doneLoadBitmap(result, new RectF(mOriginalBounds));
         }
     }
 
-    private void startSaveBitmap(Bitmap bmap, Uri uri, String format) {
-        if (bmap == null || uri == null) {
-            throw new IllegalArgumentException("bad argument to startSaveBitmap");
+    private void startFinishOutput() {
+        if (finalIOGuard) {
+            return;
+        } else {
+            finalIOGuard = true;
         }
-        mActiveBackgroundIO++;
+        enableSave(false);
+        Uri destinationUri = null;
+        int flags = 0;
+        if (mOriginalBitmap != null && mCropExtras != null) {
+            if (mCropExtras.getExtraOutput() != null) {
+                destinationUri = mCropExtras.getExtraOutput();
+                flags |= DO_EXTRA_OUTPUT;
+            }
+            if (mCropExtras.getSetAsWallpaper()) {
+                flags |= DO_SET_WALLPAPER;
+            }
+            if (mCropExtras.getReturnData()) {
+                flags |= DO_RETURN_DATA;
+            }
+        }
+        if (flags == 0) {
+            destinationUri = CropLoader.makeAndInsertUri(this, mSourceUri);
+            if (destinationUri != null) {
+                flags |= DO_EXTRA_OUTPUT;
+            }
+        }
+        if ((flags & FLAG_CHECK) != 0) {
+            RectF photo = new RectF(0, 0, mOriginalBitmap.getWidth(), mOriginalBitmap.getHeight());
+            RectF crop = getBitmapCrop(photo);
+            startBitmapIO(flags, mOriginalBitmap, mSourceUri, destinationUri, crop,
+                    photo, mOriginalBounds,
+                    (mCropExtras == null) ? null : mCropExtras.getOutputFormat());
+            return;
+        }
+        setResult(RESULT_CANCELED, mResultIntent);
+        done();
+        return;
+    }
+
+    private void startBitmapIO(int flags, Bitmap currentBitmap, Uri sourceUri, Uri destUri,
+            RectF cropBounds, RectF photoBounds, RectF currentBitmapBounds, String format) {
+        if (cropBounds == null || photoBounds == null || currentBitmap == null
+                || currentBitmap.getWidth() == 0 || currentBitmap.getHeight() == 0
+                || cropBounds.width() == 0 || cropBounds.height() == 0 || photoBounds.width() == 0
+                || photoBounds.height() == 0) {
+            return; // fail fast
+        }
+        if ((flags & FLAG_CHECK) == 0) {
+            return; // no output options
+        }
+        if ((flags & DO_SET_WALLPAPER) != 0) {
+            Toast.makeText(this, R.string.setting_wallpaper, Toast.LENGTH_LONG).show();
+        }
+
         final View loading = findViewById(R.id.loading);
         loading.setVisibility(View.VISIBLE);
-        mSaveBitmapTask = new SaveBitmapTask(uri, format);
-        mSaveBitmapTask.execute(bmap);
+        BitmapIOTask ioTask = new BitmapIOTask(sourceUri, destUri, format, flags, cropBounds,
+                photoBounds, currentBitmapBounds);
+        ioTask.execute(currentBitmap);
     }
 
-    private void doneSaveBitmap(Uri uri) {
-        mActiveBackgroundIO--;
+    private void doneBitmapIO(boolean success, Intent intent) {
         final View loading = findViewById(R.id.loading);
         loading.setVisibility(View.GONE);
-        if (uri == null) {
-            Log.w(LOGTAG, "failed to save bitmap");
-            setResult(RESULT_CANCELED, mResultIntent);
-            done();
-            return;
+        if (success) {
+            setResult(RESULT_OK, intent);
+        } else {
+            setResult(RESULT_CANCELED, intent);
         }
         done();
     }
 
-    private class SaveBitmapTask extends AsyncTask<Bitmap, Void, Boolean> {
+    private class BitmapIOTask extends AsyncTask<Bitmap, Void, Boolean> {
 
+        private final WallpaperManager mWPManager;
+        InputStream mInStream = null;
         OutputStream mOutStream = null;
         String mOutputFormat = null;
         Uri mOutUri = null;
-
-        public SaveBitmapTask(Uri uri, String outputFormat) {
+        Uri mInUri = null;
+        int mFlags = 0;
+        RectF mCrop = null;
+        RectF mPhoto = null;
+        RectF mOrig = null;
+        Intent mResultIntent = null;
+
+        public BitmapIOTask(Uri sourceUri, Uri destUri, String outputFormat, int flags,
+                RectF cropBounds, RectF photoBounds, RectF originalBitmapBounds) {
             mOutputFormat = outputFormat;
             mOutStream = null;
-            mOutUri = uri;
-            try {
-                mOutStream = getContentResolver().openOutputStream(uri);
-            } catch (FileNotFoundException e) {
-                Log.w(LOGTAG, "cannot write output: " + mOutUri.toString(), e);
-            }
-        }
+            mOutUri = destUri;
+            mInUri = sourceUri;
+            mFlags = flags;
+            mCrop = cropBounds;
+            mPhoto = photoBounds;
+            mOrig = originalBitmapBounds;
+            mWPManager = WallpaperManager.getInstance(getApplicationContext());
+            mResultIntent = new Intent();
 
-        @Override
-        protected Boolean doInBackground(Bitmap... params) {
-            if (mOutStream == null) {
-                return false;
+            if ((flags & DO_EXTRA_OUTPUT) != 0) {
+                if (mOutUri == null) {
+                    Log.w(LOGTAG, "cannot write file, no output URI given");
+                } else {
+                    try {
+                        mOutStream = getContentResolver().openOutputStream(mOutUri);
+                    } catch (FileNotFoundException e) {
+                        Log.w(LOGTAG, "cannot write file: " + mOutUri.toString(), e);
+                    }
+                }
             }
-            CompressFormat cf = convertExtensionToCompressFormat(getFileExtension(mOutputFormat));
-            return params[0].compress(cf, DEFAULT_COMPRESS_QUALITY, mOutStream);
-        }
 
-        @Override
-        protected void onPostExecute(Boolean result) {
-            if (result.booleanValue() == false) {
-                Log.w(LOGTAG, "could not compress to output: " + mOutUri.toString());
-                doneSaveBitmap(null);
+            if ((flags & (DO_EXTRA_OUTPUT | DO_SET_WALLPAPER)) != 0) {
+                if (mInUri == null) {
+                    Log.w(LOGTAG, "cannot read original file, no input URI given");
+                } else {
+                    try {
+                        mInStream = getContentResolver().openInputStream(mInUri);
+                    } catch (FileNotFoundException e) {
+                        Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e);
+                    }
+                }
             }
-            doneSaveBitmap(mOutUri);
-        }
-    }
-
-    private void startSetWallpaper(Bitmap bmap) {
-        if (bmap == null) {
-            throw new IllegalArgumentException("bad argument to startSetWallpaper");
-        }
-        mActiveBackgroundIO++;
-        Toast.makeText(this, R.string.setting_wallpaper, Toast.LENGTH_LONG).show();
-        mSetWallpaperTask = new SetWallpaperTask();
-        mSetWallpaperTask.execute(bmap);
-
-    }
-
-    private void doneSetWallpaper() {
-        mActiveBackgroundIO--;
-        done();
-    }
-
-    private class SetWallpaperTask extends AsyncTask<Bitmap, Void, Boolean> {
-        private final WallpaperManager mWPManager;
-
-        public SetWallpaperTask() {
-            mWPManager = WallpaperManager.getInstance(getApplicationContext());
         }
 
         @Override
         protected Boolean doInBackground(Bitmap... params) {
-            try {
-                mWPManager.setBitmap(params[0]);
-            } catch (IOException e) {
-                Log.w(LOGTAG, "fail to set wall paper", e);
+            boolean failure = false;
+            Bitmap img = params[0];
+
+            // Set extra for crop bounds
+            if (mCrop != null && mPhoto != null && mOrig != null) {
+                RectF trueCrop = CropMath.getScaledCropBounds(mCrop, mPhoto, mOrig);
+                if (trueCrop != null) {
+                    Rect rounded = new Rect();
+                    trueCrop.roundOut(rounded);
+                    mResultIntent.putExtra(CropExtras.KEY_CROPPED_RECT, rounded);
+                }
             }
-            return true;
-        }
 
-        @Override
-        protected void onPostExecute(Boolean result) {
-            doneSetWallpaper();
-        }
-    }
-
-    private void startFinishOutput() {
-        if (mOriginalBitmap != null && mCropExtras != null) {
-            Bitmap cropped = null;
-            if (mCropExtras.getExtraOutput() != null) {
-                if (cropped == null) {
-                    cropped = getCroppedImage(mOriginalBitmap);
+            // Find the small cropped bitmap that is returned in the intent
+            if ((mFlags & DO_RETURN_DATA) != 0) {
+                assert (img != null);
+                Bitmap ret = getCroppedImage(img, mCrop, mPhoto);
+                if (ret != null) {
+                    ret = getDownsampledBitmap(ret, MAX_BMAP_IN_INTENT);
                 }
-                startSaveBitmap(cropped, mCropExtras.getExtraOutput(),
-                        mCropExtras.getOutputFormat());
-            }
-            if (mCropExtras.getSetAsWallpaper()) {
-                if (cropped == null) {
-                    cropped = getCroppedImage(mOriginalBitmap);
+                if (ret == null) {
+                    Log.w(LOGTAG, "could not downsample bitmap to return in data");
+                    failure = true;
+                } else {
+                    mResultIntent.putExtra(CropExtras.KEY_DATA, ret);
                 }
-                startSetWallpaper(cropped);
             }
-            if (mCropExtras.getReturnData()) {
-                if (cropped == null) {
-                    cropped = getCroppedImage(mOriginalBitmap);
+
+            // Do the large cropped bitmap and/or set the wallpaper
+            if ((mFlags & (DO_EXTRA_OUTPUT | DO_SET_WALLPAPER)) != 0 && mInStream != null) {
+                BitmapRegionDecoder decoder = null;
+                try {
+                    decoder = BitmapRegionDecoder.newInstance(mInStream, true);
+                } catch (IOException e) {
+                    Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e);
+                }
+                if (decoder == null) {
+                    failure = true;
+                    return false;
+                }
+
+                // Find crop bounds (scaled to original image size)
+                RectF trueCrop = CropMath.getScaledCropBounds(mCrop, mPhoto, mOrig);
+                if (trueCrop == null) {
+                    Log.w(LOGTAG, "cannot find crop for full size image");
+                    failure = true;
+                    return false;
+                }
+                Rect roundedTrueCrop = new Rect();
+                trueCrop.roundOut(roundedTrueCrop);
+
+                if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
+                    Log.w(LOGTAG, "crop has bad values for full size image");
+                    failure = true;
+                    return false;
+                }
+                // Do region decoding to get crop bitmap
+                BitmapFactory.Options options = new BitmapFactory.Options();
+                options.inMutable = true;
+                Bitmap crop = decoder.decodeRegion(roundedTrueCrop, options);
+                decoder.recycle();
+
+                if (crop == null) {
+                    Log.w(LOGTAG, "cannot region decode file: " + mInUri.toString());
+                    failure = true;
+                    return false;
                 }
-                int bmapSize = cropped.getRowBytes() * cropped.getHeight();
-                if (bmapSize > MAX_BMAP_IN_INTENT) {
-                    Log.w(LOGTAG, "Bitmap too large to be returned via intent");
+                // Get output compression format
+                CompressFormat cf =
+                        convertExtensionToCompressFormat(getFileExtension(mOutputFormat));
+
+                // If we only need to output to a URI, compress straight to file
+                if (mFlags == DO_EXTRA_OUTPUT) {
+                    if (mOutStream == null
+                            || !crop.compress(cf, DEFAULT_COMPRESS_QUALITY, mOutStream)) {
+                        Log.w(LOGTAG, "failed to compress bitmap to file: " + mOutUri.toString());
+                        failure = true;
+                    }
                 } else {
-                    mResultIntent.putExtra(CropExtras.KEY_DATA, cropped);
+                    // Compress to byte array
+                    ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048);
+                    if (crop.compress(cf, DEFAULT_COMPRESS_QUALITY, tmpOut)) {
+
+                        // If we need to output to a Uri, write compressed
+                        // bitmap out
+                        if ((mFlags & DO_EXTRA_OUTPUT) != 0) {
+                            if (mOutStream == null) {
+                                Log.w(LOGTAG,
+                                        "failed to compress bitmap to file: " + mOutUri.toString());
+                                failure = true;
+                            } else {
+                                try {
+                                    mOutStream.write(tmpOut.toByteArray());
+                                } catch (IOException e) {
+                                    Log.w(LOGTAG,
+                                            "failed to compress bitmap to file: "
+                                                    + mOutUri.toString(), e);
+                                    failure = true;
+                                }
+                            }
+                        }
+
+                        // If we need to set to the wallpaper, set it
+                        if ((mFlags & DO_SET_WALLPAPER) != 0 && mWPManager != null) {
+                            if (mWPManager == null) {
+                                Log.w(LOGTAG, "no wallpaper manager");
+                                failure = true;
+                            } else {
+                                try {
+                                    mWPManager.setStream(new ByteArrayInputStream(tmpOut
+                                            .toByteArray()));
+                                } catch (IOException e) {
+                                    Log.w(LOGTAG, "cannot write stream to wallpaper", e);
+                                    failure = true;
+                                }
+                            }
+                        }
+                    } else {
+                        Log.w(LOGTAG, "cannot compress bitmap");
+                        failure = true;
+                    }
                 }
             }
-            setResult(RESULT_OK, mResultIntent);
-        } else {
-            setResult(RESULT_CANCELED, mResultIntent);
+            return !failure; // True if any of the operations failed
         }
-        done();
+
+        @Override
+        protected void onPostExecute(Boolean result) {
+            doneBitmapIO(result.booleanValue(), mResultIntent);
+        }
+
     }
 
     private void done() {
-        if (mActiveBackgroundIO == 0) {
-            finish();
-        }
+        finish();
     }
 
-    private Bitmap getCroppedImage(Bitmap image) {
+    protected static Bitmap getCroppedImage(Bitmap image, RectF cropBounds, RectF photoBounds) {
         RectF imageBounds = new RectF(0, 0, image.getWidth(), image.getHeight());
-        RectF crop = getBitmapCrop(imageBounds);
+        RectF crop = CropMath.getScaledCropBounds(cropBounds, photoBounds, imageBounds);
         if (crop == null) {
-            return image;
+            return null;
         }
         Rect intCrop = new Rect();
         crop.roundOut(intCrop);
@@ -387,29 +502,56 @@ public class CropActivity extends Activity {
                 intCrop.height());
     }
 
-    private RectF getBitmapCrop(RectF imageBounds) {
-        RectF crop = new RectF();
-        if (!mCropView.getCropBounds(crop, imageBounds)) {
-            Log.w(LOGTAG, "could not get crop");
+    protected static Bitmap getDownsampledBitmap(Bitmap image, int max_size) {
+        if (image == null || image.getWidth() == 0 || image.getHeight() == 0 || max_size < 16) {
+            throw new IllegalArgumentException("Bad argument to getDownsampledBitmap()");
+        }
+        int shifts = 0;
+        int size = CropMath.getBitmapSize(image);
+        while (size > max_size) {
+            shifts++;
+            size /= 4;
+        }
+        Bitmap ret = Bitmap.createScaledBitmap(image, image.getWidth() >> shifts,
+                image.getHeight() >> shifts, true);
+        if (ret == null) {
             return null;
         }
-        return crop;
+        // Handle edge case for rounding.
+        if (CropMath.getBitmapSize(ret) > max_size) {
+            return Bitmap.createScaledBitmap(ret, ret.getWidth() >> 1, ret.getHeight() >> 1, true);
+        }
+        return ret;
     }
 
     /**
-     * Helper method for unit conversions.
+     * Gets the crop extras from the intent, or null if none exist.
      */
-    public float getPixelsFromDip(float value) {
-        Resources r = getResources();
-        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value,
-                r.getDisplayMetrics());
+    protected static CropExtras getExtrasFromIntent(Intent intent) {
+        Bundle extras = intent.getExtras();
+        if (extras != null) {
+            return new CropExtras(extras.getInt(CropExtras.KEY_OUTPUT_X, 0),
+                    extras.getInt(CropExtras.KEY_OUTPUT_Y, 0),
+                    extras.getBoolean(CropExtras.KEY_SCALE, true) &&
+                            extras.getBoolean(CropExtras.KEY_SCALE_UP_IF_NEEDED, false),
+                    extras.getInt(CropExtras.KEY_ASPECT_X, 0),
+                    extras.getInt(CropExtras.KEY_ASPECT_Y, 0),
+                    extras.getBoolean(CropExtras.KEY_SET_AS_WALLPAPER, false),
+                    extras.getBoolean(CropExtras.KEY_RETURN_DATA, false),
+                    (Uri) extras.getParcelable(MediaStore.EXTRA_OUTPUT),
+                    extras.getString(CropExtras.KEY_OUTPUT_FORMAT),
+                    extras.getBoolean(CropExtras.KEY_SHOW_WHEN_LOCKED, false),
+                    extras.getFloat(CropExtras.KEY_SPOTLIGHT_X),
+                    extras.getFloat(CropExtras.KEY_SPOTLIGHT_Y));
+        }
+        return null;
     }
 
-    private static CompressFormat convertExtensionToCompressFormat(String extension) {
+    protected static CompressFormat convertExtensionToCompressFormat(String extension) {
         return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG;
     }
 
-    private static String getFileExtension(String requestFormat) {
+    protected static String getFileExtension(String requestFormat) {
         String outputFormat = (requestFormat == null)
                 ? "jpg"
                 : requestFormat;
@@ -419,4 +561,14 @@ public class CropActivity extends Activity {
                 : "jpg";
     }
 
+    private RectF getBitmapCrop(RectF imageBounds) {
+        RectF crop = mCropView.getCrop();
+        RectF photo = mCropView.getPhoto();
+        if (crop == null || photo == null) {
+            Log.w(LOGTAG, "could not get crop");
+            return null;
+        }
+        RectF scaledCrop = CropMath.getScaledCropBounds(crop, photo, imageBounds);
+        return scaledCrop;
+    }
 }
index 483cb63..a3664d7 100644 (file)
@@ -113,4 +113,17 @@ public abstract class CropDrawingUtils {
         return m.setRectToRect(imageBounds, displayBounds, Matrix.ScaleToFit.CENTER);
     }
 
+    public static boolean setImageToScreenMatrix(Matrix dst, RectF image,
+            RectF screen, int rotation) {
+        RectF rotatedImage = new RectF();
+        dst.setRotate(rotation, image.centerX(), image.centerY());
+        if (!dst.mapRect(rotatedImage, image)) {
+            return false; // fails for rotations that are not multiples of 90
+                          // degrees
+        }
+        boolean rToR = dst.setRectToRect(rotatedImage, screen, Matrix.ScaleToFit.CENTER);
+        boolean rot = dst.preRotate(rotation, image.centerX(), image.centerY());
+        return rToR && rot;
+    }
+
 }
index 132d6c1..2eb1a14 100644 (file)
@@ -17,6 +17,7 @@
 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;
@@ -24,19 +25,25 @@ 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 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
- * mantains no instance state.
+ * maintains no instance state.
  */
 public abstract class CropLoader {
     public static final String LOGTAG = "CropLoader";
@@ -46,6 +53,8 @@ public abstract class CropLoader {
     public static final int ORI_ROTATE_180 = ExifInterface.Orientation.BOTTOM_LEFT;
     public static final int ORI_ROTATE_270 = ExifInterface.Orientation.RIGHT_BOTTOM;
 
+    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.
@@ -199,4 +208,112 @@ public abstract class CropLoader {
         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];
+    }
+
+    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 52b11a5..ed800c9 100644 (file)
@@ -16,6 +16,7 @@
 
 package com.android.gallery3d.filtershow.crop;
 
+import android.graphics.Bitmap;
 import android.graphics.Matrix;
 import android.graphics.RectF;
 
@@ -205,6 +206,35 @@ public class CropMath {
         r.set(centX - hw, centY - hh, centX + hw, centY + hh);
     }
 
+    /**
+     * Stretches/Scales/Translates photoBounds to match displayBounds, and
+     * and returns an equivalent stretched/scaled/translated cropBounds or null
+     * if the mapping is invalid.
+     * @param cropBounds  cropBounds to transform
+     * @param photoBounds  original bounds containing crop bounds
+     * @param displayBounds  final bounds for crop
+     * @return  the stretched/scaled/translated crop bounds that fit within displayBounds
+     */
+    public static RectF getScaledCropBounds(RectF cropBounds, RectF photoBounds,
+            RectF displayBounds) {
+        Matrix m = new Matrix();
+        m.setRectToRect(photoBounds, displayBounds, Matrix.ScaleToFit.FILL);
+        RectF trueCrop = new RectF(cropBounds);
+        if (!m.mapRect(trueCrop)) {
+            return null;
+        }
+        return trueCrop;
+    }
+
+    /**
+     * Returns the size of a bitmap in bytes.
+     * @param bmap  bitmap whose size to check
+     * @return  bitmap size in bytes
+     */
+    public static int getBitmapSize(Bitmap bmap) {
+        return bmap.getRowBytes() * bmap.getHeight();
+    }
+
     private static float getUnrotated(float[] rotatedRect, float[] center, RectF unrotated) {
         float dy = rotatedRect[1] - rotatedRect[3];
         float dx = rotatedRect[0] - rotatedRect[2];
index d762ad4..56579f4 100644 (file)
@@ -25,30 +25,46 @@ import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.NinePatchDrawable;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 
 import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.crop.CropDrawingUtils;
+
 
 public class CropView extends View {
     private static final String LOGTAG = "CropView";
 
-    Bitmap mImage = null;
-    CropObject mCropObj = null;
+    private RectF mImageBounds = new RectF();
+    private RectF mScreenBounds = new RectF();
+    private RectF mScreenImageBounds = new RectF();
+    private RectF mScreenCropBounds = new RectF();
+    private Rect mShadowBounds = new Rect();
+
+    private Bitmap mBitmap;
+    private Paint mPaint = new Paint();
+
+    private NinePatchDrawable mShadow;
+    private CropObject mCropObj = null;
     private final Drawable mCropIndicator;
     private final int mIndicatorSize;
+    private int mRotation = 0;
+    private boolean mMovingBlock = false;
+    private Matrix mDisplayMatrix = null;
+    private Matrix mDisplayMatrixInverse = null;
+    private boolean mDirty = false;
 
     private float mPrevX = 0;
     private float mPrevY = 0;
 
-    private int mMinSideSize = 45;
-    private int mTouchTolerance = 20;
-    private boolean mMovingBlock = false;
-
-    private Matrix mDisplayMatrix = null;
-    private Matrix mDisplayMatrixInverse = null;
+    private int mShadowMargin = 15;
+    private int mMargin = 32;
+    private int mOverlayShadowColor = 0xCF000000;
+    private int mMinSideSize = 90;
+    private int mTouchTolerance = 40;
 
     private enum Mode {
         NONE, MOVE
@@ -58,57 +74,41 @@ public class CropView extends View {
 
     public CropView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        Resources resources = context.getResources();
-        mCropIndicator = resources.getDrawable(R.drawable.camera_crop);
-        mIndicatorSize = (int) resources.getDimension(R.dimen.crop_indicator_size);
+        Resources rsc = context.getResources();
+        mShadow = (NinePatchDrawable) rsc.getDrawable(R.drawable.geometry_shadow);
+        mCropIndicator = rsc.getDrawable(R.drawable.camera_crop);
+        mIndicatorSize = (int) rsc.getDimension(R.dimen.crop_indicator_size);
+        mShadowMargin = (int) rsc.getDimension(R.dimen.shadow_margin);
+        mMargin = (int) rsc.getDimension(R.dimen.preview_margin);
+        mMinSideSize = (int) rsc.getDimension(R.dimen.crop_min_side);
+        mTouchTolerance = (int) rsc.getDimension(R.dimen.crop_touch_tolerance);
+        mOverlayShadowColor = (int) rsc.getColor(R.color.crop_shadow_color);
     }
 
-    // For unchanging parameters
-    public void setup(Bitmap image, int minSideSize, int touchTolerance) {
-        mImage = image;
-        mMinSideSize = minSideSize;
-        mTouchTolerance = touchTolerance;
-        reset();
-    }
-
-    @Override
-    public void onDraw(Canvas canvas) {
-        if (mImage == null) {
-            return;
-        }
-        int displayWidth = getWidth();
-        int displayHeight = getHeight();
-        Rect imageBoundsOriginal = new Rect(0, 0, mImage.getWidth(), mImage.getHeight());
-        Rect displayBoundsOriginal = new Rect(0, 0, displayWidth, displayHeight);
-        if (mCropObj == null) {
-            reset();
-            mCropObj = new CropObject(imageBoundsOriginal, imageBoundsOriginal, 0);
-        }
-
-        RectF imageBounds = mCropObj.getInnerBounds();
-        RectF displayBounds = mCropObj.getOuterBounds();
-
-        // If display matrix doesn't exist, create it and its dependencies
-        if (mDisplayMatrix == null || mDisplayMatrixInverse == null) {
-            mDisplayMatrix = CropDrawingUtils.getBitmapToDisplayMatrix(displayBounds, new RectF(
-                    displayBoundsOriginal));
-            mDisplayMatrixInverse = new Matrix();
-            mDisplayMatrixInverse.reset();
-            if (!mDisplayMatrix.invert(mDisplayMatrixInverse)) {
-                Log.w(LOGTAG, "could not invert display matrix");
+    public void initialize(Bitmap image, RectF newCropBounds, RectF newPhotoBounds, int rotation) {
+        mBitmap = image;
+        if (mCropObj != null) {
+            RectF crop = mCropObj.getInnerBounds();
+            RectF containing = mCropObj.getOuterBounds();
+            if (crop != newCropBounds || containing != newPhotoBounds
+                    || mRotation != rotation) {
+                mRotation = rotation;
+                mCropObj.resetBoundsTo(newCropBounds, newPhotoBounds);
+                clearDisplay();
             }
-            // Scale min side and tolerance by display matrix scale factor
-            mCropObj.setMinInnerSideSize(mDisplayMatrixInverse.mapRadius(mMinSideSize));
-            mCropObj.setTouchTolerance(mDisplayMatrixInverse.mapRadius(mTouchTolerance));
+        } else {
+            mRotation = rotation;
+            mCropObj = new CropObject(newPhotoBounds, newCropBounds, 0);
+            clearDisplay();
         }
-        canvas.drawBitmap(mImage, mDisplayMatrix, new Paint());
+    }
 
-        if (mDisplayMatrix.mapRect(imageBounds)) {
-            CropDrawingUtils.drawCropRect(canvas, imageBounds);
-            CropDrawingUtils.drawRuleOfThird(canvas, imageBounds);
-            CropDrawingUtils.drawIndicators(canvas, mCropIndicator, mIndicatorSize, imageBounds,
-                    mCropObj.isFixedAspect(), mCropObj.getSelectState());
-        }
+    public RectF getCrop() {
+        return mCropObj.getInnerBounds();
+    }
+
+    public RectF getPhoto() {
+        return mCropObj.getOuterBounds();
     }
 
     @Override
@@ -133,8 +133,6 @@ public class CropView extends View {
                     mPrevX = x;
                     mPrevY = y;
                     mState = Mode.MOVE;
-                } else {
-                    reset();
                 }
                 break;
             case (MotionEvent.ACTION_UP):
@@ -144,8 +142,6 @@ public class CropView extends View {
                     mPrevX = x;
                     mPrevY = y;
                     mState = Mode.NONE;
-                } else {
-                    reset();
                 }
                 break;
             case (MotionEvent.ACTION_MOVE):
@@ -155,41 +151,129 @@ public class CropView extends View {
                     mCropObj.moveCurrentSelection(dx, dy);
                     mPrevX = x;
                     mPrevY = y;
-                } else {
-                    reset();
                 }
                 break;
             default:
-                reset();
                 break;
         }
         invalidate();
         return true;
     }
 
-    public void reset() {
-        Log.w(LOGTAG, "reset called");
+    private void reset() {
+        Log.w(LOGTAG, "crop reset called");
         mState = Mode.NONE;
         mCropObj = null;
+        mRotation = 0;
+        mMovingBlock = false;
+        clearDisplay();
+    }
+
+    private void clearDisplay() {
         mDisplayMatrix = null;
         mDisplayMatrixInverse = null;
-        mMovingBlock = false;
         invalidate();
     }
 
-    public boolean getCropBounds(RectF out_crop, RectF in_newContaining) {
-        Matrix m = new Matrix();
-        RectF inner = mCropObj.getInnerBounds();
+    protected void configChanged() {
+        mDirty = true;
+    }
+
+    public void applyFreeAspect() {
+        mCropObj.unsetAspectRatio();
+        invalidate();
+    }
+
+    public void applyOriginalAspect() {
         RectF outer = mCropObj.getOuterBounds();
-        if (!m.setRectToRect(outer, in_newContaining, Matrix.ScaleToFit.FILL)) {
-            Log.w(LOGTAG, "failed to make transform matrix");
-            return false;
+        if (!mCropObj.setInnerAspectRatio((int) outer.width(), (int) outer.height())) {
+            Log.w(LOGTAG, "failed to set aspect ratio original");
         }
-        if (!m.mapRect(inner)) {
-            Log.w(LOGTAG, "failed to transform crop bounds");
-            return false;
+        mCropObj.resetBoundsTo(outer, outer);
+        invalidate();
+    }
+
+    public void applySquareAspect() {
+        if (!mCropObj.setInnerAspectRatio(1, 1)) {
+            Log.w(LOGTAG, "failed to set aspect ratio square");
         }
-        out_crop.set(inner);
-        return true;
+        invalidate();
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        if (mBitmap == null) {
+            return;
+        }
+        if (mDirty) {
+            mDirty = false;
+            clearDisplay();
+        }
+
+        mImageBounds = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
+        mScreenBounds = new RectF(0, 0, canvas.getWidth(), canvas.getHeight());
+        mScreenBounds.inset(mMargin, mMargin);
+
+        // If crop object doesn't exist, create it and update it from master
+        // state
+        if (mCropObj == null) {
+            reset();
+            mCropObj = new CropObject(mImageBounds, mImageBounds, 0);
+        }
+
+        // If display matrix doesn't exist, create it and its dependencies
+        if (mDisplayMatrix == null || mDisplayMatrixInverse == null) {
+            mDisplayMatrix = new Matrix();
+            mDisplayMatrix.reset();
+            if (!CropDrawingUtils.setImageToScreenMatrix(mDisplayMatrix, mImageBounds, mScreenBounds,
+                    mRotation)) {
+                Log.w(LOGTAG, "failed to get screen matrix");
+                mDisplayMatrix = null;
+                return;
+            }
+            mDisplayMatrixInverse = new Matrix();
+            mDisplayMatrixInverse.reset();
+            if (!mDisplayMatrix.invert(mDisplayMatrixInverse)) {
+                Log.w(LOGTAG, "could not invert display matrix");
+                mDisplayMatrixInverse = null;
+                return;
+            }
+            // Scale min side and tolerance by display matrix scale factor
+            mCropObj.setMinInnerSideSize(mDisplayMatrixInverse.mapRadius(mMinSideSize));
+            mCropObj.setTouchTolerance(mDisplayMatrixInverse.mapRadius(mTouchTolerance));
+        }
+
+        mScreenImageBounds.set(mImageBounds);
+
+        // Draw background shadow
+        if (mDisplayMatrix.mapRect(mScreenImageBounds)) {
+            int margin = (int) mDisplayMatrix.mapRadius(mShadowMargin);
+            mScreenImageBounds.roundOut(mShadowBounds);
+            mShadowBounds.set(mShadowBounds.left - margin, mShadowBounds.top -
+                    margin, mShadowBounds.right + margin, mShadowBounds.bottom + margin);
+            mShadow.setBounds(mShadowBounds);
+            mShadow.draw(canvas);
+        }
+
+        // Draw actual bitmap
+        canvas.drawBitmap(mBitmap, mDisplayMatrix, mPaint);
+
+        mCropObj.getInnerBounds(mScreenCropBounds);
+
+        if (mDisplayMatrix.mapRect(mScreenCropBounds)) {
+
+            // Draw overlay shadows
+            Paint p = new Paint();
+            p.setColor(mOverlayShadowColor);
+            p.setStyle(Paint.Style.FILL);
+            CropDrawingUtils.drawShadows(canvas, p, mScreenCropBounds, mScreenImageBounds);
+
+            // Draw crop rect and markers
+            CropDrawingUtils.drawCropRect(canvas, mScreenCropBounds);
+            CropDrawingUtils.drawRuleOfThird(canvas, mScreenCropBounds);
+            CropDrawingUtils.drawIndicators(canvas, mCropIndicator, mIndicatorSize,
+                    mScreenCropBounds, mCropObj.isFixedAspect(), mCropObj.getSelectState());
+        }
+
     }
 }