1440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin/*
2440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * Copyright (C) 2017 The Android Open Source Project
3440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin *
4440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * Licensed under the Apache License, Version 2.0 (the "License");
5440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * you may not use this file except in compliance with the License.
6440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * You may obtain a copy of the License at
7440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin *
8440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin *      http://www.apache.org/licenses/LICENSE-2.0
9440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin *
10440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * Unless required by applicable law or agreed to in writing, software
11440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * distributed under the License is distributed on an "AS IS" BASIS,
12440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * See the License for the specific language governing permissions and
14440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * limitations under the License
15440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin */
16440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
17440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupinpackage com.android.internal.graphics;
18440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
19440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupinimport android.annotation.ColorInt;
20440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupinimport android.annotation.FloatRange;
21440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupinimport android.annotation.IntRange;
22440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupinimport android.annotation.NonNull;
23440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupinimport android.graphics.Color;
24440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
25440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin/**
26440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * Copied from: frameworks/support/core-utils/java/android/support/v4/graphics/ColorUtils.java
27440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin *
28440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * A set of color-related utility methods, building upon those available in {@code Color}.
29440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin */
30440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupinpublic final class ColorUtils {
31440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
32440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    private static final double XYZ_WHITE_REFERENCE_X = 95.047;
33440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    private static final double XYZ_WHITE_REFERENCE_Y = 100;
34440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    private static final double XYZ_WHITE_REFERENCE_Z = 108.883;
35440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    private static final double XYZ_EPSILON = 0.008856;
36440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    private static final double XYZ_KAPPA = 903.3;
37440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
38440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    private static final int MIN_ALPHA_SEARCH_MAX_ITERATIONS = 10;
39440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    private static final int MIN_ALPHA_SEARCH_PRECISION = 1;
40440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
41440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    private static final ThreadLocal<double[]> TEMP_ARRAY = new ThreadLocal<>();
42440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
43440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    private ColorUtils() {}
44440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
45440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    /**
46440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * Composite two potentially translucent colors over each other and returns the result.
47440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     */
48440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    public static int compositeColors(@ColorInt int foreground, @ColorInt int background) {
49440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        int bgAlpha = Color.alpha(background);
50440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        int fgAlpha = Color.alpha(foreground);
51440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        int a = compositeAlpha(fgAlpha, bgAlpha);
52440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
53440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        int r = compositeComponent(Color.red(foreground), fgAlpha,
54440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                Color.red(background), bgAlpha, a);
55440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        int g = compositeComponent(Color.green(foreground), fgAlpha,
56440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                Color.green(background), bgAlpha, a);
57440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        int b = compositeComponent(Color.blue(foreground), fgAlpha,
58440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                Color.blue(background), bgAlpha, a);
59440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
60440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        return Color.argb(a, r, g, b);
61440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    }
62440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
63440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    private static int compositeAlpha(int foregroundAlpha, int backgroundAlpha) {
64440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        return 0xFF - (((0xFF - backgroundAlpha) * (0xFF - foregroundAlpha)) / 0xFF);
65440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    }
66440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
67440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    private static int compositeComponent(int fgC, int fgA, int bgC, int bgA, int a) {
68440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        if (a == 0) return 0;
69440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        return ((0xFF * fgC * fgA) + (bgC * bgA * (0xFF - fgA))) / (a * 0xFF);
70440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    }
71440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
72440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    /**
73440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * Returns the luminance of a color as a float between {@code 0.0} and {@code 1.0}.
74440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <p>Defined as the Y component in the XYZ representation of {@code color}.</p>
75440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     */
76440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    @FloatRange(from = 0.0, to = 1.0)
77440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    public static double calculateLuminance(@ColorInt int color) {
78440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        final double[] result = getTempDouble3Array();
79440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        colorToXYZ(color, result);
80440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        // Luminance is the Y component
81440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        return result[1] / 100;
82440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    }
83440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
84440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    /**
85440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * Returns the contrast ratio between {@code foreground} and {@code background}.
86440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * {@code background} must be opaque.
87440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <p>
88440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * Formula defined
89440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <a href="http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef">here</a>.
90440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     */
91440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    public static double calculateContrast(@ColorInt int foreground, @ColorInt int background) {
92440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        if (Color.alpha(background) != 255) {
93440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            throw new IllegalArgumentException("background can not be translucent: #"
94440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                    + Integer.toHexString(background));
95440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        }
96440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        if (Color.alpha(foreground) < 255) {
97440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            // If the foreground is translucent, composite the foreground over the background
98440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            foreground = compositeColors(foreground, background);
99440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        }
100440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
101440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        final double luminance1 = calculateLuminance(foreground) + 0.05;
102440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        final double luminance2 = calculateLuminance(background) + 0.05;
103440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
104440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        // Now return the lighter luminance divided by the darker luminance
105440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        return Math.max(luminance1, luminance2) / Math.min(luminance1, luminance2);
106440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    }
107440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
108440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    /**
1099324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin     * Calculates the minimum alpha value which can be applied to {@code background} so that would
1109324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin     * have a contrast value of at least {@code minContrastRatio} when alpha blended to
1119324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin     * {@code foreground}.
1129324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin     *
1139324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin     * @param foreground       the foreground color
1149324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin     * @param background       the background color, opacity will be ignored
1159324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin     * @param minContrastRatio the minimum contrast ratio
1169324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin     * @return the alpha value in the range 0-255, or -1 if no value could be calculated
1179324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin     */
1189324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin    public static int calculateMinimumBackgroundAlpha(@ColorInt int foreground,
1199324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin            @ColorInt int background, float minContrastRatio) {
1209324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin        // Ignore initial alpha that the background might have since this is
1219324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin        // what we're trying to calculate.
1229324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin        background = setAlphaComponent(background, 255);
1239324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin        final int leastContrastyColor = setAlphaComponent(foreground, 255);
1249324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin        return binaryAlphaSearch(foreground, background, minContrastRatio, (fg, bg, alpha) -> {
1259324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin            int testBackground = blendARGB(leastContrastyColor, bg, alpha/255f);
1269324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin            // Float rounding might set this alpha to something other that 255,
1279324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin            // raising an exception in calculateContrast.
1289324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin            testBackground = setAlphaComponent(testBackground, 255);
1299324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin            return calculateContrast(fg, testBackground);
1309324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin        });
1319324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin    }
1329324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin
1339324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin    /**
134440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * Calculates the minimum alpha value which can be applied to {@code foreground} so that would
135440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * have a contrast value of at least {@code minContrastRatio} when compared to
136440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * {@code background}.
137440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     *
138440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param foreground       the foreground color
139440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param background       the opaque background color
140440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param minContrastRatio the minimum contrast ratio
141440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @return the alpha value in the range 0-255, or -1 if no value could be calculated
142440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     */
143440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    public static int calculateMinimumAlpha(@ColorInt int foreground, @ColorInt int background,
144440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            float minContrastRatio) {
145440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        if (Color.alpha(background) != 255) {
146440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            throw new IllegalArgumentException("background can not be translucent: #"
147440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                    + Integer.toHexString(background));
148440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        }
149440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
1509324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin        ContrastCalculator contrastCalculator = (fg, bg, alpha) -> {
1519324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin            int testForeground = setAlphaComponent(fg, alpha);
1529324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin            return calculateContrast(testForeground, bg);
1539324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin        };
1549324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin
155440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        // First lets check that a fully opaque foreground has sufficient contrast
1569324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin        double testRatio = contrastCalculator.calculateContrast(foreground, background, 255);
157440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        if (testRatio < minContrastRatio) {
158440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            // Fully opaque foreground does not have sufficient contrast, return error
159440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            return -1;
160440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        }
1619324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin        foreground = setAlphaComponent(foreground, 255);
1629324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin        return binaryAlphaSearch(foreground, background, minContrastRatio, contrastCalculator);
1639324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin    }
164440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
1659324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin    /**
1669324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin     * Calculates the alpha value using binary search based on a given contrast evaluation function
1679324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin     * and target contrast that needs to be satisfied.
1689324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin     *
1699324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin     * @param foreground         the foreground color
1709324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin     * @param background         the opaque background color
1719324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin     * @param minContrastRatio   the minimum contrast ratio
1729324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin     * @param calculator function that calculates contrast
1739324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin     * @return the alpha value in the range 0-255, or -1 if no value could be calculated
1749324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin     */
1759324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin    private static int binaryAlphaSearch(@ColorInt int foreground, @ColorInt int background,
1769324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin            float minContrastRatio, ContrastCalculator calculator) {
177440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        // Binary search to find a value with the minimum value which provides sufficient contrast
178440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        int numIterations = 0;
179440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        int minAlpha = 0;
180440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        int maxAlpha = 255;
181440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
182440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        while (numIterations <= MIN_ALPHA_SEARCH_MAX_ITERATIONS &&
183440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                (maxAlpha - minAlpha) > MIN_ALPHA_SEARCH_PRECISION) {
184440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            final int testAlpha = (minAlpha + maxAlpha) / 2;
185440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
1869324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin            final double testRatio = calculator.calculateContrast(foreground, background,
1879324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin                    testAlpha);
188440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            if (testRatio < minContrastRatio) {
189440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                minAlpha = testAlpha;
190440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            } else {
191440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                maxAlpha = testAlpha;
192440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            }
193440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
194440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            numIterations++;
195440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        }
196440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
197440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        // Conservatively return the max of the range of possible alphas, which is known to pass.
198440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        return maxAlpha;
199440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    }
200440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
201440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    /**
202440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * Convert RGB components to HSL (hue-saturation-lightness).
203440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <ul>
204440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <li>outHsl[0] is Hue [0 .. 360)</li>
205440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <li>outHsl[1] is Saturation [0...1]</li>
206440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <li>outHsl[2] is Lightness [0...1]</li>
207440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * </ul>
208440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     *
209440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param r      red component value [0..255]
210440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param g      green component value [0..255]
211440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param b      blue component value [0..255]
212440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param outHsl 3-element array which holds the resulting HSL components
213440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     */
214440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    public static void RGBToHSL(@IntRange(from = 0x0, to = 0xFF) int r,
215440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            @IntRange(from = 0x0, to = 0xFF) int g, @IntRange(from = 0x0, to = 0xFF) int b,
216440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            @NonNull float[] outHsl) {
217440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        final float rf = r / 255f;
218440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        final float gf = g / 255f;
219440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        final float bf = b / 255f;
220440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
221440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        final float max = Math.max(rf, Math.max(gf, bf));
222440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        final float min = Math.min(rf, Math.min(gf, bf));
223440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        final float deltaMaxMin = max - min;
224440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
225440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        float h, s;
226440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        float l = (max + min) / 2f;
227440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
228440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        if (max == min) {
229440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            // Monochromatic
230440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            h = s = 0f;
231440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        } else {
232440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            if (max == rf) {
233440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                h = ((gf - bf) / deltaMaxMin) % 6f;
234440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            } else if (max == gf) {
235440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                h = ((bf - rf) / deltaMaxMin) + 2f;
236440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            } else {
237440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                h = ((rf - gf) / deltaMaxMin) + 4f;
238440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            }
239440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
240440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            s = deltaMaxMin / (1f - Math.abs(2f * l - 1f));
241440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        }
242440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
243440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        h = (h * 60f) % 360f;
244440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        if (h < 0) {
245440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            h += 360f;
246440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        }
247440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
248440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        outHsl[0] = constrain(h, 0f, 360f);
249440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        outHsl[1] = constrain(s, 0f, 1f);
250440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        outHsl[2] = constrain(l, 0f, 1f);
251440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    }
252440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
253440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    /**
254440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * Convert the ARGB color to its HSL (hue-saturation-lightness) components.
255440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <ul>
256440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <li>outHsl[0] is Hue [0 .. 360)</li>
257440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <li>outHsl[1] is Saturation [0...1]</li>
258440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <li>outHsl[2] is Lightness [0...1]</li>
259440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * </ul>
260440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     *
261440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param color  the ARGB color to convert. The alpha component is ignored
262440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param outHsl 3-element array which holds the resulting HSL components
263440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     */
264440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    public static void colorToHSL(@ColorInt int color, @NonNull float[] outHsl) {
265440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), outHsl);
266440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    }
267440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
268440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    /**
269440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * Convert HSL (hue-saturation-lightness) components to a RGB color.
270440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <ul>
271440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <li>hsl[0] is Hue [0 .. 360)</li>
272440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <li>hsl[1] is Saturation [0...1]</li>
273440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <li>hsl[2] is Lightness [0...1]</li>
274440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * </ul>
275440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * If hsv values are out of range, they are pinned.
276440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     *
277440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param hsl 3-element array which holds the input HSL components
278440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @return the resulting RGB color
279440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     */
280440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    @ColorInt
281440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    public static int HSLToColor(@NonNull float[] hsl) {
282440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        final float h = hsl[0];
283440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        final float s = hsl[1];
284440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        final float l = hsl[2];
285440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
286440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        final float c = (1f - Math.abs(2 * l - 1f)) * s;
287440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        final float m = l - 0.5f * c;
288440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        final float x = c * (1f - Math.abs((h / 60f % 2f) - 1f));
289440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
290440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        final int hueSegment = (int) h / 60;
291440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
292440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        int r = 0, g = 0, b = 0;
293440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
294440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        switch (hueSegment) {
295440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            case 0:
296440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                r = Math.round(255 * (c + m));
297440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                g = Math.round(255 * (x + m));
298440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                b = Math.round(255 * m);
299440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                break;
300440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            case 1:
301440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                r = Math.round(255 * (x + m));
302440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                g = Math.round(255 * (c + m));
303440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                b = Math.round(255 * m);
304440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                break;
305440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            case 2:
306440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                r = Math.round(255 * m);
307440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                g = Math.round(255 * (c + m));
308440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                b = Math.round(255 * (x + m));
309440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                break;
310440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            case 3:
311440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                r = Math.round(255 * m);
312440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                g = Math.round(255 * (x + m));
313440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                b = Math.round(255 * (c + m));
314440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                break;
315440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            case 4:
316440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                r = Math.round(255 * (x + m));
317440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                g = Math.round(255 * m);
318440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                b = Math.round(255 * (c + m));
319440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                break;
320440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            case 5:
321440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            case 6:
322440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                r = Math.round(255 * (c + m));
323440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                g = Math.round(255 * m);
324440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                b = Math.round(255 * (x + m));
325440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                break;
326440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        }
327440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
328440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        r = constrain(r, 0, 255);
329440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        g = constrain(g, 0, 255);
330440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        b = constrain(b, 0, 255);
331440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
332440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        return Color.rgb(r, g, b);
333440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    }
334440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
335440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    /**
336440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * Set the alpha component of {@code color} to be {@code alpha}.
337440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     */
338440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    @ColorInt
339440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    public static int setAlphaComponent(@ColorInt int color,
340440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            @IntRange(from = 0x0, to = 0xFF) int alpha) {
341440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        if (alpha < 0 || alpha > 255) {
342440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            throw new IllegalArgumentException("alpha must be between 0 and 255.");
343440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        }
344440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        return (color & 0x00ffffff) | (alpha << 24);
345440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    }
346440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
347440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    /**
348440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * Convert the ARGB color to its CIE Lab representative components.
349440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     *
350440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param color  the ARGB color to convert. The alpha component is ignored
351440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param outLab 3-element array which holds the resulting LAB components
352440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     */
353440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    public static void colorToLAB(@ColorInt int color, @NonNull double[] outLab) {
354440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        RGBToLAB(Color.red(color), Color.green(color), Color.blue(color), outLab);
355440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    }
356440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
357440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    /**
358440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * Convert RGB components to its CIE Lab representative components.
359440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     *
360440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <ul>
361440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <li>outLab[0] is L [0 ...1)</li>
362440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <li>outLab[1] is a [-128...127)</li>
363440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <li>outLab[2] is b [-128...127)</li>
364440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * </ul>
365440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     *
366440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param r      red component value [0..255]
367440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param g      green component value [0..255]
368440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param b      blue component value [0..255]
369440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param outLab 3-element array which holds the resulting LAB components
370440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     */
371440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    public static void RGBToLAB(@IntRange(from = 0x0, to = 0xFF) int r,
372440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            @IntRange(from = 0x0, to = 0xFF) int g, @IntRange(from = 0x0, to = 0xFF) int b,
373440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            @NonNull double[] outLab) {
374440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        // First we convert RGB to XYZ
375440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        RGBToXYZ(r, g, b, outLab);
376440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        // outLab now contains XYZ
377440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        XYZToLAB(outLab[0], outLab[1], outLab[2], outLab);
378440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        // outLab now contains LAB representation
379440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    }
380440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
381440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    /**
382440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * Convert the ARGB color to its CIE XYZ representative components.
383440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     *
384440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <p>The resulting XYZ representation will use the D65 illuminant and the CIE
385440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * 2° Standard Observer (1931).</p>
386440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     *
387440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <ul>
388440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <li>outXyz[0] is X [0 ...95.047)</li>
389440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <li>outXyz[1] is Y [0...100)</li>
390440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <li>outXyz[2] is Z [0...108.883)</li>
391440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * </ul>
392440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     *
393440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param color  the ARGB color to convert. The alpha component is ignored
394440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param outXyz 3-element array which holds the resulting LAB components
395440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     */
396440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    public static void colorToXYZ(@ColorInt int color, @NonNull double[] outXyz) {
397440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        RGBToXYZ(Color.red(color), Color.green(color), Color.blue(color), outXyz);
398440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    }
399440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
400440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    /**
401440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * Convert RGB components to its CIE XYZ representative components.
402440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     *
403440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <p>The resulting XYZ representation will use the D65 illuminant and the CIE
404440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * 2° Standard Observer (1931).</p>
405440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     *
406440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <ul>
407440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <li>outXyz[0] is X [0 ...95.047)</li>
408440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <li>outXyz[1] is Y [0...100)</li>
409440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <li>outXyz[2] is Z [0...108.883)</li>
410440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * </ul>
411440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     *
412440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param r      red component value [0..255]
413440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param g      green component value [0..255]
414440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param b      blue component value [0..255]
415440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param outXyz 3-element array which holds the resulting XYZ components
416440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     */
417440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    public static void RGBToXYZ(@IntRange(from = 0x0, to = 0xFF) int r,
418440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            @IntRange(from = 0x0, to = 0xFF) int g, @IntRange(from = 0x0, to = 0xFF) int b,
419440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            @NonNull double[] outXyz) {
420440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        if (outXyz.length != 3) {
421440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            throw new IllegalArgumentException("outXyz must have a length of 3.");
422440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        }
423440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
424440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        double sr = r / 255.0;
425440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        sr = sr < 0.04045 ? sr / 12.92 : Math.pow((sr + 0.055) / 1.055, 2.4);
426440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        double sg = g / 255.0;
427440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        sg = sg < 0.04045 ? sg / 12.92 : Math.pow((sg + 0.055) / 1.055, 2.4);
428440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        double sb = b / 255.0;
429440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        sb = sb < 0.04045 ? sb / 12.92 : Math.pow((sb + 0.055) / 1.055, 2.4);
430440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
431440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        outXyz[0] = 100 * (sr * 0.4124 + sg * 0.3576 + sb * 0.1805);
432440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        outXyz[1] = 100 * (sr * 0.2126 + sg * 0.7152 + sb * 0.0722);
433440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        outXyz[2] = 100 * (sr * 0.0193 + sg * 0.1192 + sb * 0.9505);
434440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    }
435440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
436440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    /**
437440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * Converts a color from CIE XYZ to CIE Lab representation.
438440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     *
439440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <p>This method expects the XYZ representation to use the D65 illuminant and the CIE
440440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * 2° Standard Observer (1931).</p>
441440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     *
442440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <ul>
443440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <li>outLab[0] is L [0 ...1)</li>
444440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <li>outLab[1] is a [-128...127)</li>
445440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <li>outLab[2] is b [-128...127)</li>
446440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * </ul>
447440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     *
448440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param x      X component value [0...95.047)
449440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param y      Y component value [0...100)
450440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param z      Z component value [0...108.883)
451440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param outLab 3-element array which holds the resulting Lab components
452440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     */
453440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    public static void XYZToLAB(@FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_X) double x,
454440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Y) double y,
455440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Z) double z,
456440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            @NonNull double[] outLab) {
457440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        if (outLab.length != 3) {
458440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            throw new IllegalArgumentException("outLab must have a length of 3.");
459440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        }
460440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        x = pivotXyzComponent(x / XYZ_WHITE_REFERENCE_X);
461440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        y = pivotXyzComponent(y / XYZ_WHITE_REFERENCE_Y);
462440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        z = pivotXyzComponent(z / XYZ_WHITE_REFERENCE_Z);
463440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        outLab[0] = Math.max(0, 116 * y - 16);
464440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        outLab[1] = 500 * (x - y);
465440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        outLab[2] = 200 * (y - z);
466440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    }
467440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
468440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    /**
469440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * Converts a color from CIE Lab to CIE XYZ representation.
470440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     *
471440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <p>The resulting XYZ representation will use the D65 illuminant and the CIE
472440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * 2° Standard Observer (1931).</p>
473440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     *
474440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <ul>
475440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <li>outXyz[0] is X [0 ...95.047)</li>
476440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <li>outXyz[1] is Y [0...100)</li>
477440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <li>outXyz[2] is Z [0...108.883)</li>
478440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * </ul>
479440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     *
480440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param l      L component value [0...100)
481440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param a      A component value [-128...127)
482440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param b      B component value [-128...127)
483440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param outXyz 3-element array which holds the resulting XYZ components
484440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     */
485440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    public static void LABToXYZ(@FloatRange(from = 0f, to = 100) final double l,
486440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            @FloatRange(from = -128, to = 127) final double a,
487440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            @FloatRange(from = -128, to = 127) final double b,
488440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            @NonNull double[] outXyz) {
489440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        final double fy = (l + 16) / 116;
490440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        final double fx = a / 500 + fy;
491440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        final double fz = fy - b / 200;
492440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
493440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        double tmp = Math.pow(fx, 3);
494440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        final double xr = tmp > XYZ_EPSILON ? tmp : (116 * fx - 16) / XYZ_KAPPA;
495440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        final double yr = l > XYZ_KAPPA * XYZ_EPSILON ? Math.pow(fy, 3) : l / XYZ_KAPPA;
496440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
497440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        tmp = Math.pow(fz, 3);
498440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        final double zr = tmp > XYZ_EPSILON ? tmp : (116 * fz - 16) / XYZ_KAPPA;
499440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
500440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        outXyz[0] = xr * XYZ_WHITE_REFERENCE_X;
501440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        outXyz[1] = yr * XYZ_WHITE_REFERENCE_Y;
502440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        outXyz[2] = zr * XYZ_WHITE_REFERENCE_Z;
503440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    }
504440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
505440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    /**
506440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * Converts a color from CIE XYZ to its RGB representation.
507440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     *
508440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <p>This method expects the XYZ representation to use the D65 illuminant and the CIE
509440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * 2° Standard Observer (1931).</p>
510440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     *
511440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param x X component value [0...95.047)
512440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param y Y component value [0...100)
513440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param z Z component value [0...108.883)
514440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @return int containing the RGB representation
515440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     */
516440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    @ColorInt
517440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    public static int XYZToColor(@FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_X) double x,
518440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Y) double y,
519440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Z) double z) {
520440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        double r = (x * 3.2406 + y * -1.5372 + z * -0.4986) / 100;
521440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        double g = (x * -0.9689 + y * 1.8758 + z * 0.0415) / 100;
522440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        double b = (x * 0.0557 + y * -0.2040 + z * 1.0570) / 100;
523440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
524440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        r = r > 0.0031308 ? 1.055 * Math.pow(r, 1 / 2.4) - 0.055 : 12.92 * r;
525440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        g = g > 0.0031308 ? 1.055 * Math.pow(g, 1 / 2.4) - 0.055 : 12.92 * g;
526440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        b = b > 0.0031308 ? 1.055 * Math.pow(b, 1 / 2.4) - 0.055 : 12.92 * b;
527440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
528440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        return Color.rgb(
529440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                constrain((int) Math.round(r * 255), 0, 255),
530440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                constrain((int) Math.round(g * 255), 0, 255),
531440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                constrain((int) Math.round(b * 255), 0, 255));
532440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    }
533440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
534440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    /**
535440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * Converts a color from CIE Lab to its RGB representation.
536440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     *
537440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param l L component value [0...100]
538440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param a A component value [-128...127]
539440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param b B component value [-128...127]
540440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @return int containing the RGB representation
541440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     */
542440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    @ColorInt
543440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    public static int LABToColor(@FloatRange(from = 0f, to = 100) final double l,
544440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            @FloatRange(from = -128, to = 127) final double a,
545440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            @FloatRange(from = -128, to = 127) final double b) {
546440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        final double[] result = getTempDouble3Array();
547440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        LABToXYZ(l, a, b, result);
548440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        return XYZToColor(result[0], result[1], result[2]);
549440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    }
550440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
551440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    /**
552440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * Returns the euclidean distance between two LAB colors.
553440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     */
554440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    public static double distanceEuclidean(@NonNull double[] labX, @NonNull double[] labY) {
555440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        return Math.sqrt(Math.pow(labX[0] - labY[0], 2)
556440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                + Math.pow(labX[1] - labY[1], 2)
557440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                + Math.pow(labX[2] - labY[2], 2));
558440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    }
559440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
560440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    private static float constrain(float amount, float low, float high) {
561440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        return amount < low ? low : (amount > high ? high : amount);
562440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    }
563440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
564440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    private static int constrain(int amount, int low, int high) {
565440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        return amount < low ? low : (amount > high ? high : amount);
566440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    }
567440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
568440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    private static double pivotXyzComponent(double component) {
569440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        return component > XYZ_EPSILON
570440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                ? Math.pow(component, 1 / 3.0)
571440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                : (XYZ_KAPPA * component + 16) / 116;
572440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    }
573440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
574440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    /**
575440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * Blend between two ARGB colors using the given ratio.
576440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     *
577440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <p>A blend ratio of 0.0 will result in {@code color1}, 0.5 will give an even blend,
578440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * 1.0 will result in {@code color2}.</p>
579440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     *
580440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param color1 the first ARGB color
581440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param color2 the second ARGB color
582440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param ratio  the blend ratio of {@code color1} to {@code color2}
583440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     */
584440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    @ColorInt
585440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    public static int blendARGB(@ColorInt int color1, @ColorInt int color2,
586440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            @FloatRange(from = 0.0, to = 1.0) float ratio) {
587440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        final float inverseRatio = 1 - ratio;
588440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        float a = Color.alpha(color1) * inverseRatio + Color.alpha(color2) * ratio;
589440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        float r = Color.red(color1) * inverseRatio + Color.red(color2) * ratio;
590440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        float g = Color.green(color1) * inverseRatio + Color.green(color2) * ratio;
591440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        float b = Color.blue(color1) * inverseRatio + Color.blue(color2) * ratio;
592440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        return Color.argb((int) a, (int) r, (int) g, (int) b);
593440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    }
594440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
595440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    /**
596440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * Blend between {@code hsl1} and {@code hsl2} using the given ratio. This will interpolate
597440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * the hue using the shortest angle.
598440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     *
599440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <p>A blend ratio of 0.0 will result in {@code hsl1}, 0.5 will give an even blend,
600440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * 1.0 will result in {@code hsl2}.</p>
601440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     *
602440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param hsl1      3-element array which holds the first HSL color
603440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param hsl2      3-element array which holds the second HSL color
604440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param ratio     the blend ratio of {@code hsl1} to {@code hsl2}
605440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param outResult 3-element array which holds the resulting HSL components
606440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     */
607440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    public static void blendHSL(@NonNull float[] hsl1, @NonNull float[] hsl2,
608440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            @FloatRange(from = 0.0, to = 1.0) float ratio, @NonNull float[] outResult) {
609440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        if (outResult.length != 3) {
610440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            throw new IllegalArgumentException("result must have a length of 3.");
611440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        }
612440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        final float inverseRatio = 1 - ratio;
613440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        // Since hue is circular we will need to interpolate carefully
614440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        outResult[0] = circularInterpolate(hsl1[0], hsl2[0], ratio);
615440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        outResult[1] = hsl1[1] * inverseRatio + hsl2[1] * ratio;
616440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        outResult[2] = hsl1[2] * inverseRatio + hsl2[2] * ratio;
617440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    }
618440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
619440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    /**
620440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * Blend between two CIE-LAB colors using the given ratio.
621440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     *
622440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * <p>A blend ratio of 0.0 will result in {@code lab1}, 0.5 will give an even blend,
623440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * 1.0 will result in {@code lab2}.</p>
624440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     *
625440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param lab1      3-element array which holds the first LAB color
626440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param lab2      3-element array which holds the second LAB color
627440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param ratio     the blend ratio of {@code lab1} to {@code lab2}
628440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     * @param outResult 3-element array which holds the resulting LAB components
629440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin     */
630440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    public static void blendLAB(@NonNull double[] lab1, @NonNull double[] lab2,
631440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            @FloatRange(from = 0.0, to = 1.0) double ratio, @NonNull double[] outResult) {
632440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        if (outResult.length != 3) {
633440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            throw new IllegalArgumentException("outResult must have a length of 3.");
634440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        }
635440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        final double inverseRatio = 1 - ratio;
636440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        outResult[0] = lab1[0] * inverseRatio + lab2[0] * ratio;
637440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        outResult[1] = lab1[1] * inverseRatio + lab2[1] * ratio;
638440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        outResult[2] = lab1[2] * inverseRatio + lab2[2] * ratio;
639440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    }
640440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
641440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    static float circularInterpolate(float a, float b, float f) {
642440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        if (Math.abs(b - a) > 180) {
643440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            if (b > a) {
644440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                a += 360;
645440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            } else {
646440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin                b += 360;
647440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            }
648440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        }
649440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        return (a + ((b - a) * f)) % 360;
650440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    }
651440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
652440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    private static double[] getTempDouble3Array() {
653440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        double[] result = TEMP_ARRAY.get();
654440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        if (result == null) {
655440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            result = new double[3];
656440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin            TEMP_ARRAY.set(result);
657440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        }
658440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin        return result;
659440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin    }
660440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin
6619324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin    private interface ContrastCalculator {
6629324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin        double calculateContrast(int foreground, int background, int alpha);
6639324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin    }
6649324aa926a7fe74ed769525c3d93d4a55a378267Lucas Dupin
665440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin}