Android12 launcher3修改App图标白边问题

news2025/5/26 4:02:45

Android12 launcher3修改App图标白边问题

1.前言:

今天在Android12 Rom定制客制化系统应用时发现改变系统App图标的形状会出现一个问题,那就是图标被缩小了,没有显示完整,有一个白边,这在普通的App开发很少遇到,在Android系统Rom定制时才会出现此问题,记录一下修改过程.

2.修改前的截图如下:

2.1 圆角图标:

可以看到App的图标没有铺满,中间的图标有的是方形,有的是圆角,而且出现一个白边,显示不正常

2.2 圆形图标:

发现切换圆形图标后问题也有问题,更难看,而且有个图标还被放大了,白边很突兀,明细不正常.

3.修改BaseIconFactory:

核心修改方法如下:

    /**
     * Switches badging to left/right
     */
    public void setBadgeOnLeft(boolean badgeOnLeft) {
        mBadgeOnLeft = badgeOnLeft;
    }

    /**
     * Sets the background color used for wrapped adaptive icon
     */
    public void setWrapperBackgroundColor(int color) {
        mWrapperBackgroundColor = (Color.alpha(color) < 255) ? DEFAULT_WRAPPER_BACKGROUND : color;
    }

    /**
     * Disables the dominant color extraction for all icons loaded.
     */
    public void disableColorExtraction() {
        mDisableColorExtractor = true;
    }

    private Drawable normalizeAndWrapToAdaptiveIcon(@NonNull Drawable icon,
            boolean shrinkNonAdaptiveIcons, RectF outIconBounds, float[] outScale) {
        if (icon == null) {
            return null;
        }
        float scale = 1f;
/* 
        if (shrinkNonAdaptiveIcons && ATLEAST_OREO) {
            if (mWrapperIcon == null) {
                mWrapperIcon = mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper)
                        .mutate();
            }
            AdaptiveIconDrawable dr = (AdaptiveIconDrawable) mWrapperIcon;
            dr.setBounds(0, 0, 1, 1);
            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, null, null);

                ((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor);
            }
        } else {
            scale = getNormalizer().getScale(icon, outIconBounds, null, null);
        } */
        scale =getNormalizer().getScale(icon, outIconBounds, null, null);
        outScale[0] = scale;
      
        return icon;
    }
package com.android.launcher3.icons;

import static android.graphics.Paint.DITHER_FLAG;
import static android.graphics.Paint.FILTER_BITMAP_FLAG;

import static com.android.launcher3.icons.ShadowGenerator.BLUR_FACTOR;

import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.InsetDrawable;
import android.os.Build;
import android.os.Process;
import android.os.UserHandle;

import androidx.annotation.NonNull;

import com.android.launcher3.icons.BitmapInfo.Extender;

/**
 * This class will be moved to androidx library. There shouldn't be any dependency outside
 * this package.
 */
public class BaseIconFactory implements AutoCloseable {

    private static final String TAG = "BaseIconFactory";
    private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE;
    static final boolean ATLEAST_OREO = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
    static final boolean ATLEAST_P = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;

    private static final float ICON_BADGE_SCALE = 0.444f;

    private final Rect mOldBounds = new Rect();
    protected final Context mContext;
    private final Canvas mCanvas;
    private final PackageManager mPm;
    private final ColorExtractor mColorExtractor;
    private boolean mDisableColorExtractor;
    private boolean mBadgeOnLeft = false;

    protected final int mFillResIconDpi;
    protected final int mIconBitmapSize;

    private IconNormalizer mNormalizer;
    private ShadowGenerator mShadowGenerator;
    private final boolean mShapeDetection;

    private Drawable mWrapperIcon;
    private int mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
    private Bitmap mUserBadgeBitmap;

    private final Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
    private static final float PLACEHOLDER_TEXT_SIZE = 20f;
    private static int PLACEHOLDER_BACKGROUND_COLOR = Color.rgb(240, 240, 240);

    protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize,
            boolean shapeDetection) {
        mContext = context.getApplicationContext();
        mShapeDetection = shapeDetection;
        mFillResIconDpi = fillResIconDpi;
        mIconBitmapSize = iconBitmapSize;

        mPm = mContext.getPackageManager();
        mColorExtractor = new ColorExtractor();

        mCanvas = new Canvas();
        mCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
        mTextPaint.setTextAlign(Paint.Align.CENTER);
        mTextPaint.setColor(PLACEHOLDER_BACKGROUND_COLOR);
        mTextPaint.setTextSize(context.getResources().getDisplayMetrics().density *
                PLACEHOLDER_TEXT_SIZE);
        clear();
    }

    public BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize) {
        this(context, fillResIconDpi, iconBitmapSize, false);
    }

    protected void clear() {
        mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
        mDisableColorExtractor = false;
        mBadgeOnLeft = false;
    }

    public ShadowGenerator getShadowGenerator() {
        if (mShadowGenerator == null) {
            mShadowGenerator = new ShadowGenerator(mIconBitmapSize);
        }
        return mShadowGenerator;
    }

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

    @SuppressWarnings("deprecation")
    public BitmapInfo createIconBitmap(Intent.ShortcutIconResource iconRes) {
        try {
            Resources resources = mPm.getResourcesForApplication(iconRes.packageName);
            if (resources != null) {
                final int id = resources.getIdentifier(iconRes.resourceName, null, null);
                // do not stamp old legacy shortcuts as the app may have already forgotten about it
                return createBadgedIconBitmap(
                        resources.getDrawableForDensity(id, mFillResIconDpi),
                        Process.myUserHandle() /* only available on primary user */,
                        false /* do not apply legacy treatment */);
            }
        } catch (Exception e) {
            // Icon not found.
        }
        return null;
    }

    /**
     * Create a placeholder icon using the passed in text.
     *
     * @param placeholder used for foreground element in the icon bitmap
     * @param color used for the foreground text color
     * @return
     */
    public BitmapInfo createIconBitmap(String placeholder, int color) {
        if (!ATLEAST_OREO) return null;

        Bitmap placeholderBitmap = Bitmap.createBitmap(mIconBitmapSize, mIconBitmapSize,
                Bitmap.Config.ARGB_8888);
        mTextPaint.setColor(color);
        Canvas canvas = new Canvas(placeholderBitmap);
        canvas.drawText(placeholder, mIconBitmapSize / 2, mIconBitmapSize * 5 / 8, mTextPaint);
        AdaptiveIconDrawable drawable = new AdaptiveIconDrawable(
                new ColorDrawable(PLACEHOLDER_BACKGROUND_COLOR),
                new BitmapDrawable(mContext.getResources(), placeholderBitmap));
        Bitmap icon = createIconBitmap(drawable, 1f);
        return BitmapInfo.of(icon, extractColor(icon));
    }

    public BitmapInfo createIconBitmap(Bitmap icon) {
        if (mIconBitmapSize != icon.getWidth() || mIconBitmapSize != icon.getHeight()) {
            icon = createIconBitmap(new BitmapDrawable(mContext.getResources(), icon), 1f);
        }

        return BitmapInfo.of(icon, extractColor(icon));
    }

    /**
     * Creates an icon from the bitmap cropped to the current device icon shape
     */
    public BitmapInfo createShapedIconBitmap(Bitmap icon, UserHandle user) {
        Drawable d = new FixedSizeBitmapDrawable(icon);
        if (ATLEAST_OREO) {
            float inset = AdaptiveIconDrawable.getExtraInsetFraction();
            inset = inset / (1 + 2 * inset);
            d = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK),
                    new InsetDrawable(d, inset, inset, inset, inset));
        }
        return createBadgedIconBitmap(d, user, true);
    }

    public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
            boolean shrinkNonAdaptiveIcons) {
        return createBadgedIconBitmap(icon, user, shrinkNonAdaptiveIcons, false, null);
    }

    public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
            int iconAppTargetSdk) {
        return createBadgedIconBitmap(icon, user, iconAppTargetSdk, false);
    }

    public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
            int iconAppTargetSdk, boolean isInstantApp) {
        return createBadgedIconBitmap(icon, user, iconAppTargetSdk, isInstantApp, null);
    }

    public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
            int iconAppTargetSdk, boolean isInstantApp, float[] scale) {
        boolean shrinkNonAdaptiveIcons = ATLEAST_P ||
                (ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O);
        return createBadgedIconBitmap(icon, user, shrinkNonAdaptiveIcons, isInstantApp, scale);
    }

    public Bitmap createScaledBitmapWithoutShadow(Drawable icon, int iconAppTargetSdk) {
        boolean shrinkNonAdaptiveIcons = ATLEAST_P ||
                (ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O);
        return  createScaledBitmapWithoutShadow(icon, shrinkNonAdaptiveIcons);
    }

    /**
     * Creates bitmap using the source drawable and various parameters.
     * The bitmap is visually normalized with other icons and has enough spacing to add shadow.
     *
     * @param icon                      source of the icon
     * @param user                      info can be used for a badge
     * @param shrinkNonAdaptiveIcons    {@code true} if non adaptive icons should be treated
     * @param isInstantApp              info can be used for a badge
     * @param scale                     returns the scale result from normalization
     * @return a bitmap suitable for disaplaying as an icon at various system UIs.
     */
    public BitmapInfo createBadgedIconBitmap(@NonNull Drawable icon, UserHandle user,
            boolean shrinkNonAdaptiveIcons, boolean isInstantApp, float[] scale) {
        if (scale == null) {
            scale = new float[1];
        }
        icon = normalizeAndWrapToAdaptiveIcon(icon, shrinkNonAdaptiveIcons, null, scale);
        Bitmap bitmap = createIconBitmap(icon, scale[0]);
        if (ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
            mCanvas.setBitmap(bitmap);
            getShadowGenerator().recreateIcon(Bitmap.createBitmap(bitmap), mCanvas);
            mCanvas.setBitmap(null);
        }

        if (isInstantApp) {
            badgeWithDrawable(bitmap, mContext.getDrawable(R.drawable.ic_instant_app_badge));
        }
        if (user != null) {
            BitmapDrawable drawable = new FixedSizeBitmapDrawable(bitmap);
            Drawable badged = mPm.getUserBadgedIcon(drawable, user);
            if (badged instanceof BitmapDrawable) {
                bitmap = ((BitmapDrawable) badged).getBitmap();
            } else {
                bitmap = createIconBitmap(badged, 1f);
            }
        }
        int color = extractColor(bitmap);
        return icon instanceof BitmapInfo.Extender
                ? ((BitmapInfo.Extender) icon).getExtendedInfo(bitmap, color, this, scale[0], user)
                : BitmapInfo.of(bitmap, color);
    }

    public Bitmap getUserBadgeBitmap(UserHandle user) {
        if (mUserBadgeBitmap == null) {
            Bitmap bitmap = Bitmap.createBitmap(
                    mIconBitmapSize, mIconBitmapSize, Bitmap.Config.ARGB_8888);
            Drawable badgedDrawable = mPm.getUserBadgedIcon(
                    new FixedSizeBitmapDrawable(bitmap), user);
            if (badgedDrawable instanceof BitmapDrawable) {
                mUserBadgeBitmap = ((BitmapDrawable) badgedDrawable).getBitmap();
            } else {
                badgedDrawable.setBounds(0, 0, mIconBitmapSize, mIconBitmapSize);
                mUserBadgeBitmap = BitmapRenderer.createSoftwareBitmap(
                        mIconBitmapSize, mIconBitmapSize, badgedDrawable::draw);
            }
        }
        return mUserBadgeBitmap;
    }

    public Bitmap createScaledBitmapWithoutShadow(Drawable icon, boolean shrinkNonAdaptiveIcons) {
        RectF iconBounds = new RectF();
        float[] scale = new float[1];
        icon = normalizeAndWrapToAdaptiveIcon(icon, shrinkNonAdaptiveIcons, iconBounds, scale);
        return createIconBitmap(icon,
                Math.min(scale[0], ShadowGenerator.getScaleForBounds(iconBounds)));
    }

    /**
     * Switches badging to left/right
     */
    public void setBadgeOnLeft(boolean badgeOnLeft) {
        mBadgeOnLeft = badgeOnLeft;
    }

    /**
     * Sets the background color used for wrapped adaptive icon
     */
    public void setWrapperBackgroundColor(int color) {
        mWrapperBackgroundColor = (Color.alpha(color) < 255) ? DEFAULT_WRAPPER_BACKGROUND : color;
    }

    /**
     * Disables the dominant color extraction for all icons loaded.
     */
    public void disableColorExtraction() {
        mDisableColorExtractor = true;
    }

    private Drawable normalizeAndWrapToAdaptiveIcon(@NonNull Drawable icon,
            boolean shrinkNonAdaptiveIcons, RectF outIconBounds, float[] outScale) {
        if (icon == null) {
            return null;
        }
        float scale = 1f;
/* 
        if (shrinkNonAdaptiveIcons && ATLEAST_OREO) {
            if (mWrapperIcon == null) {
                mWrapperIcon = mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper)
                        .mutate();
            }
            AdaptiveIconDrawable dr = (AdaptiveIconDrawable) mWrapperIcon;
            dr.setBounds(0, 0, 1, 1);
            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, null, null);

                ((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor);
            }
        } else {
            scale = getNormalizer().getScale(icon, outIconBounds, null, null);
        } */
        scale =getNormalizer().getScale(icon, outIconBounds, null, null);
        outScale[0] = scale;
      
        return icon;
    }

    /**
     * Adds the {@param badge} on top of {@param target} using the badge dimensions.
     */
    public void badgeWithDrawable(Bitmap target, Drawable badge) {
        mCanvas.setBitmap(target);
        badgeWithDrawable(mCanvas, badge);
        mCanvas.setBitmap(null);
    }

    /**
     * Adds the {@param badge} on top of {@param target} using the badge dimensions.
     */
    public void badgeWithDrawable(Canvas target, Drawable badge) {
        int badgeSize = getBadgeSizeForIconSize(mIconBitmapSize);
        if (mBadgeOnLeft) {
            badge.setBounds(0, mIconBitmapSize - badgeSize, badgeSize, mIconBitmapSize);
        } else {
            badge.setBounds(mIconBitmapSize - badgeSize, mIconBitmapSize - badgeSize,
                    mIconBitmapSize, mIconBitmapSize);
        }
        badge.draw(target);
    }

    private Bitmap createIconBitmap(Drawable icon, float scale) {
        return createIconBitmap(icon, scale, mIconBitmapSize);
    }

    /**
     * @param icon drawable that should be flattened to a bitmap
     * @param scale the scale to apply before drawing {@param icon} on the canvas
     */
    public Bitmap createIconBitmap(@NonNull Drawable icon, float scale, int size) {
        Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
        if (icon == null) {
            return bitmap;
        }
        mCanvas.setBitmap(bitmap);
        mOldBounds.set(icon.getBounds());

        if (ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
            int offset = Math.max((int) Math.ceil(BLUR_FACTOR * size),
                    Math.round(size * (1 - scale) / 2 ));
            icon.setBounds(offset, offset, size - offset, size - offset);
            if (icon instanceof BitmapInfo.Extender) {
                ((Extender) icon).drawForPersistence(mCanvas);
            } else {
                icon.draw(mCanvas);
            }
        } else {
            if (icon instanceof BitmapDrawable) {
                BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
                Bitmap b = bitmapDrawable.getBitmap();
                if (bitmap != null && b.getDensity() == Bitmap.DENSITY_NONE) {
                    bitmapDrawable.setTargetDensity(mContext.getResources().getDisplayMetrics());
                }
            }
            int width = size;
            int height = size;

            int intrinsicWidth = icon.getIntrinsicWidth();
            int intrinsicHeight = icon.getIntrinsicHeight();
            if (intrinsicWidth > 0 && intrinsicHeight > 0) {
                // Scale the icon proportionally to the icon dimensions
                final float ratio = (float) intrinsicWidth / intrinsicHeight;
                if (intrinsicWidth > intrinsicHeight) {
                    height = (int) (width / ratio);
                } else if (intrinsicHeight > intrinsicWidth) {
                    width = (int) (height * ratio);
                }
            }
            final int left = (size - width) / 2;
            final int top = (size - height) / 2;
            icon.setBounds(left, top, left + width, top + height);
            mCanvas.save();
            mCanvas.scale(scale, scale, size / 2, size / 2);
            icon.draw(mCanvas);
            mCanvas.restore();

        }
        icon.setBounds(mOldBounds);
        mCanvas.setBitmap(null);
        return bitmap;
    }

    @Override
    public void close() {
        clear();
    }

    public BitmapInfo makeDefaultIcon(UserHandle user) {
        return createBadgedIconBitmap(getFullResDefaultActivityIcon(mFillResIconDpi),
                user, Build.VERSION.SDK_INT);
    }

    public static Drawable getFullResDefaultActivityIcon(int iconDpi) {
        return Resources.getSystem().getDrawableForDensity(
                Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
                        ? android.R.drawable.sym_def_app_icon : android.R.mipmap.sym_def_app_icon,
                iconDpi);
    }

    /**
     * Badges the provided source with the badge info
     */
    public BitmapInfo badgeBitmap(Bitmap source, BitmapInfo badgeInfo) {
        Bitmap icon = BitmapRenderer.createHardwareBitmap(mIconBitmapSize, mIconBitmapSize, (c) -> {
            getShadowGenerator().recreateIcon(source, c);
            badgeWithDrawable(c, new FixedSizeBitmapDrawable(badgeInfo.icon));
        });
        return BitmapInfo.of(icon, badgeInfo.color);
    }

    private int extractColor(Bitmap bitmap) {
        return mDisableColorExtractor ? 0 : mColorExtractor.findDominantColorByHue(bitmap);
    }

    /**
     * Returns the correct badge size given an icon size
     */
    public static int getBadgeSizeForIconSize(int iconSize) {
        return (int) (ICON_BADGE_SCALE * iconSize);
    }

    /**
     * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
     * This allows the badging to be done based on the action bitmap size rather than
     * the scaled bitmap size.
     */
    private static class FixedSizeBitmapDrawable extends BitmapDrawable {

        public FixedSizeBitmapDrawable(Bitmap bitmap) {
            super(null, bitmap);
        }

        @Override
        public int getIntrinsicHeight() {
            return getBitmap().getWidth();
        }

        @Override
        public int getIntrinsicWidth() {
            return getBitmap().getWidth();
        }
    }
}

4.修改FixedScaleDrawable :

核心修改如下:

    private static final float LEGACY_ICON_SCALE = 1.0f;
package com.android.launcher3.icons;

import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.graphics.Canvas;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.DrawableWrapper;
import android.util.AttributeSet;

import org.xmlpull.v1.XmlPullParser;

/**
 * Extension of {@link DrawableWrapper} which scales the child drawables by a fixed amount.
 */
public class FixedScaleDrawable extends DrawableWrapper {

    // TODO b/33553066 use the constant defined in MaskableIconDrawable
    private static final float LEGACY_ICON_SCALE = 1.0f;
    private float mScaleX, mScaleY;

    public FixedScaleDrawable() {
        super(new ColorDrawable());
        mScaleX = LEGACY_ICON_SCALE;
        mScaleY = LEGACY_ICON_SCALE;
    }

    @Override
    public void draw(Canvas canvas) {
        int saveCount = canvas.save();
        canvas.scale(mScaleX, mScaleY,
                getBounds().exactCenterX(), getBounds().exactCenterY());
        super.draw(canvas);
        canvas.restoreToCount(saveCount);
    }

    @Override
    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) { }

    @Override
    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) { }

    public void setScale(float scale) {
        float h = getIntrinsicHeight();
        float w = getIntrinsicWidth();
        mScaleX = scale * LEGACY_ICON_SCALE;
        mScaleY = scale * LEGACY_ICON_SCALE;
        if (h > w && w > 0) {
            mScaleX *= w / h;
        } else if (w > h && h > 0) {
            mScaleY *= h / w;
        }
    }
}

