Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ed45267b authored by Hyunyoung Song's avatar Hyunyoung Song Committed by Android (Google) Code Review
Browse files

Merge "Add shape detection logic only for circle icons" into ub-launcher3-qt-dev

parents acdde3d7 f69426c4
Loading
Loading
Loading
Loading
+14 −7
Original line number Diff line number Diff line
@@ -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;
+102 −3
Original line number Diff line number Diff line
@@ -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) {
@@ -126,6 +156,72 @@ public class IconNormalizer {
        return getScale(hullArea, hullArea, size * size);
    }

    /**
     * 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);
+1 −1
Original line number Diff line number Diff line
@@ -243,7 +243,7 @@ public class DragView extends View implements LauncherStateManager.StateListener
                            nDr = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK), null);
                        }
                        Utilities.scaleRectAboutCenter(bounds,
                                li.getNormalizer().getScale(nDr, null));
                                li.getNormalizer().getScale(nDr, null, null, null));
                    }
                    AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) dr;

+9 −0
Original line number Diff line number Diff line
@@ -91,6 +91,10 @@ public abstract class IconShape {

    private SparseArray<TypedValue> mAttrs;

    public boolean enableShapeDetection(){
        return false;
    };

    public abstract void drawShape(Canvas canvas, float offsetX, float offsetY, float radius,
            Paint paint);

@@ -194,6 +198,11 @@ public abstract class IconShape {
        protected float getStartRadius(Rect startRect) {
            return startRect.width() / 2f;
        }

        @Override
        public boolean enableShapeDetection() {
            return true;
        }
    }

    public static class RoundedSquare extends SimpleRectShape {
+11 −4
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.util.Themes;
@@ -50,11 +51,15 @@ public class LauncherIcons extends BaseIconFactory implements AutoCloseable {
    private static LauncherIcons sPool;
    private static int sPoolId = 0;

    public static LauncherIcons obtain(Context context) {
        return obtain(context, IconShape.getShape().enableShapeDetection());
    }

    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static LauncherIcons obtain(Context context) {
    public static LauncherIcons obtain(Context context, boolean shapeDetection) {
        int poolId;
        synchronized (sPoolSync) {
            if (sPool != null) {
@@ -67,7 +72,8 @@ public class LauncherIcons extends BaseIconFactory implements AutoCloseable {
        }

        InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
        return new LauncherIcons(context, idp.fillResIconDpi, idp.iconBitmapSize, poolId);
        return new LauncherIcons(context, idp.fillResIconDpi, idp.iconBitmapSize, poolId,
                shapeDetection);
    }

    public static void clearPool() {
@@ -81,8 +87,9 @@ public class LauncherIcons extends BaseIconFactory implements AutoCloseable {

    private LauncherIcons next;

    private LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize, int poolId) {
        super(context, fillResIconDpi, iconBitmapSize);
    private LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize, int poolId,
            boolean shapeDetection) {
        super(context, fillResIconDpi, iconBitmapSize, shapeDetection);
        mPoolId = poolId;
    }

Loading