jenkins-bot has submitted this change and it was merged.
Change subject: Face detection in lead image.
......................................................................
Face detection in lead image.
A few notes so far:
- on my test device (Galaxy S4), detection takes between 0.5 and 3
seconds. May end up being too slow on other devices.
- fortunately, this will be easy to disable/reenable if necessary.
TODO: research a better way of passing the bitmap from Picasso directly to
the FaceDetector. Right now, we have to create an intermediate bitmap,
because the FaceDetector requires a 5-6-5 bitmap to work properly.
Change-Id: Ic255a0974345270c10164df84508527b6f2767fc
---
M wikipedia/res/layout/fragment_page.xml
A wikipedia/src/main/java/org/wikipedia/page/leadimages/ImageViewWithFace.java
M wikipedia/src/main/java/org/wikipedia/page/leadimages/LeadImagesHandler.java
3 files changed, 207 insertions(+), 11 deletions(-)
Approvals:
BearND: Looks good to me, approved
jenkins-bot: Verified
diff --git a/wikipedia/res/layout/fragment_page.xml
b/wikipedia/res/layout/fragment_page.xml
index 1d0cced..d2685b3 100644
--- a/wikipedia/res/layout/fragment_page.xml
+++ b/wikipedia/res/layout/fragment_page.xml
@@ -30,10 +30,16 @@
android:orientation="vertical"
android:paddingTop="48dp"
>
- <ImageView
+ <org.wikipedia.page.leadimages.ImageViewWithFace
android:id="@+id/page_image_1"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:scaleType="centerCrop"
+ android:visibility="invisible"/>
+ <ImageView
+ android:id="@+id/page_image_placeholder"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:scaleType="centerCrop"/>
<FrameLayout
android:id="@+id/page_title_container"
diff --git
a/wikipedia/src/main/java/org/wikipedia/page/leadimages/ImageViewWithFace.java
b/wikipedia/src/main/java/org/wikipedia/page/leadimages/ImageViewWithFace.java
new file mode 100644
index 0000000..80b84a1
--- /dev/null
+++
b/wikipedia/src/main/java/org/wikipedia/page/leadimages/ImageViewWithFace.java
@@ -0,0 +1,100 @@
+package org.wikipedia.page.leadimages;
+
+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.drawable.Drawable;
+import android.media.FaceDetector;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.widget.ImageView;
+
+import com.squareup.picasso.Picasso;
+import com.squareup.picasso.Target;
+
+import org.wikipedia.concurrency.SaneAsyncTask;
+
+public class ImageViewWithFace extends ImageView implements Target {
+ private static final String TAG = "ImageViewWithFace";
+
+ public ImageViewWithFace(Context context) {
+ super(context);
+ }
+
+ public ImageViewWithFace(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public ImageViewWithFace(Context context, AttributeSet attrs, int
defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public void onBitmapLoaded(final Bitmap bitmap, Picasso.LoadedFrom from) {
+ (new SaneAsyncTask<Void>(SaneAsyncTask.LOW_CONCURRENCY) {
+ @Override
+ public Void performTask() throws Throwable {
+ // boost this thread's priority a bit
+ Thread.currentThread().setPriority(Thread.MAX_PRIORITY - 1);
+ long millis = System.currentTimeMillis();
+ // create a new bitmap onto which we'll draw the original
bitmap,
+ // because the FaceDetector requires it to be a 565 bitmap,
and it
+ // must also be even width.
+ Bitmap tempbmp = Bitmap.createBitmap(bitmap.getWidth() -
(bitmap.getWidth() % 2),
+ bitmap.getHeight(), Bitmap.Config.RGB_565);
+ Canvas canvas = new Canvas(tempbmp);
+ Paint paint = new Paint();
+ paint.setColor(Color.BLACK);
+ canvas.drawBitmap(bitmap, 0, 0, paint);
+
+ // initialize the face detector, and look for only one face...
+ FaceDetector fd = new FaceDetector(tempbmp.getWidth(),
tempbmp.getHeight(), 1);
+ FaceDetector.Face[] faces = new FaceDetector.Face[1];
+ PointF facePos = new PointF();
+ int numFound = fd.findFaces(tempbmp, faces);
+
+ if (numFound > 0) {
+ faces[0].getMidPoint(facePos);
+ android.util.Log.d(TAG, "Found face at " + facePos.x + ",
" + facePos.y);
+ }
+ // free our temporary bitmap
+ tempbmp.recycle();
+
+ Log.d(TAG, "Face detection took " +
(System.currentTimeMillis() - millis) + "ms");
+
+ if (onImageLoadListener != null) {
+ onImageLoadListener.onImageLoaded(bitmap, facePos);
+ }
+ return null;
+ }
+ }).execute();
+ // and, of course, set the original bitmap as our image
+ this.setImageBitmap(bitmap);
+ }
+
+ @Override
+ public void onBitmapFailed(Drawable errorDrawable) {
+ if (onImageLoadListener != null) {
+ onImageLoadListener.onImageFailed();
+ }
+ }
+
+ @Override
+ public void onPrepareLoad(Drawable placeHolderDrawable) {
+ }
+
+
+ public interface OnImageLoadListener {
+ void onImageLoaded(Bitmap bitmap, PointF faceLocation);
+ void onImageFailed();
+ }
+
+ private OnImageLoadListener onImageLoadListener;
+
+ public void setOnImageLoadListener(OnImageLoadListener listener) {
+ onImageLoadListener = listener;
+ }
+}
\ No newline at end of file
diff --git
a/wikipedia/src/main/java/org/wikipedia/page/leadimages/LeadImagesHandler.java
b/wikipedia/src/main/java/org/wikipedia/page/leadimages/LeadImagesHandler.java
index 3c41314..5e67388 100644
---
a/wikipedia/src/main/java/org/wikipedia/page/leadimages/LeadImagesHandler.java
+++
b/wikipedia/src/main/java/org/wikipedia/page/leadimages/LeadImagesHandler.java
@@ -1,19 +1,23 @@
package org.wikipedia.page.leadimages;
+import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Point;
import android.os.Build;
import android.util.TypedValue;
+import android.graphics.PointF;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.Transformation;
+import android.webkit.WebView;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import com.nineoldandroids.view.ViewHelper;
import com.squareup.picasso.Picasso;
+import com.squareup.picasso.Target;
import org.json.JSONException;
import org.json.JSONObject;
@@ -28,9 +32,10 @@
import java.util.Map;
-public class LeadImagesHandler implements
ObservableWebView.OnScrollChangeListener {
+public class LeadImagesHandler implements
ObservableWebView.OnScrollChangeListener, ImageViewWithFace.OnImageLoadListener
{
private final PageViewFragment parentFragment;
private final CommunicationBridge bridge;
+ private final WebView webView;
/**
* Minimum screen height for enabling lead images. If the screen is
smaller than
@@ -86,25 +91,30 @@
private boolean leadImagesEnabled = true;
private final ViewGroup imageContainer;
- private ImageView image1;
+ private ImageView imagePlaceholder;
+ private ImageViewWithFace image1;
private View pageTitleContainer;
private TextView pageTitleText;
private TextView pageDescriptionText;
private int displayHeight;
+ private int imageBaseYOffset = 0;
private float displayDensity;
public interface OnLeadImageLayoutListener {
void onLayoutComplete();
}
- public LeadImagesHandler(PageViewFragment parentFragment,
CommunicationBridge bridge,
+ public LeadImagesHandler(final PageViewFragment parentFragment,
CommunicationBridge bridge,
ObservableWebView webview, ViewGroup hidingView) {
this.parentFragment = parentFragment;
this.imageContainer = hidingView;
this.bridge = bridge;
+ this.webView = webview;
+ displayDensity =
parentFragment.getResources().getDisplayMetrics().density;
- image1 = (ImageView)imageContainer.findViewById(R.id.page_image_1);
+ imagePlaceholder =
(ImageView)imageContainer.findViewById(R.id.page_image_placeholder);
+ image1 =
(ImageViewWithFace)imageContainer.findViewById(R.id.page_image_1);
pageTitleContainer =
imageContainer.findViewById(R.id.page_title_container);
pageTitleText =
(TextView)imageContainer.findViewById(R.id.page_title_text);
pageDescriptionText =
(TextView)imageContainer.findViewById(R.id.page_description_text);
@@ -125,6 +135,10 @@
// hide ourselves by default
hide();
+
+
imagePlaceholder.setImageResource(Utils.getThemedAttributeId(parentFragment.getActivity(),
+ R.attr.lead_image_drawable));
+ image1.setOnImageLoadListener(this);
}
@Override
@@ -134,7 +148,7 @@
ViewHelper.setTranslationY(image1, 0);
} else {
ViewHelper.setTranslationY(imageContainer, -scrollY);
- ViewHelper.setTranslationY(image1, scrollY / 2); //parallax, baby
+ ViewHelper.setTranslationY(image1, imageBaseYOffset + scrollY /
2); //parallax, baby
}
}
@@ -144,6 +158,70 @@
*/
public void hide() {
imageContainer.setVisibility(View.INVISIBLE);
+ }
+
+ @Override
+ public void onImageLoaded(Bitmap bitmap, final PointF faceLocation) {
+ final int bmpHeight = bitmap.getHeight();
+ final float aspect = (float)bitmap.getHeight() /
(float)bitmap.getWidth();
+ imageContainer.post(new Runnable() {
+ @Override
+ public void run() {
+ if (!parentFragment.isAdded()) {
+ return;
+ }
+ int newWidth = image1.getWidth();
+ int newHeight = (int)(newWidth * aspect);
+
+ // give our image an offset based on the location of the face,
+ // relative to the image container
+ float scale = (float)newHeight / (float)bmpHeight;
+ if (faceLocation.y > 0) {
+ int faceY = (int)(faceLocation.y * scale);
+ // if we have a face, then offset to the face location
+ imageBaseYOffset = -(faceY - (imagePlaceholder.getHeight()
/ 2));
+ // give it a slight artificial boost, so that it appears
slightly
+ // above the page title...
+ final int faceBoost = 24;
+ imageBaseYOffset -= (faceBoost * displayDensity);
+ } else {
+ // if we don't have a face, then center on the midpoint of
the image
+ imageBaseYOffset = -(newHeight / 2
+ - (imagePlaceholder.getHeight() / 2));
+ }
+ // is the offset too far to the top?
+ if (imageBaseYOffset > 0) {
+ imageBaseYOffset = 0;
+ }
+ // is the offset too far to the bottom?
+ if (imageBaseYOffset < imagePlaceholder.getHeight() -
newHeight) {
+ imageBaseYOffset = imagePlaceholder.getHeight() -
newHeight;
+ }
+
+ // resize our image to have the same proportions as the
acquired bitmap
+ if (newHeight < imagePlaceholder.getHeight()) {
+ // if the height of the image is less than the container,
then just
+ // make it fill-parent
+ image1.setLayoutParams(new
FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
+ FrameLayout.LayoutParams.MATCH_PARENT));
+ imageBaseYOffset = 0;
+ } else {
+ image1.setLayoutParams(new
FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
+ newHeight));
+ }
+
+ // and force a refresh of its position within the container
view.
+ onScrollChanged(webView.getScrollY(), webView.getScrollY());
+
+ // fade in the new image!
+ ViewAnimations.crossFade(imagePlaceholder, image1);
+ }
+ });
+ }
+
+ @Override
+ public void onImageFailed() {
+ // just keep showing the placeholder image...
}
@@ -192,9 +270,8 @@
thumbUrl = WikipediaApp.getInstance().getNetworkProtocol() + ":" +
thumbUrl;
Picasso.with(parentFragment.getActivity())
.load(thumbUrl)
-
.placeholder(Utils.getThemedAttributeId(parentFragment.getActivity(),
R.attr.lead_image_drawable))
-
.error(Utils.getThemedAttributeId(parentFragment.getActivity(),
R.attr.lead_image_drawable))
- .into(image1);
+ .noFade()
+ .into((Target) image1);
}
}
@@ -269,6 +346,7 @@
(int) ((titleContainerHeight) * displayDensity)));
// hide the lead image
image1.setVisibility(View.GONE);
+ imagePlaceholder.setVisibility(View.GONE);
// set the color of the title
pageTitleText.setTextColor(parentFragment
.getResources()
@@ -294,8 +372,10 @@
titleContainerHeight = (int) (displayHeight *
IMAGES_CONTAINER_RATIO);
imageContainer.setLayoutParams(new
FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
(int) (titleContainerHeight * displayDensity)));
- // show the lead image
- image1.setVisibility(View.VISIBLE);
+ // prepare the lead image to be populated
+ image1.setVisibility(View.INVISIBLE);
+ imagePlaceholder.setVisibility(View.VISIBLE);
+
// set the color of the title
pageTitleText.setTextColor(parentFragment.getResources().getColor(R.color.lead_text_color));
titleBottomPadding = pageTitleText.getPaddingBottom();
@@ -330,6 +410,16 @@
}
bridge.sendMessage("setPaddingTop", payload);
+ // and start fetching the lead image, if we have one
+ String thumbUrl =
parentFragment.getFragment().getPage().getPageProperties().getLeadImageUrl();
+ if (thumbUrl != null && leadImagesEnabled) {
+ thumbUrl = WikipediaApp.getInstance().getNetworkProtocol() + ":" +
thumbUrl;
+ Picasso.with(parentFragment.getActivity())
+ .load(thumbUrl)
+ .noFade()
+ .into((Target)image1);
+ }
+
// make everything visible!
imageContainer.setVisibility(View.VISIBLE);
--
To view, visit https://gerrit.wikimedia.org/r/168773
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Ic255a0974345270c10164df84508527b6f2767fc
Gerrit-PatchSet: 11
Gerrit-Project: apps/android/wikipedia
Gerrit-Branch: master
Gerrit-Owner: Dbrant <[email protected]>
Gerrit-Reviewer: BearND <[email protected]>
Gerrit-Reviewer: Brion VIBBER <[email protected]>
Gerrit-Reviewer: Deskana <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits