Added support for blurring faces.

Co-authored-by: Alan Evans <alan@signal.org>
This commit is contained in:
Greyson Parrelli
2020-06-02 20:35:44 -04:00
parent 514048171b
commit c8dd4e5254
21 changed files with 601 additions and 71 deletions

View File

@@ -0,0 +1,10 @@
package org.thoughtcrime.securesms.scribbles;
import android.graphics.Bitmap;
import android.graphics.RectF;
import java.util.List;
interface FaceDetector {
List<RectF> detect(Bitmap bitmap);
}

View File

@@ -0,0 +1,79 @@
package org.thoughtcrime.securesms.scribbles;
import android.graphics.Bitmap;
import android.graphics.RectF;
import android.os.Build;
import com.annimon.stream.Stream;
import com.google.firebase.ml.vision.FirebaseVision;
import com.google.firebase.ml.vision.common.FirebaseVisionImage;
import com.google.firebase.ml.vision.face.FirebaseVisionFace;
import com.google.firebase.ml.vision.face.FirebaseVisionFaceDetector;
import com.google.firebase.ml.vision.face.FirebaseVisionFaceDetectorOptions;
import org.thoughtcrime.securesms.logging.Log;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
class FirebaseFaceDetector implements FaceDetector {
private static final String TAG = Log.tag(FirebaseFaceDetector.class);
private static final long MAX_SIZE = 1000 * 1000;
@Override
public List<RectF> detect(Bitmap source) {
long startTime = System.currentTimeMillis();
int performanceMode = getPerformanceMode(source);
Log.d(TAG, "Using performance mode " + performanceMode + " (API " + Build.VERSION.SDK_INT + ", " + source.getWidth() + "x" + source.getHeight() + ")");
FirebaseVisionFaceDetectorOptions options = new FirebaseVisionFaceDetectorOptions.Builder()
.setPerformanceMode(performanceMode)
.setMinFaceSize(0.05f)
.setContourMode(FirebaseVisionFaceDetectorOptions.NO_CONTOURS)
.setLandmarkMode(FirebaseVisionFaceDetectorOptions.NO_LANDMARKS)
.setClassificationMode(FirebaseVisionFaceDetectorOptions.NO_CLASSIFICATIONS)
.build();
FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(source);
List<RectF> output = new ArrayList<>();
try (FirebaseVisionFaceDetector detector = FirebaseVision.getInstance().getVisionFaceDetector(options)) {
CountDownLatch latch = new CountDownLatch(1);
detector.detectInImage(image)
.addOnSuccessListener(firebaseVisionFaces -> {
output.addAll(Stream.of(firebaseVisionFaces)
.map(FirebaseVisionFace::getBoundingBox)
.map(r -> new RectF(r.left, r.top, r.right, r.bottom))
.toList());
latch.countDown();
})
.addOnFailureListener(e -> latch.countDown());
latch.await(15, TimeUnit.SECONDS);
} catch (IOException e) {
Log.w(TAG, "Failed to close!", e);
} catch (InterruptedException e) {
Log.w(TAG, e);
}
Log.d(TAG, "Finished in " + (System.currentTimeMillis() - startTime) + " ms");
return output;
}
private static int getPerformanceMode(Bitmap source) {
if (Build.VERSION.SDK_INT < 28) {
return FirebaseVisionFaceDetectorOptions.FAST;
}
return source.getWidth() * source.getHeight() < MAX_SIZE ? FirebaseVisionFaceDetectorOptions.ACCURATE
: FirebaseVisionFaceDetectorOptions.FAST;
}
}

View File

