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 = null;
try {
facePos = detectFace(testBmp);
} catch (OutOfMemoryError e) {
L.logRemoteErrorIfProd(e);
}
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);
}
}