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