Dbrant has uploaded a new change for review.
https://gerrit.wikimedia.org/r/319063
Change subject: Fix possible leak in FacePostprocessor.
......................................................................
Fix possible leak in FacePostprocessor.
Observed in API 17, this leak seems to happen because the
FacePostprocessor class implicitly retains an instance of the
FaceAndColorDetectImageView of which it is a member.
This patch separates out the FacePostprocessor class, along with its
concerns, and makes sure that it doesn't keep any references to the View
that uses it.
Change-Id: I6e637b4e5fe676f9b6bc13f1e2ff21d1c500bbaa
---
M app/src/main/java/org/wikipedia/views/FaceAndColorDetectImageView.java
A app/src/main/java/org/wikipedia/views/FacePostprocessor.java
2 files changed, 115 insertions(+), 92 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/apps/android/wikipedia
refs/changes/63/319063/1
diff --git
a/app/src/main/java/org/wikipedia/views/FaceAndColorDetectImageView.java
b/app/src/main/java/org/wikipedia/views/FaceAndColorDetectImageView.java
index 0e7ba67..c8d6ad8 100644
--- a/app/src/main/java/org/wikipedia/views/FaceAndColorDetectImageView.java
+++ b/app/src/main/java/org/wikipedia/views/FaceAndColorDetectImageView.java
@@ -1,41 +1,28 @@
package org.wikipedia.views;
import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
import android.graphics.PointF;
-import android.graphics.Rect;
-import android.media.FaceDetector;
import android.net.Uri;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.v7.graphics.Palette;
import android.util.AttributeSet;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.drawee.view.SimpleDraweeView;
-import com.facebook.imagepipeline.request.BasePostprocessor;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
-import org.wikipedia.R;
import org.wikipedia.WikipediaApp;
-import org.wikipedia.util.MathUtil;
-import org.wikipedia.util.log.L;
public class FaceAndColorDetectImageView extends SimpleDraweeView {
- private static final int BITMAP_COPY_WIDTH = 200;
public interface OnImageLoadListener {
void onImageLoaded(int bmpHeight, @Nullable PointF faceLocation,
@ColorInt int mainColor);
void onImageFailed();
}
- private FacePostprocessor facePostprocessor = new FacePostprocessor();
@NonNull private OnImageLoadListener listener = new DefaultListener();
public FaceAndColorDetectImageView(Context context) {
@@ -61,91 +48,13 @@
}
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setProgressiveRenderingEnabled(true)
- .setPostprocessor(facePostprocessor)
+ .setPostprocessor(new FacePostprocessor(listener))
.build();
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setImageRequest(request)
.setAutoPlayAnimations(true)
.build();
setController(controller);
- }
-
- @Nullable private PointF detectFace(Bitmap testBitmap) {
- final int maxFaces = 1;
- long millis = System.currentTimeMillis();
- // initialize the face detector, and look for only one face...
- FaceDetector fd = new FaceDetector(testBitmap.getWidth(),
testBitmap.getHeight(), maxFaces);
- FaceDetector.Face[] faces = new FaceDetector.Face[maxFaces];
- int numFound = fd.findFaces(testBitmap, faces);
- PointF facePos = null;
- if (numFound > 0) {
- facePos = new PointF();
- faces[0].getMidPoint(facePos);
- // center on the nose, not on the eyes
- facePos.y += faces[0].eyesDistance() / 2;
- // normalize the position to [0, 1]
- facePos.set(MathUtil.constrain(facePos.x / testBitmap.getWidth(),
0, 1),
- MathUtil.constrain(facePos.y / testBitmap.getHeight(), 0,
1));
- L.d("Found face at " + facePos.x + ", " + facePos.y);
- }
- L.d("Face detection took " + (System.currentTimeMillis() - millis) +
"ms");
- return facePos;
- }
-
- @NonNull private Bitmap new565ScaledBitmap(Bitmap src) {
- Bitmap copy = Bitmap.createBitmap(BITMAP_COPY_WIDTH,
- (src.getHeight() * BITMAP_COPY_WIDTH) / src.getWidth(),
Bitmap.Config.RGB_565);
- Canvas canvas = new Canvas(copy);
- Rect srcRect = new Rect(0, 0, src.getWidth(), src.getHeight());
- Rect destRect = new Rect(0, 0, BITMAP_COPY_WIDTH, copy.getHeight());
- Paint paint = new Paint();
- paint.setColor(Color.BLACK);
- canvas.drawBitmap(src, srcRect, destRect, paint);
- return copy;
- }
-
- @ColorInt private int extractMainColor(Palette colorPalette, @ColorInt int
defaultColor) {
- int mainColor = defaultColor;
- if (colorPalette.getDarkMutedSwatch() != null) {
- mainColor = colorPalette.getDarkMutedSwatch().getRgb();
- } else if (colorPalette.getDarkVibrantSwatch() != null) {
- mainColor = colorPalette.getDarkVibrantSwatch().getRgb();
- }
- return mainColor;
- }
-
- private boolean isBitmapEligibleForImageProcessing(Bitmap bitmap) {
- final int minSize = 64;
- return bitmap.getWidth() >= minSize && bitmap.getHeight() >= minSize;
- }
-
- private class FacePostprocessor extends BasePostprocessor {
- @Override
- public String getName() {
- return "FacePostprocessor";
- }
-
- @Override
- public void process(Bitmap destBitmap, Bitmap sourceBitmap) {
- if (isBitmapEligibleForImageProcessing(sourceBitmap)) {
- copyWithWhiteBackground(destBitmap, sourceBitmap);
- Bitmap testBmp = new565ScaledBitmap(sourceBitmap);
- Palette colorPalette = Palette.from(testBmp).generate();
- PointF facePos = detectFace(testBmp);
- int defaultColor =
getContext().getResources().getColor(R.color.dark_gray);
- listener.onImageLoaded(destBitmap.getHeight(), facePos,
extractMainColor(colorPalette, defaultColor));
- } else {
- listener.onImageFailed();
- }
- }
-
- private void copyWithWhiteBackground(Bitmap destBitmap, Bitmap
sourceBitmap) {
- Canvas canvas = new Canvas(destBitmap);
- Paint backgroundPaint = new Paint();
- backgroundPaint.setColor(Color.WHITE);
- canvas.drawRect(0f, 0f, destBitmap.getWidth(),
destBitmap.getHeight(), backgroundPaint);
- canvas.drawBitmap(sourceBitmap, 0f, 0f, backgroundPaint);
- }
}
private class DefaultListener implements OnImageLoadListener {
diff --git a/app/src/main/java/org/wikipedia/views/FacePostprocessor.java
b/app/src/main/java/org/wikipedia/views/FacePostprocessor.java
new file mode 100644
index 0000000..1c41b87
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/views/FacePostprocessor.java
@@ -0,0 +1,114 @@
+package org.wikipedia.views;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.media.FaceDetector;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.graphics.Palette;
+
+import com.facebook.imagepipeline.request.BasePostprocessor;
+
+import org.wikipedia.R;
+import org.wikipedia.WikipediaApp;
+import org.wikipedia.util.MathUtil;
+import org.wikipedia.util.log.L;
+
+import java.lang.ref.WeakReference;
+
+public class FacePostprocessor extends BasePostprocessor {
+ private static final int BITMAP_COPY_WIDTH = 200;
+
+ @NonNull
+ private WeakReference<FaceAndColorDetectImageView.OnImageLoadListener>
listener;
+
+ public FacePostprocessor(@NonNull
FaceAndColorDetectImageView.OnImageLoadListener listener) {
+ this.listener = new WeakReference<>(listener);
+ }
+
+ @Override public String getName() {
+ return "FacePostprocessor";
+ }
+
+ @Override public void process(Bitmap destBitmap, Bitmap sourceBitmap) {
+ if (listener.get() == null) {
+ return;
+ }
+ if (isBitmapEligibleForImageProcessing(sourceBitmap)) {
+ copyWithWhiteBackground(destBitmap, sourceBitmap);
+ Bitmap testBmp = new565ScaledBitmap(sourceBitmap);
+ Palette colorPalette = Palette.from(testBmp).generate();
+ PointF facePos = detectFace(testBmp);
+ int defaultColor =
ContextCompat.getColor(WikipediaApp.getInstance(), R.color.dark_gray);
+ listener.get().onImageLoaded(destBitmap.getHeight(), facePos,
+ extractMainColor(colorPalette, defaultColor));
+ } else {
+ listener.get().onImageFailed();
+ }
+ }
+
+ @Nullable private static PointF detectFace(@NonNull Bitmap testBitmap) {
+ final int maxFaces = 1;
+ long millis = System.currentTimeMillis();
+ // initialize the face detector, and look for only one face...
+ FaceDetector fd = new FaceDetector(testBitmap.getWidth(),
testBitmap.getHeight(), maxFaces);
+ FaceDetector.Face[] faces = new FaceDetector.Face[maxFaces];
+ int numFound = fd.findFaces(testBitmap, faces);
+ PointF facePos = null;
+ if (numFound > 0) {
+ facePos = new PointF();
+ faces[0].getMidPoint(facePos);
+ // center on the nose, not on the eyes
+ facePos.y += faces[0].eyesDistance() / 2;
+ // normalize the position to [0, 1]
+ facePos.set(MathUtil.constrain(facePos.x / testBitmap.getWidth(),
0, 1),
+ MathUtil.constrain(facePos.y / testBitmap.getHeight(), 0,
1));
+ L.d("Found face at " + facePos.x + ", " + facePos.y);
+ }
+ L.d("Face detection took " + (System.currentTimeMillis() - millis) +
"ms");
+ return facePos;
+ }
+
+ @NonNull private static Bitmap new565ScaledBitmap(@NonNull Bitmap src) {
+ Bitmap copy = Bitmap.createBitmap(BITMAP_COPY_WIDTH,
+ (src.getHeight() * BITMAP_COPY_WIDTH) / src.getWidth(),
Bitmap.Config.RGB_565);
+ Canvas canvas = new Canvas(copy);
+ Rect srcRect = new Rect(0, 0, src.getWidth(), src.getHeight());
+ Rect destRect = new Rect(0, 0, BITMAP_COPY_WIDTH, copy.getHeight());
+ Paint paint = new Paint();
+ paint.setColor(Color.BLACK);
+ canvas.drawBitmap(src, srcRect, destRect, paint);
+ return copy;
+ }
+
+ @ColorInt private static int extractMainColor(@NonNull Palette
colorPalette,
+ @ColorInt int defaultColor) {
+ int mainColor = defaultColor;
+ if (colorPalette.getDarkMutedSwatch() != null) {
+ mainColor = colorPalette.getDarkMutedSwatch().getRgb();
+ } else if (colorPalette.getDarkVibrantSwatch() != null) {
+ mainColor = colorPalette.getDarkVibrantSwatch().getRgb();
+ }
+ return mainColor;
+ }
+
+ private static boolean isBitmapEligibleForImageProcessing(@NonNull Bitmap
bitmap) {
+ final int minSize = 64;
+ return bitmap.getWidth() >= minSize && bitmap.getHeight() >= minSize;
+ }
+
+ private static void copyWithWhiteBackground(@NonNull Bitmap destBitmap,
+ @NonNull Bitmap sourceBitmap) {
+ Canvas canvas = new Canvas(destBitmap);
+ Paint backgroundPaint = new Paint();
+ backgroundPaint.setColor(Color.WHITE);
+ canvas.drawRect(0f, 0f, destBitmap.getWidth(), destBitmap.getHeight(),
backgroundPaint);
+ canvas.drawBitmap(sourceBitmap, 0f, 0f, backgroundPaint);
+ }
+}
--
To view, visit https://gerrit.wikimedia.org/r/319063
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I6e637b4e5fe676f9b6bc13f1e2ff21d1c500bbaa
Gerrit-PatchSet: 1
Gerrit-Project: apps/android/wikipedia
Gerrit-Branch: master
Gerrit-Owner: Dbrant <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits