2 * Copyright (C) 2017 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.launcher3.graphics;
19 import android.app.Notification;
20 import android.content.Context;
21 import android.graphics.Color;
22 import android.graphics.ColorMatrix;
23 import android.graphics.ColorMatrixColorFilter;
24 import android.support.v4.graphics.ColorUtils;
25 import android.util.Log;
27 import com.android.launcher3.R;
28 import com.android.launcher3.util.Themes;
31 * Contains colors based on the dominant color of an icon.
33 public class IconPalette {
35 private static final boolean DEBUG = false;
36 private static final String TAG = "IconPalette";
38 public static final IconPalette FOLDER_ICON_PALETTE = new IconPalette(Color.parseColor("#BDC1C6"));
40 private static final float MIN_PRELOAD_COLOR_SATURATION = 0.2f;
41 private static final float MIN_PRELOAD_COLOR_LIGHTNESS = 0.6f;
43 public final int dominantColor;
44 public final int backgroundColor;
45 public final ColorMatrixColorFilter backgroundColorMatrixFilter;
46 public final ColorMatrixColorFilter saturatedBackgroundColorMatrixFilter;
47 public final int textColor;
48 public final int secondaryColor;
50 private IconPalette(int color) {
51 dominantColor = color;
52 backgroundColor = getMutedColor(dominantColor);
53 ColorMatrix backgroundColorMatrix = new ColorMatrix();
54 Themes.setColorScaleOnMatrix(backgroundColor, backgroundColorMatrix);
55 backgroundColorMatrixFilter = new ColorMatrixColorFilter(backgroundColorMatrix);
56 // Get slightly more saturated background color.
57 Themes.setColorScaleOnMatrix(getMutedColor(dominantColor, 0.54f), backgroundColorMatrix);
58 saturatedBackgroundColorMatrixFilter = new ColorMatrixColorFilter(backgroundColorMatrix);
59 textColor = getTextColorForBackground(backgroundColor);
60 secondaryColor = getLowContrastColor(backgroundColor);
64 * Returns a color suitable for the progress bar color of preload icon.
66 public int getPreloadProgressColor(Context context) {
67 int result = dominantColor;
69 // Make sure that the dominant color has enough saturation to be visible properly.
70 float[] hsv = new float[3];
71 Color.colorToHSV(result, hsv);
72 if (hsv[1] < MIN_PRELOAD_COLOR_SATURATION) {
73 result = Themes.getColorAccent(context);
75 hsv[2] = Math.max(MIN_PRELOAD_COLOR_LIGHTNESS, hsv[2]);
76 result = Color.HSVToColor(hsv);
81 public static IconPalette fromDominantColor(int dominantColor) {
82 return new IconPalette(dominantColor);
86 * Resolves a color such that it has enough contrast to be used as the
87 * color of an icon or text on the given background color.
89 * @return a color of the same hue with enough contrast against the background.
91 * This was copied from com.android.internal.util.NotificationColorUtil.
93 public static int resolveContrastColor(Context context, int color, int background) {
94 final int resolvedColor = resolveColor(context, color);
96 int contrastingColor = ensureTextContrast(resolvedColor, background);
98 if (contrastingColor != resolvedColor) {
100 Log.w(TAG, String.format(
101 "Enhanced contrast of notification for %s " +
102 "%s (over background) by changing #%s to %s",
103 context.getPackageName(),
104 contrastChange(resolvedColor, contrastingColor, background),
105 Integer.toHexString(resolvedColor), Integer.toHexString(contrastingColor)));
108 return contrastingColor;
112 * Resolves {@param color} to an actual color if it is {@link Notification#COLOR_DEFAULT}
114 * This was copied from com.android.internal.util.NotificationColorUtil.
116 private static int resolveColor(Context context, int color) {
117 if (color == Notification.COLOR_DEFAULT) {
118 return context.getColor(R.color.notification_icon_default_color);
123 /** For debugging. This was copied from com.android.internal.util.NotificationColorUtil. */
124 private static String contrastChange(int colorOld, int colorNew, int bg) {
125 return String.format("from %.2f:1 to %.2f:1",
126 ColorUtils.calculateContrast(colorOld, bg),
127 ColorUtils.calculateContrast(colorNew, bg));
131 * Finds a text color with sufficient contrast over bg that has the same hue as the original
134 * This was copied from com.android.internal.util.NotificationColorUtil.
136 private static int ensureTextContrast(int color, int bg) {
137 return findContrastColor(color, bg, true, 4.5);
140 * Finds a suitable color such that there's enough contrast.
142 * @param color the color to start searching from.
143 * @param other the color to ensure contrast against. Assumed to be lighter than {@param color}
144 * @param findFg if true, we assume {@param color} is a foreground, otherwise a background.
145 * @param minRatio the minimum contrast ratio required.
146 * @return a color with the same hue as {@param color}, potentially darkened to meet the
149 * This was copied from com.android.internal.util.NotificationColorUtil.
151 private static int findContrastColor(int color, int other, boolean findFg, double minRatio) {
152 int fg = findFg ? color : other;
153 int bg = findFg ? other : color;
154 if (ColorUtils.calculateContrast(fg, bg) >= minRatio) {
158 double[] lab = new double[3];
159 ColorUtils.colorToLAB(findFg ? fg : bg, lab);
161 double low = 0, high = lab[0];
162 final double a = lab[1], b = lab[2];
163 for (int i = 0; i < 15 && high - low > 0.00001; i++) {
164 final double l = (low + high) / 2;
166 fg = ColorUtils.LABToColor(l, a, b);
168 bg = ColorUtils.LABToColor(l, a, b);
170 if (ColorUtils.calculateContrast(fg, bg) > minRatio) {
176 return ColorUtils.LABToColor(low, a, b);
179 private static int getMutedColor(int color) {
180 return getMutedColor(color, 0.87f);
183 private static int getMutedColor(int color, float whiteScrimAlpha) {
184 int whiteScrim = ColorUtils.setAlphaComponent(Color.WHITE, (int) (255 * whiteScrimAlpha));
185 return ColorUtils.compositeColors(whiteScrim, color);
188 private static int getTextColorForBackground(int backgroundColor) {
189 return getLighterOrDarkerVersionOfColor(backgroundColor, 4.5f);
192 private static int getLowContrastColor(int color) {
193 return getLighterOrDarkerVersionOfColor(color, 1.5f);
196 private static int getLighterOrDarkerVersionOfColor(int color, float contrastRatio) {
197 int whiteMinAlpha = ColorUtils.calculateMinimumAlpha(Color.WHITE, color, contrastRatio);
198 int blackMinAlpha = ColorUtils.calculateMinimumAlpha(Color.BLACK, color, contrastRatio);
199 int translucentWhiteOrBlack;
200 if (whiteMinAlpha >= 0) {
201 translucentWhiteOrBlack = ColorUtils.setAlphaComponent(Color.WHITE, whiteMinAlpha);
202 } else if (blackMinAlpha >= 0) {
203 translucentWhiteOrBlack = ColorUtils.setAlphaComponent(Color.BLACK, blackMinAlpha);
205 translucentWhiteOrBlack = Color.WHITE;
207 return ColorUtils.compositeColors(translucentWhiteOrBlack, color);