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}