From ffd7e762617c007e58eeba5dc27c215546f9852b Mon Sep 17 00:00:00 2001 From: Hyunyoung Song Date: Tue, 28 Mar 2017 18:43:56 -0700 Subject: [PATCH] Fine tune the circle detection Decreased the pixel_diff_percentage from 10% to 0.5% by not counting the pixel_diff around the mask path outline. Verified that this implementation works well on both Pixel/PixelXL b/36563706 Change-Id: I8bb488f550a67e2188eff4c4b9e7717e979a741c --- src/com/android/launcher3/IconCache.java | 2 +- .../launcher3/graphics/IconNormalizer.java | 36 +++++++++++++------ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java index a5552aafdc..a472a3a223 100644 --- a/src/com/android/launcher3/IconCache.java +++ b/src/com/android/launcher3/IconCache.java @@ -763,7 +763,7 @@ public class IconCache { } private static final class IconDB extends SQLiteCacheHelper { - private final static int DB_VERSION = 10; + private final static int DB_VERSION = 11; private final static int RELEASE_VERSION = DB_VERSION + (FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ? 0 : 1); diff --git a/src/com/android/launcher3/graphics/IconNormalizer.java b/src/com/android/launcher3/graphics/IconNormalizer.java index ebbca95c3e..f34933e893 100644 --- a/src/com/android/launcher3/graphics/IconNormalizer.java +++ b/src/com/android/launcher3/graphics/IconNormalizer.java @@ -60,7 +60,7 @@ public class IconNormalizer { // Shape detection related constants private static final float BOUND_RATIO_MARGIN = .05f; - private static final float PIXEL_DIFF_PERCENTAGE_THRESHOLD = 0.1f; + private static final float PIXEL_DIFF_PERCENTAGE_THRESHOLD = 0.005f; private static final float SCALE_NOT_INITIALIZED = 0; private static final Object LOCK = new Object(); @@ -71,6 +71,7 @@ public class IconNormalizer { private final Bitmap mBitmapARGB; private final Canvas mCanvas; private final Paint mPaintMaskShape; + private final Paint mPaintMaskShapeOutline; private final byte[] mPixels; private final int[] mPixelsARGB; private float mAdaptiveIconScale; @@ -79,14 +80,15 @@ public class IconNormalizer { private final float[] mLeftBorder; private final float[] mRightBorder; private final Rect mBounds; - private final RectF mShapeBounds; private final Matrix mMatrix; private Paint mPaintIcon; private Canvas mCanvasARGB; private File mDir; + private int mFileId; private Random mRandom; + private float mDensity; private IconNormalizer(Context context) { // Use twice the icon size as maximum size to avoid scaling down twice. @@ -98,17 +100,25 @@ public class IconNormalizer { mLeftBorder = new float[mMaxSize]; mRightBorder = new float[mMaxSize]; mBounds = new Rect(); - + mDensity = context.getResources().getDisplayMetrics().density; // Needed for isShape() method mBitmapARGB = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ARGB_8888); mCanvasARGB = new Canvas(mBitmapARGB); - mShapeBounds = new RectF(); mPaintIcon = new Paint(); mPaintIcon.setColor(Color.WHITE); + 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 * mDensity); + mPaintMaskShapeOutline.setStyle(Paint.Style.STROKE); + mPaintMaskShapeOutline.setColor(Color.BLACK); + mPaintMaskShapeOutline.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); + mMatrix = new Matrix(); int[] mPixels = new int[mMaxSize * mMaxSize]; mAdaptiveIconScale = SCALE_NOT_INITIALIZED; @@ -136,12 +146,12 @@ public class IconNormalizer { // 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. - int id = mRandom.nextInt(); + mFileId = mRandom.nextInt(); mBitmapARGB.eraseColor(Color.TRANSPARENT); mCanvasARGB.drawBitmap(mBitmap, 0, 0, mPaintIcon); if (DEBUG) { - final File beforeFile = new File(mDir, "isShape" + id + "_before.png"); + final File beforeFile = new File(mDir, "isShape" + mFileId + "_before.png"); try { mBitmapARGB.compress(Bitmap.CompressFormat.PNG, 100, new FileOutputStream(beforeFile)); @@ -149,24 +159,28 @@ public class IconNormalizer { } // 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); - maskPath.computeBounds(mShapeBounds, false); // XOR operation mCanvasARGB.drawPath(maskPath, mPaintMaskShape); + // DST_OUT operation around the mask path outline + mCanvasARGB.drawPath(maskPath, mPaintMaskShapeOutline); + + boolean isTrans = isTransparentBitmap(mBitmapARGB); if (DEBUG) { - final File afterFile = new File(mDir, "isShape" + id + "_after.png"); + final File afterFile = new File(mDir, "isShape" + mFileId + "_after_" + isTrans + ".png"); try { mBitmapARGB.compress(Bitmap.CompressFormat.PNG, 100, new FileOutputStream(afterFile)); } catch (Exception e) {} } - // Check if the XOR operation result is almost transparent - if (!isTransparentBitmap(mBitmapARGB)) { + // Check if the result is almost transparent + if (!isTrans) { if (DEBUG) { Log.d(TAG, "Not same as mask shape"); } @@ -194,7 +208,7 @@ public class IconNormalizer { float percentageDiffPixels = ((float) sum) / (mBounds.width() * mBounds.height()); boolean transparentImage = percentageDiffPixels < PIXEL_DIFF_PERCENTAGE_THRESHOLD; if (DEBUG) { - Log.d(TAG, "Total # pixel that is different:" + percentageDiffPixels + "="+ sum + "/" + mBounds.width() * mBounds.height()); + Log.d(TAG, "Total # pixel that is different (id="+ mFileId + "):" + percentageDiffPixels + "="+ sum + "/" + mBounds.width() * mBounds.height()); } return transparentImage; }