Merge "Fine tune splash screen icon generation algorithm" into sc-dev

This commit is contained in:
Wei Sheng Shih
2021-07-23 13:31:27 +00:00
committed by Android (Google) Code Review

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