1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.launcher3.graphics; 18 19 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound; 20 21 import android.app.Notification; 22 import android.content.Context; 23 import android.graphics.Color; 24 import android.util.Log; 25 26 import com.android.launcher3.R; 27 import com.android.launcher3.util.Themes; 28 29 import androidx.core.graphics.ColorUtils; 30 31 /** 32 * Contains colors based on the dominant color of an icon. 33 */ 34 public class IconPalette { 35 36 private static final boolean DEBUG = false; 37 private static final String TAG = "IconPalette"; 38 39 private static final float MIN_PRELOAD_COLOR_SATURATION = 0.2f; 40 private static final float MIN_PRELOAD_COLOR_LIGHTNESS = 0.6f; 41 42 /** 43 * Returns a color suitable for the progress bar color of preload icon. 44 */ getPreloadProgressColor(Context context, int dominantColor)45 public static int getPreloadProgressColor(Context context, int dominantColor) { 46 int result = dominantColor; 47 48 // Make sure that the dominant color has enough saturation to be visible properly. 49 float[] hsv = new float[3]; 50 Color.colorToHSV(result, hsv); 51 if (hsv[1] < MIN_PRELOAD_COLOR_SATURATION) { 52 result = Themes.getColorAccent(context); 53 } else { 54 hsv[2] = Math.max(MIN_PRELOAD_COLOR_LIGHTNESS, hsv[2]); 55 result = Color.HSVToColor(hsv); 56 } 57 return result; 58 } 59 60 /** 61 * Resolves a color such that it has enough contrast to be used as the 62 * color of an icon or text on the given background color. 63 * 64 * @return a color of the same hue with enough contrast against the background. 65 * 66 * This was copied from com.android.internal.util.NotificationColorUtil. 67 */ resolveContrastColor(Context context, int color, int background)68 public static int resolveContrastColor(Context context, int color, int background) { 69 final int resolvedColor = resolveColor(context, color); 70 71 int contrastingColor = ensureTextContrast(resolvedColor, background); 72 73 if (contrastingColor != resolvedColor) { 74 if (DEBUG){ 75 Log.w(TAG, String.format( 76 "Enhanced contrast of notification for %s " + 77 "%s (over background) by changing #%s to %s", 78 context.getPackageName(), 79 contrastChange(resolvedColor, contrastingColor, background), 80 Integer.toHexString(resolvedColor), Integer.toHexString(contrastingColor))); 81 } 82 } 83 return contrastingColor; 84 } 85 86 /** 87 * Resolves {@param color} to an actual color if it is {@link Notification#COLOR_DEFAULT} 88 * 89 * This was copied from com.android.internal.util.NotificationColorUtil. 90 */ resolveColor(Context context, int color)91 private static int resolveColor(Context context, int color) { 92 if (color == Notification.COLOR_DEFAULT) { 93 return context.getColor(R.color.notification_icon_default_color); 94 } 95 return color; 96 } 97 98 /** For debugging. This was copied from com.android.internal.util.NotificationColorUtil. */ contrastChange(int colorOld, int colorNew, int bg)99 private static String contrastChange(int colorOld, int colorNew, int bg) { 100 return String.format("from %.2f:1 to %.2f:1", 101 ColorUtils.calculateContrast(colorOld, bg), 102 ColorUtils.calculateContrast(colorNew, bg)); 103 } 104 105 /** 106 * Finds a text color with sufficient contrast over bg that has the same hue as the original 107 * color. 108 * 109 * This was copied from com.android.internal.util.NotificationColorUtil. 110 */ ensureTextContrast(int color, int bg)111 private static int ensureTextContrast(int color, int bg) { 112 return findContrastColor(color, bg, 4.5); 113 } 114 /** 115 * Finds a suitable color such that there's enough contrast. 116 * 117 * @param fg the color to start searching from. 118 * @param bg the color to ensure contrast against. 119 * @param minRatio the minimum contrast ratio required. 120 * @return a color with the same hue as {@param color}, potentially darkened to meet the 121 * contrast ratio. 122 * 123 * This was copied from com.android.internal.util.NotificationColorUtil. 124 */ findContrastColor(int fg, int bg, double minRatio)125 private static int findContrastColor(int fg, int bg, double minRatio) { 126 if (ColorUtils.calculateContrast(fg, bg) >= minRatio) { 127 return fg; 128 } 129 130 double[] lab = new double[3]; 131 ColorUtils.colorToLAB(bg, lab); 132 double bgL = lab[0]; 133 ColorUtils.colorToLAB(fg, lab); 134 double fgL = lab[0]; 135 boolean isBgDark = bgL < 50; 136 137 double low = isBgDark ? fgL : 0, high = isBgDark ? 100 : fgL; 138 final double a = lab[1], b = lab[2]; 139 for (int i = 0; i < 15 && high - low > 0.00001; i++) { 140 final double l = (low + high) / 2; 141 fg = ColorUtils.LABToColor(l, a, b); 142 if (ColorUtils.calculateContrast(fg, bg) > minRatio) { 143 if (isBgDark) high = l; else low = l; 144 } else { 145 if (isBgDark) low = l; else high = l; 146 } 147 } 148 return ColorUtils.LABToColor(low, a, b); 149 } 150 getMutedColor(int color, float whiteScrimAlpha)151 public static int getMutedColor(int color, float whiteScrimAlpha) { 152 int whiteScrim = setColorAlphaBound(Color.WHITE, (int) (255 * whiteScrimAlpha)); 153 return ColorUtils.compositeColors(whiteScrim, color); 154 } 155 } 156