5.修改IconNormalizer:

核心修改方法如下:

    private static float getScale(float hullArea, float boundingArea, float fullArea) {
        float hullByRect = hullArea / boundingArea;
        float scaleRequired;
        if (hullByRect < CIRCLE_AREA_BY_RECT) {
            scaleRequired = MAX_CIRCLE_AREA_FACTOR;
        } else {
            scaleRequired = MAX_SQUARE_AREA_FACTOR + LINEAR_SCALE_SLOPE * (1 - hullByRect);
        }

        float areaScale = hullArea / fullArea;
        // Use sqrt of the final ratio as the images is scaled across both width and height.
        float scale = (float) Math.sqrt(scaleRequired / areaScale);
        return scale < 1f ? scale : 1f;
    }
/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

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;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

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
    private static final float MAX_CIRCLE_AREA_FACTOR = 380.0f / 576;

    private static final float CIRCLE_AREA_BY_RECT = (float) Math.PI / 4;

    // Slope used to calculate icon visible area to full icon size for any generic shaped icon.
    private static final float LINEAR_SCALE_SLOPE =
            (MAX_CIRCLE_AREA_FACTOR - MAX_SQUARE_AREA_FACTOR) / (1 - CIRCLE_AREA_BY_RECT);

    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 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(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);
        mCanvas = new Canvas(mBitmap);
        mPixels = new byte[mMaxSize * mMaxSize];
        mLeftBorder = new float[mMaxSize];
        mRightBorder = new float[mMaxSize];
        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) {
        float hullByRect = hullArea / boundingArea;
        float scaleRequired;
        if (hullByRect < CIRCLE_AREA_BY_RECT) {
            scaleRequired = MAX_CIRCLE_AREA_FACTOR;
        } else {
            scaleRequired = MAX_SQUARE_AREA_FACTOR + LINEAR_SCALE_SLOPE * (1 - hullByRect);
        }

        float areaScale = hullArea / fullArea;
        // Use sqrt of the final ratio as the images is scaled across both width and height.
        float scale = (float) Math.sqrt(scaleRequired / areaScale);
        return scale < 1f ? scale : 1f;
    }

    /**
     * @param d Should be AdaptiveIconDrawable
     * @param size Canvas size to use
     */
    @TargetApi(Build.VERSION_CODES.O)
    public static float normalizeAdaptiveIcon(Drawable d, int size, @Nullable RectF outBounds) {
        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));

        Rect hullBounds = region.getBounds();
        int hullArea = GraphicsUtils.getArea(region);

        if (outBounds != null) {
            float sizeF = size;
            outBounds.set(
                    hullBounds.left / sizeF,
                    hullBounds.top / sizeF,
                    1 - (hullBounds.right / sizeF),
                    1 - (hullBounds.bottom / sizeF));
        }
        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.
     *
     * We first calculate the convex hull of the visible portion of the icon.
     * This hull then compared with the bounding rectangle of the hull to find how closely it
     * resembles a circle and a square, by comparing the ratio of the areas. Note that this is not an
     * ideal solution but it gives satisfactory result without affecting the performance.
     *
     * This closeness is used to determine the ratio of hull area to the full icon size.
     * Refer {@link #MAX_CIRCLE_AREA_FACTOR} and {@link #MAX_SQUARE_AREA_FACTOR}
     *
     * @param outBounds optional rect to receive the fraction distance from each edge.
     */
    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);
            }
            if (outBounds != null) {
                outBounds.set(mAdaptiveIconBounds);
            }
            return mAdaptiveIconScale;
        }
        int width = d.getIntrinsicWidth();
        int height = d.getIntrinsicHeight();
        if (width <= 0 || height <= 0) {
            width = width <= 0 || width > mMaxSize ? mMaxSize : width;
            height = height <= 0 || height > mMaxSize ? mMaxSize : height;
        } else if (width > mMaxSize || height > mMaxSize) {
            int max = Math.max(width, height);
            width = mMaxSize * width / max;
            height = mMaxSize * height / max;
        }

        mBitmap.eraseColor(Color.TRANSPARENT);
        d.setBounds(0, 0, width, height);
        d.draw(mCanvas);

        ByteBuffer buffer = ByteBuffer.wrap(mPixels);
        buffer.rewind();
        mBitmap.copyPixelsToBuffer(buffer);

        // Overall bounds of the visible icon.
        int topY = -1;
        int bottomY = -1;
        int leftX = mMaxSize + 1;
        int rightX = -1;

        // Create border by going through all pixels one row at a time and for each row find
        // the first and the last non-transparent pixel. Set those values to mLeftBorder and
        // mRightBorder and use -1 if there are no visible pixel in the row.

        // buffer position
        int index = 0;
        // buffer shift after every row, width of buffer = mMaxSize
        int rowSizeDiff = mMaxSize - width;
        // first and last position for any row.
        int firstX, lastX;

        for (int y = 0; y < height; y++) {
            firstX = lastX = -1;
            for (int x = 0; x < width; x++) {
                if ((mPixels[index] & 0xFF) > MIN_VISIBLE_ALPHA) {
                    if (firstX == -1) {
                        firstX = x;
                    }
                    lastX = x;
                }
                index++;
            }
            index += rowSizeDiff;

            mLeftBorder[y] = firstX;
            mRightBorder[y] = lastX;

            // If there is at least one visible pixel, update the overall bounds.
            if (firstX != -1) {
                bottomY = y;
                if (topY == -1) {
                    topY = y;
                }

                leftX = Math.min(leftX, firstX);
                rightX = Math.max(rightX, lastX);
            }
        }

        if (topY == -1 || rightX == -1) {
            // No valid pixels found. Do not scale.
            return 1;
        }

        convertToConvexArray(mLeftBorder, 1, topY, bottomY);
        convertToConvexArray(mRightBorder, -1, topY, bottomY);

        // Area of the convex hull
        float area = 0;
        for (int y = 0; y < height; y++) {
            if (mLeftBorder[y] <= -1) {
                continue;
            }
            area += mRightBorder[y] - mLeftBorder[y] + 1;
        }

        mBounds.left = leftX;
        mBounds.right = rightX;

        mBounds.top = topY;
        mBounds.bottom = bottomY;

        if (outBounds != null) {
            outBounds.set(((float) mBounds.left) / width, ((float) mBounds.top) / height,
                    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);
    }

    /**
     * Modifies {@param xCoordinates} to represent a convex border. Fills in all missing values
     * (except on either ends) with appropriate values.
     * @param xCoordinates map of x coordinate per y.
     * @param direction 1 for left border and -1 for right border.
     * @param topY the first Y position (inclusive) with a valid value.
     * @param bottomY the last Y position (inclusive) with a valid value.
     */
    private static void convertToConvexArray(
            float[] xCoordinates, int direction, int topY, int bottomY) {
        int total = xCoordinates.length;
        // The tangent at each pixel.
        float[] angles = new float[total - 1];

        int first = topY; // First valid y coordinate
        int last = -1;    // Last valid y coordinate which didn't have a missing value

        float lastAngle = Float.MAX_VALUE;

        for (int i = topY + 1; i <= bottomY; i++) {
            if (xCoordinates[i] <= -1) {
                continue;
            }
            int start;

            if (lastAngle == Float.MAX_VALUE) {
                start = first;
            } else {
                float currentAngle = (xCoordinates[i] - xCoordinates[last]) / (i - last);
                start = last;
                // If this position creates a concave angle, keep moving up until we find a
                // position which creates a convex angle.
                if ((currentAngle - lastAngle) * direction < 0) {
                    while (start > first) {
                        start --;
                        currentAngle = (xCoordinates[i] - xCoordinates[start]) / (i - start);
                        if ((currentAngle - angles[start]) * direction >= 0) {
                            break;
                        }
                    }
                }
            }

            // Reset from last check
            lastAngle = (xCoordinates[i] - xCoordinates[start]) / (i - start);
            // Update all the points from start.
            for (int j = start; j < i; j++) {
                angles[j] = lastAngle;
                xCoordinates[j] = xCoordinates[start] + lastAngle * (j - start);
            }
            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));
    }
}

