diff options
author | Hyunyoung Song <hyunyoungs@google.com> | 2019-05-25 22:15:48 -0700 |
---|---|---|
committer | Hyunyoung Song <hyunyoungs@google.com> | 2019-06-03 14:05:20 -0700 |
commit | f69426c4845f35206ad370889cb489d2f4af0135 (patch) | |
tree | 5e31c1b2498506114edb645ebc2e8efed11d0f09 /iconloaderlib | |
parent | ebd5e88164ef3a59cfe5f75a9eaeb95222ded7fa (diff) | |
download | android_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.java | 21 | ||||
-rw-r--r-- | iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java | 105 |
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); |