diff options
11 files changed, 1686 insertions, 217 deletions
diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java index 89ef41744..664b67f5c 100644 --- a/src/com/android/gallery3d/filtershow/FilterShowActivity.java +++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java @@ -38,6 +38,9 @@ import com.android.gallery3d.filtershow.imageshow.ImageShow; import com.android.gallery3d.filtershow.imageshow.ImageSmallFilter; import com.android.gallery3d.filtershow.imageshow.ImageStraighten; import com.android.gallery3d.filtershow.imageshow.ImageZoom; +import com.android.gallery3d.filtershow.imageshow.ImageFlip; +import com.android.gallery3d.filtershow.imageshow.ImageCrop; +import com.android.gallery3d.filtershow.imageshow.ImageRotate; import com.android.gallery3d.filtershow.presets.ImagePreset; import com.android.gallery3d.filtershow.presets.ImagePresetBW; import com.android.gallery3d.filtershow.presets.ImagePresetBWBlue; @@ -65,6 +68,9 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, private ImageBorder mImageBorders = null; private ImageStraighten mImageStraighten = null; private ImageZoom mImageZoom = null; + private ImageCrop mImageCrop = null; + private ImageRotate mImageRotate = null; + private ImageFlip mImageFlip = null; private View mListFx = null; private View mListBorders = null; @@ -123,12 +129,18 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, mImageBorders = (ImageBorder) findViewById(R.id.imageBorder); mImageStraighten = (ImageStraighten) findViewById(R.id.imageStraighten); mImageZoom = (ImageZoom) findViewById(R.id.imageZoom); + mImageCrop = (ImageCrop) findViewById(R.id.imageCrop); + mImageRotate = (ImageRotate) findViewById(R.id.imageRotate); + mImageFlip = (ImageFlip) findViewById(R.id.imageFlip); mImageViews.add(mImageShow); mImageViews.add(mImageCurves); mImageViews.add(mImageBorders); mImageViews.add(mImageStraighten); mImageViews.add(mImageZoom); + mImageViews.add(mImageCrop); + mImageViews.add(mImageRotate); + mImageViews.add(mImageFlip); mListFx = findViewById(R.id.fxList); mListBorders = findViewById(R.id.bordersList); @@ -155,11 +167,20 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, mImageStraighten.setMaster(mImageShow); mImageZoom.setImageLoader(mImageLoader); mImageZoom.setMaster(mImageShow); + mImageCrop.setImageLoader(mImageLoader); + mImageCrop.setMaster(mImageShow); + mImageRotate.setImageLoader(mImageLoader); + mImageRotate.setMaster(mImageShow); + mImageFlip.setImageLoader(mImageLoader); + mImageFlip.setMaster(mImageShow); mPanelController.addImageView(findViewById(R.id.imageShow)); mPanelController.addImageView(findViewById(R.id.imageCurves)); mPanelController.addImageView(findViewById(R.id.imageBorder)); mPanelController.addImageView(findViewById(R.id.imageStraighten)); + mPanelController.addImageView(findViewById(R.id.imageCrop)); + mPanelController.addImageView(findViewById(R.id.imageRotate)); + mPanelController.addImageView(findViewById(R.id.imageFlip)); mPanelController.addImageView(findViewById(R.id.imageZoom)); mPanelController.addPanel(mFxButton, mListFx, 0); @@ -204,12 +225,11 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, seekBar.setMax(200); mImageShow.setSeekBar(seekBar); mPanelController.setRowPanel(findViewById(R.id.secondRowPanel)); - mPanelController.setUtilityPanel(findViewById(R.id.filterButtonsList), + mPanelController.setUtilityPanel(this, findViewById(R.id.filterButtonsList), findViewById(R.id.compareWithOriginalImage), findViewById(R.id.applyEffect)); mPanelController.setMasterImage(mImageShow); mPanelController.setCurrentPanel(mFxButton); - Intent intent = getIntent(); String data = intent.getDataString(); if (data != null) { @@ -420,6 +440,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, public void invalidateViews() { for (ImageShow views : mImageViews) { views.invalidate(); + views.updateImage(); } } diff --git a/src/com/android/gallery3d/filtershow/PanelController.java b/src/com/android/gallery3d/filtershow/PanelController.java index f9a1f1d16..dbb6f27b3 100644 --- a/src/com/android/gallery3d/filtershow/PanelController.java +++ b/src/com/android/gallery3d/filtershow/PanelController.java @@ -1,6 +1,7 @@ package com.android.gallery3d.filtershow; +import android.content.Context; import android.text.Html; import android.view.View; import android.view.View.OnClickListener; @@ -103,14 +104,17 @@ public class PanelController implements OnClickListener { } class UtilityPanel { + private final Context mContext; private final View mView; private final View mCompareView; private final TextView mTextView; private boolean mSelected = false; private String mEffectName = null; private int mParameterValue = 0; + private boolean mShowParameterValue = false; - public UtilityPanel(View view, View compareView, View textView) { + public UtilityPanel(Context context, View view, View compareView, View textView) { + mContext = context; mView = view; mCompareView = compareView; mTextView = (TextView) textView; @@ -135,12 +139,22 @@ public class PanelController implements OnClickListener { public void setEffectName(String effectName) { mEffectName = effectName; + showParameter(true); updateText(); } + public void showParameter(boolean s) { + mShowParameterValue = s; + } + public void updateText() { - mTextView.setText(Html.fromHtml("Apply" + "<br/><small>" + mEffectName + "<br/>" - + mParameterValue + "</small>")); + String apply = mContext.getString(R.string.apply_effect); + if (mShowParameterValue) { + mTextView.setText(Html.fromHtml(apply + "<br/><small>" + mEffectName + "<br/>" + + mParameterValue + "</small>")); + } else { + mTextView.setText(Html.fromHtml(apply + "<br/><small>" + mEffectName + "</small>")); + } } public ViewPropertyAnimator unselect() { @@ -227,6 +241,10 @@ public class PanelController implements OnClickListener { mUtilityPanel.onNewValue(value); } + public void showParameter(boolean s) { + mUtilityPanel.showParameter(s); + } + public void setCurrentPanel(View panel) { showPanel(panel); } @@ -235,8 +253,8 @@ public class PanelController implements OnClickListener { mRowPanel = rowPanel; } - public void setUtilityPanel(View utilityPanel, View compareView, View textView) { - mUtilityPanel = new UtilityPanel(utilityPanel, compareView, textView); + public void setUtilityPanel(Context context, View utilityPanel, View compareView, View textView) { + mUtilityPanel = new UtilityPanel(context, utilityPanel, compareView, textView); } public void setMasterImage(ImageShow imageShow) { @@ -376,20 +394,22 @@ public class PanelController implements OnClickListener { break; } case R.id.cropButton: { - mCurrentImage = showImageView(R.id.imageShow); + mCurrentImage = showImageView(R.id.imageCrop); mUtilityPanel.setEffectName("Crop"); + mUtilityPanel.showParameter(false); mUtilityPanel.setGeometryEffect(true); break; } case R.id.rotateButton: { - mCurrentImage = showImageView(R.id.imageShow); + mCurrentImage = showImageView(R.id.imageRotate); mUtilityPanel.setEffectName("Rotate"); mUtilityPanel.setGeometryEffect(true); break; } case R.id.flipButton: { - mCurrentImage = showImageView(R.id.imageShow); + mCurrentImage = showImageView(R.id.imageFlip); mUtilityPanel.setEffectName("Flip"); + mUtilityPanel.showParameter(false); mUtilityPanel.setGeometryEffect(true); break; } diff --git a/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java b/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java new file mode 100644 index 000000000..1f166255a --- /dev/null +++ b/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2012 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.imageshow; + +import android.graphics.RectF; + +/** + * This class holds metadata about an image's geometry. Specifically: rotation, + * scaling, cropping, and image boundaries. It maintains the invariant that the + * cropping boundaries are within or equal to the image boundaries (before + * rotation) WHEN mSafe is true. + */ + +public class GeometryMetadata { + // Applied in order: rotate, crop, scale. + // Do not scale saved image (presumably?). + private float mScaleFactor = 0; + private float mRotation = 0; + private float mStraightenRotation = 0; + private final RectF mCropBounds = new RectF(); + private final RectF mPhotoBounds = new RectF(); + private FLIP mFlip = FLIP.NONE; + private boolean mSafe = false; + + public enum FLIP { + NONE, VERTICAL, HORIZONTAL, BOTH + } + + public GeometryMetadata() { + } + + public GeometryMetadata(GeometryMetadata g) { + set(g); + } + + public GeometryMetadata(float scale, float rotation, float straighten, RectF cropBounds, + RectF photoBounds, FLIP flipType) { + mScaleFactor = scale; + mRotation = rotation; + mStraightenRotation = straighten; + mCropBounds.set(cropBounds); + mPhotoBounds.set(photoBounds); + mFlip = flipType; + mSafe = cropFitsInPhoto(mCropBounds); + } + + // Safe as long as invariant holds. + public void set(GeometryMetadata g) { + mScaleFactor = g.mScaleFactor; + mRotation = g.mRotation; + mStraightenRotation = g.mStraightenRotation; + mCropBounds.set(g.mCropBounds); + mPhotoBounds.set(g.mPhotoBounds); + mFlip = g.mFlip; + mSafe = g.mSafe; + } + + public void safeSet(GeometryMetadata g) { + if (g.safe()) { + set(g); + return; + } + + mScaleFactor = g.mScaleFactor; + mRotation = g.mRotation; + mStraightenRotation = g.mStraightenRotation; + mCropBounds.set(g.mCropBounds); + safeSetPhotoBounds(g.mPhotoBounds); + mFlip = g.mFlip; + } + + public void safeSet(float scale, + float rotation, + float straighten, + RectF cropBounds, + RectF photoBounds, + FLIP flipType) { + mScaleFactor = scale; + mStraightenRotation = straighten; + mRotation = rotation; + mCropBounds.set(cropBounds); + safeSetPhotoBounds(photoBounds); + mFlip = flipType; + } + + public float getScaleFactor() { + return mScaleFactor; + } + + public float getRotation() { + return mRotation; + } + + public float getStraightenRotation() { + return mStraightenRotation; + } + + public RectF getCropBounds() { + return new RectF(mCropBounds); + } + + public FLIP getFlipType() { + return mFlip; + } + + public RectF getPhotoBounds() { + return new RectF(mPhotoBounds); + } + + public boolean safe() { + return mSafe; + } + + public void setScaleFactor(float scale) { + mScaleFactor = scale; + } + + public void setFlipType(FLIP flip) { + mFlip = flip; + } + + public void setRotation(float rotation) { + mRotation = rotation; + } + + public void setStraightenRotation(float straighten) { + mStraightenRotation = straighten; + } + + /** + * Sets crop bounds to be the intersection of mPhotoBounds and the new crop + * bounds. If there was no intersection, returns false and does not set crop + * bounds + */ + public boolean safeSetCropBounds(RectF newCropBounds) { + if (mCropBounds.setIntersect(newCropBounds, mPhotoBounds)) { + mSafe = true; + return true; + } + return false; + } + + public void setCropBounds(RectF newCropBounds) { + mCropBounds.set(newCropBounds); + mSafe = false; + } + + /** + * Sets mPhotoBounds to be the new photo bounds and sets mCropBounds to be + * the intersection of the new photo bounds and the old crop bounds. Sets + * the crop bounds to mPhotoBounds if there is no intersection. + */ + + public void safeSetPhotoBounds(RectF newPhotoBounds) { + mPhotoBounds.set(newPhotoBounds); + if (!mCropBounds.intersect(mPhotoBounds)) { + mCropBounds.set(mPhotoBounds); + } + mSafe = true; + } + + public void setPhotoBounds(RectF newPhotoBounds) { + mPhotoBounds.set(newPhotoBounds); + mSafe = false; + } + + public boolean cropFitsInPhoto(RectF cropBounds) { + return mPhotoBounds.contains(cropBounds); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + GeometryMetadata d = (GeometryMetadata) o; + return (mScaleFactor == d.mScaleFactor && + mRotation == d.mRotation && + mStraightenRotation == d.mStraightenRotation && + mFlip == d.mFlip && mSafe == d.mSafe && + mCropBounds.equals(d.mCropBounds) && mPhotoBounds.equals(d.mPhotoBounds)); + } + + @Override + public int hashCode() { + int result = 23; + result = 31 * result + Float.floatToIntBits(mRotation); + result = 31 * result + Float.floatToIntBits(mStraightenRotation); + result = 31 * result + Float.floatToIntBits(mScaleFactor); + result = 31 * result + mFlip.hashCode(); + result = 31 * result + mCropBounds.hashCode(); + result = 31 * result + mPhotoBounds.hashCode(); + result = 31 * result + (mSafe ? 1 : 0); + return result; + } + + @Override + public String toString() { + return getClass().getName() + "[" + "scale=" + mScaleFactor + + ",rotation=" + mRotation + ",flip=" + mFlip + ",safe=" + + (mSafe ? "true" : "false") + ",straighten=" + + mStraightenRotation + ",cropRect=" + mCropBounds.toShortString() + + ",photoRect=" + mPhotoBounds.toShortString() + "]"; + } + +} diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java b/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java new file mode 100644 index 000000000..836ec82f8 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java @@ -0,0 +1,354 @@ +/* + * Copyright (C) 2012 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.imageshow; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PointF; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import com.android.gallery3d.R; + +import com.android.gallery3d.filtershow.presets.ImagePreset; + +public class ImageCrop extends ImageGeometry { + private static final boolean LOGV = false; + private static final int MOVE_LEFT = 1; + private static final int MOVE_TOP = 2; + private static final int MOVE_RIGHT = 4; + private static final int MOVE_BOTTOM = 8; + private static final int MOVE_BLOCK = 16; + + private static final float MIN_CROP_WIDTH_HEIGHT = 0.1f; + private static final int TOUCH_TOLERANCE = 30; + private static final int SHADOW_ALPHA = 160; + + private float mAspectWidth = 4; + private float mAspectHeight = 3; + private boolean mFixAspectRatio = false; // not working yet + + private final Paint borderPaint; + + private float mCropOffsetX = 0; + private float mCropOffsetY = 0; + private float mPrevOffsetX = 0; + private float mPrevOffsetY = 0; + + private int movingEdges; + private final Drawable cropIndicator; + private final int indicatorSize; + + private static final String LOGTAG = "ImageCrop"; + + private static final Paint gPaint = new Paint(); + + public ImageCrop(Context context) { + super(context); + Resources resources = context.getResources(); + cropIndicator = resources.getDrawable(R.drawable.camera_crop_holo); + indicatorSize = (int) resources.getDimension(R.dimen.crop_indicator_size); + int borderColor = resources.getColor(R.color.opaque_cyan); + borderPaint = new Paint(); + borderPaint.setStyle(Paint.Style.STROKE); + borderPaint.setColor(borderColor); + borderPaint.setStrokeWidth(2f); + } + + public ImageCrop(Context context, AttributeSet attrs) { + super(context, attrs); + Resources resources = context.getResources(); + cropIndicator = resources.getDrawable(R.drawable.camera_crop_holo); + indicatorSize = (int) resources.getDimension(R.dimen.crop_indicator_size); + int borderColor = resources.getColor(R.color.opaque_cyan); + borderPaint = new Paint(); + borderPaint.setStyle(Paint.Style.STROKE); + borderPaint.setColor(borderColor); + borderPaint.setStrokeWidth(2f); + } + + private float getScaledMinWidthHeight() { + RectF disp = getLocalDisplayBounds(); + float scaled = Math.min(disp.width(), disp.height()) * MIN_CROP_WIDTH_HEIGHT + / getLocalScale(); + return scaled; + } + + protected static Matrix getCropRotationMatrix(float rotation, RectF localImage) { + Matrix m = new Matrix(); + m.setRotate(rotation, localImage.centerX(), localImage.centerY()); + if (!m.rectStaysRect()) { + return null; + } + return m; + } + + protected RectF getCropBoundsDisplayed() { + RectF bounds = getLocalCropBounds(); + RectF crop = new RectF(bounds); + Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds()); + + if (m == null) { + if (LOGV) + Log.v(LOGTAG, "FAILED TO MAP CROP BOUNDS TO RECTANGLE"); + m = new Matrix(); + } else { + m.mapRect(crop); + } + m = new Matrix(); + float zoom = getLocalScale(); + m.setScale(zoom, zoom, mCenterX, mCenterY); + m.preTranslate(mXOffset, mYOffset); + m.mapRect(crop); + return crop; + } + + private RectF getRotatedCropBounds() { + RectF bounds = getLocalCropBounds(); + RectF crop = new RectF(bounds); + Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds()); + + if (m == null) { + if (LOGV) + Log.v(LOGTAG, "FAILED TO MAP CROP BOUNDS TO RECTANGLE"); + return null; + } else { + m.mapRect(crop); + } + return crop; + } + + /** + * Sets cropped bounds; modifies the bounds if it's smaller than the allowed + * dimensions. + */ + public void setCropBounds(RectF bounds) { + // Avoid cropping smaller than minimum width or height. + RectF cbounds = new RectF(bounds); + float minWidthHeight = getScaledMinWidthHeight(); + + float newWidth = cbounds.width(); + float newHeight = cbounds.height(); + if (newWidth < minWidthHeight) { + newWidth = minWidthHeight; + } + if (newHeight < minWidthHeight) { + newHeight = minWidthHeight; + } + + RectF pbounds = getLocalPhotoBounds(); + if (pbounds.width() < minWidthHeight) { + newWidth = pbounds.width(); + } + if (pbounds.height() < minWidthHeight) { + newHeight = pbounds.height(); + } + + cbounds.set(cbounds.left, cbounds.top, cbounds.left + newWidth, cbounds.top + newHeight); + RectF snappedCrop = findCropBoundForRotatedImg(cbounds, pbounds, getLocalStraighten(), + mCenterX - mXOffset, mCenterY - mYOffset); + if (mFixAspectRatio) { + // TODO: add aspect ratio stuff + fixAspectRatio(snappedCrop, mAspectWidth, mAspectHeight); + } + setLocalCropBounds(snappedCrop); + invalidate(); + } + + private void detectMovingEdges(float x, float y) { + RectF cropped = getCropBoundsDisplayed(); + movingEdges = 0; + + // Check left or right. + float left = Math.abs(x - cropped.left); + float right = Math.abs(x - cropped.right); + if ((left <= TOUCH_TOLERANCE) && (left < right)) { + movingEdges |= MOVE_LEFT; + } + else if (right <= TOUCH_TOLERANCE) { + movingEdges |= MOVE_RIGHT; + } + + // Check top or bottom. + float top = Math.abs(y - cropped.top); + float bottom = Math.abs(y - cropped.bottom); + if ((top <= TOUCH_TOLERANCE) & (top < bottom)) { + movingEdges |= MOVE_TOP; + } + else if (bottom <= TOUCH_TOLERANCE) { + movingEdges |= MOVE_BOTTOM; + } + invalidate(); + } + + private void moveEdges(float dX, float dY) { + RectF cropped = getRotatedCropBounds(); + float minWidthHeight = getScaledMinWidthHeight(); + float scale = getLocalScale(); + float deltaX = dX / scale; + float deltaY = dY / scale; + if (movingEdges == MOVE_BLOCK) { + // TODO + } else { + if ((movingEdges & MOVE_LEFT) != 0) { + cropped.left = Math.min(cropped.left + deltaX, cropped.right - minWidthHeight); + fixRectAspectW(cropped); + } + if ((movingEdges & MOVE_TOP) != 0) { + cropped.top = Math.min(cropped.top + deltaY, cropped.bottom - minWidthHeight); + fixRectAspectH(cropped); + } + if ((movingEdges & MOVE_RIGHT) != 0) { + cropped.right = Math.max(cropped.right + deltaX, cropped.left + minWidthHeight); + fixRectAspectW(cropped); + } + if ((movingEdges & MOVE_BOTTOM) != 0) { + cropped.bottom = Math.max(cropped.bottom + deltaY, cropped.top + minWidthHeight); + fixRectAspectH(cropped); + } + } + Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds()); + Matrix m0 = new Matrix(); + if (!m.invert(m0)) { + if (LOGV) + Log.v(LOGTAG, "FAILED TO INVERT ROTATION MATRIX"); + } + if (!m0.mapRect(cropped)) { + if (LOGV) + Log.v(LOGTAG, "FAILED TO UNROTATE CROPPING BOUNDS"); + } + setCropBounds(cropped); + } + + private void fixRectAspectH(RectF cropped) { + if (mFixAspectRatio) { + float half = getNewWidthForHeightAspect(cropped.height(), mAspectWidth, mAspectHeight) / 2; + float mid = (cropped.right - cropped.left) / 2; + cropped.left = mid - half; + cropped.right = mid + half; + } + } + + private void fixRectAspectW(RectF cropped) { + if (mFixAspectRatio) { + float half = getNewHeightForWidthAspect(cropped.width(), mAspectWidth, mAspectHeight) / 2; + float mid = (cropped.bottom - cropped.top) / 2; + cropped.top = mid - half; + cropped.bottom = mid + half; + } + } + + private void drawShadow(Canvas canvas, float left, float top, float right, float bottom) { + canvas.save(); + canvas.clipRect(left, top, right, bottom); + canvas.drawARGB(SHADOW_ALPHA, 0, 0, 0); + canvas.restore(); + } + + private void drawIndicator(Canvas canvas, Drawable indicator, 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); + } + + @Override + protected void setActionDown(float x, float y) { + super.setActionDown(x, y); + detectMovingEdges(x, y); + if (movingEdges == 0) { + mPrevOffsetX = mCropOffsetX; + mPrevOffsetY = mCropOffsetY; + } + } + + @Override + protected void setActionMove(float x, float y) { + if (movingEdges != 0) { + moveEdges(x - mCurrentX, y - mCurrentY); + } else { + float dx = x - mTouchCenterX; + float dy = y - mTouchCenterY; + mCropOffsetX = dx + mPrevOffsetX; + mCropOffsetY = dy + mPrevOffsetY; + } + super.setActionMove(x, y); + } + + @Override + protected void gainedVisibility() { + setCropBounds(getLocalCropBounds()); + } + + @Override + protected void lostVisibility() { + } + + @Override + protected void drawShape(Canvas canvas, Bitmap image) { + gPaint.setAntiAlias(true); + gPaint.setFilterBitmap(true); + gPaint.setDither(true); + gPaint.setARGB(255, 255, 255, 255); + + drawRegularFlippedBitmap(canvas, image, gPaint); + + RectF displayRect = getLocalDisplayBounds(); + float dWidth = displayRect.width(); + float dHeight = displayRect.height(); + RectF boundsRect = getCropBoundsDisplayed(); + gPaint.setARGB(128, 0, 0, 0); + gPaint.setStyle(Paint.Style.FILL); + // TODO: move this to style when refactoring + canvas.drawRect(0, 0, dWidth, boundsRect.top, gPaint); + canvas.drawRect(0, boundsRect.bottom, dWidth, dHeight, gPaint); + canvas.drawRect(0, boundsRect.top, boundsRect.left, boundsRect.bottom, + gPaint); + canvas.drawRect(boundsRect.right, boundsRect.top, dWidth, + boundsRect.bottom, gPaint); + + Path path = new Path(); + path.addRect(boundsRect, Path.Direction.CCW); + gPaint.setARGB(255, 255, 255, 255); + gPaint.setStrokeWidth(3); + gPaint.setStyle(Paint.Style.STROKE); + + canvas.drawPath(path, gPaint); + + boolean notMoving = movingEdges == 0; + if (((movingEdges & MOVE_TOP) != 0) || notMoving) { + drawIndicator(canvas, cropIndicator, boundsRect.centerX(), boundsRect.top); + } + if (((movingEdges & MOVE_BOTTOM) != 0) || notMoving) { + drawIndicator(canvas, cropIndicator, boundsRect.centerX(), boundsRect.bottom); + } + if (((movingEdges & MOVE_LEFT) != 0) || notMoving) { + drawIndicator(canvas, cropIndicator, boundsRect.left, boundsRect.centerY()); + } + if (((movingEdges & MOVE_RIGHT) != 0) || notMoving) { + drawIndicator(canvas, cropIndicator, boundsRect.right, boundsRect.centerY()); + } + } + +} diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageFlip.java b/src/com/android/gallery3d/filtershow/imageshow/ImageFlip.java new file mode 100644 index 000000000..ba01d888a --- /dev/null +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageFlip.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2012 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.imageshow; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.RectF; +import android.util.AttributeSet; + +import com.android.gallery3d.filtershow.imageshow.GeometryMetadata.FLIP; + +public class ImageFlip extends ImageGeometry { + + private static final Paint gPaint = new Paint(); + private static final float MIN_FLICK_DIST_FOR_FLIP = 0.2f; + private static final String LOGTAG = "ImageFlip"; + private FLIP mNextFlip = FLIP.NONE; + + public ImageFlip(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ImageFlip(Context context) { + super(context); + } + + @Override + protected void setActionDown(float x, float y) { + super.setActionDown(x, y); + } + + @Override + protected void setActionMove(float x, float y) { + super.setActionMove(x, y); + + float diffx = mTouchCenterX - x; + float diffy = mTouchCenterY - y; + float flick = getScaledMinFlick(); + if (Math.abs(diffx) >= flick) { + // flick moving left/right + FLIP flip = getLocalFlip(); + switch (flip) { + case NONE: + flip = FLIP.HORIZONTAL; + break; + case HORIZONTAL: + flip = FLIP.NONE; + break; + case VERTICAL: + flip = FLIP.BOTH; + break; + case BOTH: + flip = FLIP.VERTICAL; + break; + default: + flip = FLIP.NONE; + break; + } + mNextFlip = flip; + } + if (Math.abs(diffy) >= flick) { + // flick moving up/down + FLIP flip = getLocalFlip(); + switch (flip) { + case NONE: + flip = FLIP.VERTICAL; + break; + case VERTICAL: + flip = FLIP.NONE; + break; + case HORIZONTAL: + flip = FLIP.BOTH; + break; + case BOTH: + flip = FLIP.HORIZONTAL; + break; + default: + flip = FLIP.NONE; + break; + } + mNextFlip = flip; + } + } + + @Override + protected void setActionUp() { + super.setActionUp(); + setLocalFlip(mNextFlip); + } + + @Override + public void resetParameter() { + super.resetParameter(); + mNextFlip = FLIP.NONE; + } + + private float getScaledMinFlick() { + RectF disp = getLocalDisplayBounds(); + float scaled = Math.min(disp.width(), disp.height()) * MIN_FLICK_DIST_FOR_FLIP + / getLocalScale(); + return scaled; + } + + @Override + protected void drawShape(Canvas canvas, Bitmap image) { + gPaint.setAntiAlias(true); + gPaint.setFilterBitmap(true); + gPaint.setDither(true); + gPaint.setARGB(255, 255, 255, 255); + + FLIP flip = mNextFlip; + canvas.save(); + float zoom = getLocalScale(); + canvas.rotate(getTotalLocalRotation(), mCenterX, mCenterY); + canvas.scale(zoom, zoom, mCenterX, mCenterY); + canvas.translate(mXOffset, mYOffset); + if (flip == FLIP.HORIZONTAL) { + Matrix flipper = getHorizontalMatrix(image.getWidth()); + canvas.drawBitmap(image, flipper, gPaint); + } else if (flip == FLIP.VERTICAL) { + Matrix flipper = getVerticalMatrix(image.getHeight()); + canvas.drawBitmap(image, flipper, gPaint); + } else if (flip == FLIP.BOTH) { + Matrix flipper = getVerticalMatrix(image.getHeight()); + flipper.postConcat(getHorizontalMatrix(image.getWidth())); + canvas.drawBitmap(image, flipper, gPaint); + } else { + canvas.drawBitmap(image, 0, 0, gPaint); + } + canvas.restore(); + + RectF cropBounds = getCropBoundsDisplayed(getLocalCropBounds()); + + Matrix m0 = new Matrix(); + m0.setRotate(getLocalRotation(), mCenterX, mCenterY); + float[] corners = getCornersFromRect(cropBounds); + m0.mapPoints(corners); + gPaint.setARGB(255, 255, 255, 255); + // TODO: pull out style to xml + gPaint.setStrokeWidth(3); + gPaint.setStyle(Paint.Style.STROKE); + drawClosedPath(canvas, gPaint, corners); + + canvas.save(); + canvas.rotate(getLocalRotation(), mCenterX, mCenterY); + RectF displayRect = getLocalDisplayBounds(); + float dWidth = displayRect.width(); + float dHeight = displayRect.height(); + RectF boundsRect = cropBounds; + gPaint.setARGB(128, 0, 0, 0); + gPaint.setStyle(Paint.Style.FILL); + canvas.drawRect(0, 0, dWidth, boundsRect.top, gPaint); + canvas.drawRect(0, boundsRect.bottom, dWidth, dHeight, gPaint); + canvas.drawRect(0, boundsRect.top, boundsRect.left, boundsRect.bottom, + gPaint); + canvas.drawRect(boundsRect.right, boundsRect.top, dWidth, + boundsRect.bottom, gPaint); + canvas.rotate(-getLocalRotation(), mCenterX, mCenterY); + canvas.restore(); + } +} diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java b/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java new file mode 100644 index 000000000..c6709e81f --- /dev/null +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java @@ -0,0 +1,559 @@ +/* + * Copyright (C) 2012 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.imageshow; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; + +import com.android.gallery3d.filtershow.imageshow.GeometryMetadata.FLIP; +import com.android.gallery3d.filtershow.presets.ImagePreset; + +public abstract class ImageGeometry extends ImageSlave { + private boolean mVisibilityGained = false; + private boolean mHasDrawn = false; + + protected static final float MAX_STRAIGHTEN_ANGLE = 45; + protected static final float MIN_STRAIGHTEN_ANGLE = -45; + + protected float mCenterX; + protected float mCenterY; + + protected float mCurrentX; + protected float mCurrentY; + protected float mTouchCenterX; + protected float mTouchCenterY; + + // Local geometry data + private GeometryMetadata mLocalGeoMetadata = null; + private RectF mLocalDisplayBounds = null; + protected float mXOffset = 0; + protected float mYOffset = 0; + + protected enum MODES { + NONE, DOWN, UP, MOVE + } + + protected MODES mMode = MODES.NONE; + + private static final String LOGTAG = "ImageGeometry"; + + public ImageGeometry(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ImageGeometry(Context context) { + super(context); + } + + private void setupLocalDisplayBounds(RectF b) { + mLocalDisplayBounds = b; + calculateLocalScalingFactorAndOffset(); + } + + private void calculateLocalScalingFactorAndOffset() { + if (mLocalGeoMetadata == null || mLocalDisplayBounds == null) + return; + RectF imageBounds = mLocalGeoMetadata.getPhotoBounds(); + float imageWidth = imageBounds.width(); + float imageHeight = imageBounds.height(); + float displayWidth = mLocalDisplayBounds.width(); + float displayHeight = mLocalDisplayBounds.height(); + + mCenterX = displayWidth / 2; + mCenterY = displayHeight / 2; + float zoom = displayWidth / imageWidth; + if (imageHeight > imageWidth) { + zoom = displayHeight / imageHeight; + } + mYOffset = (displayHeight - imageHeight) / 2.0f; + mXOffset = (displayWidth - imageWidth) / 2.0f; + mLocalGeoMetadata.setScaleFactor(zoom); + } + + @Override + public void resetParameter() { + super.resetParameter(); + setLocalRotation(0); + setLocalStraighten(0); + setLocalCropBounds(getLocalPhotoBounds()); + setLocalFlip(FLIP.NONE); + saveAndSetPreset(); + invalidate(); + } + + private GeometryMetadata getMasterImageGeometryMetadataCopy() { + return getMaster().getGeometry(); + } + + // Overwrites local with master + protected void syncLocalToMasterGeometry() { + mLocalGeoMetadata = getMasterImageGeometryMetadataCopy(); + calculateLocalScalingFactorAndOffset(); + } + + protected RectF getLocalPhotoBounds() { + return mLocalGeoMetadata.getPhotoBounds(); + } + + protected RectF getLocalCropBounds() { + return mLocalGeoMetadata.getCropBounds(); + } + + protected RectF getLocalDisplayBounds() { + return new RectF(mLocalDisplayBounds); + } + + protected float getLocalScale() { + return mLocalGeoMetadata.getScaleFactor(); + } + + protected float getLocalRotation() { + return mLocalGeoMetadata.getRotation(); + } + + protected float getLocalStraighten() { + return mLocalGeoMetadata.getStraightenRotation(); + } + + protected void setLocalScale(float s) { + mLocalGeoMetadata.setScaleFactor(s); + } + + protected void setLocalRotation(float r) { + mLocalGeoMetadata.setRotation(r); + } + + protected void setLocalStraighten(float r) { + mLocalGeoMetadata.setStraightenRotation(r); + } + + protected void setLocalCropBounds(RectF c) { + mLocalGeoMetadata.setCropBounds(c); + } + + protected FLIP getLocalFlip() { + return mLocalGeoMetadata.getFlipType(); + } + + protected void setLocalFlip(FLIP flip) { + mLocalGeoMetadata.setFlipType(flip); + } + + protected float getTotalLocalRotation() { + return getLocalRotation() + getLocalStraighten(); + } + + protected Bitmap getMasterImage() { + if (getMaster() == null) + return null; + return getMaster().mForegroundImage; + } + + protected static Matrix getHorizontalMatrix(int width) { + Matrix flipHorizontalMatrix = new Matrix(); + flipHorizontalMatrix.setScale(-1, 1); + flipHorizontalMatrix.postTranslate(width, 0); + return flipHorizontalMatrix; + } + + protected static Matrix getVerticalMatrix(int height) { + Matrix flipVerticalMatrix = new Matrix(); + flipVerticalMatrix.setScale(1, -1); + flipVerticalMatrix.postTranslate(0, height); + return flipVerticalMatrix; + } + + protected static Matrix getFlipMatrix(FLIP type, int width, int height) { + if (type == FLIP.HORIZONTAL) { + return getHorizontalMatrix(width); + } else if (type == FLIP.VERTICAL) { + return getVerticalMatrix(height); + } else if (type == FLIP.BOTH) { + Matrix flipper = getVerticalMatrix(height); + flipper.postConcat(getHorizontalMatrix(width)); + return flipper; + } else { + Matrix m = new Matrix(); + m.reset(); // identity + return m; + } + } + + protected static float clamp(float i, float low, float high) { + return Math.max(Math.min(i, high), low); + } + + protected static float[] getCornersFromRect(RectF r) { + // Order is: + // 0------->1 + // ^ | + // | v + // 3<-------2 + float[] corners = { + r.left, r.top, // 0 + r.right, r.top, // 1 + r.right, r.bottom,// 2 + r.left, r.bottom + };// 3 + return corners; + } + + // Returns maximal rectangular crop bound that still fits within + // the image bound after the image has been rotated. + protected static RectF findCropBoundForRotatedImg(RectF cropBound, + RectF imageBound, + float rotation, + float centerX, + float centerY) { + Matrix m = new Matrix(); + float[] cropEdges = getCornersFromRect(cropBound); + m.setRotate(rotation, centerX, centerY); + Matrix m0 = new Matrix(); + if (!m.invert(m0)) + return null; + m0.mapPoints(cropEdges); + getEdgePoints(imageBound, cropEdges); + m.mapPoints(cropEdges); + return trapToRect(cropEdges); + } + + // If edge point [x, y] in array [x0, y0, x1, y1, ...] is outside of the + // image bound rectangle, clamps it to the edge of the rectangle. + protected static void getEdgePoints(RectF imageBound, float[] array) { + if (array.length < 2) + return; + for (int x = 0; x < array.length; x += 2) { + array[x] = clamp(array[x], imageBound.left, imageBound.right); + array[x + 1] = clamp(array[x + 1], imageBound.top, imageBound.bottom); + } + } + + protected static RectF trapToRect(float[] array) { + float dx0 = array[4] - array[0]; + float dy0 = array[5] - array[1]; + float dx1 = array[6] - array[2]; + float dy1 = array[7] - array[3]; + float l0 = dx0 * dx0 + dy0 * dy0; + float l1 = dx1 * dx1 + dy1 * dy1; + if (l0 > l1) { + RectF n = new RectF(array[2], array[3], array[6], array[7]); + n.sort(); + return n; + } else { + RectF n = new RectF(array[0], array[1], array[4], array[5]); + n.sort(); + return n; + } + } + + protected static Path drawClosedPath(Canvas canvas, Paint paint, float[] points) { + Path crop = new Path(); + crop.moveTo(points[0], points[1]); + crop.lineTo(points[2], points[3]); + crop.lineTo(points[4], points[5]); + crop.lineTo(points[6], points[7]); + crop.close(); + canvas.drawPath(crop, paint); + return crop; + } + + protected static float[] shortestVectorFromPointToLine(float[] point, float[] l1, float[] l2) { + float x1 = l1[0]; + float x2 = l2[0]; + float y1 = l1[1]; + float y2 = l2[1]; + float xdelt = x2 - x1; + float ydelt = y2 - y1; + if (xdelt == 0 && ydelt == 0) + return null; + float u = ((point[0] - x1) * xdelt + (point[1] - y1) * ydelt) + / (xdelt * xdelt + ydelt * ydelt); + float[] ret = { + (x1 + u * (x2 - x1)), (y1 + u * (y2 - y1)) + }; + return ret; + } + + protected static void fixAspectRatio(RectF r, float w, float h) { + float scale = Math.min(r.width() / w, r.height() / h); + r.set(r.left, r.top, scale * w, scale * h); + } + + protected static float getNewHeightForWidthAspect(float width, float w, float h) { + return width * h / w; + } + + protected static float getNewWidthForHeightAspect(float height, float w, float h) { + return height * w / h; + } + + @Override + protected void onVisibilityChanged(View changedView, int visibility) { + super.onVisibilityChanged(changedView, visibility); + if (visibility == View.VISIBLE) { + mVisibilityGained = true; + syncLocalToMasterGeometry(); + gainedVisibility(); + } else { + if (mVisibilityGained == true && mHasDrawn == true) { + lostVisibility(); + } + mVisibilityGained = false; + mHasDrawn = false; + } + } + + protected void gainedVisibility() { + // TODO: Override this stub. + } + + protected void lostVisibility() { + // TODO: Override this stub. + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + setupLocalDisplayBounds(new RectF(0, 0, w, h)); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getActionMasked()) { + case (MotionEvent.ACTION_DOWN): + setActionDown(event.getX(), event.getY()); + break; + case (MotionEvent.ACTION_UP): + setActionUp(); + saveAndSetPreset(); + break; + case (MotionEvent.ACTION_MOVE): + setActionMove(event.getX(), event.getY()); + break; + default: + setNoAction(); + } + if (getPanelController() != null) { + getPanelController().onNewValue((int) getLocalValue()); + } + invalidate(); + return true; + } + + protected int getLocalValue() { + return 0; // TODO: Override this + } + + protected void setActionDown(float x, float y) { + mTouchCenterX = x; + mTouchCenterY = y; + mCurrentX = x; + mCurrentY = y; + mMode = MODES.DOWN; + } + + protected void setActionMove(float x, float y) { + mCurrentX = x; + mCurrentY = y; + mMode = MODES.MOVE; + } + + protected void setActionUp() { + mMode = MODES.UP; + } + + protected void setNoAction() { + mMode = MODES.NONE; + } + + @Override + public boolean showTitle() { + return false; + } + + protected void saveAndSetPreset() { + ImagePreset copy = new ImagePreset(getImagePreset()); + copy.setGeometry(mLocalGeoMetadata); + setImagePreset(copy); + } + + @Override + public void updateImage() { + } + + protected void drawRegularFlippedBitmap(Canvas canvas, Bitmap image, Paint p) { + float zoom = getLocalScale(); + canvas.save(); + canvas.rotate(getTotalLocalRotation(), mCenterX, mCenterY); + canvas.scale(zoom, zoom, mCenterX, mCenterY); + canvas.translate(mXOffset, mYOffset); + Matrix flipper = getFlipMatrix(getLocalFlip(), image.getWidth(), image.getHeight()); + canvas.drawBitmap(image, flipper, p); + canvas.restore(); + } + + protected void drawRegularBitmap(Canvas canvas, Bitmap image, Paint p) { + float zoom = getLocalScale(); + canvas.save(); + canvas.rotate(getTotalLocalRotation(), mCenterX, mCenterY); + canvas.scale(zoom, zoom, mCenterX, mCenterY); + canvas.translate(mXOffset, mYOffset); + canvas.drawBitmap(image, 0, 0, p); + canvas.restore(); + } + + protected RectF getCropBoundsDisplayed() { + return getCropBoundsDisplayed(getLocalCropBounds()); + } + + protected RectF getCropBoundsDisplayed(RectF bounds) { + RectF crop = new RectF(bounds); + Matrix m = new Matrix(); + float zoom = getLocalScale(); + m.setScale(zoom, zoom, mCenterX, mCenterY); + m.preTranslate(mXOffset, mYOffset); + m.mapRect(crop); + return crop; + } + + protected static float[] correctAngles(float rotation, float straighten) { + float[] ret = { + rotation, straighten + }; + if (straighten >= MIN_STRAIGHTEN_ANGLE && straighten <= MAX_STRAIGHTEN_ANGLE + && (rotation % 90 == 0)) { + return ret; + } + float remainder = rotation % 90; + float newRot = (int) (rotation / 90); + float tempStraighten = straighten + remainder; + newRot += (int) (tempStraighten / 90); + tempStraighten %= 90; + if (tempStraighten > MAX_STRAIGHTEN_ANGLE) { + tempStraighten -= 90; + newRot += 1; + } else if (tempStraighten < MIN_STRAIGHTEN_ANGLE) { + tempStraighten += 90; + newRot -= 1; + } + ret[0] = newRot * 90; + ret[1] = tempStraighten; + return ret; + } + + protected void correctStraightenRotateAngles() { + float straighten = getLocalStraighten(); + float rotation = getLocalRotation(); + float[] angles = correctAngles(rotation, straighten); + setLocalStraighten(angles[1]); + setLocalRotation(angles[0]); + } + + protected static Matrix getCropRotationMatrix(float rotation) { + Matrix m = new Matrix(); + m.setRotate(rotation); + if (!m.rectStaysRect()) { + return null; + } + return m; + } + + protected RectF getStraightenCropBounds() { + RectF imageRect = getLocalPhotoBounds(); + Matrix m = new Matrix(); + float lRot = getLocalRotation(); + m.setRotate(lRot); + if (!m.rectStaysRect()) { + correctStraightenRotateAngles(); + m.setRotate(getLocalRotation()); + } + RectF crop = getStraightenCropBounds(imageRect, getLocalStraighten()); + setLocalCropBounds(crop); + if (!m.mapRect(crop)) { + return null; + } + return crop; + } + + protected RectF getStraightenCropBounds(RectF imageRect, float straightenAngle) { + float deg = straightenAngle; + if (deg < 0) { + deg = -deg; + } + double a = Math.toRadians(deg); + double sina = Math.sin(a); + double cosa = Math.cos(a); + + double rw = imageRect.width(); + double rh = imageRect.height(); + double h1 = rh * rh / (rw * sina + rh * cosa); + double h2 = rh * rw / (rw * cosa + rh * sina); + double hh = Math.min(h1, h2); + double ww = hh * rw / rh; + + float left = (float) ((rw - ww) * 0.5f); + float top = (float) ((rh - hh) * 0.5f); + float right = (float) (left + ww); + float bottom = (float) (top + hh); + + RectF boundsRect = new RectF(left, top, right, bottom); + RectF nonRotateImage = getLocalPhotoBounds(); + Matrix m1 = new Matrix(); + m1.setTranslate(nonRotateImage.centerX() - boundsRect.centerX(), nonRotateImage.centerY() + - boundsRect.centerY()); + m1.mapRect(boundsRect); + return boundsRect; + } + + protected void drawShadows(Canvas canvas, RectF innerBounds, RectF outerBounds, Paint p) { + float dWidth = outerBounds.width(); + float dHeight = outerBounds.height(); + canvas.drawRect(0, 0, dWidth, innerBounds.top, p); + canvas.drawRect(0, innerBounds.bottom, dWidth, dHeight, p); + canvas.drawRect(0, innerBounds.top, innerBounds.left, innerBounds.bottom, + p); + canvas.drawRect(innerBounds.right, innerBounds.top, dWidth, + innerBounds.bottom, p); + } + + @Override + public void onDraw(Canvas canvas) { + if (getDirtyGeometryFlag()) { + syncLocalToMasterGeometry(); + clearDirtyGeometryFlag(); + } + Bitmap image = getMasterImage(); + if (image == null) { + return; + } + mHasDrawn = true; + drawShape(canvas, image); + } + + protected void drawShape(Canvas canvas, Bitmap image) { + // TODO: Override this stub. + } +} diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageRotate.java b/src/com/android/gallery3d/filtershow/imageshow/ImageRotate.java new file mode 100644 index 000000000..c0999e3d0 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageRotate.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2012 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.imageshow; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.util.Log; + +public class ImageRotate extends ImageGeometry { + private static final float MATH_PI = (float) Math.PI; + + private float mBaseAngle = 0; + private float mAngle = 0; + + private RectF mLocalBoundsCopy = null; + private boolean mSnapToNinety = true; + private static final String LOGTAG = "ImageRotate"; + + public ImageRotate(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ImageRotate(Context context) { + super(context); + } + + private float angleFor(float dx, float dy) { + return (float) (Math.atan2(dx, dy) * 180 / MATH_PI); + } + + private int snappedAngle(float angle) { + float remainder = angle % 90; + int current = (int) (angle / 90); // truncates + if (remainder < -45) { + --current; + } else if (remainder > 45) { + ++current; + } + return current * 90; + } + + private static final Paint gPaint = new Paint(); + + private void computeValue() { + if (mCurrentX == mTouchCenterX && mCurrentY == mTouchCenterY) { + return; + } + float dX1 = mTouchCenterX - mCenterX; + float dY1 = mTouchCenterY - mCenterY; + float dX2 = mCurrentX - mCenterX; + float dY2 = mCurrentY - mCenterY; + + float angleA = angleFor(dX1, dY1); + float angleB = angleFor(dX2, dY2); + float angle = (angleB - angleA) % 360; + mAngle = (mBaseAngle - angle) % 360; + } + + @Override + protected void setActionDown(float x, float y) { + super.setActionDown(x, y); + mBaseAngle = mAngle = getLocalRotation(); + } + + @Override + protected void setActionMove(float x, float y) { + super.setActionMove(x, y); + computeValue(); + setLocalRotation(mAngle % 360); + } + + @Override + protected void setActionUp() { + super.setActionUp(); + if (mSnapToNinety) { + setLocalRotation(snappedAngle(mAngle % 360)); + } + } + + @Override + public void resetParameter() { + super.resetParameter(); + mLocalBoundsCopy = getLocalCropBounds(); + } + + @Override + protected void gainedVisibility() { + mLocalBoundsCopy = getLocalCropBounds(); + } + + @Override + protected void lostVisibility() { + } + + @Override + protected int getLocalValue() { + return (int) getLocalRotation(); + } + + @Override + protected void drawShape(Canvas canvas, Bitmap image) { + gPaint.setAntiAlias(true); + gPaint.setFilterBitmap(true); + gPaint.setDither(true); + gPaint.setARGB(255, 255, 255, 255); + + drawRegularFlippedBitmap(canvas, image, gPaint); + + RectF cropBounds = getCropBoundsDisplayed(mLocalBoundsCopy); + + Matrix m0 = new Matrix(); + m0.setRotate(getLocalRotation(), mCenterX, mCenterY); + float[] corners = getCornersFromRect(cropBounds); + m0.mapPoints(corners); + + gPaint.setARGB(255, 255, 255, 255); + gPaint.setStrokeWidth(3); + gPaint.setStyle(Paint.Style.STROKE); + drawClosedPath(canvas, gPaint, corners); + + canvas.save(); + canvas.rotate(getLocalRotation(), mCenterX, mCenterY); + RectF displayRect = getLocalDisplayBounds(); + float dWidth = displayRect.width(); + float dHeight = displayRect.height(); + RectF boundsRect = cropBounds; + gPaint.setARGB(128, 0, 0, 0); + // TODO: move style to xml + gPaint.setStyle(Paint.Style.FILL); + canvas.drawRect(0, 0, dWidth, boundsRect.top, gPaint); + canvas.drawRect(0, boundsRect.bottom, dWidth, dHeight, gPaint); + canvas.drawRect(0, boundsRect.top, boundsRect.left, boundsRect.bottom, + gPaint); + canvas.drawRect(boundsRect.right, boundsRect.top, dWidth, + boundsRect.bottom, gPaint); + canvas.rotate(-getLocalRotation(), mCenterX, mCenterY); + canvas.restore(); + + } + +} diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java index da38d3b7e..d6d3793ce 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java @@ -1,3 +1,18 @@ +/* + * Copyright (C) 2012 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.imageshow; @@ -6,6 +21,7 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; +import android.graphics.RectF; import android.os.Handler; import android.util.AttributeSet; import android.view.MotionEvent; @@ -38,6 +54,7 @@ public class ImageShow extends View implements SliderListener, OnSeekBarChangeLi protected ImagePreset mImagePreset = null; protected ImageLoader mImageLoader = null; private ImageFilter mCurrentFilter = null; + private boolean mDirtyGeometry = true; private Bitmap mBackgroundImage = null; protected Bitmap mForegroundImage = null; @@ -48,9 +65,14 @@ public class ImageShow extends View implements SliderListener, OnSeekBarChangeLi private HistoryAdapter mHistoryAdapter = null; private ImageStateAdapter mImageStateAdapter = null; - protected Rect mImageBounds = null; - protected float mImageRotation = 0; - protected float mImageRotationZoomFactor = 0; + protected GeometryMetadata getGeometry(){ + return new GeometryMetadata(mImagePreset.mGeoData); + } + + public void setGeometry(GeometryMetadata d){ + mImagePreset.mGeoData.set(d); + } + private boolean mShowControls = false; private boolean mShowOriginal = false; @@ -190,7 +212,9 @@ public class ImageShow extends View implements SliderListener, OnSeekBarChangeLi } public Rect getImageBounds() { - return mImageBounds; + Rect dst = new Rect(); + mImagePreset.mGeoData.getPhotoBounds().roundOut(dst); + return dst; } public ImagePreset getImagePreset() { @@ -284,7 +308,6 @@ public class ImageShow extends View implements SliderListener, OnSeekBarChangeLi float h = w / ratio; float ty = (getHeight() - h) / 2.0f; float tx = 0; - // t = 0; if (ratio < 1.0f) { // portrait image h = getHeight(); w = h * ratio; @@ -293,7 +316,6 @@ public class ImageShow extends View implements SliderListener, OnSeekBarChangeLi } Rect d = new Rect((int) tx, (int) ty, (int) (w + tx), (int) (h + ty)); - mImageBounds = d; canvas.drawBitmap(image, s, d, mPaint); } @@ -361,18 +383,33 @@ public class ImageShow extends View implements SliderListener, OnSeekBarChangeLi } } + protected void setDirtyGeometryFlag(){ + mDirtyGeometry = true; + } + + protected void clearDirtyGeometryFlag(){ + mDirtyGeometry = false; + } + + protected boolean getDirtyGeometryFlag() { + return mDirtyGeometry; + } + + private void imageSizeChanged(Bitmap image) { + if(image == null || mImagePreset == null) + return; + float w = image.getWidth(); + float h = image.getHeight(); + RectF r = new RectF(0, 0, w, h); + RectF c = new RectF(w/4f, h/4f, w * 3/4f, h * 3/4f); + mImagePreset.mGeoData.setPhotoBounds(r); + mImagePreset.mGeoData.setCropBounds(c); + setDirtyGeometryFlag(); + } + public void updateImage() { mForegroundImage = getOriginalFrontBitmap(); - /* - * if (mImageLoader != null) { - * mImageLoader.resetImageForPreset(getImagePreset(), this); } - */ - - /* - * if (mForegroundImage != null) { Bitmap filteredImage = - * mForegroundImage.copy(mConfig, true); - * getImagePreset().apply(filteredImage); invalidate(); } - */ + imageSizeChanged(mForegroundImage); // TODO: should change to filtered } public void updateFilteredImage(Bitmap bitmap) { @@ -413,20 +450,29 @@ public class ImageShow extends View implements SliderListener, OnSeekBarChangeLi } public float getImageRotation() { - return mImageRotation; + return mImagePreset.mGeoData.getRotation(); } public float getImageRotationZoomFactor() { - return mImageRotationZoomFactor; + return mImagePreset.mGeoData.getScaleFactor(); + } + + public void setImageRotation(float r){ + mImagePreset.mGeoData.setRotation(r); + } + + public void setImageRotationZoomFactor(float f){ + mImagePreset.mGeoData.setScaleFactor(f); } public void setImageRotation(float imageRotation, float imageRotationZoomFactor) { - if (imageRotation != mImageRotation) { + float r = getImageRotation(); + if (imageRotation != r) { invalidate(); } - mImageRotation = imageRotation; - mImageRotationZoomFactor = imageRotationZoomFactor; + setImageRotation(imageRotation); + setImageRotationZoomFactor(imageRotationZoomFactor); } @Override diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageSlave.java b/src/com/android/gallery3d/filtershow/imageshow/ImageSlave.java index 4fdf8303a..f320a0341 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageSlave.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageSlave.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2012 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.imageshow; import android.content.Context; @@ -47,10 +63,6 @@ public class ImageSlave extends ImageShow { return mMasterImageShow.getCurrentFilter(); } - public void updateAngle() { - mMasterImageShow.setImageRotation(mImageRotation, mImageRotationZoomFactor); - } - @Override public boolean showTitle() { return false; @@ -81,5 +93,4 @@ public class ImageSlave extends ImageShow { return mMasterImageShow.getPanelController(); } - } diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java b/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java index 99aa389ac..2b15bf373 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java @@ -1,3 +1,18 @@ +/* + * Copyright (C) 2012 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.imageshow; @@ -13,26 +28,10 @@ import android.view.MotionEvent; import com.android.gallery3d.filtershow.presets.ImagePreset; -public class ImageStraighten extends ImageSlave { - private float mImageRotation = 0; - private float mImageRotationZoomFactor = 0; +public class ImageStraighten extends ImageGeometry { - private final float mMinAngle = -45; - private final float mMaxAngle = 45; private float mBaseAngle = 0; private float mAngle = 0; - private float mCenterX; - private float mCenterY; - private float mTouchCenterX; - private float mTouchCenterY; - private float mCurrentX; - private float mCurrentY; - - private enum MODES { - NONE, DOWN, UP, MOVE - } - - private MODES mMode = MODES.NONE; private static final String LOGTAG = "ImageStraighten"; private static final Paint gPaint = new Paint(); @@ -45,72 +44,17 @@ public class ImageStraighten extends ImageSlave { super(context, attrs); } - // /////////////////////////////////////////////////////////////////////////// - // touch event handler - - public void setActionDown(float x, float y) { - mTouchCenterX = x; - mTouchCenterY = y; - mCurrentX = x; - mCurrentY = y; - mBaseAngle = mAngle; - mMode = MODES.DOWN; - } - - public void setActionMove(float x, float y) { - mCurrentX = x; - mCurrentY = y; - mMode = MODES.MOVE; - computeValue(); - } - - public void setActionUp() { - mMode = MODES.UP; - updatePreset(); - invalidate(); - } - - public void setNoAction() { - mMode = MODES.NONE; - } - - private void updatePreset() { - ImagePreset copy = new ImagePreset(getImagePreset()); - copy.setStraightenRotation(mImageRotation, mImageRotationZoomFactor); - setImagePreset(copy); - } - @Override - public void resetParameter() { - super.resetParameter(); - mImageRotation = 0; - mAngle = 0; - updatePreset(); - invalidate(); + protected void setActionDown(float x, float y) { + super.setActionDown(x, y); + mBaseAngle = mAngle = getLocalStraighten(); } @Override - public boolean onTouchEvent(MotionEvent event) { - switch (event.getActionMasked()) { - case (MotionEvent.ACTION_DOWN): - setActionDown(event.getX(), event.getY()); - break; - case (MotionEvent.ACTION_UP): - setActionUp(); - break; - case (MotionEvent.ACTION_MOVE): - setActionMove(event.getX(), event.getY()); - break; - default: - setNoAction(); - } - mImageRotation = mAngle; - updateAngle(); - if (getPanelController() != null) { - getPanelController().onNewValue((int) mImageRotation); - } - invalidate(); - return true; + protected void setActionMove(float x, float y) { + super.setActionMove(x, y); + computeValue(); + setLocalStraighten(mAngle); } private float angleFor(float dx, float dy) { @@ -130,122 +74,91 @@ public class ImageStraighten extends ImageSlave { float angleB = angleFor(dX2, dY2); float angle = (angleB - angleA) % 360; mAngle = (mBaseAngle - angle) % 360; - mAngle = Math.max(mMinAngle, mAngle); - mAngle = Math.min(mMaxAngle, mAngle); + mAngle = Math.max(MIN_STRAIGHTEN_ANGLE, mAngle); + mAngle = Math.min(MAX_STRAIGHTEN_ANGLE, mAngle); } - // /////////////////////////////////////////////////////////////////////////// + @Override + protected void gainedVisibility() { + correctStraightenRotateAngles(); + } + + @Override + protected void lostVisibility() { + saveAndSetPreset(); + } @Override public void onNewValue(int value) { - mImageRotation = value; + setLocalStraighten(clamp(value, MIN_STRAIGHTEN_ANGLE, MAX_STRAIGHTEN_ANGLE)); if (getPanelController() != null) { - getPanelController().onNewValue(value); + getPanelController().onNewValue((int) getLocalStraighten()); } invalidate(); } @Override - public void onDraw(Canvas canvas) { - mCenterX = getWidth() / 2; - mCenterY = getHeight() / 2; - drawStraighten(canvas); + protected int getLocalValue() { + return (int) getLocalStraighten(); } - public void drawStraighten(Canvas canvas) { + @Override + protected void drawShape(Canvas canvas, Bitmap image) { gPaint.setAntiAlias(true); gPaint.setFilterBitmap(true); gPaint.setDither(true); gPaint.setARGB(255, 255, 255, 255); - // canvas.drawARGB(255, 255, 0, 0); + // Draw fully rotated image. + drawRegularFlippedBitmap(canvas, image, gPaint); - // TODO: have the concept of multiple image passes (geometry, filter) - // so that we can fake the rotation, etc. - Bitmap image = null; // mMasterImageShow.mFilteredImage; - if (image == null) { - image = getMaster().mForegroundImage; - } - if (image == null) { - return; - } + // Get cropping frame + RectF boundsRect = getStraightenCropBounds(); - double iw = image.getWidth(); - float zoom = (float) (getWidth() / iw); - // iw = getWidth(); // we will apply the zoom - double ih = image.getHeight(); - if (ih > iw) { - zoom = (float) (getHeight() / ih); - } - float offset = (float) ((getHeight() - ih) / 2.0f); - - canvas.save(); - float dx = (float) ((getWidth() - iw) / 2.0f); - float dy = offset; - - canvas.rotate(mImageRotation, mCenterX, mCenterY); - canvas.scale(zoom, zoom, mCenterX, mCenterY); - canvas.translate(dx, dy); - canvas.drawBitmap(image, 0, 0, gPaint); - - canvas.restore(); - - double deg = mImageRotation; - if (deg < 0) { - deg = -deg; - } - double a = Math.toRadians(deg); - double sina = Math.sin(a); - double cosa = Math.cos(a); - - double rw = image.getWidth(); - double rh = image.getHeight(); - double h1 = rh * rh / (rw * sina + rh * cosa); - double h2 = rh * rw / (rw * cosa + rh * sina); - double hh = Math.min(h1, h2); - double ww = hh * rw / rh; - - float left = (float) ((rw - ww) * 0.5f); - float top = (float) ((rh - hh) * 0.5f); - float right = (float) (left + ww); - float bottom = (float) (top + hh); - - RectF boundsRect = new RectF(left, top, right, bottom); - Matrix m = new Matrix(); - m.setScale(zoom, zoom, mCenterX, mCenterY); - m.preTranslate(dx, dy); - m.mapRect(boundsRect); + Matrix m1 = new Matrix(); + float zoom = getLocalScale(); + // Center and scale + m1.setScale(zoom, zoom, mCenterX, mCenterY); + m1.preTranslate(mCenterX - boundsRect.centerX(), mCenterY - boundsRect.centerY()); + m1.mapRect(boundsRect); + RectF displayRect = getLocalDisplayBounds(); + float dWidth = displayRect.width(); + float dHeight = displayRect.height(); + // Draw shadows gPaint.setARGB(128, 0, 0, 0); gPaint.setStyle(Paint.Style.FILL); - canvas.drawRect(0, 0, getWidth(), boundsRect.top, gPaint); - canvas.drawRect(0, boundsRect.bottom, getWidth(), getHeight(), gPaint); + + // TODO: move to xml file + canvas.drawRect(0, 0, dWidth, boundsRect.top, gPaint); + canvas.drawRect(0, boundsRect.bottom, dWidth, dHeight, gPaint); canvas.drawRect(0, boundsRect.top, boundsRect.left, boundsRect.bottom, gPaint); - canvas.drawRect(boundsRect.right, boundsRect.top, getWidth(), + canvas.drawRect(boundsRect.right, boundsRect.top, dWidth, boundsRect.bottom, gPaint); + // Draw crop frame Path path = new Path(); path.addRect(boundsRect, Path.Direction.CCW); gPaint.setARGB(255, 255, 255, 255); gPaint.setStrokeWidth(3); gPaint.setStyle(Paint.Style.STROKE); canvas.drawPath(path, gPaint); - gPaint.setStyle(Paint.Style.FILL_AND_STROKE); - mImageRotationZoomFactor = (float) (rw / boundsRect.width()); + gPaint.setStyle(Paint.Style.FILL_AND_STROKE); + // Draw grid if (mMode == MODES.MOVE) { canvas.save(); canvas.clipPath(path); int n = 16; - float step = getWidth() / n; + float step = dWidth / n; float p = 0; for (int i = 1; i < n; i++) { p = i * step; gPaint.setARGB(60, 255, 255, 255); - canvas.drawLine(p, 0, p, getHeight(), gPaint); - canvas.drawLine(0, p, getWidth(), p, gPaint); + canvas.drawLine(p, 0, p, dHeight, gPaint); + canvas.drawLine(0, p, dWidth, p, gPaint); } canvas.restore(); } diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePreset.java b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java index c0c310374..be08e7090 100644 --- a/src/com/android/gallery3d/filtershow/presets/ImagePreset.java +++ b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java @@ -8,6 +8,7 @@ import android.util.Log; import com.android.gallery3d.filtershow.ImageStateAdapter; import com.android.gallery3d.filtershow.filters.ImageFilter; import com.android.gallery3d.filtershow.filters.ImageFilterStraighten; +import com.android.gallery3d.filtershow.imageshow.GeometryMetadata; import com.android.gallery3d.filtershow.imageshow.ImageShow; import java.util.Vector; @@ -22,15 +23,15 @@ public class ImagePreset { protected boolean mIsFxPreset = false; enum FullRotate { - ZERO, NINETY, HUNDRED_HEIGHTY, TWO_HUNDRED_SEVENTY + ZERO, NINETY, HUNDRED_EIGHTY, TWO_HUNDRED_SEVENTY } - protected FullRotate mFullRotate = FullRotate.ZERO; - protected float mStraightenRotate = 0; - protected float mStraightenZoom = 0; - protected boolean mHorizontalFlip = false; - protected boolean mVerticalFlip = false; - protected RectF mCrop = null; + // This is where the geometry metadata lives now. + public final GeometryMetadata mGeoData = new GeometryMetadata(); + + public void setGeometry(GeometryMetadata m) { + mGeoData.set(m); + } private float mScaleFactor = 1.0f; private boolean mIsHighQuality = false; @@ -51,32 +52,13 @@ public class ImagePreset { mHistoryName = source.name(); mIsFxPreset = source.isFx(); - mStraightenRotate = source.mStraightenRotate; - mStraightenZoom = source.mStraightenZoom; - - mScaleFactor = source.mScaleFactor; - mIsHighQuality = source.mIsHighQuality; - } - - public void setStraightenRotation(float rotate, float zoom) { - mStraightenRotate = rotate; - mStraightenZoom = zoom; + mGeoData.set(source.mGeoData); } private Bitmap applyGeometry(Bitmap original, float scaleFactor, boolean highQuality) { Bitmap bitmap = original; - if (mFullRotate != FullRotate.ZERO) { - // TODO - } - - if (mStraightenRotate != 0) { - // TODO: keep the instances around - ImageFilter straighten = new ImageFilterStraighten(mStraightenRotate, mStraightenZoom); - bitmap = straighten.apply(bitmap, scaleFactor, highQuality); - straighten = null; - } - + // TODO: put geometry filters return bitmap; } @@ -104,7 +86,8 @@ public class ImagePreset { if (!mName.equalsIgnoreCase(preset.name())) { return false; } - if (mStraightenRotate != preset.mStraightenRotate) { + + if (!mGeoData.equals(preset.mGeoData)) { return false; } for (int i = 0; i < preset.mFilters.size(); i++) { |