Blur faces rotation and crop and zoom support.
This commit is contained in:
parent
b4f60f3acb
commit
1033bd7bda
5 changed files with 110 additions and 46 deletions
|
@ -109,7 +109,7 @@ public final class EditorFlags {
|
||||||
persistedFlags = flags;
|
persistedFlags = flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset() {
|
public void reset() {
|
||||||
restoreState(persistedFlags);
|
restoreState(persistedFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue