Loading iconloaderlib/res/values-night-v31/colors.xml +0 −1 Original line number Diff line number Diff line Loading @@ -19,7 +19,6 @@ <resources> <color name="themed_icon_color">@android:color/system_accent1_200</color> <color name="themed_icon_background_color">@android:color/system_accent2_800</color> <color name="themed_icon_adaptive_background_color">@android:color/system_accent1_800</color> <color name="themed_badge_icon_color">@android:color/system_accent2_800</color> <color name="themed_badge_icon_background_color">@android:color/system_accent1_200</color> </resources> iconloaderlib/res/values-v31/colors.xml +0 −1 Original line number Diff line number Diff line Loading @@ -19,7 +19,6 @@ <resources> <color name="themed_icon_color">@android:color/system_accent1_700</color> <color name="themed_icon_background_color">@android:color/system_accent1_100</color> <color name="themed_icon_adaptive_background_color">@android:color/system_accent1_500</color> <color name="themed_badge_icon_color">@android:color/system_accent1_700</color> <color name="themed_badge_icon_background_color">@android:color/system_accent1_100</color> </resources> iconloaderlib/src/com/android/launcher3/icons/LuminanceComputer.kt +55 −14 Original line number Diff line number Diff line Loading @@ -14,8 +14,11 @@ package com.android.launcher3.icons import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.drawable.Drawable import android.util.Log import androidx.annotation.FloatRange import androidx.annotation.VisibleForTesting import androidx.core.graphics.ColorUtils import kotlin.math.abs Loading @@ -26,9 +29,6 @@ enum class ComputationType { /** Compute the average luminance of a drawable or a bitmap. */ AVERAGE, /** Compute the difference between the min and max luminance of a drawable or a bitmap. */ SPREAD, } /** Wrapper for the color space to use when computing the luminance. */ Loading Loading @@ -142,6 +142,7 @@ class LuminanceComputer( val targetColorWrapper = colorToColorWrapper(targetColor) val basisColorWrapper = colorToColorWrapper(basisColor) val originalTargetLuminance = targetColorWrapper.luminance val basisLuminance = basisColorWrapper.luminance // The target luminance should be between 0 and 1, so we need to clamp Loading @@ -161,14 +162,22 @@ class LuminanceComputer( return targetColorWrapper } /** * Compute the luminance of a drawable using the selected color space. * * @param drawable The drawable to compute the luminance of. */ fun computeLuminance(drawable: Drawable): Double { val bitmap = createBitmapFromDrawable(drawable) return computeLuminance(bitmap) } /** * Compute the luminance of a bitmap using the selected color space. * * @param bitmap The bitmap to compute the luminance of. * @param scale if true, the bitmap is resized to [BITMAP_SAMPLE_SIZE] for color calculation */ @JvmOverloads fun computeLuminance(bitmap: Bitmap, scale: Boolean = true): Double { fun computeLuminance(bitmap: Bitmap, scale: Boolean = false): Double { val bitmapHeight = bitmap.height val bitmapWidth = bitmap.width if (bitmapHeight == 0 || bitmapWidth == 0) { Loading Loading @@ -208,7 +217,42 @@ class LuminanceComputer( when (computationType) { ComputationType.MEDIAN -> return luminances.sorted().median() ComputationType.AVERAGE -> return luminances.average() ComputationType.SPREAD -> return luminances.max() - luminances.min() } } private fun scaleBitmap( bitmap: Bitmap, targetWidth: Int, targetHeight: Int, filter: Boolean, ): Bitmap { if (targetWidth <= 0 || targetHeight <= 0) { Log.w(TAG, "Invalid dimensions for scaling: $targetWidth x $targetHeight") return bitmap } return Bitmap.createScaledBitmap(bitmap, targetWidth, targetHeight, filter) } private fun createBitmapFromDrawable(drawable: Drawable): Bitmap { val b = Bitmap.createBitmap(BITMAP_SAMPLE_SIZE, BITMAP_SAMPLE_SIZE, Bitmap.Config.ARGB_8888) drawable.setBounds(0, 0, BITMAP_SAMPLE_SIZE, BITMAP_SAMPLE_SIZE) drawable.draw(Canvas(b)) return b } /** * Scale the height and width of a bitmap to a maximum size. * * @param height The height of the bitmap. * @param width The width of the bitmap. * @return A pair of the scaled height and width. */ @VisibleForTesting fun scaleHeightAndWidth(height: Int, width: Int): Pair<Int, Int> { if (height > width) { return Pair(BITMAP_SAMPLE_SIZE, (width * BITMAP_SAMPLE_SIZE) / height) } else { return Pair((height * BITMAP_SAMPLE_SIZE) / width, BITMAP_SAMPLE_SIZE) } } Loading Loading @@ -254,12 +298,12 @@ class LuminanceComputer( LuminanceColorSpace.HSL -> { val hsl = FloatArray(3) ColorUtils.colorToHSL(color, hsl) HslColor(hsl) return HslColor(hsl) } LuminanceColorSpace.LAB -> { val lab = DoubleArray(3) ColorUtils.colorToLAB(color, lab) LabColor(lab) return LabColor(lab) } } } Loading Loading @@ -288,13 +332,10 @@ class LuminanceComputer( const val DEFAULT_ABSOLUTE_LUMINANCE_DELTA = 0.1 @JvmStatic @JvmOverloads fun createDefaultLuminanceComputer( computationType: ComputationType = ComputationType.AVERAGE ): LuminanceComputer { fun createDefaultLuminanceComputer(): LuminanceComputer { return LuminanceComputer( LuminanceColorSpace.LAB, // Keep this as the default color space computationType, ComputationType.AVERAGE, Options( ensureMinContrast = ENABLED_CONTRAST_ADJUSTMENT, absoluteLuminanceDelta = ENABLED_ABSOLUTE_LUMINANCE_DELTA, Loading iconloaderlib/src/com/android/launcher3/icons/MonochromeIconFactory.java +19 −33 Original line number Diff line number Diff line Loading @@ -17,8 +17,6 @@ package com.android.launcher3.icons; import static android.graphics.Paint.FILTER_BITMAP_FLAG; import static com.android.launcher3.icons.LuminanceComputer.createDefaultLuminanceComputer; import android.annotation.TargetApi; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; Loading Loading @@ -56,17 +54,17 @@ public class MonochromeIconFactory extends Drawable { private final byte[] mPixels; private final int mBitmapSize; private final int mEdgePixelLength; private final Paint mDrawPaint; private final Rect mSrcRect; private double mLuminanceDiff = Double.NaN; public MonochromeIconFactory(int iconBitmapSize) { float extraFactor = AdaptiveIconDrawable.getExtraInsetFraction(); float viewPortScale = 1 / (1 + 2 * extraFactor); mBitmapSize = Math.round(iconBitmapSize * 2 * viewPortScale); mPixels = new byte[mBitmapSize * mBitmapSize]; mEdgePixelLength = mBitmapSize * (mBitmapSize - iconBitmapSize) / 2; mFlatBitmap = Bitmap.createBitmap(mBitmapSize, mBitmapSize, Config.ARGB_8888); mFlatCanvas = new Canvas(mFlatBitmap); Loading Loading @@ -112,41 +110,17 @@ public class MonochromeIconFactory extends Drawable { @WorkerThread public Drawable wrap(AdaptiveIconDrawable icon) { mFlatCanvas.drawColor(Color.BLACK); Drawable bg = icon.getBackground(); Drawable fg = icon.getForeground(); if (bg != null && fg != null) { LuminanceComputer computer = createDefaultLuminanceComputer(); // Calculate foreground luminance on black first to account for any transparent pixels drawDrawable(fg); double fgLuminance = computer.computeLuminance(mFlatBitmap); // Start drawing from scratch and calculate background luminance mFlatCanvas.drawColor(Color.BLACK); drawDrawable(bg); double bgLuminance = computer.computeLuminance(mFlatBitmap); drawDrawable(fg); mLuminanceDiff = fgLuminance - bgLuminance; } else { // We do not have separate layer information. // Try to calculate everything from a single layer drawDrawable(bg); drawDrawable(fg); LuminanceComputer computer = createDefaultLuminanceComputer(ComputationType.SPREAD); mLuminanceDiff = computer.computeLuminance(mFlatBitmap, /* scale= */ true); } drawDrawable(icon.getBackground()); drawDrawable(icon.getForeground()); generateMono(); return new InsetDrawable(this, -AdaptiveIconDrawable.getExtraInsetFraction()); } public double getLuminanceDiff() { return mLuminanceDiff; } @WorkerThread private void generateMono() { mAlphaCanvas.drawBitmap(mFlatBitmap, 0, 0, mCopyPaint); // Scale the end points: ByteBuffer buffer = ByteBuffer.wrap(mPixels); buffer.rewind(); mAlphaBitmap.copyPixelsToBuffer(buffer); Loading @@ -162,10 +136,22 @@ public class MonochromeIconFactory extends Drawable { // rescale pixels to increase contrast float range = max - min; // In order to check if the colors should be flipped, we just take the average color // of top and bottom edge which should correspond to be background color. If the edge // colors have more opacity, we flip the colors; int sum = 0; for (int i = 0; i < mEdgePixelLength; i++) { sum += (mPixels[i] & 0xFF); sum += (mPixels[mPixels.length - 1 - i] & 0xFF); } float edgeAverage = sum / (mEdgePixelLength * 2f); float edgeMapped = (edgeAverage - min) / range; boolean flipColor = edgeMapped > .5f; for (int i = 0; i < mPixels.length; i++) { int p = mPixels[i] & 0xFF; int p2 = Math.round((p - min) * 0xFF / range); mPixels[i] = (byte) (p2); mPixels[i] = flipColor ? (byte) (255 - p2) : (byte) (p2); } // Second phase of processing, aimed on increasing the contrast Loading iconloaderlib/src/com/android/launcher3/icons/mono/MonoIconThemeController.kt +59 −65 Original line number Diff line number Diff line Loading @@ -24,9 +24,9 @@ import android.graphics.Bitmap.Config.HARDWARE import android.graphics.BlendMode.SRC_IN import android.graphics.BlendModeColorFilter import android.graphics.drawable.AdaptiveIconDrawable import android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable import android.graphics.drawable.InsetDrawable import android.graphics.drawable.LayerDrawable import android.os.Build Loading Loading @@ -55,6 +55,7 @@ class MonoIconThemeController( factory: BaseIconFactory, sourceHint: SourceHint?, ): ThemedBitmap { val currentDelegateFactory = info.delegateFactory if (currentDelegateFactory is ClockAnimationInfo) { val fullDrawable = currentDelegateFactory.baseDrawableState.newDrawable() Loading @@ -72,35 +73,41 @@ class MonoIconThemeController( } } val mono = icon.monochrome val mono = getMonochromeDrawable( icon, info, sourceHint?.isFileDrawable ?: false, shouldForceThemeIcon, ) if (mono != null) { return MonoThemedBitmap( factory.createIconBitmap( InsetDrawable(mono, -getExtraInsetFraction()), 1f /* scale */, MODE_ALPHA, true, /* isFullBleed */ ), factory.createIconBitmap(mono, 1f /* scale */, MODE_ALPHA, true /* isFullBleed */), colorProvider, ) } if (Flags.forceMonochromeAppIcons() && shouldForceThemeIcon) { val monoFactory = MonochromeIconFactory(info.icon.width) val wrappedIcon = monoFactory.wrap(icon) return MonoThemedBitmap( factory.createIconBitmap( wrappedIcon, 1f /* scale */, MODE_ALPHA, true, /* isFullBleed */ ), colorProvider, monoFactory.luminanceDiff, ) return ThemedBitmap.NOT_SUPPORTED } return ThemedBitmap.NOT_SUPPORTED /** * Returns a monochromatic version of the given drawable or null, if it is not supported * * @param base the original icon */ private fun getMonochromeDrawable( base: AdaptiveIconDrawable, info: BitmapInfo, isFileDrawable: Boolean, shouldForceThemeIcon: Boolean, ): Drawable? { val mono = base.monochrome if (mono != null) { return InsetDrawable(mono, -AdaptiveIconDrawable.getExtraInsetFraction()) } if (Flags.forceMonochromeAppIcons() && shouldForceThemeIcon && !isFileDrawable) { return MonochromeIconFactory(info.icon.width).wrap(base) } return null } override fun decode( Loading @@ -110,31 +117,17 @@ class MonoIconThemeController( sourceHint: SourceHint, ): ThemedBitmap { val icon = info.icon val expectedSize = icon.height * icon.width if (bytes.size != icon.height * icon.width) return ThemedBitmap.NOT_SUPPORTED return when (bytes.size) { expectedSize -> { MonoThemedBitmap( ByteBuffer.wrap(bytes).readMonoBitmap(icon.width, icon.height), colorProvider, ) } (expectedSize + MonoThemedBitmap.DOUBLE_BYTE_SIZE) -> { val buffer = ByteBuffer.wrap(bytes) val monoBitmap = buffer.readMonoBitmap(icon.width, icon.height) val luminanceDelta = buffer.asDoubleBuffer().get() MonoThemedBitmap(monoBitmap, colorProvider, luminanceDelta) } else -> ThemedBitmap.NOT_SUPPORTED } } private fun ByteBuffer.readMonoBitmap(width: Int, height: Int): Bitmap { val monoBitmap = Bitmap.createBitmap(width, height, ALPHA_8) monoBitmap.copyPixelsFromBuffer(this) var monoBitmap = Bitmap.createBitmap(icon.width, icon.height, ALPHA_8) monoBitmap.copyPixelsFromBuffer(ByteBuffer.wrap(bytes)) val hwMonoBitmap = monoBitmap.copy(HARDWARE, false /*isMutable*/) return hwMonoBitmap?.also { monoBitmap.recycle() } ?: monoBitmap if (hwMonoBitmap != null) { monoBitmap.recycle() monoBitmap = hwMonoBitmap } return MonoThemedBitmap(monoBitmap, colorProvider) } override fun createThemedAdaptiveIcon( Loading @@ -142,28 +135,29 @@ class MonoIconThemeController( originalIcon: AdaptiveIconDrawable, info: BitmapInfo?, ): AdaptiveIconDrawable { originalIcon.mutate() originalIcon.monochrome?.let { val colors = colorProvider(context) it.setTint(colors[1]) return@createThemedAdaptiveIcon AdaptiveIconDrawable(ColorDrawable(colors[0]), it) } val themedBitmap = info?.themedBitmap as? MonoThemedBitmap ?: return originalIcon val colors = themedBitmap.getUpdatedColors(context) originalIcon.mutate() var monoDrawable = originalIcon.monochrome?.apply { setTint(colors[1]) } if (monoDrawable == null) { info?.themedBitmap?.let { themedBitmap -> if (themedBitmap is MonoThemedBitmap) { // Inject a previously generated monochrome icon // Use BitmapDrawable instead of FastBitmapDrawable so that the colorState is // preserved in constantState // Inset the drawable according to the AdaptiveIconDrawable layers val monoDrawable = monoDrawable = InsetDrawable( BitmapDrawable(themedBitmap.mono).apply { colorFilter = BlendModeColorFilter(colors[1], SRC_IN) }, getExtraInsetFraction() / 2, AdaptiveIconDrawable.getExtraInsetFraction() / 2, ) return AdaptiveIconDrawable(ColorDrawable(colors[0]), monoDrawable) } } } return monoDrawable?.let { AdaptiveIconDrawable(ColorDrawable(colors[0]), it) } ?: originalIcon } } Loading
iconloaderlib/res/values-night-v31/colors.xml +0 −1 Original line number Diff line number Diff line Loading @@ -19,7 +19,6 @@ <resources> <color name="themed_icon_color">@android:color/system_accent1_200</color> <color name="themed_icon_background_color">@android:color/system_accent2_800</color> <color name="themed_icon_adaptive_background_color">@android:color/system_accent1_800</color> <color name="themed_badge_icon_color">@android:color/system_accent2_800</color> <color name="themed_badge_icon_background_color">@android:color/system_accent1_200</color> </resources>
iconloaderlib/res/values-v31/colors.xml +0 −1 Original line number Diff line number Diff line Loading @@ -19,7 +19,6 @@ <resources> <color name="themed_icon_color">@android:color/system_accent1_700</color> <color name="themed_icon_background_color">@android:color/system_accent1_100</color> <color name="themed_icon_adaptive_background_color">@android:color/system_accent1_500</color> <color name="themed_badge_icon_color">@android:color/system_accent1_700</color> <color name="themed_badge_icon_background_color">@android:color/system_accent1_100</color> </resources>
iconloaderlib/src/com/android/launcher3/icons/LuminanceComputer.kt +55 −14 Original line number Diff line number Diff line Loading @@ -14,8 +14,11 @@ package com.android.launcher3.icons import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.drawable.Drawable import android.util.Log import androidx.annotation.FloatRange import androidx.annotation.VisibleForTesting import androidx.core.graphics.ColorUtils import kotlin.math.abs Loading @@ -26,9 +29,6 @@ enum class ComputationType { /** Compute the average luminance of a drawable or a bitmap. */ AVERAGE, /** Compute the difference between the min and max luminance of a drawable or a bitmap. */ SPREAD, } /** Wrapper for the color space to use when computing the luminance. */ Loading Loading @@ -142,6 +142,7 @@ class LuminanceComputer( val targetColorWrapper = colorToColorWrapper(targetColor) val basisColorWrapper = colorToColorWrapper(basisColor) val originalTargetLuminance = targetColorWrapper.luminance val basisLuminance = basisColorWrapper.luminance // The target luminance should be between 0 and 1, so we need to clamp Loading @@ -161,14 +162,22 @@ class LuminanceComputer( return targetColorWrapper } /** * Compute the luminance of a drawable using the selected color space. * * @param drawable The drawable to compute the luminance of. */ fun computeLuminance(drawable: Drawable): Double { val bitmap = createBitmapFromDrawable(drawable) return computeLuminance(bitmap) } /** * Compute the luminance of a bitmap using the selected color space. * * @param bitmap The bitmap to compute the luminance of. * @param scale if true, the bitmap is resized to [BITMAP_SAMPLE_SIZE] for color calculation */ @JvmOverloads fun computeLuminance(bitmap: Bitmap, scale: Boolean = true): Double { fun computeLuminance(bitmap: Bitmap, scale: Boolean = false): Double { val bitmapHeight = bitmap.height val bitmapWidth = bitmap.width if (bitmapHeight == 0 || bitmapWidth == 0) { Loading Loading @@ -208,7 +217,42 @@ class LuminanceComputer( when (computationType) { ComputationType.MEDIAN -> return luminances.sorted().median() ComputationType.AVERAGE -> return luminances.average() ComputationType.SPREAD -> return luminances.max() - luminances.min() } } private fun scaleBitmap( bitmap: Bitmap, targetWidth: Int, targetHeight: Int, filter: Boolean, ): Bitmap { if (targetWidth <= 0 || targetHeight <= 0) { Log.w(TAG, "Invalid dimensions for scaling: $targetWidth x $targetHeight") return bitmap } return Bitmap.createScaledBitmap(bitmap, targetWidth, targetHeight, filter) } private fun createBitmapFromDrawable(drawable: Drawable): Bitmap { val b = Bitmap.createBitmap(BITMAP_SAMPLE_SIZE, BITMAP_SAMPLE_SIZE, Bitmap.Config.ARGB_8888) drawable.setBounds(0, 0, BITMAP_SAMPLE_SIZE, BITMAP_SAMPLE_SIZE) drawable.draw(Canvas(b)) return b } /** * Scale the height and width of a bitmap to a maximum size. * * @param height The height of the bitmap. * @param width The width of the bitmap. * @return A pair of the scaled height and width. */ @VisibleForTesting fun scaleHeightAndWidth(height: Int, width: Int): Pair<Int, Int> { if (height > width) { return Pair(BITMAP_SAMPLE_SIZE, (width * BITMAP_SAMPLE_SIZE) / height) } else { return Pair((height * BITMAP_SAMPLE_SIZE) / width, BITMAP_SAMPLE_SIZE) } } Loading Loading @@ -254,12 +298,12 @@ class LuminanceComputer( LuminanceColorSpace.HSL -> { val hsl = FloatArray(3) ColorUtils.colorToHSL(color, hsl) HslColor(hsl) return HslColor(hsl) } LuminanceColorSpace.LAB -> { val lab = DoubleArray(3) ColorUtils.colorToLAB(color, lab) LabColor(lab) return LabColor(lab) } } } Loading Loading @@ -288,13 +332,10 @@ class LuminanceComputer( const val DEFAULT_ABSOLUTE_LUMINANCE_DELTA = 0.1 @JvmStatic @JvmOverloads fun createDefaultLuminanceComputer( computationType: ComputationType = ComputationType.AVERAGE ): LuminanceComputer { fun createDefaultLuminanceComputer(): LuminanceComputer { return LuminanceComputer( LuminanceColorSpace.LAB, // Keep this as the default color space computationType, ComputationType.AVERAGE, Options( ensureMinContrast = ENABLED_CONTRAST_ADJUSTMENT, absoluteLuminanceDelta = ENABLED_ABSOLUTE_LUMINANCE_DELTA, Loading
iconloaderlib/src/com/android/launcher3/icons/MonochromeIconFactory.java +19 −33 Original line number Diff line number Diff line Loading @@ -17,8 +17,6 @@ package com.android.launcher3.icons; import static android.graphics.Paint.FILTER_BITMAP_FLAG; import static com.android.launcher3.icons.LuminanceComputer.createDefaultLuminanceComputer; import android.annotation.TargetApi; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; Loading Loading @@ -56,17 +54,17 @@ public class MonochromeIconFactory extends Drawable { private final byte[] mPixels; private final int mBitmapSize; private final int mEdgePixelLength; private final Paint mDrawPaint; private final Rect mSrcRect; private double mLuminanceDiff = Double.NaN; public MonochromeIconFactory(int iconBitmapSize) { float extraFactor = AdaptiveIconDrawable.getExtraInsetFraction(); float viewPortScale = 1 / (1 + 2 * extraFactor); mBitmapSize = Math.round(iconBitmapSize * 2 * viewPortScale); mPixels = new byte[mBitmapSize * mBitmapSize]; mEdgePixelLength = mBitmapSize * (mBitmapSize - iconBitmapSize) / 2; mFlatBitmap = Bitmap.createBitmap(mBitmapSize, mBitmapSize, Config.ARGB_8888); mFlatCanvas = new Canvas(mFlatBitmap); Loading Loading @@ -112,41 +110,17 @@ public class MonochromeIconFactory extends Drawable { @WorkerThread public Drawable wrap(AdaptiveIconDrawable icon) { mFlatCanvas.drawColor(Color.BLACK); Drawable bg = icon.getBackground(); Drawable fg = icon.getForeground(); if (bg != null && fg != null) { LuminanceComputer computer = createDefaultLuminanceComputer(); // Calculate foreground luminance on black first to account for any transparent pixels drawDrawable(fg); double fgLuminance = computer.computeLuminance(mFlatBitmap); // Start drawing from scratch and calculate background luminance mFlatCanvas.drawColor(Color.BLACK); drawDrawable(bg); double bgLuminance = computer.computeLuminance(mFlatBitmap); drawDrawable(fg); mLuminanceDiff = fgLuminance - bgLuminance; } else { // We do not have separate layer information. // Try to calculate everything from a single layer drawDrawable(bg); drawDrawable(fg); LuminanceComputer computer = createDefaultLuminanceComputer(ComputationType.SPREAD); mLuminanceDiff = computer.computeLuminance(mFlatBitmap, /* scale= */ true); } drawDrawable(icon.getBackground()); drawDrawable(icon.getForeground()); generateMono(); return new InsetDrawable(this, -AdaptiveIconDrawable.getExtraInsetFraction()); } public double getLuminanceDiff() { return mLuminanceDiff; } @WorkerThread private void generateMono() { mAlphaCanvas.drawBitmap(mFlatBitmap, 0, 0, mCopyPaint); // Scale the end points: ByteBuffer buffer = ByteBuffer.wrap(mPixels); buffer.rewind(); mAlphaBitmap.copyPixelsToBuffer(buffer); Loading @@ -162,10 +136,22 @@ public class MonochromeIconFactory extends Drawable { // rescale pixels to increase contrast float range = max - min; // In order to check if the colors should be flipped, we just take the average color // of top and bottom edge which should correspond to be background color. If the edge // colors have more opacity, we flip the colors; int sum = 0; for (int i = 0; i < mEdgePixelLength; i++) { sum += (mPixels[i] & 0xFF); sum += (mPixels[mPixels.length - 1 - i] & 0xFF); } float edgeAverage = sum / (mEdgePixelLength * 2f); float edgeMapped = (edgeAverage - min) / range; boolean flipColor = edgeMapped > .5f; for (int i = 0; i < mPixels.length; i++) { int p = mPixels[i] & 0xFF; int p2 = Math.round((p - min) * 0xFF / range); mPixels[i] = (byte) (p2); mPixels[i] = flipColor ? (byte) (255 - p2) : (byte) (p2); } // Second phase of processing, aimed on increasing the contrast Loading
iconloaderlib/src/com/android/launcher3/icons/mono/MonoIconThemeController.kt +59 −65 Original line number Diff line number Diff line Loading @@ -24,9 +24,9 @@ import android.graphics.Bitmap.Config.HARDWARE import android.graphics.BlendMode.SRC_IN import android.graphics.BlendModeColorFilter import android.graphics.drawable.AdaptiveIconDrawable import android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable import android.graphics.drawable.InsetDrawable import android.graphics.drawable.LayerDrawable import android.os.Build Loading Loading @@ -55,6 +55,7 @@ class MonoIconThemeController( factory: BaseIconFactory, sourceHint: SourceHint?, ): ThemedBitmap { val currentDelegateFactory = info.delegateFactory if (currentDelegateFactory is ClockAnimationInfo) { val fullDrawable = currentDelegateFactory.baseDrawableState.newDrawable() Loading @@ -72,35 +73,41 @@ class MonoIconThemeController( } } val mono = icon.monochrome val mono = getMonochromeDrawable( icon, info, sourceHint?.isFileDrawable ?: false, shouldForceThemeIcon, ) if (mono != null) { return MonoThemedBitmap( factory.createIconBitmap( InsetDrawable(mono, -getExtraInsetFraction()), 1f /* scale */, MODE_ALPHA, true, /* isFullBleed */ ), factory.createIconBitmap(mono, 1f /* scale */, MODE_ALPHA, true /* isFullBleed */), colorProvider, ) } if (Flags.forceMonochromeAppIcons() && shouldForceThemeIcon) { val monoFactory = MonochromeIconFactory(info.icon.width) val wrappedIcon = monoFactory.wrap(icon) return MonoThemedBitmap( factory.createIconBitmap( wrappedIcon, 1f /* scale */, MODE_ALPHA, true, /* isFullBleed */ ), colorProvider, monoFactory.luminanceDiff, ) return ThemedBitmap.NOT_SUPPORTED } return ThemedBitmap.NOT_SUPPORTED /** * Returns a monochromatic version of the given drawable or null, if it is not supported * * @param base the original icon */ private fun getMonochromeDrawable( base: AdaptiveIconDrawable, info: BitmapInfo, isFileDrawable: Boolean, shouldForceThemeIcon: Boolean, ): Drawable? { val mono = base.monochrome if (mono != null) { return InsetDrawable(mono, -AdaptiveIconDrawable.getExtraInsetFraction()) } if (Flags.forceMonochromeAppIcons() && shouldForceThemeIcon && !isFileDrawable) { return MonochromeIconFactory(info.icon.width).wrap(base) } return null } override fun decode( Loading @@ -110,31 +117,17 @@ class MonoIconThemeController( sourceHint: SourceHint, ): ThemedBitmap { val icon = info.icon val expectedSize = icon.height * icon.width if (bytes.size != icon.height * icon.width) return ThemedBitmap.NOT_SUPPORTED return when (bytes.size) { expectedSize -> { MonoThemedBitmap( ByteBuffer.wrap(bytes).readMonoBitmap(icon.width, icon.height), colorProvider, ) } (expectedSize + MonoThemedBitmap.DOUBLE_BYTE_SIZE) -> { val buffer = ByteBuffer.wrap(bytes) val monoBitmap = buffer.readMonoBitmap(icon.width, icon.height) val luminanceDelta = buffer.asDoubleBuffer().get() MonoThemedBitmap(monoBitmap, colorProvider, luminanceDelta) } else -> ThemedBitmap.NOT_SUPPORTED } } private fun ByteBuffer.readMonoBitmap(width: Int, height: Int): Bitmap { val monoBitmap = Bitmap.createBitmap(width, height, ALPHA_8) monoBitmap.copyPixelsFromBuffer(this) var monoBitmap = Bitmap.createBitmap(icon.width, icon.height, ALPHA_8) monoBitmap.copyPixelsFromBuffer(ByteBuffer.wrap(bytes)) val hwMonoBitmap = monoBitmap.copy(HARDWARE, false /*isMutable*/) return hwMonoBitmap?.also { monoBitmap.recycle() } ?: monoBitmap if (hwMonoBitmap != null) { monoBitmap.recycle() monoBitmap = hwMonoBitmap } return MonoThemedBitmap(monoBitmap, colorProvider) } override fun createThemedAdaptiveIcon( Loading @@ -142,28 +135,29 @@ class MonoIconThemeController( originalIcon: AdaptiveIconDrawable, info: BitmapInfo?, ): AdaptiveIconDrawable { originalIcon.mutate() originalIcon.monochrome?.let { val colors = colorProvider(context) it.setTint(colors[1]) return@createThemedAdaptiveIcon AdaptiveIconDrawable(ColorDrawable(colors[0]), it) } val themedBitmap = info?.themedBitmap as? MonoThemedBitmap ?: return originalIcon val colors = themedBitmap.getUpdatedColors(context) originalIcon.mutate() var monoDrawable = originalIcon.monochrome?.apply { setTint(colors[1]) } if (monoDrawable == null) { info?.themedBitmap?.let { themedBitmap -> if (themedBitmap is MonoThemedBitmap) { // Inject a previously generated monochrome icon // Use BitmapDrawable instead of FastBitmapDrawable so that the colorState is // preserved in constantState // Inset the drawable according to the AdaptiveIconDrawable layers val monoDrawable = monoDrawable = InsetDrawable( BitmapDrawable(themedBitmap.mono).apply { colorFilter = BlendModeColorFilter(colors[1], SRC_IN) }, getExtraInsetFraction() / 2, AdaptiveIconDrawable.getExtraInsetFraction() / 2, ) return AdaptiveIconDrawable(ColorDrawable(colors[0]), monoDrawable) } } } return monoDrawable?.let { AdaptiveIconDrawable(ColorDrawable(colors[0]), it) } ?: originalIcon } }