mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 04:58:45 +00:00
@@ -427,6 +427,11 @@ public final class ImageEditorView extends FrameLayout {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public void setMainImageEditorMatrixRotation(float angle, float minScaleDown) {
|
||||
model.setMainImageEditorMatrixRotation(angle, minScaleDown);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void startDrawing(float thickness, @NonNull Paint.Cap cap, boolean blur) {
|
||||
this.thickness = thickness;
|
||||
this.cap = cap;
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.signal.imageeditor.core;
|
||||
|
||||
import android.graphics.Matrix;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public final class MatrixUtils {
|
||||
|
||||
private static final ThreadLocal<float[]> tempMatrixValues = new ThreadLocal<>();
|
||||
|
||||
protected static @NonNull float[] getTempMatrixValues() {
|
||||
float[] floats = tempMatrixValues.get();
|
||||
if(floats == null) {
|
||||
floats = new float[9];
|
||||
tempMatrixValues.set(floats);
|
||||
}
|
||||
return floats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the angle from a matrix in radians.
|
||||
*/
|
||||
public static float getRotationAngle(@NonNull Matrix matrix) {
|
||||
float[] matrixValues = getTempMatrixValues();
|
||||
matrix.getValues(matrixValues);
|
||||
return (float) -Math.atan2(matrixValues[Matrix.MSKEW_X], matrixValues[Matrix.MSCALE_X]);
|
||||
}
|
||||
|
||||
/** Gets the scale on the X axis */
|
||||
public static float getScaleX(@NonNull Matrix matrix) {
|
||||
float[] matrixValues = getTempMatrixValues();
|
||||
matrix.getValues(matrixValues);
|
||||
float scaleX = matrixValues[Matrix.MSCALE_X];
|
||||
float skewX = matrixValues[Matrix.MSKEW_X];
|
||||
return (float) Math.sqrt(scaleX * scaleX + skewX * skewX);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import android.os.Parcelable;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.imageeditor.core.MatrixUtils;
|
||||
import org.signal.imageeditor.core.Renderer;
|
||||
import org.signal.imageeditor.core.RendererContext;
|
||||
|
||||
@@ -299,6 +300,14 @@ public final class EditorElement implements Parcelable {
|
||||
children.clear();
|
||||
}
|
||||
|
||||
public float getLocalRotationAngle() {
|
||||
return MatrixUtils.getRotationAngle(localMatrix);
|
||||
}
|
||||
|
||||
public float getLocalScaleX() {
|
||||
return MatrixUtils.getScaleX(localMatrix);
|
||||
}
|
||||
|
||||
public interface PerElementFunction {
|
||||
void apply(EditorElement element);
|
||||
}
|
||||
|
||||
@@ -221,6 +221,12 @@ final class EditorElementHierarchy {
|
||||
selectedElement = null;
|
||||
}
|
||||
|
||||
void updateSelectionThumbsForElement(@NonNull EditorElement element, @Nullable Matrix overlayMappingMatrix) {
|
||||
if (element == selectedElement) {
|
||||
setOrUpdateSelectionThumbsForElement(element, overlayMappingMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
void setOrUpdateSelectionThumbsForElement(@NonNull EditorElement element, @Nullable Matrix overlayMappingMatrix) {
|
||||
if (selectedElement != element) {
|
||||
removeAllSelectionArtifacts();
|
||||
@@ -433,7 +439,7 @@ final class EditorElementHierarchy {
|
||||
return dst;
|
||||
}
|
||||
|
||||
void flipRotate(int degrees, int scaleX, int scaleY, @NonNull RectF visibleViewPort, @Nullable Runnable invalidate) {
|
||||
void flipRotate(float degrees, int scaleX, int scaleY, @NonNull RectF visibleViewPort, @Nullable Runnable invalidate) {
|
||||
Matrix newLocal = new Matrix(flipRotate.getLocalMatrix());
|
||||
if (degrees != 0) {
|
||||
newLocal.postRotate(degrees);
|
||||
|
||||
@@ -76,6 +76,11 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
|
||||
}
|
||||
}
|
||||
|
||||
public void updateSelectionThumbsIfSelected(@NonNull EditorElement editorElement) {
|
||||
Matrix overlayMappingMatrix = findRelativeMatrix(editorElement, editorElementHierarchy.getOverlay());
|
||||
editorElementHierarchy.updateSelectionThumbsForElement(editorElement, overlayMappingMatrix);
|
||||
}
|
||||
|
||||
public void setSelectionVisible(boolean visible) {
|
||||
editorElementHierarchy.getSelection()
|
||||
.getFlags()
|
||||
@@ -145,6 +150,51 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
|
||||
updateUndoRedoAvailableState(getActiveUndoRedoStacks(isCropping()));
|
||||
}
|
||||
|
||||
/** Keeps the image within the crop bounds as it rotates */
|
||||
public void setMainImageEditorMatrixRotation(float angle, float minScaleDown) {
|
||||
setEditorMatrixToRotationMatrixAboutParentsOrigin(editorElementHierarchy.getMainImage(), angle);
|
||||
scaleMainImageEditorMatrixToFitInsideCropBounds(minScaleDown, 2f);
|
||||
}
|
||||
|
||||
private void scaleMainImageEditorMatrixToFitInsideCropBounds(float minScaleDown, float maxScaleUp) {
|
||||
EditorElement mainImage = editorElementHierarchy.getMainImage();
|
||||
Matrix mainImageLocalBackup = new Matrix(mainImage.getLocalMatrix());
|
||||
Matrix mainImageEditorBackup = new Matrix(mainImage.getEditorMatrix());
|
||||
|
||||
mainImage.commitEditorMatrix();
|
||||
Matrix combinedLocal = new Matrix(mainImage.getLocalMatrix());
|
||||
Matrix newLocal = Bisect.bisectToTest(mainImage,
|
||||
minScaleDown,
|
||||
maxScaleUp,
|
||||
this::cropIsWithinMainImageBounds,
|
||||
(matrix, scale) -> matrix.preScale(scale, scale));
|
||||
|
||||
Matrix invertLocal = new Matrix();
|
||||
if (newLocal != null && combinedLocal.invert(invertLocal)) {
|
||||
invertLocal.preConcat(newLocal); // L^-1 (L * Scale) -> Scale
|
||||
mainImageEditorBackup.preConcat(invertLocal); // add the scale to editor matrix to keep this image within crop
|
||||
}
|
||||
mainImage.getLocalMatrix().set(mainImageLocalBackup);
|
||||
mainImage.getEditorMatrix().set(mainImageEditorBackup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the editor matrix for the element to a rotation of the degrees but does so that we are rotating around the
|
||||
* parents elements origin.
|
||||
*/
|
||||
private void setEditorMatrixToRotationMatrixAboutParentsOrigin(@NonNull EditorElement element, float degrees) {
|
||||
Matrix localMatrix = element.getLocalMatrix();
|
||||
Matrix editorMatrix = element.getEditorMatrix();
|
||||
localMatrix.invert(editorMatrix);
|
||||
editorMatrix.preRotate(degrees);
|
||||
editorMatrix.preConcat(localMatrix);
|
||||
// Editor Matrix is then: Local^-1 * Rotate(degrees) * Local
|
||||
// So you end up with this overall for the element: Local * Local^-1 * Rotate(degrees) * Local
|
||||
// Meaning the rotate applies after existing effects of the local matrix
|
||||
// Where as simply setting the editor matrix rotate gives this: Local * Rotate(degrees)
|
||||
// which rotates around local origin first
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders tree with the following matrix:
|
||||
* <p>
|
||||
@@ -233,6 +283,10 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
|
||||
getActiveUndoRedoStacks(cropping).pushState(editorElementHierarchy.getRoot());
|
||||
}
|
||||
|
||||
public void updateUndoRedoAvailabilityState() {
|
||||
updateUndoRedoAvailableState(getActiveUndoRedoStacks(isCropping()));
|
||||
}
|
||||
|
||||
public void clearUndoStack() {
|
||||
EditorElement root = editorElementHierarchy.getRoot();
|
||||
EditorElement original = root;
|
||||
@@ -598,7 +652,7 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
|
||||
*/
|
||||
public void moving(@NonNull EditorElement editorElement) {
|
||||
if (!isCropping()) {
|
||||
setSelected(editorElement);
|
||||
updateSelectionThumbsIfSelected(editorElement);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -902,10 +956,6 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void rotate90clockwise() {
|
||||
flipRotate(90, 1, 1);
|
||||
}
|
||||
|
||||
public void rotate90anticlockwise() {
|
||||
flipRotate(-90, 1, 1);
|
||||
}
|
||||
@@ -914,11 +964,7 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
|
||||
flipRotate(0, -1, 1);
|
||||
}
|
||||
|
||||
public void flipVertical() {
|
||||
flipRotate(0, 1, -1);
|
||||
}
|
||||
|
||||
private void flipRotate(int degrees, int scaleX, int scaleY) {
|
||||
private void flipRotate(float degrees, int scaleX, int scaleY) {
|
||||
pushUndoPoint();
|
||||
editorElementHierarchy.flipRotate(degrees, scaleX, scaleY, visibleViewPort, invalidate);
|
||||
updateUndoRedoAvailableState(getActiveUndoRedoStacks(isCropping()));
|
||||
|
||||
Reference in New Issue
Block a user