1 /* 2 * Copyright (C) 2016 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; 18 19 import android.annotation.AnyThread; 20 import android.annotation.ColorInt; 21 import android.annotation.IntRange; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.Size; 25 import android.annotation.SuppressAutoDoc; 26 import android.util.Pair; 27 28 import libcore.util.NativeAllocationRegistry; 29 30 import java.util.ArrayList; 31 import java.util.Arrays; 32 import java.util.List; 33 import java.util.function.DoubleUnaryOperator; 34 35 /** 36 * {@usesMathJax} 37 * 38 * <p>A {@link ColorSpace} is used to identify a specific organization of colors. 39 * Each color space is characterized by a {@link Model color model} that defines 40 * how a color value is represented (for instance the {@link Model#RGB RGB} color 41 * model defines a color value as a triplet of numbers).</p> 42 * 43 * <p>Each component of a color must fall within a valid range, specific to each 44 * color space, defined by {@link #getMinValue(int)} and {@link #getMaxValue(int)} 45 * This range is commonly \([0..1]\). While it is recommended to use values in the 46 * valid range, a color space always clamps input and output values when performing 47 * operations such as converting to a different color space.</p> 48 * 49 * <h3>Using color spaces</h3> 50 * 51 * <p>This implementation provides a pre-defined set of common color spaces 52 * described in the {@link Named} enum. To obtain an instance of one of the 53 * pre-defined color spaces, simply invoke {@link #get(Named)}:</p> 54 * 55 * <pre class="prettyprint"> 56 * ColorSpace sRgb = ColorSpace.get(ColorSpace.Named.SRGB); 57 * </pre> 58 * 59 * <p>The {@link #get(Named)} method always returns the same instance for a given 60 * name. Color spaces with an {@link Model#RGB RGB} color model can be safely 61 * cast to {@link Rgb}. Doing so gives you access to more APIs to query various 62 * properties of RGB color models: color gamut primaries, transfer functions, 63 * conversions to and from linear space, etc. Please refer to {@link Rgb} for 64 * more information.</p> 65 * 66 * <p>The documentation of {@link Named} provides a detailed description of the 67 * various characteristics of each available color space.</p> 68 * 69 * <h3>Color space conversions</h3> 70 71 * <p>To allow conversion between color spaces, this implementation uses the CIE 72 * XYZ profile connection space (PCS). Color values can be converted to and from 73 * this PCS using {@link #toXyz(float[])} and {@link #fromXyz(float[])}.</p> 74 * 75 * <p>For color space with a non-RGB color model, the white point of the PCS 76 * <em>must be</em> the CIE standard illuminant D50. RGB color spaces use their 77 * native white point (D65 for {@link Named#SRGB sRGB} for instance and must 78 * undergo {@link Adaptation chromatic adaptation} as necessary.</p> 79 * 80 * <p>Since the white point of the PCS is not defined for RGB color space, it is 81 * highly recommended to use the variants of the {@link #connect(ColorSpace, ColorSpace)} 82 * method to perform conversions between color spaces. A color space can be 83 * manually adapted to a specific white point using {@link #adapt(ColorSpace, float[])}. 84 * Please refer to the documentation of {@link Rgb RGB color spaces} for more 85 * information. Several common CIE standard illuminants are provided in this 86 * class as reference (see {@link #ILLUMINANT_D65} or {@link #ILLUMINANT_D50} 87 * for instance).</p> 88 * 89 * <p>Here is an example of how to convert from a color space to another:</p> 90 * 91 * <pre class="prettyprint"> 92 * // Convert from DCI-P3 to Rec.2020 93 * ColorSpace.Connector connector = ColorSpace.connect( 94 * ColorSpace.get(ColorSpace.Named.DCI_P3), 95 * ColorSpace.get(ColorSpace.Named.BT2020)); 96 * 97 * float[] bt2020 = connector.transform(p3r, p3g, p3b); 98 * </pre> 99 * 100 * <p>You can easily convert to {@link Named#SRGB sRGB} by omitting the second 101 * parameter:</p> 102 * 103 * <pre class="prettyprint"> 104 * // Convert from DCI-P3 to sRGB 105 * ColorSpace.Connector connector = ColorSpace.connect(ColorSpace.get(ColorSpace.Named.DCI_P3)); 106 * 107 * float[] sRGB = connector.transform(p3r, p3g, p3b); 108 * </pre> 109 * 110 * <p>Conversions also work between color spaces with different color models:</p> 111 * 112 * <pre class="prettyprint"> 113 * // Convert from CIE L*a*b* (color model Lab) to Rec.709 (color model RGB) 114 * ColorSpace.Connector connector = ColorSpace.connect( 115 * ColorSpace.get(ColorSpace.Named.CIE_LAB), 116 * ColorSpace.get(ColorSpace.Named.BT709)); 117 * </pre> 118 * 119 * <h3>Color spaces and multi-threading</h3> 120 * 121 * <p>Color spaces and other related classes ({@link Connector} for instance) 122 * are immutable and stateless. They can be safely used from multiple concurrent 123 * threads.</p> 124 * 125 * <p>Public static methods provided by this class, such as {@link #get(Named)} 126 * and {@link #connect(ColorSpace, ColorSpace)}, are also guaranteed to be 127 * thread-safe.</p> 128 * 129 * @see #get(Named) 130 * @see Named 131 * @see Model 132 * @see Connector 133 * @see Adaptation 134 */ 135 @AnyThread 136 @SuppressWarnings("StaticInitializerReferencesSubClass") 137 @SuppressAutoDoc 138 public abstract class ColorSpace { 139 /** 140 * Standard CIE 1931 2° illuminant A, encoded in xyY. 141 * This illuminant has a color temperature of 2856K. 142 */ 143 public static final float[] ILLUMINANT_A = { 0.44757f, 0.40745f }; 144 /** 145 * Standard CIE 1931 2° illuminant B, encoded in xyY. 146 * This illuminant has a color temperature of 4874K. 147 */ 148 public static final float[] ILLUMINANT_B = { 0.34842f, 0.35161f }; 149 /** 150 * Standard CIE 1931 2° illuminant C, encoded in xyY. 151 * This illuminant has a color temperature of 6774K. 152 */ 153 public static final float[] ILLUMINANT_C = { 0.31006f, 0.31616f }; 154 /** 155 * Standard CIE 1931 2° illuminant D50, encoded in xyY. 156 * This illuminant has a color temperature of 5003K. This illuminant 157 * is used by the profile connection space in ICC profiles. 158 */ 159 public static final float[] ILLUMINANT_D50 = { 0.34567f, 0.35850f }; 160 /** 161 * Standard CIE 1931 2° illuminant D55, encoded in xyY. 162 * This illuminant has a color temperature of 5503K. 163 */ 164 public static final float[] ILLUMINANT_D55 = { 0.33242f, 0.34743f }; 165 /** 166 * Standard CIE 1931 2° illuminant D60, encoded in xyY. 167 * This illuminant has a color temperature of 6004K. 168 */ 169 public static final float[] ILLUMINANT_D60 = { 0.32168f, 0.33767f }; 170 /** 171 * Standard CIE 1931 2° illuminant D65, encoded in xyY. 172 * This illuminant has a color temperature of 6504K. This illuminant 173 * is commonly used in RGB color spaces such as sRGB, BT.209, etc. 174 */ 175 public static final float[] ILLUMINANT_D65 = { 0.31271f, 0.32902f }; 176 /** 177 * Standard CIE 1931 2° illuminant D75, encoded in xyY. 178 * This illuminant has a color temperature of 7504K. 179 */ 180 public static final float[] ILLUMINANT_D75 = { 0.29902f, 0.31485f }; 181 /** 182 * Standard CIE 1931 2° illuminant E, encoded in xyY. 183 * This illuminant has a color temperature of 5454K. 184 */ 185 public static final float[] ILLUMINANT_E = { 0.33333f, 0.33333f }; 186 187 /** 188 * The minimum ID value a color space can have. 189 * 190 * @see #getId() 191 */ 192 public static final int MIN_ID = -1; // Do not change 193 /** 194 * The maximum ID value a color space can have. 195 * 196 * @see #getId() 197 */ 198 public static final int MAX_ID = 63; // Do not change, used to encode in longs 199 200 private static final float[] SRGB_PRIMARIES = { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f }; 201 private static final float[] NTSC_1953_PRIMARIES = { 0.67f, 0.33f, 0.21f, 0.71f, 0.14f, 0.08f }; 202 /** 203 * A gray color space does not have meaningful primaries, so we use this arbitrary set. 204 */ 205 private static final float[] GRAY_PRIMARIES = { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }; 206 207 private static final float[] ILLUMINANT_D50_XYZ = { 0.964212f, 1.0f, 0.825188f }; 208 209 private static final Rgb.TransferParameters SRGB_TRANSFER_PARAMETERS = 210 new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4); 211 212 // See static initialization block next to #get(Named) 213 private static final ColorSpace[] sNamedColorSpaces = new ColorSpace[Named.values().length]; 214 215 @NonNull private final String mName; 216 @NonNull private final Model mModel; 217 @IntRange(from = MIN_ID, to = MAX_ID) private final int mId; 218 219 /** 220 * {@usesMathJax} 221 * 222 * <p>List of common, named color spaces. A corresponding instance of 223 * {@link ColorSpace} can be obtained by calling {@link ColorSpace#get(Named)}:</p> 224 * 225 * <pre class="prettyprint"> 226 * ColorSpace cs = ColorSpace.get(ColorSpace.Named.DCI_P3); 227 * </pre> 228 * 229 * <p>The properties of each color space are described below (see {@link #SRGB sRGB} 230 * for instance). When applicable, the color gamut of each color space is compared 231 * to the color gamut of sRGB using a CIE 1931 xy chromaticity diagram. This diagram 232 * shows the location of the color space's primaries and white point.</p> 233 * 234 * @see ColorSpace#get(Named) 235 */ 236 public enum Named { 237 // NOTE: Do NOT change the order of the enum 238 /** 239 * <p>{@link ColorSpace.Rgb RGB} color space sRGB standardized as IEC 61966-2.1:1999.</p> 240 * <table summary="Color space definition"> 241 * <tr> 242 * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> 243 * </tr> 244 * <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr> 245 * <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr> 246 * <tr><th>Property</th><th colspan="4">Value</th></tr> 247 * <tr><td>Name</td><td colspan="4">sRGB IEC61966-2.1</td></tr> 248 * <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr> 249 * <tr> 250 * <td>Opto-electronic transfer function (OETF)</td> 251 * <td colspan="4">\(\begin{equation} 252 * C_{sRGB} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \lt 0.0031308 \\\ 253 * 1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \ge 0.0031308 \end{cases} 254 * \end{equation}\) 255 * </td> 256 * </tr> 257 * <tr> 258 * <td>Electro-optical transfer function (EOTF)</td> 259 * <td colspan="4">\(\begin{equation} 260 * C_{linear} = \begin{cases}\frac{C_{sRGB}}{12.92} & C_{sRGB} \lt 0.04045 \\\ 261 * \left( \frac{C_{sRGB} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \ge 0.04045 \end{cases} 262 * \end{equation}\) 263 * </td> 264 * </tr> 265 * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> 266 * </table> 267 * <p> 268 * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_srgb.png" /> 269 * <figcaption style="text-align: center;">sRGB</figcaption> 270 * </p> 271 */ 272 SRGB, 273 /** 274 * <p>{@link ColorSpace.Rgb RGB} color space sRGB standardized as IEC 61966-2.1:1999.</p> 275 * <table summary="Color space definition"> 276 * <tr> 277 * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> 278 * </tr> 279 * <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr> 280 * <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr> 281 * <tr><th>Property</th><th colspan="4">Value</th></tr> 282 * <tr><td>Name</td><td colspan="4">sRGB IEC61966-2.1 (Linear)</td></tr> 283 * <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr> 284 * <tr> 285 * <td>Opto-electronic transfer function (OETF)</td> 286 * <td colspan="4">\(C_{sRGB} = C_{linear}\)</td> 287 * </tr> 288 * <tr> 289 * <td>Electro-optical transfer function (EOTF)</td> 290 * <td colspan="4">\(C_{linear} = C_{sRGB}\)</td> 291 * </tr> 292 * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> 293 * </table> 294 * <p> 295 * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_srgb.png" /> 296 * <figcaption style="text-align: center;">sRGB</figcaption> 297 * </p> 298 */ 299 LINEAR_SRGB, 300 /** 301 * <p>{@link ColorSpace.Rgb RGB} color space scRGB-nl standardized as IEC 61966-2-2:2003.</p> 302 * <table summary="Color space definition"> 303 * <tr> 304 * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> 305 * </tr> 306 * <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr> 307 * <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr> 308 * <tr><th>Property</th><th colspan="4">Value</th></tr> 309 * <tr><td>Name</td><td colspan="4">scRGB-nl IEC 61966-2-2:2003</td></tr> 310 * <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr> 311 * <tr> 312 * <td>Opto-electronic transfer function (OETF)</td> 313 * <td colspan="4">\(\begin{equation} 314 * C_{scRGB} = \begin{cases} sign(C_{linear}) 12.92 \times \left| C_{linear} \right| & 315 * \left| C_{linear} \right| \lt 0.0031308 \\\ 316 * sign(C_{linear}) 1.055 \times \left| C_{linear} \right| ^{\frac{1}{2.4}} - 0.055 & 317 * \left| C_{linear} \right| \ge 0.0031308 \end{cases} 318 * \end{equation}\) 319 * </td> 320 * </tr> 321 * <tr> 322 * <td>Electro-optical transfer function (EOTF)</td> 323 * <td colspan="4">\(\begin{equation} 324 * C_{linear} = \begin{cases}sign(C_{scRGB}) \frac{\left| C_{scRGB} \right|}{12.92} & 325 * \left| C_{scRGB} \right| \lt 0.04045 \\\ 326 * sign(C_{scRGB}) \left( \frac{\left| C_{scRGB} \right| + 0.055}{1.055} \right) ^{2.4} & 327 * \left| C_{scRGB} \right| \ge 0.04045 \end{cases} 328 * \end{equation}\) 329 * </td> 330 * </tr> 331 * <tr><td>Range</td><td colspan="4">\([-0.799..2.399[\)</td></tr> 332 * </table> 333 * <p> 334 * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_scrgb.png" /> 335 * <figcaption style="text-align: center;">Extended sRGB (orange) vs sRGB (white)</figcaption> 336 * </p> 337 */ 338 EXTENDED_SRGB, 339 /** 340 * <p>{@link ColorSpace.Rgb RGB} color space scRGB standardized as IEC 61966-2-2:2003.</p> 341 * <table summary="Color space definition"> 342 * <tr> 343 * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> 344 * </tr> 345 * <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr> 346 * <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr> 347 * <tr><th>Property</th><th colspan="4">Value</th></tr> 348 * <tr><td>Name</td><td colspan="4">scRGB IEC 61966-2-2:2003</td></tr> 349 * <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr> 350 * <tr> 351 * <td>Opto-electronic transfer function (OETF)</td> 352 * <td colspan="4">\(C_{scRGB} = C_{linear}\)</td> 353 * </tr> 354 * <tr> 355 * <td>Electro-optical transfer function (EOTF)</td> 356 * <td colspan="4">\(C_{linear} = C_{scRGB}\)</td> 357 * </tr> 358 * <tr><td>Range</td><td colspan="4">\([-0.5..7.499[\)</td></tr> 359 * </table> 360 * <p> 361 * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_scrgb.png" /> 362 * <figcaption style="text-align: center;">Extended sRGB (orange) vs sRGB (white)</figcaption> 363 * </p> 364 */ 365 LINEAR_EXTENDED_SRGB, 366 /** 367 * <p>{@link ColorSpace.Rgb RGB} color space BT.709 standardized as Rec. ITU-R BT.709-5.</p> 368 * <table summary="Color space definition"> 369 * <tr> 370 * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> 371 * </tr> 372 * <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr> 373 * <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr> 374 * <tr><th>Property</th><th colspan="4">Value</th></tr> 375 * <tr><td>Name</td><td colspan="4">Rec. ITU-R BT.709-5</td></tr> 376 * <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr> 377 * <tr> 378 * <td>Opto-electronic transfer function (OETF)</td> 379 * <td colspan="4">\(\begin{equation} 380 * C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\\ 381 * 1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases} 382 * \end{equation}\) 383 * </td> 384 * </tr> 385 * <tr> 386 * <td>Electro-optical transfer function (EOTF)</td> 387 * <td colspan="4">\(\begin{equation} 388 * C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\\ 389 * \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases} 390 * \end{equation}\) 391 * </td> 392 * </tr> 393 * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> 394 * </table> 395 * <p> 396 * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_bt709.png" /> 397 * <figcaption style="text-align: center;">BT.709</figcaption> 398 * </p> 399 */ 400 BT709, 401 /** 402 * <p>{@link ColorSpace.Rgb RGB} color space BT.2020 standardized as Rec. ITU-R BT.2020-1.</p> 403 * <table summary="Color space definition"> 404 * <tr> 405 * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> 406 * </tr> 407 * <tr><td>x</td><td>0.708</td><td>0.170</td><td>0.131</td><td>0.3127</td></tr> 408 * <tr><td>y</td><td>0.292</td><td>0.797</td><td>0.046</td><td>0.3290</td></tr> 409 * <tr><th>Property</th><th colspan="4">Value</th></tr> 410 * <tr><td>Name</td><td colspan="4">Rec. ITU-R BT.2020-1</td></tr> 411 * <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr> 412 * <tr> 413 * <td>Opto-electronic transfer function (OETF)</td> 414 * <td colspan="4">\(\begin{equation} 415 * C_{BT2020} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.0181 \\\ 416 * 1.0993 \times C_{linear}^{\frac{1}{2.2}} - 0.0993 & C_{linear} \ge 0.0181 \end{cases} 417 * \end{equation}\) 418 * </td> 419 * </tr> 420 * <tr> 421 * <td>Electro-optical transfer function (EOTF)</td> 422 * <td colspan="4">\(\begin{equation} 423 * C_{linear} = \begin{cases}\frac{C_{BT2020}}{4.5} & C_{BT2020} \lt 0.08145 \\\ 424 * \left( \frac{C_{BT2020} + 0.0993}{1.0993} \right) ^{2.2} & C_{BT2020} \ge 0.08145 \end{cases} 425 * \end{equation}\) 426 * </td> 427 * </tr> 428 * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> 429 * </table> 430 * <p> 431 * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_bt2020.png" /> 432 * <figcaption style="text-align: center;">BT.2020 (orange) vs sRGB (white)</figcaption> 433 * </p> 434 */ 435 BT2020, 436 /** 437 * <p>{@link ColorSpace.Rgb RGB} color space DCI-P3 standardized as SMPTE RP 431-2-2007.</p> 438 * <table summary="Color space definition"> 439 * <tr> 440 * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> 441 * </tr> 442 * <tr><td>x</td><td>0.680</td><td>0.265</td><td>0.150</td><td>0.314</td></tr> 443 * <tr><td>y</td><td>0.320</td><td>0.690</td><td>0.060</td><td>0.351</td></tr> 444 * <tr><th>Property</th><th colspan="4">Value</th></tr> 445 * <tr><td>Name</td><td colspan="4">SMPTE RP 431-2-2007 DCI (P3)</td></tr> 446 * <tr><td>CIE standard illuminant</td><td colspan="4">N/A</td></tr> 447 * <tr> 448 * <td>Opto-electronic transfer function (OETF)</td> 449 * <td colspan="4">\(C_{P3} = C_{linear}^{\frac{1}{2.6}}\)</td> 450 * </tr> 451 * <tr> 452 * <td>Electro-optical transfer function (EOTF)</td> 453 * <td colspan="4">\(C_{linear} = C_{P3}^{2.6}\)</td> 454 * </tr> 455 * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> 456 * </table> 457 * <p> 458 * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_dci_p3.png" /> 459 * <figcaption style="text-align: center;">DCI-P3 (orange) vs sRGB (white)</figcaption> 460 * </p> 461 */ 462 DCI_P3, 463 /** 464 * <p>{@link ColorSpace.Rgb RGB} color space Display P3 based on SMPTE RP 431-2-2007 and IEC 61966-2.1:1999.</p> 465 * <table summary="Color space definition"> 466 * <tr> 467 * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> 468 * </tr> 469 * <tr><td>x</td><td>0.680</td><td>0.265</td><td>0.150</td><td>0.3127</td></tr> 470 * <tr><td>y</td><td>0.320</td><td>0.690</td><td>0.060</td><td>0.3290</td></tr> 471 * <tr><th>Property</th><th colspan="4">Value</th></tr> 472 * <tr><td>Name</td><td colspan="4">Display P3</td></tr> 473 * <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr> 474 * <tr> 475 * <td>Opto-electronic transfer function (OETF)</td> 476 * <td colspan="4">\(\begin{equation} 477 * C_{DisplayP3} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \lt 0.0030186 \\\ 478 * 1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \ge 0.0030186 \end{cases} 479 * \end{equation}\) 480 * </td> 481 * </tr> 482 * <tr> 483 * <td>Electro-optical transfer function (EOTF)</td> 484 * <td colspan="4">\(\begin{equation} 485 * C_{linear} = \begin{cases}\frac{C_{DisplayP3}}{12.92} & C_{sRGB} \lt 0.04045 \\\ 486 * \left( \frac{C_{DisplayP3} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \ge 0.04045 \end{cases} 487 * \end{equation}\) 488 * </td> 489 * </tr> 490 * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> 491 * </table> 492 * <p> 493 * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_display_p3.png" /> 494 * <figcaption style="text-align: center;">Display P3 (orange) vs sRGB (white)</figcaption> 495 * </p> 496 */ 497 DISPLAY_P3, 498 /** 499 * <p>{@link ColorSpace.Rgb RGB} color space NTSC, 1953 standard.</p> 500 * <table summary="Color space definition"> 501 * <tr> 502 * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> 503 * </tr> 504 * <tr><td>x</td><td>0.67</td><td>0.21</td><td>0.14</td><td>0.310</td></tr> 505 * <tr><td>y</td><td>0.33</td><td>0.71</td><td>0.08</td><td>0.316</td></tr> 506 * <tr><th>Property</th><th colspan="4">Value</th></tr> 507 * <tr><td>Name</td><td colspan="4">NTSC (1953)</td></tr> 508 * <tr><td>CIE standard illuminant</td><td colspan="4">C</td></tr> 509 * <tr> 510 * <td>Opto-electronic transfer function (OETF)</td> 511 * <td colspan="4">\(\begin{equation} 512 * C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\\ 513 * 1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases} 514 * \end{equation}\) 515 * </td> 516 * </tr> 517 * <tr> 518 * <td>Electro-optical transfer function (EOTF)</td> 519 * <td colspan="4">\(\begin{equation} 520 * C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\\ 521 * \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases} 522 * \end{equation}\) 523 * </td> 524 * </tr> 525 * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> 526 * </table> 527 * <p> 528 * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_ntsc_1953.png" /> 529 * <figcaption style="text-align: center;">NTSC 1953 (orange) vs sRGB (white)</figcaption> 530 * </p> 531 */ 532 NTSC_1953, 533 /** 534 * <p>{@link ColorSpace.Rgb RGB} color space SMPTE C.</p> 535 * <table summary="Color space definition"> 536 * <tr> 537 * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> 538 * </tr> 539 * <tr><td>x</td><td>0.630</td><td>0.310</td><td>0.155</td><td>0.3127</td></tr> 540 * <tr><td>y</td><td>0.340</td><td>0.595</td><td>0.070</td><td>0.3290</td></tr> 541 * <tr><th>Property</th><th colspan="4">Value</th></tr> 542 * <tr><td>Name</td><td colspan="4">SMPTE-C RGB</td></tr> 543 * <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr> 544 * <tr> 545 * <td>Opto-electronic transfer function (OETF)</td> 546 * <td colspan="4">\(\begin{equation} 547 * C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\\ 548 * 1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases} 549 * \end{equation}\) 550 * </td> 551 * </tr> 552 * <tr> 553 * <td>Electro-optical transfer function (EOTF)</td> 554 * <td colspan="4">\(\begin{equation} 555 * C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\\ 556 * \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases} 557 * \end{equation}\) 558 * </td> 559 * </tr> 560 * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> 561 * </table> 562 * <p> 563 * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_smpte_c.png" /> 564 * <figcaption style="text-align: center;">SMPTE-C (orange) vs sRGB (white)</figcaption> 565 * </p> 566 */ 567 SMPTE_C, 568 /** 569 * <p>{@link ColorSpace.Rgb RGB} color space Adobe RGB (1998).</p> 570 * <table summary="Color space definition"> 571 * <tr> 572 * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> 573 * </tr> 574 * <tr><td>x</td><td>0.64</td><td>0.21</td><td>0.15</td><td>0.3127</td></tr> 575 * <tr><td>y</td><td>0.33</td><td>0.71</td><td>0.06</td><td>0.3290</td></tr> 576 * <tr><th>Property</th><th colspan="4">Value</th></tr> 577 * <tr><td>Name</td><td colspan="4">Adobe RGB (1998)</td></tr> 578 * <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr> 579 * <tr> 580 * <td>Opto-electronic transfer function (OETF)</td> 581 * <td colspan="4">\(C_{RGB} = C_{linear}^{\frac{1}{2.2}}\)</td> 582 * </tr> 583 * <tr> 584 * <td>Electro-optical transfer function (EOTF)</td> 585 * <td colspan="4">\(C_{linear} = C_{RGB}^{2.2}\)</td> 586 * </tr> 587 * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> 588 * </table> 589 * <p> 590 * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_adobe_rgb.png" /> 591 * <figcaption style="text-align: center;">Adobe RGB (orange) vs sRGB (white)</figcaption> 592 * </p> 593 */ 594 ADOBE_RGB, 595 /** 596 * <p>{@link ColorSpace.Rgb RGB} color space ProPhoto RGB standardized as ROMM RGB ISO 22028-2:2013.</p> 597 * <table summary="Color space definition"> 598 * <tr> 599 * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> 600 * </tr> 601 * <tr><td>x</td><td>0.7347</td><td>0.1596</td><td>0.0366</td><td>0.3457</td></tr> 602 * <tr><td>y</td><td>0.2653</td><td>0.8404</td><td>0.0001</td><td>0.3585</td></tr> 603 * <tr><th>Property</th><th colspan="4">Value</th></tr> 604 * <tr><td>Name</td><td colspan="4">ROMM RGB ISO 22028-2:2013</td></tr> 605 * <tr><td>CIE standard illuminant</td><td colspan="4">D50</td></tr> 606 * <tr> 607 * <td>Opto-electronic transfer function (OETF)</td> 608 * <td colspan="4">\(\begin{equation} 609 * C_{ROMM} = \begin{cases} 16 \times C_{linear} & C_{linear} \lt 0.001953 \\\ 610 * C_{linear}^{\frac{1}{1.8}} & C_{linear} \ge 0.001953 \end{cases} 611 * \end{equation}\) 612 * </td> 613 * </tr> 614 * <tr> 615 * <td>Electro-optical transfer function (EOTF)</td> 616 * <td colspan="4">\(\begin{equation} 617 * C_{linear} = \begin{cases}\frac{C_{ROMM}}{16} & C_{ROMM} \lt 0.031248 \\\ 618 * C_{ROMM}^{1.8} & C_{ROMM} \ge 0.031248 \end{cases} 619 * \end{equation}\) 620 * </td> 621 * </tr> 622 * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> 623 * </table> 624 * <p> 625 * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_pro_photo_rgb.png" /> 626 * <figcaption style="text-align: center;">ProPhoto RGB (orange) vs sRGB (white)</figcaption> 627 * </p> 628 */ 629 PRO_PHOTO_RGB, 630 /** 631 * <p>{@link ColorSpace.Rgb RGB} color space ACES standardized as SMPTE ST 2065-1:2012.</p> 632 * <table summary="Color space definition"> 633 * <tr> 634 * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> 635 * </tr> 636 * <tr><td>x</td><td>0.73470</td><td>0.00000</td><td>0.00010</td><td>0.32168</td></tr> 637 * <tr><td>y</td><td>0.26530</td><td>1.00000</td><td>-0.07700</td><td>0.33767</td></tr> 638 * <tr><th>Property</th><th colspan="4">Value</th></tr> 639 * <tr><td>Name</td><td colspan="4">SMPTE ST 2065-1:2012 ACES</td></tr> 640 * <tr><td>CIE standard illuminant</td><td colspan="4">D60</td></tr> 641 * <tr> 642 * <td>Opto-electronic transfer function (OETF)</td> 643 * <td colspan="4">\(C_{ACES} = C_{linear}\)</td> 644 * </tr> 645 * <tr> 646 * <td>Electro-optical transfer function (EOTF)</td> 647 * <td colspan="4">\(C_{linear} = C_{ACES}\)</td> 648 * </tr> 649 * <tr><td>Range</td><td colspan="4">\([-65504.0, 65504.0]\)</td></tr> 650 * </table> 651 * <p> 652 * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_aces.png" /> 653 * <figcaption style="text-align: center;">ACES (orange) vs sRGB (white)</figcaption> 654 * </p> 655 */ 656 ACES, 657 /** 658 * <p>{@link ColorSpace.Rgb RGB} color space ACEScg standardized as Academy S-2014-004.</p> 659 * <table summary="Color space definition"> 660 * <tr> 661 * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> 662 * </tr> 663 * <tr><td>x</td><td>0.713</td><td>0.165</td><td>0.128</td><td>0.32168</td></tr> 664 * <tr><td>y</td><td>0.293</td><td>0.830</td><td>0.044</td><td>0.33767</td></tr> 665 * <tr><th>Property</th><th colspan="4">Value</th></tr> 666 * <tr><td>Name</td><td colspan="4">Academy S-2014-004 ACEScg</td></tr> 667 * <tr><td>CIE standard illuminant</td><td colspan="4">D60</td></tr> 668 * <tr> 669 * <td>Opto-electronic transfer function (OETF)</td> 670 * <td colspan="4">\(C_{ACEScg} = C_{linear}\)</td> 671 * </tr> 672 * <tr> 673 * <td>Electro-optical transfer function (EOTF)</td> 674 * <td colspan="4">\(C_{linear} = C_{ACEScg}\)</td> 675 * </tr> 676 * <tr><td>Range</td><td colspan="4">\([-65504.0, 65504.0]\)</td></tr> 677 * </table> 678 * <p> 679 * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_acescg.png" /> 680 * <figcaption style="text-align: center;">ACEScg (orange) vs sRGB (white)</figcaption> 681 * </p> 682 */ 683 ACESCG, 684 /** 685 * <p>{@link Model#XYZ XYZ} color space CIE XYZ. This color space assumes standard 686 * illuminant D50 as its white point.</p> 687 * <table summary="Color space definition"> 688 * <tr><th>Property</th><th colspan="4">Value</th></tr> 689 * <tr><td>Name</td><td colspan="4">Generic XYZ</td></tr> 690 * <tr><td>CIE standard illuminant</td><td colspan="4">D50</td></tr> 691 * <tr><td>Range</td><td colspan="4">\([-2.0, 2.0]\)</td></tr> 692 * </table> 693 */ 694 CIE_XYZ, 695 /** 696 * <p>{@link Model#LAB Lab} color space CIE L*a*b*. This color space uses CIE XYZ D50 697 * as a profile conversion space.</p> 698 * <table summary="Color space definition"> 699 * <tr><th>Property</th><th colspan="4">Value</th></tr> 700 * <tr><td>Name</td><td colspan="4">Generic L*a*b*</td></tr> 701 * <tr><td>CIE standard illuminant</td><td colspan="4">D50</td></tr> 702 * <tr><td>Range</td><td colspan="4">\(L: [0.0, 100.0], a: [-128, 128], b: [-128, 128]\)</td></tr> 703 * </table> 704 */ 705 CIE_LAB 706 // Update the initialization block next to #get(Named) when adding new values 707 } 708 709 /** 710 * <p>A render intent determines how a {@link ColorSpace.Connector connector} 711 * maps colors from one color space to another. The choice of mapping is 712 * important when the source color space has a larger color gamut than the 713 * destination color space.</p> 714 * 715 * @see ColorSpace#connect(ColorSpace, ColorSpace, RenderIntent) 716 */ 717 public enum RenderIntent { 718 /** 719 * <p>Compresses the source gamut into the destination gamut. 720 * This render intent affects all colors, inside and outside 721 * of destination gamut. The goal of this render intent is 722 * to preserve the visual relationship between colors.</p> 723 * 724 * <p class="note">This render intent is currently not 725 * implemented and behaves like {@link #RELATIVE}.</p> 726 */ 727 PERCEPTUAL, 728 /** 729 * Similar to the {@link #ABSOLUTE} render intent, this render 730 * intent matches the closest color in the destination gamut 731 * but makes adjustments for the destination white point. 732 */ 733 RELATIVE, 734 /** 735 * <p>Attempts to maintain the relative saturation of colors 736 * from the source gamut to the destination gamut, to keep 737 * highly saturated colors as saturated as possible.</p> 738 * 739 * <p class="note">This render intent is currently not 740 * implemented and behaves like {@link #RELATIVE}.</p> 741 */ 742 SATURATION, 743 /** 744 * Colors that are in the destination gamut are left unchanged. 745 * Colors that fall outside of the destination gamut are mapped 746 * to the closest possible color within the gamut of the destination 747 * color space (they are clipped). 748 */ 749 ABSOLUTE 750 } 751 752 /** 753 * {@usesMathJax} 754 * 755 * <p>List of adaptation matrices that can be used for chromatic adaptation 756 * using the von Kries transform. These matrices are used to convert values 757 * in the CIE XYZ space to values in the LMS space (Long Medium Short).</p> 758 * 759 * <p>Given an adaptation matrix \(A\), the conversion from XYZ to 760 * LMS is straightforward:</p> 761 * 762 * $$\left[ \begin{array}{c} L\\ M\\ S \end{array} \right] = 763 * A \left[ \begin{array}{c} X\\ Y\\ Z \end{array} \right]$$ 764 * 765 * <p>The complete von Kries transform \(T\) uses a diagonal matrix 766 * noted \(D\) to perform the adaptation in LMS space. In addition 767 * to \(A\) and \(D\), the source white point \(W1\) and the destination 768 * white point \(W2\) must be specified:</p> 769 * 770 * $$\begin{align*} 771 * \left[ \begin{array}{c} L_1\\ M_1\\ S_1 \end{array} \right] &= 772 * A \left[ \begin{array}{c} W1_X\\ W1_Y\\ W1_Z \end{array} \right] \\\ 773 * \left[ \begin{array}{c} L_2\\ M_2\\ S_2 \end{array} \right] &= 774 * A \left[ \begin{array}{c} W2_X\\ W2_Y\\ W2_Z \end{array} \right] \\\ 775 * D &= \left[ \begin{matrix} \frac{L_2}{L_1} & 0 & 0 \\\ 776 * 0 & \frac{M_2}{M_1} & 0 \\\ 777 * 0 & 0 & \frac{S_2}{S_1} \end{matrix} \right] \\\ 778 * T &= A^{-1}.D.A 779 * \end{align*}$$ 780 * 781 * <p>As an example, the resulting matrix \(T\) can then be used to 782 * perform the chromatic adaptation of sRGB XYZ transform from D65 783 * to D50:</p> 784 * 785 * $$sRGB_{D50} = T.sRGB_{D65}$$ 786 * 787 * @see ColorSpace.Connector 788 * @see ColorSpace#connect(ColorSpace, ColorSpace) 789 */ 790 public enum Adaptation { 791 /** 792 * Bradford chromatic adaptation transform, as defined in the 793 * CIECAM97s color appearance model. 794 */ 795 BRADFORD(new float[] { 796 0.8951f, -0.7502f, 0.0389f, 797 0.2664f, 1.7135f, -0.0685f, 798 -0.1614f, 0.0367f, 1.0296f 799 }), 800 /** 801 * von Kries chromatic adaptation transform. 802 */ 803 VON_KRIES(new float[] { 804 0.40024f, -0.22630f, 0.00000f, 805 0.70760f, 1.16532f, 0.00000f, 806 -0.08081f, 0.04570f, 0.91822f 807 }), 808 /** 809 * CIECAT02 chromatic adaption transform, as defined in the 810 * CIECAM02 color appearance model. 811 */ 812 CIECAT02(new float[] { 813 0.7328f, -0.7036f, 0.0030f, 814 0.4296f, 1.6975f, 0.0136f, 815 -0.1624f, 0.0061f, 0.9834f 816 }); 817 818 final float[] mTransform; 819 Adaptation(@onNull @ize9) float[] transform)820 Adaptation(@NonNull @Size(9) float[] transform) { 821 mTransform = transform; 822 } 823 } 824 825 /** 826 * A color model is required by a {@link ColorSpace} to describe the 827 * way colors can be represented as tuples of numbers. A common color 828 * model is the {@link #RGB RGB} color model which defines a color 829 * as represented by a tuple of 3 numbers (red, green and blue). 830 */ 831 public enum Model { 832 /** 833 * The RGB model is a color model with 3 components that 834 * refer to the three additive primiaries: red, green 835 * andd blue. 836 */ 837 RGB(3), 838 /** 839 * The XYZ model is a color model with 3 components that 840 * are used to model human color vision on a basic sensory 841 * level. 842 */ 843 XYZ(3), 844 /** 845 * The Lab model is a color model with 3 components used 846 * to describe a color space that is more perceptually 847 * uniform than XYZ. 848 */ 849 LAB(3), 850 /** 851 * The CMYK model is a color model with 4 components that 852 * refer to four inks used in color printing: cyan, magenta, 853 * yellow and black (or key). CMYK is a subtractive color 854 * model. 855 */ 856 CMYK(4); 857 858 private final int mComponentCount; 859 Model(@ntRangefrom = 1, to = 4) int componentCount)860 Model(@IntRange(from = 1, to = 4) int componentCount) { 861 mComponentCount = componentCount; 862 } 863 864 /** 865 * Returns the number of components for this color model. 866 * 867 * @return An integer between 1 and 4 868 */ 869 @IntRange(from = 1, to = 4) getComponentCount()870 public int getComponentCount() { 871 return mComponentCount; 872 } 873 } 874 875 /** @hide */ ColorSpace( @onNull String name, @NonNull Model model, @IntRange(from = MIN_ID, to = MAX_ID) int id)876 ColorSpace( 877 @NonNull String name, 878 @NonNull Model model, 879 @IntRange(from = MIN_ID, to = MAX_ID) int id) { 880 881 if (name == null || name.length() < 1) { 882 throw new IllegalArgumentException("The name of a color space cannot be null and " + 883 "must contain at least 1 character"); 884 } 885 886 if (model == null) { 887 throw new IllegalArgumentException("A color space must have a model"); 888 } 889 890 if (id < MIN_ID || id > MAX_ID) { 891 throw new IllegalArgumentException("The id must be between " + 892 MIN_ID + " and " + MAX_ID); 893 } 894 895 mName = name; 896 mModel = model; 897 mId = id; 898 } 899 900 /** 901 * <p>Returns the name of this color space. The name is never null 902 * and contains always at least 1 character.</p> 903 * 904 * <p>Color space names are recommended to be unique but are not 905 * guaranteed to be. There is no defined format but the name usually 906 * falls in one of the following categories:</p> 907 * <ul> 908 * <li>Generic names used to identify color spaces in non-RGB 909 * color models. For instance: {@link Named#CIE_LAB Generic L*a*b*}.</li> 910 * <li>Names tied to a particular specification. For instance: 911 * {@link Named#SRGB sRGB IEC61966-2.1} or 912 * {@link Named#ACES SMPTE ST 2065-1:2012 ACES}.</li> 913 * <li>Ad-hoc names, often generated procedurally or by the user 914 * during a calibration workflow. These names often contain the 915 * make and model of the display.</li> 916 * </ul> 917 * 918 * <p>Because the format of color space names is not defined, it is 919 * not recommended to programmatically identify a color space by its 920 * name alone. Names can be used as a first approximation.</p> 921 * 922 * <p>It is however perfectly acceptable to display color space names to 923 * users in a UI, or in debuggers and logs. When displaying a color space 924 * name to the user, it is recommended to add extra information to avoid 925 * ambiguities: color model, a representation of the color space's gamut, 926 * white point, etc.</p> 927 * 928 * @return A non-null String of length >= 1 929 */ 930 @NonNull getName()931 public String getName() { 932 return mName; 933 } 934 935 /** 936 * Returns the ID of this color space. Positive IDs match the color 937 * spaces enumerated in {@link Named}. A negative ID indicates a 938 * color space created by calling one of the public constructors. 939 * 940 * @return An integer between {@link #MIN_ID} and {@link #MAX_ID} 941 */ 942 @IntRange(from = MIN_ID, to = MAX_ID) getId()943 public int getId() { 944 return mId; 945 } 946 947 /** 948 * Return the color model of this color space. 949 * 950 * @return A non-null {@link Model} 951 * 952 * @see Model 953 * @see #getComponentCount() 954 */ 955 @NonNull getModel()956 public Model getModel() { 957 return mModel; 958 } 959 960 /** 961 * Returns the number of components that form a color value according 962 * to this color space's color model. 963 * 964 * @return An integer between 1 and 4 965 * 966 * @see Model 967 * @see #getModel() 968 */ 969 @IntRange(from = 1, to = 4) getComponentCount()970 public int getComponentCount() { 971 return mModel.getComponentCount(); 972 } 973 974 /** 975 * Returns whether this color space is a wide-gamut color space. 976 * An RGB color space is wide-gamut if its gamut entirely contains 977 * the {@link Named#SRGB sRGB} gamut and if the area of its gamut is 978 * 90% of greater than the area of the {@link Named#NTSC_1953 NTSC} 979 * gamut. 980 * 981 * @return True if this color space is a wide-gamut color space, 982 * false otherwise 983 */ isWideGamut()984 public abstract boolean isWideGamut(); 985 986 /** 987 * <p>Indicates whether this color space is the sRGB color space or 988 * equivalent to the sRGB color space.</p> 989 * <p>A color space is considered sRGB if it meets all the following 990 * conditions:</p> 991 * <ul> 992 * <li>Its color model is {@link Model#RGB}.</li> 993 * <li> 994 * Its primaries are within 1e-3 of the true 995 * {@link Named#SRGB sRGB} primaries. 996 * </li> 997 * <li> 998 * Its white point is within 1e-3 of the CIE standard 999 * illuminant {@link #ILLUMINANT_D65 D65}. 1000 * </li> 1001 * <li>Its opto-electronic transfer function is not linear.</li> 1002 * <li>Its electro-optical transfer function is not linear.</li> 1003 * <li>Its transfer functions yield values within 1e-3 of {@link Named#SRGB}.</li> 1004 * <li>Its range is \([0..1]\).</li> 1005 * </ul> 1006 * <p>This method always returns true for {@link Named#SRGB}.</p> 1007 * 1008 * @return True if this color space is the sRGB color space (or a 1009 * close approximation), false otherwise 1010 */ isSrgb()1011 public boolean isSrgb() { 1012 return false; 1013 } 1014 1015 /** 1016 * Returns the minimum valid value for the specified component of this 1017 * color space's color model. 1018 * 1019 * @param component The index of the component 1020 * @return A floating point value less than {@link #getMaxValue(int)} 1021 * 1022 * @see #getMaxValue(int) 1023 * @see Model#getComponentCount() 1024 */ getMinValue(@ntRangefrom = 0, to = 3) int component)1025 public abstract float getMinValue(@IntRange(from = 0, to = 3) int component); 1026 1027 /** 1028 * Returns the maximum valid value for the specified component of this 1029 * color space's color model. 1030 * 1031 * @param component The index of the component 1032 * @return A floating point value greater than {@link #getMinValue(int)} 1033 * 1034 * @see #getMinValue(int) 1035 * @see Model#getComponentCount() 1036 */ getMaxValue(@ntRangefrom = 0, to = 3) int component)1037 public abstract float getMaxValue(@IntRange(from = 0, to = 3) int component); 1038 1039 /** 1040 * <p>Converts a color value from this color space's model to 1041 * tristimulus CIE XYZ values. If the color model of this color 1042 * space is not {@link Model#RGB RGB}, it is assumed that the 1043 * target CIE XYZ space uses a {@link #ILLUMINANT_D50 D50} 1044 * standard illuminant.</p> 1045 * 1046 * <p>This method is a convenience for color spaces with a model 1047 * of 3 components ({@link Model#RGB RGB} or {@link Model#LAB} 1048 * for instance). With color spaces using fewer or more components, 1049 * use {@link #toXyz(float[])} instead</p>. 1050 * 1051 * @param r The first component of the value to convert from (typically R in RGB) 1052 * @param g The second component of the value to convert from (typically G in RGB) 1053 * @param b The third component of the value to convert from (typically B in RGB) 1054 * @return A new array of 3 floats, containing tristimulus XYZ values 1055 * 1056 * @see #toXyz(float[]) 1057 * @see #fromXyz(float, float, float) 1058 */ 1059 @NonNull 1060 @Size(3) toXyz(float r, float g, float b)1061 public float[] toXyz(float r, float g, float b) { 1062 return toXyz(new float[] { r, g, b }); 1063 } 1064 1065 /** 1066 * <p>Converts a color value from this color space's model to 1067 * tristimulus CIE XYZ values. If the color model of this color 1068 * space is not {@link Model#RGB RGB}, it is assumed that the 1069 * target CIE XYZ space uses a {@link #ILLUMINANT_D50 D50} 1070 * standard illuminant.</p> 1071 * 1072 * <p class="note">The specified array's length must be at least 1073 * equal to to the number of color components as returned by 1074 * {@link Model#getComponentCount()}.</p> 1075 * 1076 * @param v An array of color components containing the color space's 1077 * color value to convert to XYZ, and large enough to hold 1078 * the resulting tristimulus XYZ values 1079 * @return The array passed in parameter 1080 * 1081 * @see #toXyz(float, float, float) 1082 * @see #fromXyz(float[]) 1083 */ 1084 @NonNull 1085 @Size(min = 3) toXyz(@onNull @izemin = 3) float[] v)1086 public abstract float[] toXyz(@NonNull @Size(min = 3) float[] v); 1087 1088 /** 1089 * <p>Converts tristimulus values from the CIE XYZ space to this 1090 * color space's color model.</p> 1091 * 1092 * @param x The X component of the color value 1093 * @param y The Y component of the color value 1094 * @param z The Z component of the color value 1095 * @return A new array whose size is equal to the number of color 1096 * components as returned by {@link Model#getComponentCount()} 1097 * 1098 * @see #fromXyz(float[]) 1099 * @see #toXyz(float, float, float) 1100 */ 1101 @NonNull 1102 @Size(min = 3) fromXyz(float x, float y, float z)1103 public float[] fromXyz(float x, float y, float z) { 1104 float[] xyz = new float[mModel.getComponentCount()]; 1105 xyz[0] = x; 1106 xyz[1] = y; 1107 xyz[2] = z; 1108 return fromXyz(xyz); 1109 } 1110 1111 /** 1112 * <p>Converts tristimulus values from the CIE XYZ space to this color 1113 * space's color model. The resulting value is passed back in the specified 1114 * array.</p> 1115 * 1116 * <p class="note">The specified array's length must be at least equal to 1117 * to the number of color components as returned by 1118 * {@link Model#getComponentCount()}, and its first 3 values must 1119 * be the XYZ components to convert from.</p> 1120 * 1121 * @param v An array of color components containing the XYZ values 1122 * to convert from, and large enough to hold the number 1123 * of components of this color space's model 1124 * @return The array passed in parameter 1125 * 1126 * @see #fromXyz(float, float, float) 1127 * @see #toXyz(float[]) 1128 */ 1129 @NonNull 1130 @Size(min = 3) fromXyz(@onNull @izemin = 3) float[] v)1131 public abstract float[] fromXyz(@NonNull @Size(min = 3) float[] v); 1132 1133 /** 1134 * <p>Returns a string representation of the object. This method returns 1135 * a string equal to the value of:</p> 1136 * 1137 * <pre class="prettyprint"> 1138 * getName() + "(id=" + getId() + ", model=" + getModel() + ")" 1139 * </pre> 1140 * 1141 * <p>For instance, the string representation of the {@link Named#SRGB sRGB} 1142 * color space is equal to the following value:</p> 1143 * 1144 * <pre> 1145 * sRGB IEC61966-2.1 (id=0, model=RGB) 1146 * </pre> 1147 * 1148 * @return A string representation of the object 1149 */ 1150 @Override 1151 @NonNull toString()1152 public String toString() { 1153 return mName + " (id=" + mId + ", model=" + mModel + ")"; 1154 } 1155 1156 @Override equals(Object o)1157 public boolean equals(Object o) { 1158 if (this == o) return true; 1159 if (o == null || getClass() != o.getClass()) return false; 1160 1161 ColorSpace that = (ColorSpace) o; 1162 1163 if (mId != that.mId) return false; 1164 //noinspection SimplifiableIfStatement 1165 if (!mName.equals(that.mName)) return false; 1166 return mModel == that.mModel; 1167 1168 } 1169 1170 @Override hashCode()1171 public int hashCode() { 1172 int result = mName.hashCode(); 1173 result = 31 * result + mModel.hashCode(); 1174 result = 31 * result + mId; 1175 return result; 1176 } 1177 1178 /** 1179 * <p>Connects two color spaces to allow conversion from the source color 1180 * space to the destination color space. If the source and destination 1181 * color spaces do not have the same profile connection space (CIE XYZ 1182 * with the same white point), they are chromatically adapted to use the 1183 * CIE standard illuminant {@link #ILLUMINANT_D50 D50} as needed.</p> 1184 * 1185 * <p>If the source and destination are the same, an optimized connector 1186 * is returned to avoid unnecessary computations and loss of precision.</p> 1187 * 1188 * <p>Colors are mapped from the source color space to the destination color 1189 * space using the {@link RenderIntent#PERCEPTUAL perceptual} render intent.</p> 1190 * 1191 * @param source The color space to convert colors from 1192 * @param destination The color space to convert colors to 1193 * @return A non-null connector between the two specified color spaces 1194 * 1195 * @see #connect(ColorSpace) 1196 * @see #connect(ColorSpace, RenderIntent) 1197 * @see #connect(ColorSpace, ColorSpace, RenderIntent) 1198 */ 1199 @NonNull connect(@onNull ColorSpace source, @NonNull ColorSpace destination)1200 public static Connector connect(@NonNull ColorSpace source, @NonNull ColorSpace destination) { 1201 return connect(source, destination, RenderIntent.PERCEPTUAL); 1202 } 1203 1204 /** 1205 * <p>Connects two color spaces to allow conversion from the source color 1206 * space to the destination color space. If the source and destination 1207 * color spaces do not have the same profile connection space (CIE XYZ 1208 * with the same white point), they are chromatically adapted to use the 1209 * CIE standard illuminant {@link #ILLUMINANT_D50 D50} as needed.</p> 1210 * 1211 * <p>If the source and destination are the same, an optimized connector 1212 * is returned to avoid unnecessary computations and loss of precision.</p> 1213 * 1214 * @param source The color space to convert colors from 1215 * @param destination The color space to convert colors to 1216 * @param intent The render intent to map colors from the source to the destination 1217 * @return A non-null connector between the two specified color spaces 1218 * 1219 * @see #connect(ColorSpace) 1220 * @see #connect(ColorSpace, RenderIntent) 1221 * @see #connect(ColorSpace, ColorSpace) 1222 */ 1223 @NonNull 1224 @SuppressWarnings("ConstantConditions") connect(@onNull ColorSpace source, @NonNull ColorSpace destination, @NonNull RenderIntent intent)1225 public static Connector connect(@NonNull ColorSpace source, @NonNull ColorSpace destination, 1226 @NonNull RenderIntent intent) { 1227 if (source.equals(destination)) return Connector.identity(source); 1228 1229 if (source.getModel() == Model.RGB && destination.getModel() == Model.RGB) { 1230 return new Connector.Rgb((Rgb) source, (Rgb) destination, intent); 1231 } 1232 1233 return new Connector(source, destination, intent); 1234 } 1235 1236 /** 1237 * <p>Connects the specified color spaces to sRGB. 1238 * If the source color space does not use CIE XYZ D65 as its profile 1239 * connection space, the two spaces are chromatically adapted to use the 1240 * CIE standard illuminant {@link #ILLUMINANT_D50 D50} as needed.</p> 1241 * 1242 * <p>If the source is the sRGB color space, an optimized connector 1243 * is returned to avoid unnecessary computations and loss of precision.</p> 1244 * 1245 * <p>Colors are mapped from the source color space to the destination color 1246 * space using the {@link RenderIntent#PERCEPTUAL perceptual} render intent.</p> 1247 * 1248 * @param source The color space to convert colors from 1249 * @return A non-null connector between the specified color space and sRGB 1250 * 1251 * @see #connect(ColorSpace, RenderIntent) 1252 * @see #connect(ColorSpace, ColorSpace) 1253 * @see #connect(ColorSpace, ColorSpace, RenderIntent) 1254 */ 1255 @NonNull connect(@onNull ColorSpace source)1256 public static Connector connect(@NonNull ColorSpace source) { 1257 return connect(source, RenderIntent.PERCEPTUAL); 1258 } 1259 1260 /** 1261 * <p>Connects the specified color spaces to sRGB. 1262 * If the source color space does not use CIE XYZ D65 as its profile 1263 * connection space, the two spaces are chromatically adapted to use the 1264 * CIE standard illuminant {@link #ILLUMINANT_D50 D50} as needed.</p> 1265 * 1266 * <p>If the source is the sRGB color space, an optimized connector 1267 * is returned to avoid unnecessary computations and loss of precision.</p> 1268 * 1269 * @param source The color space to convert colors from 1270 * @param intent The render intent to map colors from the source to the destination 1271 * @return A non-null connector between the specified color space and sRGB 1272 * 1273 * @see #connect(ColorSpace) 1274 * @see #connect(ColorSpace, ColorSpace) 1275 * @see #connect(ColorSpace, ColorSpace, RenderIntent) 1276 */ 1277 @NonNull connect(@onNull ColorSpace source, @NonNull RenderIntent intent)1278 public static Connector connect(@NonNull ColorSpace source, @NonNull RenderIntent intent) { 1279 if (source.isSrgb()) return Connector.identity(source); 1280 1281 if (source.getModel() == Model.RGB) { 1282 return new Connector.Rgb((Rgb) source, (Rgb) get(Named.SRGB), intent); 1283 } 1284 1285 return new Connector(source, get(Named.SRGB), intent); 1286 } 1287 1288 /** 1289 * <p>Performs the chromatic adaptation of a color space from its native 1290 * white point to the specified white point.</p> 1291 * 1292 * <p>The chromatic adaptation is performed using the 1293 * {@link Adaptation#BRADFORD} matrix.</p> 1294 * 1295 * <p class="note">The color space returned by this method always has 1296 * an ID of {@link #MIN_ID}.</p> 1297 * 1298 * @param colorSpace The color space to chromatically adapt 1299 * @param whitePoint The new white point 1300 * @return A {@link ColorSpace} instance with the same name, primaries, 1301 * transfer functions and range as the specified color space 1302 * 1303 * @see Adaptation 1304 * @see #adapt(ColorSpace, float[], Adaptation) 1305 */ 1306 @NonNull adapt(@onNull ColorSpace colorSpace, @NonNull @Size(min = 2, max = 3) float[] whitePoint)1307 public static ColorSpace adapt(@NonNull ColorSpace colorSpace, 1308 @NonNull @Size(min = 2, max = 3) float[] whitePoint) { 1309 return adapt(colorSpace, whitePoint, Adaptation.BRADFORD); 1310 } 1311 1312 /** 1313 * <p>Performs the chromatic adaptation of a color space from its native 1314 * white point to the specified white point. If the specified color space 1315 * does not have an {@link Model#RGB RGB} color model, or if the color 1316 * space already has the target white point, the color space is returned 1317 * unmodified.</p> 1318 * 1319 * <p>The chromatic adaptation is performed using the von Kries method 1320 * described in the documentation of {@link Adaptation}.</p> 1321 * 1322 * <p class="note">The color space returned by this method always has 1323 * an ID of {@link #MIN_ID}.</p> 1324 * 1325 * @param colorSpace The color space to chromatically adapt 1326 * @param whitePoint The new white point 1327 * @param adaptation The adaptation matrix 1328 * @return A new color space if the specified color space has an RGB 1329 * model and a white point different from the specified white 1330 * point; the specified color space otherwise 1331 * 1332 * @see Adaptation 1333 * @see #adapt(ColorSpace, float[]) 1334 */ 1335 @NonNull adapt(@onNull ColorSpace colorSpace, @NonNull @Size(min = 2, max = 3) float[] whitePoint, @NonNull Adaptation adaptation)1336 public static ColorSpace adapt(@NonNull ColorSpace colorSpace, 1337 @NonNull @Size(min = 2, max = 3) float[] whitePoint, 1338 @NonNull Adaptation adaptation) { 1339 if (colorSpace.getModel() == Model.RGB) { 1340 ColorSpace.Rgb rgb = (ColorSpace.Rgb) colorSpace; 1341 if (compare(rgb.mWhitePoint, whitePoint)) return colorSpace; 1342 1343 float[] xyz = whitePoint.length == 3 ? 1344 Arrays.copyOf(whitePoint, 3) : xyYToXyz(whitePoint); 1345 float[] adaptationTransform = chromaticAdaptation(adaptation.mTransform, 1346 xyYToXyz(rgb.getWhitePoint()), xyz); 1347 float[] transform = mul3x3(adaptationTransform, rgb.mTransform); 1348 1349 return new ColorSpace.Rgb(rgb, transform, whitePoint); 1350 } 1351 return colorSpace; 1352 } 1353 1354 /** 1355 * Helper method for creating native SkColorSpace. 1356 * 1357 * This essentially calls adapt on a ColorSpace that has not been fully 1358 * created. It also does not fully create the adapted ColorSpace, but 1359 * just returns the transform. 1360 */ 1361 @NonNull @Size(9) adaptToIlluminantD50( @onNull @ize2) float[] origWhitePoint, @NonNull @Size(9) float[] origTransform)1362 private static float[] adaptToIlluminantD50( 1363 @NonNull @Size(2) float[] origWhitePoint, 1364 @NonNull @Size(9) float[] origTransform) { 1365 float[] desired = ILLUMINANT_D50; 1366 if (compare(origWhitePoint, desired)) return origTransform; 1367 1368 float[] xyz = xyYToXyz(desired); 1369 float[] adaptationTransform = chromaticAdaptation(Adaptation.BRADFORD.mTransform, 1370 xyYToXyz(origWhitePoint), xyz); 1371 return mul3x3(adaptationTransform, origTransform); 1372 } 1373 1374 /** 1375 * <p>Returns an instance of {@link ColorSpace} whose ID matches the 1376 * specified ID.</p> 1377 * 1378 * <p>This method always returns the same instance for a given ID.</p> 1379 * 1380 * <p>This method is thread-safe.</p> 1381 * 1382 * @param index An integer ID between {@link #MIN_ID} and {@link #MAX_ID} 1383 * @return A non-null {@link ColorSpace} instance 1384 * @throws IllegalArgumentException If the ID does not match the ID of one of the 1385 * {@link Named named color spaces} 1386 */ 1387 @NonNull get(@ntRangefrom = MIN_ID, to = MAX_ID) int index)1388 static ColorSpace get(@IntRange(from = MIN_ID, to = MAX_ID) int index) { 1389 if (index < 0 || index >= sNamedColorSpaces.length) { 1390 throw new IllegalArgumentException("Invalid ID, must be in the range [0.." + 1391 sNamedColorSpaces.length + ")"); 1392 } 1393 return sNamedColorSpaces[index]; 1394 } 1395 1396 /** 1397 * <p>Returns an instance of {@link ColorSpace} identified by the specified 1398 * name. The list of names provided in the {@link Named} enum gives access 1399 * to a variety of common RGB color spaces.</p> 1400 * 1401 * <p>This method always returns the same instance for a given name.</p> 1402 * 1403 * <p>This method is thread-safe.</p> 1404 * 1405 * @param name The name of the color space to get an instance of 1406 * @return A non-null {@link ColorSpace} instance 1407 */ 1408 @NonNull get(@onNull Named name)1409 public static ColorSpace get(@NonNull Named name) { 1410 return sNamedColorSpaces[name.ordinal()]; 1411 } 1412 1413 /** 1414 * <p>Returns a {@link Named} instance of {@link ColorSpace} that matches 1415 * the specified RGB to CIE XYZ transform and transfer functions. If no 1416 * instance can be found, this method returns null.</p> 1417 * 1418 * <p>The color transform matrix is assumed to target the CIE XYZ space 1419 * a {@link #ILLUMINANT_D50 D50} standard illuminant.</p> 1420 * 1421 * @param toXYZD50 3x3 column-major transform matrix from RGB to the profile 1422 * connection space CIE XYZ as an array of 9 floats, cannot be null 1423 * @param function Parameters for the transfer functions 1424 * @return A non-null {@link ColorSpace} if a match is found, null otherwise 1425 */ 1426 @Nullable match( @onNull @ize9) float[] toXYZD50, @NonNull Rgb.TransferParameters function)1427 public static ColorSpace match( 1428 @NonNull @Size(9) float[] toXYZD50, 1429 @NonNull Rgb.TransferParameters function) { 1430 1431 for (ColorSpace colorSpace : sNamedColorSpaces) { 1432 if (colorSpace.getModel() == Model.RGB) { 1433 ColorSpace.Rgb rgb = (ColorSpace.Rgb) adapt(colorSpace, ILLUMINANT_D50_XYZ); 1434 if (compare(toXYZD50, rgb.mTransform) && 1435 compare(function, rgb.mTransferParameters)) { 1436 return colorSpace; 1437 } 1438 } 1439 } 1440 1441 return null; 1442 } 1443 1444 /** 1445 * <p>Creates a new {@link Renderer} that can be used to visualize and 1446 * debug color spaces. See the documentation of {@link Renderer} for 1447 * more information.</p> 1448 * 1449 * @return A new non-null {@link Renderer} instance 1450 * 1451 * @see Renderer 1452 * 1453 * @hide 1454 */ 1455 @NonNull createRenderer()1456 public static Renderer createRenderer() { 1457 return new Renderer(); 1458 } 1459 1460 static { 1461 sNamedColorSpaces[Named.SRGB.ordinal()] = new ColorSpace.Rgb( 1462 "sRGB IEC61966-2.1", 1463 SRGB_PRIMARIES, 1464 ILLUMINANT_D65, 1465 null, 1466 SRGB_TRANSFER_PARAMETERS, 1467 Named.SRGB.ordinal() 1468 ); 1469 sNamedColorSpaces[Named.LINEAR_SRGB.ordinal()] = new ColorSpace.Rgb( 1470 "sRGB IEC61966-2.1 (Linear)", 1471 SRGB_PRIMARIES, 1472 ILLUMINANT_D65, 1473 1.0, 1474 0.0f, 1.0f, 1475 Named.LINEAR_SRGB.ordinal() 1476 ); 1477 sNamedColorSpaces[Named.EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb( 1478 "scRGB-nl IEC 61966-2-2:2003", 1479 SRGB_PRIMARIES, 1480 ILLUMINANT_D65, 1481 null, 1482 x -> absRcpResponse(x, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4), 1483 x -> absResponse(x, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4), 1484 -0.799f, 2.399f, 1485 SRGB_TRANSFER_PARAMETERS, 1486 Named.EXTENDED_SRGB.ordinal() 1487 ); 1488 sNamedColorSpaces[Named.LINEAR_EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb( 1489 "scRGB IEC 61966-2-2:2003", 1490 SRGB_PRIMARIES, 1491 ILLUMINANT_D65, 1492 1.0, 1493 -0.5f, 7.499f, 1494 Named.LINEAR_EXTENDED_SRGB.ordinal() 1495 ); 1496 sNamedColorSpaces[Named.BT709.ordinal()] = new ColorSpace.Rgb( 1497 "Rec. ITU-R BT.709-5", 1498 new float[] { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f }, 1499 ILLUMINANT_D65, 1500 null, 1501 new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45), 1502 Named.BT709.ordinal() 1503 ); 1504 sNamedColorSpaces[Named.BT2020.ordinal()] = new ColorSpace.Rgb( 1505 "Rec. ITU-R BT.2020-1", 1506 new float[] { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f }, 1507 ILLUMINANT_D65, 1508 null, 1509 new Rgb.TransferParameters(1 / 1.0993, 0.0993 / 1.0993, 1 / 4.5, 0.08145, 1 / 0.45), 1510 Named.BT2020.ordinal() 1511 ); 1512 sNamedColorSpaces[Named.DCI_P3.ordinal()] = new ColorSpace.Rgb( 1513 "SMPTE RP 431-2-2007 DCI (P3)", 1514 new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f }, 1515 new float[] { 0.314f, 0.351f }, 1516 2.6, 1517 0.0f, 1.0f, 1518 Named.DCI_P3.ordinal() 1519 ); 1520 sNamedColorSpaces[Named.DISPLAY_P3.ordinal()] = new ColorSpace.Rgb( 1521 "Display P3", 1522 new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f }, 1523 ILLUMINANT_D65, 1524 null, 1525 SRGB_TRANSFER_PARAMETERS, 1526 Named.DISPLAY_P3.ordinal() 1527 ); 1528 sNamedColorSpaces[Named.NTSC_1953.ordinal()] = new ColorSpace.Rgb( 1529 "NTSC (1953)", 1530 NTSC_1953_PRIMARIES, 1531 ILLUMINANT_C, 1532 null, 1533 new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45), 1534 Named.NTSC_1953.ordinal() 1535 ); 1536 sNamedColorSpaces[Named.SMPTE_C.ordinal()] = new ColorSpace.Rgb( 1537 "SMPTE-C RGB", 1538 new float[] { 0.630f, 0.340f, 0.310f, 0.595f, 0.155f, 0.070f }, 1539 ILLUMINANT_D65, 1540 null, 1541 new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45), 1542 Named.SMPTE_C.ordinal() 1543 ); 1544 sNamedColorSpaces[Named.ADOBE_RGB.ordinal()] = new ColorSpace.Rgb( 1545 "Adobe RGB (1998)", 1546 new float[] { 0.64f, 0.33f, 0.21f, 0.71f, 0.15f, 0.06f }, 1547 ILLUMINANT_D65, 1548 2.2, 1549 0.0f, 1.0f, 1550 Named.ADOBE_RGB.ordinal() 1551 ); 1552 sNamedColorSpaces[Named.PRO_PHOTO_RGB.ordinal()] = new ColorSpace.Rgb( 1553 "ROMM RGB ISO 22028-2:2013", 1554 new float[] { 0.7347f, 0.2653f, 0.1596f, 0.8404f, 0.0366f, 0.0001f }, 1555 ILLUMINANT_D50, 1556 null, 1557 new Rgb.TransferParameters(1.0, 0.0, 1 / 16.0, 0.031248, 1.8), 1558 Named.PRO_PHOTO_RGB.ordinal() 1559 ); 1560 sNamedColorSpaces[Named.ACES.ordinal()] = new ColorSpace.Rgb( 1561 "SMPTE ST 2065-1:2012 ACES", 1562 new float[] { 0.73470f, 0.26530f, 0.0f, 1.0f, 0.00010f, -0.0770f }, 1563 ILLUMINANT_D60, 1564 1.0, 1565 -65504.0f, 65504.0f, 1566 Named.ACES.ordinal() 1567 ); 1568 sNamedColorSpaces[Named.ACESCG.ordinal()] = new ColorSpace.Rgb( 1569 "Academy S-2014-004 ACEScg", 1570 new float[] { 0.713f, 0.293f, 0.165f, 0.830f, 0.128f, 0.044f }, 1571 ILLUMINANT_D60, 1572 1.0, 1573 -65504.0f, 65504.0f, 1574 Named.ACESCG.ordinal() 1575 ); 1576 sNamedColorSpaces[Named.CIE_XYZ.ordinal()] = new Xyz( 1577 "Generic XYZ", 1578 Named.CIE_XYZ.ordinal() 1579 ); 1580 sNamedColorSpaces[Named.CIE_LAB.ordinal()] = new ColorSpace.Lab( 1581 "Generic L*a*b*", 1582 Named.CIE_LAB.ordinal() 1583 ); 1584 } 1585 1586 // Reciprocal piecewise gamma response rcpResponse(double x, double a, double b, double c, double d, double g)1587 private static double rcpResponse(double x, double a, double b, double c, double d, double g) { 1588 return x >= d * c ? (Math.pow(x, 1.0 / g) - b) / a : x / c; 1589 } 1590 1591 // Piecewise gamma response response(double x, double a, double b, double c, double d, double g)1592 private static double response(double x, double a, double b, double c, double d, double g) { 1593 return x >= d ? Math.pow(a * x + b, g) : c * x; 1594 } 1595 1596 // Reciprocal piecewise gamma response rcpResponse(double x, double a, double b, double c, double d, double e, double f, double g)1597 private static double rcpResponse(double x, double a, double b, double c, double d, 1598 double e, double f, double g) { 1599 return x >= d * c ? (Math.pow(x - e, 1.0 / g) - b) / a : (x - f) / c; 1600 } 1601 1602 // Piecewise gamma response response(double x, double a, double b, double c, double d, double e, double f, double g)1603 private static double response(double x, double a, double b, double c, double d, 1604 double e, double f, double g) { 1605 return x >= d ? Math.pow(a * x + b, g) + e : c * x + f; 1606 } 1607 1608 // Reciprocal piecewise gamma response, encoded as sign(x).f(abs(x)) for color 1609 // spaces that allow negative values 1610 @SuppressWarnings("SameParameterValue") absRcpResponse(double x, double a, double b, double c, double d, double g)1611 private static double absRcpResponse(double x, double a, double b, double c, double d, double g) { 1612 return Math.copySign(rcpResponse(x < 0.0 ? -x : x, a, b, c, d, g), x); 1613 } 1614 1615 // Piecewise gamma response, encoded as sign(x).f(abs(x)) for color spaces that 1616 // allow negative values 1617 @SuppressWarnings("SameParameterValue") absResponse(double x, double a, double b, double c, double d, double g)1618 private static double absResponse(double x, double a, double b, double c, double d, double g) { 1619 return Math.copySign(response(x < 0.0 ? -x : x, a, b, c, d, g), x); 1620 } 1621 1622 /** 1623 * Compares two sets of parametric transfer functions parameters with a precision of 1e-3. 1624 * 1625 * @param a The first set of parameters to compare 1626 * @param b The second set of parameters to compare 1627 * @return True if the two sets are equal, false otherwise 1628 */ compare( @ullable Rgb.TransferParameters a, @Nullable Rgb.TransferParameters b)1629 private static boolean compare( 1630 @Nullable Rgb.TransferParameters a, 1631 @Nullable Rgb.TransferParameters b) { 1632 //noinspection SimplifiableIfStatement 1633 if (a == null && b == null) return true; 1634 return a != null && b != null && 1635 Math.abs(a.a - b.a) < 1e-3 && 1636 Math.abs(a.b - b.b) < 1e-3 && 1637 Math.abs(a.c - b.c) < 1e-3 && 1638 Math.abs(a.d - b.d) < 2e-3 && // Special case for variations in sRGB OETF/EOTF 1639 Math.abs(a.e - b.e) < 1e-3 && 1640 Math.abs(a.f - b.f) < 1e-3 && 1641 Math.abs(a.g - b.g) < 1e-3; 1642 } 1643 1644 /** 1645 * Compares two arrays of float with a precision of 1e-3. 1646 * 1647 * @param a The first array to compare 1648 * @param b The second array to compare 1649 * @return True if the two arrays are equal, false otherwise 1650 */ compare(@onNull float[] a, @NonNull float[] b)1651 private static boolean compare(@NonNull float[] a, @NonNull float[] b) { 1652 if (a == b) return true; 1653 for (int i = 0; i < a.length; i++) { 1654 if (Float.compare(a[i], b[i]) != 0 && Math.abs(a[i] - b[i]) > 1e-3f) return false; 1655 } 1656 return true; 1657 } 1658 1659 /** 1660 * Inverts a 3x3 matrix. This method assumes the matrix is invertible. 1661 * 1662 * @param m A 3x3 matrix as a non-null array of 9 floats 1663 * @return A new array of 9 floats containing the inverse of the input matrix 1664 */ 1665 @NonNull 1666 @Size(9) inverse3x3(@onNull @ize9) float[] m)1667 private static float[] inverse3x3(@NonNull @Size(9) float[] m) { 1668 float a = m[0]; 1669 float b = m[3]; 1670 float c = m[6]; 1671 float d = m[1]; 1672 float e = m[4]; 1673 float f = m[7]; 1674 float g = m[2]; 1675 float h = m[5]; 1676 float i = m[8]; 1677 1678 float A = e * i - f * h; 1679 float B = f * g - d * i; 1680 float C = d * h - e * g; 1681 1682 float det = a * A + b * B + c * C; 1683 1684 float inverted[] = new float[m.length]; 1685 inverted[0] = A / det; 1686 inverted[1] = B / det; 1687 inverted[2] = C / det; 1688 inverted[3] = (c * h - b * i) / det; 1689 inverted[4] = (a * i - c * g) / det; 1690 inverted[5] = (b * g - a * h) / det; 1691 inverted[6] = (b * f - c * e) / det; 1692 inverted[7] = (c * d - a * f) / det; 1693 inverted[8] = (a * e - b * d) / det; 1694 return inverted; 1695 } 1696 1697 /** 1698 * Multiplies two 3x3 matrices, represented as non-null arrays of 9 floats. 1699 * 1700 * @param lhs 3x3 matrix, as a non-null array of 9 floats 1701 * @param rhs 3x3 matrix, as a non-null array of 9 floats 1702 * @return A new array of 9 floats containing the result of the multiplication 1703 * of rhs by lhs 1704 * 1705 * @hide 1706 */ 1707 @NonNull 1708 @Size(9) mul3x3(@onNull @ize9) float[] lhs, @NonNull @Size(9) float[] rhs)1709 public static float[] mul3x3(@NonNull @Size(9) float[] lhs, @NonNull @Size(9) float[] rhs) { 1710 float[] r = new float[9]; 1711 r[0] = lhs[0] * rhs[0] + lhs[3] * rhs[1] + lhs[6] * rhs[2]; 1712 r[1] = lhs[1] * rhs[0] + lhs[4] * rhs[1] + lhs[7] * rhs[2]; 1713 r[2] = lhs[2] * rhs[0] + lhs[5] * rhs[1] + lhs[8] * rhs[2]; 1714 r[3] = lhs[0] * rhs[3] + lhs[3] * rhs[4] + lhs[6] * rhs[5]; 1715 r[4] = lhs[1] * rhs[3] + lhs[4] * rhs[4] + lhs[7] * rhs[5]; 1716 r[5] = lhs[2] * rhs[3] + lhs[5] * rhs[4] + lhs[8] * rhs[5]; 1717 r[6] = lhs[0] * rhs[6] + lhs[3] * rhs[7] + lhs[6] * rhs[8]; 1718 r[7] = lhs[1] * rhs[6] + lhs[4] * rhs[7] + lhs[7] * rhs[8]; 1719 r[8] = lhs[2] * rhs[6] + lhs[5] * rhs[7] + lhs[8] * rhs[8]; 1720 return r; 1721 } 1722 1723 /** 1724 * Multiplies a vector of 3 components by a 3x3 matrix and stores the 1725 * result in the input vector. 1726 * 1727 * @param lhs 3x3 matrix, as a non-null array of 9 floats 1728 * @param rhs Vector of 3 components, as a non-null array of 3 floats 1729 * @return The array of 3 passed as the rhs parameter 1730 */ 1731 @NonNull 1732 @Size(min = 3) mul3x3Float3( @onNull @ize9) float[] lhs, @NonNull @Size(min = 3) float[] rhs)1733 private static float[] mul3x3Float3( 1734 @NonNull @Size(9) float[] lhs, @NonNull @Size(min = 3) float[] rhs) { 1735 float r0 = rhs[0]; 1736 float r1 = rhs[1]; 1737 float r2 = rhs[2]; 1738 rhs[0] = lhs[0] * r0 + lhs[3] * r1 + lhs[6] * r2; 1739 rhs[1] = lhs[1] * r0 + lhs[4] * r1 + lhs[7] * r2; 1740 rhs[2] = lhs[2] * r0 + lhs[5] * r1 + lhs[8] * r2; 1741 return rhs; 1742 } 1743 1744 /** 1745 * Multiplies a diagonal 3x3 matrix lhs, represented as an array of 3 floats, 1746 * by a 3x3 matrix represented as an array of 9 floats. 1747 * 1748 * @param lhs Diagonal 3x3 matrix, as a non-null array of 3 floats 1749 * @param rhs 3x3 matrix, as a non-null array of 9 floats 1750 * @return A new array of 9 floats containing the result of the multiplication 1751 * of rhs by lhs 1752 */ 1753 @NonNull 1754 @Size(9) mul3x3Diag( @onNull @ize3) float[] lhs, @NonNull @Size(9) float[] rhs)1755 private static float[] mul3x3Diag( 1756 @NonNull @Size(3) float[] lhs, @NonNull @Size(9) float[] rhs) { 1757 return new float[] { 1758 lhs[0] * rhs[0], lhs[1] * rhs[1], lhs[2] * rhs[2], 1759 lhs[0] * rhs[3], lhs[1] * rhs[4], lhs[2] * rhs[5], 1760 lhs[0] * rhs[6], lhs[1] * rhs[7], lhs[2] * rhs[8] 1761 }; 1762 } 1763 1764 /** 1765 * Converts a value from CIE xyY to CIE XYZ. Y is assumed to be 1 so the 1766 * input xyY array only contains the x and y components. 1767 * 1768 * @param xyY The xyY value to convert to XYZ, cannot be null, length must be 2 1769 * @return A new float array of length 3 containing XYZ values 1770 */ 1771 @NonNull 1772 @Size(3) xyYToXyz(@onNull @ize2) float[] xyY)1773 private static float[] xyYToXyz(@NonNull @Size(2) float[] xyY) { 1774 return new float[] { xyY[0] / xyY[1], 1.0f, (1 - xyY[0] - xyY[1]) / xyY[1] }; 1775 } 1776 1777 /** 1778 * Converts values from CIE xyY to CIE L*u*v*. Y is assumed to be 1 so the 1779 * input xyY array only contains the x and y components. After this method 1780 * returns, the xyY array contains the converted u and v components. 1781 * 1782 * @param xyY The xyY value to convert to XYZ, cannot be null, 1783 * length must be a multiple of 2 1784 */ xyYToUv(@onNull @izemultiple = 2) float[] xyY)1785 private static void xyYToUv(@NonNull @Size(multiple = 2) float[] xyY) { 1786 for (int i = 0; i < xyY.length; i += 2) { 1787 float x = xyY[i]; 1788 float y = xyY[i + 1]; 1789 1790 float d = -2.0f * x + 12.0f * y + 3; 1791 float u = (4.0f * x) / d; 1792 float v = (9.0f * y) / d; 1793 1794 xyY[i] = u; 1795 xyY[i + 1] = v; 1796 } 1797 } 1798 1799 /** 1800 * <p>Computes the chromatic adaptation transform from the specified 1801 * source white point to the specified destination white point.</p> 1802 * 1803 * <p>The transform is computed using the von Kries method, described 1804 * in more details in the documentation of {@link Adaptation}. The 1805 * {@link Adaptation} enum provides different matrices that can be 1806 * used to perform the adaptation.</p> 1807 * 1808 * @param matrix The adaptation matrix 1809 * @param srcWhitePoint The white point to adapt from, *will be modified* 1810 * @param dstWhitePoint The white point to adapt to, *will be modified* 1811 * @return A 3x3 matrix as a non-null array of 9 floats 1812 */ 1813 @NonNull 1814 @Size(9) chromaticAdaptation(@onNull @ize9) float[] matrix, @NonNull @Size(3) float[] srcWhitePoint, @NonNull @Size(3) float[] dstWhitePoint)1815 private static float[] chromaticAdaptation(@NonNull @Size(9) float[] matrix, 1816 @NonNull @Size(3) float[] srcWhitePoint, @NonNull @Size(3) float[] dstWhitePoint) { 1817 float[] srcLMS = mul3x3Float3(matrix, srcWhitePoint); 1818 float[] dstLMS = mul3x3Float3(matrix, dstWhitePoint); 1819 // LMS is a diagonal matrix stored as a float[3] 1820 float[] LMS = { dstLMS[0] / srcLMS[0], dstLMS[1] / srcLMS[1], dstLMS[2] / srcLMS[2] }; 1821 return mul3x3(inverse3x3(matrix), mul3x3Diag(LMS, matrix)); 1822 } 1823 1824 /** 1825 * <p>Computes the chromaticity coordinates of a specified correlated color 1826 * temperature (CCT) on the Planckian locus. The specified CCT must be 1827 * greater than 0. A meaningful CCT range is [1667, 25000].</p> 1828 * 1829 * <p>The transform is computed using the methods in Kang et 1830 * al., <i>Design of Advanced Color - Temperature Control System for HDTV 1831 * Applications</i>, Journal of Korean Physical Society 41, 865-871 1832 * (2002).</p> 1833 * 1834 * @param cct The correlated color temperature, in Kelvin 1835 * @return Corresponding XYZ values 1836 * @throws IllegalArgumentException If cct is invalid 1837 * 1838 * @hide 1839 */ 1840 @NonNull 1841 @Size(3) cctToXyz(@ntRangefrom = 1) int cct)1842 public static float[] cctToXyz(@IntRange(from = 1) int cct) { 1843 if (cct < 1) { 1844 throw new IllegalArgumentException("Temperature must be greater than 0"); 1845 } 1846 1847 final float icct = 1e3f / cct; 1848 final float icct2 = icct * icct; 1849 final float x = cct <= 4000.0f ? 1850 0.179910f + 0.8776956f * icct - 0.2343589f * icct2 - 0.2661239f * icct2 * icct : 1851 0.240390f + 0.2226347f * icct + 2.1070379f * icct2 - 3.0258469f * icct2 * icct; 1852 1853 final float x2 = x * x; 1854 final float y = cct <= 2222.0f ? 1855 -0.20219683f + 2.18555832f * x - 1.34811020f * x2 - 1.1063814f * x2 * x : 1856 cct <= 4000.0f ? 1857 -0.16748867f + 2.09137015f * x - 1.37418593f * x2 - 0.9549476f * x2 * x : 1858 -0.37001483f + 3.75112997f * x - 5.8733867f * x2 + 3.0817580f * x2 * x; 1859 1860 return xyYToXyz(new float[] {x, y}); 1861 } 1862 1863 /** 1864 * <p>Computes the chromaticity coordinates of a CIE series D illuminant 1865 * from the specified correlated color temperature (CCT). The specified CCT 1866 * must be greater than 0. A meaningful CCT range is [4000, 25000].</p> 1867 * 1868 * <p>The transform is computed using the methods referred to in Kang et 1869 * al., <i>Design of Advanced Color - Temperature Control System for HDTV 1870 * Applications</i>, Journal of Korean Physical Society 41, 865-871 1871 * (2002).</p> 1872 * 1873 * @param cct The correlated color temperature, in Kelvin 1874 * @return Corresponding XYZ values 1875 * @throws IllegalArgumentException If cct is invalid 1876 * 1877 * @hide 1878 */ 1879 @NonNull 1880 @Size(3) cctToIlluminantdXyz(@ntRangefrom = 1) int cct)1881 public static float[] cctToIlluminantdXyz(@IntRange(from = 1) int cct) { 1882 if (cct < 1) { 1883 throw new IllegalArgumentException("Temperature must be greater than 0"); 1884 } 1885 1886 final float icct = 1.0f / cct; 1887 final float icct2 = icct * icct; 1888 final float x = cct <= 7000.0f ? 1889 0.244063f + 0.09911e3f * icct + 2.9678e6f * icct2 - 4.6070e9f * icct2 * icct : 1890 0.237040f + 0.24748e3f * icct + 1.9018e6f * icct2 - 2.0064e9f * icct2 * icct; 1891 final float y = -3.0f * x * x + 2.87f * x - 0.275f; 1892 return xyYToXyz(new float[] {x, y}); 1893 } 1894 1895 /** 1896 * <p>Computes the chromatic adaptation transform from the specified 1897 * source white point to the specified destination white point.</p> 1898 * 1899 * <p>The transform is computed using the von Kries method, described 1900 * in more details in the documentation of {@link Adaptation}. The 1901 * {@link Adaptation} enum provides different matrices that can be 1902 * used to perform the adaptation.</p> 1903 * 1904 * @param adaptation The adaptation method 1905 * @param srcWhitePoint The white point to adapt from 1906 * @param dstWhitePoint The white point to adapt to 1907 * @return A 3x3 matrix as a non-null array of 9 floats 1908 * 1909 * @hide 1910 */ 1911 @NonNull 1912 @Size(9) chromaticAdaptation(@onNull Adaptation adaptation, @NonNull @Size(min = 2, max = 3) float[] srcWhitePoint, @NonNull @Size(min = 2, max = 3) float[] dstWhitePoint)1913 public static float[] chromaticAdaptation(@NonNull Adaptation adaptation, 1914 @NonNull @Size(min = 2, max = 3) float[] srcWhitePoint, 1915 @NonNull @Size(min = 2, max = 3) float[] dstWhitePoint) { 1916 float[] srcXyz = srcWhitePoint.length == 3 ? 1917 Arrays.copyOf(srcWhitePoint, 3) : xyYToXyz(srcWhitePoint); 1918 float[] dstXyz = dstWhitePoint.length == 3 ? 1919 Arrays.copyOf(dstWhitePoint, 3) : xyYToXyz(dstWhitePoint); 1920 1921 if (compare(srcXyz, dstXyz)) { 1922 return new float[] { 1923 1.0f, 0.0f, 0.0f, 1924 0.0f, 1.0f, 0.0f, 1925 0.0f, 0.0f, 1.0f 1926 }; 1927 } 1928 return chromaticAdaptation(adaptation.mTransform, srcXyz, dstXyz); 1929 } 1930 1931 /** 1932 * Implementation of the CIE XYZ color space. Assumes the white point is D50. 1933 */ 1934 @AnyThread 1935 private static final class Xyz extends ColorSpace { Xyz(@onNull String name, @IntRange(from = MIN_ID, to = MAX_ID) int id)1936 private Xyz(@NonNull String name, @IntRange(from = MIN_ID, to = MAX_ID) int id) { 1937 super(name, Model.XYZ, id); 1938 } 1939 1940 @Override isWideGamut()1941 public boolean isWideGamut() { 1942 return true; 1943 } 1944 1945 @Override getMinValue(@ntRangefrom = 0, to = 3) int component)1946 public float getMinValue(@IntRange(from = 0, to = 3) int component) { 1947 return -2.0f; 1948 } 1949 1950 @Override getMaxValue(@ntRangefrom = 0, to = 3) int component)1951 public float getMaxValue(@IntRange(from = 0, to = 3) int component) { 1952 return 2.0f; 1953 } 1954 1955 @Override toXyz(@onNull @izemin = 3) float[] v)1956 public float[] toXyz(@NonNull @Size(min = 3) float[] v) { 1957 v[0] = clamp(v[0]); 1958 v[1] = clamp(v[1]); 1959 v[2] = clamp(v[2]); 1960 return v; 1961 } 1962 1963 @Override fromXyz(@onNull @izemin = 3) float[] v)1964 public float[] fromXyz(@NonNull @Size(min = 3) float[] v) { 1965 v[0] = clamp(v[0]); 1966 v[1] = clamp(v[1]); 1967 v[2] = clamp(v[2]); 1968 return v; 1969 } 1970 clamp(float x)1971 private static float clamp(float x) { 1972 return x < -2.0f ? -2.0f : x > 2.0f ? 2.0f : x; 1973 } 1974 } 1975 1976 /** 1977 * Implementation of the CIE L*a*b* color space. Its PCS is CIE XYZ 1978 * with a white point of D50. 1979 */ 1980 @AnyThread 1981 private static final class Lab extends ColorSpace { 1982 private static final float A = 216.0f / 24389.0f; 1983 private static final float B = 841.0f / 108.0f; 1984 private static final float C = 4.0f / 29.0f; 1985 private static final float D = 6.0f / 29.0f; 1986 Lab(@onNull String name, @IntRange(from = MIN_ID, to = MAX_ID) int id)1987 private Lab(@NonNull String name, @IntRange(from = MIN_ID, to = MAX_ID) int id) { 1988 super(name, Model.LAB, id); 1989 } 1990 1991 @Override isWideGamut()1992 public boolean isWideGamut() { 1993 return true; 1994 } 1995 1996 @Override getMinValue(@ntRangefrom = 0, to = 3) int component)1997 public float getMinValue(@IntRange(from = 0, to = 3) int component) { 1998 return component == 0 ? 0.0f : -128.0f; 1999 } 2000 2001 @Override getMaxValue(@ntRangefrom = 0, to = 3) int component)2002 public float getMaxValue(@IntRange(from = 0, to = 3) int component) { 2003 return component == 0 ? 100.0f : 128.0f; 2004 } 2005 2006 @Override toXyz(@onNull @izemin = 3) float[] v)2007 public float[] toXyz(@NonNull @Size(min = 3) float[] v) { 2008 v[0] = clamp(v[0], 0.0f, 100.0f); 2009 v[1] = clamp(v[1], -128.0f, 128.0f); 2010 v[2] = clamp(v[2], -128.0f, 128.0f); 2011 2012 float fy = (v[0] + 16.0f) / 116.0f; 2013 float fx = fy + (v[1] * 0.002f); 2014 float fz = fy - (v[2] * 0.005f); 2015 float X = fx > D ? fx * fx * fx : (1.0f / B) * (fx - C); 2016 float Y = fy > D ? fy * fy * fy : (1.0f / B) * (fy - C); 2017 float Z = fz > D ? fz * fz * fz : (1.0f / B) * (fz - C); 2018 2019 v[0] = X * ILLUMINANT_D50_XYZ[0]; 2020 v[1] = Y * ILLUMINANT_D50_XYZ[1]; 2021 v[2] = Z * ILLUMINANT_D50_XYZ[2]; 2022 2023 return v; 2024 } 2025 2026 @Override fromXyz(@onNull @izemin = 3) float[] v)2027 public float[] fromXyz(@NonNull @Size(min = 3) float[] v) { 2028 float X = v[0] / ILLUMINANT_D50_XYZ[0]; 2029 float Y = v[1] / ILLUMINANT_D50_XYZ[1]; 2030 float Z = v[2] / ILLUMINANT_D50_XYZ[2]; 2031 2032 float fx = X > A ? (float) Math.pow(X, 1.0 / 3.0) : B * X + C; 2033 float fy = Y > A ? (float) Math.pow(Y, 1.0 / 3.0) : B * Y + C; 2034 float fz = Z > A ? (float) Math.pow(Z, 1.0 / 3.0) : B * Z + C; 2035 2036 float L = 116.0f * fy - 16.0f; 2037 float a = 500.0f * (fx - fy); 2038 float b = 200.0f * (fy - fz); 2039 2040 v[0] = clamp(L, 0.0f, 100.0f); 2041 v[1] = clamp(a, -128.0f, 128.0f); 2042 v[2] = clamp(b, -128.0f, 128.0f); 2043 2044 return v; 2045 } 2046 clamp(float x, float min, float max)2047 private static float clamp(float x, float min, float max) { 2048 return x < min ? min : x > max ? max : x; 2049 } 2050 } 2051 2052 /** 2053 * Retrieve the native SkColorSpace object for passing to native. 2054 * 2055 * Only valid on ColorSpace.Rgb. 2056 */ getNativeInstance()2057 long getNativeInstance() { 2058 throw new IllegalArgumentException("colorSpace must be an RGB color space"); 2059 } 2060 2061 /** 2062 * {@usesMathJax} 2063 * 2064 * <p>An RGB color space is an additive color space using the 2065 * {@link Model#RGB RGB} color model (a color is therefore represented 2066 * by a tuple of 3 numbers).</p> 2067 * 2068 * <p>A specific RGB color space is defined by the following properties:</p> 2069 * <ul> 2070 * <li>Three chromaticities of the red, green and blue primaries, which 2071 * define the gamut of the color space.</li> 2072 * <li>A white point chromaticity that defines the stimulus to which 2073 * color space values are normalized (also just called "white").</li> 2074 * <li>An opto-electronic transfer function, also called opto-electronic 2075 * conversion function or often, and approximately, gamma function.</li> 2076 * <li>An electro-optical transfer function, also called electo-optical 2077 * conversion function or often, and approximately, gamma function.</li> 2078 * <li>A range of valid RGB values (most commonly \([0..1]\)).</li> 2079 * </ul> 2080 * 2081 * <p>The most commonly used RGB color space is {@link Named#SRGB sRGB}.</p> 2082 * 2083 * <h3>Primaries and white point chromaticities</h3> 2084 * <p>In this implementation, the chromaticity of the primaries and the white 2085 * point of an RGB color space is defined in the CIE xyY color space. This 2086 * color space separates the chromaticity of a color, the x and y components, 2087 * and its luminance, the Y component. Since the primaries and the white 2088 * point have full brightness, the Y component is assumed to be 1 and only 2089 * the x and y components are needed to encode them.</p> 2090 * <p>For convenience, this implementation also allows to define the 2091 * primaries and white point in the CIE XYZ space. The tristimulus XYZ values 2092 * are internally converted to xyY.</p> 2093 * 2094 * <p> 2095 * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_srgb.png" /> 2096 * <figcaption style="text-align: center;">sRGB primaries and white point</figcaption> 2097 * </p> 2098 * 2099 * <h3>Transfer functions</h3> 2100 * <p>A transfer function is a color component conversion function, defined as 2101 * a single variable, monotonic mathematical function. It is applied to each 2102 * individual component of a color. They are used to perform the mapping 2103 * between linear tristimulus values and non-linear electronic signal value.</p> 2104 * <p>The <em>opto-electronic transfer function</em> (OETF or OECF) encodes 2105 * tristimulus values in a scene to a non-linear electronic signal value. 2106 * An OETF is often expressed as a power function with an exponent between 2107 * 0.38 and 0.55 (the reciprocal of 1.8 to 2.6).</p> 2108 * <p>The <em>electro-optical transfer function</em> (EOTF or EOCF) decodes 2109 * a non-linear electronic signal value to a tristimulus value at the display. 2110 * An EOTF is often expressed as a power function with an exponent between 2111 * 1.8 and 2.6.</p> 2112 * <p>Transfer functions are used as a compression scheme. For instance, 2113 * linear sRGB values would normally require 11 to 12 bits of precision to 2114 * store all values that can be perceived by the human eye. When encoding 2115 * sRGB values using the appropriate OETF (see {@link Named#SRGB sRGB} for 2116 * an exact mathematical description of that OETF), the values can be 2117 * compressed to only 8 bits precision.</p> 2118 * <p>When manipulating RGB values, particularly sRGB values, it is safe 2119 * to assume that these values have been encoded with the appropriate 2120 * OETF (unless noted otherwise). Encoded values are often said to be in 2121 * "gamma space". They are therefore defined in a non-linear space. This 2122 * in turns means that any linear operation applied to these values is 2123 * going to yield mathematically incorrect results (any linear interpolation 2124 * such as gradient generation for instance, most image processing functions 2125 * such as blurs, etc.).</p> 2126 * <p>To properly process encoded RGB values you must first apply the 2127 * EOTF to decode the value into linear space. After processing, the RGB 2128 * value must be encoded back to non-linear ("gamma") space. Here is a 2129 * formal description of the process, where \(f\) is the processing 2130 * function to apply:</p> 2131 * 2132 * $$RGB_{out} = OETF(f(EOTF(RGB_{in})))$$ 2133 * 2134 * <p>If the transfer functions of the color space can be expressed as an 2135 * ICC parametric curve as defined in ICC.1:2004-10, the numeric parameters 2136 * can be retrieved by calling {@link #getTransferParameters()}. This can 2137 * be useful to match color spaces for instance.</p> 2138 * 2139 * <p class="note">Some RGB color spaces, such as {@link Named#ACES} and 2140 * {@link Named#LINEAR_EXTENDED_SRGB scRGB}, are said to be linear because 2141 * their transfer functions are the identity function: \(f(x) = x\). 2142 * If the source and/or destination are known to be linear, it is not 2143 * necessary to invoke the transfer functions.</p> 2144 * 2145 * <h3>Range</h3> 2146 * <p>Most RGB color spaces allow RGB values in the range \([0..1]\). There 2147 * are however a few RGB color spaces that allow much larger ranges. For 2148 * instance, {@link Named#EXTENDED_SRGB scRGB} is used to manipulate the 2149 * range \([-0.5..7.5]\) while {@link Named#ACES ACES} can be used throughout 2150 * the range \([-65504, 65504]\).</p> 2151 * 2152 * <p> 2153 * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_scrgb.png" /> 2154 * <figcaption style="text-align: center;">Extended sRGB and its large range</figcaption> 2155 * </p> 2156 * 2157 * <h3>Converting between RGB color spaces</h3> 2158 * <p>Conversion between two color spaces is achieved by using an intermediate 2159 * color space called the profile connection space (PCS). The PCS used by 2160 * this implementation is CIE XYZ. The conversion operation is defined 2161 * as such:</p> 2162 * 2163 * $$RGB_{out} = OETF(T_{dst}^{-1} \cdot T_{src} \cdot EOTF(RGB_{in}))$$ 2164 * 2165 * <p>Where \(T_{src}\) is the {@link #getTransform() RGB to XYZ transform} 2166 * of the source color space and \(T_{dst}^{-1}\) the {@link #getInverseTransform() 2167 * XYZ to RGB transform} of the destination color space.</p> 2168 * <p>Many RGB color spaces commonly used with electronic devices use the 2169 * standard illuminant {@link #ILLUMINANT_D65 D65}. Care must be take however 2170 * when converting between two RGB color spaces if their white points do not 2171 * match. This can be achieved by either calling 2172 * {@link #adapt(ColorSpace, float[])} to adapt one or both color spaces to 2173 * a single common white point. This can be achieved automatically by calling 2174 * {@link ColorSpace#connect(ColorSpace, ColorSpace)}, which also handles 2175 * non-RGB color spaces.</p> 2176 * <p>To learn more about the white point adaptation process, refer to the 2177 * documentation of {@link Adaptation}.</p> 2178 */ 2179 @AnyThread 2180 public static class Rgb extends ColorSpace { 2181 /** 2182 * {@usesMathJax} 2183 * 2184 * <p>Defines the parameters for the ICC parametric curve type 4, as 2185 * defined in ICC.1:2004-10, section 10.15.</p> 2186 * 2187 * <p>The EOTF is of the form:</p> 2188 * 2189 * \(\begin{equation} 2190 * Y = \begin{cases}c X + f & X \lt d \\\ 2191 * \left( a X + b \right) ^{g} + e & X \ge d \end{cases} 2192 * \end{equation}\) 2193 * 2194 * <p>The corresponding OETF is simply the inverse function.</p> 2195 * 2196 * <p>The parameters defined by this class form a valid transfer 2197 * function only if all the following conditions are met:</p> 2198 * <ul> 2199 * <li>No parameter is a {@link Double#isNaN(double) Not-a-Number}</li> 2200 * <li>\(d\) is in the range \([0..1]\)</li> 2201 * <li>The function is not constant</li> 2202 * <li>The function is positive and increasing</li> 2203 * </ul> 2204 */ 2205 public static class TransferParameters { 2206 /** Variable \(a\) in the equation of the EOTF described above. */ 2207 public final double a; 2208 /** Variable \(b\) in the equation of the EOTF described above. */ 2209 public final double b; 2210 /** Variable \(c\) in the equation of the EOTF described above. */ 2211 public final double c; 2212 /** Variable \(d\) in the equation of the EOTF described above. */ 2213 public final double d; 2214 /** Variable \(e\) in the equation of the EOTF described above. */ 2215 public final double e; 2216 /** Variable \(f\) in the equation of the EOTF described above. */ 2217 public final double f; 2218 /** Variable \(g\) in the equation of the EOTF described above. */ 2219 public final double g; 2220 2221 /** 2222 * <p>Defines the parameters for the ICC parametric curve type 3, as 2223 * defined in ICC.1:2004-10, section 10.15.</p> 2224 * 2225 * <p>The EOTF is of the form:</p> 2226 * 2227 * \(\begin{equation} 2228 * Y = \begin{cases}c X & X \lt d \\\ 2229 * \left( a X + b \right) ^{g} & X \ge d \end{cases} 2230 * \end{equation}\) 2231 * 2232 * <p>This constructor is equivalent to setting \(e\) and \(f\) to 0.</p> 2233 * 2234 * @param a The value of \(a\) in the equation of the EOTF described above 2235 * @param b The value of \(b\) in the equation of the EOTF described above 2236 * @param c The value of \(c\) in the equation of the EOTF described above 2237 * @param d The value of \(d\) in the equation of the EOTF described above 2238 * @param g The value of \(g\) in the equation of the EOTF described above 2239 * 2240 * @throws IllegalArgumentException If the parameters form an invalid transfer function 2241 */ TransferParameters(double a, double b, double c, double d, double g)2242 public TransferParameters(double a, double b, double c, double d, double g) { 2243 this(a, b, c, d, 0.0, 0.0, g); 2244 } 2245 2246 /** 2247 * <p>Defines the parameters for the ICC parametric curve type 4, as 2248 * defined in ICC.1:2004-10, section 10.15.</p> 2249 * 2250 * @param a The value of \(a\) in the equation of the EOTF described above 2251 * @param b The value of \(b\) in the equation of the EOTF described above 2252 * @param c The value of \(c\) in the equation of the EOTF described above 2253 * @param d The value of \(d\) in the equation of the EOTF described above 2254 * @param e The value of \(e\) in the equation of the EOTF described above 2255 * @param f The value of \(f\) in the equation of the EOTF described above 2256 * @param g The value of \(g\) in the equation of the EOTF described above 2257 * 2258 * @throws IllegalArgumentException If the parameters form an invalid transfer function 2259 */ TransferParameters(double a, double b, double c, double d, double e, double f, double g)2260 public TransferParameters(double a, double b, double c, double d, double e, 2261 double f, double g) { 2262 2263 if (Double.isNaN(a) || Double.isNaN(b) || Double.isNaN(c) || 2264 Double.isNaN(d) || Double.isNaN(e) || Double.isNaN(f) || 2265 Double.isNaN(g)) { 2266 throw new IllegalArgumentException("Parameters cannot be NaN"); 2267 } 2268 2269 // Next representable float after 1.0 2270 // We use doubles here but the representation inside our native code is often floats 2271 if (!(d >= 0.0 && d <= 1.0f + Math.ulp(1.0f))) { 2272 throw new IllegalArgumentException("Parameter d must be in the range [0..1], " + 2273 "was " + d); 2274 } 2275 2276 if (d == 0.0 && (a == 0.0 || g == 0.0)) { 2277 throw new IllegalArgumentException( 2278 "Parameter a or g is zero, the transfer function is constant"); 2279 } 2280 2281 if (d >= 1.0 && c == 0.0) { 2282 throw new IllegalArgumentException( 2283 "Parameter c is zero, the transfer function is constant"); 2284 } 2285 2286 if ((a == 0.0 || g == 0.0) && c == 0.0) { 2287 throw new IllegalArgumentException("Parameter a or g is zero," + 2288 " and c is zero, the transfer function is constant"); 2289 } 2290 2291 if (c < 0.0) { 2292 throw new IllegalArgumentException("The transfer function must be increasing"); 2293 } 2294 2295 if (a < 0.0 || g < 0.0) { 2296 throw new IllegalArgumentException("The transfer function must be " + 2297 "positive or increasing"); 2298 } 2299 2300 this.a = a; 2301 this.b = b; 2302 this.c = c; 2303 this.d = d; 2304 this.e = e; 2305 this.f = f; 2306 this.g = g; 2307 } 2308 2309 @SuppressWarnings("SimplifiableIfStatement") 2310 @Override equals(Object o)2311 public boolean equals(Object o) { 2312 if (this == o) return true; 2313 if (o == null || getClass() != o.getClass()) return false; 2314 2315 TransferParameters that = (TransferParameters) o; 2316 2317 if (Double.compare(that.a, a) != 0) return false; 2318 if (Double.compare(that.b, b) != 0) return false; 2319 if (Double.compare(that.c, c) != 0) return false; 2320 if (Double.compare(that.d, d) != 0) return false; 2321 if (Double.compare(that.e, e) != 0) return false; 2322 if (Double.compare(that.f, f) != 0) return false; 2323 return Double.compare(that.g, g) == 0; 2324 } 2325 2326 @Override hashCode()2327 public int hashCode() { 2328 int result; 2329 long temp; 2330 temp = Double.doubleToLongBits(a); 2331 result = (int) (temp ^ (temp >>> 32)); 2332 temp = Double.doubleToLongBits(b); 2333 result = 31 * result + (int) (temp ^ (temp >>> 32)); 2334 temp = Double.doubleToLongBits(c); 2335 result = 31 * result + (int) (temp ^ (temp >>> 32)); 2336 temp = Double.doubleToLongBits(d); 2337 result = 31 * result + (int) (temp ^ (temp >>> 32)); 2338 temp = Double.doubleToLongBits(e); 2339 result = 31 * result + (int) (temp ^ (temp >>> 32)); 2340 temp = Double.doubleToLongBits(f); 2341 result = 31 * result + (int) (temp ^ (temp >>> 32)); 2342 temp = Double.doubleToLongBits(g); 2343 result = 31 * result + (int) (temp ^ (temp >>> 32)); 2344 return result; 2345 } 2346 } 2347 2348 @NonNull private final float[] mWhitePoint; 2349 @NonNull private final float[] mPrimaries; 2350 @NonNull private final float[] mTransform; 2351 @NonNull private final float[] mInverseTransform; 2352 2353 @NonNull private final DoubleUnaryOperator mOetf; 2354 @NonNull private final DoubleUnaryOperator mEotf; 2355 @NonNull private final DoubleUnaryOperator mClampedOetf; 2356 @NonNull private final DoubleUnaryOperator mClampedEotf; 2357 2358 private final float mMin; 2359 private final float mMax; 2360 2361 private final boolean mIsWideGamut; 2362 private final boolean mIsSrgb; 2363 2364 @Nullable private final TransferParameters mTransferParameters; 2365 private final long mNativePtr; 2366 2367 @Override getNativeInstance()2368 long getNativeInstance() { 2369 if (mNativePtr == 0) { 2370 // If this object has TransferParameters, it must have a native object. 2371 throw new IllegalArgumentException("ColorSpace must use an ICC " 2372 + "parametric transfer function! used " + this); 2373 } 2374 return mNativePtr; 2375 } 2376 nativeGetNativeFinalizer()2377 private static native long nativeGetNativeFinalizer(); nativeCreate(float a, float b, float c, float d, float e, float f, float g, float[] xyz)2378 private static native long nativeCreate(float a, float b, float c, float d, 2379 float e, float f, float g, float[] xyz); 2380 2381 /** 2382 * <p>Creates a new RGB color space using a 3x3 column-major transform matrix. 2383 * The transform matrix must convert from the RGB space to the profile connection 2384 * space CIE XYZ.</p> 2385 * 2386 * <p class="note">The range of the color space is imposed to be \([0..1]\).</p> 2387 * 2388 * @param name Name of the color space, cannot be null, its length must be >= 1 2389 * @param toXYZ 3x3 column-major transform matrix from RGB to the profile 2390 * connection space CIE XYZ as an array of 9 floats, cannot be null 2391 * @param oetf Opto-electronic transfer function, cannot be null 2392 * @param eotf Electro-optical transfer function, cannot be null 2393 * 2394 * @throws IllegalArgumentException If any of the following conditions is met: 2395 * <ul> 2396 * <li>The name is null or has a length of 0.</li> 2397 * <li>The OETF is null or the EOTF is null.</li> 2398 * <li>The minimum valid value is >= the maximum valid value.</li> 2399 * </ul> 2400 * 2401 * @see #get(Named) 2402 */ Rgb( @onNull @izemin = 1) String name, @NonNull @Size(9) float[] toXYZ, @NonNull DoubleUnaryOperator oetf, @NonNull DoubleUnaryOperator eotf)2403 public Rgb( 2404 @NonNull @Size(min = 1) String name, 2405 @NonNull @Size(9) float[] toXYZ, 2406 @NonNull DoubleUnaryOperator oetf, 2407 @NonNull DoubleUnaryOperator eotf) { 2408 this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ), null, 2409 oetf, eotf, 0.0f, 1.0f, null, MIN_ID); 2410 } 2411 2412 /** 2413 * <p>Creates a new RGB color space using a specified set of primaries 2414 * and a specified white point.</p> 2415 * 2416 * <p>The primaries and white point can be specified in the CIE xyY space 2417 * or in CIE XYZ. The length of the arrays depends on the chosen space:</p> 2418 * 2419 * <table summary="Parameters length"> 2420 * <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr> 2421 * <tr><td>xyY</td><td>6</td><td>2</td></tr> 2422 * <tr><td>XYZ</td><td>9</td><td>3</td></tr> 2423 * </table> 2424 * 2425 * <p>When the primaries and/or white point are specified in xyY, the Y component 2426 * does not need to be specified and is assumed to be 1.0. Only the xy components 2427 * are required.</p> 2428 * 2429 * <p class="note">The ID, areturned by {@link #getId()}, of an object created by 2430 * this constructor is always {@link #MIN_ID}.</p> 2431 * 2432 * @param name Name of the color space, cannot be null, its length must be >= 1 2433 * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats 2434 * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats 2435 * @param oetf Opto-electronic transfer function, cannot be null 2436 * @param eotf Electro-optical transfer function, cannot be null 2437 * @param min The minimum valid value in this color space's RGB range 2438 * @param max The maximum valid value in this color space's RGB range 2439 * 2440 * @throws IllegalArgumentException <p>If any of the following conditions is met:</p> 2441 * <ul> 2442 * <li>The name is null or has a length of 0.</li> 2443 * <li>The primaries array is null or has a length that is neither 6 or 9.</li> 2444 * <li>The white point array is null or has a length that is neither 2 or 3.</li> 2445 * <li>The OETF is null or the EOTF is null.</li> 2446 * <li>The minimum valid value is >= the maximum valid value.</li> 2447 * </ul> 2448 * 2449 * @see #get(Named) 2450 */ Rgb( @onNull @izemin = 1) String name, @NonNull @Size(min = 6, max = 9) float[] primaries, @NonNull @Size(min = 2, max = 3) float[] whitePoint, @NonNull DoubleUnaryOperator oetf, @NonNull DoubleUnaryOperator eotf, float min, float max)2451 public Rgb( 2452 @NonNull @Size(min = 1) String name, 2453 @NonNull @Size(min = 6, max = 9) float[] primaries, 2454 @NonNull @Size(min = 2, max = 3) float[] whitePoint, 2455 @NonNull DoubleUnaryOperator oetf, 2456 @NonNull DoubleUnaryOperator eotf, 2457 float min, 2458 float max) { 2459 this(name, primaries, whitePoint, null, oetf, eotf, min, max, null, MIN_ID); 2460 } 2461 2462 /** 2463 * <p>Creates a new RGB color space using a 3x3 column-major transform matrix. 2464 * The transform matrix must convert from the RGB space to the profile connection 2465 * space CIE XYZ.</p> 2466 * 2467 * <p class="note">The range of the color space is imposed to be \([0..1]\).</p> 2468 * 2469 * @param name Name of the color space, cannot be null, its length must be >= 1 2470 * @param toXYZ 3x3 column-major transform matrix from RGB to the profile 2471 * connection space CIE XYZ as an array of 9 floats, cannot be null 2472 * @param function Parameters for the transfer functions 2473 * 2474 * @throws IllegalArgumentException If any of the following conditions is met: 2475 * <ul> 2476 * <li>The name is null or has a length of 0.</li> 2477 * <li>Gamma is negative.</li> 2478 * </ul> 2479 * 2480 * @see #get(Named) 2481 */ Rgb( @onNull @izemin = 1) String name, @NonNull @Size(9) float[] toXYZ, @NonNull TransferParameters function)2482 public Rgb( 2483 @NonNull @Size(min = 1) String name, 2484 @NonNull @Size(9) float[] toXYZ, 2485 @NonNull TransferParameters function) { 2486 // Note: when isGray() returns false, this passes null for the transform for 2487 // consistency with other constructors, which compute the transform from the primaries 2488 // and white point. 2489 this(name, isGray(toXYZ) ? GRAY_PRIMARIES : computePrimaries(toXYZ), 2490 computeWhitePoint(toXYZ), isGray(toXYZ) ? toXYZ : null, function, MIN_ID); 2491 } 2492 2493 /** 2494 * <p>Creates a new RGB color space using a specified set of primaries 2495 * and a specified white point.</p> 2496 * 2497 * <p>The primaries and white point can be specified in the CIE xyY space 2498 * or in CIE XYZ. The length of the arrays depends on the chosen space:</p> 2499 * 2500 * <table summary="Parameters length"> 2501 * <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr> 2502 * <tr><td>xyY</td><td>6</td><td>2</td></tr> 2503 * <tr><td>XYZ</td><td>9</td><td>3</td></tr> 2504 * </table> 2505 * 2506 * <p>When the primaries and/or white point are specified in xyY, the Y component 2507 * does not need to be specified and is assumed to be 1.0. Only the xy components 2508 * are required.</p> 2509 * 2510 * @param name Name of the color space, cannot be null, its length must be >= 1 2511 * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats 2512 * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats 2513 * @param function Parameters for the transfer functions 2514 * 2515 * @throws IllegalArgumentException If any of the following conditions is met: 2516 * <ul> 2517 * <li>The name is null or has a length of 0.</li> 2518 * <li>The primaries array is null or has a length that is neither 6 or 9.</li> 2519 * <li>The white point array is null or has a length that is neither 2 or 3.</li> 2520 * <li>The transfer parameters are invalid.</li> 2521 * </ul> 2522 * 2523 * @see #get(Named) 2524 */ Rgb( @onNull @izemin = 1) String name, @NonNull @Size(min = 6, max = 9) float[] primaries, @NonNull @Size(min = 2, max = 3) float[] whitePoint, @NonNull TransferParameters function)2525 public Rgb( 2526 @NonNull @Size(min = 1) String name, 2527 @NonNull @Size(min = 6, max = 9) float[] primaries, 2528 @NonNull @Size(min = 2, max = 3) float[] whitePoint, 2529 @NonNull TransferParameters function) { 2530 this(name, primaries, whitePoint, null, function, MIN_ID); 2531 } 2532 2533 /** 2534 * <p>Creates a new RGB color space using a specified set of primaries 2535 * and a specified white point.</p> 2536 * 2537 * <p>The primaries and white point can be specified in the CIE xyY space 2538 * or in CIE XYZ. The length of the arrays depends on the chosen space:</p> 2539 * 2540 * <table summary="Parameters length"> 2541 * <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr> 2542 * <tr><td>xyY</td><td>6</td><td>2</td></tr> 2543 * <tr><td>XYZ</td><td>9</td><td>3</td></tr> 2544 * </table> 2545 * 2546 * <p>When the primaries and/or white point are specified in xyY, the Y component 2547 * does not need to be specified and is assumed to be 1.0. Only the xy components 2548 * are required.</p> 2549 * 2550 * @param name Name of the color space, cannot be null, its length must be >= 1 2551 * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats 2552 * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats 2553 * @param transform Computed transform matrix that converts from RGB to XYZ, or 2554 * {@code null} to compute it from {@code primaries} and {@code whitePoint}. 2555 * @param function Parameters for the transfer functions 2556 * @param id ID of this color space as an integer between {@link #MIN_ID} and {@link #MAX_ID} 2557 * 2558 * @throws IllegalArgumentException If any of the following conditions is met: 2559 * <ul> 2560 * <li>The name is null or has a length of 0.</li> 2561 * <li>The primaries array is null or has a length that is neither 6 or 9.</li> 2562 * <li>The white point array is null or has a length that is neither 2 or 3.</li> 2563 * <li>The ID is not between {@link #MIN_ID} and {@link #MAX_ID}.</li> 2564 * <li>The transfer parameters are invalid.</li> 2565 * </ul> 2566 * 2567 * @see #get(Named) 2568 */ Rgb( @onNull @izemin = 1) String name, @NonNull @Size(min = 6, max = 9) float[] primaries, @NonNull @Size(min = 2, max = 3) float[] whitePoint, @Nullable @Size(9) float[] transform, @NonNull TransferParameters function, @IntRange(from = MIN_ID, to = MAX_ID) int id)2569 private Rgb( 2570 @NonNull @Size(min = 1) String name, 2571 @NonNull @Size(min = 6, max = 9) float[] primaries, 2572 @NonNull @Size(min = 2, max = 3) float[] whitePoint, 2573 @Nullable @Size(9) float[] transform, 2574 @NonNull TransferParameters function, 2575 @IntRange(from = MIN_ID, to = MAX_ID) int id) { 2576 this(name, primaries, whitePoint, transform, 2577 function.e == 0.0 && function.f == 0.0 ? 2578 x -> rcpResponse(x, function.a, function.b, 2579 function.c, function.d, function.g) : 2580 x -> rcpResponse(x, function.a, function.b, function.c, 2581 function.d, function.e, function.f, function.g), 2582 function.e == 0.0 && function.f == 0.0 ? 2583 x -> response(x, function.a, function.b, 2584 function.c, function.d, function.g) : 2585 x -> response(x, function.a, function.b, function.c, 2586 function.d, function.e, function.f, function.g), 2587 0.0f, 1.0f, function, id); 2588 } 2589 2590 /** 2591 * <p>Creates a new RGB color space using a 3x3 column-major transform matrix. 2592 * The transform matrix must convert from the RGB space to the profile connection 2593 * space CIE XYZ.</p> 2594 * 2595 * <p class="note">The range of the color space is imposed to be \([0..1]\).</p> 2596 * 2597 * @param name Name of the color space, cannot be null, its length must be >= 1 2598 * @param toXYZ 3x3 column-major transform matrix from RGB to the profile 2599 * connection space CIE XYZ as an array of 9 floats, cannot be null 2600 * @param gamma Gamma to use as the transfer function 2601 * 2602 * @throws IllegalArgumentException If any of the following conditions is met: 2603 * <ul> 2604 * <li>The name is null or has a length of 0.</li> 2605 * <li>Gamma is negative.</li> 2606 * </ul> 2607 * 2608 * @see #get(Named) 2609 */ Rgb( @onNull @izemin = 1) String name, @NonNull @Size(9) float[] toXYZ, double gamma)2610 public Rgb( 2611 @NonNull @Size(min = 1) String name, 2612 @NonNull @Size(9) float[] toXYZ, 2613 double gamma) { 2614 this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ), gamma, 0.0f, 1.0f, MIN_ID); 2615 } 2616 2617 /** 2618 * <p>Creates a new RGB color space using a specified set of primaries 2619 * and a specified white point.</p> 2620 * 2621 * <p>The primaries and white point can be specified in the CIE xyY space 2622 * or in CIE XYZ. The length of the arrays depends on the chosen space:</p> 2623 * 2624 * <table summary="Parameters length"> 2625 * <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr> 2626 * <tr><td>xyY</td><td>6</td><td>2</td></tr> 2627 * <tr><td>XYZ</td><td>9</td><td>3</td></tr> 2628 * </table> 2629 * 2630 * <p>When the primaries and/or white point are specified in xyY, the Y component 2631 * does not need to be specified and is assumed to be 1.0. Only the xy components 2632 * are required.</p> 2633 * 2634 * @param name Name of the color space, cannot be null, its length must be >= 1 2635 * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats 2636 * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats 2637 * @param gamma Gamma to use as the transfer function 2638 * 2639 * @throws IllegalArgumentException If any of the following conditions is met: 2640 * <ul> 2641 * <li>The name is null or has a length of 0.</li> 2642 * <li>The primaries array is null or has a length that is neither 6 or 9.</li> 2643 * <li>The white point array is null or has a length that is neither 2 or 3.</li> 2644 * <li>Gamma is negative.</li> 2645 * </ul> 2646 * 2647 * @see #get(Named) 2648 */ Rgb( @onNull @izemin = 1) String name, @NonNull @Size(min = 6, max = 9) float[] primaries, @NonNull @Size(min = 2, max = 3) float[] whitePoint, double gamma)2649 public Rgb( 2650 @NonNull @Size(min = 1) String name, 2651 @NonNull @Size(min = 6, max = 9) float[] primaries, 2652 @NonNull @Size(min = 2, max = 3) float[] whitePoint, 2653 double gamma) { 2654 this(name, primaries, whitePoint, gamma, 0.0f, 1.0f, MIN_ID); 2655 } 2656 2657 /** 2658 * <p>Creates a new RGB color space using a specified set of primaries 2659 * and a specified white point.</p> 2660 * 2661 * <p>The primaries and white point can be specified in the CIE xyY space 2662 * or in CIE XYZ. The length of the arrays depends on the chosen space:</p> 2663 * 2664 * <table summary="Parameters length"> 2665 * <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr> 2666 * <tr><td>xyY</td><td>6</td><td>2</td></tr> 2667 * <tr><td>XYZ</td><td>9</td><td>3</td></tr> 2668 * </table> 2669 * 2670 * <p>When the primaries and/or white point are specified in xyY, the Y component 2671 * does not need to be specified and is assumed to be 1.0. Only the xy components 2672 * are required.</p> 2673 * 2674 * @param name Name of the color space, cannot be null, its length must be >= 1 2675 * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats 2676 * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats 2677 * @param gamma Gamma to use as the transfer function 2678 * @param min The minimum valid value in this color space's RGB range 2679 * @param max The maximum valid value in this color space's RGB range 2680 * @param id ID of this color space as an integer between {@link #MIN_ID} and {@link #MAX_ID} 2681 * 2682 * @throws IllegalArgumentException If any of the following conditions is met: 2683 * <ul> 2684 * <li>The name is null or has a length of 0.</li> 2685 * <li>The primaries array is null or has a length that is neither 6 or 9.</li> 2686 * <li>The white point array is null or has a length that is neither 2 or 3.</li> 2687 * <li>The minimum valid value is >= the maximum valid value.</li> 2688 * <li>The ID is not between {@link #MIN_ID} and {@link #MAX_ID}.</li> 2689 * <li>Gamma is negative.</li> 2690 * </ul> 2691 * 2692 * @see #get(Named) 2693 */ Rgb( @onNull @izemin = 1) String name, @NonNull @Size(min = 6, max = 9) float[] primaries, @NonNull @Size(min = 2, max = 3) float[] whitePoint, double gamma, float min, float max, @IntRange(from = MIN_ID, to = MAX_ID) int id)2694 private Rgb( 2695 @NonNull @Size(min = 1) String name, 2696 @NonNull @Size(min = 6, max = 9) float[] primaries, 2697 @NonNull @Size(min = 2, max = 3) float[] whitePoint, 2698 double gamma, 2699 float min, 2700 float max, 2701 @IntRange(from = MIN_ID, to = MAX_ID) int id) { 2702 this(name, primaries, whitePoint, null, 2703 gamma == 1.0 ? DoubleUnaryOperator.identity() : 2704 x -> Math.pow(x < 0.0 ? 0.0 : x, 1 / gamma), 2705 gamma == 1.0 ? DoubleUnaryOperator.identity() : 2706 x -> Math.pow(x < 0.0 ? 0.0 : x, gamma), 2707 min, max, new TransferParameters(1.0, 0.0, 0.0, 0.0, gamma), id); 2708 } 2709 2710 /** 2711 * <p>Creates a new RGB color space using a specified set of primaries 2712 * and a specified white point.</p> 2713 * 2714 * <p>The primaries and white point can be specified in the CIE xyY space 2715 * or in CIE XYZ. The length of the arrays depends on the chosen space:</p> 2716 * 2717 * <table summary="Parameters length"> 2718 * <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr> 2719 * <tr><td>xyY</td><td>6</td><td>2</td></tr> 2720 * <tr><td>XYZ</td><td>9</td><td>3</td></tr> 2721 * </table> 2722 * 2723 * <p>When the primaries and/or white point are specified in xyY, the Y component 2724 * does not need to be specified and is assumed to be 1.0. Only the xy components 2725 * are required.</p> 2726 * 2727 * @param name Name of the color space, cannot be null, its length must be >= 1 2728 * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats 2729 * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats 2730 * @param transform Computed transform matrix that converts from RGB to XYZ, or 2731 * {@code null} to compute it from {@code primaries} and {@code whitePoint}. 2732 * @param oetf Opto-electronic transfer function, cannot be null 2733 * @param eotf Electro-optical transfer function, cannot be null 2734 * @param min The minimum valid value in this color space's RGB range 2735 * @param max The maximum valid value in this color space's RGB range 2736 * @param transferParameters Parameters for the transfer functions 2737 * @param id ID of this color space as an integer between {@link #MIN_ID} and {@link #MAX_ID} 2738 * 2739 * @throws IllegalArgumentException If any of the following conditions is met: 2740 * <ul> 2741 * <li>The name is null or has a length of 0.</li> 2742 * <li>The primaries array is null or has a length that is neither 6 or 9.</li> 2743 * <li>The white point array is null or has a length that is neither 2 or 3.</li> 2744 * <li>The OETF is null or the EOTF is null.</li> 2745 * <li>The minimum valid value is >= the maximum valid value.</li> 2746 * <li>The ID is not between {@link #MIN_ID} and {@link #MAX_ID}.</li> 2747 * </ul> 2748 * 2749 * @see #get(Named) 2750 */ Rgb( @onNull @izemin = 1) String name, @NonNull @Size(min = 6, max = 9) float[] primaries, @NonNull @Size(min = 2, max = 3) float[] whitePoint, @Nullable @Size(9) float[] transform, @NonNull DoubleUnaryOperator oetf, @NonNull DoubleUnaryOperator eotf, float min, float max, @Nullable TransferParameters transferParameters, @IntRange(from = MIN_ID, to = MAX_ID) int id)2751 private Rgb( 2752 @NonNull @Size(min = 1) String name, 2753 @NonNull @Size(min = 6, max = 9) float[] primaries, 2754 @NonNull @Size(min = 2, max = 3) float[] whitePoint, 2755 @Nullable @Size(9) float[] transform, 2756 @NonNull DoubleUnaryOperator oetf, 2757 @NonNull DoubleUnaryOperator eotf, 2758 float min, 2759 float max, 2760 @Nullable TransferParameters transferParameters, 2761 @IntRange(from = MIN_ID, to = MAX_ID) int id) { 2762 2763 super(name, Model.RGB, id); 2764 2765 if (primaries == null || (primaries.length != 6 && primaries.length != 9)) { 2766 throw new IllegalArgumentException("The color space's primaries must be " + 2767 "defined as an array of 6 floats in xyY or 9 floats in XYZ"); 2768 } 2769 2770 if (whitePoint == null || (whitePoint.length != 2 && whitePoint.length != 3)) { 2771 throw new IllegalArgumentException("The color space's white point must be " + 2772 "defined as an array of 2 floats in xyY or 3 float in XYZ"); 2773 } 2774 2775 if (oetf == null || eotf == null) { 2776 throw new IllegalArgumentException("The transfer functions of a color space " + 2777 "cannot be null"); 2778 } 2779 2780 if (min >= max) { 2781 throw new IllegalArgumentException("Invalid range: min=" + min + ", max=" + max + 2782 "; min must be strictly < max"); 2783 } 2784 2785 mWhitePoint = xyWhitePoint(whitePoint); 2786 mPrimaries = xyPrimaries(primaries); 2787 2788 if (transform == null) { 2789 mTransform = computeXYZMatrix(mPrimaries, mWhitePoint); 2790 } else { 2791 if (transform.length != 9) { 2792 throw new IllegalArgumentException("Transform must have 9 entries! Has " 2793 + transform.length); 2794 } 2795 mTransform = transform; 2796 } 2797 mInverseTransform = inverse3x3(mTransform); 2798 2799 mOetf = oetf; 2800 mEotf = eotf; 2801 2802 mMin = min; 2803 mMax = max; 2804 2805 DoubleUnaryOperator clamp = this::clamp; 2806 mClampedOetf = oetf.andThen(clamp); 2807 mClampedEotf = clamp.andThen(eotf); 2808 2809 mTransferParameters = transferParameters; 2810 2811 // A color space is wide-gamut if its area is >90% of NTSC 1953 and 2812 // if it entirely contains the Color space definition in xyY 2813 mIsWideGamut = isWideGamut(mPrimaries, min, max); 2814 mIsSrgb = isSrgb(mPrimaries, mWhitePoint, oetf, eotf, min, max, id); 2815 2816 if (mTransferParameters != null) { 2817 if (mWhitePoint == null || mTransform == null) { 2818 throw new IllegalStateException( 2819 "ColorSpace (" + this + ") cannot create native object! mWhitePoint: " 2820 + mWhitePoint + " mTransform: " + mTransform); 2821 } 2822 2823 // This mimics the old code that was in native. 2824 float[] nativeTransform = adaptToIlluminantD50(mWhitePoint, mTransform); 2825 mNativePtr = nativeCreate((float) mTransferParameters.a, 2826 (float) mTransferParameters.b, 2827 (float) mTransferParameters.c, 2828 (float) mTransferParameters.d, 2829 (float) mTransferParameters.e, 2830 (float) mTransferParameters.f, 2831 (float) mTransferParameters.g, 2832 nativeTransform); 2833 NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativePtr); 2834 } else { 2835 mNativePtr = 0; 2836 } 2837 } 2838 2839 private static class NoImagePreloadHolder { 2840 public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( 2841 ColorSpace.Rgb.class.getClassLoader(), nativeGetNativeFinalizer(), 0); 2842 } 2843 2844 /** 2845 * Creates a copy of the specified color space with a new transform. 2846 * 2847 * @param colorSpace The color space to create a copy of 2848 */ Rgb(Rgb colorSpace, @NonNull @Size(9) float[] transform, @NonNull @Size(min = 2, max = 3) float[] whitePoint)2849 private Rgb(Rgb colorSpace, 2850 @NonNull @Size(9) float[] transform, 2851 @NonNull @Size(min = 2, max = 3) float[] whitePoint) { 2852 this(colorSpace.getName(), colorSpace.mPrimaries, whitePoint, transform, 2853 colorSpace.mOetf, colorSpace.mEotf, colorSpace.mMin, colorSpace.mMax, 2854 colorSpace.mTransferParameters, MIN_ID); 2855 } 2856 2857 /** 2858 * Copies the non-adapted CIE xyY white point of this color space in 2859 * specified array. The Y component is assumed to be 1 and is therefore 2860 * not copied into the destination. The x and y components are written 2861 * in the array at positions 0 and 1 respectively. 2862 * 2863 * @param whitePoint The destination array, cannot be null, its length 2864 * must be >= 2 2865 * 2866 * @return The destination array passed as a parameter 2867 * 2868 * @see #getWhitePoint() 2869 */ 2870 @NonNull 2871 @Size(min = 2) getWhitePoint(@onNull @izemin = 2) float[] whitePoint)2872 public float[] getWhitePoint(@NonNull @Size(min = 2) float[] whitePoint) { 2873 whitePoint[0] = mWhitePoint[0]; 2874 whitePoint[1] = mWhitePoint[1]; 2875 return whitePoint; 2876 } 2877 2878 /** 2879 * Returns the non-adapted CIE xyY white point of this color space as 2880 * a new array of 2 floats. The Y component is assumed to be 1 and is 2881 * therefore not copied into the destination. The x and y components 2882 * are written in the array at positions 0 and 1 respectively. 2883 * 2884 * @return A new non-null array of 2 floats 2885 * 2886 * @see #getWhitePoint(float[]) 2887 */ 2888 @NonNull 2889 @Size(2) getWhitePoint()2890 public float[] getWhitePoint() { 2891 return Arrays.copyOf(mWhitePoint, mWhitePoint.length); 2892 } 2893 2894 /** 2895 * Copies the primaries of this color space in specified array. The Y 2896 * component is assumed to be 1 and is therefore not copied into the 2897 * destination. The x and y components of the first primary are written 2898 * in the array at positions 0 and 1 respectively. 2899 * 2900 * <p>Note: Some ColorSpaces represent gray profiles. The concept of 2901 * primaries for such a ColorSpace does not make sense, so we use a special 2902 * set of primaries that are all 1s.</p> 2903 * 2904 * @param primaries The destination array, cannot be null, its length 2905 * must be >= 6 2906 * 2907 * @return The destination array passed as a parameter 2908 * 2909 * @see #getPrimaries() 2910 */ 2911 @NonNull 2912 @Size(min = 6) getPrimaries(@onNull @izemin = 6) float[] primaries)2913 public float[] getPrimaries(@NonNull @Size(min = 6) float[] primaries) { 2914 System.arraycopy(mPrimaries, 0, primaries, 0, mPrimaries.length); 2915 return primaries; 2916 } 2917 2918 /** 2919 * Returns the primaries of this color space as a new array of 6 floats. 2920 * The Y component is assumed to be 1 and is therefore not copied into 2921 * the destination. The x and y components of the first primary are 2922 * written in the array at positions 0 and 1 respectively. 2923 * 2924 * <p>Note: Some ColorSpaces represent gray profiles. The concept of 2925 * primaries for such a ColorSpace does not make sense, so we use a special 2926 * set of primaries that are all 1s.</p> 2927 * 2928 * @return A new non-null array of 2 floats 2929 * 2930 * @see #getPrimaries(float[]) 2931 */ 2932 @NonNull 2933 @Size(6) getPrimaries()2934 public float[] getPrimaries() { 2935 return Arrays.copyOf(mPrimaries, mPrimaries.length); 2936 } 2937 2938 /** 2939 * <p>Copies the transform of this color space in specified array. The 2940 * transform is used to convert from RGB to XYZ (with the same white 2941 * point as this color space). To connect color spaces, you must first 2942 * {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them to the 2943 * same white point.</p> 2944 * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)} 2945 * to convert between color spaces.</p> 2946 * 2947 * @param transform The destination array, cannot be null, its length 2948 * must be >= 9 2949 * 2950 * @return The destination array passed as a parameter 2951 * 2952 * @see #getTransform() 2953 */ 2954 @NonNull 2955 @Size(min = 9) getTransform(@onNull @izemin = 9) float[] transform)2956 public float[] getTransform(@NonNull @Size(min = 9) float[] transform) { 2957 System.arraycopy(mTransform, 0, transform, 0, mTransform.length); 2958 return transform; 2959 } 2960 2961 /** 2962 * <p>Returns the transform of this color space as a new array. The 2963 * transform is used to convert from RGB to XYZ (with the same white 2964 * point as this color space). To connect color spaces, you must first 2965 * {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them to the 2966 * same white point.</p> 2967 * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)} 2968 * to convert between color spaces.</p> 2969 * 2970 * @return A new array of 9 floats 2971 * 2972 * @see #getTransform(float[]) 2973 */ 2974 @NonNull 2975 @Size(9) getTransform()2976 public float[] getTransform() { 2977 return Arrays.copyOf(mTransform, mTransform.length); 2978 } 2979 2980 /** 2981 * <p>Copies the inverse transform of this color space in specified array. 2982 * The inverse transform is used to convert from XYZ to RGB (with the 2983 * same white point as this color space). To connect color spaces, you 2984 * must first {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them 2985 * to the same white point.</p> 2986 * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)} 2987 * to convert between color spaces.</p> 2988 * 2989 * @param inverseTransform The destination array, cannot be null, its length 2990 * must be >= 9 2991 * 2992 * @return The destination array passed as a parameter 2993 * 2994 * @see #getInverseTransform() 2995 */ 2996 @NonNull 2997 @Size(min = 9) getInverseTransform(@onNull @izemin = 9) float[] inverseTransform)2998 public float[] getInverseTransform(@NonNull @Size(min = 9) float[] inverseTransform) { 2999 System.arraycopy(mInverseTransform, 0, inverseTransform, 0, mInverseTransform.length); 3000 return inverseTransform; 3001 } 3002 3003 /** 3004 * <p>Returns the inverse transform of this color space as a new array. 3005 * The inverse transform is used to convert from XYZ to RGB (with the 3006 * same white point as this color space). To connect color spaces, you 3007 * must first {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them 3008 * to the same white point.</p> 3009 * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)} 3010 * to convert between color spaces.</p> 3011 * 3012 * @return A new array of 9 floats 3013 * 3014 * @see #getInverseTransform(float[]) 3015 */ 3016 @NonNull 3017 @Size(9) getInverseTransform()3018 public float[] getInverseTransform() { 3019 return Arrays.copyOf(mInverseTransform, mInverseTransform.length); 3020 } 3021 3022 /** 3023 * <p>Returns the opto-electronic transfer function (OETF) of this color space. 3024 * The inverse function is the electro-optical transfer function (EOTF) returned 3025 * by {@link #getEotf()}. These functions are defined to satisfy the following 3026 * equality for \(x \in [0..1]\):</p> 3027 * 3028 * $$OETF(EOTF(x)) = EOTF(OETF(x)) = x$$ 3029 * 3030 * <p>For RGB colors, this function can be used to convert from linear space 3031 * to "gamma space" (gamma encoded). The terms gamma space and gamma encoded 3032 * are frequently used because many OETFs can be closely approximated using 3033 * a simple power function of the form \(x^{\frac{1}{\gamma}}\) (the 3034 * approximation of the {@link Named#SRGB sRGB} OETF uses \(\gamma=2.2\) 3035 * for instance).</p> 3036 * 3037 * @return A transfer function that converts from linear space to "gamma space" 3038 * 3039 * @see #getEotf() 3040 * @see #getTransferParameters() 3041 */ 3042 @NonNull getOetf()3043 public DoubleUnaryOperator getOetf() { 3044 return mClampedOetf; 3045 } 3046 3047 /** 3048 * <p>Returns the electro-optical transfer function (EOTF) of this color space. 3049 * The inverse function is the opto-electronic transfer function (OETF) 3050 * returned by {@link #getOetf()}. These functions are defined to satisfy the 3051 * following equality for \(x \in [0..1]\):</p> 3052 * 3053 * $$OETF(EOTF(x)) = EOTF(OETF(x)) = x$$ 3054 * 3055 * <p>For RGB colors, this function can be used to convert from "gamma space" 3056 * (gamma encoded) to linear space. The terms gamma space and gamma encoded 3057 * are frequently used because many EOTFs can be closely approximated using 3058 * a simple power function of the form \(x^\gamma\) (the approximation of the 3059 * {@link Named#SRGB sRGB} EOTF uses \(\gamma=2.2\) for instance).</p> 3060 * 3061 * @return A transfer function that converts from "gamma space" to linear space 3062 * 3063 * @see #getOetf() 3064 * @see #getTransferParameters() 3065 */ 3066 @NonNull getEotf()3067 public DoubleUnaryOperator getEotf() { 3068 return mClampedEotf; 3069 } 3070 3071 /** 3072 * <p>Returns the parameters used by the {@link #getEotf() electro-optical} 3073 * and {@link #getOetf() opto-electronic} transfer functions. If the transfer 3074 * functions do not match the ICC parametric curves defined in ICC.1:2004-10 3075 * (section 10.15), this method returns null.</p> 3076 * 3077 * <p>See {@link TransferParameters} for a full description of the transfer 3078 * functions.</p> 3079 * 3080 * @return An instance of {@link TransferParameters} or null if this color 3081 * space's transfer functions do not match the equation defined in 3082 * {@link TransferParameters} 3083 */ 3084 @Nullable getTransferParameters()3085 public TransferParameters getTransferParameters() { 3086 return mTransferParameters; 3087 } 3088 3089 @Override isSrgb()3090 public boolean isSrgb() { 3091 return mIsSrgb; 3092 } 3093 3094 @Override isWideGamut()3095 public boolean isWideGamut() { 3096 return mIsWideGamut; 3097 } 3098 3099 @Override getMinValue(int component)3100 public float getMinValue(int component) { 3101 return mMin; 3102 } 3103 3104 @Override getMaxValue(int component)3105 public float getMaxValue(int component) { 3106 return mMax; 3107 } 3108 3109 /** 3110 * <p>Decodes an RGB value to linear space. This is achieved by 3111 * applying this color space's electro-optical transfer function 3112 * to the supplied values.</p> 3113 * 3114 * <p>Refer to the documentation of {@link ColorSpace.Rgb} for 3115 * more information about transfer functions and their use for 3116 * encoding and decoding RGB values.</p> 3117 * 3118 * @param r The red component to decode to linear space 3119 * @param g The green component to decode to linear space 3120 * @param b The blue component to decode to linear space 3121 * @return A new array of 3 floats containing linear RGB values 3122 * 3123 * @see #toLinear(float[]) 3124 * @see #fromLinear(float, float, float) 3125 */ 3126 @NonNull 3127 @Size(3) toLinear(float r, float g, float b)3128 public float[] toLinear(float r, float g, float b) { 3129 return toLinear(new float[] { r, g, b }); 3130 } 3131 3132 /** 3133 * <p>Decodes an RGB value to linear space. This is achieved by 3134 * applying this color space's electro-optical transfer function 3135 * to the first 3 values of the supplied array. The result is 3136 * stored back in the input array.</p> 3137 * 3138 * <p>Refer to the documentation of {@link ColorSpace.Rgb} for 3139 * more information about transfer functions and their use for 3140 * encoding and decoding RGB values.</p> 3141 * 3142 * @param v A non-null array of non-linear RGB values, its length 3143 * must be at least 3 3144 * @return The specified array 3145 * 3146 * @see #toLinear(float, float, float) 3147 * @see #fromLinear(float[]) 3148 */ 3149 @NonNull 3150 @Size(min = 3) toLinear(@onNull @izemin = 3) float[] v)3151 public float[] toLinear(@NonNull @Size(min = 3) float[] v) { 3152 v[0] = (float) mClampedEotf.applyAsDouble(v[0]); 3153 v[1] = (float) mClampedEotf.applyAsDouble(v[1]); 3154 v[2] = (float) mClampedEotf.applyAsDouble(v[2]); 3155 return v; 3156 } 3157 3158 /** 3159 * <p>Encodes an RGB value from linear space to this color space's 3160 * "gamma space". This is achieved by applying this color space's 3161 * opto-electronic transfer function to the supplied values.</p> 3162 * 3163 * <p>Refer to the documentation of {@link ColorSpace.Rgb} for 3164 * more information about transfer functions and their use for 3165 * encoding and decoding RGB values.</p> 3166 * 3167 * @param r The red component to encode from linear space 3168 * @param g The green component to encode from linear space 3169 * @param b The blue component to encode from linear space 3170 * @return A new array of 3 floats containing non-linear RGB values 3171 * 3172 * @see #fromLinear(float[]) 3173 * @see #toLinear(float, float, float) 3174 */ 3175 @NonNull 3176 @Size(3) fromLinear(float r, float g, float b)3177 public float[] fromLinear(float r, float g, float b) { 3178 return fromLinear(new float[] { r, g, b }); 3179 } 3180 3181 /** 3182 * <p>Encodes an RGB value from linear space to this color space's 3183 * "gamma space". This is achieved by applying this color space's 3184 * opto-electronic transfer function to the first 3 values of the 3185 * supplied array. The result is stored back in the input array.</p> 3186 * 3187 * <p>Refer to the documentation of {@link ColorSpace.Rgb} for 3188 * more information about transfer functions and their use for 3189 * encoding and decoding RGB values.</p> 3190 * 3191 * @param v A non-null array of linear RGB values, its length 3192 * must be at least 3 3193 * @return A new array of 3 floats containing non-linear RGB values 3194 * 3195 * @see #fromLinear(float[]) 3196 * @see #toLinear(float, float, float) 3197 */ 3198 @NonNull 3199 @Size(min = 3) fromLinear(@onNull @izemin = 3) float[] v)3200 public float[] fromLinear(@NonNull @Size(min = 3) float[] v) { 3201 v[0] = (float) mClampedOetf.applyAsDouble(v[0]); 3202 v[1] = (float) mClampedOetf.applyAsDouble(v[1]); 3203 v[2] = (float) mClampedOetf.applyAsDouble(v[2]); 3204 return v; 3205 } 3206 3207 @Override 3208 @NonNull 3209 @Size(min = 3) toXyz(@onNull @izemin = 3) float[] v)3210 public float[] toXyz(@NonNull @Size(min = 3) float[] v) { 3211 v[0] = (float) mClampedEotf.applyAsDouble(v[0]); 3212 v[1] = (float) mClampedEotf.applyAsDouble(v[1]); 3213 v[2] = (float) mClampedEotf.applyAsDouble(v[2]); 3214 return mul3x3Float3(mTransform, v); 3215 } 3216 3217 @Override 3218 @NonNull 3219 @Size(min = 3) fromXyz(@onNull @izemin = 3) float[] v)3220 public float[] fromXyz(@NonNull @Size(min = 3) float[] v) { 3221 mul3x3Float3(mInverseTransform, v); 3222 v[0] = (float) mClampedOetf.applyAsDouble(v[0]); 3223 v[1] = (float) mClampedOetf.applyAsDouble(v[1]); 3224 v[2] = (float) mClampedOetf.applyAsDouble(v[2]); 3225 return v; 3226 } 3227 clamp(double x)3228 private double clamp(double x) { 3229 return x < mMin ? mMin : x > mMax ? mMax : x; 3230 } 3231 3232 @Override equals(Object o)3233 public boolean equals(Object o) { 3234 if (this == o) return true; 3235 if (o == null || getClass() != o.getClass()) return false; 3236 if (!super.equals(o)) return false; 3237 3238 Rgb rgb = (Rgb) o; 3239 3240 if (Float.compare(rgb.mMin, mMin) != 0) return false; 3241 if (Float.compare(rgb.mMax, mMax) != 0) return false; 3242 if (!Arrays.equals(mWhitePoint, rgb.mWhitePoint)) return false; 3243 if (!Arrays.equals(mPrimaries, rgb.mPrimaries)) return false; 3244 if (mTransferParameters != null) { 3245 return mTransferParameters.equals(rgb.mTransferParameters); 3246 } else if (rgb.mTransferParameters == null) { 3247 return true; 3248 } 3249 //noinspection SimplifiableIfStatement 3250 if (!mOetf.equals(rgb.mOetf)) return false; 3251 return mEotf.equals(rgb.mEotf); 3252 } 3253 3254 @Override hashCode()3255 public int hashCode() { 3256 int result = super.hashCode(); 3257 result = 31 * result + Arrays.hashCode(mWhitePoint); 3258 result = 31 * result + Arrays.hashCode(mPrimaries); 3259 result = 31 * result + (mMin != +0.0f ? Float.floatToIntBits(mMin) : 0); 3260 result = 31 * result + (mMax != +0.0f ? Float.floatToIntBits(mMax) : 0); 3261 result = 31 * result + 3262 (mTransferParameters != null ? mTransferParameters.hashCode() : 0); 3263 if (mTransferParameters == null) { 3264 result = 31 * result + mOetf.hashCode(); 3265 result = 31 * result + mEotf.hashCode(); 3266 } 3267 return result; 3268 } 3269 3270 /** 3271 * Computes whether a color space is the sRGB color space or at least 3272 * a close approximation. 3273 * 3274 * @param primaries The set of RGB primaries in xyY as an array of 6 floats 3275 * @param whitePoint The white point in xyY as an array of 2 floats 3276 * @param OETF The opto-electronic transfer function 3277 * @param EOTF The electro-optical transfer function 3278 * @param min The minimum value of the color space's range 3279 * @param max The minimum value of the color space's range 3280 * @param id The ID of the color space 3281 * @return True if the color space can be considered as the sRGB color space 3282 * 3283 * @see #isSrgb() 3284 */ 3285 @SuppressWarnings("RedundantIfStatement") isSrgb( @onNull @ize6) float[] primaries, @NonNull @Size(2) float[] whitePoint, @NonNull DoubleUnaryOperator OETF, @NonNull DoubleUnaryOperator EOTF, float min, float max, @IntRange(from = MIN_ID, to = MAX_ID) int id)3286 private static boolean isSrgb( 3287 @NonNull @Size(6) float[] primaries, 3288 @NonNull @Size(2) float[] whitePoint, 3289 @NonNull DoubleUnaryOperator OETF, 3290 @NonNull DoubleUnaryOperator EOTF, 3291 float min, 3292 float max, 3293 @IntRange(from = MIN_ID, to = MAX_ID) int id) { 3294 if (id == 0) return true; 3295 if (!ColorSpace.compare(primaries, SRGB_PRIMARIES)) { 3296 return false; 3297 } 3298 if (!ColorSpace.compare(whitePoint, ILLUMINANT_D65)) { 3299 return false; 3300 } 3301 3302 if (min != 0.0f) return false; 3303 if (max != 1.0f) return false; 3304 3305 // We would have already returned true if this was SRGB itself, so 3306 // it is safe to reference it here. 3307 ColorSpace.Rgb srgb = (ColorSpace.Rgb) get(Named.SRGB); 3308 3309 for (double x = 0.0; x <= 1.0; x += 1 / 255.0) { 3310 if (!compare(x, OETF, srgb.mOetf)) return false; 3311 if (!compare(x, EOTF, srgb.mEotf)) return false; 3312 } 3313 3314 return true; 3315 } 3316 3317 /** 3318 * Report whether this matrix is a special gray matrix. 3319 * @param toXYZ A XYZD50 matrix. Skia uses a special form for a gray profile. 3320 * @return true if this is a special gray matrix. 3321 */ isGray(@onNull @ize9) float[] toXYZ)3322 private static boolean isGray(@NonNull @Size(9) float[] toXYZ) { 3323 return toXYZ.length == 9 && toXYZ[1] == 0 && toXYZ[2] == 0 && toXYZ[3] == 0 3324 && toXYZ[5] == 0 && toXYZ[6] == 0 && toXYZ[7] == 0; 3325 } 3326 compare(double point, @NonNull DoubleUnaryOperator a, @NonNull DoubleUnaryOperator b)3327 private static boolean compare(double point, @NonNull DoubleUnaryOperator a, 3328 @NonNull DoubleUnaryOperator b) { 3329 double rA = a.applyAsDouble(point); 3330 double rB = b.applyAsDouble(point); 3331 return Math.abs(rA - rB) <= 1e-3; 3332 } 3333 3334 /** 3335 * Computes whether the specified CIE xyY or XYZ primaries (with Y set to 1) form 3336 * a wide color gamut. A color gamut is considered wide if its area is > 90% 3337 * of the area of NTSC 1953 and if it contains the sRGB color gamut entirely. 3338 * If the conditions above are not met, the color space is considered as having 3339 * a wide color gamut if its range is larger than [0..1]. 3340 * 3341 * @param primaries RGB primaries in CIE xyY as an array of 6 floats 3342 * @param min The minimum value of the color space's range 3343 * @param max The minimum value of the color space's range 3344 * @return True if the color space has a wide gamut, false otherwise 3345 * 3346 * @see #isWideGamut() 3347 * @see #area(float[]) 3348 */ isWideGamut(@onNull @ize6) float[] primaries, float min, float max)3349 private static boolean isWideGamut(@NonNull @Size(6) float[] primaries, 3350 float min, float max) { 3351 return (area(primaries) / area(NTSC_1953_PRIMARIES) > 0.9f && 3352 contains(primaries, SRGB_PRIMARIES)) || (min < 0.0f && max > 1.0f); 3353 } 3354 3355 /** 3356 * Computes the area of the triangle represented by a set of RGB primaries 3357 * in the CIE xyY space. 3358 * 3359 * @param primaries The triangle's vertices, as RGB primaries in an array of 6 floats 3360 * @return The area of the triangle 3361 * 3362 * @see #isWideGamut(float[], float, float) 3363 */ area(@onNull @ize6) float[] primaries)3364 private static float area(@NonNull @Size(6) float[] primaries) { 3365 float Rx = primaries[0]; 3366 float Ry = primaries[1]; 3367 float Gx = primaries[2]; 3368 float Gy = primaries[3]; 3369 float Bx = primaries[4]; 3370 float By = primaries[5]; 3371 float det = Rx * Gy + Ry * Bx + Gx * By - Gy * Bx - Ry * Gx - Rx * By; 3372 float r = 0.5f * det; 3373 return r < 0.0f ? -r : r; 3374 } 3375 3376 /** 3377 * Computes the cross product of two 2D vectors. 3378 * 3379 * @param ax The x coordinate of the first vector 3380 * @param ay The y coordinate of the first vector 3381 * @param bx The x coordinate of the second vector 3382 * @param by The y coordinate of the second vector 3383 * @return The result of a x b 3384 */ cross(float ax, float ay, float bx, float by)3385 private static float cross(float ax, float ay, float bx, float by) { 3386 return ax * by - ay * bx; 3387 } 3388 3389 /** 3390 * Decides whether a 2D triangle, identified by the 6 coordinates of its 3391 * 3 vertices, is contained within another 2D triangle, also identified 3392 * by the 6 coordinates of its 3 vertices. 3393 * 3394 * In the illustration below, we want to test whether the RGB triangle 3395 * is contained within the triangle XYZ formed by the 3 vertices at 3396 * the "+" locations. 3397 * 3398 * Y . 3399 * . + . 3400 * . .. 3401 * . . 3402 * . . 3403 * . G 3404 * * 3405 * * * 3406 * ** * 3407 * * ** 3408 * * * 3409 * ** * 3410 * * * 3411 * * * 3412 * ** * 3413 * * * 3414 * * ** 3415 * ** * R ... 3416 * * * ..... 3417 * * ***** .. 3418 * ** ************ . + 3419 * B * ************ . X 3420 * ......***** . 3421 * ...... . . 3422 * .. 3423 * + . 3424 * Z . 3425 * 3426 * RGB is contained within XYZ if all the following conditions are true 3427 * (with "x" the cross product operator): 3428 * 3429 * --> --> 3430 * GR x RX >= 0 3431 * --> --> 3432 * RX x BR >= 0 3433 * --> --> 3434 * RG x GY >= 0 3435 * --> --> 3436 * GY x RG >= 0 3437 * --> --> 3438 * RB x BZ >= 0 3439 * --> --> 3440 * BZ x GB >= 0 3441 * 3442 * @param p1 The enclosing triangle 3443 * @param p2 The enclosed triangle 3444 * @return True if the triangle p1 contains the triangle p2 3445 * 3446 * @see #isWideGamut(float[], float, float) 3447 */ 3448 @SuppressWarnings("RedundantIfStatement") contains(@onNull @ize6) float[] p1, @NonNull @Size(6) float[] p2)3449 private static boolean contains(@NonNull @Size(6) float[] p1, @NonNull @Size(6) float[] p2) { 3450 // Translate the vertices p1 in the coordinates system 3451 // with the vertices p2 as the origin 3452 float[] p0 = new float[] { 3453 p1[0] - p2[0], p1[1] - p2[1], 3454 p1[2] - p2[2], p1[3] - p2[3], 3455 p1[4] - p2[4], p1[5] - p2[5], 3456 }; 3457 // Check the first vertex of p1 3458 if (cross(p0[0], p0[1], p2[0] - p2[4], p2[1] - p2[5]) < 0 || 3459 cross(p2[0] - p2[2], p2[1] - p2[3], p0[0], p0[1]) < 0) { 3460 return false; 3461 } 3462 // Check the second vertex of p1 3463 if (cross(p0[2], p0[3], p2[2] - p2[0], p2[3] - p2[1]) < 0 || 3464 cross(p2[2] - p2[4], p2[3] - p2[5], p0[2], p0[3]) < 0) { 3465 return false; 3466 } 3467 // Check the third vertex of p1 3468 if (cross(p0[4], p0[5], p2[4] - p2[2], p2[5] - p2[3]) < 0 || 3469 cross(p2[4] - p2[0], p2[5] - p2[1], p0[4], p0[5]) < 0) { 3470 return false; 3471 } 3472 return true; 3473 } 3474 3475 /** 3476 * Computes the primaries of a color space identified only by 3477 * its RGB->XYZ transform matrix. This method assumes that the 3478 * range of the color space is [0..1]. 3479 * 3480 * @param toXYZ The color space's 3x3 transform matrix to XYZ 3481 * @return A new array of 6 floats containing the color space's 3482 * primaries in CIE xyY 3483 */ 3484 @NonNull 3485 @Size(6) computePrimaries(@onNull @ize9) float[] toXYZ)3486 private static float[] computePrimaries(@NonNull @Size(9) float[] toXYZ) { 3487 float[] r = mul3x3Float3(toXYZ, new float[] { 1.0f, 0.0f, 0.0f }); 3488 float[] g = mul3x3Float3(toXYZ, new float[] { 0.0f, 1.0f, 0.0f }); 3489 float[] b = mul3x3Float3(toXYZ, new float[] { 0.0f, 0.0f, 1.0f }); 3490 3491 float rSum = r[0] + r[1] + r[2]; 3492 float gSum = g[0] + g[1] + g[2]; 3493 float bSum = b[0] + b[1] + b[2]; 3494 3495 return new float[] { 3496 r[0] / rSum, r[1] / rSum, 3497 g[0] / gSum, g[1] / gSum, 3498 b[0] / bSum, b[1] / bSum, 3499 }; 3500 } 3501 3502 /** 3503 * Computes the white point of a color space identified only by 3504 * its RGB->XYZ transform matrix. This method assumes that the 3505 * range of the color space is [0..1]. 3506 * 3507 * @param toXYZ The color space's 3x3 transform matrix to XYZ 3508 * @return A new array of 2 floats containing the color space's 3509 * white point in CIE xyY 3510 */ 3511 @NonNull 3512 @Size(2) computeWhitePoint(@onNull @ize9) float[] toXYZ)3513 private static float[] computeWhitePoint(@NonNull @Size(9) float[] toXYZ) { 3514 float[] w = mul3x3Float3(toXYZ, new float[] { 1.0f, 1.0f, 1.0f }); 3515 float sum = w[0] + w[1] + w[2]; 3516 return new float[] { w[0] / sum, w[1] / sum }; 3517 } 3518 3519 /** 3520 * Converts the specified RGB primaries point to xyY if needed. The primaries 3521 * can be specified as an array of 6 floats (in CIE xyY) or 9 floats 3522 * (in CIE XYZ). If no conversion is needed, the input array is copied. 3523 * 3524 * @param primaries The primaries in xyY or XYZ 3525 * @return A new array of 6 floats containing the primaries in xyY 3526 */ 3527 @NonNull 3528 @Size(6) xyPrimaries(@onNull @izemin = 6, max = 9) float[] primaries)3529 private static float[] xyPrimaries(@NonNull @Size(min = 6, max = 9) float[] primaries) { 3530 float[] xyPrimaries = new float[6]; 3531 3532 // XYZ to xyY 3533 if (primaries.length == 9) { 3534 float sum; 3535 3536 sum = primaries[0] + primaries[1] + primaries[2]; 3537 xyPrimaries[0] = primaries[0] / sum; 3538 xyPrimaries[1] = primaries[1] / sum; 3539 3540 sum = primaries[3] + primaries[4] + primaries[5]; 3541 xyPrimaries[2] = primaries[3] / sum; 3542 xyPrimaries[3] = primaries[4] / sum; 3543 3544 sum = primaries[6] + primaries[7] + primaries[8]; 3545 xyPrimaries[4] = primaries[6] / sum; 3546 xyPrimaries[5] = primaries[7] / sum; 3547 } else { 3548 System.arraycopy(primaries, 0, xyPrimaries, 0, 6); 3549 } 3550 3551 return xyPrimaries; 3552 } 3553 3554 /** 3555 * Converts the specified white point to xyY if needed. The white point 3556 * can be specified as an array of 2 floats (in CIE xyY) or 3 floats 3557 * (in CIE XYZ). If no conversion is needed, the input array is copied. 3558 * 3559 * @param whitePoint The white point in xyY or XYZ 3560 * @return A new array of 2 floats containing the white point in xyY 3561 */ 3562 @NonNull 3563 @Size(2) xyWhitePoint(@izemin = 2, max = 3) float[] whitePoint)3564 private static float[] xyWhitePoint(@Size(min = 2, max = 3) float[] whitePoint) { 3565 float[] xyWhitePoint = new float[2]; 3566 3567 // XYZ to xyY 3568 if (whitePoint.length == 3) { 3569 float sum = whitePoint[0] + whitePoint[1] + whitePoint[2]; 3570 xyWhitePoint[0] = whitePoint[0] / sum; 3571 xyWhitePoint[1] = whitePoint[1] / sum; 3572 } else { 3573 System.arraycopy(whitePoint, 0, xyWhitePoint, 0, 2); 3574 } 3575 3576 return xyWhitePoint; 3577 } 3578 3579 /** 3580 * Computes the matrix that converts from RGB to XYZ based on RGB 3581 * primaries and a white point, both specified in the CIE xyY space. 3582 * The Y component of the primaries and white point is implied to be 1. 3583 * 3584 * @param primaries The RGB primaries in xyY, as an array of 6 floats 3585 * @param whitePoint The white point in xyY, as an array of 2 floats 3586 * @return A 3x3 matrix as a new array of 9 floats 3587 */ 3588 @NonNull 3589 @Size(9) computeXYZMatrix( @onNull @ize6) float[] primaries, @NonNull @Size(2) float[] whitePoint)3590 private static float[] computeXYZMatrix( 3591 @NonNull @Size(6) float[] primaries, 3592 @NonNull @Size(2) float[] whitePoint) { 3593 float Rx = primaries[0]; 3594 float Ry = primaries[1]; 3595 float Gx = primaries[2]; 3596 float Gy = primaries[3]; 3597 float Bx = primaries[4]; 3598 float By = primaries[5]; 3599 float Wx = whitePoint[0]; 3600 float Wy = whitePoint[1]; 3601 3602 float oneRxRy = (1 - Rx) / Ry; 3603 float oneGxGy = (1 - Gx) / Gy; 3604 float oneBxBy = (1 - Bx) / By; 3605 float oneWxWy = (1 - Wx) / Wy; 3606 3607 float RxRy = Rx / Ry; 3608 float GxGy = Gx / Gy; 3609 float BxBy = Bx / By; 3610 float WxWy = Wx / Wy; 3611 3612 float BY = 3613 ((oneWxWy - oneRxRy) * (GxGy - RxRy) - (WxWy - RxRy) * (oneGxGy - oneRxRy)) / 3614 ((oneBxBy - oneRxRy) * (GxGy - RxRy) - (BxBy - RxRy) * (oneGxGy - oneRxRy)); 3615 float GY = (WxWy - RxRy - BY * (BxBy - RxRy)) / (GxGy - RxRy); 3616 float RY = 1 - GY - BY; 3617 3618 float RYRy = RY / Ry; 3619 float GYGy = GY / Gy; 3620 float BYBy = BY / By; 3621 3622 return new float[] { 3623 RYRy * Rx, RY, RYRy * (1 - Rx - Ry), 3624 GYGy * Gx, GY, GYGy * (1 - Gx - Gy), 3625 BYBy * Bx, BY, BYBy * (1 - Bx - By) 3626 }; 3627 } 3628 } 3629 3630 /** 3631 * {@usesMathJax} 3632 * 3633 * <p>A connector transforms colors from a source color space to a destination 3634 * color space.</p> 3635 * 3636 * <p>A source color space is connected to a destination color space using the 3637 * color transform \(C\) computed from their respective transforms noted 3638 * \(T_{src}\) and \(T_{dst}\) in the following equation:</p> 3639 * 3640 * $$C = T^{-1}_{dst} . T_{src}$$ 3641 * 3642 * <p>The transform \(C\) shown above is only valid when the source and 3643 * destination color spaces have the same profile connection space (PCS). 3644 * We know that instances of {@link ColorSpace} always use CIE XYZ as their 3645 * PCS but their white points might differ. When they do, we must perform 3646 * a chromatic adaptation of the color spaces' transforms. To do so, we 3647 * use the von Kries method described in the documentation of {@link Adaptation}, 3648 * using the CIE standard illuminant {@link ColorSpace#ILLUMINANT_D50 D50} 3649 * as the target white point.</p> 3650 * 3651 * <p>Example of conversion from {@link Named#SRGB sRGB} to 3652 * {@link Named#DCI_P3 DCI-P3}:</p> 3653 * 3654 * <pre class="prettyprint"> 3655 * ColorSpace.Connector connector = ColorSpace.connect( 3656 * ColorSpace.get(ColorSpace.Named.SRGB), 3657 * ColorSpace.get(ColorSpace.Named.DCI_P3)); 3658 * float[] p3 = connector.transform(1.0f, 0.0f, 0.0f); 3659 * // p3 contains { 0.9473, 0.2740, 0.2076 } 3660 * </pre> 3661 * 3662 * @see Adaptation 3663 * @see ColorSpace#adapt(ColorSpace, float[], Adaptation) 3664 * @see ColorSpace#adapt(ColorSpace, float[]) 3665 * @see ColorSpace#connect(ColorSpace, ColorSpace, RenderIntent) 3666 * @see ColorSpace#connect(ColorSpace, ColorSpace) 3667 * @see ColorSpace#connect(ColorSpace, RenderIntent) 3668 * @see ColorSpace#connect(ColorSpace) 3669 */ 3670 @AnyThread 3671 public static class Connector { 3672 @NonNull private final ColorSpace mSource; 3673 @NonNull private final ColorSpace mDestination; 3674 @NonNull private final ColorSpace mTransformSource; 3675 @NonNull private final ColorSpace mTransformDestination; 3676 @NonNull private final RenderIntent mIntent; 3677 @NonNull @Size(3) private final float[] mTransform; 3678 3679 /** 3680 * Creates a new connector between a source and a destination color space. 3681 * 3682 * @param source The source color space, cannot be null 3683 * @param destination The destination color space, cannot be null 3684 * @param intent The render intent to use when compressing gamuts 3685 */ Connector(@onNull ColorSpace source, @NonNull ColorSpace destination, @NonNull RenderIntent intent)3686 Connector(@NonNull ColorSpace source, @NonNull ColorSpace destination, 3687 @NonNull RenderIntent intent) { 3688 this(source, destination, 3689 source.getModel() == Model.RGB ? adapt(source, ILLUMINANT_D50_XYZ) : source, 3690 destination.getModel() == Model.RGB ? 3691 adapt(destination, ILLUMINANT_D50_XYZ) : destination, 3692 intent, computeTransform(source, destination, intent)); 3693 } 3694 3695 /** 3696 * To connect between color spaces, we might need to use adapted transforms. 3697 * This should be transparent to the user so this constructor takes the 3698 * original source and destinations (returned by the getters), as well as 3699 * possibly adapted color spaces used by transform(). 3700 */ Connector( @onNull ColorSpace source, @NonNull ColorSpace destination, @NonNull ColorSpace transformSource, @NonNull ColorSpace transformDestination, @NonNull RenderIntent intent, @Nullable @Size(3) float[] transform)3701 private Connector( 3702 @NonNull ColorSpace source, @NonNull ColorSpace destination, 3703 @NonNull ColorSpace transformSource, @NonNull ColorSpace transformDestination, 3704 @NonNull RenderIntent intent, @Nullable @Size(3) float[] transform) { 3705 mSource = source; 3706 mDestination = destination; 3707 mTransformSource = transformSource; 3708 mTransformDestination = transformDestination; 3709 mIntent = intent; 3710 mTransform = transform; 3711 } 3712 3713 /** 3714 * Computes an extra transform to apply in XYZ space depending on the 3715 * selected rendering intent. 3716 */ 3717 @Nullable computeTransform(@onNull ColorSpace source, @NonNull ColorSpace destination, @NonNull RenderIntent intent)3718 private static float[] computeTransform(@NonNull ColorSpace source, 3719 @NonNull ColorSpace destination, @NonNull RenderIntent intent) { 3720 if (intent != RenderIntent.ABSOLUTE) return null; 3721 3722 boolean srcRGB = source.getModel() == Model.RGB; 3723 boolean dstRGB = destination.getModel() == Model.RGB; 3724 3725 if (srcRGB && dstRGB) return null; 3726 3727 if (srcRGB || dstRGB) { 3728 ColorSpace.Rgb rgb = (ColorSpace.Rgb) (srcRGB ? source : destination); 3729 float[] srcXYZ = srcRGB ? xyYToXyz(rgb.mWhitePoint) : ILLUMINANT_D50_XYZ; 3730 float[] dstXYZ = dstRGB ? xyYToXyz(rgb.mWhitePoint) : ILLUMINANT_D50_XYZ; 3731 return new float[] { 3732 srcXYZ[0] / dstXYZ[0], 3733 srcXYZ[1] / dstXYZ[1], 3734 srcXYZ[2] / dstXYZ[2], 3735 }; 3736 } 3737 3738 return null; 3739 } 3740 3741 /** 3742 * Returns the source color space this connector will convert from. 3743 * 3744 * @return A non-null instance of {@link ColorSpace} 3745 * 3746 * @see #getDestination() 3747 */ 3748 @NonNull getSource()3749 public ColorSpace getSource() { 3750 return mSource; 3751 } 3752 3753 /** 3754 * Returns the destination color space this connector will convert to. 3755 * 3756 * @return A non-null instance of {@link ColorSpace} 3757 * 3758 * @see #getSource() 3759 */ 3760 @NonNull getDestination()3761 public ColorSpace getDestination() { 3762 return mDestination; 3763 } 3764 3765 /** 3766 * Returns the render intent this connector will use when mapping the 3767 * source color space to the destination color space. 3768 * 3769 * @return A non-null {@link RenderIntent} 3770 * 3771 * @see RenderIntent 3772 */ getRenderIntent()3773 public RenderIntent getRenderIntent() { 3774 return mIntent; 3775 } 3776 3777 /** 3778 * <p>Transforms the specified color from the source color space 3779 * to a color in the destination color space. This convenience 3780 * method assumes a source color model with 3 components 3781 * (typically RGB). To transform from color models with more than 3782 * 3 components, such as {@link Model#CMYK CMYK}, use 3783 * {@link #transform(float[])} instead.</p> 3784 * 3785 * @param r The red component of the color to transform 3786 * @param g The green component of the color to transform 3787 * @param b The blue component of the color to transform 3788 * @return A new array of 3 floats containing the specified color 3789 * transformed from the source space to the destination space 3790 * 3791 * @see #transform(float[]) 3792 */ 3793 @NonNull 3794 @Size(3) transform(float r, float g, float b)3795 public float[] transform(float r, float g, float b) { 3796 return transform(new float[] { r, g, b }); 3797 } 3798 3799 /** 3800 * <p>Transforms the specified color from the source color space 3801 * to a color in the destination color space.</p> 3802 * 3803 * @param v A non-null array of 3 floats containing the value to transform 3804 * and that will hold the result of the transform 3805 * @return The v array passed as a parameter, containing the specified color 3806 * transformed from the source space to the destination space 3807 * 3808 * @see #transform(float, float, float) 3809 */ 3810 @NonNull 3811 @Size(min = 3) transform(@onNull @izemin = 3) float[] v)3812 public float[] transform(@NonNull @Size(min = 3) float[] v) { 3813 float[] xyz = mTransformSource.toXyz(v); 3814 if (mTransform != null) { 3815 xyz[0] *= mTransform[0]; 3816 xyz[1] *= mTransform[1]; 3817 xyz[2] *= mTransform[2]; 3818 } 3819 return mTransformDestination.fromXyz(xyz); 3820 } 3821 3822 /** 3823 * Optimized connector for RGB->RGB conversions. 3824 */ 3825 private static class Rgb extends Connector { 3826 @NonNull private final ColorSpace.Rgb mSource; 3827 @NonNull private final ColorSpace.Rgb mDestination; 3828 @NonNull private final float[] mTransform; 3829 Rgb(@onNull ColorSpace.Rgb source, @NonNull ColorSpace.Rgb destination, @NonNull RenderIntent intent)3830 Rgb(@NonNull ColorSpace.Rgb source, @NonNull ColorSpace.Rgb destination, 3831 @NonNull RenderIntent intent) { 3832 super(source, destination, source, destination, intent, null); 3833 mSource = source; 3834 mDestination = destination; 3835 mTransform = computeTransform(source, destination, intent); 3836 } 3837 3838 @Override transform(@onNull @izemin = 3) float[] rgb)3839 public float[] transform(@NonNull @Size(min = 3) float[] rgb) { 3840 rgb[0] = (float) mSource.mClampedEotf.applyAsDouble(rgb[0]); 3841 rgb[1] = (float) mSource.mClampedEotf.applyAsDouble(rgb[1]); 3842 rgb[2] = (float) mSource.mClampedEotf.applyAsDouble(rgb[2]); 3843 mul3x3Float3(mTransform, rgb); 3844 rgb[0] = (float) mDestination.mClampedOetf.applyAsDouble(rgb[0]); 3845 rgb[1] = (float) mDestination.mClampedOetf.applyAsDouble(rgb[1]); 3846 rgb[2] = (float) mDestination.mClampedOetf.applyAsDouble(rgb[2]); 3847 return rgb; 3848 } 3849 3850 /** 3851 * <p>Computes the color transform that connects two RGB color spaces.</p> 3852 * 3853 * <p>We can only connect color spaces if they use the same profile 3854 * connection space. We assume the connection space is always 3855 * CIE XYZ but we maye need to perform a chromatic adaptation to 3856 * match the white points. If an adaptation is needed, we use the 3857 * CIE standard illuminant D50. The unmatched color space is adapted 3858 * using the von Kries transform and the {@link Adaptation#BRADFORD} 3859 * matrix.</p> 3860 * 3861 * @param source The source color space, cannot be null 3862 * @param destination The destination color space, cannot be null 3863 * @param intent The render intent to use when compressing gamuts 3864 * @return An array of 9 floats containing the 3x3 matrix transform 3865 */ 3866 @NonNull 3867 @Size(9) computeTransform( @onNull ColorSpace.Rgb source, @NonNull ColorSpace.Rgb destination, @NonNull RenderIntent intent)3868 private static float[] computeTransform( 3869 @NonNull ColorSpace.Rgb source, 3870 @NonNull ColorSpace.Rgb destination, 3871 @NonNull RenderIntent intent) { 3872 if (compare(source.mWhitePoint, destination.mWhitePoint)) { 3873 // RGB->RGB using the PCS of both color spaces since they have the same 3874 return mul3x3(destination.mInverseTransform, source.mTransform); 3875 } else { 3876 // RGB->RGB using CIE XYZ D50 as the PCS 3877 float[] transform = source.mTransform; 3878 float[] inverseTransform = destination.mInverseTransform; 3879 3880 float[] srcXYZ = xyYToXyz(source.mWhitePoint); 3881 float[] dstXYZ = xyYToXyz(destination.mWhitePoint); 3882 3883 if (!compare(source.mWhitePoint, ILLUMINANT_D50)) { 3884 float[] srcAdaptation = chromaticAdaptation( 3885 Adaptation.BRADFORD.mTransform, srcXYZ, 3886 Arrays.copyOf(ILLUMINANT_D50_XYZ, 3)); 3887 transform = mul3x3(srcAdaptation, source.mTransform); 3888 } 3889 3890 if (!compare(destination.mWhitePoint, ILLUMINANT_D50)) { 3891 float[] dstAdaptation = chromaticAdaptation( 3892 Adaptation.BRADFORD.mTransform, dstXYZ, 3893 Arrays.copyOf(ILLUMINANT_D50_XYZ, 3)); 3894 inverseTransform = inverse3x3(mul3x3(dstAdaptation, destination.mTransform)); 3895 } 3896 3897 if (intent == RenderIntent.ABSOLUTE) { 3898 transform = mul3x3Diag( 3899 new float[] { 3900 srcXYZ[0] / dstXYZ[0], 3901 srcXYZ[1] / dstXYZ[1], 3902 srcXYZ[2] / dstXYZ[2], 3903 }, transform); 3904 } 3905 3906 return mul3x3(inverseTransform, transform); 3907 } 3908 } 3909 } 3910 3911 /** 3912 * Returns the identity connector for a given color space. 3913 * 3914 * @param source The source and destination color space 3915 * @return A non-null connector that does not perform any transform 3916 * 3917 * @see ColorSpace#connect(ColorSpace, ColorSpace) 3918 */ identity(ColorSpace source)3919 static Connector identity(ColorSpace source) { 3920 return new Connector(source, source, RenderIntent.RELATIVE) { 3921 @Override 3922 public float[] transform(@NonNull @Size(min = 3) float[] v) { 3923 return v; 3924 } 3925 }; 3926 } 3927 } 3928 3929 /** 3930 * <p>A color space renderer can be used to visualize and compare the gamut and 3931 * white point of one or more color spaces. The output is an sRGB {@link Bitmap} 3932 * showing a CIE 1931 xyY or a CIE 1976 UCS chromaticity diagram.</p> 3933 * 3934 * <p>The following code snippet shows how to compare the {@link Named#SRGB} 3935 * and {@link Named#DCI_P3} color spaces in a CIE 1931 diagram:</p> 3936 * 3937 * <pre class="prettyprint"> 3938 * Bitmap bitmap = ColorSpace.createRenderer() 3939 * .size(768) 3940 * .clip(true) 3941 * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff) 3942 * .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845) 3943 * .render(); 3944 * </pre> 3945 * <p> 3946 * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_clipped.png" /> 3947 * <figcaption style="text-align: center;">sRGB vs DCI-P3</figcaption> 3948 * </p> 3949 * 3950 * <p>A renderer can also be used to show the location of specific colors, 3951 * associated with a color space, in the CIE 1931 xyY chromaticity diagram. 3952 * See {@link #add(ColorSpace, float, float, float, int)} for more information.</p> 3953 * 3954 * @see ColorSpace#createRenderer() 3955 * 3956 * @hide 3957 */ 3958 public static class Renderer { 3959 private static final int NATIVE_SIZE = 1440; 3960 private static final float UCS_SCALE = 9.0f / 6.0f; 3961 3962 // Number of subdivision of the inside of the spectral locus 3963 private static final int CHROMATICITY_RESOLUTION = 32; 3964 private static final double ONE_THIRD = 1.0 / 3.0; 3965 3966 @IntRange(from = 128, to = Integer.MAX_VALUE) 3967 private int mSize = 1024; 3968 3969 private boolean mShowWhitePoint = true; 3970 private boolean mClip = false; 3971 private boolean mUcs = false; 3972 3973 private final List<Pair<ColorSpace, Integer>> mColorSpaces = new ArrayList<>(2); 3974 private final List<Point> mPoints = new ArrayList<>(0); 3975 3976 private Renderer() { 3977 } 3978 3979 /** 3980 * <p>Defines whether the chromaticity diagram should be clipped by the first 3981 * registered color space. The default value is false.</p> 3982 * 3983 * <p>The following code snippet and image show the default behavior:</p> 3984 * <pre class="prettyprint"> 3985 * Bitmap bitmap = ColorSpace.createRenderer() 3986 * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff) 3987 * .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845) 3988 * .render(); 3989 * </pre> 3990 * <p> 3991 * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_comparison.png" /> 3992 * <figcaption style="text-align: center;">Clipping disabled</figcaption> 3993 * </p> 3994 * 3995 * <p>Here is the same example with clipping enabled:</p> 3996 * <pre class="prettyprint"> 3997 * Bitmap bitmap = ColorSpace.createRenderer() 3998 * .clip(true) 3999 * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff) 4000 * .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845) 4001 * .render(); 4002 * </pre> 4003 * <p> 4004 * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_clipped.png" /> 4005 * <figcaption style="text-align: center;">Clipping enabled</figcaption> 4006 * </p> 4007 * 4008 * @param clip True to clip the chromaticity diagram to the first registered color space, 4009 * false otherwise 4010 * @return This instance of {@link Renderer} 4011 */ 4012 @NonNull 4013 public Renderer clip(boolean clip) { 4014 mClip = clip; 4015 return this; 4016 } 4017 4018 /** 4019 * <p>Defines whether the chromaticity diagram should use the uniform 4020 * chromaticity scale (CIE 1976 UCS). When the uniform chromaticity scale 4021 * is used, the distance between two points on the diagram is approximately 4022 * proportional to the perceived color difference.</p> 4023 * 4024 * <p>The following code snippet shows how to enable the uniform chromaticity 4025 * scale. The image below shows the result:</p> 4026 * <pre class="prettyprint"> 4027 * Bitmap bitmap = ColorSpace.createRenderer() 4028 * .uniformChromaticityScale(true) 4029 * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff) 4030 * .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845) 4031 * .render(); 4032 * </pre> 4033 * <p> 4034 * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_ucs.png" /> 4035 * <figcaption style="text-align: center;">CIE 1976 UCS diagram</figcaption> 4036 * </p> 4037 * 4038 * @param ucs True to render the chromaticity diagram as the CIE 1976 UCS diagram 4039 * @return This instance of {@link Renderer} 4040 */ 4041 @NonNull 4042 public Renderer uniformChromaticityScale(boolean ucs) { 4043 mUcs = ucs; 4044 return this; 4045 } 4046 4047 /** 4048 * Sets the dimensions (width and height) in pixels of the output bitmap. 4049 * The size must be at least 128px and defaults to 1024px. 4050 * 4051 * @param size The size in pixels of the output bitmap 4052 * @return This instance of {@link Renderer} 4053 */ 4054 @NonNull 4055 public Renderer size(@IntRange(from = 128, to = Integer.MAX_VALUE) int size) { 4056 mSize = Math.max(128, size); 4057 return this; 4058 } 4059 4060 /** 4061 * Shows or hides the white point of each color space in the output bitmap. 4062 * The default is true. 4063 * 4064 * @param show True to show the white point of each color space, false 4065 * otherwise 4066 * @return This instance of {@link Renderer} 4067 */ 4068 @NonNull 4069 public Renderer showWhitePoint(boolean show) { 4070 mShowWhitePoint = show; 4071 return this; 4072 } 4073 4074 /** 4075 * <p>Adds a color space to represent on the output CIE 1931 chromaticity 4076 * diagram. The color space is represented as a triangle showing the 4077 * footprint of its color gamut and, optionally, the location of its 4078 * white point.</p> 4079 * 4080 * <p class="note">Color spaces with a color model that is not RGB are 4081 * accepted but ignored.</p> 4082 * 4083 * <p>The following code snippet and image show an example of calling this 4084 * method to compare {@link Named#SRGB sRGB} and {@link Named#DCI_P3 DCI-P3}:</p> 4085 * <pre class="prettyprint"> 4086 * Bitmap bitmap = ColorSpace.createRenderer() 4087 * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff) 4088 * .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845) 4089 * .render(); 4090 * </pre> 4091 * <p> 4092 * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_comparison.png" /> 4093 * <figcaption style="text-align: center;">sRGB vs DCI-P3</figcaption> 4094 * </p> 4095 * 4096 * <p>Adding a color space extending beyond the boundaries of the 4097 * spectral locus will alter the size of the diagram within the output 4098 * bitmap as shown in this example:</p> 4099 * <pre class="prettyprint"> 4100 * Bitmap bitmap = ColorSpace.createRenderer() 4101 * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff) 4102 * .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845) 4103 * .add(ColorSpace.get(ColorSpace.Named.ACES), 0xff097ae9) 4104 * .add(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB), 0xff000000) 4105 * .render(); 4106 * </pre> 4107 * <p> 4108 * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_comparison2.png" /> 4109 * <figcaption style="text-align: center;">sRGB, DCI-P3, ACES and scRGB</figcaption> 4110 * </p> 4111 * 4112 * @param colorSpace The color space whose gamut to render on the diagram 4113 * @param color The sRGB color to use to render the color space's gamut and white point 4114 * @return This instance of {@link Renderer} 4115 * 4116 * @see #clip(boolean) 4117 * @see #showWhitePoint(boolean) 4118 */ 4119 @NonNull 4120 public Renderer add(@NonNull ColorSpace colorSpace, @ColorInt int color) { 4121 mColorSpaces.add(new Pair<>(colorSpace, color)); 4122 return this; 4123 } 4124 4125 /** 4126 * <p>Adds a color to represent as a point on the chromaticity diagram. 4127 * The color is associated with a color space which will be used to 4128 * perform the conversion to CIE XYZ and compute the location of the point 4129 * on the diagram. The point is rendered as a colored circle.</p> 4130 * 4131 * <p>The following code snippet and image show an example of calling this 4132 * method to render the location of several sRGB colors as white circles:</p> 4133 * <pre class="prettyprint"> 4134 * Bitmap bitmap = ColorSpace.createRenderer() 4135 * .clip(true) 4136 * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff) 4137 * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.0f, 0.1f, 0xffffffff) 4138 * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.1f, 0.1f, 0xffffffff) 4139 * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.2f, 0.1f, 0xffffffff) 4140 * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.3f, 0.1f, 0xffffffff) 4141 * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.4f, 0.1f, 0xffffffff) 4142 * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.5f, 0.1f, 0xffffffff) 4143 * .render(); 4144 * </pre> 4145 * <p> 4146 * <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_points.png" /> 4147 * <figcaption style="text-align: center;"> 4148 * Locating colors on the chromaticity diagram 4149 * </figcaption> 4150 * </p> 4151 * 4152 * @param colorSpace The color space of the color to locate on the diagram 4153 * @param r The first component of the color to locate on the diagram 4154 * @param g The second component of the color to locate on the diagram 4155 * @param b The third component of the color to locate on the diagram 4156 * @param pointColor The sRGB color to use to render the point on the diagram 4157 * @return This instance of {@link Renderer} 4158 */ 4159 @NonNull 4160 public Renderer add(@NonNull ColorSpace colorSpace, float r, float g, float b, 4161 @ColorInt int pointColor) { 4162 mPoints.add(new Point(colorSpace, new float[] { r, g, b }, pointColor)); 4163 return this; 4164 } 4165 4166 /** 4167 * <p>Renders the {@link #add(ColorSpace, int) color spaces} and 4168 * {@link #add(ColorSpace, float, float, float, int) points} registered 4169 * with this renderer. The output bitmap is an sRGB image with the 4170 * dimensions specified by calling {@link #size(int)} (1204x1024px by 4171 * default).</p> 4172 * 4173 * @return A new non-null {@link Bitmap} with the dimensions specified 4174 * by {@link #size(int)} (1024x1024 by default) 4175 */ 4176 @NonNull 4177 public Bitmap render() { 4178 Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); 4179 Bitmap bitmap = Bitmap.createBitmap(mSize, mSize, Bitmap.Config.ARGB_8888); 4180 Canvas canvas = new Canvas(bitmap); 4181 4182 float[] primaries = new float[6]; 4183 float[] whitePoint = new float[2]; 4184 4185 int width = NATIVE_SIZE; 4186 int height = NATIVE_SIZE; 4187 4188 Path path = new Path(); 4189 4190 setTransform(canvas, width, height, primaries); 4191 drawBox(canvas, width, height, paint, path); 4192 setUcsTransform(canvas, height); 4193 drawLocus(canvas, width, height, paint, path, primaries); 4194 drawGamuts(canvas, width, height, paint, path, primaries, whitePoint); 4195 drawPoints(canvas, width, height, paint); 4196 4197 return bitmap; 4198 } 4199 4200 /** 4201 * Draws registered points at their correct position in the xyY coordinates. 4202 * Each point is positioned according to its associated color space. 4203 * 4204 * @param canvas The canvas to transform 4205 * @param width Width in pixel of the final image 4206 * @param height Height in pixel of the final image 4207 * @param paint A pre-allocated paint used to avoid temporary allocations 4208 */ 4209 private void drawPoints(@NonNull Canvas canvas, int width, int height, 4210 @NonNull Paint paint) { 4211 4212 paint.setStyle(Paint.Style.FILL); 4213 4214 float radius = 4.0f / (mUcs ? UCS_SCALE : 1.0f); 4215 4216 float[] v = new float[3]; 4217 float[] xy = new float[2]; 4218 4219 for (final Point point : mPoints) { 4220 v[0] = point.mRgb[0]; 4221 v[1] = point.mRgb[1]; 4222 v[2] = point.mRgb[2]; 4223 point.mColorSpace.toXyz(v); 4224 4225 paint.setColor(point.mColor); 4226 4227 // XYZ to xyY, assuming Y=1.0, then to L*u*v* if needed 4228 float sum = v[0] + v[1] + v[2]; 4229 xy[0] = v[0] / sum; 4230 xy[1] = v[1] / sum; 4231 if (mUcs) xyYToUv(xy); 4232 4233 canvas.drawCircle(width * xy[0], height - height * xy[1], radius, paint); 4234 } 4235 } 4236 4237 /** 4238 * Draws the color gamuts and white points of all the registered color 4239 * spaces. Only color spaces with an RGB color model are rendered, the 4240 * others are ignored. 4241 * 4242 * @param canvas The canvas to transform 4243 * @param width Width in pixel of the final image 4244 * @param height Height in pixel of the final image 4245 * @param paint A pre-allocated paint used to avoid temporary allocations 4246 * @param path A pre-allocated path used to avoid temporary allocations 4247 * @param primaries A pre-allocated array of 6 floats to avoid temporary allocations 4248 * @param whitePoint A pre-allocated array of 2 floats to avoid temporary allocations 4249 */ 4250 private void drawGamuts( 4251 @NonNull Canvas canvas, int width, int height, 4252 @NonNull Paint paint, @NonNull Path path, 4253 @NonNull @Size(6) float[] primaries, @NonNull @Size(2) float[] whitePoint) { 4254 4255 float radius = 4.0f / (mUcs ? UCS_SCALE : 1.0f); 4256 4257 for (final Pair<ColorSpace, Integer> item : mColorSpaces) { 4258 ColorSpace colorSpace = item.first; 4259 int color = item.second; 4260 4261 if (colorSpace.getModel() != Model.RGB) continue; 4262 4263 Rgb rgb = (Rgb) colorSpace; 4264 getPrimaries(rgb, primaries, mUcs); 4265 4266 path.rewind(); 4267 path.moveTo(width * primaries[0], height - height * primaries[1]); 4268 path.lineTo(width * primaries[2], height - height * primaries[3]); 4269 path.lineTo(width * primaries[4], height - height * primaries[5]); 4270 path.close(); 4271 4272 paint.setStyle(Paint.Style.STROKE); 4273 paint.setColor(color); 4274 canvas.drawPath(path, paint); 4275 4276 // Draw the white point 4277 if (mShowWhitePoint) { 4278 rgb.getWhitePoint(whitePoint); 4279 if (mUcs) xyYToUv(whitePoint); 4280 4281 paint.setStyle(Paint.Style.FILL); 4282 paint.setColor(color); 4283 canvas.drawCircle( 4284 width * whitePoint[0], height - height * whitePoint[1], radius, paint); 4285 } 4286 } 4287 } 4288 4289 /** 4290 * Returns the primaries of the specified RGB color space. This method handles 4291 * the special case of the {@link Named#EXTENDED_SRGB} family of color spaces. 4292 * 4293 * @param rgb The color space whose primaries to extract 4294 * @param primaries A pre-allocated array of 6 floats that will hold the result 4295 * @param asUcs True if the primaries should be returned in Luv, false for xyY 4296 */ 4297 @NonNull 4298 @Size(6) 4299 private static void getPrimaries(@NonNull Rgb rgb, 4300 @NonNull @Size(6) float[] primaries, boolean asUcs) { 4301 // TODO: We should find a better way to handle these cases 4302 if (rgb.equals(ColorSpace.get(Named.EXTENDED_SRGB)) || 4303 rgb.equals(ColorSpace.get(Named.LINEAR_EXTENDED_SRGB))) { 4304 primaries[0] = 1.41f; 4305 primaries[1] = 0.33f; 4306 primaries[2] = 0.27f; 4307 primaries[3] = 1.24f; 4308 primaries[4] = -0.23f; 4309 primaries[5] = -0.57f; 4310 } else { 4311 rgb.getPrimaries(primaries); 4312 } 4313 if (asUcs) xyYToUv(primaries); 4314 } 4315 4316 /** 4317 * Draws the CIE 1931 chromaticity diagram: the spectral locus and its inside. 4318 * This method respect the clip parameter. 4319 * 4320 * @param canvas The canvas to transform 4321 * @param width Width in pixel of the final image 4322 * @param height Height in pixel of the final image 4323 * @param paint A pre-allocated paint used to avoid temporary allocations 4324 * @param path A pre-allocated path used to avoid temporary allocations 4325 * @param primaries A pre-allocated array of 6 floats to avoid temporary allocations 4326 */ 4327 private void drawLocus( 4328 @NonNull Canvas canvas, int width, int height, @NonNull Paint paint, 4329 @NonNull Path path, @NonNull @Size(6) float[] primaries) { 4330 4331 int vertexCount = SPECTRUM_LOCUS_X.length * CHROMATICITY_RESOLUTION * 6; 4332 float[] vertices = new float[vertexCount * 2]; 4333 int[] colors = new int[vertices.length]; 4334 computeChromaticityMesh(vertices, colors); 4335 4336 if (mUcs) xyYToUv(vertices); 4337 for (int i = 0; i < vertices.length; i += 2) { 4338 vertices[i] *= width; 4339 vertices[i + 1] = height - vertices[i + 1] * height; 4340 } 4341 4342 // Draw the spectral locus 4343 if (mClip && mColorSpaces.size() > 0) { 4344 for (final Pair<ColorSpace, Integer> item : mColorSpaces) { 4345 ColorSpace colorSpace = item.first; 4346 if (colorSpace.getModel() != Model.RGB) continue; 4347 4348 Rgb rgb = (Rgb) colorSpace; 4349 getPrimaries(rgb, primaries, mUcs); 4350 4351 break; 4352 } 4353 4354 path.rewind(); 4355 path.moveTo(width * primaries[0], height - height * primaries[1]); 4356 path.lineTo(width * primaries[2], height - height * primaries[3]); 4357 path.lineTo(width * primaries[4], height - height * primaries[5]); 4358 path.close(); 4359 4360 int[] solid = new int[colors.length]; 4361 Arrays.fill(solid, 0xff6c6c6c); 4362 canvas.drawVertices(Canvas.VertexMode.TRIANGLES, vertices.length, vertices, 0, 4363 null, 0, solid, 0, null, 0, 0, paint); 4364 4365 canvas.save(); 4366 canvas.clipPath(path); 4367 4368 canvas.drawVertices(Canvas.VertexMode.TRIANGLES, vertices.length, vertices, 0, 4369 null, 0, colors, 0, null, 0, 0, paint); 4370 4371 canvas.restore(); 4372 } else { 4373 canvas.drawVertices(Canvas.VertexMode.TRIANGLES, vertices.length, vertices, 0, 4374 null, 0, colors, 0, null, 0, 0, paint); 4375 } 4376 4377 // Draw the non-spectral locus 4378 int index = (CHROMATICITY_RESOLUTION - 1) * 12; 4379 path.reset(); 4380 path.moveTo(vertices[index], vertices[index + 1]); 4381 for (int x = 2; x < SPECTRUM_LOCUS_X.length; x++) { 4382 index += CHROMATICITY_RESOLUTION * 12; 4383 path.lineTo(vertices[index], vertices[index + 1]); 4384 } 4385 path.close(); 4386 4387 paint.setStrokeWidth(4.0f / (mUcs ? UCS_SCALE : 1.0f)); 4388 paint.setStyle(Paint.Style.STROKE); 4389 paint.setColor(0xff000000); 4390 canvas.drawPath(path, paint); 4391 } 4392 4393 /** 4394 * Draws the diagram box, including borders, tick marks, grid lines 4395 * and axis labels. 4396 * 4397 * @param canvas The canvas to transform 4398 * @param width Width in pixel of the final image 4399 * @param height Height in pixel of the final image 4400 * @param paint A pre-allocated paint used to avoid temporary allocations 4401 * @param path A pre-allocated path used to avoid temporary allocations 4402 */ 4403 private void drawBox(@NonNull Canvas canvas, int width, int height, @NonNull Paint paint, 4404 @NonNull Path path) { 4405 4406 int lineCount = 10; 4407 float scale = 1.0f; 4408 if (mUcs) { 4409 lineCount = 7; 4410 scale = UCS_SCALE; 4411 } 4412 4413 // Draw the unit grid 4414 paint.setStyle(Paint.Style.STROKE); 4415 paint.setStrokeWidth(2.0f); 4416 paint.setColor(0xffc0c0c0); 4417 4418 for (int i = 1; i < lineCount - 1; i++) { 4419 float v = i / 10.0f; 4420 float x = (width * v) * scale; 4421 float y = height - (height * v) * scale; 4422 4423 canvas.drawLine(0.0f, y, 0.9f * width, y, paint); 4424 canvas.drawLine(x, height, x, 0.1f * height, paint); 4425 } 4426 4427 // Draw tick marks 4428 paint.setStrokeWidth(4.0f); 4429 paint.setColor(0xff000000); 4430 for (int i = 1; i < lineCount - 1; i++) { 4431 float v = i / 10.0f; 4432 float x = (width * v) * scale; 4433 float y = height - (height * v) * scale; 4434 4435 canvas.drawLine(0.0f, y, width / 100.0f, y, paint); 4436 canvas.drawLine(x, height, x, height - (height / 100.0f), paint); 4437 } 4438 4439 // Draw the axis labels 4440 paint.setStyle(Paint.Style.FILL); 4441 paint.setTextSize(36.0f); 4442 paint.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL)); 4443 4444 Rect bounds = new Rect(); 4445 for (int i = 1; i < lineCount - 1; i++) { 4446 String text = "0." + i; 4447 paint.getTextBounds(text, 0, text.length(), bounds); 4448 4449 float v = i / 10.0f; 4450 float x = (width * v) * scale; 4451 float y = height - (height * v) * scale; 4452 4453 canvas.drawText(text, -0.05f * width + 10, y + bounds.height() / 2.0f, paint); 4454 canvas.drawText(text, x - bounds.width() / 2.0f, 4455 height + bounds.height() + 16, paint); 4456 } 4457 paint.setStyle(Paint.Style.STROKE); 4458 4459 // Draw the diagram box 4460 path.moveTo(0.0f, height); 4461 path.lineTo(0.9f * width, height); 4462 path.lineTo(0.9f * width, 0.1f * height); 4463 path.lineTo(0.0f, 0.1f * height); 4464 path.close(); 4465 canvas.drawPath(path, paint); 4466 } 4467 4468 /** 4469 * Computes and applies the Canvas transforms required to make the color 4470 * gamut of each color space visible in the final image. 4471 * 4472 * @param canvas The canvas to transform 4473 * @param width Width in pixel of the final image 4474 * @param height Height in pixel of the final image 4475 * @param primaries Array of 6 floats used to avoid temporary allocations 4476 */ 4477 private void setTransform(@NonNull Canvas canvas, int width, int height, 4478 @NonNull @Size(6) float[] primaries) { 4479 4480 RectF primariesBounds = new RectF(); 4481 for (final Pair<ColorSpace, Integer> item : mColorSpaces) { 4482 ColorSpace colorSpace = item.first; 4483 if (colorSpace.getModel() != Model.RGB) continue; 4484 4485 Rgb rgb = (Rgb) colorSpace; 4486 getPrimaries(rgb, primaries, mUcs); 4487 4488 primariesBounds.left = Math.min(primariesBounds.left, primaries[4]); 4489 primariesBounds.top = Math.min(primariesBounds.top, primaries[5]); 4490 primariesBounds.right = Math.max(primariesBounds.right, primaries[0]); 4491 primariesBounds.bottom = Math.max(primariesBounds.bottom, primaries[3]); 4492 } 4493 4494 float max = mUcs ? 0.6f : 0.9f; 4495 4496 primariesBounds.left = Math.min(0.0f, primariesBounds.left); 4497 primariesBounds.top = Math.min(0.0f, primariesBounds.top); 4498 primariesBounds.right = Math.max(max, primariesBounds.right); 4499 primariesBounds.bottom = Math.max(max, primariesBounds.bottom); 4500 4501 float scaleX = max / primariesBounds.width(); 4502 float scaleY = max / primariesBounds.height(); 4503 float scale = Math.min(scaleX, scaleY); 4504 4505 canvas.scale(mSize / (float) NATIVE_SIZE, mSize / (float) NATIVE_SIZE); 4506 canvas.scale(scale, scale); 4507 canvas.translate( 4508 (primariesBounds.width() - max) * width / 2.0f, 4509 (primariesBounds.height() - max) * height / 2.0f); 4510 4511 // The spectrum extends ~0.85 vertically and ~0.65 horizontally 4512 // We shift the canvas a little bit to get nicer margins 4513 canvas.translate(0.05f * width, -0.05f * height); 4514 } 4515 4516 /** 4517 * Computes and applies the Canvas transforms required to render the CIE 4518 * 197 UCS chromaticity diagram. 4519 * 4520 * @param canvas The canvas to transform 4521 * @param height Height in pixel of the final image 4522 */ 4523 private void setUcsTransform(@NonNull Canvas canvas, int height) { 4524 if (mUcs) { 4525 canvas.translate(0.0f, (height - height * UCS_SCALE)); 4526 canvas.scale(UCS_SCALE, UCS_SCALE); 4527 } 4528 } 4529 4530 // X coordinates of the spectral locus in CIE 1931 4531 private static final float[] SPECTRUM_LOCUS_X = { 4532 0.175596f, 0.172787f, 0.170806f, 0.170085f, 0.160343f, 4533 0.146958f, 0.139149f, 0.133536f, 0.126688f, 0.115830f, 4534 0.109616f, 0.099146f, 0.091310f, 0.078130f, 0.068717f, 4535 0.054675f, 0.040763f, 0.027497f, 0.016270f, 0.008169f, 4536 0.004876f, 0.003983f, 0.003859f, 0.004646f, 0.007988f, 4537 0.013870f, 0.022244f, 0.027273f, 0.032820f, 0.038851f, 4538 0.045327f, 0.052175f, 0.059323f, 0.066713f, 0.074299f, 4539 0.089937f, 0.114155f, 0.138695f, 0.154714f, 0.192865f, 4540 0.229607f, 0.265760f, 0.301588f, 0.337346f, 0.373083f, 4541 0.408717f, 0.444043f, 0.478755f, 0.512467f, 0.544767f, 4542 0.575132f, 0.602914f, 0.627018f, 0.648215f, 0.665746f, 4543 0.680061f, 0.691487f, 0.700589f, 0.707901f, 0.714015f, 4544 0.719017f, 0.723016f, 0.734674f, 0.717203f, 0.699732f, 4545 0.682260f, 0.664789f, 0.647318f, 0.629847f, 0.612376f, 4546 0.594905f, 0.577433f, 0.559962f, 0.542491f, 0.525020f, 4547 0.507549f, 0.490077f, 0.472606f, 0.455135f, 0.437664f, 4548 0.420193f, 0.402721f, 0.385250f, 0.367779f, 0.350308f, 4549 0.332837f, 0.315366f, 0.297894f, 0.280423f, 0.262952f, 4550 0.245481f, 0.228010f, 0.210538f, 0.193067f, 0.175596f 4551 }; 4552 // Y coordinates of the spectral locus in CIE 1931 4553 private static final float[] SPECTRUM_LOCUS_Y = { 4554 0.005295f, 0.004800f, 0.005472f, 0.005976f, 0.014496f, 4555 0.026643f, 0.035211f, 0.042704f, 0.053441f, 0.073601f, 4556 0.086866f, 0.112037f, 0.132737f, 0.170464f, 0.200773f, 4557 0.254155f, 0.317049f, 0.387997f, 0.463035f, 0.538504f, 4558 0.587196f, 0.610526f, 0.654897f, 0.675970f, 0.715407f, 4559 0.750246f, 0.779682f, 0.792153f, 0.802971f, 0.812059f, 4560 0.819430f, 0.825200f, 0.829460f, 0.832306f, 0.833833f, 4561 0.833316f, 0.826231f, 0.814796f, 0.805884f, 0.781648f, 4562 0.754347f, 0.724342f, 0.692326f, 0.658867f, 0.624470f, 4563 0.589626f, 0.554734f, 0.520222f, 0.486611f, 0.454454f, 4564 0.424252f, 0.396516f, 0.372510f, 0.351413f, 0.334028f, 4565 0.319765f, 0.308359f, 0.299317f, 0.292044f, 0.285945f, 4566 0.280951f, 0.276964f, 0.265326f, 0.257200f, 0.249074f, 4567 0.240948f, 0.232822f, 0.224696f, 0.216570f, 0.208444f, 4568 0.200318f, 0.192192f, 0.184066f, 0.175940f, 0.167814f, 4569 0.159688f, 0.151562f, 0.143436f, 0.135311f, 0.127185f, 4570 0.119059f, 0.110933f, 0.102807f, 0.094681f, 0.086555f, 4571 0.078429f, 0.070303f, 0.062177f, 0.054051f, 0.045925f, 4572 0.037799f, 0.029673f, 0.021547f, 0.013421f, 0.005295f 4573 }; 4574 4575 /** 4576 * Computes a 2D mesh representation of the CIE 1931 chromaticity 4577 * diagram. 4578 * 4579 * @param vertices Array of floats that will hold the mesh vertices 4580 * @param colors Array of floats that will hold the mesh colors 4581 */ 4582 private static void computeChromaticityMesh(@NonNull float[] vertices, 4583 @NonNull int[] colors) { 4584 4585 ColorSpace colorSpace = get(Named.SRGB); 4586 4587 float[] color = new float[3]; 4588 4589 int vertexIndex = 0; 4590 int colorIndex = 0; 4591 4592 for (int x = 0; x < SPECTRUM_LOCUS_X.length; x++) { 4593 int nextX = (x % (SPECTRUM_LOCUS_X.length - 1)) + 1; 4594 4595 float a1 = (float) Math.atan2( 4596 SPECTRUM_LOCUS_Y[x] - ONE_THIRD, 4597 SPECTRUM_LOCUS_X[x] - ONE_THIRD); 4598 float a2 = (float) Math.atan2( 4599 SPECTRUM_LOCUS_Y[nextX] - ONE_THIRD, 4600 SPECTRUM_LOCUS_X[nextX] - ONE_THIRD); 4601 4602 float radius1 = (float) Math.pow( 4603 sqr(SPECTRUM_LOCUS_X[x] - ONE_THIRD) + 4604 sqr(SPECTRUM_LOCUS_Y[x] - ONE_THIRD), 4605 0.5); 4606 float radius2 = (float) Math.pow( 4607 sqr(SPECTRUM_LOCUS_X[nextX] - ONE_THIRD) + 4608 sqr(SPECTRUM_LOCUS_Y[nextX] - ONE_THIRD), 4609 0.5); 4610 4611 // Compute patches; each patch is a quad with a different 4612 // color associated with each vertex 4613 for (int c = 1; c <= CHROMATICITY_RESOLUTION; c++) { 4614 float f1 = c / (float) CHROMATICITY_RESOLUTION; 4615 float f2 = (c - 1) / (float) CHROMATICITY_RESOLUTION; 4616 4617 double cr1 = radius1 * Math.cos(a1); 4618 double sr1 = radius1 * Math.sin(a1); 4619 double cr2 = radius2 * Math.cos(a2); 4620 double sr2 = radius2 * Math.sin(a2); 4621 4622 // Compute the XYZ coordinates of the 4 vertices of the patch 4623 float v1x = (float) (ONE_THIRD + cr1 * f1); 4624 float v1y = (float) (ONE_THIRD + sr1 * f1); 4625 float v1z = 1 - v1x - v1y; 4626 4627 float v2x = (float) (ONE_THIRD + cr1 * f2); 4628 float v2y = (float) (ONE_THIRD + sr1 * f2); 4629 float v2z = 1 - v2x - v2y; 4630 4631 float v3x = (float) (ONE_THIRD + cr2 * f2); 4632 float v3y = (float) (ONE_THIRD + sr2 * f2); 4633 float v3z = 1 - v3x - v3y; 4634 4635 float v4x = (float) (ONE_THIRD + cr2 * f1); 4636 float v4y = (float) (ONE_THIRD + sr2 * f1); 4637 float v4z = 1 - v4x - v4y; 4638 4639 // Compute the sRGB representation of each XYZ coordinate of the patch 4640 colors[colorIndex ] = computeColor(color, v1x, v1y, v1z, colorSpace); 4641 colors[colorIndex + 1] = computeColor(color, v2x, v2y, v2z, colorSpace); 4642 colors[colorIndex + 2] = computeColor(color, v3x, v3y, v3z, colorSpace); 4643 colors[colorIndex + 3] = colors[colorIndex]; 4644 colors[colorIndex + 4] = colors[colorIndex + 2]; 4645 colors[colorIndex + 5] = computeColor(color, v4x, v4y, v4z, colorSpace); 4646 colorIndex += 6; 4647 4648 // Flip the mesh upside down to match Canvas' coordinates system 4649 vertices[vertexIndex++] = v1x; 4650 vertices[vertexIndex++] = v1y; 4651 vertices[vertexIndex++] = v2x; 4652 vertices[vertexIndex++] = v2y; 4653 vertices[vertexIndex++] = v3x; 4654 vertices[vertexIndex++] = v3y; 4655 vertices[vertexIndex++] = v1x; 4656 vertices[vertexIndex++] = v1y; 4657 vertices[vertexIndex++] = v3x; 4658 vertices[vertexIndex++] = v3y; 4659 vertices[vertexIndex++] = v4x; 4660 vertices[vertexIndex++] = v4y; 4661 } 4662 } 4663 } 4664 4665 @ColorInt 4666 private static int computeColor(@NonNull @Size(3) float[] color, 4667 float x, float y, float z, @NonNull ColorSpace cs) { 4668 color[0] = x; 4669 color[1] = y; 4670 color[2] = z; 4671 cs.fromXyz(color); 4672 return 0xff000000 | 4673 (((int) (color[0] * 255.0f) & 0xff) << 16) | 4674 (((int) (color[1] * 255.0f) & 0xff) << 8) | 4675 (((int) (color[2] * 255.0f) & 0xff) ); 4676 } 4677 4678 private static double sqr(double v) { 4679 return v * v; 4680 } 4681 4682 private static class Point { 4683 @NonNull final ColorSpace mColorSpace; 4684 @NonNull final float[] mRgb; 4685 final int mColor; 4686 4687 Point(@NonNull ColorSpace colorSpace, 4688 @NonNull @Size(3) float[] rgb, @ColorInt int color) { 4689 mColorSpace = colorSpace; 4690 mRgb = rgb; 4691 mColor = color; 4692 } 4693 } 4694 } 4695 } 4696