1059178a8c7cc80848e5594a9287be91bd924831aChris Banes/*
2711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes * Copyright 2015 The Android Open Source Project
3059178a8c7cc80848e5594a9287be91bd924831aChris Banes *
4059178a8c7cc80848e5594a9287be91bd924831aChris Banes * Licensed under the Apache License, Version 2.0 (the "License");
5059178a8c7cc80848e5594a9287be91bd924831aChris Banes * you may not use this file except in compliance with the License.
6059178a8c7cc80848e5594a9287be91bd924831aChris Banes * You may obtain a copy of the License at
7059178a8c7cc80848e5594a9287be91bd924831aChris Banes *
8059178a8c7cc80848e5594a9287be91bd924831aChris Banes *       http://www.apache.org/licenses/LICENSE-2.0
9059178a8c7cc80848e5594a9287be91bd924831aChris Banes *
10059178a8c7cc80848e5594a9287be91bd924831aChris Banes * Unless required by applicable law or agreed to in writing, software
11059178a8c7cc80848e5594a9287be91bd924831aChris Banes * distributed under the License is distributed on an "AS IS" BASIS,
12059178a8c7cc80848e5594a9287be91bd924831aChris Banes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13059178a8c7cc80848e5594a9287be91bd924831aChris Banes * See the License for the specific language governing permissions and
14059178a8c7cc80848e5594a9287be91bd924831aChris Banes * limitations under the License.
15059178a8c7cc80848e5594a9287be91bd924831aChris Banes */
16059178a8c7cc80848e5594a9287be91bd924831aChris Banes
17711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banespackage android.support.v4.graphics;
18059178a8c7cc80848e5594a9287be91bd924831aChris Banes
19059178a8c7cc80848e5594a9287be91bd924831aChris Banesimport android.graphics.Color;
20059178a8c7cc80848e5594a9287be91bd924831aChris Banes
217aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes/**
22711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes * A set of color-related utility methods, building upon those available in {@code Color}.
237aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes */
24711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banespublic class ColorUtils {
25059178a8c7cc80848e5594a9287be91bd924831aChris Banes
26f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes    private static final int MIN_ALPHA_SEARCH_MAX_ITERATIONS = 10;
27f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes    private static final int MIN_ALPHA_SEARCH_PRECISION = 10;
28f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes
29059178a8c7cc80848e5594a9287be91bd924831aChris Banes    private ColorUtils() {}
30059178a8c7cc80848e5594a9287be91bd924831aChris Banes
31059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
32f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes     * Composite two potentially translucent colors over each other and returns the result.
33f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes     */
34711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes    public static int compositeColors(int foreground, int background) {
35eb0d0c030a15e93f456cc1403fffb909c0ae4e66Chris Banes        int bgAlpha = Color.alpha(background);
36eb0d0c030a15e93f456cc1403fffb909c0ae4e66Chris Banes        int fgAlpha = Color.alpha(foreground);
37eb0d0c030a15e93f456cc1403fffb909c0ae4e66Chris Banes        int a = compositeAlpha(fgAlpha, bgAlpha);
38f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes
39eb0d0c030a15e93f456cc1403fffb909c0ae4e66Chris Banes        int r = compositeComponent(Color.red(foreground), fgAlpha,
40eb0d0c030a15e93f456cc1403fffb909c0ae4e66Chris Banes                Color.red(background), bgAlpha, a);
41eb0d0c030a15e93f456cc1403fffb909c0ae4e66Chris Banes        int g = compositeComponent(Color.green(foreground), fgAlpha,
42eb0d0c030a15e93f456cc1403fffb909c0ae4e66Chris Banes                Color.green(background), bgAlpha, a);
43eb0d0c030a15e93f456cc1403fffb909c0ae4e66Chris Banes        int b = compositeComponent(Color.blue(foreground), fgAlpha,
44eb0d0c030a15e93f456cc1403fffb909c0ae4e66Chris Banes                Color.blue(background), bgAlpha, a);
45f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes
46eb0d0c030a15e93f456cc1403fffb909c0ae4e66Chris Banes        return Color.argb(a, r, g, b);
47eb0d0c030a15e93f456cc1403fffb909c0ae4e66Chris Banes    }
48eb0d0c030a15e93f456cc1403fffb909c0ae4e66Chris Banes
49eb0d0c030a15e93f456cc1403fffb909c0ae4e66Chris Banes    private static int compositeAlpha(int foregroundAlpha, int backgroundAlpha) {
50eb0d0c030a15e93f456cc1403fffb909c0ae4e66Chris Banes        return 0xFF - (((0xFF - backgroundAlpha) * (0xFF - foregroundAlpha)) / 0xFF);
51eb0d0c030a15e93f456cc1403fffb909c0ae4e66Chris Banes    }
52eb0d0c030a15e93f456cc1403fffb909c0ae4e66Chris Banes
53eb0d0c030a15e93f456cc1403fffb909c0ae4e66Chris Banes    private static int compositeComponent(int fgC, int fgA, int bgC, int bgA, int a) {
54eb0d0c030a15e93f456cc1403fffb909c0ae4e66Chris Banes        if (a == 0) return 0;
55eb0d0c030a15e93f456cc1403fffb909c0ae4e66Chris Banes        return ((0xFF * fgC * fgA) + (bgC * bgA * (0xFF - fgA))) / (a * 0xFF);
56f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes    }
57f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes
58f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes    /**
59f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes     * Returns the luminance of a color.
60f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes     *
61f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes     * Formula defined here: http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
62f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes     */
63711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes    public static double calculateLuminance(int color) {
64f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        double red = Color.red(color) / 255d;
65f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        red = red < 0.03928 ? red / 12.92 : Math.pow((red + 0.055) / 1.055, 2.4);
66f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes
67f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        double green = Color.green(color) / 255d;
68f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        green = green < 0.03928 ? green / 12.92 : Math.pow((green + 0.055) / 1.055, 2.4);
69f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes
70f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        double blue = Color.blue(color) / 255d;
71f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        blue = blue < 0.03928 ? blue / 12.92 : Math.pow((blue + 0.055) / 1.055, 2.4);
72f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes
73f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        return (0.2126 * red) + (0.7152 * green) + (0.0722 * blue);
74f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes    }
75f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes
76f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes    /**
77711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes     * Returns the contrast ratio between {@code foreground} and {@code background}.
78711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes     * {@code background} must be opaque.
79711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes     * <p>
80711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes     * Formula defined
81711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes     * <a href="http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef">here</a>.
82f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes     */
83711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes    public static double calculateContrast(int foreground, int background) {
84f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        if (Color.alpha(background) != 255) {
85f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes            throw new IllegalArgumentException("background can not be translucent");
86f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        }
87f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        if (Color.alpha(foreground) < 255) {
88f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes            // If the foreground is translucent, composite the foreground over the background
89f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes            foreground = compositeColors(foreground, background);
90f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        }
91f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes
92f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        final double luminance1 = calculateLuminance(foreground) + 0.05;
93f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        final double luminance2 = calculateLuminance(background) + 0.05;
94f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes
95f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        // Now return the lighter luminance divided by the darker luminance
96f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        return Math.max(luminance1, luminance2) / Math.min(luminance1, luminance2);
97f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes    }
98f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes
99f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes    /**
100711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes     * Calculates the minimum alpha value which can be applied to {@code foreground} so that would
101711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes     * have a contrast value of at least {@code minContrastRatio} when compared to
102711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes     * {@code background}.
103f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes     *
104711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes     * @param foreground       the foreground color.
105711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes     * @param background       the background color. Should be opaque.
106711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes     * @param minContrastRatio the minimum contrast ratio.
107711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes     * @return the alpha value in the range 0-255, or -1 if no value could be calculated.
108059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
109711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes    public static int calculateMinimumAlpha(int foreground, int background,
110711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes            float minContrastRatio) {
111f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        if (Color.alpha(background) != 255) {
112f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes            throw new IllegalArgumentException("background can not be translucent");
113f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        }
114f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes
115f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        // First lets check that a fully opaque foreground has sufficient contrast
1167aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes        int testForeground = setAlphaComponent(foreground, 255);
117f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        double testRatio = calculateContrast(testForeground, background);
118f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        if (testRatio < minContrastRatio) {
119f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes            // Fully opaque foreground does not have sufficient contrast, return error
120f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes            return -1;
121f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        }
122f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes
123f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        // Binary search to find a value with the minimum value which provides sufficient contrast
124f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        int numIterations = 0;
125f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        int minAlpha = 0;
126f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        int maxAlpha = 255;
127f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes
128f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        while (numIterations <= MIN_ALPHA_SEARCH_MAX_ITERATIONS &&
129f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes                (maxAlpha - minAlpha) > MIN_ALPHA_SEARCH_PRECISION) {
130f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes            final int testAlpha = (minAlpha + maxAlpha) / 2;
131f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes
1327aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes            testForeground = setAlphaComponent(foreground, testAlpha);
133f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes            testRatio = calculateContrast(testForeground, background);
134f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes
135f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes            if (testRatio < minContrastRatio) {
136f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes                minAlpha = testAlpha;
137f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes            } else {
138f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes                maxAlpha = testAlpha;
139f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes            }
140f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes
141f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes            numIterations++;
142f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        }
143f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes
144f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        // Conservatively return the max of the range of possible alphas, which is known to pass.
145f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        return maxAlpha;
146059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
147059178a8c7cc80848e5594a9287be91bd924831aChris Banes
1487aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes    /**
149711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes     * Convert RGB components to HSL (hue-saturation-lightness).
1507aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes     * <ul>
151711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes     * <li>hsl[0] is Hue [0 .. 360)</li>
152711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes     * <li>hsl[1] is Saturation [0...1]</li>
153711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes     * <li>hsl[2] is Lightness [0...1]</li>
1547aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes     * </ul>
1557aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes     *
1567aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes     * @param r   red component value [0..255]
1577aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes     * @param g   green component value [0..255]
1587aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes     * @param b   blue component value [0..255]
1597aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes     * @param hsl 3 element array which holds the resulting HSL components.
1607aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes     */
161711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes    public static void RGBToHSL(int r, int g, int b, float[] hsl) {
162059178a8c7cc80848e5594a9287be91bd924831aChris Banes        final float rf = r / 255f;
163059178a8c7cc80848e5594a9287be91bd924831aChris Banes        final float gf = g / 255f;
164059178a8c7cc80848e5594a9287be91bd924831aChris Banes        final float bf = b / 255f;
165059178a8c7cc80848e5594a9287be91bd924831aChris Banes
166059178a8c7cc80848e5594a9287be91bd924831aChris Banes        final float max = Math.max(rf, Math.max(gf, bf));
167059178a8c7cc80848e5594a9287be91bd924831aChris Banes        final float min = Math.min(rf, Math.min(gf, bf));
168059178a8c7cc80848e5594a9287be91bd924831aChris Banes        final float deltaMaxMin = max - min;
169059178a8c7cc80848e5594a9287be91bd924831aChris Banes
170059178a8c7cc80848e5594a9287be91bd924831aChris Banes        float h, s;
171059178a8c7cc80848e5594a9287be91bd924831aChris Banes        float l = (max + min) / 2f;
172059178a8c7cc80848e5594a9287be91bd924831aChris Banes
173059178a8c7cc80848e5594a9287be91bd924831aChris Banes        if (max == min) {
174059178a8c7cc80848e5594a9287be91bd924831aChris Banes            // Monochromatic
175059178a8c7cc80848e5594a9287be91bd924831aChris Banes            h = s = 0f;
176059178a8c7cc80848e5594a9287be91bd924831aChris Banes        } else {
177059178a8c7cc80848e5594a9287be91bd924831aChris Banes            if (max == rf) {
178059178a8c7cc80848e5594a9287be91bd924831aChris Banes                h = ((gf - bf) / deltaMaxMin) % 6f;
179059178a8c7cc80848e5594a9287be91bd924831aChris Banes            } else if (max == gf) {
180059178a8c7cc80848e5594a9287be91bd924831aChris Banes                h = ((bf - rf) / deltaMaxMin) + 2f;
181059178a8c7cc80848e5594a9287be91bd924831aChris Banes            } else {
182059178a8c7cc80848e5594a9287be91bd924831aChris Banes                h = ((rf - gf) / deltaMaxMin) + 4f;
183059178a8c7cc80848e5594a9287be91bd924831aChris Banes            }
184059178a8c7cc80848e5594a9287be91bd924831aChris Banes
185711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes            s = deltaMaxMin / (1f - Math.abs(2f * l - 1f));
186059178a8c7cc80848e5594a9287be91bd924831aChris Banes        }
187059178a8c7cc80848e5594a9287be91bd924831aChris Banes
188c929274ecb2495a428f214393c787360c3433438Chris Banes        h = (h * 60f) % 360f;
189c929274ecb2495a428f214393c787360c3433438Chris Banes        if (h < 0) {
190c929274ecb2495a428f214393c787360c3433438Chris Banes            h += 360f;
191c929274ecb2495a428f214393c787360c3433438Chris Banes        }
192c929274ecb2495a428f214393c787360c3433438Chris Banes
193c929274ecb2495a428f214393c787360c3433438Chris Banes        hsl[0] = constrain(h, 0f, 360f);
194c929274ecb2495a428f214393c787360c3433438Chris Banes        hsl[1] = constrain(s, 0f, 1f);
195c929274ecb2495a428f214393c787360c3433438Chris Banes        hsl[2] = constrain(l, 0f, 1f);
196059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
197059178a8c7cc80848e5594a9287be91bd924831aChris Banes
1987aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes    /**
199711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes     * Convert the ARGB color to its HSL (hue-saturation-lightness) components.
2007aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes     * <ul>
201711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes     * <li>hsl[0] is Hue [0 .. 360)</li>
202711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes     * <li>hsl[1] is Saturation [0...1]</li>
203711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes     * <li>hsl[2] is Lightness [0...1]</li>
204711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes     * </ul>
205711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes     *
206711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes     * @param color the ARGB color to convert. The alpha component is ignored.
207711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes     * @param hsl 3 element array which holds the resulting HSL components.
208711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes     */
209711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes    public static void colorToHSL(int color, float[] hsl) {
210711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes        RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), hsl);
211711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes    }
212711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes
213711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes    /**
214711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes     * Convert HSL (hue-saturation-lightness) components to a RGB color.
215711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes     * <ul>
216711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes     * <li>hsl[0] is Hue [0 .. 360)</li>
217711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes     * <li>hsl[1] is Saturation [0...1]</li>
218711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes     * <li>hsl[2] is Lightness [0...1]</li>
2197aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes     * </ul>
2207aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes     * If hsv values are out of range, they are pinned.
2217aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes     *
2227aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes     * @param hsl 3 element array which holds the input HSL components.
2237aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes     * @return the resulting RGB color
2247aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes     */
225711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes    public static int HSLToColor(float[] hsl) {
226059178a8c7cc80848e5594a9287be91bd924831aChris Banes        final float h = hsl[0];
227059178a8c7cc80848e5594a9287be91bd924831aChris Banes        final float s = hsl[1];
228059178a8c7cc80848e5594a9287be91bd924831aChris Banes        final float l = hsl[2];
229059178a8c7cc80848e5594a9287be91bd924831aChris Banes
230059178a8c7cc80848e5594a9287be91bd924831aChris Banes        final float c = (1f - Math.abs(2 * l - 1f)) * s;
231059178a8c7cc80848e5594a9287be91bd924831aChris Banes        final float m = l - 0.5f * c;
232059178a8c7cc80848e5594a9287be91bd924831aChris Banes        final float x = c * (1f - Math.abs((h / 60f % 2f) - 1f));
233059178a8c7cc80848e5594a9287be91bd924831aChris Banes
234059178a8c7cc80848e5594a9287be91bd924831aChris Banes        final int hueSegment = (int) h / 60;
235059178a8c7cc80848e5594a9287be91bd924831aChris Banes
236059178a8c7cc80848e5594a9287be91bd924831aChris Banes        int r = 0, g = 0, b = 0;
237059178a8c7cc80848e5594a9287be91bd924831aChris Banes
238059178a8c7cc80848e5594a9287be91bd924831aChris Banes        switch (hueSegment) {
239059178a8c7cc80848e5594a9287be91bd924831aChris Banes            case 0:
240059178a8c7cc80848e5594a9287be91bd924831aChris Banes                r = Math.round(255 * (c + m));
241059178a8c7cc80848e5594a9287be91bd924831aChris Banes                g = Math.round(255 * (x + m));
242059178a8c7cc80848e5594a9287be91bd924831aChris Banes                b = Math.round(255 * m);
243059178a8c7cc80848e5594a9287be91bd924831aChris Banes                break;
244059178a8c7cc80848e5594a9287be91bd924831aChris Banes            case 1:
245059178a8c7cc80848e5594a9287be91bd924831aChris Banes                r = Math.round(255 * (x + m));
246059178a8c7cc80848e5594a9287be91bd924831aChris Banes                g = Math.round(255 * (c + m));
247059178a8c7cc80848e5594a9287be91bd924831aChris Banes                b = Math.round(255 * m);
248059178a8c7cc80848e5594a9287be91bd924831aChris Banes                break;
249059178a8c7cc80848e5594a9287be91bd924831aChris Banes            case 2:
250059178a8c7cc80848e5594a9287be91bd924831aChris Banes                r = Math.round(255 * m);
251059178a8c7cc80848e5594a9287be91bd924831aChris Banes                g = Math.round(255 * (c + m));
252059178a8c7cc80848e5594a9287be91bd924831aChris Banes                b = Math.round(255 * (x + m));
253059178a8c7cc80848e5594a9287be91bd924831aChris Banes                break;
254059178a8c7cc80848e5594a9287be91bd924831aChris Banes            case 3:
255059178a8c7cc80848e5594a9287be91bd924831aChris Banes                r = Math.round(255 * m);
256059178a8c7cc80848e5594a9287be91bd924831aChris Banes                g = Math.round(255 * (x + m));
257059178a8c7cc80848e5594a9287be91bd924831aChris Banes                b = Math.round(255 * (c + m));
258059178a8c7cc80848e5594a9287be91bd924831aChris Banes                break;
259059178a8c7cc80848e5594a9287be91bd924831aChris Banes            case 4:
260059178a8c7cc80848e5594a9287be91bd924831aChris Banes                r = Math.round(255 * (x + m));
261059178a8c7cc80848e5594a9287be91bd924831aChris Banes                g = Math.round(255 * m);
262059178a8c7cc80848e5594a9287be91bd924831aChris Banes                b = Math.round(255 * (c + m));
263059178a8c7cc80848e5594a9287be91bd924831aChris Banes                break;
264059178a8c7cc80848e5594a9287be91bd924831aChris Banes            case 5:
265059178a8c7cc80848e5594a9287be91bd924831aChris Banes            case 6:
266059178a8c7cc80848e5594a9287be91bd924831aChris Banes                r = Math.round(255 * (c + m));
267059178a8c7cc80848e5594a9287be91bd924831aChris Banes                g = Math.round(255 * m);
268059178a8c7cc80848e5594a9287be91bd924831aChris Banes                b = Math.round(255 * (x + m));
269059178a8c7cc80848e5594a9287be91bd924831aChris Banes                break;
270059178a8c7cc80848e5594a9287be91bd924831aChris Banes        }
271059178a8c7cc80848e5594a9287be91bd924831aChris Banes
272c929274ecb2495a428f214393c787360c3433438Chris Banes        r = constrain(r, 0, 255);
273c929274ecb2495a428f214393c787360c3433438Chris Banes        g = constrain(g, 0, 255);
274c929274ecb2495a428f214393c787360c3433438Chris Banes        b = constrain(b, 0, 255);
275059178a8c7cc80848e5594a9287be91bd924831aChris Banes
276059178a8c7cc80848e5594a9287be91bd924831aChris Banes        return Color.rgb(r, g, b);
277059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
278059178a8c7cc80848e5594a9287be91bd924831aChris Banes
279f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes    /**
280f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes     * Set the alpha component of {@code color} to be {@code alpha}.
281f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes     */
282711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes    public static int setAlphaComponent(int color, int alpha) {
283711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes        if (alpha < 0 || alpha > 255) {
284711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes            throw new IllegalArgumentException("alpha must be between 0 and 255.");
285711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes        }
286f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        return (color & 0x00ffffff) | (alpha << 24);
287f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes    }
288f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes
289c929274ecb2495a428f214393c787360c3433438Chris Banes    private static float constrain(float amount, float low, float high) {
290c929274ecb2495a428f214393c787360c3433438Chris Banes        return amount < low ? low : (amount > high ? high : amount);
291c929274ecb2495a428f214393c787360c3433438Chris Banes    }
292c929274ecb2495a428f214393c787360c3433438Chris Banes
293c929274ecb2495a428f214393c787360c3433438Chris Banes    private static int constrain(int amount, int low, int high) {
294c929274ecb2495a428f214393c787360c3433438Chris Banes        return amount < low ? low : (amount > high ? high : amount);
295c929274ecb2495a428f214393c787360c3433438Chris Banes    }
296c929274ecb2495a428f214393c787360c3433438Chris Banes
297059178a8c7cc80848e5594a9287be91bd924831aChris Banes}
298