That fuck shit the fascists are using
at master 340 lines 12 kB view raw
1package org.tm.archive.scribbles; 2 3import android.content.Context; 4import android.graphics.Bitmap; 5import android.graphics.Matrix; 6import android.graphics.Paint; 7import android.graphics.Point; 8import android.graphics.PorterDuff; 9import android.graphics.PorterDuffXfermode; 10import android.graphics.RectF; 11import android.graphics.drawable.Drawable; 12import android.net.Uri; 13import android.os.Parcel; 14import android.renderscript.Allocation; 15import android.renderscript.Element; 16import android.renderscript.RenderScript; 17import android.renderscript.ScriptIntrinsicBlur; 18 19import androidx.annotation.NonNull; 20import androidx.annotation.Nullable; 21 22import com.bumptech.glide.Glide; 23import com.bumptech.glide.RequestBuilder; 24import com.bumptech.glide.request.RequestListener; 25import com.bumptech.glide.request.target.CustomTarget; 26import com.bumptech.glide.request.transition.Transition; 27 28import org.signal.core.util.logging.Log; 29import org.signal.imageeditor.core.Bounds; 30import org.signal.imageeditor.core.Renderer; 31import org.signal.imageeditor.core.RendererContext; 32import org.signal.imageeditor.core.SelectableRenderer; 33import org.signal.imageeditor.core.model.EditorElement; 34import org.signal.imageeditor.core.model.EditorModel; 35import org.tm.archive.mms.DecryptableStreamUriLoader; 36import org.tm.archive.util.BitmapUtil; 37 38import java.util.concurrent.ExecutionException; 39 40/** 41 * Uses Glide to load an image and implements a {@link Renderer}. 42 * 43 * The image can be encrypted. 44 */ 45public final class UriGlideRenderer implements SelectableRenderer { 46 47 private static final String TAG = Log.tag(UriGlideRenderer.class); 48 49 private static final int PREVIEW_DIMENSION_LIMIT = 2048; 50 private static final int MAX_BLUR_DIMENSION = 300; 51 52 public static final float WEAK_BLUR = 3f; 53 public static final float STRONG_BLUR = 25f; 54 55 private final Uri imageUri; 56 private final Paint paint = new Paint(); 57 private final Matrix imageProjectionMatrix = new Matrix(); 58 private final Matrix temp = new Matrix(); 59 private final Matrix blurScaleMatrix = new Matrix(); 60 private final boolean decryptable; 61 private final int maxWidth; 62 private final int maxHeight; 63 private final float blurRadius; 64 private final RequestListener<Bitmap> bitmapRequestListener; 65 66 private boolean selected; 67 68 @Nullable private Bitmap bitmap; 69 @Nullable private Bitmap blurredBitmap; 70 @Nullable private Paint blurPaint; 71 72 public UriGlideRenderer(@NonNull Uri imageUri, boolean decryptable, int maxWidth, int maxHeight) { 73 this(imageUri, decryptable, maxWidth, maxHeight, STRONG_BLUR); 74 } 75 76 public UriGlideRenderer(@NonNull Uri imageUri, boolean decryptable, int maxWidth, int maxHeight, float blurRadius) { 77 this(imageUri, decryptable, maxWidth, maxHeight, blurRadius, null); 78 } 79 80 public UriGlideRenderer(@NonNull Uri imageUri, boolean decryptable, int maxWidth, int maxHeight, float blurRadius, @Nullable RequestListener<Bitmap> bitmapRequestListener) { 81 this.imageUri = imageUri; 82 this.decryptable = decryptable; 83 this.maxWidth = maxWidth; 84 this.maxHeight = maxHeight; 85 this.blurRadius = blurRadius; 86 this.bitmapRequestListener = bitmapRequestListener; 87 paint.setAntiAlias(true); 88 paint.setFilterBitmap(true); 89 paint.setDither(true); 90 } 91 92 @Override 93 public void render(@NonNull RendererContext rendererContext) { 94 if (getBitmap() == null) { 95 if (rendererContext.isBlockingLoad()) { 96 try { 97 Bitmap bitmap = getGlideRequestBuilder(rendererContext.context, false).submit().get(); 98 setBitmap(rendererContext, bitmap); 99 } catch (ExecutionException | InterruptedException e) { 100 throw new RuntimeException(e); 101 } 102 } else { 103 getGlideRequestBuilder(rendererContext.context, true).into(new CustomTarget<Bitmap>() { 104 @Override 105 public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) { 106 setBitmap(rendererContext, resource); 107 108 rendererContext.invalidate.onInvalidate(UriGlideRenderer.this); 109 } 110 111 @Override 112 public void onLoadCleared(@Nullable Drawable placeholder) { 113 bitmap = null; 114 } 115 }); 116 } 117 } 118 119 final Bitmap bitmap = getBitmap(); 120 if (bitmap != null) { 121 rendererContext.save(); 122 123 rendererContext.canvasMatrix.concat(imageProjectionMatrix); 124 125 // Units are image level pixels at this point. 126 127 int alpha = paint.getAlpha(); 128 paint.setAlpha(rendererContext.getAlpha(alpha)); 129 130 rendererContext.canvas.drawBitmap(bitmap, 0, 0, rendererContext.getMaskPaint() != null ? rendererContext.getMaskPaint() : paint); 131 132 paint.setAlpha(alpha); 133 134 rendererContext.restore(); 135 136 renderBlurOverlay(rendererContext); 137 } else if (rendererContext.isBlockingLoad()) { 138 // If failed to load, we draw a black out, in case image was sticker positioned to cover private info. 139 rendererContext.canvas.drawRect(Bounds.FULL_BOUNDS, paint); 140 } 141 } 142 143 private void renderBlurOverlay(RendererContext rendererContext) { 144 boolean renderMask = false; 145 146 for (EditorElement child : rendererContext.getChildren()) { 147 if (child.getZOrder() == EditorModel.Z_MASK) { 148 renderMask = true; 149 if (blurPaint == null) { 150 blurPaint = new Paint(); 151 blurPaint.setAntiAlias(true); 152 blurPaint.setFilterBitmap(true); 153 blurPaint.setDither(true); 154 } 155 blurPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); 156 rendererContext.setMaskPaint(blurPaint); 157 child.draw(rendererContext); 158 } 159 } 160 161 if (renderMask) { 162 rendererContext.save(); 163 rendererContext.canvasMatrix.concat(imageProjectionMatrix); 164 165 blurPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP)); 166 blurPaint.setMaskFilter(null); 167 168 if (blurredBitmap == null) { 169 blurredBitmap = blur(bitmap, rendererContext.context, blurRadius); 170 171 blurScaleMatrix.setRectToRect(new RectF(0, 0, blurredBitmap.getWidth(), blurredBitmap.getHeight()), 172 new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight()), 173 Matrix.ScaleToFit.FILL); 174 } 175 176 rendererContext.canvas.concat(blurScaleMatrix); 177 rendererContext.canvas.drawBitmap(blurredBitmap, 0, 0, blurPaint); 178 blurPaint.setXfermode(null); 179 180 rendererContext.restore(); 181 } 182 } 183 184 private RequestBuilder<Bitmap> getGlideRequestBuilder(@NonNull Context context, boolean preview) { 185 int width = this.maxWidth; 186 int height = this.maxHeight; 187 188 if (preview) { 189 width = Math.min(width, PREVIEW_DIMENSION_LIMIT); 190 height = Math.min(height, PREVIEW_DIMENSION_LIMIT); 191 } 192 193 return Glide.with(context) 194 .asBitmap() 195 .override(width, height) 196 .centerInside() 197 .addListener(bitmapRequestListener) 198 .load(decryptable ? new DecryptableStreamUriLoader.DecryptableUri(imageUri) : imageUri); 199 } 200 201 @Override 202 public boolean hitTest(float x, float y) { 203 return selected ? Bounds.contains(x, y) : pixelAlphaNotZero(x, y); 204 } 205 206 private boolean pixelAlphaNotZero(float x, float y) { 207 Bitmap bitmap = getBitmap(); 208 209 if (bitmap == null) return false; 210 211 imageProjectionMatrix.invert(temp); 212 213 float[] onBmp = new float[2]; 214 temp.mapPoints(onBmp, new float[]{ x, y }); 215 216 int xInt = (int) onBmp[0]; 217 int yInt = (int) onBmp[1]; 218 219 if (xInt >= 0 && xInt < bitmap.getWidth() && yInt >= 0 && yInt < bitmap.getHeight()) { 220 return (bitmap.getPixel(xInt, yInt) & 0xff000000) != 0; 221 } else { 222 return false; 223 } 224 } 225 226 /** 227 * Always use this getter, as Bitmap is kept in Glide's LRUCache, so it could have been recycled 228 * by Glide. If it has, or was never set, this method returns null. 229 */ 230 public @Nullable Bitmap getBitmap() { 231 if (bitmap != null && bitmap.isRecycled()) { 232 bitmap = null; 233 } 234 return bitmap; 235 } 236 237 private void setBitmap(@NonNull RendererContext rendererContext, @Nullable Bitmap bitmap) { 238 this.bitmap = bitmap; 239 if (bitmap != null) { 240 RectF from = new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight()); 241 imageProjectionMatrix.setRectToRect(from, Bounds.FULL_BOUNDS, Matrix.ScaleToFit.CENTER); 242 rendererContext.rendererReady.onReady(UriGlideRenderer.this, cropMatrix(bitmap), new Point(bitmap.getWidth(), bitmap.getHeight())); 243 } 244 } 245 246 private static Matrix cropMatrix(Bitmap bitmap) { 247 Matrix matrix = new Matrix(); 248 if (bitmap.getWidth() > bitmap.getHeight()) { 249 matrix.preScale(1, ((float) bitmap.getHeight()) / bitmap.getWidth()); 250 } else { 251 matrix.preScale(((float) bitmap.getWidth()) / bitmap.getHeight(), 1); 252 } 253 return matrix; 254 } 255 256 private static @NonNull Bitmap blur(Bitmap bitmap, Context context, float blurRadius) { 257 Point previewSize = scaleKeepingAspectRatio(new Point(bitmap.getWidth(), bitmap.getHeight()), PREVIEW_DIMENSION_LIMIT); 258 Point blurSize = scaleKeepingAspectRatio(new Point(previewSize.x / 2, previewSize.y / 2 ), MAX_BLUR_DIMENSION); 259 Bitmap small = BitmapUtil.createScaledBitmap(bitmap, blurSize.x, blurSize.y); 260 261 Log.d(TAG, "Bitmap: " + bitmap.getWidth() + "x" + bitmap.getHeight() + ", Blur: " + blurSize.x + "x" + blurSize.y); 262 263 RenderScript rs = RenderScript.create(context); 264 Allocation input = Allocation.createFromBitmap(rs, small); 265 Allocation output = Allocation.createTyped(rs, input.getType()); 266 ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); 267 268 script.setRadius(blurRadius); 269 script.setInput(input); 270 script.forEach(output); 271 272 Bitmap blurred = Bitmap.createBitmap(small.getWidth(), small.getHeight(), small.getConfig()); 273 output.copyTo(blurred); 274 return blurred; 275 } 276 277 private static @NonNull Point scaleKeepingAspectRatio(@NonNull Point dimens, int maxDimen) { 278 int outX = dimens.x; 279 int outY = dimens.y; 280 281 if (dimens.x > maxDimen || dimens.y > maxDimen) { 282 outX = maxDimen; 283 outY = maxDimen; 284 285 float widthRatio = dimens.x / (float) maxDimen; 286 float heightRatio = dimens.y / (float) maxDimen; 287 288 if (widthRatio > heightRatio) { 289 outY = (int) (dimens.y / widthRatio); 290 } else { 291 outX = (int) (dimens.x / heightRatio); 292 } 293 } 294 295 return new Point(outX, outY); 296 } 297 298 public static final Creator<UriGlideRenderer> CREATOR = new Creator<UriGlideRenderer>() { 299 @Override 300 public UriGlideRenderer createFromParcel(Parcel in) { 301 return new UriGlideRenderer(Uri.parse(in.readString()), 302 in.readInt() == 1, 303 in.readInt(), 304 in.readInt(), 305 in.readFloat() 306 ); 307 } 308 309 @Override 310 public UriGlideRenderer[] newArray(int size) { 311 return new UriGlideRenderer[size]; 312 } 313 }; 314 315 @Override 316 public int describeContents() { 317 return 0; 318 } 319 320 @Override 321 public void writeToParcel(Parcel dest, int flags) { 322 dest.writeString(imageUri.toString()); 323 dest.writeInt(decryptable ? 1 : 0); 324 dest.writeInt(maxWidth); 325 dest.writeInt(maxHeight); 326 dest.writeFloat(blurRadius); 327 } 328 329 @Override 330 public void onSelected(boolean selected) { 331 if (this.selected != selected) { 332 this.selected = selected; 333 } 334 } 335 336 @Override 337 public void getSelectionBounds(@NonNull RectF bounds) { 338 bounds.set(Bounds.FULL_BOUNDS); 339 } 340}