Add Image Editor support for blur mask layer.

This commit is contained in:
Alan Evans 2020-06-03 03:30:24 -03:00
parent 32e9901592
commit 514048171b
5 changed files with 101 additions and 5 deletions

View File

@ -3,11 +3,17 @@ package org.thoughtcrime.securesms.imageeditor;
import android.content.Context; import android.content.Context;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Matrix; import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Point; import android.graphics.Point;
import android.graphics.RectF; import android.graphics.RectF;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.imageeditor.model.EditorElement;
import java.util.Collections;
import java.util.List;
/** /**
* Contains all of the information required for a {@link Renderer} to do its job. * Contains all of the information required for a {@link Renderer} to do its job.
* <p> * <p>
@ -38,6 +44,9 @@ public final class RendererContext {
private boolean isEditing = true; private boolean isEditing = true;
private List<EditorElement> children = Collections.emptyList();
private Paint maskPaint;
public RendererContext(@NonNull Context context, @NonNull Canvas canvas, @NonNull Ready rendererReady, @NonNull Invalidate invalidate) { public RendererContext(@NonNull Context context, @NonNull Canvas canvas, @NonNull Ready rendererReady, @NonNull Invalidate invalidate) {
this.context = context; this.context = context;
this.canvas = canvas; this.canvas = canvas;
@ -100,6 +109,22 @@ public final class RendererContext {
canvasMatrix.getCurrent(into); canvasMatrix.getCurrent(into);
} }
public void setChildren(@NonNull List<EditorElement> children) {
this.children = children;
}
public @NonNull List<EditorElement> getChildren() {
return children;
}
public void setMaskPaint(@Nullable Paint maskPaint) {
this.maskPaint = maskPaint;
}
public @Nullable Paint getMaskPaint() {
return maskPaint;
}
public interface Ready { public interface Ready {
Ready NULL = (renderer, cropMatrix, size) -> { Ready NULL = (renderer, cropMatrix, size) -> {

View File

@ -97,7 +97,7 @@ public final class EditorElement implements Parcelable {
* *
* @param rendererContext Canvas to draw on to. * @param rendererContext Canvas to draw on to.
*/ */
void draw(@NonNull RendererContext rendererContext) { public void draw(@NonNull RendererContext rendererContext) {
if (!flags.isVisible() && !flags.isChildrenVisible()) return; if (!flags.isVisible() && !flags.isChildrenVisible()) return;
rendererContext.save(); rendererContext.save();
@ -113,6 +113,7 @@ public final class EditorElement implements Parcelable {
float alpha = alphaAnimation.getValue(); float alpha = alphaAnimation.getValue();
if (alpha > 0) { if (alpha > 0) {
rendererContext.setFade(alpha); rendererContext.setFade(alpha);
rendererContext.setChildren(children);
drawSelf(rendererContext); drawSelf(rendererContext);
rendererContext.setFade(1f); rendererContext.setFade(1f);
} }
@ -133,9 +134,11 @@ public final class EditorElement implements Parcelable {
private static void drawChildren(@NonNull List<EditorElement> children, @NonNull RendererContext rendererContext) { private static void drawChildren(@NonNull List<EditorElement> children, @NonNull RendererContext rendererContext) {
for (EditorElement element : children) { for (EditorElement element : children) {
if (element.zOrder >= 0) {
element.draw(rendererContext); element.draw(rendererContext);
} }
} }
}
public void addElement(@NonNull EditorElement element) { public void addElement(@NonNull EditorElement element) {
children.add(element); children.add(element);
@ -252,6 +255,10 @@ public final class EditorElement implements Parcelable {
animationMatrix = AnimationMatrix.singlePulse(scale, invalidate); animationMatrix = AnimationMatrix.singlePulse(scale, invalidate);
} }
public int getZOrder() {
return zOrder;
}
public interface PerElementFunction { public interface PerElementFunction {
void apply(EditorElement element); void apply(EditorElement element);
} }

View File

@ -35,6 +35,7 @@ import java.util.UUID;
*/ */
public final class EditorModel implements Parcelable, RendererContext.Ready { public final class EditorModel implements Parcelable, RendererContext.Ready {
public static final int Z_MASK = -1;
public static final int Z_DRAWING = 0; public static final int Z_DRAWING = 0;
public static final int Z_STICKERS = 0; public static final int Z_STICKERS = 0;
public static final int Z_TEXT = 1; public static final int Z_TEXT = 1;

View File

@ -98,6 +98,8 @@ public final class BezierDrawingRenderer extends InvalidateableRenderer implemen
int alpha = paint.getAlpha(); int alpha = paint.getAlpha();
paint.setAlpha(rendererContext.getAlpha(alpha)); paint.setAlpha(rendererContext.getAlpha(alpha));
paint.setXfermode(rendererContext.getMaskPaint() != null ? rendererContext.getMaskPaint().getXfermode() : null);
bezierLine.draw(canvas, paint); bezierLine.draw(canvas, paint);
paint.setAlpha(alpha); paint.setAlpha(alpha);

View File

@ -2,13 +2,20 @@ package org.thoughtcrime.securesms.scribbles;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BlurMaskFilter;
import android.graphics.Matrix; import android.graphics.Matrix;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Point; import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF; import android.graphics.RectF;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.os.Parcel; import android.os.Parcel;
import android.renderscript.Allocation;
import android.renderscript.Element;
import android.renderscript.RenderScript;
import android.renderscript.ScriptIntrinsicBlur;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -20,6 +27,8 @@ import com.bumptech.glide.request.transition.Transition;
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.imageeditor.model.EditorElement;
import org.thoughtcrime.securesms.imageeditor.model.EditorModel;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequest; import org.thoughtcrime.securesms.mms.GlideRequest;
@ -43,8 +52,10 @@ final class UriGlideRenderer implements Renderer {
private final int maxWidth; private final int maxWidth;
private final int maxHeight; private final int maxHeight;
@Nullable @Nullable private Bitmap bitmap;
private Bitmap bitmap; @Nullable private Bitmap blurredBitmap;
@Nullable private Paint blurPaint;
@Nullable private BlurMaskFilter blurMaskFilter;
UriGlideRenderer(@NonNull Uri imageUri, boolean decryptable, int maxWidth, int maxHeight) { UriGlideRenderer(@NonNull Uri imageUri, boolean decryptable, int maxWidth, int maxHeight) {
this.imageUri = imageUri; this.imageUri = imageUri;
@ -94,17 +105,52 @@ final class UriGlideRenderer implements Renderer {
int alpha = paint.getAlpha(); int alpha = paint.getAlpha();
paint.setAlpha(rendererContext.getAlpha(alpha)); paint.setAlpha(rendererContext.getAlpha(alpha));
rendererContext.canvas.drawBitmap(bitmap, 0, 0, paint); rendererContext.canvas.drawBitmap(bitmap, 0, 0, rendererContext.getMaskPaint() != null ? rendererContext.getMaskPaint() : paint);
paint.setAlpha(alpha); paint.setAlpha(alpha);
rendererContext.restore(); rendererContext.restore();
renderBlurOverlay(rendererContext);
} else if (rendererContext.isBlockingLoad()) { } else if (rendererContext.isBlockingLoad()) {
// If failed to load, we draw a black out, in case image was sticker positioned to cover private info. // If failed to load, we draw a black out, in case image was sticker positioned to cover private info.
rendererContext.canvas.drawRect(Bounds.FULL_BOUNDS, paint); rendererContext.canvas.drawRect(Bounds.FULL_BOUNDS, paint);
} }
} }
private void renderBlurOverlay(RendererContext rendererContext) {
boolean renderMask = false;
for (EditorElement child : rendererContext.getChildren()) {
if (child.getZOrder() == EditorModel.Z_MASK) {
renderMask = true;
if (blurMaskFilter == null) {
blurMaskFilter = new BlurMaskFilter(4, BlurMaskFilter.Blur.NORMAL); // This blurs edges of the mask shapes
}
if (blurPaint == null) {
blurPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
blurPaint.setMaskFilter(blurMaskFilter);
}
blurPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
rendererContext.setMaskPaint(blurPaint);
child.draw(rendererContext);
}
}
if (renderMask) {
rendererContext.save();
rendererContext.canvasMatrix.concat(imageProjectionMatrix);
blurPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));
blurPaint.setMaskFilter(null);
if (blurredBitmap == null) blurredBitmap = blur(bitmap, rendererContext.context);
rendererContext.canvas.drawBitmap(blurredBitmap, 0, 0, blurPaint);
blurPaint.setXfermode(null);
rendererContext.restore();
}
}
private GlideRequest<Bitmap> getBitmapGlideRequest(@NonNull Context context, boolean preview) { private GlideRequest<Bitmap> getBitmapGlideRequest(@NonNull Context context, boolean preview) {
int width = this.maxWidth; int width = this.maxWidth;
int height = this.maxHeight; int height = this.maxHeight;
@ -177,6 +223,21 @@ final class UriGlideRenderer implements Renderer {
return matrix; return matrix;
} }
private static @NonNull Bitmap blur(@NonNull Bitmap bitmap, @NonNull Context context) {
RenderScript rs = RenderScript.create(context);
Allocation input = Allocation.createFromBitmap(rs, bitmap);
Allocation output = Allocation.createTyped (rs, input.getType());
ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
script.setRadius(25f);
script.setInput(input);
script.forEach(output);
Bitmap blurred = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
output.copyTo(blurred);
return blurred;
}
public static final Creator<UriGlideRenderer> CREATOR = new Creator<UriGlideRenderer>() { public static final Creator<UriGlideRenderer> CREATOR = new Creator<UriGlideRenderer>() {
@Override @Override
public UriGlideRenderer createFromParcel(Parcel in) { public UriGlideRenderer createFromParcel(Parcel in) {