blob: f582cc2d806e0571c3318f2182dfd47403dfe55e [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.car.notification.template;
import android.annotation.ColorInt;
import android.graphics.Color;
import com.android.internal.graphics.ColorUtils;
/**
* Helper class to determine if a color is a light color and find a foreground color that has enough
* contrast than the background color.
*/
public class NotificationColorUtil {
private static final int MAX_FIND_COLOR_STEPS = 15;
private static final double MIN_COLOR_CONTRAST = 0.00001;
private static final double MIN_CONTRAST_RATIO = 4.5;
private static final double MIN_LIGHTNESS = 0;
private static final float MAX_LIGHTNESS = 1;
private static final float LIGHT_COLOR_LUMINANCE_THRESHOLD = 0.5f;
private NotificationColorUtil() {
}
/**
* Resolves a Notification's color such that it has enough contrast to be used as the
* color for the Notification's action and header text.
*
* @param backgroundColor the background color to ensure the contrast against.
* @return a color of the same hue as {@code notificationColor} with enough contrast against
* the backgrounds.
*/
public static int resolveContrastColor(
@ColorInt int notificationColor, @ColorInt int backgroundColor) {
return getContrastedForegroundColor(notificationColor, backgroundColor, MIN_CONTRAST_RATIO);
}
/**
* Returns true if a color is considered a light color.
*/
public static boolean isColorLight(int backgroundColor) {
return Color.luminance(backgroundColor) > LIGHT_COLOR_LUMINANCE_THRESHOLD;
}
/**
* Finds a suitable color such that there's enough contrast.
*
* @param foregroundColor the color to start searching from.
* @param backgroundColor the color to ensure contrast against. Assumed to be lighter than
* {@param foregroundColor}
* @param minContrastRatio the minimum contrast ratio required.
* @return a color with the same hue as {@param foregroundColor}, potentially darkened to
* meet the contrast ratio.
*/
private static int findContrastColorAgainstLightBackground(
@ColorInt int foregroundColor, @ColorInt int backgroundColor, double minContrastRatio) {
if (ColorUtils.calculateContrast(foregroundColor, backgroundColor) >= minContrastRatio) {
return foregroundColor;
}
double[] lab = new double[3];
ColorUtils.colorToLAB(foregroundColor, lab);
double low = MIN_LIGHTNESS;
double high = lab[0];
double a = lab[1];
double b = lab[2];
for (int i = 0; i < MAX_FIND_COLOR_STEPS && high - low > MIN_COLOR_CONTRAST; i++) {
double l = (low + high) / 2;
foregroundColor = ColorUtils.LABToColor(l, a, b);
if (ColorUtils.calculateContrast(foregroundColor, backgroundColor) > minContrastRatio) {
low = l;
} else {
high = l;
}
}
return ColorUtils.LABToColor(low, a, b);
}
/**
* Finds a suitable color such that there's enough contrast.
*
* @param foregroundColor the foregroundColor to start searching from.
* @param backgroundColor the foregroundColor to ensure contrast against. Assumed to be
* darker than {@param foregroundColor}
* @param minContrastRatio the minimum contrast ratio required.
* @return a foregroundColor with the same hue as {@param foregroundColor}, potentially
* lightened to meet the contrast ratio.
*/
private static int findContrastColorAgainstDarkBackground(
@ColorInt int foregroundColor, @ColorInt int backgroundColor, double minContrastRatio) {
if (ColorUtils.calculateContrast(foregroundColor, backgroundColor) >= minContrastRatio) {
return foregroundColor;
}
float[] hsl = new float[3];
ColorUtils.colorToHSL(foregroundColor, hsl);
float low = hsl[2];
float high = MAX_LIGHTNESS;
for (int i = 0; i < MAX_FIND_COLOR_STEPS && high - low > MIN_COLOR_CONTRAST; i++) {
float l = (low + high) / 2;
hsl[2] = l;
foregroundColor = ColorUtils.HSLToColor(hsl);
if (ColorUtils.calculateContrast(foregroundColor, backgroundColor)
> minContrastRatio) {
high = l;
} else {
low = l;
}
}
return foregroundColor;
}
/**
* Finds a foregroundColor with sufficient contrast over backgroundColor that has the same or
* darker hue as the original foregroundColor.
*
* @param foregroundColor the foregroundColor to start searching from
* @param backgroundColor the foregroundColor to ensure contrast against
* @param minContrastRatio the minimum contrast ratio required
*/
private static int getContrastedForegroundColor(
@ColorInt int foregroundColor, @ColorInt int backgroundColor, double minContrastRatio) {
boolean isBackgroundDarker =
Color.luminance(foregroundColor) > Color.luminance(backgroundColor);
return isBackgroundDarker
? findContrastColorAgainstDarkBackground(
foregroundColor, backgroundColor, minContrastRatio)
: findContrastColorAgainstLightBackground(
foregroundColor, backgroundColor, minContrastRatio);
}
}