6.修改后的效果如下:

可以看到图标都正常显示了,不管是圆角还是圆形,当然还有方角、8边形、水滴图标等等。

6.1 圆角图标

在这里插入图片描述

6.2 圆形图标:

在这里插入图片描述

7.总结:

在Android12 Launcher3中系统设置App桌面图标形状后会出现一个白边,导致显示很难看,这明细需要修改,解决方法就是在系统源码中修改缩放级别,默认不缩小图标,以上方法需要编译源码后重新打包验证.

  • 修改BaseIconFactory,
  • 修改FixedScaleDrawable
  • 修改IconNormalizer
  • 当然这只是系统级修改,如果想要App本来的图标就适配Android12启动图标
  • 普通App不会有此问题,App在手机上会自行适配,因为这是在系统Launcher设置App形状后出现的.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2385817.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【iOS】分类、扩展、关联对象

分类、扩展、关联对象 前言分类扩展扩展和分类的区别关联对象key的几种用法流程 总结 前言 最近的学习中笔者发现自己对于分类、扩展相关知识并不是很熟悉&#xff0c;刚好看源码类的加载过程中发现有类扩展与关联对象详解。本篇我们来探索一下这部分相关知识&#xff0c;首先…

内蒙古工程系列建设工程技术人才评审条件

关于印发《内蒙古自治区工程系列建设工程专业技术人才职称评审条件》的通知 内蒙古工程系列建设工程技术人才评审条件适用范围 内蒙古工程系列建设工程技术人才评审条件之技术员评审要求 内蒙古工程系列建设工程技术人才评审条件之助理工程师评审要求 内蒙古工程系列建设工程技…

目标检测DINO-DETR(2023)详细解读

文章目录 对比去噪训练混合查询选择look forward twice 论文全称为&#xff1a;DETR with Improved DeNoising Anchor Boxes for End-to-End Object Detection 提出了三个新的方法&#xff1a; 首先&#xff0c;为了改进一对一的匹配效果&#xff0c;提出了一种对比去噪训练方法…

SGlang 推理模型优化(PD架构分离)

一、技术背景 随着大型语言模型&#xff08;LLM&#xff09;广泛应用于搜索、内容生成、AI助手等领域&#xff0c;对模型推理服务的并发能力、响应延迟和资源利用效率提出了前所未有的高要求。与模型训练相比&#xff0c;推理是一个持续进行、资源消耗巨大的任务&#xff0c;尤…

Vue语法【2】

1.插值表达式&#xff1a; 语法规则&#xff1a; {{Vue实例中data的变量名}}使用场景&#xff1a; 插值表达式一般使用在文本内容中&#xff0c;如果是元素的属性内容中则无法使用&#xff1b; 案例&#xff1a; <!DOCTYPE html> <html lang"en"> &l…

