diff options
Diffstat (limited to 'src/com/android')
16 files changed, 1292 insertions, 16 deletions
diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java index 22bd6503f..7c0c15a82 100644 --- a/src/com/android/camera/PhotoModule.java +++ b/src/com/android/camera/PhotoModule.java @@ -64,8 +64,8 @@ import com.android.gallery3d.common.ApiHelper; 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 51591cc51..001ce87b7 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.MediaItem; 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 1bbe8d2c6..91bc77271 100644 --- a/src/com/android/gallery3d/app/Wallpaper.java +++ b/src/com/android/gallery3d/app/Wallpaper.java @@ -27,7 +27,7 @@ import android.view.Display; 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 b65943279..874a7c911 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.data.LocalAlbum; 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 index e94d1ed9e..c2c768eaf 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 @@ public class BoundedRect { 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 @@ public class BoundedRect { 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 000000000..26659a600 --- /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 index 7ed8f1eb5..60fe9af53 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 000000000..40254931f --- /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 index 9037ca043..5914f1cb8 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 @@ public class CropMath { 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 000000000..00baba980 --- /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 000000000..561f7ae7f --- /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 df922f10a..e2173ad77 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.view.View; 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 5a33cc823..4f46eed82 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.Rect; 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 ad2152ab1..898fdf021 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.Matrix; 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 2ea6f6a42..6d62bbd1d 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.LinearLayout; 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 4818d261b..eb81b6ef3 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.LocalAlbum; 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") |