diff --git a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/RendererContext.java b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/RendererContext.java
index 0883d6fcd1..4b510e6d03 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/RendererContext.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/RendererContext.java
@@ -3,11 +3,17 @@ package org.thoughtcrime.securesms.imageeditor;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Matrix;
+import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.RectF;
import androidx.annotation.NonNull;
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.
*
@@ -38,6 +44,9 @@ public final class RendererContext {
private boolean isEditing = true;
+ private List children = Collections.emptyList();
+ private Paint maskPaint;
+
public RendererContext(@NonNull Context context, @NonNull Canvas canvas, @NonNull Ready rendererReady, @NonNull Invalidate invalidate) {
this.context = context;
this.canvas = canvas;
@@ -100,6 +109,22 @@ public final class RendererContext {
canvasMatrix.getCurrent(into);
}
+ public void setChildren(@NonNull List children) {
+ this.children = children;
+ }
+
+ public @NonNull List getChildren() {
+ return children;
+ }
+
+ public void setMaskPaint(@Nullable Paint maskPaint) {
+ this.maskPaint = maskPaint;
+ }
+
+ public @Nullable Paint getMaskPaint() {
+ return maskPaint;
+ }
+
public interface Ready {
Ready NULL = (renderer, cropMatrix, size) -> {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorElement.java b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorElement.java
index cb30c3106f..26d10d8556 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorElement.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorElement.java
@@ -97,7 +97,7 @@ public final class EditorElement implements Parcelable {
*
* @param rendererContext Canvas to draw on to.
*/
- void draw(@NonNull RendererContext rendererContext) {
+ public void draw(@NonNull RendererContext rendererContext) {
if (!flags.isVisible() && !flags.isChildrenVisible()) return;
rendererContext.save();
@@ -113,6 +113,7 @@ public final class EditorElement implements Parcelable {
float alpha = alphaAnimation.getValue();
if (alpha > 0) {
rendererContext.setFade(alpha);
+ rendererContext.setChildren(children);
drawSelf(rendererContext);
rendererContext.setFade(1f);
}
@@ -133,7 +134,9 @@ public final class EditorElement implements Parcelable {
private static void drawChildren(@NonNull List children, @NonNull RendererContext rendererContext) {
for (EditorElement element : children) {
- element.draw(rendererContext);
+ if (element.zOrder >= 0) {
+ element.draw(rendererContext);
+ }
}
}
@@ -252,6 +255,10 @@ public final class EditorElement implements Parcelable {
animationMatrix = AnimationMatrix.singlePulse(scale, invalidate);
}
+ public int getZOrder() {
+ return zOrder;
+ }
+
public interface PerElementFunction {
void apply(EditorElement element);
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorModel.java b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorModel.java
index 79cca5babc..7a95f9831f 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorModel.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorModel.java
@@ -35,6 +35,7 @@ import java.util.UUID;
*/
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_STICKERS = 0;
public static final int Z_TEXT = 1;
diff --git a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/renderers/BezierDrawingRenderer.java b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/renderers/BezierDrawingRenderer.java
index c69cb452e1..6617508b5f 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/renderers/BezierDrawingRenderer.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/renderers/BezierDrawingRenderer.java
@@ -98,6 +98,8 @@ public final class BezierDrawingRenderer extends InvalidateableRenderer implemen
int alpha = paint.getAlpha();
paint.setAlpha(rendererContext.getAlpha(alpha));
+ paint.setXfermode(rendererContext.getMaskPaint() != null ? rendererContext.getMaskPaint().getXfermode() : null);
+
bezierLine.draw(canvas, paint);
paint.setAlpha(alpha);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/scribbles/UriGlideRenderer.java b/app/src/main/java/org/thoughtcrime/securesms/scribbles/UriGlideRenderer.java
index 37f1a206c7..faf51e34b1 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/scribbles/UriGlideRenderer.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/scribbles/UriGlideRenderer.java
@@ -2,13 +2,20 @@ package org.thoughtcrime.securesms.scribbles;
import android.content.Context;
import android.graphics.Bitmap;
+import android.graphics.BlurMaskFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Point;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.net.Uri;
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.Nullable;
@@ -20,6 +27,8 @@ import com.bumptech.glide.request.transition.Transition;
import org.thoughtcrime.securesms.imageeditor.Bounds;
import org.thoughtcrime.securesms.imageeditor.Renderer;
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.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequest;
@@ -43,8 +52,10 @@ final class UriGlideRenderer implements Renderer {
private final int maxWidth;
private final int maxHeight;
- @Nullable
- private Bitmap bitmap;
+ @Nullable 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) {
this.imageUri = imageUri;
@@ -94,17 +105,52 @@ final class UriGlideRenderer implements Renderer {
int alpha = paint.getAlpha();
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);
rendererContext.restore();
+
+ renderBlurOverlay(rendererContext);
} else if (rendererContext.isBlockingLoad()) {
// 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);
}
}
+ 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 getBitmapGlideRequest(@NonNull Context context, boolean preview) {
int width = this.maxWidth;
int height = this.maxHeight;
@@ -177,6 +223,21 @@ final class UriGlideRenderer implements Renderer {
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 CREATOR = new Creator() {
@Override
public UriGlideRenderer createFromParcel(Parcel in) {