1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.graphics.cam; 18 19 20 import android.annotation.NonNull; 21 import android.graphics.Color; 22 import android.ravenwood.annotation.RavenwoodKeepWholeClass; 23 24 import com.android.internal.graphics.ColorUtils; 25 26 /** 27 * Collection of methods for transforming between color spaces. 28 * 29 * <p>Methods are named $xFrom$Y. For example, lstarFromInt() returns L* from an ARGB integer. 30 * 31 * <p>These methods, generally, convert colors between the L*a*b*, XYZ, and sRGB spaces. 32 * 33 * <p>L*a*b* is a perceptually accurate color space. This is particularly important in the L* 34 * dimension: it measures luminance and unlike lightness measures traditionally used in UI work via 35 * RGB or HSL, this luminance transitions smoothly, permitting creation of pleasing shades of a 36 * color, and more pleasing transitions between colors. 37 * 38 * <p>XYZ is commonly used as an intermediate color space for converting between one color space to 39 * another. For example, to convert RGB to L*a*b*, first RGB is converted to XYZ, then XYZ is 40 * convered to L*a*b*. 41 * 42 * <p>sRGB is a "specification originated from work in 1990s through cooperation by Hewlett-Packard 43 * and Microsoft, and it was designed to be a standard definition of RGB for the internet, which it 44 * indeed became...The standard is based on a sampling of computer monitors at the time...The whole 45 * idea of sRGB is that if everyone assumed that RGB meant the same thing, then the results would be 46 * consistent, and reasonably good. It worked." - Fairchild, Color Models and Systems: Handbook of 47 * Color Psychology, 2015 48 */ 49 @RavenwoodKeepWholeClass 50 public final class CamUtils { CamUtils()51 private CamUtils() { 52 } 53 54 // Transforms XYZ color space coordinates to 'cone'/'RGB' responses in CAM16. 55 static final float[][] XYZ_TO_CAM16RGB = { 56 {0.401288f, 0.650173f, -0.051461f}, 57 {-0.250268f, 1.204414f, 0.045854f}, 58 {-0.002079f, 0.048952f, 0.953127f} 59 }; 60 61 // Transforms 'cone'/'RGB' responses in CAM16 to XYZ color space coordinates. 62 static final float[][] CAM16RGB_TO_XYZ = { 63 {1.86206786f, -1.01125463f, 0.14918677f}, 64 {0.38752654f, 0.62144744f, -0.00897398f}, 65 {-0.01584150f, -0.03412294f, 1.04996444f} 66 }; 67 68 // Need this, XYZ coordinates in internal ColorUtils are private 69 70 // sRGB specification has D65 whitepoint - Stokes, Anderson, Chandrasekar, Motta - A Standard 71 // Default Color Space for the Internet: sRGB, 1996 72 static final float[] WHITE_POINT_D65 = {95.047f, 100.0f, 108.883f}; 73 74 // This is a more precise sRGB to XYZ transformation matrix than traditionally 75 // used. It was derived using Schlomer's technique of transforming the xyY 76 // primaries to XYZ, then applying a correction to ensure mapping from sRGB 77 // 1, 1, 1 to the reference white point, D65. 78 static final double[][] SRGB_TO_XYZ = 79 new double[][] { 80 new double[] {0.41233895, 0.35762064, 0.18051042}, 81 new double[] {0.2126, 0.7152, 0.0722}, 82 new double[] {0.01932141, 0.11916382, 0.95034478}, 83 }; 84 85 static final double[][] XYZ_TO_SRGB = 86 new double[][] { 87 new double[] { 88 3.2413774792388685, -1.5376652402851851, -0.49885366846268053, 89 }, 90 new double[] { 91 -0.9691452513005321, 1.8758853451067872, 0.04156585616912061, 92 }, 93 new double[] { 94 0.05562093689691305, -0.20395524564742123, 1.0571799111220335, 95 }, 96 }; 97 98 /** 99 * The signum function. 100 * 101 * @return 1 if num > 0, -1 if num < 0, and 0 if num = 0 102 */ signum(double num)103 public static int signum(double num) { 104 if (num < 0) { 105 return -1; 106 } else if (num == 0) { 107 return 0; 108 } else { 109 return 1; 110 } 111 } 112 113 /** 114 * Converts an L* value to an ARGB representation. 115 * 116 * @param lstar L* in L*a*b* 117 * @return ARGB representation of grayscale color with lightness matching L* 118 */ argbFromLstar(double lstar)119 public static int argbFromLstar(double lstar) { 120 double fy = (lstar + 16.0) / 116.0; 121 double fz = fy; 122 double fx = fy; 123 double kappa = 24389.0 / 27.0; 124 double epsilon = 216.0 / 24389.0; 125 boolean lExceedsEpsilonKappa = lstar > 8.0; 126 double y = lExceedsEpsilonKappa ? fy * fy * fy : lstar / kappa; 127 boolean cubeExceedEpsilon = fy * fy * fy > epsilon; 128 double x = cubeExceedEpsilon ? fx * fx * fx : lstar / kappa; 129 double z = cubeExceedEpsilon ? fz * fz * fz : lstar / kappa; 130 float[] whitePoint = WHITE_POINT_D65; 131 return argbFromXyz(x * whitePoint[0], y * whitePoint[1], z * whitePoint[2]); 132 } 133 134 /** Converts a color from ARGB to XYZ. */ argbFromXyz(double x, double y, double z)135 public static int argbFromXyz(double x, double y, double z) { 136 double[][] matrix = XYZ_TO_SRGB; 137 double linearR = matrix[0][0] * x + matrix[0][1] * y + matrix[0][2] * z; 138 double linearG = matrix[1][0] * x + matrix[1][1] * y + matrix[1][2] * z; 139 double linearB = matrix[2][0] * x + matrix[2][1] * y + matrix[2][2] * z; 140 int r = delinearized(linearR); 141 int g = delinearized(linearG); 142 int b = delinearized(linearB); 143 return argbFromRgb(r, g, b); 144 } 145 146 /** Converts a color from linear RGB components to ARGB format. */ argbFromLinrgb(double[] linrgb)147 public static int argbFromLinrgb(double[] linrgb) { 148 int r = delinearized(linrgb[0]); 149 int g = delinearized(linrgb[1]); 150 int b = delinearized(linrgb[2]); 151 return argbFromRgb(r, g, b); 152 } 153 154 /** Converts a color from linear RGB components to ARGB format. */ argbFromLinrgbComponents(double r, double g, double b)155 public static int argbFromLinrgbComponents(double r, double g, double b) { 156 return argbFromRgb(delinearized(r), delinearized(g), delinearized(b)); 157 } 158 159 /** 160 * Delinearizes an RGB component. 161 * 162 * @param rgbComponent 0.0 <= rgb_component <= 100.0, represents linear R/G/B channel 163 * @return 0 <= output <= 255, color channel converted to regular RGB space 164 */ delinearized(double rgbComponent)165 public static int delinearized(double rgbComponent) { 166 double normalized = rgbComponent / 100.0; 167 double delinearized = 0.0; 168 if (normalized <= 0.0031308) { 169 delinearized = normalized * 12.92; 170 } else { 171 delinearized = 1.055 * Math.pow(normalized, 1.0 / 2.4) - 0.055; 172 } 173 return clampInt(0, 255, (int) Math.round(delinearized * 255.0)); 174 } 175 176 /** 177 * Clamps an integer between two integers. 178 * 179 * @return input when min <= input <= max, and either min or max otherwise. 180 */ clampInt(int min, int max, int input)181 public static int clampInt(int min, int max, int input) { 182 if (input < min) { 183 return min; 184 } else if (input > max) { 185 return max; 186 } 187 188 return input; 189 } 190 191 /** Converts a color from RGB components to ARGB format. */ argbFromRgb(int red, int green, int blue)192 public static int argbFromRgb(int red, int green, int blue) { 193 return (255 << 24) | ((red & 255) << 16) | ((green & 255) << 8) | (blue & 255); 194 } 195 intFromLstar(float lstar)196 static int intFromLstar(float lstar) { 197 if (lstar < 1) { 198 return 0xff000000; 199 } else if (lstar > 99) { 200 return 0xffffffff; 201 } 202 203 // XYZ to LAB conversion routine, assume a and b are 0. 204 float fy = (lstar + 16.0f) / 116.0f; 205 206 // fz = fx = fy because a and b are 0 207 float fz = fy; 208 float fx = fy; 209 210 float kappa = 24389f / 27f; 211 float epsilon = 216f / 24389f; 212 boolean lExceedsEpsilonKappa = (lstar > 8.0f); 213 float yT = lExceedsEpsilonKappa ? fy * fy * fy : lstar / kappa; 214 boolean cubeExceedEpsilon = (fy * fy * fy) > epsilon; 215 float xT = cubeExceedEpsilon ? fx * fx * fx : (116f * fx - 16f) / kappa; 216 float zT = cubeExceedEpsilon ? fz * fz * fz : (116f * fx - 16f) / kappa; 217 218 return ColorUtils.XYZToColor(xT * CamUtils.WHITE_POINT_D65[0], 219 yT * CamUtils.WHITE_POINT_D65[1], zT * CamUtils.WHITE_POINT_D65[2]); 220 } 221 222 /** Returns L* from L*a*b*, perceptual luminance, from an ARGB integer (ColorInt). */ lstarFromInt(int argb)223 public static float lstarFromInt(int argb) { 224 return lstarFromY(yFromInt(argb)); 225 } 226 lstarFromY(float y)227 static float lstarFromY(float y) { 228 y = y / 100.0f; 229 final float e = 216.f / 24389.f; 230 float yIntermediate; 231 if (y <= e) { 232 return ((24389.f / 27.f) * y); 233 } else { 234 yIntermediate = (float) Math.cbrt(y); 235 } 236 return 116.f * yIntermediate - 16.f; 237 } 238 yFromInt(int argb)239 static float yFromInt(int argb) { 240 final float r = linearized(Color.red(argb)); 241 final float g = linearized(Color.green(argb)); 242 final float b = linearized(Color.blue(argb)); 243 double[][] matrix = SRGB_TO_XYZ; 244 double y = (r * matrix[1][0]) + (g * matrix[1][1]) + (b * matrix[1][2]); 245 return (float) y; 246 } 247 248 @NonNull xyzFromInt(int argb)249 static float[] xyzFromInt(int argb) { 250 final float r = linearized(Color.red(argb)); 251 final float g = linearized(Color.green(argb)); 252 final float b = linearized(Color.blue(argb)); 253 254 double[][] matrix = SRGB_TO_XYZ; 255 double x = (r * matrix[0][0]) + (g * matrix[0][1]) + (b * matrix[0][2]); 256 double y = (r * matrix[1][0]) + (g * matrix[1][1]) + (b * matrix[1][2]); 257 double z = (r * matrix[2][0]) + (g * matrix[2][1]) + (b * matrix[2][2]); 258 return new float[]{(float) x, (float) y, (float) z}; 259 } 260 261 /** 262 * Converts an L* value to a Y value. 263 * 264 * <p>L* in L*a*b* and Y in XYZ measure the same quantity, luminance. 265 * 266 * <p>L* measures perceptual luminance, a linear scale. Y in XYZ measures relative luminance, a 267 * logarithmic scale. 268 * 269 * @param lstar L* in L*a*b* 270 * @return Y in XYZ 271 */ yFromLstar(double lstar)272 public static double yFromLstar(double lstar) { 273 double ke = 8.0; 274 if (lstar > ke) { 275 return Math.pow((lstar + 16.0) / 116.0, 3.0) * 100.0; 276 } else { 277 return lstar / (24389.0 / 27.0) * 100.0; 278 } 279 } 280 linearized(int rgbComponent)281 static float linearized(int rgbComponent) { 282 float normalized = (float) rgbComponent / 255.0f; 283 284 if (normalized <= 0.04045f) { 285 return (normalized / 12.92f) * 100.0f; 286 } else { 287 return (float) Math.pow(((normalized + 0.055f) / 1.055f), 2.4f) * 100.0f; 288 } 289 } 290 } 291