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 android.graphics.cts.utils; 18 19 import android.graphics.Color; 20 21 /** 22 * Copied from frameworks/base/core to support SystemPalette CTS test 23 * 24 * Collection of methods for transforming between color spaces. 25 * 26 * <p>Methods are named $xFrom$Y. For example, lstarFromInt() returns L* from an ARGB integer. 27 * 28 * <p>These methods, generally, convert colors between the L*a*b*, XYZ, and sRGB spaces. 29 * 30 * <p>L*a*b* is a perceptually accurate color space. This is particularly important in the L* 31 * dimension: it measures luminance and unlike lightness measures traditionally used in UI work via 32 * RGB or HSL, this luminance transitions smoothly, permitting creation of pleasing shades of a 33 * color, and more pleasing transitions between colors. 34 * 35 * <p>XYZ is commonly used as an intermediate color space for converting between one color space to 36 * another. For example, to convert RGB to L*a*b*, first RGB is converted to XYZ, then XYZ is 37 * convered to L*a*b*. 38 * 39 * <p>sRGB is a "specification originated from work in 1990s through cooperation by Hewlett-Packard 40 * and Microsoft, and it was designed to be a standard definition of RGB for the internet, which it 41 * indeed became...The standard is based on a sampling of computer monitors at the time...The whole 42 * idea of sRGB is that if everyone assumed that RGB meant the same thing, then the results would be 43 * consistent, and reasonably good. It worked." - Fairchild, Color Models and Systems: Handbook of 44 * Color Psychology, 2015 45 */ 46 public final class CamUtils { CamUtils()47 private CamUtils() { 48 } 49 50 // Transforms XYZ color space coordinates to 'cone'/'RGB' responses in CAM16. 51 static final float[][] XYZ_TO_CAM16RGB = { 52 {0.401288f, 0.650173f, -0.051461f}, 53 {-0.250268f, 1.204414f, 0.045854f}, 54 {-0.002079f, 0.048952f, 0.953127f} 55 }; 56 57 // Transforms 'cone'/'RGB' responses in CAM16 to XYZ color space coordinates. 58 static final float[][] CAM16RGB_TO_XYZ = { 59 {1.86206786f, -1.01125463f, 0.14918677f}, 60 {0.38752654f, 0.62144744f, -0.00897398f}, 61 {-0.01584150f, -0.03412294f, 1.04996444f} 62 }; 63 64 // Need this, XYZ coordinates in internal ColorUtils are private 65 66 // sRGB specification has D65 whitepoint - Stokes, Anderson, Chandrasekar, Motta - A Standard 67 // Default Color Space for the Internet: sRGB, 1996 68 static final float[] WHITE_POINT_D65 = {95.047f, 100.0f, 108.883f}; 69 70 // This is a more precise sRGB to XYZ transformation matrix than traditionally 71 // used. It was derived using Schlomer's technique of transforming the xyY 72 // primaries to XYZ, then applying a correction to ensure mapping from sRGB 73 // 1, 1, 1 to the reference white point, D65. 74 static final float[][] SRGB_TO_XYZ = { 75 {0.41233895f, 0.35762064f, 0.18051042f}, 76 {0.2126f, 0.7152f, 0.0722f}, 77 {0.01932141f, 0.11916382f, 0.95034478f} 78 }; 79 intFromLstar(float lstar)80 static int intFromLstar(float lstar) { 81 if (lstar < 1) { 82 return 0xff000000; 83 } else if (lstar > 99) { 84 return 0xffffffff; 85 } 86 87 // XYZ to LAB conversion routine, assume a and b are 0. 88 float fy = (lstar + 16.0f) / 116.0f; 89 90 // fz = fx = fy because a and b are 0 91 float fz = fy; 92 float fx = fy; 93 94 float kappa = 24389f / 27f; 95 float epsilon = 216f / 24389f; 96 boolean lExceedsEpsilonKappa = (lstar > 8.0f); 97 float yT = lExceedsEpsilonKappa ? fy * fy * fy : lstar / kappa; 98 boolean cubeExceedEpsilon = (fy * fy * fy) > epsilon; 99 float xT = cubeExceedEpsilon ? fx * fx * fx : (116f * fx - 16f) / kappa; 100 float zT = cubeExceedEpsilon ? fz * fz * fz : (116f * fx - 16f) / kappa; 101 102 return ColorUtils.xyzToColor(xT * CamUtils.WHITE_POINT_D65[0], 103 yT * CamUtils.WHITE_POINT_D65[1], zT * CamUtils.WHITE_POINT_D65[2]); 104 } 105 106 /** Returns L* from L*a*b*, perceptual luminance, from an ARGB integer (ColorInt). */ lstarFromInt(int argb)107 public static float lstarFromInt(int argb) { 108 return lstarFromY(yFromInt(argb)); 109 } 110 lstarFromY(float y)111 static float lstarFromY(float y) { 112 y = y / 100.0f; 113 final float e = 216.f / 24389.f; 114 float yIntermediate; 115 if (y <= e) { 116 return ((24389.f / 27.f) * y); 117 } else { 118 yIntermediate = (float) Math.cbrt(y); 119 } 120 return 116.f * yIntermediate - 16.f; 121 } 122 yFromInt(int argb)123 static float yFromInt(int argb) { 124 final float r = linearized(Color.red(argb)); 125 final float g = linearized(Color.green(argb)); 126 final float b = linearized(Color.blue(argb)); 127 float[][] matrix = SRGB_TO_XYZ; 128 float y = (r * matrix[1][0]) + (g * matrix[1][1]) + (b * matrix[1][2]); 129 return y; 130 } 131 xyzFromInt(int argb)132 static float[] xyzFromInt(int argb) { 133 final float r = linearized(Color.red(argb)); 134 final float g = linearized(Color.green(argb)); 135 final float b = linearized(Color.blue(argb)); 136 137 float[][] matrix = SRGB_TO_XYZ; 138 float x = (r * matrix[0][0]) + (g * matrix[0][1]) + (b * matrix[0][2]); 139 float y = (r * matrix[1][0]) + (g * matrix[1][1]) + (b * matrix[1][2]); 140 float z = (r * matrix[2][0]) + (g * matrix[2][1]) + (b * matrix[2][2]); 141 return new float[]{x, y, z}; 142 } 143 yFromLstar(float lstar)144 static float yFromLstar(float lstar) { 145 float ke = 8.0f; 146 if (lstar > ke) { 147 return (float) Math.pow(((lstar + 16.0) / 116.0), 3) * 100f; 148 } else { 149 return lstar / (24389f / 27f) * 100f; 150 } 151 } 152 linearized(int rgbComponent)153 static float linearized(int rgbComponent) { 154 float normalized = (float) rgbComponent / 255.0f; 155 156 if (normalized <= 0.04045f) { 157 return (normalized / 12.92f) * 100.0f; 158 } else { 159 return (float) Math.pow(((normalized + 0.055f) / 1.055f), 2.4f) * 100.0f; 160 } 161 } 162 } 163