2.2.1 05年T2

引言 本文将从一预习、二自习、三学习、四复习等四个阶段来分析2005年考研英语阅读第二篇文章。为了便于后续阅读&#xff0c;我将第四部分复习放在了首位。 四、复习 方法&#xff1a;错误思路分析总结考点文章梳理 4.1 错题分析 题目&#xff1a;26&#xff08;细节题&…

Linux虚拟文件系统(2)

2.3 目录项-dentry 目录项&#xff0c;即 dentry&#xff0c;用来记录文件的名字、索引节点指针以及与其他目录项的关联关系。多个关联的目录项&#xff0c;就构成了文件系统的目录结构。和上一章中超级块和索引节点不同&#xff0c;目录项并不是实际存在于磁盘上的&#xff0c…

【数据结构】栈和队列(上)

目录 一、栈&#xff08;先进后出、后进先出的线性表&#xff09; 1、栈的概念及结构 2、栈的底层结构分析 二、代码实现 1、定义一个栈 2、栈的初始化 3、入栈 3、增容 4、出栈 5、取栈顶 6、销毁栈 一、栈&#xff08;先进后出、后进先出的线性表&#xff09; 1、…

科技赋能·长效治理|无忧树建筑修缮渗漏水长效治理交流会圆满举行!

聚焦行业痛点&#xff0c;共话长效未来&#xff01;5月16日&#xff0c;由无忧树主办的主题为“科技赋能长效治理”的建筑修缮渗漏水长效治理技术交流会在上海圆满举行。来自全国的建筑企业代表、专家学者、技术精英齐聚一堂&#xff0c;共探渗漏治理前沿技术&#xff0c;见证科…

【闲聊篇】java好丰富!

1、在学习mybatis-plus的文档时&#xff0c;发现引入了solon依赖&#xff0c;才发现这是一个对标spring生态的框架&#xff0c;有意思&#xff01; 还有若依框架&#xff0c;真的好丰富~~~~~~~ 2、今天面试官问我&#xff0c;他说很少遇到用redission做延迟队列的。后面我就反…

6.3.2图的深度优先遍历

知识总览&#xff1a; 树的先根遍历&#xff1a; 采用递归一直找某个节点的子树直到找不到从上往下找 访问根节点1&#xff0c;1的子树有2、3、4,访问2&#xff0c;2节点子树有5访问5,5没有子树&#xff0c;退回到2,2还有子树6访问6,6没有子树再退回到2,2的子树都被访问了再退…

畅游Diffusion数字人(30):情绪化数字人视频生成

畅游Diffusion数字人(0):专栏文章导航 前言:仅从音频生成此类运动极具挑战性,因为它在音频和运动之间存在一对多的相关性。运动视频的情绪是多元化的选择,之前的工作很少考虑情绪化的数字人生成。今天解读一个最新的工作FLOAT,可以生成制定情绪化的数字人视频。 目录 贡献…

UE5 Va Res发送请求、处理请求、json使用

文章目录 介绍发送一个Get请求发送Post请求设置请求头请求体带添json发送请求完整的发送蓝图 处理收到的数据常用的json处理节点 介绍 UE5 自带的Http插件&#xff0c;插件内自带json解析功能 发送一个Get请求 只能写在事件图表里 发送Post请求 只能写在事件图表里 设置…

【读代码】BAGEL:统一多模态理解与生成的模型

一、项目概览 1.1 核心定位 BAGEL是字节跳动推出的开源多模态基础模型,具有70亿激活参数(140亿总参数)。该模型在统一架构下实现了三大核心能力: 多模态理解:在MME、MMBench等9大评测基准中超越Qwen2.5-VL等主流模型文本生成图像:生成质量媲美SD3等专业生成模型智能图像…

隧道自动化监测解决方案

行业现状 隧道作为一种重要的交通运输通道&#xff0c;不管是缓解交通压力&#xff0c;还是让路网结构更趋于完善&#xff0c;它都有着不可估量的作用。隧道在运营过程中&#xff0c;由于受到材料退化、地震、人为因素等影响会发生隧道主体结构的损坏和劣化。若不及时检修和维护…

游戏引擎学习第307天:排序组可视化

简短谈谈直播编程的一些好处。 上次结束后&#xff0c;很多人都指出代码中存在一个拼写错误&#xff0c;因此这次我们一开始就知道有一个 bug 等待修复&#xff0c;省去了调试寻找错误的时间。 今天的任务就是修复这个已知 bug&#xff0c;然后继续排查其他潜在的问题。如果短…

java接口自动化初识

简介 了解什么是接口和为什么要做接口测试。并且知道接口自动化测试应该学习哪些技术以及接口自动化测试的落地过程。 一、什么是接口 在这里我举了一个比较生活化的例子&#xff0c;比如我们有一台笔记本&#xff0c;在笔记本的两端有很多插口。例如&#xff1a;USB插口。那…

NVM安装使用及问题解决

目录 一、前言 二、NVM安装 三、配置下载源 四、nvm使用 五、安装nvm list available没有的版本 六、问题解决 一、前言 如果你开发 Node.js 项目&#xff0c;可能会遇到这些问题&#xff1a; ①新项目需要 Node.js 18&#xff0c;但老项目只能用 Node.js 14&#xff0c;…

C++学习之STL学习:string类使用

在之前的学习中&#xff0c;我们初步了解到了STL的概念&#xff0c;接下来我们将深入学习STL中的string类的使用&#xff0c;后续还会结合他们的功能进行模拟实验 目录 为什么要学习string类&#xff1f; 标准库中的string类 string类&#xff08;了解&#xff09; auto和范围…

5月24日day35打卡

模型可视化与推理 知识点回顾&#xff1a; 三种不同的模型可视化方法&#xff1a;推荐torchinfo打印summary权重分布可视化进度条功能&#xff1a;手动和自动写法&#xff0c;让打印结果更加美观推理的写法&#xff1a;评估模式 作业&#xff1a;调整模型定义时的超参数&#x…