1/*
2 * Copyright (C) 2015 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 */
16package com.android.car.apps.common;
17
18import android.content.Context;
19import android.graphics.Color;
20import android.util.Log;
21
22/**
23 * @hide
24 */
25public class ColorChecker {
26    private static final String TAG = "ColorChecker";
27    private static final double MIN_CONTRAST_RATIO = 4.5;
28    /**
29     * Non-critical information doesn't have to meet as stringent contrast requirements.
30     */
31    private static final double MIN_NON_CRITICAL_CONTRAST_RATIO = 1.5;
32
33    /**
34     * Calls {@link #getTintColor(int, int...)} with:
35     *     {@link R.color#car_tint_light} and
36     *     {@link R.color#car_tint_dark}
37     */
38    public static int getTintColor(Context context, int backgroundColor) {
39        int lightTintColor = context.getResources().getColor(R.color.car_tint_light);
40        int darkTintColor = context.getResources().getColor(R.color.car_tint_dark);
41
42        return getTintColor(backgroundColor, lightTintColor, darkTintColor);
43    }
44
45    /**
46     * Calls {@link #getNonCriticalTintColor(int, int...)} with:
47     *     {@link R.color#car_tint_light} and
48     *     {@link R.color#car_tint_dark}
49     */
50    public static int getNonCriticalTintColor(Context context, int backgroundColor) {
51        int lightTintColor = context.getResources().getColor(R.color.car_tint_light);
52        int darkTintColor = context.getResources().getColor(R.color.car_tint_dark);
53
54        return getNonCriticalTintColor(backgroundColor, lightTintColor, darkTintColor);
55    }
56
57    /**
58     * Calls {@link #getTintColor(int, int...)} with {@link #MIN_CONTRAST_RATIO}.
59     */
60    public static int getTintColor(int backgroundColor, int... tintColors) {
61        return getTintColor(MIN_CONTRAST_RATIO, backgroundColor, tintColors);
62    }
63
64    /**
65     * Calls {@link #getTintColor(int, int...)} with {@link #MIN_NON_CRITICAL_CONTRAST_RATIO}.
66     */
67    public static int getNonCriticalTintColor(int backgroundColor, int... tintColors) {
68        return getTintColor(MIN_NON_CRITICAL_CONTRAST_RATIO, backgroundColor, tintColors);
69    }
70
71    /**
72     *
73     * Determines what color to tint icons given the background color that they sit on.
74     *
75     * @param minAllowedContrastRatio The minimum contrast ratio
76     * @param bgColor The background color that the icons sit on.
77     * @param tintColors A list of potential colors to tint the icons with.
78     * @return The color that the icons should be tinted. Will be the first tinted color that
79     *         meets the requirements. If none of the tint colors meet the minimum requirements,
80     *         either black or white will be returned, whichever has a higher contrast.
81     */
82    public static int getTintColor(double minAllowedContrastRatio, int bgColor, int... tintColors) {
83        for (int tc : tintColors) {
84            double contrastRatio = getContrastRatio(bgColor, tc);
85            if (contrastRatio >= minAllowedContrastRatio) {
86                return tc;
87            }
88        }
89        double blackContrastRatio = getContrastRatio(bgColor, Color.BLACK);
90        double whiteContrastRatio = getContrastRatio(bgColor, Color.WHITE);
91        if (whiteContrastRatio >= blackContrastRatio) {
92            Log.w(TAG, "Tint color does not meet contrast requirements. Using white.");
93            return Color.WHITE;
94        } else {
95            Log.w(TAG, "Tint color does not meet contrast requirements. Using black.");
96            return Color.BLACK;
97        }
98    }
99
100    public static double getContrastRatio(int color1, int color2) {
101        return getContrastRatio(getLuminance(color1), getLuminance(color2));
102    }
103
104    public static double getContrastRatio(double luminance1, double luminance2) {
105        return (Math.max(luminance1, luminance2) + 0.05) /
106                (Math.min(luminance1, luminance2) + 0.05);
107    }
108
109    /**
110     * Calculates the luminance of a color as specified by:
111     *     http://www.w3.org/TR/WCAG20-TECHS/G17.html
112     *
113     * @param color The color to calculate the luminance of.
114     * @return The luminance.
115     */
116    public static double getLuminance(int color) {
117        // Values are in sRGB
118        double r = convert8BitToLuminanceComponent(Color.red(color));
119        double g = convert8BitToLuminanceComponent(Color.green(color));
120        double b = convert8BitToLuminanceComponent(Color.blue(color));
121        return r * 0.2126 + g * 0.7152 + b * 0.0722;
122    }
123
124    /**
125     * Converts am 8 bit color component (0-255) to the luminance component as specified by:
126     *     http://www.w3.org/TR/WCAG20-TECHS/G17.html
127     */
128    private static double convert8BitToLuminanceComponent(double component) {
129        component /= 255.0;
130        if (component <= 0.03928) {
131            return component / 12.92;
132        } else {
133            return Math.pow(((component + 0.055) / 1.055), 2.4);
134        }
135    }
136}
137