Fine tune splash screen icon generation algorithm

Filter out the translucent pixels when compute the foreground colors
of an adaptive icon, which should make sense because the translucent
pixels on icon should only used for decoration, do not let it affect
the opaque pixels, also with this filter we won't drop down the
performance when using palette.

Bug: 193304878
Test: manual launch single color background apps.
Change-Id: Ia9873cf907d30bb7f30318a289a54159d0a51ee3
This commit is contained in:
wilsonshih
2021-07-14 19:35:15 +08:00
parent 61d71ccaae
commit 331615ad61

View File

@@ -23,6 +23,7 @@ import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLA
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
import android.annotation.ColorInt;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.ActivityThread;
import android.content.BroadcastReceiver;
@@ -64,6 +65,7 @@ import com.android.wm.shell.common.TransactionPool;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.IntPredicate;
import java.util.function.IntSupplier;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
@@ -132,7 +134,6 @@ public class SplashscreenContentDrawer {
* @param splashScreenViewConsumer Receiving the SplashScreenView object, which will also be
* executed on splash screen thread. Note that the view can be
* null if failed.
* @param bgColorConsumer Receiving the background color once it's estimated complete.
*/
void createContentView(Context context, @StartingWindowType int suggestType, ActivityInfo info,
int taskId, Consumer<SplashScreenView> splashScreenViewConsumer) {
@@ -209,9 +210,9 @@ public class SplashscreenContentDrawer {
}
private static int estimateWindowBGColor(Drawable themeBGDrawable) {
final DrawableColorTester themeBGTester =
new DrawableColorTester(themeBGDrawable, true /* filterTransparent */);
if (themeBGTester.nonTransparentRatio() == 0) {
final DrawableColorTester themeBGTester = new DrawableColorTester(
themeBGDrawable, DrawableColorTester.TRANSPARENT_FILTER /* filterType */);
if (themeBGTester.passFilterRatio() == 0) {
// the window background is transparent, unable to draw
Slog.w(TAG, "Window background is transparent, fill background with black color");
return getSystemBGColor();
@@ -414,7 +415,8 @@ public class SplashscreenContentDrawer {
final ColorCache.IconColor iconColor = mColorCache.getIconColor(
mActivityInfo.packageName, mActivityInfo.getIconResource(),
mLastPackageContextConfigHash,
() -> new DrawableColorTester(iconForeground, true /* filterTransparent */),
() -> new DrawableColorTester(iconForeground,
DrawableColorTester.TRANSLUCENT_FILTER /* filterType */),
() -> new DrawableColorTester(adaptiveIconDrawable.getBackground()));
if (DEBUG) {
@@ -443,7 +445,7 @@ public class SplashscreenContentDrawer {
// Reference AdaptiveIcon description, outer is 108 and inner is 72, so we
// scale by 192/160 if we only draw adaptiveIcon's foreground.
final float noBgScale =
iconColor.mFgNonTransparentRatio < ENLARGE_FOREGROUND_ICON_THRESHOLD
iconColor.mFgNonTranslucentRatio < ENLARGE_FOREGROUND_ICON_THRESHOLD
? NO_BACKGROUND_SCALE : 1f;
// Using AdaptiveIconDrawable here can help keep the shape consistent with the
// current settings.
@@ -545,13 +547,26 @@ public class SplashscreenContentDrawer {
}
private static class DrawableColorTester {
private static final int NO_ALPHA_FILTER = 0;
// filter out completely invisible pixels
private static final int TRANSPARENT_FILTER = 1;
// filter out translucent and invisible pixels
private static final int TRANSLUCENT_FILTER = 2;
@IntDef(flag = true, value = {
NO_ALPHA_FILTER,
TRANSPARENT_FILTER,
TRANSLUCENT_FILTER
})
private @interface QuantizerFilterType {}
private final ColorTester mColorChecker;
DrawableColorTester(Drawable drawable) {
this(drawable, false /* filterTransparent */);
this(drawable, NO_ALPHA_FILTER /* filterType */);
}
DrawableColorTester(Drawable drawable, boolean filterTransparent) {
DrawableColorTester(Drawable drawable, @QuantizerFilterType int filterType) {
// Some applications use LayerDrawable for their windowBackground. To ensure that we
// only get the real background, so that the color is not affected by the alpha of the
// upper layer, try to get the lower layer here. This can also speed up the calculation.
@@ -570,12 +585,12 @@ public class SplashscreenContentDrawer {
} else {
mColorChecker = drawable instanceof ColorDrawable
? new SingleColorTester((ColorDrawable) drawable)
: new ComplexDrawableTester(drawable, filterTransparent);
: new ComplexDrawableTester(drawable, filterType);
}
}
public float nonTransparentRatio() {
return mColorChecker.nonTransparentRatio();
public float passFilterRatio() {
return mColorChecker.passFilterRatio();
}
public boolean isComplexColor() {
@@ -594,7 +609,7 @@ public class SplashscreenContentDrawer {
* A help class to check the color information from a Drawable.
*/
private interface ColorTester {
float nonTransparentRatio();
float passFilterRatio();
boolean isComplexColor();
@@ -622,7 +637,7 @@ public class SplashscreenContentDrawer {
}
@Override
public float nonTransparentRatio() {
public float passFilterRatio() {
final int alpha = mColorDrawable.getAlpha();
return (float) (alpha / 255);
}
@@ -651,15 +666,21 @@ public class SplashscreenContentDrawer {
private static final int MAX_BITMAP_SIZE = 40;
private final Palette mPalette;
private final boolean mFilterTransparent;
private static final TransparentFilterQuantizer TRANSPARENT_FILTER_QUANTIZER =
new TransparentFilterQuantizer();
private static final AlphaFilterQuantizer ALPHA_FILTER_QUANTIZER =
new AlphaFilterQuantizer();
ComplexDrawableTester(Drawable drawable, boolean filterTransparent) {
/**
* @param drawable The test target.
* @param filterType Targeting to filter out transparent or translucent pixels,
* this would be needed if want to check
* {@link #passFilterRatio()}, also affecting the estimated result
* of the dominant color.
*/
ComplexDrawableTester(Drawable drawable, @QuantizerFilterType int filterType) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "ComplexDrawableTester");
final Rect initialBounds = drawable.copyBounds();
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
// Some drawables do not have intrinsic dimensions
if (width <= 0 || height <= 0) {
width = MAX_BITMAP_SIZE;
@@ -680,9 +701,10 @@ public class SplashscreenContentDrawer {
// The Palette API will ignore Alpha, so it cannot handle transparent pixels, but
// sometimes we will need this information to know if this Drawable object is
// transparent.
mFilterTransparent = filterTransparent;
mFilterTransparent = filterType != NO_ALPHA_FILTER;
if (mFilterTransparent) {
builder = new Palette.Builder(bitmap, TRANSPARENT_FILTER_QUANTIZER)
ALPHA_FILTER_QUANTIZER.setFilter(filterType);
builder = new Palette.Builder(bitmap, ALPHA_FILTER_QUANTIZER)
.maximumColorCount(5);
} else {
builder = new Palette.Builder(bitmap, null)
@@ -694,8 +716,8 @@ public class SplashscreenContentDrawer {
}
@Override
public float nonTransparentRatio() {
return mFilterTransparent ? TRANSPARENT_FILTER_QUANTIZER.mNonTransparentRatio : 1;
public float passFilterRatio() {
return mFilterTransparent ? ALPHA_FILTER_QUANTIZER.mPassFilterRatio : 1;
}
@Override
@@ -726,17 +748,34 @@ public class SplashscreenContentDrawer {
return true;
}
private static class TransparentFilterQuantizer implements Quantizer {
private static class AlphaFilterQuantizer implements Quantizer {
private static final int NON_TRANSPARENT = 0xFF000000;
private final Quantizer mInnerQuantizer = new VariationalKMeansQuantizer();
private float mNonTransparentRatio;
private final IntPredicate mTransparentFilter = i -> (i & NON_TRANSPARENT) != 0;
private final IntPredicate mTranslucentFilter = i ->
(i & NON_TRANSPARENT) == NON_TRANSPARENT;
private IntPredicate mFilter = mTransparentFilter;
private float mPassFilterRatio;
void setFilter(@QuantizerFilterType int filterType) {
switch (filterType) {
case TRANSLUCENT_FILTER:
mFilter = mTranslucentFilter;
break;
case TRANSPARENT_FILTER:
default:
mFilter = mTransparentFilter;
break;
}
}
@Override
public void quantize(final int[] pixels, final int maxColors) {
mNonTransparentRatio = 0;
mPassFilterRatio = 0;
int realSize = 0;
for (int i = pixels.length - 1; i > 0; i--) {
if ((pixels[i] & NON_TRANSPARENT) != 0) {
if (mFilter.test(pixels[i])) {
realSize++;
}
}
@@ -747,11 +786,11 @@ public class SplashscreenContentDrawer {
mInnerQuantizer.quantize(pixels, maxColors);
return;
}
mNonTransparentRatio = (float) realSize / pixels.length;
mPassFilterRatio = (float) realSize / pixels.length;
final int[] samplePixels = new int[realSize];
int rowIndex = 0;
for (int i = pixels.length - 1; i > 0; i--) {
if ((pixels[i] & NON_TRANSPARENT) == NON_TRANSPARENT) {
if (mFilter.test(pixels[i])) {
samplePixels[rowIndex] = pixels[i];
rowIndex++;
}
@@ -810,16 +849,16 @@ public class SplashscreenContentDrawer {
final int mBgColor;
final boolean mIsBgComplex;
final boolean mIsBgGrayscale;
final float mFgNonTransparentRatio;
final float mFgNonTranslucentRatio;
IconColor(int hash, int fgColor, int bgColor, boolean isBgComplex,
boolean isBgGrayscale, float fgNonTransparentRatio) {
boolean isBgGrayscale, float fgNonTranslucnetRatio) {
super(hash);
mFgColor = fgColor;
mBgColor = bgColor;
mIsBgComplex = isBgComplex;
mIsBgGrayscale = isBgGrayscale;
mFgNonTransparentRatio = fgNonTransparentRatio;
mFgNonTranslucentRatio = fgNonTranslucnetRatio;
}
}
@@ -905,7 +944,7 @@ public class SplashscreenContentDrawer {
final DrawableColorTester bgTester = bgColorTesterSupplier.get();
final IconColor iconColor = new IconColor(hash, fgTester.getDominateColor(),
bgTester.getDominateColor(), bgTester.isComplexColor(), bgTester.isGrayscale(),
fgTester.nonTransparentRatio());
fgTester.passFilterRatio());
colors.mIconColors[leastUsedIndex[0]] = iconColor;
return iconColor;
}