diff --git a/src/org/thoughtcrime/securesms/imageeditor/Bounds.java b/src/org/thoughtcrime/securesms/imageeditor/Bounds.java index 0b8faf663b..f5d4e0ca5a 100644 --- a/src/org/thoughtcrime/securesms/imageeditor/Bounds.java +++ b/src/org/thoughtcrime/securesms/imageeditor/Bounds.java @@ -1,6 +1,9 @@ package org.thoughtcrime.securesms.imageeditor; +import android.graphics.Matrix; import android.graphics.RectF; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; /** * The local extent of a {@link org.thoughtcrime.securesms.imageeditor.model.EditorElement}. @@ -21,9 +24,51 @@ public final class Bounds { public static final float[] CENTRE = new float[]{ CENTRE_X, CENTRE_Y }; + private static final float[] POINTS = { Bounds.LEFT, Bounds.TOP, + Bounds.RIGHT, Bounds.TOP, + Bounds.RIGHT, Bounds.BOTTOM, + Bounds.LEFT, Bounds.BOTTOM }; + static RectF newFullBounds() { return new RectF(LEFT, TOP, RIGHT, BOTTOM); } public static RectF FULL_BOUNDS = newFullBounds(); + + public static boolean contains(float x, float y) { + return x >= FULL_BOUNDS.left && x <= FULL_BOUNDS.right && + y >= FULL_BOUNDS.top && y <= FULL_BOUNDS.bottom; + } + + /** + * Maps all the points of bounds with the supplied matrix and determines whether they are still in bounds. + * + * @param matrix matrix to transform points by, null is treated as identity. + * @return true iff all points remain in bounds after transformation. + */ + public static boolean boundsRemainInBounds(@Nullable Matrix matrix) { + if (matrix == null) return true; + + float[] dst = new float[POINTS.length]; + + matrix.mapPoints(dst, POINTS); + + return allWithinBounds(dst); + } + + private static boolean allWithinBounds(@NonNull float[] points) { + boolean allHit = true; + + for (int i = 0; i < points.length / 2; i++) { + float x = points[2 * i]; + float y = points[2 * i + 1]; + + if (!Bounds.contains(x, y)) { + allHit = false; + break; + } + } + + return allHit; + } } diff --git a/src/org/thoughtcrime/securesms/imageeditor/EditSession.java b/src/org/thoughtcrime/securesms/imageeditor/EditSession.java index 3c79c07b45..cd883cf562 100644 --- a/src/org/thoughtcrime/securesms/imageeditor/EditSession.java +++ b/src/org/thoughtcrime/securesms/imageeditor/EditSession.java @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.imageeditor; import android.graphics.Matrix; import android.graphics.PointF; +import android.support.annotation.NonNull; import org.thoughtcrime.securesms.imageeditor.model.EditorElement; @@ -18,13 +19,13 @@ import org.thoughtcrime.securesms.imageeditor.model.EditorElement; */ interface EditSession { - void movePoint(int p, PointF point); + void movePoint(int p, @NonNull PointF point); EditorElement getSelected(); - EditSession newPoint(Matrix newInverse, PointF point, int p); + EditSession newPoint(@NonNull Matrix newInverse, @NonNull PointF point, int p); - EditSession removePoint(Matrix newInverse, int p); + EditSession removePoint(@NonNull Matrix newInverse, int p); void commit(); } diff --git a/src/org/thoughtcrime/securesms/imageeditor/ElementDragEditSession.java b/src/org/thoughtcrime/securesms/imageeditor/ElementDragEditSession.java index 732175104c..6f8e053563 100644 --- a/src/org/thoughtcrime/securesms/imageeditor/ElementDragEditSession.java +++ b/src/org/thoughtcrime/securesms/imageeditor/ElementDragEditSession.java @@ -31,12 +31,12 @@ final class ElementDragEditSession extends ElementEditSession { } @Override - public EditSession newPoint(Matrix newInverse, PointF point, int p) { + public EditSession newPoint(@NonNull Matrix newInverse, PointF point, int p) { return ElementScaleEditSession.startScale(this, newInverse, point, p); } @Override - public EditSession removePoint(Matrix newInverse, int p) { + public EditSession removePoint(@NonNull Matrix newInverse, int p) { return this; } } diff --git a/src/org/thoughtcrime/securesms/imageeditor/ElementEditSession.java b/src/org/thoughtcrime/securesms/imageeditor/ElementEditSession.java index cc37ddb587..4dd7221121 100644 --- a/src/org/thoughtcrime/securesms/imageeditor/ElementEditSession.java +++ b/src/org/thoughtcrime/securesms/imageeditor/ElementEditSession.java @@ -8,7 +8,7 @@ import org.thoughtcrime.securesms.imageeditor.model.EditorElement; abstract class ElementEditSession implements EditSession { - private final Matrix inverseMatrix; + private final Matrix inverseMatrix; final EditorElement selected; @@ -60,7 +60,7 @@ abstract class ElementEditSession implements EditSession { * @param matrix Matrix to transform point with. * @param src Input point. */ - static void mapPoint(@NonNull PointF dst, @NonNull Matrix matrix, @NonNull PointF src) { + private static void mapPoint(@NonNull PointF dst, @NonNull Matrix matrix, @NonNull PointF src) { float[] in = { src.x, src.y }; float[] out = new float[2]; matrix.mapPoints(out, in); diff --git a/src/org/thoughtcrime/securesms/imageeditor/ElementScaleEditSession.java b/src/org/thoughtcrime/securesms/imageeditor/ElementScaleEditSession.java index 95197c1fb6..4594c964a7 100644 --- a/src/org/thoughtcrime/securesms/imageeditor/ElementScaleEditSession.java +++ b/src/org/thoughtcrime/securesms/imageeditor/ElementScaleEditSession.java @@ -8,7 +8,7 @@ import org.thoughtcrime.securesms.imageeditor.model.EditorElement; final class ElementScaleEditSession extends ElementEditSession { - private ElementScaleEditSession(EditorElement selected, Matrix inverseMatrix) { + private ElementScaleEditSession(@NonNull EditorElement selected, @NonNull Matrix inverseMatrix) { super(selected, inverseMatrix); } @@ -56,20 +56,20 @@ final class ElementScaleEditSession extends ElementEditSession { } @Override - public EditSession newPoint(Matrix newInverse, PointF point, int p) { + public EditSession newPoint(@NonNull Matrix newInverse, @NonNull PointF point, int p) { return this; } @Override - public EditSession removePoint(Matrix newInverse, int p) { + public EditSession removePoint(@NonNull Matrix newInverse, int p) { return convertToDrag(p, newInverse); } - private static double angle(PointF a, PointF b) { + private static double angle(@NonNull PointF a, @NonNull PointF b) { return Math.atan2(a.y - b.y, a.x - b.x); } - private ElementDragEditSession convertToDrag(int p, Matrix inverse) { + private ElementDragEditSession convertToDrag(int p, @NonNull Matrix inverse) { return ElementDragEditSession.startDrag(selected, inverse, endPointScreen[1 - p]); } diff --git a/src/org/thoughtcrime/securesms/imageeditor/ImageEditorView.java b/src/org/thoughtcrime/securesms/imageeditor/ImageEditorView.java index 7b746667fc..ed98f296e2 100644 --- a/src/org/thoughtcrime/securesms/imageeditor/ImageEditorView.java +++ b/src/org/thoughtcrime/securesms/imageeditor/ImageEditorView.java @@ -70,6 +70,7 @@ public final class ImageEditorView extends FrameLayout { @Nullable private EditSession editSession; + private boolean moreThanOnePointerUsedInSession; public ImageEditorView(Context context) { super(context); @@ -215,6 +216,7 @@ public final class ImageEditorView extends FrameLayout { PointF point = getPoint(event); EditorElement selected = model.findElementAtPoint(point, viewMatrix, inverse); + moreThanOnePointerUsedInSession = false; model.pushUndoPoint(); editSession = startEdit(inverse, point, selected); @@ -230,9 +232,19 @@ public final class ImageEditorView extends FrameLayout { } case MotionEvent.ACTION_MOVE: { if (editSession != null) { - for (int p = 0; p < Math.min(2, event.getPointerCount()); p++) { + int historySize = event.getHistorySize(); + int pointerCount = Math.min(2, event.getPointerCount()); + + for (int h = 0; h < historySize; h++) { + for (int p = 0; p < pointerCount; p++) { + editSession.movePoint(p, getHistoricalPoint(event, p, h)); + } + } + + for (int p = 0; p < pointerCount; p++) { editSession.movePoint(p, getPoint(event, p)); } + model.moving(editSession.getSelected()); invalidate(); return true; } @@ -240,11 +252,16 @@ public final class ImageEditorView extends FrameLayout { } case MotionEvent.ACTION_POINTER_DOWN: { if (editSession != null && event.getPointerCount() == 2) { + moreThanOnePointerUsedInSession = true; editSession.commit(); model.pushUndoPoint(); Matrix newInverse = model.findElementInverseMatrix(editSession.getSelected(), viewMatrix); - editSession = editSession.newPoint(newInverse, getPoint(event, event.getActionIndex()), event.getActionIndex()); + if (newInverse != null) { + editSession = editSession.newPoint(newInverse, getPoint(event, event.getActionIndex()), event.getActionIndex()); + } else { + editSession = null; + } if (editSession == null) { dragDropRelease(); } @@ -259,7 +276,11 @@ public final class ImageEditorView extends FrameLayout { dragDropRelease(); Matrix newInverse = model.findElementInverseMatrix(editSession.getSelected(), viewMatrix); - editSession = editSession.removePoint(newInverse, event.getActionIndex()); + if (newInverse != null) { + editSession = editSession.removePoint(newInverse, event.getActionIndex()); + } else { + editSession = null; + } return true; } break; @@ -270,8 +291,11 @@ public final class ImageEditorView extends FrameLayout { dragDropRelease(); editSession = null; + model.postEdit(moreThanOnePointerUsedInSession); invalidate(); return true; + } else { + model.postEdit(moreThanOnePointerUsedInSession); } break; } @@ -349,6 +373,11 @@ public final class ImageEditorView extends FrameLayout { return new PointF(event.getX(p), event.getY(p)); } + private static PointF getHistoricalPoint(MotionEvent event, int p, int historicalIndex) { + return new PointF(event.getHistoricalX(p, historicalIndex), + event.getHistoricalY(p, historicalIndex)); + } + public EditorModel getModel() { return model; } diff --git a/src/org/thoughtcrime/securesms/imageeditor/RendererContext.java b/src/org/thoughtcrime/securesms/imageeditor/RendererContext.java index 9ca600f39f..d133e8f6c1 100644 --- a/src/org/thoughtcrime/securesms/imageeditor/RendererContext.java +++ b/src/org/thoughtcrime/securesms/imageeditor/RendererContext.java @@ -96,6 +96,10 @@ public final class RendererContext { canvasMatrix.restore(); } + public void getCurrent(@NonNull Matrix into) { + canvasMatrix.getCurrent(into); + } + public interface Ready { Ready NULL = (renderer, cropMatrix, size) -> { diff --git a/src/org/thoughtcrime/securesms/imageeditor/ThumbDragEditSession.java b/src/org/thoughtcrime/securesms/imageeditor/ThumbDragEditSession.java index 591e16afe8..197921c1fa 100644 --- a/src/org/thoughtcrime/securesms/imageeditor/ThumbDragEditSession.java +++ b/src/org/thoughtcrime/securesms/imageeditor/ThumbDragEditSession.java @@ -39,7 +39,7 @@ class ThumbDragEditSession extends ElementEditSession { editorMatrix.postTranslate(-x, -y); - boolean aspectLocked = selected.getFlags().isAspectLocked(); + boolean aspectLocked = selected.getFlags().isAspectLocked() && !controlPoint.isCenter(); float defaultScale = aspectLocked ? 2 : 1; @@ -57,12 +57,12 @@ class ThumbDragEditSession extends ElementEditSession { } @Override - public EditSession newPoint(Matrix newInverse, PointF point, int p) { + public EditSession newPoint(@NonNull Matrix newInverse, @NonNull PointF point, int p) { return null; } @Override - public EditSession removePoint(Matrix newInverse, int p) { + public EditSession removePoint(@NonNull Matrix newInverse, int p) { return null; } } diff --git a/src/org/thoughtcrime/securesms/imageeditor/model/EditorElementHierarchy.java b/src/org/thoughtcrime/securesms/imageeditor/model/EditorElementHierarchy.java index d16c677bfa..a8c72588fd 100644 --- a/src/org/thoughtcrime/securesms/imageeditor/model/EditorElementHierarchy.java +++ b/src/org/thoughtcrime/securesms/imageeditor/model/EditorElementHierarchy.java @@ -66,7 +66,7 @@ final class EditorElementHierarchy { private EditorElementHierarchy(@NonNull EditorElement root) { this.root = root; - this.view = this.root.getChild(0); + this.view = this.root.getChild(0); this.flipRotate = this.view.getChild(0); this.imageRoot = this.flipRotate.getChild(0); this.overlay = this.flipRotate.getChild(1); @@ -237,6 +237,30 @@ final class EditorElementHierarchy { return matrix; } + /** + * Returns a matrix that maps points from the crop on to the visible image. + *

+ * i.e. if a mapped point is in bounds, then the point is on the visible image. + */ + @Nullable Matrix imageMatrixRelativeToCrop() { + EditorElement mainImage = getMainImage(); + if (mainImage == null) return null; + + Matrix matrix1 = new Matrix(imageCrop.getLocalMatrix()); + matrix1.preConcat(cropEditorElement.getLocalMatrix()); + matrix1.preConcat(cropEditorElement.getEditorMatrix()); + + Matrix matrix2 = new Matrix(mainImage.getLocalMatrix()); + matrix2.preConcat(mainImage.getEditorMatrix()); + matrix2.preConcat(imageCrop.getLocalMatrix()); + + Matrix inverse = new Matrix(); + matrix2.invert(inverse); + inverse.preConcat(matrix1); + + return inverse; + } + void dragDropRelease(@NonNull RectF visibleViewPort, @NonNull Runnable invalidate) { if (cropEditorElement.getFlags().isVisible()) { updateViewToCrop(visibleViewPort, invalidate); @@ -299,9 +323,10 @@ final class EditorElementHierarchy { matrix.preConcat(flipRotate.getLocalMatrix()); matrix.preConcat(cropEditorElement.getLocalMatrix()); + matrix.preConcat(cropEditorElement.getEditorMatrix()); EditorElement mainImage = getMainImage(); if (mainImage != null) { - float xScale = 1f / xScale(mainImage.getLocalMatrix()); + float xScale = 1f / (xScale(mainImage.getLocalMatrix()) * xScale(mainImage.getEditorMatrix())); matrix.preScale(xScale, xScale); } diff --git a/src/org/thoughtcrime/securesms/imageeditor/model/EditorModel.java b/src/org/thoughtcrime/securesms/imageeditor/model/EditorModel.java index 9f178c1a3b..fde68048c1 100644 --- a/src/org/thoughtcrime/securesms/imageeditor/model/EditorModel.java +++ b/src/org/thoughtcrime/securesms/imageeditor/model/EditorModel.java @@ -37,11 +37,14 @@ public final class EditorModel implements Parcelable, RendererContext.Ready { private static final int MINIMUM_OUTPUT_WIDTH = 0; + private static final float MAXIMUM_CROP = 0.20f; + @NonNull private Runnable invalidate = NULL_RUNNABLE; private final UndoRedoStacks undoRedoStacks; private final UndoRedoStacks cropUndoRedoStacks; + private final InBoundsMemory inBoundsMemory = new InBoundsMemory(); private EditorElementHierarchy editorElementHierarchy; @@ -88,7 +91,7 @@ public final class EditorModel implements Parcelable, RendererContext.Ready { return null; } - private @Nullable Matrix findElementMatrix(@NonNull EditorElement element, @NonNull Matrix viewMatrix) { + public @Nullable Matrix findElementMatrix(@NonNull EditorElement element, @NonNull Matrix viewMatrix) { Matrix inverse = findElementInverseMatrix(element, viewMatrix); if (inverse != null) { Matrix regular = new Matrix(); @@ -107,7 +110,12 @@ public final class EditorModel implements Parcelable, RendererContext.Ready { } public void pushUndoPoint() { - UndoRedoStacks stacks = isCropping() ? cropUndoRedoStacks : undoRedoStacks; + boolean cropping = isCropping(); + if (cropping && !currentCropIsAcceptable()) { + return; + } + + UndoRedoStacks stacks = cropping ? cropUndoRedoStacks : undoRedoStacks; if (stacks.getUndoStack().tryPush(editorElementHierarchy.getRoot())) { stacks.getRedoStack().clear(); @@ -140,12 +148,14 @@ public final class EditorModel implements Parcelable, RendererContext.Ready { // re-zoom image root as the view port might be different now editorElementHierarchy.updateViewToCrop(visibleViewPort, invalidate); + + inBoundsMemory.push(editorElementHierarchy.getMainImage(), editorElementHierarchy.getCropEditorElement()); } } private static void restoreStateWithAnimations(@NonNull EditorElement fromRootElement, @NonNull EditorElement toRootElement, @NonNull Runnable onInvalidate, boolean keepEditorState) { Map fromMap = getElementMap(fromRootElement); - Map toMap = getElementMap(toRootElement); + Map toMap = getElementMap(toRootElement); for (EditorElement fromElement : fromMap.values()) { fromElement.stopAnimation(); @@ -188,6 +198,7 @@ public final class EditorModel implements Parcelable, RendererContext.Ready { cropUndoRedoStacks.getUndoStack().clear(); cropUndoRedoStacks.getUndoStack().clear(); editorElementHierarchy.startCrop(invalidate); + inBoundsMemory.push(editorElementHierarchy.getMainImage(), editorElementHierarchy.getCropEditorElement()); } public void doneCrop() { @@ -196,9 +207,11 @@ public final class EditorModel implements Parcelable, RendererContext.Ready { public void setCropAspectLock(boolean locked) { EditorFlags flags = editorElementHierarchy.getCropEditorElement().getFlags(); - int currentState = flags.setAspectLocked(locked).getCurrentState(); + int currentState = flags.setAspectLocked(locked).getCurrentState(); + flags.reset(); - flags.setAspectLocked(locked).persist(); + flags.setAspectLocked(locked) + .persist(); flags.restoreState(currentState); } @@ -206,10 +219,113 @@ public final class EditorModel implements Parcelable, RendererContext.Ready { return editorElementHierarchy.getCropEditorElement().getFlags().isAspectLocked(); } + public void postEdit(boolean allowScaleToRepairCrop) { + if (isCropping()) { + ensureFitsBounds(allowScaleToRepairCrop); + } + } + + private void ensureFitsBounds(boolean allowScaleToRepairCrop) { + EditorElement mainImage = editorElementHierarchy.getMainImage(); + if (mainImage == null) return; + + EditorElement cropEditorElement = editorElementHierarchy.getCropEditorElement(); + + if (!currentCropIsAcceptable()) { + if (allowScaleToRepairCrop) { + if (!tryToScaleToFit(cropEditorElement, 0.9f)) { + tryToScaleToFit(mainImage, 2f); + } + } + + if (!currentCropIsAcceptable()) { + inBoundsMemory.restore(mainImage, cropEditorElement, invalidate); + } else { + inBoundsMemory.push(mainImage, cropEditorElement); + } + } + + editorElementHierarchy.dragDropRelease(visibleViewPort, invalidate); + } + + /** + * Attempts to scale the supplied element such that {@link #cropIsWithinMainImageBounds} is true. + *

+ * Does not respect minimum scale, so does need a further check to {@link #currentCropIsAcceptable} afterwards. + * + * @param element The element to be scaled. If successful, it will be animated to the correct position. + * @param scaleAtMost The amount of scale to apply at most. Use < 1 for the crop, and > 1 for the image. + * @return true if successfully scaled the element. false if the element was left unchanged. + */ + private boolean tryToScaleToFit(@NonNull EditorElement element, float scaleAtMost) { + Matrix elementMatrix = element.getLocalMatrix(); + Matrix original = new Matrix(elementMatrix); + Matrix lastSuccessful = new Matrix(); + boolean success = false; + float unsuccessfulScale = 1; + int attempt = 0; + + do { + float tryScale = (scaleAtMost + unsuccessfulScale) / 2f; + elementMatrix.set(original); + elementMatrix.preScale(tryScale, tryScale); + + if (cropIsWithinMainImageBounds(editorElementHierarchy)) { + scaleAtMost = tryScale; + success = true; + lastSuccessful.set(elementMatrix); + } else { + unsuccessfulScale = tryScale; + } + attempt++; + } while (attempt < 16 && Math.abs(scaleAtMost - unsuccessfulScale) > 0.001f); + + elementMatrix.set(original); + if (success) { + element.animateLocalTo(lastSuccessful, invalidate); + } + return success; + } + public void dragDropRelease() { editorElementHierarchy.dragDropRelease(visibleViewPort, invalidate); } + /** + * Pixel count must be no smaller than the MAXIMUM_CROP of its original size and all points must be within the bounds. + */ + private boolean currentCropIsAcceptable() { + Point outputSize = getOutputSize(); + int outputPixelCount = outputSize.x * outputSize.y; + int minimumPixelCount = (int) (size.x * size.y * MAXIMUM_CROP * MAXIMUM_CROP); + + return outputPixelCount >= minimumPixelCount && + cropIsWithinMainImageBounds(editorElementHierarchy); + } + + /** + * @return true if and only if the current crop rect is fully in the bounds. + */ + private static boolean cropIsWithinMainImageBounds(@NonNull EditorElementHierarchy hierarchy) { + return Bounds.boundsRemainInBounds(hierarchy.imageMatrixRelativeToCrop()); + } + + /** + * Called as edits are underway. + */ + public void moving(@NonNull EditorElement editorElement) { + if (!isCropping()) return; + + EditorElement mainImage = editorElementHierarchy.getMainImage(); + EditorElement cropEditorElement = editorElementHierarchy.getCropEditorElement(); + + if (editorElement == mainImage || editorElement == cropEditorElement) { + if (currentCropIsAcceptable()) { + inBoundsMemory.push(mainImage, cropEditorElement); + } + } + } + public void setVisibleViewPort(@NonNull RectF visibleViewPort) { this.visibleViewPort.set(visibleViewPort); this.editorElementHierarchy.updateViewToCrop(visibleViewPort, invalidate); @@ -364,23 +480,38 @@ public final class EditorModel implements Parcelable, RendererContext.Ready { return findCropRelativeTo(editorElementHierarchy.getRoot()); } - private RectF findCropRelativeTo(EditorElement element) { + RectF findCropRelativeTo(EditorElement element) { return findRelativeBounds(editorElementHierarchy.getCropEditorElement(), element); } - private RectF findRelativeBounds(EditorElement from, EditorElement to) { - Matrix matrix1 = findElementMatrix(from, new Matrix()); - Matrix matrix2 = findElementInverseMatrix(to, new Matrix()); + RectF findRelativeBounds(EditorElement from, EditorElement to) { + Matrix relative = findRelativeMatrix(from, to); RectF dst = new RectF(Bounds.FULL_BOUNDS); - if (matrix1 != null) { - matrix1.preConcat(matrix2); - - matrix1.mapRect(dst, Bounds.FULL_BOUNDS); + if (relative != null) { + relative.mapRect(dst, Bounds.FULL_BOUNDS); } return dst; } + /** + * Returns a matrix that maps points in the {@param from} element in to points in the {@param to} element. + * + * @param from + * @param to + * @return + */ + @Nullable Matrix findRelativeMatrix(@NonNull EditorElement from, @NonNull EditorElement to) { + Matrix matrix = findElementInverseMatrix(to, new Matrix()); + Matrix outOf = findElementMatrix(from, new Matrix()); + + if (outOf != null && matrix != null) { + matrix.preConcat(outOf); + return matrix; + } + return null; + } + public void rotate90clockwise() { pushUndoPoint(); editorElementHierarchy.flipRotate(90, 1, 1, visibleViewPort, invalidate); diff --git a/src/org/thoughtcrime/securesms/imageeditor/model/InBoundsMemory.java b/src/org/thoughtcrime/securesms/imageeditor/model/InBoundsMemory.java new file mode 100644 index 0000000000..fcc3533806 --- /dev/null +++ b/src/org/thoughtcrime/securesms/imageeditor/model/InBoundsMemory.java @@ -0,0 +1,30 @@ +package org.thoughtcrime.securesms.imageeditor.model; + +import android.graphics.Matrix; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +final class InBoundsMemory { + + private final Matrix lastGoodUserCrop = new Matrix(); + private final Matrix lastGoodMainImage = new Matrix(); + + void push(@Nullable EditorElement mainImage, @NonNull EditorElement userCrop) { + if (mainImage == null) { + lastGoodMainImage.reset(); + } else { + lastGoodMainImage.set(mainImage.getLocalMatrix()); + lastGoodMainImage.preConcat(mainImage.getEditorMatrix()); + } + + lastGoodUserCrop.set(userCrop.getLocalMatrix()); + lastGoodUserCrop.preConcat(userCrop.getEditorMatrix()); + } + + void restore(@Nullable EditorElement mainImage, @NonNull EditorElement cropEditorElement, @Nullable Runnable invalidate) { + if (mainImage != null) { + mainImage.animateLocalTo(lastGoodMainImage, invalidate); + } + cropEditorElement.animateLocalTo(lastGoodUserCrop, invalidate); + } +} \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/imageeditor/model/ThumbRenderer.java b/src/org/thoughtcrime/securesms/imageeditor/model/ThumbRenderer.java index 21020b5253..1447664490 100644 --- a/src/org/thoughtcrime/securesms/imageeditor/model/ThumbRenderer.java +++ b/src/org/thoughtcrime/securesms/imageeditor/model/ThumbRenderer.java @@ -65,6 +65,10 @@ public interface ThumbRenderer extends Renderer { public boolean isVerticalCenter() { return this == ControlPoint.TOP_CENTER || this == ControlPoint.BOTTOM_CENTER; } + + public boolean isCenter() { + return isHorizontalCenter() || isVerticalCenter(); + } } ControlPoint getControlPoint(); diff --git a/src/org/thoughtcrime/securesms/imageeditor/renderers/CropAreaRenderer.java b/src/org/thoughtcrime/securesms/imageeditor/renderers/CropAreaRenderer.java index 67f26354fc..6c15f2b2e6 100644 --- a/src/org/thoughtcrime/securesms/imageeditor/renderers/CropAreaRenderer.java +++ b/src/org/thoughtcrime/securesms/imageeditor/renderers/CropAreaRenderer.java @@ -106,7 +106,7 @@ public final class CropAreaRenderer implements Renderer { @Override public boolean hitTest(float x, float y) { - return !Bounds.FULL_BOUNDS.contains(x, y); + return !Bounds.contains(x, y); } public static final Creator CREATOR = new Creator() { diff --git a/src/org/thoughtcrime/securesms/imageeditor/renderers/InverseFillRenderer.java b/src/org/thoughtcrime/securesms/imageeditor/renderers/InverseFillRenderer.java index 71f42c6876..c7bba78f4b 100644 --- a/src/org/thoughtcrime/securesms/imageeditor/renderers/InverseFillRenderer.java +++ b/src/org/thoughtcrime/securesms/imageeditor/renderers/InverseFillRenderer.java @@ -44,7 +44,7 @@ public final class InverseFillRenderer implements Renderer { @Override public boolean hitTest(float x, float y) { - return !Bounds.FULL_BOUNDS.contains(x, y); + return !Bounds.contains(x, y); } public static final Creator CREATOR = new Creator() {