• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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