mirror of
https://github.com/oxen-io/session-android.git
synced 2025-06-09 05:28:34 +00:00
Add Image Editor support for blur mask layer.
This commit is contained in:
parent
32e9901592
commit
514048171b
@ -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) -> {
|
||||||
|
@ -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,7 +134,9 @@ 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) {
|
||||||
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);
|
animationMatrix = AnimationMatrix.singlePulse(scale, invalidate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getZOrder() {
|
||||||
|
return zOrder;
|
||||||
|
}
|
||||||
|
|
||||||
public interface PerElementFunction {
|
public interface PerElementFunction {
|
||||||
void apply(EditorElement element);
|
void apply(EditorElement element);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user