@@ -4,6 +4,8 @@ import android.Manifest;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.RectF;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
@@ -13,15 +15,19 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.TooltipPopup;
import org.thoughtcrime.securesms.imageeditor.ColorableRenderer;
import org.thoughtcrime.securesms.imageeditor.ImageEditorView;
import org.thoughtcrime.securesms.imageeditor.Renderer;
import org.thoughtcrime.securesms.imageeditor.model.EditorElement;
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.keyvalue.SignalStore;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mediasend.MediaSendPageFragment;
import org.thoughtcrime.securesms.mms.MediaConstraints;
@@ -29,15 +35,18 @@ import org.thoughtcrime.securesms.mms.PushMediaConstraints;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.scribbles.widget.VerticalSlideColorPicker;
import org.thoughtcrime.securesms.stickers.StickerSearchRepository;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.ParcelUtil;
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
import org.whispersystems.libsignal.util.Pair;
import java.io.ByteArrayOutputStream;
import java.util.Collections;
import java.util.List;
import static android.app.Activity.RESULT_OK;
@@ -54,6 +63,8 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
private EditorModel restoredModel;
private Pair<Uri, FaceDetectionResult> cachedFaceDetection;
@Nullable private EditorElement currentSelection;
private int imageMaxHeight;
private int imageMaxWidth;
@@ -84,10 +95,10 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
}
}
private Uri imageUri;
private Controller controller;
private ImageEditorHud imageEditorHud;
private ImageEditorView imageEditorView;
private Uri imageUri;
private Controller controller;
private ImageEditorHud imageEditorHud;
private ImageEditorView imageEditorView;
public static ImageEditorFragment newInstanceForAvatar(@NonNull Uri imageUri) {
ImageEditorFragment fragment = newInstance(imageUri);
@@ -169,6 +180,11 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
imageEditorView.setModel(editorModel);
if (!SignalStore.tooltips().hasSeenBlurHudIconTooltip()) {
imageEditorHud.showBlurHudTooltip();
SignalStore.tooltips().markBlurHudIconTooltipSeen();
}
refreshUniqueColors();
}
@@ -279,12 +295,22 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
}
case DRAW: {
imageEditorView.startDrawing(0.01f, Paint.Cap.ROUND);
imageEditorView.startDrawing(0.01f, Paint.Cap.ROUND, false);
break;
}
case HIGHLIGHT: {
imageEditorView.startDrawing(0.03f, Paint.Cap.SQUARE);
imageEditorView.startDrawing(0.03f, Paint.Cap.SQUARE, false);
break;
}
case BLUR: {
imageEditorView.startDrawing(0.055f, Paint.Cap.ROUND, true);
imageEditorHud.setBlurFacesToggleEnabled(imageEditorView.getModel().hasFaceRenderer());
if (!SignalStore.tooltips().hasSeenAutoBlurFacesTooltip()) {
imageEditorHud.showAutoBlurFacesTooltip();
SignalStore.tooltips().markAutoBlurFacesTooltipSeen();
}
break;
}
@@ -316,10 +342,42 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
changeEntityColor(color);
}
@Override
public void onBlurFacesToggled(boolean enabled) {
if (!enabled) {
imageEditorView.getModel().clearFaceRenderers();
return;
}
if (cachedFaceDetection != null && cachedFaceDetection.first().equals(getUri())) {
renderFaceBlurs(cachedFaceDetection.second());
return;
} else if (cachedFaceDetection != null && !cachedFaceDetection.first().equals(getUri())) {
cachedFaceDetection = null;
}
AlertDialog progress = SimpleProgressDialog.show(requireContext());
SimpleTask.run(() -> {
Bitmap bitmap = ((UriGlideRenderer) imageEditorView.getModel().getMainImage().getRenderer()).getBitmap();
if (bitmap != null) {
FaceDetector detector = new FirebaseFaceDetector();
return new FaceDetectionResult(detector.detect(bitmap), new Point(bitmap.getWidth(), bitmap.getHeight()));
} else {
return new FaceDetectionResult(Collections.emptyList(), new Point(0, 0));
}
}, result -> {
renderFaceBlurs(result);
progress.dismiss();
});
}
@Override
public void onUndo() {
imageEditorView.getModel().undo();
refreshUniqueColors();
imageEditorHud.setBlurFacesToggleEnabled(imageEditorView.getModel().hasFaceRenderer());
}
@Override
@@ -396,7 +454,30 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
imageEditorHud.setUndoAvailability(undoAvailable);
}
private final ImageEditorView.TapListener selectionListener = new ImageEditorView.TapListener() {
private void renderFaceBlurs(@NonNull FaceDetectionResult result) {
List<RectF> faces = result.rects;
Point size = result.imageSize;
if (faces.isEmpty()) {
Toast.makeText(requireContext(), R.string.ImageEditorFragment_no_faces_detected, Toast.LENGTH_SHORT).show();
imageEditorHud.setBlurFacesToggleEnabled(false);
cachedFaceDetection = null;
return;
}
imageEditorView.getModel().pushUndoPoint();
for (RectF face : faces) {
FaceBlurRenderer faceBlurRenderer = new FaceBlurRenderer(face, size);
imageEditorView.getModel().addElementWithoutPushUndo(new EditorElement(faceBlurRenderer, EditorModel.Z_MASK));
}
imageEditorView.invalidate();
cachedFaceDetection = new Pair<>(getUri(), result);
}
private final ImageEditorView.TapListener selectionListener = new ImageEditorView.TapListener() {
@Override
public void onEntityDown(@Nullable EditorElement editorElement) {
@@ -449,4 +530,14 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
void onDoneEditing();
}
private static class FaceDetectionResult {
private final List<RectF> rects;
private final Point imageSize;
private FaceDetectionResult(@NonNull List<RectF> rects, @NonNull Point imageSize) {
this.rects = rects;
this.imageSize = imageSize;
}
}
}

View File

@@ -4,15 +4,19 @@ import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Switch;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.TooltipPopup;
import org.thoughtcrime.securesms.scribbles.widget.ColorPaletteAdapter;
import org.thoughtcrime.securesms.scribbles.widget.VerticalSlideColorPicker;
@@ -34,6 +38,7 @@ public final class ImageEditorHud extends LinearLayout {
private ImageView cropAspectLock;
private View drawButton;
private View highlightButton;
private View blurButton;
private View textButton;
private View stickerButton;
private View undoButton;
@@ -41,6 +46,8 @@ public final class ImageEditorHud extends LinearLayout {
private View deleteButton;
private View confirmButton;
private View doneButton;
private View blurToggleContainer;
private Switch blurToggle;
private VerticalSlideColorPicker colorPicker;
private RecyclerView colorPalette;
@@ -74,21 +81,24 @@ public final class ImageEditorHud extends LinearLayout {
inflate(getContext(), R.layout.image_editor_hud, this);
setOrientation(VERTICAL);
cropButton = findViewById(R.id.scribble_crop_button);
cropFlipButton = findViewById(R.id.scribble_crop_flip);
cropRotateButton = findViewById(R.id.scribble_crop_rotate);
cropAspectLock = findViewById(R.id.scribble_crop_aspect_lock);
colorPalette = findViewById(R.id.scribble_color_palette);
drawButton = findViewById(R.id.scribble_draw_button);
highlightButton = findViewById(R.id.scribble_highlight_button);
textButton = findViewById(R.id.scribble_text_button);
stickerButton = findViewById(R.id.scribble_sticker_button);
undoButton = findViewById(R.id.scribble_undo_button);
saveButton = findViewById(R.id.scribble_save_button);
deleteButton = findViewById(R.id.scribble_delete_button);
confirmButton = findViewById(R.id.scribble_confirm_button);
colorPicker = findViewById(R.id.scribble_color_picker);
doneButton = findViewById(R.id.scribble_done_button);
cropButton = findViewById(R.id.scribble_crop_button);
cropFlipButton = findViewById(R.id.scribble_crop_flip);
cropRotateButton = findViewById(R.id.scribble_crop_rotate);
cropAspectLock = findViewById(R.id.scribble_crop_aspect_lock);
colorPalette = findViewById(R.id.scribble_color_palette);
drawButton = findViewById(R.id.scribble_draw_button);
highlightButton = findViewById(R.id.scribble_highlight_button);
blurButton = findViewById(R.id.scribble_blur_button);
textButton = findViewById(R.id.scribble_text_button);
stickerButton = findViewById(R.id.scribble_sticker_button);
undoButton = findViewById(R.id.scribble_undo_button);
saveButton = findViewById(R.id.scribble_save_button);
deleteButton = findViewById(R.id.scribble_delete_button);
confirmButton = findViewById(R.id.scribble_confirm_button);
colorPicker = findViewById(R.id.scribble_color_picker);
doneButton = findViewById(R.id.scribble_done_button);
blurToggleContainer = findViewById(R.id.scribble_blur_toggle_container);
blurToggle = findViewById(R.id.scribble_blur_toggle);
cropAspectLock.setOnClickListener(v -> {
eventListener.onCropAspectLock(!eventListener.isCropAspectLocked());
@@ -105,12 +115,14 @@ public final class ImageEditorHud extends LinearLayout {
}
private void initializeVisibilityMap() {
setVisibleViewsWhenInMode(Mode.NONE, drawButton, highlightButton, textButton, stickerButton, cropButton, undoButton, saveButton);
setVisibleViewsWhenInMode(Mode.NONE, drawButton, blurButton, textButton, stickerButton, cropButton, undoButton, saveButton);
setVisibleViewsWhenInMode(Mode.DRAW, confirmButton, undoButton, colorPicker, colorPalette);
setVisibleViewsWhenInMode(Mode.HIGHLIGHT, confirmButton, undoButton, colorPicker, colorPalette);
setVisibleViewsWhenInMode(Mode.BLUR, confirmButton, undoButton, blurToggleContainer);
setVisibleViewsWhenInMode(Mode.TEXT, confirmButton, deleteButton, colorPicker, colorPalette);
setVisibleViewsWhenInMode(Mode.MOVE_DELETE, confirmButton, deleteButton);
@@ -152,11 +164,13 @@ public final class ImageEditorHud extends LinearLayout {
colorPalette.setAdapter(colorPaletteAdapter);
drawButton.setOnClickListener(v -> setMode(Mode.DRAW));
blurButton.setOnClickListener(v -> setMode(Mode.BLUR));
highlightButton.setOnClickListener(v -> setMode(Mode.HIGHLIGHT));
textButton.setOnClickListener(v -> setMode(Mode.TEXT));
stickerButton.setOnClickListener(v -> setMode(Mode.INSERT_STICKER));
saveButton.setOnClickListener(v -> eventListener.onSave());
doneButton.setOnClickListener(v -> eventListener.onDone());
blurToggle.setOnCheckedChangeListener((button, enabled) -> eventListener.onBlurFacesToggled(enabled));
}
public void setUpForAvatarEditing() {
@@ -186,6 +200,26 @@ public final class ImageEditorHud extends LinearLayout {
colorPicker.setActiveColor(color);
}
public void setBlurFacesToggleEnabled(boolean enabled) {
blurToggle.setChecked(enabled);
}
public void showBlurHudTooltip() {
TooltipPopup.forTarget(blurButton)
.setText(R.string.ImageEditorHud_new_auto_blur_faces_and_blur_brush)
.setBackgroundTint(ContextCompat.getColor(getContext(), R.color.core_ultramarine))
.setTextColor(ContextCompat.getColor(getContext(), R.color.core_white))
.show(TooltipPopup.POSITION_BELOW);
}
public void showAutoBlurFacesTooltip() {
TooltipPopup.forTarget(blurToggleContainer)
.setText(R.string.ImageEditorHud_draw_to_blur_or_try_auto_blur)
.setBackgroundTint(ContextCompat.getColor(getContext(), R.color.core_ultramarine))
.setTextColor(ContextCompat.getColor(getContext(), R.color.core_white))
.show(TooltipPopup.POSITION_ABOVE);
}
public void setEventListener(@Nullable EventListener eventListener) {
this.eventListener = eventListener != null ? eventListener : NULL_EVENT_LISTENER;
}
@@ -267,6 +301,7 @@ public final class ImageEditorHud extends LinearLayout {
TEXT,
DRAW,
HIGHLIGHT,
BLUR,
MOVE_DELETE,
INSERT_STICKER,
}
@@ -274,6 +309,7 @@ public final class ImageEditorHud extends LinearLayout {
public interface EventListener {
void onModeStarted(@NonNull Mode mode);
void onColorChange(int color);
void onBlurFacesToggled(boolean enabled);
void onUndo();
void onDelete();
void onSave();
@@ -295,6 +331,10 @@ public final class ImageEditorHud extends LinearLayout {
public void onColorChange(int color) {
}
@Override
public void onBlurFacesToggled(boolean enabled) {
}
@Override
public void onUndo() {
}

View File

@@ -2,7 +2,6 @@ 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;
@@ -29,9 +28,11 @@ 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.logging.Log;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequest;
import org.thoughtcrime.securesms.util.BitmapUtil;
import java.util.concurrent.ExecutionException;
@@ -42,12 +43,16 @@ import java.util.concurrent.ExecutionException;
*/
final class UriGlideRenderer implements Renderer {
private static final String TAG = Log.tag(UriGlideRenderer.class);
private static final int PREVIEW_DIMENSION_LIMIT = 2048;
private static final int MAX_BLUR_DIMENSION = 300;
private final Uri imageUri;
private final Paint paint = new Paint();
private final Matrix imageProjectionMatrix = new Matrix();
private final Matrix temp = new Matrix();
private final Matrix blurScaleMatrix = new Matrix();
private final boolean decryptable;
private final int maxWidth;
private final int maxHeight;
@@ -55,7 +60,6 @@ final class UriGlideRenderer implements Renderer {
@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;
@@ -124,12 +128,8 @@ final class UriGlideRenderer implements Renderer {
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);
@@ -143,7 +143,16 @@ final class UriGlideRenderer implements Renderer {
blurPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));
blurPaint.setMaskFilter(null);
if (blurredBitmap == null) blurredBitmap = blur(bitmap, rendererContext.context);
if (blurredBitmap == null) {
blurredBitmap = blur(bitmap, rendererContext.context);
blurScaleMatrix.setRectToRect(new RectF(0, 0, blurredBitmap.getWidth(), blurredBitmap.getHeight()),
new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight()),
Matrix.ScaleToFit.FILL);
}
rendererContext.canvas.concat(blurScaleMatrix);
rendererContext.canvas.drawBitmap(blurredBitmap, 0, 0, blurPaint);
blurPaint.setXfermode(null);
@@ -197,7 +206,7 @@ final class UriGlideRenderer implements Renderer {
* Always use this getter, as Bitmap is kept in Glide's LRUCache, so it could have been recycled
* by Glide. If it has, or was never set, this method returns null.
*/
private @Nullable Bitmap getBitmap() {
public @Nullable Bitmap getBitmap() {
if (bitmap != null && bitmap.isRecycled()) {
bitmap = null;
}
@@ -223,21 +232,48 @@ final class UriGlideRenderer implements Renderer {
return matrix;
}
private static @NonNull Bitmap blur(@NonNull Bitmap bitmap, @NonNull Context context) {
private static @NonNull Bitmap blur(Bitmap bitmap, Context context) {
Point previewSize = scaleKeepingAspectRatio(new Point(bitmap.getWidth(), bitmap.getHeight()), PREVIEW_DIMENSION_LIMIT);
Point blurSize = scaleKeepingAspectRatio(new Point(previewSize.x / 2, previewSize.y / 2 ), MAX_BLUR_DIMENSION);
Bitmap small = BitmapUtil.createScaledBitmap(bitmap, blurSize.x, blurSize.y);
Log.d(TAG, "Bitmap: " + bitmap.getWidth() + "x" + bitmap.getHeight() + ", Blur: " + blurSize.x + "x" + blurSize.y);
RenderScript rs = RenderScript.create(context);
Allocation input = Allocation.createFromBitmap(rs, bitmap);
Allocation output = Allocation.createTyped (rs, input.getType());
Allocation input = Allocation.createFromBitmap(rs, small);
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());
Bitmap blurred = Bitmap.createBitmap(small.getWidth(), small.getHeight(), small.getConfig());
output.copyTo(blurred);
return blurred;
}
private static @NonNull Point scaleKeepingAspectRatio(@NonNull Point dimens, int maxDimen) {
int outX = dimens.x;
int outY = dimens.y;
if (dimens.x > maxDimen || dimens.y > maxDimen) {
outX = maxDimen;
outY = maxDimen;
float widthRatio = dimens.x / (float) maxDimen;
float heightRatio = dimens.y / (float) maxDimen;
if (widthRatio > heightRatio) {
outY = (int) (dimens.y / widthRatio);
} else {
outX = (int) (dimens.x / heightRatio);
}
}
return new Point(outX, outY);
}
public static final Creator<UriGlideRenderer> CREATOR = new Creator<UriGlideRenderer>() {
@Override
public UriGlideRenderer createFromParcel(Parcel in) {