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

Commit 4ec961f8 authored by Sunny Goyal's avatar Sunny Goyal Committed by Android (Google) Code Review
Browse files

Merge "Simplifying icon normalization for adaptive icons" into main

parents d38edc36 5bdac8e4
Loading
Loading
Loading
Loading
+3 −15
Original line number Diff line number Diff line
@@ -92,9 +92,6 @@ public class BaseIconFactory implements AutoCloseable {

    protected IconThemeController mThemeController;

    @Nullable
    private IconNormalizer mNormalizer;

    @Nullable
    private ShadowGenerator mShadowGenerator;

@@ -135,14 +132,6 @@ public class BaseIconFactory implements AutoCloseable {
        return mShadowGenerator;
    }

    @NonNull
    public IconNormalizer getNormalizer() {
        if (mNormalizer == null) {
            mNormalizer = new IconNormalizer(mContext, mIconBitmapSize);
        }
        return mNormalizer;
    }

    @Nullable
    public IconThemeController getThemeController() {
        return mThemeController;
@@ -322,9 +311,8 @@ public class BaseIconFactory implements AutoCloseable {
            return null;
        }

        AdaptiveIconDrawable adaptiveIcon = wrapToAdaptiveIcon(icon);
        outScale[0] = getNormalizer().getScale(adaptiveIcon);
        return adaptiveIcon;
        outScale[0] = IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
        return wrapToAdaptiveIcon(icon);
    }

    /**
@@ -356,7 +344,7 @@ public class BaseIconFactory implements AutoCloseable {
            AdaptiveIconDrawable dr = new AdaptiveIconDrawable(
                    new ColorDrawable(mWrapperBackgroundColor), foreground);
            dr.setBounds(0, 0, 1, 1);
            float scale = getNormalizer().getScale(icon);
            float scale = new IconNormalizer(mIconBitmapSize).getScale(icon);
            foreground.setDrawable(createScaledDrawable(icon, scale * LEGACY_ICON_SCALE));
            return dr;
        }
+2 −136
Original line number Diff line number Diff line
@@ -16,22 +16,12 @@

package com.android.launcher3.icons;

import android.annotation.TargetApi;
import android.content.Context;
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.Region;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.Log;

import androidx.annotation.NonNull;

@@ -39,8 +29,6 @@ import java.nio.ByteBuffer;

public class IconNormalizer {

    private static final String TAG = "IconNormalizer";
    private static final boolean DEBUG = false;
    // Ratio of icon visible area to full icon size for a square shaped icon
    private static final float MAX_SQUARE_AREA_FACTOR = 375.0f / 576;
    // Ratio of icon visible area to full icon size for a circular shaped icon
@@ -54,32 +42,21 @@ 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.
    public static final float ICON_VISIBLE_AREA_FACTOR = 0.92f;

    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 float mAdaptiveIconScale;

    // 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(Context context, int iconBitmapSize) {
    public IconNormalizer(int iconBitmapSize) {
        // 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);
@@ -88,22 +65,6 @@ public class IconNormalizer {
        mLeftBorder = new float[mMaxSize];
        mRightBorder = new float[mMaxSize];
        mBounds = new Rect();

        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;
    }

    private static float getScale(float hullArea, float boundingArea, float fullArea) {
@@ -120,90 +81,6 @@ public class IconNormalizer {
        return areaScale > scaleRequired ? (float) Math.sqrt(scaleRequired / areaScale) : 1;
    }

    /**
     * @param d Should be AdaptiveIconDrawable
     * @param size Canvas size to use
     */
    @TargetApi(Build.VERSION_CODES.O)
    public static float normalizeAdaptiveIcon(Drawable d, int size) {
        Rect tmpBounds = new Rect(d.getBounds());
        d.setBounds(0, 0, size, size);

        Path path = ((AdaptiveIconDrawable) d).getIconMask();
        Region region = new Region();
        region.setPath(path, new Region(0, 0, size, size));

        int hullArea = GraphicsUtils.getArea(region);
        d.setBounds(tmpBounds);
        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.
@@ -218,10 +95,7 @@ public class IconNormalizer {
     */
    public synchronized float getScale(@NonNull Drawable d) {
        if (d instanceof AdaptiveIconDrawable) {
            if (mAdaptiveIconScale == SCALE_NOT_INITIALIZED) {
                mAdaptiveIconScale = normalizeAdaptiveIcon(d, mMaxSize);
            }
            return mAdaptiveIconScale;
            return ICON_VISIBLE_AREA_FACTOR;
        }
        int width = d.getIntrinsicWidth();
        int height = d.getIntrinsicHeight();
@@ -368,12 +242,4 @@ public class IconNormalizer {
            last = i;
        }
    }

    /**
     * @return The diameter of the normalized circle that fits inside of the square (size x size).
     */
    public static int getNormalizedCircleSize(int size) {
        float area = size * size * MAX_CIRCLE_AREA_FACTOR;
        return (int) Math.round(Math.sqrt((4 * area) / Math.PI));
    }
}
+3 −3
Original line number Diff line number Diff line
@@ -33,7 +33,9 @@ import android.graphics.drawable.InsetDrawable
import android.os.Build
import com.android.launcher3.Flags
import com.android.launcher3.icons.BaseIconFactory
import com.android.launcher3.icons.BaseIconFactory.MODE_ALPHA
import com.android.launcher3.icons.BitmapInfo
import com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR
import com.android.launcher3.icons.IconThemeController
import com.android.launcher3.icons.MonochromeIconFactory
import com.android.launcher3.icons.SourceHint
@@ -55,10 +57,8 @@ class MonoIconThemeController(
    ): ThemedBitmap? {
        val mono = getMonochromeDrawable(icon, info)
        if (mono != null) {
            val scale =
                factory.normalizer.getScale(AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null))
            return MonoThemedBitmap(
                factory.createIconBitmap(mono, scale, BaseIconFactory.MODE_ALPHA),
                factory.createIconBitmap(mono, ICON_VISIBLE_AREA_FACTOR, MODE_ALPHA),
                factory.whiteShadowLayer,
                colorProvider,
            )