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