Merge "Moving crop to a separate activity. Refactoring." into gb-ub-photos-bryce
diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java
index 22bd650..7c0c15a 100644
--- a/src/com/android/camera/PhotoModule.java
+++ b/src/com/android/camera/PhotoModule.java
@@ -64,8 +64,8 @@
import com.android.gallery3d.exif.ExifInterface;
import com.android.gallery3d.exif.ExifTag;
import com.android.gallery3d.exif.Rational;
-import com.android.gallery3d.filtershow.CropExtras;
import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.crop.CropExtras;
import com.android.gallery3d.util.UsageStatistics;
import java.io.File;
diff --git a/src/com/android/gallery3d/app/AlbumPage.java b/src/com/android/gallery3d/app/AlbumPage.java
index 51591cc..001ce87 100644
--- a/src/com/android/gallery3d/app/AlbumPage.java
+++ b/src/com/android/gallery3d/app/AlbumPage.java
@@ -39,8 +39,8 @@
import com.android.gallery3d.data.MediaObject;
import com.android.gallery3d.data.MediaSet;
import com.android.gallery3d.data.Path;
-import com.android.gallery3d.filtershow.CropExtras;
import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.crop.CropExtras;
import com.android.gallery3d.glrenderer.FadeTexture;
import com.android.gallery3d.glrenderer.GLCanvas;
import com.android.gallery3d.ui.ActionModeHandler;
diff --git a/src/com/android/gallery3d/app/Wallpaper.java b/src/com/android/gallery3d/app/Wallpaper.java
index 1bbe8d2..91bc772 100644
--- a/src/com/android/gallery3d/app/Wallpaper.java
+++ b/src/com/android/gallery3d/app/Wallpaper.java
@@ -27,7 +27,7 @@
import com.android.gallery3d.common.ApiHelper;
import com.android.gallery3d.filtershow.FilterShowActivity;
-import com.android.gallery3d.filtershow.CropExtras;
+import com.android.gallery3d.filtershow.crop.CropExtras;
/**
* Wallpaper picker for the gallery application. This just redirects to the
diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
index b659432..874a7c9 100644
--- a/src/com/android/gallery3d/filtershow/FilterShowActivity.java
+++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
@@ -55,6 +55,7 @@
import com.android.gallery3d.filtershow.cache.CachingPipeline;
import com.android.gallery3d.filtershow.cache.FilteringPipeline;
import com.android.gallery3d.filtershow.cache.ImageLoader;
+import com.android.gallery3d.filtershow.crop.CropExtras;
import com.android.gallery3d.filtershow.editors.BasicEditor;
import com.android.gallery3d.filtershow.editors.EditorCrop;
import com.android.gallery3d.filtershow.editors.EditorDraw;
diff --git a/src/com/android/gallery3d/filtershow/imageshow/BoundedRect.java b/src/com/android/gallery3d/filtershow/crop/BoundedRect.java
similarity index 94%
rename from src/com/android/gallery3d/filtershow/imageshow/BoundedRect.java
rename to src/com/android/gallery3d/filtershow/crop/BoundedRect.java
index e94d1ed..c2c768e 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/BoundedRect.java
+++ b/src/com/android/gallery3d/filtershow/crop/BoundedRect.java
@@ -13,11 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.gallery3d.filtershow.imageshow;
+package com.android.gallery3d.filtershow.crop;
import android.graphics.Matrix;
+import android.graphics.Rect;
import android.graphics.RectF;
+import com.android.gallery3d.filtershow.imageshow.GeometryMath;
+
import java.util.Arrays;
/**
@@ -30,11 +33,14 @@
private RectF inner;
private float[] innerRotated;
- public BoundedRect() {
- rot = 0;
- outer = new RectF();
- inner = new RectF();
- innerRotated = new float[8];
+ public BoundedRect(float rotation, Rect outerRect, Rect innerRect) {
+ rot = rotation;
+ outer = new RectF(outerRect);
+ inner = new RectF(innerRect);
+ innerRotated = CropMath.getCornersFromRect(inner);
+ rotateInner();
+ if (!isConstrained())
+ reconstrain();
}
public BoundedRect(float rotation, RectF outerRect, RectF innerRect) {
@@ -73,10 +79,22 @@
reconstrain();
}
+ public void setToInner(RectF r) {
+ r.set(inner);
+ }
+
+ public void setToOuter(RectF r) {
+ r.set(outer);
+ }
+
public RectF getInner() {
return new RectF(inner);
}
+ public RectF getOuter() {
+ return new RectF(outer);
+ }
+
/**
* Tries to move the inner rectangle by (dx, dy). If this would cause it to leave
* the bounding rectangle, snaps the inner rectangle to the edge of the bounding
diff --git a/src/com/android/gallery3d/filtershow/crop/CropActivity.java b/src/com/android/gallery3d/filtershow/crop/CropActivity.java
new file mode 100644
index 0000000..26659a6
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/crop/CropActivity.java
@@ -0,0 +1,422 @@
+/*
+ * 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.app.ActionBar;
+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.Rect;
+import android.graphics.RectF;
+import android.net.Uri;
+import android.os.AsyncTask;
+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;
+import android.widget.Toast;
+
+import com.android.gallery3d.R;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Activity for cropping an image.
+ */
+public class CropActivity extends Activity {
+ private static final String LOGTAG = "CropActivity";
+ private CropExtras mCropExtras = null;
+ private LoadBitmapTask mLoadBitmapTask = null;
+ private SaveBitmapTask mSaveBitmapTask = null;
+ private SetWallpaperTask mSetWallpaperTask = null;
+ private Bitmap mOriginalBitmap = null;
+ private CropView mCropView = null;
+ private int mActiveBackgroundIO = 0;
+ 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;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Intent intent = getIntent();
+ mResultIntent = new Intent();
+ setResult(RESULT_CANCELED, mResultIntent);
+ mCropExtras = getExtrasFromIntent(intent);
+ if (mCropExtras != null && mCropExtras.getShowWhenLocked()) {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+ }
+
+ 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() {
+ @Override
+ public void onClick(View view) {
+ startFinishOutput();
+ }
+ });
+ }
+
+ @Override
+ protected void onDestroy() {
+ if (mLoadBitmapTask != null) {
+ mLoadBitmapTask.cancel(false);
+ }
+ super.onDestroy();
+ }
+
+ /**
+ * Opens a selector in Gallery to chose an image for use when none was given
+ * in the CROP intent.
+ */
+ public void pickImage() {
+ Intent intent = new Intent();
+ intent.setType("image/*");
+ intent.setAction(Intent.ACTION_GET_CONTENT);
+ startActivityForResult(Intent.createChooser(intent, getString(R.string.select_image)),
+ SELECT_PICTURE);
+ }
+
+ /**
+ * Callback for pickImage().
+ */
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (resultCode == RESULT_OK && requestCode == SELECT_PICTURE) {
+ Uri selectedImageUri = data.getData();
+ startLoadBitmap(selectedImageUri);
+ }
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Method that loads a bitmap in an async task.
+ */
+ private void startLoadBitmap(Uri uri) {
+ mActiveBackgroundIO++;
+ final View loading = findViewById(R.id.loading);
+ loading.setVisibility(View.VISIBLE);
+ mLoadBitmapTask = new LoadBitmapTask();
+ mLoadBitmapTask.execute(uri);
+ }
+
+ /**
+ * Method called on UI thread with loaded bitmap.
+ */
+ private void doneLoadBitmap(Bitmap bitmap) {
+ mActiveBackgroundIO--;
+ 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));
+ } else {
+ Log.w(LOGTAG, "could not load image for cropping");
+ cannotLoadImage();
+ setResult(RESULT_CANCELED, mResultIntent);
+ done();
+ }
+ }
+
+ /**
+ * Display toast for image loading failure.
+ */
+ private void cannotLoadImage() {
+ CharSequence text = getString(R.string.cannot_load_image);
+ Toast toast = Toast.makeText(this, text, Toast.LENGTH_SHORT);
+ toast.show();
+ }
+
+ /**
+ * AsyncTask for loading a bitmap into memory.
+ *
+ * @see #startLoadBitmap(Uri)
+ * @see #doneLoadBitmap(Bitmap)
+ */
+ private class LoadBitmapTask extends AsyncTask<Uri, Void, Bitmap> {
+ int mBitmapSize;
+ Context mContext;
+ Rect mOriginalBounds;
+
+ public LoadBitmapTask() {
+ mBitmapSize = getScreenImageSize();
+ Log.v(LOGTAG, "bitmap size: " + mBitmapSize);
+ mContext = getApplicationContext();
+ mOriginalBounds = new Rect();
+ }
+
+ @Override
+ protected Bitmap doInBackground(Uri... params) {
+ Bitmap bmap = CropLoader.getConstrainedBitmap(params[0], mContext, mBitmapSize,
+ mOriginalBounds);
+ return bmap;
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap result) {
+ doneLoadBitmap(result);
+ // super.onPostExecute(result);
+ }
+ }
+
+ private void startSaveBitmap(Bitmap bmap, Uri uri, String format) {
+ if (bmap == null || uri == null) {
+ throw new IllegalArgumentException("bad argument to startSaveBitmap");
+ }
+ mActiveBackgroundIO++;
+ final View loading = findViewById(R.id.loading);
+ loading.setVisibility(View.VISIBLE);
+ mSaveBitmapTask = new SaveBitmapTask(uri, format);
+ mSaveBitmapTask.execute(bmap);
+ }
+
+ private void doneSaveBitmap(Uri uri) {
+ mActiveBackgroundIO--;
+ 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;
+ }
+ done();
+ }
+
+ private class SaveBitmapTask extends AsyncTask<Bitmap, Void, Boolean> {
+
+ OutputStream mOutStream = null;
+ String mOutputFormat = null;
+ Uri mOutUri = null;
+
+ public SaveBitmapTask(Uri uri, String outputFormat) {
+ mOutputFormat = outputFormat;
+ mOutStream = null;
+ mOutUri = uri;
+ try {
+ mOutStream = getContentResolver().openOutputStream(uri);
+ } catch (FileNotFoundException e) {
+ Log.w(LOGTAG, "cannot write output: " + mOutUri.toString(), e);
+ }
+ }
+
+ @Override
+ protected Boolean doInBackground(Bitmap... params) {
+ if (mOutStream == null) {
+ return false;
+ }
+ 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);
+ }
+ 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);
+ }
+ 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);
+ }
+ startSaveBitmap(cropped, mCropExtras.getExtraOutput(),
+ mCropExtras.getOutputFormat());
+ }
+ if (mCropExtras.getSetAsWallpaper()) {
+ if (cropped == null) {
+ cropped = getCroppedImage(mOriginalBitmap);
+ }
+ startSetWallpaper(cropped);
+ }
+ if (mCropExtras.getReturnData()) {
+ if (cropped == null) {
+ cropped = getCroppedImage(mOriginalBitmap);
+ }
+ int bmapSize = cropped.getRowBytes() * cropped.getHeight();
+ if (bmapSize > MAX_BMAP_IN_INTENT) {
+ Log.w(LOGTAG, "Bitmap too large to be returned via intent");
+ } else {
+ mResultIntent.putExtra(CropExtras.KEY_DATA, cropped);
+ }
+ }
+ setResult(RESULT_OK, mResultIntent);
+ } else {
+ setResult(RESULT_CANCELED, mResultIntent);
+ }
+ done();
+ }
+
+ private void done() {
+ if (mActiveBackgroundIO == 0) {
+ finish();
+ }
+ }
+
+ private Bitmap getCroppedImage(Bitmap image) {
+ RectF imageBounds = new RectF(0, 0, image.getWidth(), image.getHeight());
+ RectF crop = getBitmapCrop(imageBounds);
+ if (crop == null) {
+ return image;
+ }
+ Rect intCrop = new Rect();
+ crop.roundOut(intCrop);
+ return Bitmap.createBitmap(image, intCrop.left, intCrop.top, intCrop.width(),
+ intCrop.height());
+ }
+
+ private RectF getBitmapCrop(RectF imageBounds) {
+ RectF crop = new RectF();
+ if (!mCropView.getCropBounds(crop, imageBounds)) {
+ Log.w(LOGTAG, "could not get crop");
+ return null;
+ }
+ return crop;
+ }
+
+ /**
+ * Helper method for unit conversions.
+ */
+ public float getPixelsFromDip(float value) {
+ Resources r = getResources();
+ return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value,
+ r.getDisplayMetrics());
+ }
+
+ private static CompressFormat convertExtensionToCompressFormat(String extension) {
+ return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG;
+ }
+
+ private static String getFileExtension(String requestFormat) {
+ String outputFormat = (requestFormat == null)
+ ? "jpg"
+ : requestFormat;
+ outputFormat = outputFormat.toLowerCase();
+ return (outputFormat.equals("png") || outputFormat.equals("gif"))
+ ? "png" // We don't support gif compression.
+ : "jpg";
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/CropExtras.java b/src/com/android/gallery3d/filtershow/crop/CropExtras.java
similarity index 98%
rename from src/com/android/gallery3d/filtershow/CropExtras.java
rename to src/com/android/gallery3d/filtershow/crop/CropExtras.java
index 7ed8f1e..60fe9af 100644
--- a/src/com/android/gallery3d/filtershow/CropExtras.java
+++ b/src/com/android/gallery3d/filtershow/crop/CropExtras.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.gallery3d.filtershow;
+package com.android.gallery3d.filtershow.crop;
import android.net.Uri;
diff --git a/src/com/android/gallery3d/filtershow/crop/CropLoader.java b/src/com/android/gallery3d/filtershow/crop/CropLoader.java
new file mode 100644
index 0000000..4025493
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/crop/CropLoader.java
@@ -0,0 +1,199 @@
+/*
+ * 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.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.provider.MediaStore;
+import android.util.Log;
+
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.exif.ExifInterface;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * This class contains reentrant static methods for loading a bitmap.
+ */
+public abstract class CropLoader {
+ public static final String LOGTAG = "CropLoader";
+ public static final String JPEG_MIME_TYPE = "image/jpeg";
+ public static final int ORI_NORMAL = ExifInterface.Orientation.TOP_LEFT;
+ public static final int ORI_ROTATE_90 = ExifInterface.Orientation.RIGHT_TOP;
+ public static final int ORI_ROTATE_180 = ExifInterface.Orientation.BOTTOM_LEFT;
+ public static final int ORI_ROTATE_270 = ExifInterface.Orientation.RIGHT_BOTTOM;
+
+ /**
+ * 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 getMetadataOrientation(Uri uri, Context context) {
+ if (uri == null || context == null) {
+ throw new IllegalArgumentException("bad argument to getScaledBitmap");
+ }
+ if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
+ String mimeType = context.getContentResolver().getType(uri);
+ if (mimeType != JPEG_MIME_TYPE) {
+ return 0;
+ }
+ String path = uri.getPath();
+ int orientation = 0;
+ 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;
+ }
+ Cursor cursor = null;
+ try {
+ cursor = context.getContentResolver().query(uri,
+ new String[] {
+ MediaStore.Images.ImageColumns.ORIENTATION
+ },
+ null, null, null);
+ if (cursor.moveToNext()) {
+ int ori = cursor.getInt(0);
+
+ switch (ori) {
+ case 0:
+ return ORI_NORMAL;
+ case 90:
+ return ORI_ROTATE_90;
+ case 270:
+ return ORI_ROTATE_270;
+ case 180:
+ return ORI_ROTATE_180;
+ default:
+ return 0;
+ }
+ }
+ } catch (SQLiteException e) {
+ return 0;
+ } catch (IllegalArgumentException e) {
+ return 0;
+ } finally {
+ Utils.closeSilently(cursor);
+ }
+ 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);
+ if (imageSide > maxSideLength) {
+ int shifts = 1 + Integer.numberOfLeadingZeros(maxSideLength)
+ - Integer.numberOfLeadingZeros(imageSide);
+ options.inSampleSize = 1 << shifts;
+ }
+
+ // Make sure sample size is reasonable
+ if (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;
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/CropMath.java b/src/com/android/gallery3d/filtershow/crop/CropMath.java
similarity index 87%
rename from src/com/android/gallery3d/filtershow/imageshow/CropMath.java
rename to src/com/android/gallery3d/filtershow/crop/CropMath.java
index 9037ca0..5914f1c 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/CropMath.java
+++ b/src/com/android/gallery3d/filtershow/crop/CropMath.java
@@ -14,11 +14,13 @@
* limitations under the License.
*/
-package com.android.gallery3d.filtershow.imageshow;
+package com.android.gallery3d.filtershow.crop;
import android.graphics.Matrix;
import android.graphics.RectF;
+import com.android.gallery3d.filtershow.imageshow.GeometryMath;
+
import java.util.Arrays;
public class CropMath {
@@ -176,6 +178,33 @@
r.set(centX - hw, centY - hh, centX + hw, centY + hh);
}
+ /**
+ * Resizes rectangle to have a certain aspect ratio (center remains
+ * stationary) while constraining it to remain within the original rect.
+ *
+ * @param r rectangle to resize
+ * @param w new width aspect
+ * @param h new height aspect
+ */
+ public static void fixAspectRatioContained(RectF r, float w, float h) {
+ float origW = r.width();
+ float origH = r.height();
+ float origA = origW / origH;
+ float a = w / h;
+ float finalW = origW;
+ float finalH = origH;
+ if (origA < a) {
+ finalH = origH / a;
+ } else {
+ finalW = origW * a;
+ }
+ float centX = r.centerX();
+ float centY = r.centerY();
+ float hw = finalW / 2;
+ float hh = finalH / 2;
+ r.set(centX - hw, centY - hh, centX + hw, centY + hh);
+ }
+
private static float getUnrotated(float[] rotatedRect, float[] center, RectF unrotated) {
float dy = rotatedRect[1] - rotatedRect[3];
float dx = rotatedRect[0] - rotatedRect[2];
diff --git a/src/com/android/gallery3d/filtershow/crop/CropObject.java b/src/com/android/gallery3d/filtershow/crop/CropObject.java
new file mode 100644
index 0000000..00baba9
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/crop/CropObject.java
@@ -0,0 +1,327 @@
+/*
+ * 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.graphics.Rect;
+import android.graphics.RectF;
+
+import com.android.gallery3d.filtershow.imageshow.GeometryMath;
+
+public class CropObject {
+
+ private BoundedRect mBoundedRect;
+ private float mAspectWidth = 1;
+ private float mAspectHeight = 1;
+ private boolean mFixAspectRatio = false;
+ private float mRotation = 0;
+ private float mTouchTolerance = 45;
+ private float mMinSideSize = 20;
+
+ public static final int MOVE_NONE = 0;
+ // Sides
+ public static final int MOVE_LEFT = 1;
+ public static final int MOVE_TOP = 2;
+ public static final int MOVE_RIGHT = 4;
+ public static final int MOVE_BOTTOM = 8;
+ public static final int MOVE_BLOCK = 16;
+
+ // Corners
+ public static final int TOP_LEFT = MOVE_TOP | MOVE_LEFT;
+ public static final int TOP_RIGHT = MOVE_TOP | MOVE_RIGHT;
+ public static final int BOTTOM_RIGHT = MOVE_BOTTOM | MOVE_RIGHT;
+ public static final int BOTTOM_LEFT = MOVE_BOTTOM | MOVE_LEFT;
+
+ private int mMovingEdges = MOVE_NONE;
+
+ public CropObject(Rect outerBound, Rect innerBound, int outerAngle) {
+ mBoundedRect = new BoundedRect(outerAngle % 360, outerBound, innerBound);
+ }
+
+ public CropObject(RectF outerBound, RectF innerBound, int outerAngle) {
+ mBoundedRect = new BoundedRect(outerAngle % 360, outerBound, innerBound);
+ }
+
+ public void setToInnerBounds(RectF r) {
+ mBoundedRect.setToInner(r);
+ }
+
+ public void setToOuterBounds(RectF r) {
+ mBoundedRect.setToOuter(r);
+ }
+
+ public RectF getInnerBounds() {
+ return mBoundedRect.getInner();
+ }
+
+ public RectF getOuterBounds() {
+ return mBoundedRect.getOuter();
+ }
+
+ public int getSelectState() {
+ return mMovingEdges;
+ }
+
+ public boolean isFixedAspect() {
+ return mFixAspectRatio;
+ }
+
+ public void rotateOuter(int angle) {
+ mRotation = angle % 360;
+ mBoundedRect.setRotation(mRotation);
+ clearSelectState();
+ }
+
+ public boolean setInnerAspectRatio(int width, int height) {
+ if (width <= 0 || height <= 0) {
+ throw new IllegalArgumentException("Width and Height must be greater than zero");
+ }
+ RectF inner = mBoundedRect.getInner();
+ CropMath.fixAspectRatioContained(inner, width, height);
+ if (inner.width() < mMinSideSize || inner.height() < mMinSideSize) {
+ return false;
+ }
+ mAspectWidth = width;
+ mAspectHeight = height;
+ mFixAspectRatio = true;
+ mBoundedRect.setInner(inner);
+ clearSelectState();
+ return true;
+ }
+
+ public void setTouchTolerance(float tolerance) {
+ if (tolerance <= 0) {
+ throw new IllegalArgumentException("Tolerance must be greater than zero");
+ }
+ mTouchTolerance = tolerance;
+ }
+
+ public void setMinInnerSideSize(float minSide) {
+ if (minSide <= 0) {
+ throw new IllegalArgumentException("Min dide must be greater than zero");
+ }
+ mMinSideSize = minSide;
+ }
+
+ public void unsetAspectRatio() {
+ mFixAspectRatio = false;
+ clearSelectState();
+ }
+
+ public boolean hasSelectedEdge() {
+ return mMovingEdges != MOVE_NONE;
+ }
+
+ public static boolean checkCorner(int selected) {
+ return selected == TOP_LEFT || selected == TOP_RIGHT || selected == BOTTOM_RIGHT
+ || selected == BOTTOM_LEFT;
+ }
+
+ public static boolean checkEdge(int selected) {
+ return selected == MOVE_LEFT || selected == MOVE_TOP || selected == MOVE_RIGHT
+ || selected == MOVE_BOTTOM;
+ }
+
+ public static boolean checkBlock(int selected) {
+ return selected == MOVE_BLOCK;
+ }
+
+ public static boolean checkValid(int selected) {
+ return selected == MOVE_NONE || checkBlock(selected) || checkEdge(selected)
+ || checkCorner(selected);
+ }
+
+ public void clearSelectState() {
+ mMovingEdges = MOVE_NONE;
+ }
+
+ public int wouldSelectEdge(float x, float y) {
+ int edgeSelected = calculateSelectedEdge(x, y);
+ if (edgeSelected != MOVE_NONE && edgeSelected != MOVE_BLOCK) {
+ return edgeSelected;
+ }
+ return MOVE_NONE;
+ }
+
+ public boolean selectEdge(int edge) {
+ if (!checkValid(edge)) {
+ // temporary
+ throw new IllegalArgumentException("bad edge selected");
+ // return false;
+ }
+ if ((mFixAspectRatio && !checkCorner(edge)) && !checkBlock(edge)) {
+ // temporary
+ throw new IllegalArgumentException("bad corner selected");
+ // return false;
+ }
+ mMovingEdges = edge;
+ return true;
+ }
+
+ public boolean selectEdge(float x, float y) {
+ int edgeSelected = calculateSelectedEdge(x, y);
+ if (mFixAspectRatio) {
+ edgeSelected = fixEdgeToCorner(edgeSelected);
+ }
+ if (edgeSelected == MOVE_NONE) {
+ return false;
+ }
+ return selectEdge(edgeSelected);
+ }
+
+ public boolean moveCurrentSelection(float dX, float dY) {
+ if (mMovingEdges == MOVE_NONE) {
+ return false;
+ }
+ RectF crop = mBoundedRect.getInner();
+
+ float minWidthHeight = mMinSideSize;
+
+ int movingEdges = mMovingEdges;
+ if (movingEdges == MOVE_BLOCK) {
+ mBoundedRect.moveInner(dX, dY);
+ return true;
+ } else {
+ float dx = 0;
+ float dy = 0;
+
+ if ((movingEdges & MOVE_LEFT) != 0) {
+ dx = Math.min(crop.left + dX, crop.right - minWidthHeight) - crop.left;
+ }
+ if ((movingEdges & MOVE_TOP) != 0) {
+ dy = Math.min(crop.top + dY, crop.bottom - minWidthHeight) - crop.top;
+ }
+ if ((movingEdges & MOVE_RIGHT) != 0) {
+ dx = Math.max(crop.right + dX, crop.left + minWidthHeight)
+ - crop.right;
+ }
+ if ((movingEdges & MOVE_BOTTOM) != 0) {
+ dy = Math.max(crop.bottom + dY, crop.top + minWidthHeight)
+ - crop.bottom;
+ }
+
+ if (mFixAspectRatio) {
+ float[] l1 = {
+ crop.left, crop.bottom
+ };
+ float[] l2 = {
+ crop.right, crop.top
+ };
+ if (movingEdges == TOP_LEFT || movingEdges == BOTTOM_RIGHT) {
+ l1[1] = crop.top;
+ l2[1] = crop.bottom;
+ }
+ float[] b = {
+ l1[0] - l2[0], l1[1] - l2[1]
+ };
+ float[] disp = {
+ dx, dy
+ };
+ float[] bUnit = GeometryMath.normalize(b);
+ float sp = GeometryMath.scalarProjection(disp, bUnit);
+ dx = sp * bUnit[0];
+ dy = sp * bUnit[1];
+ RectF newCrop = fixedCornerResize(crop, movingEdges, dx, dy);
+
+ mBoundedRect.fixedAspectResizeInner(newCrop);
+ } else {
+ if ((movingEdges & MOVE_LEFT) != 0) {
+ crop.left += dx;
+ }
+ if ((movingEdges & MOVE_TOP) != 0) {
+ crop.top += dy;
+ }
+ if ((movingEdges & MOVE_RIGHT) != 0) {
+ crop.right += dx;
+ }
+ if ((movingEdges & MOVE_BOTTOM) != 0) {
+ crop.bottom += dy;
+ }
+ mBoundedRect.resizeInner(crop);
+ }
+ }
+ return true;
+ }
+
+ // Helper methods
+
+ private int calculateSelectedEdge(float x, float y) {
+ RectF cropped = mBoundedRect.getInner();
+
+ float left = Math.abs(x - cropped.left);
+ float right = Math.abs(x - cropped.right);
+ float top = Math.abs(y - cropped.top);
+ float bottom = Math.abs(y - cropped.bottom);
+
+ int edgeSelected = MOVE_NONE;
+ // Check left or right.
+ if ((left <= mTouchTolerance) && ((y + mTouchTolerance) >= cropped.top)
+ && ((y - mTouchTolerance) <= cropped.bottom) && (left < right)) {
+ edgeSelected |= MOVE_LEFT;
+ }
+ else if ((right <= mTouchTolerance) && ((y + mTouchTolerance) >= cropped.top)
+ && ((y - mTouchTolerance) <= cropped.bottom)) {
+ edgeSelected |= MOVE_RIGHT;
+ }
+
+ // Check top or bottom.
+ if ((top <= mTouchTolerance) && ((x + mTouchTolerance) >= cropped.left)
+ && ((x - mTouchTolerance) <= cropped.right) && (top < bottom)) {
+ edgeSelected |= MOVE_TOP;
+ }
+ else if ((bottom <= mTouchTolerance) && ((x + mTouchTolerance) >= cropped.left)
+ && ((x - mTouchTolerance) <= cropped.right)) {
+ edgeSelected |= MOVE_BOTTOM;
+ }
+ return edgeSelected;
+ }
+
+ private static RectF fixedCornerResize(RectF r, int moving_corner, float dx, float dy) {
+ RectF newCrop = null;
+ // Fix opposite corner in place and move sides
+ if (moving_corner == BOTTOM_RIGHT) {
+ newCrop = new RectF(r.left, r.top, r.left + r.width() + dx, r.top + r.height()
+ + dy);
+ } else if (moving_corner == BOTTOM_LEFT) {
+ newCrop = new RectF(r.right - r.width() + dx, r.top, r.right, r.top + r.height()
+ + dy);
+ } else if (moving_corner == TOP_LEFT) {
+ newCrop = new RectF(r.right - r.width() + dx, r.bottom - r.height() + dy,
+ r.right, r.bottom);
+ } else if (moving_corner == TOP_RIGHT) {
+ newCrop = new RectF(r.left, r.bottom - r.height() + dy, r.left
+ + r.width() + dx, r.bottom);
+ }
+ return newCrop;
+ }
+
+ private static int fixEdgeToCorner(int moving_edges) {
+ if (moving_edges == MOVE_LEFT) {
+ moving_edges |= MOVE_TOP;
+ }
+ if (moving_edges == MOVE_TOP) {
+ moving_edges |= MOVE_LEFT;
+ }
+ if (moving_edges == MOVE_RIGHT) {
+ moving_edges |= MOVE_BOTTOM;
+ }
+ if (moving_edges == MOVE_BOTTOM) {
+ moving_edges |= MOVE_RIGHT;
+ }
+ return moving_edges;
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/crop/CropView.java b/src/com/android/gallery3d/filtershow/crop/CropView.java
new file mode 100644
index 0000000..561f7ae
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/crop/CropView.java
@@ -0,0 +1,278 @@
+/*
+ * 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.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.gallery3d.R;
+
+public class CropView extends View {
+ private static final String LOGTAG = "CropView";
+
+ Bitmap mImage = null;
+ CropObject mCropObj = null;
+ private final Drawable mCropIndicator;
+ private final int mIndicatorSize;
+
+ 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 enum Mode {
+ NONE, MOVE
+ }
+
+ private Mode mState = Mode.NONE;
+
+ 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);
+ }
+
+ // 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 = getBitmapToDisplayMatrix(displayBounds, new RectF(
+ displayBoundsOriginal));
+ mDisplayMatrixInverse = new Matrix();
+ mDisplayMatrixInverse.reset();
+ if (!mDisplayMatrix.invert(mDisplayMatrixInverse)) {
+ Log.w(LOGTAG, "could not invert display matrix");
+ }
+ // Scale min side and tolerance by display matrix scale factor
+ mCropObj.setMinInnerSideSize(mDisplayMatrixInverse.mapRadius(mMinSideSize));
+ mCropObj.setTouchTolerance(mDisplayMatrixInverse.mapRadius(mTouchTolerance));
+ }
+ canvas.drawBitmap(mImage, mDisplayMatrix, new Paint());
+
+ if (mDisplayMatrix.mapRect(imageBounds)) {
+ drawCropRect(canvas, imageBounds);
+ drawRuleOfThird(canvas, imageBounds);
+ drawIndicators(canvas, mCropIndicator, mIndicatorSize, imageBounds,
+ mCropObj.isFixedAspect(), mCropObj.getSelectState());
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ float x = event.getX();
+ float y = event.getY();
+ if (mDisplayMatrix == null || mDisplayMatrixInverse == null) {
+ return true;
+ }
+ float[] touchPoint = {
+ x, y
+ };
+ mDisplayMatrixInverse.mapPoints(touchPoint);
+ x = touchPoint[0];
+ y = touchPoint[1];
+ switch (event.getActionMasked()) {
+ case (MotionEvent.ACTION_DOWN):
+ if (mState == Mode.NONE) {
+ if (!mCropObj.selectEdge(x, y)) {
+ mMovingBlock = mCropObj.selectEdge(CropObject.MOVE_BLOCK);
+ }
+ mPrevX = x;
+ mPrevY = y;
+ mState = Mode.MOVE;
+ } else {
+ reset();
+ }
+ break;
+ case (MotionEvent.ACTION_UP):
+ if (mState == Mode.MOVE) {
+ mCropObj.selectEdge(CropObject.MOVE_NONE);
+ mMovingBlock = false;
+ mPrevX = x;
+ mPrevY = y;
+ mState = Mode.NONE;
+ } else {
+ reset();
+ }
+ break;
+ case (MotionEvent.ACTION_MOVE):
+ if (mState == Mode.MOVE) {
+ float dx = x - mPrevX;
+ float dy = y - mPrevY;
+ 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");
+ mState = Mode.NONE;
+ mCropObj = null;
+ mDisplayMatrix = null;
+ mDisplayMatrixInverse = null;
+ mMovingBlock = false;
+ invalidate();
+ }
+
+ public boolean getCropBounds(RectF out_crop, RectF in_newContaining) {
+ Matrix m = new Matrix();
+ RectF inner = mCropObj.getInnerBounds();
+ RectF outer = mCropObj.getOuterBounds();
+ if (!m.setRectToRect(outer, in_newContaining, Matrix.ScaleToFit.FILL)) {
+ Log.w(LOGTAG, "failed to make transform matrix");
+ return false;
+ }
+ if (!m.mapRect(inner)) {
+ Log.w(LOGTAG, "failed to transform crop bounds");
+ return false;
+ }
+ out_crop.set(inner);
+ return true;
+ }
+
+ // Helper methods
+
+ private static void drawRuleOfThird(Canvas canvas, RectF bounds) {
+ Paint p = new Paint();
+ p.setStyle(Paint.Style.STROKE);
+ p.setColor(Color.argb(128, 255, 255, 255));
+ p.setStrokeWidth(2);
+ float stepX = bounds.width() / 3.0f;
+ float stepY = bounds.height() / 3.0f;
+ float x = bounds.left + stepX;
+ float y = bounds.top + stepY;
+ for (int i = 0; i < 2; i++) {
+ canvas.drawLine(x, bounds.top, x, bounds.bottom, p);
+ x += stepX;
+ }
+ for (int j = 0; j < 2; j++) {
+ canvas.drawLine(bounds.left, y, bounds.right, y, p);
+ y += stepY;
+ }
+ }
+
+ private static void drawCropRect(Canvas canvas, RectF bounds) {
+ Paint p = new Paint();
+ p.setStyle(Paint.Style.STROKE);
+ p.setColor(Color.WHITE);
+ p.setStrokeWidth(3);
+ canvas.drawRect(bounds, p);
+ }
+
+ private static void drawIndicator(Canvas canvas, Drawable indicator, int indicatorSize,
+ float centerX, float centerY) {
+ int left = (int) centerX - indicatorSize / 2;
+ int top = (int) centerY - indicatorSize / 2;
+ indicator.setBounds(left, top, left + indicatorSize, top + indicatorSize);
+ indicator.draw(canvas);
+ }
+
+ private static void drawIndicators(Canvas canvas, Drawable cropIndicator, int indicatorSize,
+ RectF bounds, boolean fixedAspect, int selection) {
+ boolean notMoving = (selection == CropObject.MOVE_NONE);
+ if (fixedAspect) {
+ if ((selection == CropObject.TOP_LEFT) || notMoving) {
+ drawIndicator(canvas, cropIndicator, indicatorSize, bounds.left, bounds.top);
+ }
+ if ((selection == CropObject.TOP_RIGHT) || notMoving) {
+ drawIndicator(canvas, cropIndicator, indicatorSize, bounds.right, bounds.top);
+ }
+ if ((selection == CropObject.BOTTOM_LEFT) || notMoving) {
+ drawIndicator(canvas, cropIndicator, indicatorSize, bounds.left, bounds.bottom);
+ }
+ if ((selection == CropObject.BOTTOM_RIGHT) || notMoving) {
+ drawIndicator(canvas, cropIndicator, indicatorSize, bounds.right, bounds.bottom);
+ }
+ } else {
+ if (((selection & CropObject.MOVE_TOP) != 0) || notMoving) {
+ drawIndicator(canvas, cropIndicator, indicatorSize, bounds.centerX(), bounds.top);
+ }
+ if (((selection & CropObject.MOVE_BOTTOM) != 0) || notMoving) {
+ drawIndicator(canvas, cropIndicator, indicatorSize, bounds.centerX(), bounds.bottom);
+ }
+ if (((selection & CropObject.MOVE_LEFT) != 0) || notMoving) {
+ drawIndicator(canvas, cropIndicator, indicatorSize, bounds.left, bounds.centerY());
+ }
+ if (((selection & CropObject.MOVE_RIGHT) != 0) || notMoving) {
+ drawIndicator(canvas, cropIndicator, indicatorSize, bounds.right, bounds.centerY());
+ }
+ }
+ }
+
+ private static Matrix getBitmapToDisplayMatrix(RectF imageBounds, RectF displayBounds) {
+ Matrix m = new Matrix();
+ setBitmapToDisplayMatrix(m, imageBounds, displayBounds);
+ return m;
+ }
+
+ private static boolean setBitmapToDisplayMatrix(Matrix m, RectF imageBounds,
+ RectF displayBounds) {
+ m.reset();
+ return m.setRectToRect(imageBounds, displayBounds, Matrix.ScaleToFit.CENTER);
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorCrop.java b/src/com/android/gallery3d/filtershow/editors/EditorCrop.java
index df922f1..e2173ad 100644
--- a/src/com/android/gallery3d/filtershow/editors/EditorCrop.java
+++ b/src/com/android/gallery3d/filtershow/editors/EditorCrop.java
@@ -21,7 +21,7 @@
import android.widget.FrameLayout;
import com.android.gallery3d.R;
-import com.android.gallery3d.filtershow.CropExtras;
+import com.android.gallery3d.filtershow.crop.CropExtras;
import com.android.gallery3d.filtershow.imageshow.ImageCrop;
import com.android.gallery3d.filtershow.imageshow.MasterImage;
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java
index 5a33cc8..4f46eed 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java
@@ -24,7 +24,7 @@
import android.graphics.RectF;
import android.util.Log;
-import com.android.gallery3d.filtershow.CropExtras;
+import com.android.gallery3d.filtershow.crop.CropExtras;
import com.android.gallery3d.filtershow.imageshow.GeometryMath;
import com.android.gallery3d.filtershow.imageshow.GeometryMetadata;
diff --git a/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java b/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java
index ad2152a..898fdf0 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java
@@ -21,8 +21,8 @@
import android.graphics.Rect;
import android.graphics.RectF;
-import com.android.gallery3d.filtershow.CropExtras;
import com.android.gallery3d.filtershow.cache.ImageLoader;
+import com.android.gallery3d.filtershow.crop.CropExtras;
import com.android.gallery3d.filtershow.editors.EditorCrop;
import com.android.gallery3d.filtershow.editors.EditorFlip;
import com.android.gallery3d.filtershow.editors.EditorRotate;
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java b/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java
index 2ea6f6a..6d62bbd 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java
@@ -33,7 +33,9 @@
import android.widget.PopupMenu;
import com.android.gallery3d.R;
-import com.android.gallery3d.filtershow.CropExtras;
+import com.android.gallery3d.filtershow.crop.BoundedRect;
+import com.android.gallery3d.filtershow.crop.CropExtras;
+import com.android.gallery3d.filtershow.crop.CropMath;
import com.android.gallery3d.filtershow.editors.EditorCrop;
import com.android.gallery3d.filtershow.ui.FramedTextButton;
diff --git a/src/com/android/gallery3d/gadget/WidgetConfigure.java b/src/com/android/gallery3d/gadget/WidgetConfigure.java
index 4818d26..eb81b6e 100644
--- a/src/com/android/gallery3d/gadget/WidgetConfigure.java
+++ b/src/com/android/gallery3d/gadget/WidgetConfigure.java
@@ -36,7 +36,7 @@
import com.android.gallery3d.data.MediaSet;
import com.android.gallery3d.data.Path;
import com.android.gallery3d.filtershow.FilterShowActivity;
-import com.android.gallery3d.filtershow.CropExtras;
+import com.android.gallery3d.filtershow.crop.CropExtras;
public class WidgetConfigure extends Activity {
@SuppressWarnings("unused")