summaryrefslogtreecommitdiffstats
path: root/iconloaderlib
diff options
context:
space:
mode:
authorHyunyoung Song <hyunyoungs@google.com>2019-05-25 22:15:48 -0700
committerHyunyoung Song <hyunyoungs@google.com>2019-06-03 14:05:20 -0700
commitf69426c4845f35206ad370889cb489d2f4af0135 (patch)
tree5e31c1b2498506114edb645ebc2e8efed11d0f09 /iconloaderlib
parentebd5e88164ef3a59cfe5f75a9eaeb95222ded7fa (diff)
downloadandroid_packages_apps_Trebuchet-f69426c4845f35206ad370889cb489d2f4af0135.tar.gz
android_packages_apps_Trebuchet-f69426c4845f35206ad370889cb489d2f4af0135.tar.bz2
android_packages_apps_Trebuchet-f69426c4845f35206ad370889cb489d2f4af0135.zip
Add shape detection logic only for circle icons
b/119330044 Change-Id: I93042effa8417167d844f073275e8b941e861e60
Diffstat (limited to 'iconloaderlib')
-rw-r--r--iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java21
-rw-r--r--iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java105
2 files changed, 116 insertions, 10 deletions
diff --git a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
index 3c5585421..db5515b74 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
@@ -46,13 +46,15 @@ public class BaseIconFactory implements AutoCloseable {
private IconNormalizer mNormalizer;
private ShadowGenerator mShadowGenerator;
+ private final boolean mShapeDetection;
private Drawable mWrapperIcon;
private int mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
- protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize) {
+ protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize,
+ boolean shapeDetection) {
mContext = context.getApplicationContext();
-
+ mShapeDetection = shapeDetection;
mFillResIconDpi = fillResIconDpi;
mIconBitmapSize = iconBitmapSize;
@@ -64,6 +66,10 @@ public class BaseIconFactory implements AutoCloseable {
clear();
}
+ protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize) {
+ this(context, fillResIconDpi, iconBitmapSize, false);
+ }
+
protected void clear() {
mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
mDisableColorExtractor = false;
@@ -78,7 +84,7 @@ public class BaseIconFactory implements AutoCloseable {
public IconNormalizer getNormalizer() {
if (mNormalizer == null) {
- mNormalizer = new IconNormalizer(mIconBitmapSize);
+ mNormalizer = new IconNormalizer(mContext, mIconBitmapSize, mShapeDetection);
}
return mNormalizer;
}
@@ -212,18 +218,19 @@ public class BaseIconFactory implements AutoCloseable {
}
AdaptiveIconDrawable dr = (AdaptiveIconDrawable) mWrapperIcon;
dr.setBounds(0, 0, 1, 1);
- scale = getNormalizer().getScale(icon, outIconBounds);
- if (!(icon instanceof AdaptiveIconDrawable)) {
+ boolean[] outShape = new boolean[1];
+ scale = getNormalizer().getScale(icon, outIconBounds, dr.getIconMask(), outShape);
+ if (!(icon instanceof AdaptiveIconDrawable) && !outShape[0]) {
FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground());
fsd.setDrawable(icon);
fsd.setScale(scale);
icon = dr;
- scale = getNormalizer().getScale(icon, outIconBounds);
+ scale = getNormalizer().getScale(icon, outIconBounds, null, null);
((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor);
}
} else {
- scale = getNormalizer().getScale(icon, outIconBounds);
+ scale = getNormalizer().getScale(icon, outIconBounds, null, null);
}
outScale[0] = scale;
diff --git a/iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java b/iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java
index 4a2a7cf9f..de39e79fe 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java
@@ -18,16 +18,22 @@ package com.android.launcher3.icons;
import android.annotation.TargetApi;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
import android.graphics.Path;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
+import android.util.Log;
import java.nio.ByteBuffer;
@@ -51,6 +57,9 @@ public class IconNormalizer {
private static final int MIN_VISIBLE_ALPHA = 40;
+ // Shape detection related constants
+ private static final float BOUND_RATIO_MARGIN = .05f;
+ private static final float PIXEL_DIFF_PERCENTAGE_THRESHOLD = 0.005f;
private static final float SCALE_NOT_INITIALIZED = 0;
// Ratio of the diameter of an normalized circular icon to the actual icon size.
@@ -59,18 +68,24 @@ public class IconNormalizer {
private final int mMaxSize;
private final Bitmap mBitmap;
private final Canvas mCanvas;
+ private final Paint mPaintMaskShape;
+ private final Paint mPaintMaskShapeOutline;
private final byte[] mPixels;
private final RectF mAdaptiveIconBounds;
private float mAdaptiveIconScale;
+ private boolean mEnableShapeDetection;
+
// for each y, stores the position of the leftmost x and the rightmost x
private final float[] mLeftBorder;
private final float[] mRightBorder;
private final Rect mBounds;
+ private final Path mShapePath;
+ private final Matrix mMatrix;
/** package private **/
- IconNormalizer(int iconBitmapSize) {
+ IconNormalizer(Context context, int iconBitmapSize, boolean shapeDetection) {
// Use twice the icon size as maximum size to avoid scaling down twice.
mMaxSize = iconBitmapSize * 2;
mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8);
@@ -81,7 +96,22 @@ public class IconNormalizer {
mBounds = new Rect();
mAdaptiveIconBounds = new RectF();
+ mPaintMaskShape = new Paint();
+ mPaintMaskShape.setColor(Color.RED);
+ mPaintMaskShape.setStyle(Paint.Style.FILL);
+ mPaintMaskShape.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));
+
+ mPaintMaskShapeOutline = new Paint();
+ mPaintMaskShapeOutline.setStrokeWidth(
+ 2 * context.getResources().getDisplayMetrics().density);
+ mPaintMaskShapeOutline.setStyle(Paint.Style.STROKE);
+ mPaintMaskShapeOutline.setColor(Color.BLACK);
+ mPaintMaskShapeOutline.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+
+ mShapePath = new Path();
+ mMatrix = new Matrix();
mAdaptiveIconScale = SCALE_NOT_INITIALIZED;
+ mEnableShapeDetection = shapeDetection;
}
private static float getScale(float hullArea, float boundingArea, float fullArea) {
@@ -127,6 +157,72 @@ public class IconNormalizer {
}
/**
+ * Returns if the shape of the icon is same as the path.
+ * For this method to work, the shape path bounds should be in [0,1]x[0,1] bounds.
+ */
+ private boolean isShape(Path maskPath) {
+ // Condition1:
+ // If width and height of the path not close to a square, then the icon shape is
+ // not same as the mask shape.
+ float iconRatio = ((float) mBounds.width()) / mBounds.height();
+ if (Math.abs(iconRatio - 1) > BOUND_RATIO_MARGIN) {
+ if (DEBUG) {
+ Log.d(TAG, "Not same as mask shape because width != height. " + iconRatio);
+ }
+ return false;
+ }
+
+ // Condition 2:
+ // Actual icon (white) and the fitted shape (e.g., circle)(red) XOR operation
+ // should generate transparent image, if the actual icon is equivalent to the shape.
+
+ // Fit the shape within the icon's bounding box
+ mMatrix.reset();
+ mMatrix.setScale(mBounds.width(), mBounds.height());
+ mMatrix.postTranslate(mBounds.left, mBounds.top);
+ maskPath.transform(mMatrix, mShapePath);
+
+ // XOR operation
+ mCanvas.drawPath(mShapePath, mPaintMaskShape);
+
+ // DST_OUT operation around the mask path outline
+ mCanvas.drawPath(mShapePath, mPaintMaskShapeOutline);
+
+ // Check if the result is almost transparent
+ return isTransparentBitmap();
+ }
+
+ /**
+ * Used to determine if certain the bitmap is transparent.
+ */
+ private boolean isTransparentBitmap() {
+ ByteBuffer buffer = ByteBuffer.wrap(mPixels);
+ buffer.rewind();
+ mBitmap.copyPixelsToBuffer(buffer);
+
+ int y = mBounds.top;
+ // buffer position
+ int index = y * mMaxSize;
+ // buffer shift after every row, width of buffer = mMaxSize
+ int rowSizeDiff = mMaxSize - mBounds.right;
+
+ int sum = 0;
+ for (; y < mBounds.bottom; y++) {
+ index += mBounds.left;
+ for (int x = mBounds.left; x < mBounds.right; x++) {
+ if ((mPixels[index] & 0xFF) > MIN_VISIBLE_ALPHA) {
+ sum++;
+ }
+ index++;
+ }
+ index += rowSizeDiff;
+ }
+
+ float percentageDiffPixels = ((float) sum) / (mBounds.width() * mBounds.height());
+ return percentageDiffPixels < PIXEL_DIFF_PERCENTAGE_THRESHOLD;
+ }
+
+ /**
* Returns the amount by which the {@param d} should be scaled (in both dimensions) so that it
* matches the design guidelines for a launcher icon.
*
@@ -140,7 +236,8 @@ public class IconNormalizer {
*
* @param outBounds optional rect to receive the fraction distance from each edge.
*/
- public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds) {
+ public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds,
+ @Nullable Path path, @Nullable boolean[] outMaskShape) {
if (BaseIconFactory.ATLEAST_OREO && d instanceof AdaptiveIconDrawable) {
if (mAdaptiveIconScale == SCALE_NOT_INITIALIZED) {
mAdaptiveIconScale = normalizeAdaptiveIcon(d, mMaxSize, mAdaptiveIconBounds);
@@ -242,7 +339,9 @@ public class IconNormalizer {
1 - ((float) mBounds.right) / width,
1 - ((float) mBounds.bottom) / height);
}
-
+ if (outMaskShape != null && mEnableShapeDetection && outMaskShape.length > 0) {
+ outMaskShape[0] = isShape(path);
+ }
// Area of the rectangle required to fit the convex hull
float rectArea = (bottomY + 1 - topY) * (rightX + 1 - leftX);
return getScale(area, rectArea, width * height);