Blur faces rotation and crop and zoom support.

This commit is contained in:
Alan Evans 2020-06-03 14:02:24 -03:00 committed by GitHub
parent b4f60f3acb
commit 1033bd7bda
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 110 additions and 46 deletions

View file

@ -109,7 +109,7 @@ public final class EditorFlags {
persistedFlags = flags; persistedFlags = flags;
} }
void reset() { public void reset() {
restoreState(persistedFlags); restoreState(persistedFlags);
} }

View file

@ -604,6 +604,21 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
return new Point(width, height); return new Point(width, height);
} }
@NonNull
public Point getOutputSizeMaxWidth(int maxDimension) {
PointF outputSize = editorElementHierarchy.getOutputSize(size);
int width = Math.min(maxDimension, (int) Math.max(MINIMUM_OUTPUT_WIDTH, outputSize.x));
int height = (int) (width * outputSize.y / outputSize.x);
if (height > maxDimension) {
height = maxDimension;
width = (int) (height * outputSize.x / outputSize.y);
}
return new Point(width, height);
}
@Override @Override
public void onReady(@NonNull Renderer renderer, @Nullable Matrix cropMatrix, @Nullable Point size) { public void onReady(@NonNull Renderer renderer, @Nullable Matrix cropMatrix, @Nullable Point size) {
if (cropMatrix != null && size != null && isRendererOfMainImage(renderer)) { if (cropMatrix != null && size != null && isRendererOfMainImage(renderer)) {
@ -819,4 +834,16 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
return editorElementHierarchy.getCropEditorElement().getFlags().isVisible(); return editorElementHierarchy.getCropEditorElement().getFlags().isVisible();
} }
/**
* Returns a matrix that maps bounds to the crop area.
*/
public Matrix getInverseCropPosition() {
Matrix matrix = new Matrix();
matrix.set(findRelativeMatrix(editorElementHierarchy.getMainImage(), editorElementHierarchy.getCropEditorElement()));
matrix.postConcat(editorElementHierarchy.getFlipRotate().getLocalMatrix());
Matrix positionRelativeToCrop = new Matrix();
matrix.invert(positionRelativeToCrop);
return positionRelativeToCrop;
}
} }

View file

