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 /** 109440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * Calculates the minimum alpha value which can be applied to {@code foreground} so that would 110440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * have a contrast value of at least {@code minContrastRatio} when compared to 111440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * {@code background}. 112440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 113440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param foreground the foreground color 114440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param background the opaque background color 115440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param minContrastRatio the minimum contrast ratio 116440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @return the alpha value in the range 0-255, or -1 if no value could be calculated 117440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin */ 118440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin public static int calculateMinimumAlpha(@ColorInt int foreground, @ColorInt int background, 119440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin float minContrastRatio) { 120440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin if (Color.alpha(background) != 255) { 121440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin throw new IllegalArgumentException("background can not be translucent: #" 122440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin + Integer.toHexString(background)); 123440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 124440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 125440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin // First lets check that a fully opaque foreground has sufficient contrast 126440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin int testForeground = setAlphaComponent(foreground, 255); 127440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin double testRatio = calculateContrast(testForeground, background); 128440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin if (testRatio < minContrastRatio) { 129440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin // Fully opaque foreground does not have sufficient contrast, return error 130440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin return -1; 131440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 132440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 133440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin // Binary search to find a value with the minimum value which provides sufficient contrast 134440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin int numIterations = 0; 135440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin int minAlpha = 0; 136440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin int maxAlpha = 255; 137440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 138440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin while (numIterations <= MIN_ALPHA_SEARCH_MAX_ITERATIONS && 139440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin (maxAlpha - minAlpha) > MIN_ALPHA_SEARCH_PRECISION) { 140440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin final int testAlpha = (minAlpha + maxAlpha) / 2; 141440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 142440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin testForeground = setAlphaComponent(foreground, testAlpha); 143440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin testRatio = calculateContrast(testForeground, background); 144440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 145440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin if (testRatio < minContrastRatio) { 146440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin minAlpha = testAlpha; 147440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } else { 148440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin maxAlpha = testAlpha; 149440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 150440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 151440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin numIterations++; 152440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 153440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 154440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin // Conservatively return the max of the range of possible alphas, which is known to pass. 155440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin return maxAlpha; 156440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 157440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 158440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin /** 159440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * Convert RGB components to HSL (hue-saturation-lightness). 160440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <ul> 161440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <li>outHsl[0] is Hue [0 .. 360)</li> 162440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <li>outHsl[1] is Saturation [0...1]</li> 163440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <li>outHsl[2] is Lightness [0...1]</li> 164440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * </ul> 165440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 166440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param r red component value [0..255] 167440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param g green component value [0..255] 168440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param b blue component value [0..255] 169440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param outHsl 3-element array which holds the resulting HSL components 170440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin */ 171440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin public static void RGBToHSL(@IntRange(from = 0x0, to = 0xFF) int r, 172440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin @IntRange(from = 0x0, to = 0xFF) int g, @IntRange(from = 0x0, to = 0xFF) int b, 173440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin @NonNull float[] outHsl) { 174440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin final float rf = r / 255f; 175440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin final float gf = g / 255f; 176440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin final float bf = b / 255f; 177440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 178440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin final float max = Math.max(rf, Math.max(gf, bf)); 179440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin final float min = Math.min(rf, Math.min(gf, bf)); 180440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin final float deltaMaxMin = max - min; 181440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 182440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin float h, s; 183440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin float l = (max + min) / 2f; 184440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 185440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin if (max == min) { 186440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin // Monochromatic 187440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin h = s = 0f; 188440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } else { 189440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin if (max == rf) { 190440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin h = ((gf - bf) / deltaMaxMin) % 6f; 191440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } else if (max == gf) { 192440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin h = ((bf - rf) / deltaMaxMin) + 2f; 193440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } else { 194440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin h = ((rf - gf) / deltaMaxMin) + 4f; 195440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 196440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 197440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin s = deltaMaxMin / (1f - Math.abs(2f * l - 1f)); 198440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 199440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 200440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin h = (h * 60f) % 360f; 201440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin if (h < 0) { 202440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin h += 360f; 203440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 204440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 205440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin outHsl[0] = constrain(h, 0f, 360f); 206440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin outHsl[1] = constrain(s, 0f, 1f); 207440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin outHsl[2] = constrain(l, 0f, 1f); 208440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 209440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 210440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin /** 211440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * Convert the ARGB color to its HSL (hue-saturation-lightness) components. 212440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <ul> 213440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <li>outHsl[0] is Hue [0 .. 360)</li> 214440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <li>outHsl[1] is Saturation [0...1]</li> 215440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <li>outHsl[2] is Lightness [0...1]</li> 216440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * </ul> 217440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 218440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param color the ARGB color to convert. The alpha component is ignored 219440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param outHsl 3-element array which holds the resulting HSL components 220440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin */ 221440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin public static void colorToHSL(@ColorInt int color, @NonNull float[] outHsl) { 222440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), outHsl); 223440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 224440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 225440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin /** 226440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * Convert HSL (hue-saturation-lightness) components to a RGB color. 227440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <ul> 228440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <li>hsl[0] is Hue [0 .. 360)</li> 229440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <li>hsl[1] is Saturation [0...1]</li> 230440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <li>hsl[2] is Lightness [0...1]</li> 231440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * </ul> 232440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * If hsv values are out of range, they are pinned. 233440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 234440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param hsl 3-element array which holds the input HSL components 235440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @return the resulting RGB color 236440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin */ 237440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin @ColorInt 238440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin public static int HSLToColor(@NonNull float[] hsl) { 239440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin final float h = hsl[0]; 240440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin final float s = hsl[1]; 241440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin final float l = hsl[2]; 242440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 243440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin final float c = (1f - Math.abs(2 * l - 1f)) * s; 244440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin final float m = l - 0.5f * c; 245440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin final float x = c * (1f - Math.abs((h / 60f % 2f) - 1f)); 246440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 247440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin final int hueSegment = (int) h / 60; 248440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 249440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin int r = 0, g = 0, b = 0; 250440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 251440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin switch (hueSegment) { 252440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin case 0: 253440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin r = Math.round(255 * (c + m)); 254440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin g = Math.round(255 * (x + m)); 255440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin b = Math.round(255 * m); 256440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin break; 257440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin case 1: 258440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin r = Math.round(255 * (x + m)); 259440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin g = Math.round(255 * (c + m)); 260440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin b = Math.round(255 * m); 261440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin break; 262440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin case 2: 263440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin r = Math.round(255 * m); 264440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin g = Math.round(255 * (c + m)); 265440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin b = Math.round(255 * (x + m)); 266440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin break; 267440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin case 3: 268440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin r = Math.round(255 * m); 269440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin g = Math.round(255 * (x + m)); 270440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin b = Math.round(255 * (c + m)); 271440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin break; 272440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin case 4: 273440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin r = Math.round(255 * (x + m)); 274440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin g = Math.round(255 * m); 275440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin b = Math.round(255 * (c + m)); 276440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin break; 277440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin case 5: 278440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin case 6: 279440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin r = Math.round(255 * (c + m)); 280440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin g = Math.round(255 * m); 281440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin b = Math.round(255 * (x + m)); 282440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin break; 283440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 284440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 285440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin r = constrain(r, 0, 255); 286440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin g = constrain(g, 0, 255); 287440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin b = constrain(b, 0, 255); 288440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 289440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin return Color.rgb(r, g, b); 290440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 291440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 292440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin /** 293440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * Set the alpha component of {@code color} to be {@code alpha}. 294440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin */ 295440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin @ColorInt 296440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin public static int setAlphaComponent(@ColorInt int color, 297440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin @IntRange(from = 0x0, to = 0xFF) int alpha) { 298440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin if (alpha < 0 || alpha > 255) { 299440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin throw new IllegalArgumentException("alpha must be between 0 and 255."); 300440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 301440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin return (color & 0x00ffffff) | (alpha << 24); 302440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 303440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 304440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin /** 305440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * Convert the ARGB color to its CIE Lab representative components. 306440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 307440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param color the ARGB color to convert. The alpha component is ignored 308440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param outLab 3-element array which holds the resulting LAB components 309440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin */ 310440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin public static void colorToLAB(@ColorInt int color, @NonNull double[] outLab) { 311440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin RGBToLAB(Color.red(color), Color.green(color), Color.blue(color), outLab); 312440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 313440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 314440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin /** 315440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * Convert RGB components to its CIE Lab representative components. 316440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 317440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <ul> 318440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <li>outLab[0] is L [0 ...1)</li> 319440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <li>outLab[1] is a [-128...127)</li> 320440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <li>outLab[2] is b [-128...127)</li> 321440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * </ul> 322440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 323440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param r red component value [0..255] 324440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param g green component value [0..255] 325440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param b blue component value [0..255] 326440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param outLab 3-element array which holds the resulting LAB components 327440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin */ 328440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin public static void RGBToLAB(@IntRange(from = 0x0, to = 0xFF) int r, 329440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin @IntRange(from = 0x0, to = 0xFF) int g, @IntRange(from = 0x0, to = 0xFF) int b, 330440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin @NonNull double[] outLab) { 331440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin // First we convert RGB to XYZ 332440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin RGBToXYZ(r, g, b, outLab); 333440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin // outLab now contains XYZ 334440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin XYZToLAB(outLab[0], outLab[1], outLab[2], outLab); 335440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin // outLab now contains LAB representation 336440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 337440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 338440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin /** 339440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * Convert the ARGB color to its CIE XYZ representative components. 340440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 341440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <p>The resulting XYZ representation will use the D65 illuminant and the CIE 342440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 2° Standard Observer (1931).</p> 343440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 344440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <ul> 345440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <li>outXyz[0] is X [0 ...95.047)</li> 346440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <li>outXyz[1] is Y [0...100)</li> 347440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <li>outXyz[2] is Z [0...108.883)</li> 348440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * </ul> 349440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 350440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param color the ARGB color to convert. The alpha component is ignored 351440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param outXyz 3-element array which holds the resulting LAB components 352440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin */ 353440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin public static void colorToXYZ(@ColorInt int color, @NonNull double[] outXyz) { 354440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin RGBToXYZ(Color.red(color), Color.green(color), Color.blue(color), outXyz); 355440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 356440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 357440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin /** 358440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * Convert RGB components to its CIE XYZ representative components. 359440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 360440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <p>The resulting XYZ representation will use the D65 illuminant and the CIE 361440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 2° Standard Observer (1931).</p> 362440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 363440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <ul> 364440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <li>outXyz[0] is X [0 ...95.047)</li> 365440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <li>outXyz[1] is Y [0...100)</li> 366440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <li>outXyz[2] is Z [0...108.883)</li> 367440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * </ul> 368440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 369440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param r red component value [0..255] 370440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param g green component value [0..255] 371440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param b blue component value [0..255] 372440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param outXyz 3-element array which holds the resulting XYZ components 373440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin */ 374440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin public static void RGBToXYZ(@IntRange(from = 0x0, to = 0xFF) int r, 375440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin @IntRange(from = 0x0, to = 0xFF) int g, @IntRange(from = 0x0, to = 0xFF) int b, 376440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin @NonNull double[] outXyz) { 377440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin if (outXyz.length != 3) { 378440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin throw new IllegalArgumentException("outXyz must have a length of 3."); 379440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 380440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 381440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin double sr = r / 255.0; 382440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin sr = sr < 0.04045 ? sr / 12.92 : Math.pow((sr + 0.055) / 1.055, 2.4); 383440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin double sg = g / 255.0; 384440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin sg = sg < 0.04045 ? sg / 12.92 : Math.pow((sg + 0.055) / 1.055, 2.4); 385440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin double sb = b / 255.0; 386440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin sb = sb < 0.04045 ? sb / 12.92 : Math.pow((sb + 0.055) / 1.055, 2.4); 387440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 388440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin outXyz[0] = 100 * (sr * 0.4124 + sg * 0.3576 + sb * 0.1805); 389440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin outXyz[1] = 100 * (sr * 0.2126 + sg * 0.7152 + sb * 0.0722); 390440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin outXyz[2] = 100 * (sr * 0.0193 + sg * 0.1192 + sb * 0.9505); 391440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 392440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 393440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin /** 394440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * Converts a color from CIE XYZ to CIE Lab representation. 395440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 396440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <p>This method expects the XYZ representation to use the D65 illuminant and the CIE 397440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 2° Standard Observer (1931).</p> 398440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 399440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <ul> 400440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <li>outLab[0] is L [0 ...1)</li> 401440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <li>outLab[1] is a [-128...127)</li> 402440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <li>outLab[2] is b [-128...127)</li> 403440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * </ul> 404440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 405440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param x X component value [0...95.047) 406440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param y Y component value [0...100) 407440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param z Z component value [0...108.883) 408440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param outLab 3-element array which holds the resulting Lab components 409440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin */ 410440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin public static void XYZToLAB(@FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_X) double x, 411440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Y) double y, 412440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Z) double z, 413440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin @NonNull double[] outLab) { 414440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin if (outLab.length != 3) { 415440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin throw new IllegalArgumentException("outLab must have a length of 3."); 416440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 417440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin x = pivotXyzComponent(x / XYZ_WHITE_REFERENCE_X); 418440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin y = pivotXyzComponent(y / XYZ_WHITE_REFERENCE_Y); 419440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin z = pivotXyzComponent(z / XYZ_WHITE_REFERENCE_Z); 420440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin outLab[0] = Math.max(0, 116 * y - 16); 421440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin outLab[1] = 500 * (x - y); 422440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin outLab[2] = 200 * (y - z); 423440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 424440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 425440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin /** 426440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * Converts a color from CIE Lab to CIE XYZ representation. 427440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 428440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <p>The resulting XYZ representation will use the D65 illuminant and the CIE 429440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 2° Standard Observer (1931).</p> 430440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 431440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <ul> 432440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <li>outXyz[0] is X [0 ...95.047)</li> 433440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <li>outXyz[1] is Y [0...100)</li> 434440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <li>outXyz[2] is Z [0...108.883)</li> 435440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * </ul> 436440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 437440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param l L component value [0...100) 438440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param a A component value [-128...127) 439440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param b B component value [-128...127) 440440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param outXyz 3-element array which holds the resulting XYZ components 441440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin */ 442440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin public static void LABToXYZ(@FloatRange(from = 0f, to = 100) final double l, 443440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin @FloatRange(from = -128, to = 127) final double a, 444440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin @FloatRange(from = -128, to = 127) final double b, 445440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin @NonNull double[] outXyz) { 446440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin final double fy = (l + 16) / 116; 447440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin final double fx = a / 500 + fy; 448440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin final double fz = fy - b / 200; 449440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 450440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin double tmp = Math.pow(fx, 3); 451440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin final double xr = tmp > XYZ_EPSILON ? tmp : (116 * fx - 16) / XYZ_KAPPA; 452440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin final double yr = l > XYZ_KAPPA * XYZ_EPSILON ? Math.pow(fy, 3) : l / XYZ_KAPPA; 453440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 454440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin tmp = Math.pow(fz, 3); 455440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin final double zr = tmp > XYZ_EPSILON ? tmp : (116 * fz - 16) / XYZ_KAPPA; 456440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 457440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin outXyz[0] = xr * XYZ_WHITE_REFERENCE_X; 458440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin outXyz[1] = yr * XYZ_WHITE_REFERENCE_Y; 459440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin outXyz[2] = zr * XYZ_WHITE_REFERENCE_Z; 460440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 461440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 462440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin /** 463440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * Converts a color from CIE XYZ to its RGB representation. 464440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 465440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <p>This method expects the XYZ representation to use the D65 illuminant and the CIE 466440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 2° Standard Observer (1931).</p> 467440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 468440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param x X component value [0...95.047) 469440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param y Y component value [0...100) 470440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param z Z component value [0...108.883) 471440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @return int containing the RGB representation 472440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin */ 473440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin @ColorInt 474440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin public static int XYZToColor(@FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_X) double x, 475440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Y) double y, 476440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Z) double z) { 477440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin double r = (x * 3.2406 + y * -1.5372 + z * -0.4986) / 100; 478440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin double g = (x * -0.9689 + y * 1.8758 + z * 0.0415) / 100; 479440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin double b = (x * 0.0557 + y * -0.2040 + z * 1.0570) / 100; 480440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 481440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin r = r > 0.0031308 ? 1.055 * Math.pow(r, 1 / 2.4) - 0.055 : 12.92 * r; 482440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin g = g > 0.0031308 ? 1.055 * Math.pow(g, 1 / 2.4) - 0.055 : 12.92 * g; 483440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin b = b > 0.0031308 ? 1.055 * Math.pow(b, 1 / 2.4) - 0.055 : 12.92 * b; 484440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 485440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin return Color.rgb( 486440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin constrain((int) Math.round(r * 255), 0, 255), 487440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin constrain((int) Math.round(g * 255), 0, 255), 488440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin constrain((int) Math.round(b * 255), 0, 255)); 489440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 490440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 491440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin /** 492440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * Converts a color from CIE Lab to its RGB representation. 493440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 494440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param l L component value [0...100] 495440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param a A component value [-128...127] 496440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param b B component value [-128...127] 497440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @return int containing the RGB representation 498440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin */ 499440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin @ColorInt 500440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin public static int LABToColor(@FloatRange(from = 0f, to = 100) final double l, 501440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin @FloatRange(from = -128, to = 127) final double a, 502440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin @FloatRange(from = -128, to = 127) final double b) { 503440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin final double[] result = getTempDouble3Array(); 504440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin LABToXYZ(l, a, b, result); 505440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin return XYZToColor(result[0], result[1], result[2]); 506440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 507440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 508440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin /** 509440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * Returns the euclidean distance between two LAB colors. 510440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin */ 511440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin public static double distanceEuclidean(@NonNull double[] labX, @NonNull double[] labY) { 512440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin return Math.sqrt(Math.pow(labX[0] - labY[0], 2) 513440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin + Math.pow(labX[1] - labY[1], 2) 514440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin + Math.pow(labX[2] - labY[2], 2)); 515440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 516440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 517440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin private static float constrain(float amount, float low, float high) { 518440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin return amount < low ? low : (amount > high ? high : amount); 519440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 520440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 521440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin private static int constrain(int amount, int low, int high) { 522440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin return amount < low ? low : (amount > high ? high : amount); 523440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 524440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 525440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin private static double pivotXyzComponent(double component) { 526440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin return component > XYZ_EPSILON 527440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin ? Math.pow(component, 1 / 3.0) 528440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin : (XYZ_KAPPA * component + 16) / 116; 529440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 530440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 531440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin /** 532440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * Blend between two ARGB colors using the given ratio. 533440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 534440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <p>A blend ratio of 0.0 will result in {@code color1}, 0.5 will give an even blend, 535440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 1.0 will result in {@code color2}.</p> 536440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 537440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param color1 the first ARGB color 538440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param color2 the second ARGB color 539440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param ratio the blend ratio of {@code color1} to {@code color2} 540440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin */ 541440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin @ColorInt 542440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin public static int blendARGB(@ColorInt int color1, @ColorInt int color2, 543440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin @FloatRange(from = 0.0, to = 1.0) float ratio) { 544440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin final float inverseRatio = 1 - ratio; 545440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin float a = Color.alpha(color1) * inverseRatio + Color.alpha(color2) * ratio; 546440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin float r = Color.red(color1) * inverseRatio + Color.red(color2) * ratio; 547440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin float g = Color.green(color1) * inverseRatio + Color.green(color2) * ratio; 548440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin float b = Color.blue(color1) * inverseRatio + Color.blue(color2) * ratio; 549440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin return Color.argb((int) a, (int) r, (int) g, (int) b); 550440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 551440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 552440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin /** 553440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * Blend between {@code hsl1} and {@code hsl2} using the given ratio. This will interpolate 554440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * the hue using the shortest angle. 555440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 556440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <p>A blend ratio of 0.0 will result in {@code hsl1}, 0.5 will give an even blend, 557440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 1.0 will result in {@code hsl2}.</p> 558440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 559440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param hsl1 3-element array which holds the first HSL color 560440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param hsl2 3-element array which holds the second HSL color 561440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param ratio the blend ratio of {@code hsl1} to {@code hsl2} 562440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param outResult 3-element array which holds the resulting HSL components 563440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin */ 564440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin public static void blendHSL(@NonNull float[] hsl1, @NonNull float[] hsl2, 565440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin @FloatRange(from = 0.0, to = 1.0) float ratio, @NonNull float[] outResult) { 566440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin if (outResult.length != 3) { 567440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin throw new IllegalArgumentException("result must have a length of 3."); 568440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 569440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin final float inverseRatio = 1 - ratio; 570440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin // Since hue is circular we will need to interpolate carefully 571440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin outResult[0] = circularInterpolate(hsl1[0], hsl2[0], ratio); 572440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin outResult[1] = hsl1[1] * inverseRatio + hsl2[1] * ratio; 573440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin outResult[2] = hsl1[2] * inverseRatio + hsl2[2] * ratio; 574440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 575440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 576440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin /** 577440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * Blend between two CIE-LAB colors using the given ratio. 578440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 579440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * <p>A blend ratio of 0.0 will result in {@code lab1}, 0.5 will give an even blend, 580440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 1.0 will result in {@code lab2}.</p> 581440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * 582440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param lab1 3-element array which holds the first LAB color 583440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param lab2 3-element array which holds the second LAB color 584440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param ratio the blend ratio of {@code lab1} to {@code lab2} 585440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin * @param outResult 3-element array which holds the resulting LAB components 586440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin */ 587440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin public static void blendLAB(@NonNull double[] lab1, @NonNull double[] lab2, 588440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin @FloatRange(from = 0.0, to = 1.0) double ratio, @NonNull double[] outResult) { 589440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin if (outResult.length != 3) { 590440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin throw new IllegalArgumentException("outResult must have a length of 3."); 591440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 592440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin final double inverseRatio = 1 - ratio; 593440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin outResult[0] = lab1[0] * inverseRatio + lab2[0] * ratio; 594440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin outResult[1] = lab1[1] * inverseRatio + lab2[1] * ratio; 595440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin outResult[2] = lab1[2] * inverseRatio + lab2[2] * ratio; 596440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 597440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 598440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin static float circularInterpolate(float a, float b, float f) { 599440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin if (Math.abs(b - a) > 180) { 600440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin if (b > a) { 601440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin a += 360; 602440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } else { 603440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin b += 360; 604440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 605440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 606440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin return (a + ((b - a) * f)) % 360; 607440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 608440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 609440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin private static double[] getTempDouble3Array() { 610440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin double[] result = TEMP_ARRAY.get(); 611440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin if (result == null) { 612440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin result = new double[3]; 613440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin TEMP_ARRAY.set(result); 614440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 615440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin return result; 616440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin } 617440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin 618440e8e9dbc4ed4ecb20284607251f746833cd472Lucas Dupin}