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

Commit 7b4c1e2d authored by Charlie Anderson's avatar Charlie Anderson
Browse files

Use BitmapShader to anti-alias shaped icons

Bug: 402202632
Test: manually changing shapes, image tests
Flag: com.android.launcher3.enable_launcher_icon_shapes

Change-Id: I52b1147c105259d94f5aa7c18e0748be07e471f8
parent 76ac1cf8
Loading
Loading
Loading
Loading
+56 −17
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@ import static android.graphics.Paint.FILTER_BITMAP_FLAG;
import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction;

import static com.android.launcher3.icons.BitmapInfo.FLAG_INSTANT;
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
import static com.android.launcher3.icons.ShadowGenerator.BLUR_FACTOR;
import static com.android.launcher3.icons.ShadowGenerator.ICON_SCALE_FOR_SHADOWS;

@@ -19,12 +20,14 @@ import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.Shader.TileMode;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
@@ -91,8 +94,10 @@ public class BaseIconFactory implements AutoCloseable {
    @Nullable
    private ShadowGenerator mShadowGenerator;

    // Shadow bitmap used as background for theme icons
    /** Shadow bitmap used as background for theme icons */
    private Bitmap mWhiteShadowLayer;
    /** Bitmap used for {@link BitmapShader} to mask Adaptive Icons when drawing */
    private Bitmap mShaderBitmap;

    private int mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;

@@ -170,7 +175,7 @@ public class BaseIconFactory implements AutoCloseable {
        AdaptiveIconDrawable drawable = new AdaptiveIconDrawable(
                new ColorDrawable(PLACEHOLDER_BACKGROUND_COLOR),
                new CenterTextDrawable(placeholder, color));
        Bitmap icon = createIconBitmap(drawable, IconNormalizer.ICON_VISIBLE_AREA_FACTOR);
        Bitmap icon = createIconBitmap(drawable, ICON_VISIBLE_AREA_FACTOR);
        return BitmapInfo.of(icon, color);
    }

@@ -224,7 +229,6 @@ public class BaseIconFactory implements AutoCloseable {
        AdaptiveIconDrawable adaptiveIcon = normalizeAndWrapToAdaptiveIcon(tempIcon, scale);
        Bitmap bitmap = createIconBitmap(adaptiveIcon, scale[0],
                options == null ? MODE_WITH_SHADOW : options.mGenerationMode);

        int color = (options != null && options.mExtractedColor != null)
                ? options.mExtractedColor : ColorExtractor.findDominantColorByHue(bitmap);
        BitmapInfo info = BitmapInfo.of(bitmap, color);
@@ -308,6 +312,42 @@ public class BaseIconFactory implements AutoCloseable {
        return mWhiteShadowLayer;
    }

    /**
     * Takes an {@link AdaptiveIconDrawable} and uses it to create a new Shader Bitmap.
     * {@link mShaderBitmap} will be used to create a {@link BitmapShader} for masking,
     * such as for icon shapes. Will reuse underlying Bitmap where possible.
     *
     * @param adaptiveIcon AdaptiveIconDrawable to draw with shader
     */
    @NonNull
    private Bitmap getAdaptiveShaderBitmap(AdaptiveIconDrawable adaptiveIcon) {
        Rect bounds = adaptiveIcon.getBounds();
        int iconWidth = bounds.width();
        int iconHeight = bounds.width();

        BitmapRenderer shaderRenderer = new BitmapRenderer() {
            @Override
            public void draw(Canvas canvas) {
                canvas.translate(-bounds.left, -bounds.top);
                canvas.drawColor(BLACK);
                if (adaptiveIcon.getBackground() != null) {
                    adaptiveIcon.getBackground().draw(canvas);
                }
                if (adaptiveIcon.getForeground() != null) {
                    adaptiveIcon.getForeground().draw(canvas);
                }
            }
        };
        if (mShaderBitmap == null || iconWidth != mShaderBitmap.getWidth()
                || iconHeight != mShaderBitmap.getHeight()) {
            mShaderBitmap = BitmapRenderer.createSoftwareBitmap(iconWidth, iconHeight,
                    shaderRenderer);
        } else {
            shaderRenderer.draw(new Canvas(mShaderBitmap));
        }
        return mShaderBitmap;
    }

    @NonNull
    public Bitmap createScaledBitmap(@NonNull Drawable icon, @BitmapGenerationMode int mode) {
        float[] scale = new float[1];
@@ -329,7 +369,7 @@ public class BaseIconFactory implements AutoCloseable {
            return null;
        }

        outScale[0] = IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
        outScale[0] = ICON_VISIBLE_AREA_FACTOR;
        return wrapToAdaptiveIcon(icon);
    }

@@ -426,6 +466,7 @@ public class BaseIconFactory implements AutoCloseable {
            } else {
                drawAdaptiveIcon(canvas, aid, shapePath);
            }

            canvas.restoreToCount(count);
        } else {
            if (icon instanceof BitmapDrawable) {
@@ -473,28 +514,26 @@ public class BaseIconFactory implements AutoCloseable {
    }

    /**
     * Draws AdaptiveIconDrawable onto canvas.
     * Draws AdaptiveIconDrawable onto canvas using provided Path
     * and {@link mShaderBitmap} as a shader.
     *
     * @param canvas    canvas to draw on
     * @param drawable  AdaptiveIconDrawable to draw
     * @param overridePath path to clip icon with for shapes
     * @param shapePath path to clip icon with for shapes
     */
    protected void drawAdaptiveIcon(
            @NonNull Canvas canvas,
            @NonNull AdaptiveIconDrawable drawable,
            @NonNull Path overridePath
            @NonNull Path shapePath
    ) {
        if (!Flags.enableLauncherIconShapes()) {
            drawable.draw(canvas);
            return;
        }
        canvas.clipPath(overridePath);
        canvas.drawColor(BLACK);
        if (drawable.getBackground() != null) {
            drawable.getBackground().draw(canvas);
        }
        if (drawable.getForeground() != null) {
            drawable.getForeground().draw(canvas);
        }
        Bitmap shaderBitmap = getAdaptiveShaderBitmap(drawable);
        Paint paint = new Paint();
        paint.setShader(new BitmapShader(shaderBitmap, TileMode.CLAMP, TileMode.CLAMP));
        canvas.drawPath(shapePath, paint);
    }

    @Override