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 android.app.Notification; 20 import android.content.Context; 21 import android.graphics.Color; 22 import android.util.Log; 23 24 import androidx.core.graphics.ColorUtils; 25 26 import com.android.launcher3.R; 27 import com.android.launcher3.util.Themes; 28 29 /** 30 * Contains colors based on the dominant color of an icon. 31 */ 32 public class IconPalette { 33 34 private static final boolean DEBUG = false; 35 private static final String TAG = "IconPalette"; 36 37 private static final float MIN_PRELOAD_COLOR_SATURATION = 0.2f; 38 private static final float MIN_PRELOAD_COLOR_LIGHTNESS = 0.6f; 39 40 /** 41 * Returns a color suitable for the progress bar color of preload icon. 42 */ getPreloadProgressColor(Context context, int dominantColor)43 public static int getPreloadProgressColor(Context context, int dominantColor) { 44 int result = dominantColor; 45 46 // Make sure that the dominant color has enough saturation to be visible properly. 47 float[] hsv = new float[3]; 48 Color.colorToHSV(result, hsv); 49 if (hsv[1] < MIN_PRELOAD_COLOR_SATURATION) { 50 result = Themes.getColorAccent(context); 51 } else { 52 hsv[2] = Math.max(MIN_PRELOAD_COLOR_LIGHTNESS, hsv[2]); 53 result = Color.HSVToColor(hsv); 54 } 55 return result; 56 } 57 58 /** 59 * Resolves a color such that it has enough contrast to be used as the 60 * color of an icon or text on the given background color. 61 * 62 * @return a color of the same hue with enough contrast against the background. 63 * 64 * This was copied from com.android.internal.util.NotificationColorUtil. 65 */ resolveContrastColor(Context context, int color, int background)66 public static int resolveContrastColor(Context context, int color, int background) { 67 final int resolvedColor = resolveColor(context, color); 68 69 int contrastingColor = ensureTextContrast(resolvedColor, background); 70 71 if (contrastingColor != resolvedColor) { 72 if (DEBUG){ 73 Log.w(TAG, String.format( 74 "Enhanced contrast of notification for %s " + 75 "%s (over background) by changing #%s to %s", 76 context.getPackageName(), 77 contrastChange(resolvedColor, contrastingColor, background), 78 Integer.toHexString(resolvedColor), Integer.toHexString(contrastingColor))); 79 } 80 } 81 return contrastingColor; 82 } 83 84 /** 85 * Resolves {@param color} to an actual color if it is {@link Notification#COLOR_DEFAULT} 86 * 87 * This was copied from com.android.internal.util.NotificationColorUtil. 88 */ resolveColor(Context context, int color)89 private static int resolveColor(Context context, int color) { 90 if (color == Notification.COLOR_DEFAULT) { 91 return context.getColor(R.color.notification_icon_default_color); 92 } 93 return color; 94 } 95 96 /** For debugging. This was copied from com.android.internal.util.NotificationColorUtil. */ contrastChange(int colorOld, int colorNew, int bg)97 private static String contrastChange(int colorOld, int colorNew, int bg) { 98 return String.format("from %.2f:1 to %.2f:1", 99 ColorUtils.calculateContrast(colorOld, bg), 100 ColorUtils.calculateContrast(colorNew, bg)); 101 } 102 103 /** 104 * Finds a text color with sufficient contrast over bg that has the same hue as the original 105 * color. 106 * 107 * This was copied from com.android.internal.util.NotificationColorUtil. 108 */ ensureTextContrast(int color, int bg)109 private static int ensureTextContrast(int color, int bg) { 110 return findContrastColor(color, bg, 4.5); 111 } 112 /** 113 * Finds a suitable color such that there's enough contrast. 114 * 115 * @param fg the color to start searching from. 116 * @param bg the color to ensure contrast against. 117 * @param minRatio the minimum contrast ratio required. 118 * @return a color with the same hue as {@param color}, potentially darkened to meet the 119 * contrast ratio. 120 * 121 * This was copied from com.android.internal.util.NotificationColorUtil. 122 */ findContrastColor(int fg, int bg, double minRatio)123 private static int findContrastColor(int fg, int bg, double minRatio) { 124 if (ColorUtils.calculateContrast(fg, bg) >= minRatio) { 125 return fg; 126 } 127 128 double[] lab = new double[3]; 129 ColorUtils.colorToLAB(bg, lab); 130 double bgL = lab[0]; 131 ColorUtils.colorToLAB(fg, lab); 132 double fgL = lab[0]; 133 boolean isBgDark = bgL < 50; 134 135 double low = isBgDark ? fgL : 0, high = isBgDark ? 100 : fgL; 136 final double a = lab[1], b = lab[2]; 137 for (int i = 0; i < 15 && high - low > 0.00001; i++) { 138 final double l = (low + high) / 2; 139 fg = ColorUtils.LABToColor(l, a, b); 140 if (ColorUtils.calculateContrast(fg, bg) > minRatio) { 141 if (isBgDark) high = l; else low = l; 142 } else { 143 if (isBgDark) low = l; else high = l; 144 } 145 } 146 return ColorUtils.LABToColor(low, a, b); 147 } 148 } 149