@ -1,28 +1,50 @@
package org.thoughtcrime.securesms.imageeditor.model; package org.thoughtcrime.securesms.imageeditor.model;
import android.graphics.Matrix; import android.graphics.Matrix;
import android.graphics.RectF;
import android.os.Parcel; import android.os.Parcel;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import java.util.UUID; import java.util.UUID;
final class ParcelUtils { public final class ParcelUtils {
private ParcelUtils() { private ParcelUtils() {
} }
static void writeMatrix(@NonNull Parcel dest, @NonNull Matrix matrix) { public static void writeMatrix(@NonNull Parcel dest, @NonNull Matrix matrix) {
float[] values = new float[9]; float[] values = new float[9];
matrix.getValues(values); matrix.getValues(values);
dest.writeFloatArray(values); dest.writeFloatArray(values);
} }
static void readMatrix(@NonNull Matrix matrix, @NonNull Parcel in) { public static void readMatrix(@NonNull Matrix matrix, @NonNull Parcel in) {
float[] values = new float[9]; float[] values = new float[9];
in.readFloatArray(values); in.readFloatArray(values);
matrix.setValues(values); matrix.setValues(values);
} }
public static @NonNull Matrix readMatrix(@NonNull Parcel in) {
Matrix matrix = new Matrix();
readMatrix(matrix, in);
return matrix;
}
public static void writeRect(@NonNull Parcel dest, @NonNull RectF rect) {
dest.writeFloat(rect.left);
dest.writeFloat(rect.top);
dest.writeFloat(rect.right);
dest.writeFloat(rect.bottom);
}
public static @NonNull RectF readRectF(@NonNull Parcel in) {
float left = in.readFloat();
float top = in.readFloat();
float right = in.readFloat();
float bottom = in.readFloat();
return new RectF(left, top, right, bottom);
}
static UUID readUUID(@NonNull Parcel in) { static UUID readUUID(@NonNull Parcel in) {
return new UUID(in.readLong(), in.readLong()); return new UUID(in.readLong(), in.readLong());
} }

View file

@ -10,7 +10,7 @@ import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.imageeditor.Bounds; import org.thoughtcrime.securesms.imageeditor.Bounds;
import org.thoughtcrime.securesms.imageeditor.Renderer; import org.thoughtcrime.securesms.imageeditor.Renderer;
import org.thoughtcrime.securesms.imageeditor.RendererContext; import org.thoughtcrime.securesms.imageeditor.RendererContext;
import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.imageeditor.model.ParcelUtils;
/** /**
* A rectangle that will be rendered on the blur mask layer. Intended for blurring faces. * A rectangle that will be rendered on the blur mask layer. Intended for blurring faces.
@ -20,27 +20,24 @@ public class FaceBlurRenderer implements Renderer {
private static final int CORNER_RADIUS = 0; private static final int CORNER_RADIUS = 0;
private final RectF faceRect; private final RectF faceRect;
private final Point imageDimensions; private final Matrix imageProjectionMatrix;
private final Matrix scaleMatrix;
public FaceBlurRenderer(@NonNull RectF faceRect, @NonNull Matrix matrix) { private FaceBlurRenderer(@NonNull RectF faceRect, @NonNull Matrix imageProjectionMatrix) {
this.faceRect = faceRect; this.faceRect = faceRect;
this.imageDimensions = new Point(0, 0); this.imageProjectionMatrix = imageProjectionMatrix;
this.scaleMatrix = matrix;
} }
public FaceBlurRenderer(@NonNull RectF faceRect, @NonNull Point imageDimensions) { public FaceBlurRenderer(@NonNull RectF faceRect, @NonNull Point imageDimensions) {
this.faceRect = faceRect; this.faceRect = faceRect;
this.imageDimensions = imageDimensions; this.imageProjectionMatrix = new Matrix();
this.scaleMatrix = new Matrix();
scaleMatrix.setRectToRect(new RectF(0, 0, this.imageDimensions.x, this.imageDimensions.y), Bounds.FULL_BOUNDS, Matrix.ScaleToFit.CENTER); this.imageProjectionMatrix.setRectToRect(new RectF(0, 0, imageDimensions.x, imageDimensions.y), Bounds.FULL_BOUNDS, Matrix.ScaleToFit.FILL);
} }
@Override @Override
public void render(@NonNull RendererContext rendererContext) { public void render(@NonNull RendererContext rendererContext) {
rendererContext.canvas.save(); rendererContext.canvas.save();
rendererContext.canvas.concat(scaleMatrix); rendererContext.canvas.concat(imageProjectionMatrix);
rendererContext.canvas.drawRoundRect(faceRect, CORNER_RADIUS, CORNER_RADIUS, rendererContext.getMaskPaint()); rendererContext.canvas.drawRoundRect(faceRect, CORNER_RADIUS, CORNER_RADIUS, rendererContext.getMaskPaint());
rendererContext.canvas.restore(); rendererContext.canvas.restore();
} }
@ -57,25 +54,17 @@ public class FaceBlurRenderer implements Renderer {
@Override @Override
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
dest.writeFloat(faceRect.left); ParcelUtils.writeMatrix(dest, imageProjectionMatrix);
dest.writeFloat(faceRect.top); ParcelUtils.writeRect(dest, faceRect);
dest.writeFloat(faceRect.right);
dest.writeFloat(faceRect.bottom);
dest.writeInt(imageDimensions.x);
dest.writeInt(imageDimensions.y);
} }
public static final Creator<FaceBlurRenderer> CREATOR = new Creator<FaceBlurRenderer>() { public static final Creator<FaceBlurRenderer> CREATOR = new Creator<FaceBlurRenderer>() {
@Override @Override
public FaceBlurRenderer createFromParcel(Parcel in) { public FaceBlurRenderer createFromParcel(Parcel in) {
float left = in.readFloat(); Matrix imageProjection = ParcelUtils.readMatrix(in);
float top = in.readFloat(); RectF faceRect = ParcelUtils.readRectF (in);
float right = in.readFloat();
float bottom = in.readFloat();
int x = in.readInt();
int y = in.readInt();
return new FaceBlurRenderer(new RectF(left, top, right, bottom), new Point(x, y)); return new FaceBlurRenderer(faceRect, imageProjection);
} }
@Override @Override

View file

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.scribbles;
import android.Manifest; import android.Manifest;
import android.content.Intent; import android.content.Intent;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Point; import android.graphics.Point;
import android.graphics.RectF; import android.graphics.RectF;
@ -19,14 +20,14 @@ import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.TooltipPopup; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.imageeditor.ColorableRenderer; import org.thoughtcrime.securesms.imageeditor.ColorableRenderer;
import org.thoughtcrime.securesms.imageeditor.ImageEditorView; import org.thoughtcrime.securesms.imageeditor.ImageEditorView;
import org.thoughtcrime.securesms.imageeditor.Renderer; import org.thoughtcrime.securesms.imageeditor.Renderer;
import org.thoughtcrime.securesms.imageeditor.model.EditorElement; import org.thoughtcrime.securesms.imageeditor.model.EditorElement;
import org.thoughtcrime.securesms.imageeditor.model.EditorModel; import org.thoughtcrime.securesms.imageeditor.model.EditorModel;
import org.thoughtcrime.securesms.imageeditor.renderers.MultiLineTextRenderer;
import org.thoughtcrime.securesms.imageeditor.renderers.FaceBlurRenderer; import org.thoughtcrime.securesms.imageeditor.renderers.FaceBlurRenderer;
import org.thoughtcrime.securesms.imageeditor.renderers.MultiLineTextRenderer;
import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mediasend.MediaSendPageFragment; import org.thoughtcrime.securesms.mediasend.MediaSendPageFragment;
@ -344,30 +345,51 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
@Override @Override
public void onBlurFacesToggled(boolean enabled) { public void onBlurFacesToggled(boolean enabled) {
if (!enabled) { EditorModel model = imageEditorView.getModel();
imageEditorView.getModel().clearFaceRenderers(); EditorElement mainImage = model.getMainImage();
if (mainImage == null) {
return; return;
} }
if (cachedFaceDetection != null && cachedFaceDetection.first().equals(getUri())) { if (!enabled) {
renderFaceBlurs(cachedFaceDetection.second()); model.clearFaceRenderers();
return; return;
} else if (cachedFaceDetection != null && !cachedFaceDetection.first().equals(getUri())) { }
cachedFaceDetection = null;
Matrix inverseCropPosition = model.getInverseCropPosition();
if (cachedFaceDetection != null) {
if (cachedFaceDetection.first().equals(getUri()) && cachedFaceDetection.second().position.equals(inverseCropPosition)) {
renderFaceBlurs(cachedFaceDetection.second());
return;
} else {
cachedFaceDetection = null;
}
} }
AlertDialog progress = SimpleProgressDialog.show(requireContext()); AlertDialog progress = SimpleProgressDialog.show(requireContext());
mainImage.getFlags().setChildrenVisible(false);
SimpleTask.run(() -> { SimpleTask.run(getLifecycle(), () -> {
Bitmap bitmap = ((UriGlideRenderer) imageEditorView.getModel().getMainImage().getRenderer()).getBitmap(); if (mainImage.getRenderer() != null) {
Bitmap bitmap = ((UriGlideRenderer) mainImage.getRenderer()).getBitmap();
if (bitmap != null) {
FaceDetector detector = new FirebaseFaceDetector();
if (bitmap != null) { Point size = model.getOutputSizeMaxWidth(1000);
FaceDetector detector = new FirebaseFaceDetector(); Bitmap render = model.render(ApplicationDependencies.getApplication(), size);
return new FaceDetectionResult(detector.detect(bitmap), new Point(bitmap.getWidth(), bitmap.getHeight())); try {
} else { return new FaceDetectionResult(detector.detect(render), new Point(render.getWidth(), render.getHeight()), inverseCropPosition);
return new FaceDetectionResult(Collections.emptyList(), new Point(0, 0)); } finally {
render.recycle();
mainImage.getFlags().reset();
}
}
} }
return new FaceDetectionResult(Collections.emptyList(), new Point(0, 0), new Matrix());
}, result -> { }, result -> {
mainImage.getFlags().reset();
renderFaceBlurs(result); renderFaceBlurs(result);
progress.dismiss(); progress.dismiss();
}); });
@ -469,7 +491,9 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
for (RectF face : faces) { for (RectF face : faces) {
FaceBlurRenderer faceBlurRenderer = new FaceBlurRenderer(face, size); FaceBlurRenderer faceBlurRenderer = new FaceBlurRenderer(face, size);
imageEditorView.getModel().addElementWithoutPushUndo(new EditorElement(faceBlurRenderer, EditorModel.Z_MASK)); EditorElement element = new EditorElement(faceBlurRenderer, EditorModel.Z_MASK);
element.getLocalMatrix().set(result.position);
imageEditorView.getModel().addElementWithoutPushUndo(element);
} }
imageEditorView.invalidate(); imageEditorView.invalidate();
@ -534,10 +558,12 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
private static class FaceDetectionResult { private static class FaceDetectionResult {
private final List<RectF> rects; private final List<RectF> rects;
private final Point imageSize; private final Point imageSize;
private final Matrix position;
private FaceDetectionResult(@NonNull List<RectF> rects, @NonNull Point imageSize) { private FaceDetectionResult(@NonNull List<RectF> rects, @NonNull Point imageSize, @NonNull Matrix position) {
this.rects = rects; this.rects = rects;
this.imageSize = imageSize; this.imageSize = imageSize;
this.position = new Matrix(position);
} }
} }
} }