ColorSpace.java revision efb4b06493fe7b1604c762a448b13c7af2845a8d
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.Nullable; 24import android.annotation.Size; 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 (OETF)</td> 267 * <td colspan="4">\(\begin{equation} 268 * C_{sRGB} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \lt 0.0031308 \\ 269 * 1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \ge 0.0031308 \end{cases} 270 * \end{equation}\) 271 * </td> 272 * </tr> 273 * <tr> 274 * <td>Electro-optical transfer function (EOTF)</td> 275 * <td colspan="4">\(\begin{equation} 276 * C_{linear} = \begin{cases}\frac{C_{sRGB}}{12.92} & C_{sRGB} \lt 0.04045 \\ 277 * \left( \frac{C_{sRGB} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \ge 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 (OETF)</td> 302 * <td colspan="4">\(C_{sRGB} = C_{linear}\)</td> 303 * </tr> 304 * <tr> 305 * <td>Electro-optical transfer function (EOTF)</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 (OETF)</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| \lt 0.0031308 \\ 332 * sign(C_{linear}) 1.055 \times \left| C_{linear} \right| ^{\frac{1}{2.4}} - 0.055 & 333 * \left| C_{linear} \right| \ge 0.0031308 \end{cases} 334 * \end{equation}\) 335 * </td> 336 * </tr> 337 * <tr> 338 * <td>Electro-optical transfer function (EOTF)</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| \lt 0.04045 \\ 342 * sign(C_{scRGB}) \left( \frac{\left| C_{scRGB} \right| + 0.055}{1.055} \right) ^{2.4} & 343 * \left| C_{scRGB} \right| \ge 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 (OETF)</td> 368 * <td colspan="4">\(C_{scRGB} = C_{linear}\)</td> 369 * </tr> 370 * <tr> 371 * <td>Electro-optical transfer function (EOTF)</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 (OETF)</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 (EOTF)</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 (OETF)</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 (EOTF)</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 (OETF)</td> 465 * <td colspan="4">\(C_{P3} = C_{linear}^{\frac{1}{2.6}}\)</td> 466 * </tr> 467 * <tr> 468 * <td>Electro-optical transfer function (EOTF)</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 (OETF)</td> 492 * <td colspan="4">\(\begin{equation} 493 * C_{sRGB} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \lt 0.0031308 \\ 494 * 1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \ge 0.0031308 \end{cases} 495 * \end{equation}\) 496 * </td> 497 * </tr> 498 * <tr> 499 * <td>Electro-optical transfer function (EOTF)</td> 500 * <td colspan="4">\(\begin{equation} 501 * C_{linear} = \begin{cases}\frac{C_{sRGB}}{12.92} & C_{sRGB} \lt 0.04045 \\ 502 * \left( \frac{C_{sRGB} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \ge 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 (OETF)</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 (EOTF)</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 (OETF)</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 (EOTF)</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 (OETF)</td> 597 * <td colspan="4">\(C_{RGB} = C_{linear}^{\frac{1}{2.2}}\)</td> 598 * </tr> 599 * <tr> 600 * <td>Electro-optical transfer function (EOTF)</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 (OETF)</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 (EOTF)</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 (OETF)</td> 659 * <td colspan="4">\(C_{ACES} = C_{linear}\)</td> 660 * </tr> 661 * <tr> 662 * <td>Electro-optical transfer function (EOTF)</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 (OETF)</td> 686 * <td colspan="4">\(C_{ACEScg} = C_{linear}\)</td> 687 * </tr> 688 * <tr> 689 * <td>Electro-optical transfer function (EOTF)</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>Returns a {@link Named} instance of {@link ColorSpace} that matches 1383 * the specified RGB to CIE XYZ transform and transfer functions. If no 1384 * instance can be found, this method returns null.</p> 1385 * 1386 * <p>The color transform matrix is assumed to target the CIE XYZ space 1387 * a {@link #ILLUMINANT_D50 D50} standard illuminant.</p> 1388 * 1389 * @param toXYZD50 3x3 column-major transform matrix from RGB to the profile 1390 * connection space CIE XYZ as an array of 9 floats, cannot be null 1391 * @param function Parameters for the transfer functions 1392 * @return A non-null {@link ColorSpace} if a match is found, null otherwise 1393 */ 1394 @Nullable 1395 public static ColorSpace match( 1396 @NonNull @Size(9) float[] toXYZD50, 1397 @NonNull Rgb.TransferParameters function) { 1398 1399 for (ColorSpace colorSpace : sNamedColorSpaces) { 1400 if (colorSpace.getModel() == Model.RGB) { 1401 ColorSpace.Rgb rgb = (ColorSpace.Rgb) adapt(colorSpace, ILLUMINANT_D50_XYZ); 1402 if (compare(toXYZD50, rgb.mTransform) && 1403 compare(function, rgb.mTransferParameters)) { 1404 return colorSpace; 1405 } 1406 } 1407 } 1408 1409 return null; 1410 } 1411 1412 /** 1413 * <p>Creates a new {@link Renderer} that can be used to visualize and 1414 * debug color spaces. See the documentation of {@link Renderer} for 1415 * more information.</p> 1416 * 1417 * @return A new non-null {@link Renderer} instance 1418 * 1419 * @see Renderer 1420 */ 1421 @NonNull 1422 public static Renderer createRenderer() { 1423 return new Renderer(); 1424 } 1425 1426 static { 1427 sNamedColorSpaces[Named.SRGB.ordinal()] = new ColorSpace.Rgb( 1428 "sRGB IEC61966-2.1", 1429 SRGB_PRIMARIES, 1430 ILLUMINANT_D65, 1431 new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4), 1432 Named.SRGB.ordinal() 1433 ); 1434 sNamedColorSpaces[Named.LINEAR_SRGB.ordinal()] = new ColorSpace.Rgb( 1435 "sRGB IEC61966-2.1 (Linear)", 1436 SRGB_PRIMARIES, 1437 ILLUMINANT_D65, 1438 1.0, 1439 0.0f, 1.0f, 1440 Named.LINEAR_SRGB.ordinal() 1441 ); 1442 sNamedColorSpaces[Named.EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb( 1443 "scRGB-nl IEC 61966-2-2:2003", 1444 SRGB_PRIMARIES, 1445 ILLUMINANT_D65, 1446 x -> absRcpResponse(x, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4), 1447 x -> absResponse(x, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4), 1448 -0.799f, 2.399f, 1449 Named.EXTENDED_SRGB.ordinal() 1450 ); 1451 sNamedColorSpaces[Named.LINEAR_EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb( 1452 "scRGB IEC 61966-2-2:2003", 1453 SRGB_PRIMARIES, 1454 ILLUMINANT_D65, 1455 1.0, 1456 -0.5f, 7.499f, 1457 Named.LINEAR_EXTENDED_SRGB.ordinal() 1458 ); 1459 sNamedColorSpaces[Named.BT709.ordinal()] = new ColorSpace.Rgb( 1460 "Rec. ITU-R BT.709-5", 1461 new float[] { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f }, 1462 ILLUMINANT_D65, 1463 new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45), 1464 Named.BT709.ordinal() 1465 ); 1466 sNamedColorSpaces[Named.BT2020.ordinal()] = new ColorSpace.Rgb( 1467 "Rec. ITU-R BT.2020-1", 1468 new float[] { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f }, 1469 ILLUMINANT_D65, 1470 new Rgb.TransferParameters(1 / 1.0993, 0.0993 / 1.0993, 1 / 4.5, 0.08145, 1 / 0.45), 1471 Named.BT2020.ordinal() 1472 ); 1473 sNamedColorSpaces[Named.DCI_P3.ordinal()] = new ColorSpace.Rgb( 1474 "SMPTE RP 431-2-2007 DCI (P3)", 1475 new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f }, 1476 new float[] { 0.314f, 0.351f }, 1477 2.6, 1478 0.0f, 1.0f, 1479 Named.DCI_P3.ordinal() 1480 ); 1481 sNamedColorSpaces[Named.DISPLAY_P3.ordinal()] = new ColorSpace.Rgb( 1482 "Display P3", 1483 new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f }, 1484 ILLUMINANT_D65, 1485 new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4), 1486 Named.DISPLAY_P3.ordinal() 1487 ); 1488 sNamedColorSpaces[Named.NTSC_1953.ordinal()] = new ColorSpace.Rgb( 1489 "NTSC (1953)", 1490 NTSC_1953_PRIMARIES, 1491 ILLUMINANT_C, 1492 new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45), 1493 Named.NTSC_1953.ordinal() 1494 ); 1495 sNamedColorSpaces[Named.SMPTE_C.ordinal()] = new ColorSpace.Rgb( 1496 "SMPTE-C RGB", 1497 new float[] { 0.630f, 0.340f, 0.310f, 0.595f, 0.155f, 0.070f }, 1498 ILLUMINANT_D65, 1499 new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45), 1500 Named.SMPTE_C.ordinal() 1501 ); 1502 sNamedColorSpaces[Named.ADOBE_RGB.ordinal()] = new ColorSpace.Rgb( 1503 "Adobe RGB (1998)", 1504 new float[] { 0.64f, 0.33f, 0.21f, 0.71f, 0.15f, 0.06f }, 1505 ILLUMINANT_D65, 1506 2.2, 1507 0.0f, 1.0f, 1508 Named.ADOBE_RGB.ordinal() 1509 ); 1510 sNamedColorSpaces[Named.PRO_PHOTO_RGB.ordinal()] = new ColorSpace.Rgb( 1511 "ROMM RGB ISO 22028-2:2013", 1512 new float[] { 0.7347f, 0.2653f, 0.1596f, 0.8404f, 0.0366f, 0.0001f }, 1513 ILLUMINANT_D50, 1514 new Rgb.TransferParameters(1.0, 0.0, 1 / 16.0, 0.031248, 1.8), 1515 Named.PRO_PHOTO_RGB.ordinal() 1516 ); 1517 sNamedColorSpaces[Named.ACES.ordinal()] = new ColorSpace.Rgb( 1518 "SMPTE ST 2065-1:2012 ACES", 1519 new float[] { 0.73470f, 0.26530f, 0.0f, 1.0f, 0.00010f, -0.0770f }, 1520 ILLUMINANT_D60, 1521 1.0, 1522 -65504.0f, 65504.0f, 1523 Named.ACES.ordinal() 1524 ); 1525 sNamedColorSpaces[Named.ACESCG.ordinal()] = new ColorSpace.Rgb( 1526 "Academy S-2014-004 ACEScg", 1527 new float[] { 0.713f, 0.293f, 0.165f, 0.830f, 0.128f, 0.044f }, 1528 ILLUMINANT_D60, 1529 1.0, 1530 -65504.0f, 65504.0f, 1531 Named.ACESCG.ordinal() 1532 ); 1533 sNamedColorSpaces[Named.CIE_XYZ.ordinal()] = new Xyz( 1534 "Generic XYZ", 1535 Named.CIE_XYZ.ordinal() 1536 ); 1537 sNamedColorSpaces[Named.CIE_LAB.ordinal()] = new ColorSpace.Lab( 1538 "Generic L*a*b*", 1539 Named.CIE_LAB.ordinal() 1540 ); 1541 } 1542 1543 // Reciprocal piecewise gamma response 1544 private static double rcpResponse(double x, double a, double b, double c, double d, double g) { 1545 return x >= d * c ? (Math.pow(x, 1.0 / g) - b) / a : x / c; 1546 } 1547 1548 // Piecewise gamma response 1549 private static double response(double x, double a, double b, double c, double d, double g) { 1550 return x >= d ? Math.pow(a * x + b, g) : c * x; 1551 } 1552 1553 // Reciprocal piecewise gamma response 1554 private static double rcpResponse(double x, double a, double b, double c, double d, 1555 double e, double f, double g) { 1556 return x >= d * c ? (Math.pow(x - e, 1.0 / g) - b) / a : (x - f) / c; 1557 } 1558 1559 // Piecewise gamma response 1560 private static double response(double x, double a, double b, double c, double d, 1561 double e, double f, double g) { 1562 return x >= d ? Math.pow(a * x + b, g) + e : c * x + f; 1563 } 1564 1565 // Reciprocal piecewise gamma response, encoded as sign(x).f(abs(x)) for color 1566 // spaces that allow negative values 1567 @SuppressWarnings("SameParameterValue") 1568 private static double absRcpResponse(double x, double a, double b, double c, double d, double g) { 1569 return Math.copySign(rcpResponse(x < 0.0 ? -x : x, a, b, c, d, g), x); 1570 } 1571 1572 // Piecewise gamma response, encoded as sign(x).f(abs(x)) for color spaces that 1573 // allow negative values 1574 @SuppressWarnings("SameParameterValue") 1575 private static double absResponse(double x, double a, double b, double c, double d, double g) { 1576 return Math.copySign(response(x < 0.0 ? -x : x, a, b, c, d, g), x); 1577 } 1578 1579 /** 1580 * Compares two sets of parametric transfer functions parameters with a precision of 1e-3. 1581 * 1582 * @param a The first set of parameters to compare 1583 * @param b The second set of parameters to compare 1584 * @return True if the two sets are equal, false otherwise 1585 */ 1586 private static boolean compare( 1587 @Nullable Rgb.TransferParameters a, 1588 @Nullable Rgb.TransferParameters b) { 1589 //noinspection SimplifiableIfStatement 1590 if (a == null && b == null) return true; 1591 return a != null && b != null && 1592 Math.abs(a.a - b.a) < 1e-3 && 1593 Math.abs(a.b - b.b) < 1e-3 && 1594 Math.abs(a.c - b.c) < 1e-3 && 1595 Math.abs(a.d - b.d) < 1e-3 && 1596 Math.abs(a.e - b.e) < 1e-3 && 1597 Math.abs(a.f - b.f) < 1e-3 && 1598 Math.abs(a.g - b.g) < 1e-3; 1599 } 1600 1601 /** 1602 * Compares two arrays of float with a precision of 1e-3. 1603 * 1604 * @param a The first array to compare 1605 * @param b The second array to compare 1606 * @return True if the two arrays are equal, false otherwise 1607 */ 1608 private static boolean compare(@NonNull float[] a, @NonNull float[] b) { 1609 if (a == b) return true; 1610 for (int i = 0; i < a.length; i++) { 1611 if (Float.compare(a[i], b[i]) != 0 && Math.abs(a[i] - b[i]) > 1e-3f) return false; 1612 } 1613 return true; 1614 } 1615 1616 /** 1617 * Inverts a 3x3 matrix. This method assumes the matrix is invertible. 1618 * 1619 * @param m A 3x3 matrix as a non-null array of 9 floats 1620 * @return A new array of 9 floats containing the inverse of the input matrix 1621 */ 1622 @NonNull 1623 @Size(9) 1624 private static float[] inverse3x3(@NonNull @Size(9) float[] m) { 1625 float a = m[0]; 1626 float b = m[3]; 1627 float c = m[6]; 1628 float d = m[1]; 1629 float e = m[4]; 1630 float f = m[7]; 1631 float g = m[2]; 1632 float h = m[5]; 1633 float i = m[8]; 1634 1635 float A = e * i - f * h; 1636 float B = f * g - d * i; 1637 float C = d * h - e * g; 1638 1639 float det = a * A + b * B + c * C; 1640 1641 float inverted[] = new float[m.length]; 1642 inverted[0] = A / det; 1643 inverted[1] = B / det; 1644 inverted[2] = C / det; 1645 inverted[3] = (c * h - b * i) / det; 1646 inverted[4] = (a * i - c * g) / det; 1647 inverted[5] = (b * g - a * h) / det; 1648 inverted[6] = (b * f - c * e) / det; 1649 inverted[7] = (c * d - a * f) / det; 1650 inverted[8] = (a * e - b * d) / det; 1651 return inverted; 1652 } 1653 1654 /** 1655 * Multiplies two 3x3 matrices, represented as non-null arrays of 9 floats. 1656 * 1657 * @param lhs 3x3 matrix, as a non-null array of 9 floats 1658 * @param rhs 3x3 matrix, as a non-null array of 9 floats 1659 * @return A new array of 9 floats containing the result of the multiplication 1660 * of rhs by lhs 1661 */ 1662 @NonNull 1663 @Size(9) 1664 private static float[] mul3x3(@NonNull @Size(9) float[] lhs, @NonNull @Size(9) float[] rhs) { 1665 float[] r = new float[9]; 1666 r[0] = lhs[0] * rhs[0] + lhs[3] * rhs[1] + lhs[6] * rhs[2]; 1667 r[1] = lhs[1] * rhs[0] + lhs[4] * rhs[1] + lhs[7] * rhs[2]; 1668 r[2] = lhs[2] * rhs[0] + lhs[5] * rhs[1] + lhs[8] * rhs[2]; 1669 r[3] = lhs[0] * rhs[3] + lhs[3] * rhs[4] + lhs[6] * rhs[5]; 1670 r[4] = lhs[1] * rhs[3] + lhs[4] * rhs[4] + lhs[7] * rhs[5]; 1671 r[5] = lhs[2] * rhs[3] + lhs[5] * rhs[4] + lhs[8] * rhs[5]; 1672 r[6] = lhs[0] * rhs[6] + lhs[3] * rhs[7] + lhs[6] * rhs[8]; 1673 r[7] = lhs[1] * rhs[6] + lhs[4] * rhs[7] + lhs[7] * rhs[8]; 1674 r[8] = lhs[2] * rhs[6] + lhs[5] * rhs[7] + lhs[8] * rhs[8]; 1675 return r; 1676 } 1677 1678 /** 1679 * Multiplies a vector of 3 components by a 3x3 matrix and stores the 1680 * result in the input vector. 1681 * 1682 * @param lhs 3x3 matrix, as a non-null array of 9 floats 1683 * @param rhs Vector of 3 components, as a non-null array of 3 floats 1684 * @return The array of 3 passed as the rhs parameter 1685 */ 1686 @NonNull 1687 @Size(min = 3) 1688 private static float[] mul3x3Float3( 1689 @NonNull @Size(9) float[] lhs, @NonNull @Size(min = 3) float[] rhs) { 1690 float r0 = rhs[0]; 1691 float r1 = rhs[1]; 1692 float r2 = rhs[2]; 1693 rhs[0] = lhs[0] * r0 + lhs[3] * r1 + lhs[6] * r2; 1694 rhs[1] = lhs[1] * r0 + lhs[4] * r1 + lhs[7] * r2; 1695 rhs[2] = lhs[2] * r0 + lhs[5] * r1 + lhs[8] * r2; 1696 return rhs; 1697 } 1698 1699 /** 1700 * Multiplies a diagonal 3x3 matrix lhs, represented as an array of 3 floats, 1701 * by a 3x3 matrix represented as an array of 9 floats. 1702 * 1703 * @param lhs Diagonal 3x3 matrix, as a non-null array of 3 floats 1704 * @param rhs 3x3 matrix, as a non-null array of 9 floats 1705 * @return A new array of 9 floats containing the result of the multiplication 1706 * of rhs by lhs 1707 */ 1708 @NonNull 1709 @Size(9) 1710 private static float[] mul3x3Diag( 1711 @NonNull @Size(3) float[] lhs, @NonNull @Size(9) float[] rhs) { 1712 return new float[] { 1713 lhs[0] * rhs[0], lhs[1] * rhs[1], lhs[2] * rhs[2], 1714 lhs[0] * rhs[3], lhs[1] * rhs[4], lhs[2] * rhs[5], 1715 lhs[0] * rhs[6], lhs[1] * rhs[7], lhs[2] * rhs[8] 1716 }; 1717 } 1718 1719 /** 1720 * Converts a value from CIE xyY to CIE XYZ. Y is assumed to be 1 so the 1721 * input xyY array only contains the x and y components. 1722 * 1723 * @param xyY The xyY value to convert to XYZ, cannot be null, length must be 2 1724 * @return A new float array of length 3 containing XYZ values 1725 */ 1726 @NonNull 1727 @Size(3) 1728 private static float[] xyYToXyz(@NonNull @Size(2) float[] xyY) { 1729 return new float[] { xyY[0] / xyY[1], 1.0f, (1 - xyY[0] - xyY[1]) / xyY[1] }; 1730 } 1731 1732 /** 1733 * Converts values from CIE xyY to CIE L*u*v*. Y is assumed to be 1 so the 1734 * input xyY array only contains the x and y components. After this method 1735 * returns, the xyY array contains the converted u and v components. 1736 * 1737 * @param xyY The xyY value to convert to XYZ, cannot be null, 1738 * length must be a multiple of 2 1739 */ 1740 private static void xyYToUv(@NonNull @Size(multiple = 2) float[] xyY) { 1741 for (int i = 0; i < xyY.length; i += 2) { 1742 float x = xyY[i]; 1743 float y = xyY[i + 1]; 1744 1745 float d = -2.0f * x + 12.0f * y + 3; 1746 float u = (4.0f * x) / d; 1747 float v = (9.0f * y) / d; 1748 1749 xyY[i] = u; 1750 xyY[i + 1] = v; 1751 } 1752 } 1753 1754 /** 1755 * <p>Computes the chromatic adaptation transform from the specified 1756 * source white point to the specified destination white point.</p> 1757 * 1758 * <p>The transform is computed using the von Kries method, described 1759 * in more details in the documentation of {@link Adaptation}. The 1760 * {@link Adaptation} enum provides different matrices that can be 1761 * used to perform the adaptation.</p> 1762 * 1763 * @param matrix The adaptation matrix 1764 * @param srcWhitePoint The white point to adapt from, *will be modified* 1765 * @param dstWhitePoint The white point to adapt to, *will be modified* 1766 * @return A 3x3 matrix as a non-null array of 9 floats 1767 */ 1768 @NonNull 1769 @Size(9) 1770 private static float[] chromaticAdaptation(@NonNull @Size(9) float[] matrix, 1771 @NonNull @Size(3) float[] srcWhitePoint, @NonNull @Size(3) float[] dstWhitePoint) { 1772 float[] srcLMS = mul3x3Float3(matrix, srcWhitePoint); 1773 float[] dstLMS = mul3x3Float3(matrix, dstWhitePoint); 1774 // LMS is a diagonal matrix stored as a float[3] 1775 float[] LMS = { dstLMS[0] / srcLMS[0], dstLMS[1] / srcLMS[1], dstLMS[2] / srcLMS[2] }; 1776 return mul3x3(inverse3x3(matrix), mul3x3Diag(LMS, matrix)); 1777 } 1778 1779 /** 1780 * Implementation of the CIE XYZ color space. Assumes the white point is D50. 1781 */ 1782 @AnyThread 1783 private static final class Xyz extends ColorSpace { 1784 private Xyz(@NonNull String name, @IntRange(from = MIN_ID, to = MAX_ID) int id) { 1785 super(name, Model.XYZ, id); 1786 } 1787 1788 @Override 1789 public boolean isWideGamut() { 1790 return true; 1791 } 1792 1793 @Override 1794 public float getMinValue(@IntRange(from = 0, to = 3) int component) { 1795 return -2.0f; 1796 } 1797 1798 @Override 1799 public float getMaxValue(@IntRange(from = 0, to = 3) int component) { 1800 return 2.0f; 1801 } 1802 1803 @Override 1804 public float[] toXyz(@NonNull @Size(min = 3) float[] v) { 1805 v[0] = clamp(v[0]); 1806 v[1] = clamp(v[1]); 1807 v[2] = clamp(v[2]); 1808 return v; 1809 } 1810 1811 @Override 1812 public float[] fromXyz(@NonNull @Size(min = 3) float[] v) { 1813 v[0] = clamp(v[0]); 1814 v[1] = clamp(v[1]); 1815 v[2] = clamp(v[2]); 1816 return v; 1817 } 1818 1819 private static float clamp(float x) { 1820 return x < -2.0f ? -2.0f : x > 2.0f ? 2.0f : x; 1821 } 1822 } 1823 1824 /** 1825 * Implementation of the CIE L*a*b* color space. Its PCS is CIE XYZ 1826 * with a white point of D50. 1827 */ 1828 @AnyThread 1829 private static final class Lab extends ColorSpace { 1830 private static final float A = 216.0f / 24389.0f; 1831 private static final float B = 841.0f / 108.0f; 1832 private static final float C = 4.0f / 29.0f; 1833 private static final float D = 6.0f / 29.0f; 1834 1835 private Lab(@NonNull String name, @IntRange(from = MIN_ID, to = MAX_ID) int id) { 1836 super(name, Model.LAB, id); 1837 } 1838 1839 @Override 1840 public boolean isWideGamut() { 1841 return true; 1842 } 1843 1844 @Override 1845 public float getMinValue(@IntRange(from = 0, to = 3) int component) { 1846 return component == 0 ? 0.0f : -128.0f; 1847 } 1848 1849 @Override 1850 public float getMaxValue(@IntRange(from = 0, to = 3) int component) { 1851 return component == 0 ? 100.0f : 128.0f; 1852 } 1853 1854 @Override 1855 public float[] toXyz(@NonNull @Size(min = 3) float[] v) { 1856 v[0] = clamp(v[0], 0.0f, 100.0f); 1857 v[1] = clamp(v[1], -128.0f, 128.0f); 1858 v[2] = clamp(v[2], -128.0f, 128.0f); 1859 1860 float fy = (v[0] + 16.0f) / 116.0f; 1861 float fx = fy + (v[1] * 0.002f); 1862 float fz = fy - (v[2] * 0.005f); 1863 float X = fx > D ? fx * fx * fx : (1.0f / B) * (fx - C); 1864 float Y = fy > D ? fy * fy * fy : (1.0f / B) * (fy - C); 1865 float Z = fz > D ? fz * fz * fz : (1.0f / B) * (fz - C); 1866 1867 v[0] = X * ILLUMINANT_D50_XYZ[0]; 1868 v[1] = Y * ILLUMINANT_D50_XYZ[1]; 1869 v[2] = Z * ILLUMINANT_D50_XYZ[2]; 1870 1871 return v; 1872 } 1873 1874 @Override 1875 public float[] fromXyz(@NonNull @Size(min = 3) float[] v) { 1876 float X = v[0] / ILLUMINANT_D50_XYZ[0]; 1877 float Y = v[1] / ILLUMINANT_D50_XYZ[1]; 1878 float Z = v[2] / ILLUMINANT_D50_XYZ[2]; 1879 1880 float fx = X > A ? (float) Math.pow(X, 1.0 / 3.0) : B * X + C; 1881 float fy = Y > A ? (float) Math.pow(Y, 1.0 / 3.0) : B * Y + C; 1882 float fz = Z > A ? (float) Math.pow(Z, 1.0 / 3.0) : B * Z + C; 1883 1884 float L = 116.0f * fy - 16.0f; 1885 float a = 500.0f * (fx - fy); 1886 float b = 200.0f * (fy - fz); 1887 1888 v[0] = clamp(L, 0.0f, 100.0f); 1889 v[1] = clamp(a, -128.0f, 128.0f); 1890 v[2] = clamp(b, -128.0f, 128.0f); 1891 1892 return v; 1893 } 1894 1895 private static float clamp(float x, float min, float max) { 1896 return x < min ? min : x > max ? max : x; 1897 } 1898 } 1899 1900 /** 1901 * {@usesMathJax} 1902 * 1903 * <p>An RGB color space is an additive color space using the 1904 * {@link Model#RGB RGB} color model (a color is therefore represented 1905 * by a tuple of 3 numbers).</p> 1906 * 1907 * <p>A specific RGB color space is defined by the following properties:</p> 1908 * <ul> 1909 * <li>Three chromaticities of the red, green and blue primaries, which 1910 * define the gamut of the color space.</li> 1911 * <li>A white point chromaticity that defines the stimulus to which 1912 * color space values are normalized (also just called "white").</li> 1913 * <li>An opto-electronic transfer function, also called opto-electronic 1914 * conversion function or often, and approximately, gamma function.</li> 1915 * <li>An electro-optical transfer function, also called electo-optical 1916 * conversion function or often, and approximately, gamma function.</li> 1917 * <li>A range of valid RGB values (most commonly \([0..1]\)).</li> 1918 * </ul> 1919 * 1920 * <p>The most commonly used RGB color space is {@link Named#SRGB sRGB}.</p> 1921 * 1922 * <h3>Primaries and white point chromaticities</h3> 1923 * <p>In this implementation, the chromaticity of the primaries and the white 1924 * point of an RGB color space is defined in the CIE xyY color space. This 1925 * color space separates the chromaticity of a color, the x and y components, 1926 * and its luminance, the Y component. Since the primaries and the white 1927 * point have full brightness, the Y component is assumed to be 1 and only 1928 * the x and y components are needed to encode them.</p> 1929 * <p>For convenience, this implementation also allows to define the 1930 * primaries and white point in the CIE XYZ space. The tristimulus XYZ values 1931 * are internally converted to xyY.</p> 1932 * 1933 * <p> 1934 * <img src="{@docRoot}reference/android/images/graphics/colorspace_srgb.png" /> 1935 * <figcaption style="text-align: center;">sRGB primaries and white point</figcaption> 1936 * </p> 1937 * 1938 * <h3>Transfer functions</h3> 1939 * <p>A transfer function is a color component conversion function, defined as 1940 * a single variable, monotonic mathematical function. It is applied to each 1941 * individual component of a color. They are used to perform the mapping 1942 * between linear tristimulus values and non-linear electronic signal value.</p> 1943 * <p>The <em>opto-electronic transfer function</em> (OETF or OECF) encodes 1944 * tristimulus values in a scene to a non-linear electronic signal value. 1945 * An OETF is often expressed as a power function with an exponent between 1946 * 0.38 and 0.55 (the reciprocal of 1.8 to 2.6).</p> 1947 * <p>The <em>electro-optical transfer function</em> (EOTF or EOCF) decodes 1948 * a non-linear electronic signal value to a tristimulus value at the display. 1949 * An EOTF is often expressed as a power function with an exponent between 1950 * 1.8 and 2.6.</p> 1951 * <p>Transfer functions are used as a compression scheme. For instance, 1952 * linear sRGB values would normally require 11 to 12 bits of precision to 1953 * store all values that can be perceived by the human eye. When encoding 1954 * sRGB values using the appropriate OETF (see {@link Named#SRGB sRGB} for 1955 * an exact mathematical description of that OETF), the values can be 1956 * compressed to only 8 bits precision.</p> 1957 * <p>When manipulating RGB values, particularly sRGB values, it is safe 1958 * to assume that these values have been encoded with the appropriate 1959 * OETF (unless noted otherwise). Encoded values are often said to be in 1960 * "gamma space". They are therefore defined in a non-linear space. This 1961 * in turns means that any linear operation applied to these values is 1962 * going to yield mathematically incorrect results (any linear interpolation 1963 * such as gradient generation for instance, most image processing functions 1964 * such as blurs, etc.).</p> 1965 * <p>To properly process encoded RGB values you must first apply the 1966 * EOTF to decode the value into linear space. After processing, the RGB 1967 * value must be encoded back to non-linear ("gamma") space. Here is a 1968 * formal description of the process, where \(f\) is the processing 1969 * function to apply:</p> 1970 * 1971 * $$RGB_{out} = OETF(f(EOTF(RGB_{in})))$$ 1972 * 1973 * <p>If the transfer functions of the color space can be expressed as an 1974 * ICC parametric curve as defined in ICC.1:2004-10, the numeric parameters 1975 * can be retrieved by calling {@link #getTransferParameters()}. This can 1976 * be useful to match color spaces for instance.</p> 1977 * 1978 * <p class="note">Some RGB color spaces, such as {@link Named#ACES} and 1979 * {@link Named#LINEAR_EXTENDED_SRGB scRGB}, are said to be linear because 1980 * their transfer functions are the identity function: \(f(x) = x\). 1981 * If the source and/or destination are known to be linear, it is not 1982 * necessary to invoke the transfer functions.</p> 1983 * 1984 * <h3>Range</h3> 1985 * <p>Most RGB color spaces allow RGB values in the range \([0..1]\). There 1986 * are however a few RGB color spaces that allow much larger ranges. For 1987 * instance, {@link Named#EXTENDED_SRGB scRGB} is used to manipulate the 1988 * range \([-0.5..7.5]\) while {@link Named#ACES ACES} can be used throughout 1989 * the range \([-65504, 65504]\).</p> 1990 * 1991 * <p> 1992 * <img src="{@docRoot}reference/android/images/graphics/colorspace_scrgb.png" /> 1993 * <figcaption style="text-align: center;">Extended sRGB and its large range</figcaption> 1994 * </p> 1995 * 1996 * <h3>Converting between RGB color spaces</h3> 1997 * <p>Conversion between two color spaces is achieved by using an intermediate 1998 * color space called the profile connection space (PCS). The PCS used by 1999 * this implementation is CIE XYZ. The conversion operation is defined 2000 * as such:</p> 2001 * 2002 * $$RGB_{out} = OETF(T_{dst}^{-1} \cdot T_{src} \cdot EOTF(RGB_{in}))$$ 2003 * 2004 * <p>Where \(T_{src}\) is the {@link #getTransform() RGB to XYZ transform} 2005 * of the source color space and \(T_{dst}^{-1}\) the {@link #getInverseTransform() 2006 * XYZ to RGB transform} of the destination color space.</p> 2007 * <p>Many RGB color spaces commonly used with electronic devices use the 2008 * standard illuminant {@link #ILLUMINANT_D65 D65}. Care must be take however 2009 * when converting between two RGB color spaces if their white points do not 2010 * match. This can be achieved by either calling 2011 * {@link #adapt(ColorSpace, float[])} to adapt one or both color spaces to 2012 * a single common white point. This can be achieved automatically by calling 2013 * {@link ColorSpace#connect(ColorSpace, ColorSpace)}, which also handles 2014 * non-RGB color spaces.</p> 2015 * <p>To learn more about the white point adaptation process, refer to the 2016 * documentation of {@link Adaptation}.</p> 2017 */ 2018 @AnyThread 2019 public static class Rgb extends ColorSpace { 2020 /** 2021 * {@usesMathJax} 2022 * 2023 * <p>Defines the parameters for the ICC parametric curve type 4, as 2024 * defined in ICC.1:2004-10, section 10.15.</p> 2025 * 2026 * <p>The EOTF is of the form:</p> 2027 * 2028 * \(\begin{equation} 2029 * Y = \begin{cases}c X + f & X \lt d \\ 2030 * \left( a X + b \right) ^{g} + e & X \ge d \end{cases} 2031 * \end{equation}\) 2032 * 2033 * <p>The corresponding OETF is simply the inverse function.</p> 2034 * 2035 * <p>The parameters defined by this class form a valid transfer 2036 * function only if all the following conditions are met:</p> 2037 * <ul> 2038 * <li>No parameter is a {@link Double#isNaN(double) Not-a-Number}</li> 2039 * <li>\(d\) is in the range \([0..1]\)</li> 2040 * <li>The function is not constant</li> 2041 * <li>The function is positive and increasing</li> 2042 * </ul> 2043 */ 2044 public static class TransferParameters { 2045 /** Variable \(a\) in the equation of the EOTF described above. */ 2046 public final double a; 2047 /** Variable \(b\) in the equation of the EOTF described above. */ 2048 public final double b; 2049 /** Variable \(c\) in the equation of the EOTF described above. */ 2050 public final double c; 2051 /** Variable \(d\) in the equation of the EOTF described above. */ 2052 public final double d; 2053 /** Variable \(e\) in the equation of the EOTF described above. */ 2054 public final double e; 2055 /** Variable \(f\) in the equation of the EOTF described above. */ 2056 public final double f; 2057 /** Variable \(g\) in the equation of the EOTF described above. */ 2058 public final double g; 2059 2060 /** 2061 * <p>Defines the parameters for the ICC parametric curve type 3, as 2062 * defined in ICC.1:2004-10, section 10.15.</p> 2063 * 2064 * <p>The EOTF is of the form:</p> 2065 * 2066 * \(\begin{equation} 2067 * Y = \begin{cases}c X & X \lt d \\ 2068 * \left( a X + b \right) ^{g} & X \ge d \end{cases} 2069 * \end{equation}\) 2070 * 2071 * <p>This constructor is equivalent to setting \(e\) and \(f\) to 0.</p> 2072 * 2073 * @param a The value of \(a\) in the equation of the EOTF described above 2074 * @param b The value of \(b\) in the equation of the EOTF described above 2075 * @param c The value of \(c\) in the equation of the EOTF described above 2076 * @param d The value of \(d\) in the equation of the EOTF described above 2077 * @param g The value of \(g\) in the equation of the EOTF described above 2078 * 2079 * @throws IllegalArgumentException If the parameters form an invalid transfer function 2080 */ 2081 public TransferParameters(double a, double b, double c, double d, double g) { 2082 this(a, b, c, d, 0.0, 0.0, g); 2083 } 2084 2085 /** 2086 * <p>Defines the parameters for the ICC parametric curve type 4, as 2087 * defined in ICC.1:2004-10, section 10.15.</p> 2088 * 2089 * @param a The value of \(a\) in the equation of the EOTF described above 2090 * @param b The value of \(b\) in the equation of the EOTF described above 2091 * @param c The value of \(c\) in the equation of the EOTF described above 2092 * @param d The value of \(d\) in the equation of the EOTF described above 2093 * @param e The value of \(e\) in the equation of the EOTF described above 2094 * @param f The value of \(f\) in the equation of the EOTF described above 2095 * @param g The value of \(g\) in the equation of the EOTF described above 2096 * 2097 * @throws IllegalArgumentException If the parameters form an invalid transfer function 2098 */ 2099 public TransferParameters(double a, double b, double c, double d, double e, 2100 double f, double g) { 2101 2102 if (Double.isNaN(a) || Double.isNaN(b) || Double.isNaN(c) || 2103 Double.isNaN(d) || Double.isNaN(e) || Double.isNaN(f) || 2104 Double.isNaN(g)) { 2105 throw new IllegalArgumentException("Parameters cannot be NaN"); 2106 } 2107 2108 if (!(d >= 0.0 && d <= 1.0 + Math.ulp(1.0))) { 2109 throw new IllegalArgumentException("Parameter d must be in the range [0..1]"); 2110 } 2111 2112 if (d == 0.0 && (a == 0.0 || g == 0.0)) { 2113 throw new IllegalArgumentException( 2114 "Parameter a or g is zero, the transfer function is constant"); 2115 } 2116 2117 if (d >= 1.0 && c == 0.0) { 2118 throw new IllegalArgumentException( 2119 "Parameter c is zero, the transfer function is constant"); 2120 } 2121 2122 if ((a == 0.0 || g == 0.0) && c == 0.0) { 2123 throw new IllegalArgumentException("Parameter a or g is zero," + 2124 " and c is zero, the transfer function is constant"); 2125 } 2126 2127 if (c < 0.0) { 2128 throw new IllegalArgumentException("The transfer function must be increasing"); 2129 } 2130 2131 if (a < 0.0 || g < 0.0) { 2132 throw new IllegalArgumentException("The transfer function must be " + 2133 "positive or increasing"); 2134 } 2135 2136 this.a = a; 2137 this.b = b; 2138 this.c = c; 2139 this.d = d; 2140 this.e = e; 2141 this.f = f; 2142 this.g = g; 2143 } 2144 2145 @SuppressWarnings("SimplifiableIfStatement") 2146 @Override 2147 public boolean equals(Object o) { 2148 if (this == o) return true; 2149 if (o == null || getClass() != o.getClass()) return false; 2150 2151 TransferParameters that = (TransferParameters) o; 2152 2153 if (Double.compare(that.a, a) != 0) return false; 2154 if (Double.compare(that.b, b) != 0) return false; 2155 if (Double.compare(that.c, c) != 0) return false; 2156 if (Double.compare(that.d, d) != 0) return false; 2157 if (Double.compare(that.e, e) != 0) return false; 2158 if (Double.compare(that.f, f) != 0) return false; 2159 return Double.compare(that.g, g) == 0; 2160 } 2161 2162 @Override 2163 public int hashCode() { 2164 int result; 2165 long temp; 2166 temp = Double.doubleToLongBits(a); 2167 result = (int) (temp ^ (temp >>> 32)); 2168 temp = Double.doubleToLongBits(b); 2169 result = 31 * result + (int) (temp ^ (temp >>> 32)); 2170 temp = Double.doubleToLongBits(c); 2171 result = 31 * result + (int) (temp ^ (temp >>> 32)); 2172 temp = Double.doubleToLongBits(d); 2173 result = 31 * result + (int) (temp ^ (temp >>> 32)); 2174 temp = Double.doubleToLongBits(e); 2175 result = 31 * result + (int) (temp ^ (temp >>> 32)); 2176 temp = Double.doubleToLongBits(f); 2177 result = 31 * result + (int) (temp ^ (temp >>> 32)); 2178 temp = Double.doubleToLongBits(g); 2179 result = 31 * result + (int) (temp ^ (temp >>> 32)); 2180 return result; 2181 } 2182 } 2183 2184 @NonNull private final float[] mWhitePoint; 2185 @NonNull private final float[] mPrimaries; 2186 @NonNull private final float[] mTransform; 2187 @NonNull private final float[] mInverseTransform; 2188 2189 @NonNull private final DoubleUnaryOperator mOetf; 2190 @NonNull private final DoubleUnaryOperator mEotf; 2191 @NonNull private final DoubleUnaryOperator mClampedOetf; 2192 @NonNull private final DoubleUnaryOperator mClampedEotf; 2193 2194 private final float mMin; 2195 private final float mMax; 2196 2197 private final boolean mIsWideGamut; 2198 private final boolean mIsSrgb; 2199 2200 @Nullable private TransferParameters mTransferParameters; 2201 2202 /** 2203 * <p>Creates a new RGB color space using a 3x3 column-major transform matrix. 2204 * The transform matrix must convert from the RGB space to the profile connection 2205 * space CIE XYZ.</p> 2206 * 2207 * <p class="note">The range of the color space is imposed to be \([0..1]\).</p> 2208 * 2209 * @param name Name of the color space, cannot be null, its length must be >= 1 2210 * @param toXYZ 3x3 column-major transform matrix from RGB to the profile 2211 * connection space CIE XYZ as an array of 9 floats, cannot be null 2212 * @param oetf Opto-electronic transfer function, cannot be null 2213 * @param eotf Electro-optical transfer function, cannot be null 2214 * 2215 * @throws IllegalArgumentException If any of the following conditions is met: 2216 * <ul> 2217 * <li>The name is null or has a length of 0.</li> 2218 * <li>The OETF is null or the EOTF is null.</li> 2219 * <li>The minimum valid value is >= the maximum valid value.</li> 2220 * </ul> 2221 * 2222 * @see #get(Named) 2223 */ 2224 public Rgb( 2225 @NonNull @Size(min = 1) String name, 2226 @NonNull @Size(9) float[] toXYZ, 2227 @NonNull DoubleUnaryOperator oetf, 2228 @NonNull DoubleUnaryOperator eotf) { 2229 this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ), 2230 oetf, eotf, 0.0f, 1.0f, MIN_ID); 2231 } 2232 2233 /** 2234 * <p>Creates a new RGB color space using a specified set of primaries 2235 * and a specified white point.</p> 2236 * 2237 * <p>The primaries and white point can be specified in the CIE xyY space 2238 * or in CIE XYZ. The length of the arrays depends on the chosen space:</p> 2239 * 2240 * <table summary="Parameters length"> 2241 * <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr> 2242 * <tr><td>xyY</td><td>6</td><td>2</td></tr> 2243 * <tr><td>XYZ</td><td>9</td><td>3</td></tr> 2244 * </table> 2245 * 2246 * <p>When the primaries and/or white point are specified in xyY, the Y component 2247 * does not need to be specified and is assumed to be 1.0. Only the xy components 2248 * are required.</p> 2249 * 2250 * <p class="note">The ID, areturned by {@link #getId()}, of an object created by 2251 * this constructor is always {@link #MIN_ID}.</p> 2252 * 2253 * @param name Name of the color space, cannot be null, its length must be >= 1 2254 * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats 2255 * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats 2256 * @param oetf Opto-electronic transfer function, cannot be null 2257 * @param eotf Electro-optical transfer function, cannot be null 2258 * @param min The minimum valid value in this color space's RGB range 2259 * @param max The maximum valid value in this color space's RGB range 2260 * 2261 * @throws IllegalArgumentException <p>If any of the following conditions is met:</p> 2262 * <ul> 2263 * <li>The name is null or has a length of 0.</li> 2264 * <li>The primaries array is null or has a length that is neither 6 or 9.</li> 2265 * <li>The white point array is null or has a length that is neither 2 or 3.</li> 2266 * <li>The OETF is null or the EOTF is null.</li> 2267 * <li>The minimum valid value is >= the maximum valid value.</li> 2268 * </ul> 2269 * 2270 * @see #get(Named) 2271 */ 2272 public Rgb( 2273 @NonNull @Size(min = 1) String name, 2274 @NonNull @Size(min = 6, max = 9) float[] primaries, 2275 @NonNull @Size(min = 2, max = 3) float[] whitePoint, 2276 @NonNull DoubleUnaryOperator oetf, 2277 @NonNull DoubleUnaryOperator eotf, 2278 float min, 2279 float max) { 2280 this(name, primaries, whitePoint, oetf, eotf, min, max, MIN_ID); 2281 } 2282 2283 /** 2284 * <p>Creates a new RGB color space using a 3x3 column-major transform matrix. 2285 * The transform matrix must convert from the RGB space to the profile connection 2286 * space CIE XYZ.</p> 2287 * 2288 * <p class="note">The range of the color space is imposed to be \([0..1]\).</p> 2289 * 2290 * @param name Name of the color space, cannot be null, its length must be >= 1 2291 * @param toXYZ 3x3 column-major transform matrix from RGB to the profile 2292 * connection space CIE XYZ as an array of 9 floats, cannot be null 2293 * @param function Parameters for the transfer functions 2294 * 2295 * @throws IllegalArgumentException If any of the following conditions is met: 2296 * <ul> 2297 * <li>The name is null or has a length of 0.</li> 2298 * <li>Gamma is negative.</li> 2299 * </ul> 2300 * 2301 * @see #get(Named) 2302 */ 2303 public Rgb( 2304 @NonNull @Size(min = 1) String name, 2305 @NonNull @Size(9) float[] toXYZ, 2306 @NonNull TransferParameters function) { 2307 this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ), function, MIN_ID); 2308 } 2309 2310 /** 2311 * <p>Creates a new RGB color space using a specified set of primaries 2312 * and a specified white point.</p> 2313 * 2314 * <p>The primaries and white point can be specified in the CIE xyY space 2315 * or in CIE XYZ. The length of the arrays depends on the chosen space:</p> 2316 * 2317 * <table summary="Parameters length"> 2318 * <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr> 2319 * <tr><td>xyY</td><td>6</td><td>2</td></tr> 2320 * <tr><td>XYZ</td><td>9</td><td>3</td></tr> 2321 * </table> 2322 * 2323 * <p>When the primaries and/or white point are specified in xyY, the Y component 2324 * does not need to be specified and is assumed to be 1.0. Only the xy components 2325 * are required.</p> 2326 * 2327 * @param name Name of the color space, cannot be null, its length must be >= 1 2328 * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats 2329 * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats 2330 * @param function Parameters for the transfer functions 2331 * 2332 * @throws IllegalArgumentException If any of the following conditions is met: 2333 * <ul> 2334 * <li>The name is null or has a length of 0.</li> 2335 * <li>The primaries array is null or has a length that is neither 6 or 9.</li> 2336 * <li>The white point array is null or has a length that is neither 2 or 3.</li> 2337 * <li>The transfer parameters are invalid.</li> 2338 * </ul> 2339 * 2340 * @see #get(Named) 2341 */ 2342 public Rgb( 2343 @NonNull @Size(min = 1) String name, 2344 @NonNull @Size(min = 6, max = 9) float[] primaries, 2345 @NonNull @Size(min = 2, max = 3) float[] whitePoint, 2346 @NonNull TransferParameters function) { 2347 this(name, primaries, whitePoint, function, MIN_ID); 2348 } 2349 2350 /** 2351 * <p>Creates a new RGB color space using a specified set of primaries 2352 * and a specified white point.</p> 2353 * 2354 * <p>The primaries and white point can be specified in the CIE xyY space 2355 * or in CIE XYZ. The length of the arrays depends on the chosen space:</p> 2356 * 2357 * <table summary="Parameters length"> 2358 * <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr> 2359 * <tr><td>xyY</td><td>6</td><td>2</td></tr> 2360 * <tr><td>XYZ</td><td>9</td><td>3</td></tr> 2361 * </table> 2362 * 2363 * <p>When the primaries and/or white point are specified in xyY, the Y component 2364 * does not need to be specified and is assumed to be 1.0. Only the xy components 2365 * are required.</p> 2366 * 2367 * @param name Name of the color space, cannot be null, its length must be >= 1 2368 * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats 2369 * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats 2370 * @param function Parameters for the transfer functions 2371 * @param id ID of this color space as an integer between {@link #MIN_ID} and {@link #MAX_ID} 2372 * 2373 * @throws IllegalArgumentException If any of the following conditions is met: 2374 * <ul> 2375 * <li>The name is null or has a length of 0.</li> 2376 * <li>The primaries array is null or has a length that is neither 6 or 9.</li> 2377 * <li>The white point array is null or has a length that is neither 2 or 3.</li> 2378 * <li>The ID is not between {@link #MIN_ID} and {@link #MAX_ID}.</li> 2379 * <li>The transfer parameters are invalid.</li> 2380 * </ul> 2381 * 2382 * @see #get(Named) 2383 */ 2384 private Rgb( 2385 @NonNull @Size(min = 1) String name, 2386 @NonNull @Size(min = 6, max = 9) float[] primaries, 2387 @NonNull @Size(min = 2, max = 3) float[] whitePoint, 2388 @NonNull TransferParameters function, 2389 @IntRange(from = MIN_ID, to = MAX_ID) int id) { 2390 this(name, primaries, whitePoint, 2391 function.e == 0.0 && function.f == 0.0 ? 2392 x -> rcpResponse(x, function.a, function.b, 2393 function.c, function.d, function.g) : 2394 x -> rcpResponse(x, function.a, function.b, function.c, 2395 function.d, function.e, function.f, function.g), 2396 function.e == 0.0 && function.f == 0.0 ? 2397 x -> response(x, function.a, function.b, 2398 function.c, function.d, function.g) : 2399 x -> response(x, function.a, function.b, function.c, 2400 function.d, function.e, function.f, function.g), 2401 0.0f, 1.0f, id); 2402 mTransferParameters = function; 2403 } 2404 2405 /** 2406 * <p>Creates a new RGB color space using a 3x3 column-major transform matrix. 2407 * The transform matrix must convert from the RGB space to the profile connection 2408 * space CIE XYZ.</p> 2409 * 2410 * <p class="note">The range of the color space is imposed to be \([0..1]\).</p> 2411 * 2412 * @param name Name of the color space, cannot be null, its length must be >= 1 2413 * @param toXYZ 3x3 column-major transform matrix from RGB to the profile 2414 * connection space CIE XYZ as an array of 9 floats, cannot be null 2415 * @param gamma Gamma to use as the transfer function 2416 * 2417 * @throws IllegalArgumentException If any of the following conditions is met: 2418 * <ul> 2419 * <li>The name is null or has a length of 0.</li> 2420 * <li>Gamma is negative.</li> 2421 * </ul> 2422 * 2423 * @see #get(Named) 2424 */ 2425 public Rgb( 2426 @NonNull @Size(min = 1) String name, 2427 @NonNull @Size(9) float[] toXYZ, 2428 double gamma) { 2429 this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ), gamma, 0.0f, 1.0f, MIN_ID); 2430 } 2431 2432 /** 2433 * <p>Creates a new RGB color space using a specified set of primaries 2434 * and a specified white point.</p> 2435 * 2436 * <p>The primaries and white point can be specified in the CIE xyY space 2437 * or in CIE XYZ. The length of the arrays depends on the chosen space:</p> 2438 * 2439 * <table summary="Parameters length"> 2440 * <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr> 2441 * <tr><td>xyY</td><td>6</td><td>2</td></tr> 2442 * <tr><td>XYZ</td><td>9</td><td>3</td></tr> 2443 * </table> 2444 * 2445 * <p>When the primaries and/or white point are specified in xyY, the Y component 2446 * does not need to be specified and is assumed to be 1.0. Only the xy components 2447 * are required.</p> 2448 * 2449 * @param name Name of the color space, cannot be null, its length must be >= 1 2450 * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats 2451 * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats 2452 * @param gamma Gamma to use as the transfer function 2453 * 2454 * @throws IllegalArgumentException If any of the following conditions is met: 2455 * <ul> 2456 * <li>The name is null or has a length of 0.</li> 2457 * <li>The primaries array is null or has a length that is neither 6 or 9.</li> 2458 * <li>The white point array is null or has a length that is neither 2 or 3.</li> 2459 * <li>Gamma is negative.</li> 2460 * </ul> 2461 * 2462 * @see #get(Named) 2463 */ 2464 public Rgb( 2465 @NonNull @Size(min = 1) String name, 2466 @NonNull @Size(min = 6, max = 9) float[] primaries, 2467 @NonNull @Size(min = 2, max = 3) float[] whitePoint, 2468 double gamma) { 2469 this(name, primaries, whitePoint, gamma, 0.0f, 1.0f, MIN_ID); 2470 } 2471 2472 /** 2473 * <p>Creates a new RGB color space using a specified set of primaries 2474 * and a specified white point.</p> 2475 * 2476 * <p>The primaries and white point can be specified in the CIE xyY space 2477 * or in CIE XYZ. The length of the arrays depends on the chosen space:</p> 2478 * 2479 * <table summary="Parameters length"> 2480 * <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr> 2481 * <tr><td>xyY</td><td>6</td><td>2</td></tr> 2482 * <tr><td>XYZ</td><td>9</td><td>3</td></tr> 2483 * </table> 2484 * 2485 * <p>When the primaries and/or white point are specified in xyY, the Y component 2486 * does not need to be specified and is assumed to be 1.0. Only the xy components 2487 * are required.</p> 2488 * 2489 * @param name Name of the color space, cannot be null, its length must be >= 1 2490 * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats 2491 * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats 2492 * @param gamma Gamma to use as the transfer function 2493 * @param min The minimum valid value in this color space's RGB range 2494 * @param max The maximum valid value in this color space's RGB range 2495 * @param id ID of this color space as an integer between {@link #MIN_ID} and {@link #MAX_ID} 2496 * 2497 * @throws IllegalArgumentException If any of the following conditions is met: 2498 * <ul> 2499 * <li>The name is null or has a length of 0.</li> 2500 * <li>The primaries array is null or has a length that is neither 6 or 9.</li> 2501 * <li>The white point array is null or has a length that is neither 2 or 3.</li> 2502 * <li>The minimum valid value is >= the maximum valid value.</li> 2503 * <li>The ID is not between {@link #MIN_ID} and {@link #MAX_ID}.</li> 2504 * <li>Gamma is negative.</li> 2505 * </ul> 2506 * 2507 * @see #get(Named) 2508 */ 2509 private Rgb( 2510 @NonNull @Size(min = 1) String name, 2511 @NonNull @Size(min = 6, max = 9) float[] primaries, 2512 @NonNull @Size(min = 2, max = 3) float[] whitePoint, 2513 double gamma, 2514 float min, 2515 float max, 2516 @IntRange(from = MIN_ID, to = MAX_ID) int id) { 2517 this(name, primaries, whitePoint, 2518 gamma == 1.0 ? DoubleUnaryOperator.identity() : 2519 x -> Math.pow(x < 0.0 ? 0.0 : x, 1 / gamma), 2520 gamma == 1.0 ? DoubleUnaryOperator.identity() : 2521 x -> Math.pow(x < 0.0 ? 0.0 : x, gamma), 2522 min, max, id); 2523 mTransferParameters = gamma == 1.0 ? 2524 new TransferParameters(0.0, 0.0, 1.0, 1.0 + Math.ulp(1.0), gamma) : 2525 new TransferParameters(1.0, 0.0, 0.0, 0.0, gamma); 2526 } 2527 2528 /** 2529 * <p>Creates a new RGB color space using a specified set of primaries 2530 * and a specified white point.</p> 2531 * 2532 * <p>The primaries and white point can be specified in the CIE xyY space 2533 * or in CIE XYZ. The length of the arrays depends on the chosen space:</p> 2534 * 2535 * <table summary="Parameters length"> 2536 * <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr> 2537 * <tr><td>xyY</td><td>6</td><td>2</td></tr> 2538 * <tr><td>XYZ</td><td>9</td><td>3</td></tr> 2539 * </table> 2540 * 2541 * <p>When the primaries and/or white point are specified in xyY, the Y component 2542 * does not need to be specified and is assumed to be 1.0. Only the xy components 2543 * are required.</p> 2544 * 2545 * @param name Name of the color space, cannot be null, its length must be >= 1 2546 * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats 2547 * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats 2548 * @param oetf Opto-electronic transfer function, cannot be null 2549 * @param eotf Electro-optical transfer function, cannot be null 2550 * @param min The minimum valid value in this color space's RGB range 2551 * @param max The maximum valid value in this color space's RGB range 2552 * @param id ID of this color space as an integer between {@link #MIN_ID} and {@link #MAX_ID} 2553 * 2554 * @throws IllegalArgumentException If any of the following conditions is met: 2555 * <ul> 2556 * <li>The name is null or has a length of 0.</li> 2557 * <li>The primaries array is null or has a length that is neither 6 or 9.</li> 2558 * <li>The white point array is null or has a length that is neither 2 or 3.</li> 2559 * <li>The OETF is null or the EOTF is null.</li> 2560 * <li>The minimum valid value is >= the maximum valid value.</li> 2561 * <li>The ID is not between {@link #MIN_ID} and {@link #MAX_ID}.</li> 2562 * </ul> 2563 * 2564 * @see #get(Named) 2565 */ 2566 private Rgb( 2567 @NonNull @Size(min = 1) String name, 2568 @NonNull @Size(min = 6, max = 9) float[] primaries, 2569 @NonNull @Size(min = 2, max = 3) float[] whitePoint, 2570 @NonNull DoubleUnaryOperator oetf, 2571 @NonNull DoubleUnaryOperator eotf, 2572 float min, 2573 float max, 2574 @IntRange(from = MIN_ID, to = MAX_ID) int id) { 2575 2576 super(name, Model.RGB, id); 2577 2578 if (primaries == null || (primaries.length != 6 && primaries.length != 9)) { 2579 throw new IllegalArgumentException("The color space's primaries must be " + 2580 "defined as an array of 6 floats in xyY or 9 floats in XYZ"); 2581 } 2582 2583 if (whitePoint == null || (whitePoint.length != 2 && whitePoint.length != 3)) { 2584 throw new IllegalArgumentException("The color space's white point must be " + 2585 "defined as an array of 2 floats in xyY or 3 float in XYZ"); 2586 } 2587 2588 if (oetf == null || eotf == null) { 2589 throw new IllegalArgumentException("The transfer functions of a color space " + 2590 "cannot be null"); 2591 } 2592 2593 if (min >= max) { 2594 throw new IllegalArgumentException("Invalid range: min=" + min + ", max=" + max + 2595 "; min must be strictly < max"); 2596 } 2597 2598 mWhitePoint = xyWhitePoint(whitePoint); 2599 mPrimaries = xyPrimaries(primaries); 2600 2601 mTransform = computeXYZMatrix(mPrimaries, mWhitePoint); 2602 mInverseTransform = inverse3x3(mTransform); 2603 2604 mOetf = oetf; 2605 mEotf = eotf; 2606 2607 mMin = min; 2608 mMax = max; 2609 2610 DoubleUnaryOperator clamp = this::clamp; 2611 mClampedOetf = oetf.andThen(clamp); 2612 mClampedEotf = clamp.andThen(eotf); 2613 2614 // A color space is wide-gamut if its area is >90% of NTSC 1953 and 2615 // if it entirely contains the Color space definition in xyY 2616 mIsWideGamut = isWideGamut(mPrimaries, min, max); 2617 mIsSrgb = isSrgb(mPrimaries, mWhitePoint, oetf, eotf, min, max, id); 2618 } 2619 2620 /** 2621 * Creates a copy of the specified color space with a new transform. 2622 * 2623 * @param colorSpace The color space to create a copy of 2624 */ 2625 private Rgb(Rgb colorSpace, 2626 @NonNull @Size(9) float[] transform, 2627 @NonNull @Size(min = 2, max = 3) float[] whitePoint) { 2628 super(colorSpace.getName(), Model.RGB, -1); 2629 2630 mWhitePoint = xyWhitePoint(whitePoint); 2631 mPrimaries = colorSpace.mPrimaries; 2632 2633 mTransform = transform; 2634 mInverseTransform = inverse3x3(transform); 2635 2636 mMin = colorSpace.mMin; 2637 mMax = colorSpace.mMax; 2638 2639 mOetf = colorSpace.mOetf; 2640 mEotf = colorSpace.mEotf; 2641 2642 mClampedOetf = colorSpace.mClampedOetf; 2643 mClampedEotf = colorSpace.mClampedEotf; 2644 2645 mIsWideGamut = colorSpace.mIsWideGamut; 2646 mIsSrgb = colorSpace.mIsSrgb; 2647 2648 mTransferParameters = colorSpace.mTransferParameters; 2649 } 2650 2651 /** 2652 * Copies the non-adapted CIE xyY white point of this color space in 2653 * specified array. The Y component is assumed to be 1 and is therefore 2654 * not copied into the destination. The x and y components are written 2655 * in the array at positions 0 and 1 respectively. 2656 * 2657 * @param whitePoint The destination array, cannot be null, its length 2658 * must be >= 2 2659 * 2660 * @return The destination array passed as a parameter 2661 * 2662 * @see #getWhitePoint(float[]) 2663 */ 2664 @NonNull 2665 @Size(min = 2) 2666 public float[] getWhitePoint(@NonNull @Size(min = 2) float[] whitePoint) { 2667 whitePoint[0] = mWhitePoint[0]; 2668 whitePoint[1] = mWhitePoint[1]; 2669 return whitePoint; 2670 } 2671 2672 /** 2673 * Returns the non-adapted CIE xyY white point of this color space as 2674 * a new array of 2 floats. The Y component is assumed to be 1 and is 2675 * therefore not copied into the destination. The x and y components 2676 * are written in the array at positions 0 and 1 respectively. 2677 * 2678 * @return A new non-null array of 2 floats 2679 * 2680 * @see #getWhitePoint() 2681 */ 2682 @NonNull 2683 @Size(2) 2684 public float[] getWhitePoint() { 2685 return Arrays.copyOf(mWhitePoint, mWhitePoint.length); 2686 } 2687 2688 /** 2689 * Copies the primaries of this color space in specified array. The Y 2690 * component is assumed to be 1 and is therefore not copied into the 2691 * destination. The x and y components of the first primary are written 2692 * in the array at positions 0 and 1 respectively. 2693 * 2694 * @param primaries The destination array, cannot be null, its length 2695 * must be >= 6 2696 * 2697 * @return The destination array passed as a parameter 2698 * 2699 * @see #getPrimaries(float[]) 2700 */ 2701 @NonNull 2702 @Size(min = 6) 2703 public float[] getPrimaries(@NonNull @Size(min = 6) float[] primaries) { 2704 System.arraycopy(mPrimaries, 0, primaries, 0, mPrimaries.length); 2705 return primaries; 2706 } 2707 2708 /** 2709 * Returns the primaries of this color space as a new array of 6 floats. 2710 * The Y component is assumed to be 1 and is therefore not copied into 2711 * the destination. The x and y components of the first primary are 2712 * written in the array at positions 0 and 1 respectively. 2713 * 2714 * @return A new non-null array of 2 floats 2715 * 2716 * @see #getWhitePoint() 2717 */ 2718 @NonNull 2719 @Size(6) 2720 public float[] getPrimaries() { 2721 return Arrays.copyOf(mPrimaries, mPrimaries.length); 2722 } 2723 2724 /** 2725 * <p>Copies the transform of this color space in specified array. The 2726 * transform is used to convert from RGB to XYZ (with the same white 2727 * point as this color space). To connect color spaces, you must first 2728 * {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them to the 2729 * same white point.</p> 2730 * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)} 2731 * to convert between color spaces.</p> 2732 * 2733 * @param transform The destination array, cannot be null, its length 2734 * must be >= 9 2735 * 2736 * @return The destination array passed as a parameter 2737 * 2738 * @see #getInverseTransform() 2739 */ 2740 @NonNull 2741 @Size(min = 9) 2742 public float[] getTransform(@NonNull @Size(min = 9) float[] transform) { 2743 System.arraycopy(mTransform, 0, transform, 0, mTransform.length); 2744 return transform; 2745 } 2746 2747 /** 2748 * <p>Returns the transform of this color space as a new array. The 2749 * transform is used to convert from RGB to XYZ (with the same white 2750 * point as this color space). To connect color spaces, you must first 2751 * {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them to the 2752 * same white point.</p> 2753 * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)} 2754 * to convert between color spaces.</p> 2755 * 2756 * @return A new array of 9 floats 2757 * 2758 * @see #getInverseTransform(float[]) 2759 */ 2760 @NonNull 2761 @Size(9) 2762 public float[] getTransform() { 2763 return Arrays.copyOf(mTransform, mTransform.length); 2764 } 2765 2766 /** 2767 * <p>Copies the inverse transform of this color space in specified array. 2768 * The inverse transform is used to convert from XYZ to RGB (with the 2769 * same white point as this color space). To connect color spaces, you 2770 * must first {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them 2771 * to the same white point.</p> 2772 * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)} 2773 * to convert between color spaces.</p> 2774 * 2775 * @param inverseTransform The destination array, cannot be null, its length 2776 * must be >= 9 2777 * 2778 * @return The destination array passed as a parameter 2779 * 2780 * @see #getTransform() 2781 */ 2782 @NonNull 2783 @Size(min = 9) 2784 public float[] getInverseTransform(@NonNull @Size(min = 9) float[] inverseTransform) { 2785 System.arraycopy(mInverseTransform, 0, inverseTransform, 0, mInverseTransform.length); 2786 return inverseTransform; 2787 } 2788 2789 /** 2790 * <p>Returns the inverse transform of this color space as a new array. 2791 * The inverse transform is used to convert from XYZ to RGB (with the 2792 * same white point as this color space). To connect color spaces, you 2793 * must first {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them 2794 * to the same white point.</p> 2795 * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)} 2796 * to convert between color spaces.</p> 2797 * 2798 * @return A new array of 9 floats 2799 * 2800 * @see #getTransform(float[]) 2801 */ 2802 @NonNull 2803 @Size(9) 2804 public float[] getInverseTransform() { 2805 return Arrays.copyOf(mInverseTransform, mInverseTransform.length); 2806 } 2807 2808 /** 2809 * <p>Returns the opto-electronic transfer function (OETF) of this color space. 2810 * The inverse function is the electro-optical transfer function (EOTF) returned 2811 * by {@link #getEotf()}. These functions are defined to satisfy the following 2812 * equality for \(x \in [0..1]\):</p> 2813 * 2814 * $$OETF(EOTF(x)) = EOTF(OETF(x)) = x$$ 2815 * 2816 * <p>For RGB colors, this function can be used to convert from linear space 2817 * to "gamma space" (gamma encoded). The terms gamma space and gamma encoded 2818 * are frequently used because many OETFs can be closely approximated using 2819 * a simple power function of the form \(x^{\frac{1}{\gamma}}\) (the 2820 * approximation of the {@link Named#SRGB sRGB} OETF uses \(\gamma=2.2\) 2821 * for instance).</p> 2822 * 2823 * @return A transfer function that converts from linear space to "gamma space" 2824 * 2825 * @see #getEotf() 2826 * @see #getTransferParameters() 2827 */ 2828 @NonNull 2829 public DoubleUnaryOperator getOetf() { 2830 return mClampedOetf; 2831 } 2832 2833 /** 2834 * <p>Returns the electro-optical transfer function (EOTF) of this color space. 2835 * The inverse function is the opto-electronic transfer function (OETF) 2836 * returned by {@link #getOetf()}. These functions are defined to satisfy the 2837 * following equality for \(x \in [0..1]\):</p> 2838 * 2839 * $$OETF(EOTF(x)) = EOTF(OETF(x)) = x$$ 2840 * 2841 * <p>For RGB colors, this function can be used to convert from "gamma space" 2842 * (gamma encoded) to linear space. The terms gamma space and gamma encoded 2843 * are frequently used because many EOTFs can be closely approximated using 2844 * a simple power function of the form \(x^\gamma\) (the approximation of the 2845 * {@link Named#SRGB sRGB} EOTF uses \(\gamma=2.2\) for instance).</p> 2846 * 2847 * @return A transfer function that converts from "gamma space" to linear space 2848 * 2849 * @see #getOetf() 2850 * @see #getTransferParameters() 2851 */ 2852 @NonNull 2853 public DoubleUnaryOperator getEotf() { 2854 return mClampedEotf; 2855 } 2856 2857 /** 2858 * <p>Returns the parameters used by the {@link #getEotf() electro-optical} 2859 * and {@link #getOetf() opto-electronic} transfer functions. If the transfer 2860 * functions do not match the ICC parametric curves defined in ICC.1:2004-10 2861 * (section 10.15), this method returns null.</p> 2862 * 2863 * <p>See {@link TransferParameters} for a full description of the transfer 2864 * functions.</p> 2865 * 2866 * @return An instance of {@link TransferParameters} or null if this color 2867 * space's transfer functions do not match the equation defined in 2868 * {@link TransferParameters} 2869 */ 2870 @Nullable 2871 public TransferParameters getTransferParameters() { 2872 return mTransferParameters; 2873 } 2874 2875 @Override 2876 public boolean isSrgb() { 2877 return mIsSrgb; 2878 } 2879 2880 @Override 2881 public boolean isWideGamut() { 2882 return mIsWideGamut; 2883 } 2884 2885 @Override 2886 public float getMinValue(int component) { 2887 return mMin; 2888 } 2889 2890 @Override 2891 public float getMaxValue(int component) { 2892 return mMax; 2893 } 2894 2895 /** 2896 * <p>Decodes an RGB value to linear space. This is achieved by 2897 * applying this color space's electro-optical transfer function 2898 * to the supplied values.</p> 2899 * 2900 * <p>Refer to the documentation of {@link ColorSpace.Rgb} for 2901 * more information about transfer functions and their use for 2902 * encoding and decoding RGB values.</p> 2903 * 2904 * @param r The red component to decode to linear space 2905 * @param g The green component to decode to linear space 2906 * @param b The blue component to decode to linear space 2907 * @return A new array of 3 floats containing linear RGB values 2908 * 2909 * @see #toLinear(float[]) 2910 * @see #fromLinear(float, float, float) 2911 */ 2912 @NonNull 2913 @Size(3) 2914 public float[] toLinear(float r, float g, float b) { 2915 return toLinear(new float[] { r, g, b }); 2916 } 2917 2918 /** 2919 * <p>Decodes an RGB value to linear space. This is achieved by 2920 * applying this color space's electro-optical transfer function 2921 * to the first 3 values of the supplied array. The result is 2922 * stored back in the input array.</p> 2923 * 2924 * <p>Refer to the documentation of {@link ColorSpace.Rgb} for 2925 * more information about transfer functions and their use for 2926 * encoding and decoding RGB values.</p> 2927 * 2928 * @param v A non-null array of non-linear RGB values, its length 2929 * must be at least 3 2930 * @return The specified array 2931 * 2932 * @see #toLinear(float, float, float) 2933 * @see #fromLinear(float[]) 2934 */ 2935 @NonNull 2936 @Size(min = 3) 2937 public float[] toLinear(@NonNull @Size(min = 3) float[] v) { 2938 v[0] = (float) mClampedEotf.applyAsDouble(v[0]); 2939 v[1] = (float) mClampedEotf.applyAsDouble(v[1]); 2940 v[2] = (float) mClampedEotf.applyAsDouble(v[2]); 2941 return v; 2942 } 2943 2944 /** 2945 * <p>Encodes an RGB value from linear space to this color space's 2946 * "gamma space". This is achieved by applying this color space's 2947 * opto-electronic transfer function to the supplied values.</p> 2948 * 2949 * <p>Refer to the documentation of {@link ColorSpace.Rgb} for 2950 * more information about transfer functions and their use for 2951 * encoding and decoding RGB values.</p> 2952 * 2953 * @param r The red component to encode from linear space 2954 * @param g The green component to encode from linear space 2955 * @param b The blue component to encode from linear space 2956 * @return A new array of 3 floats containing non-linear RGB values 2957 * 2958 * @see #fromLinear(float[]) 2959 * @see #toLinear(float, float, float) 2960 */ 2961 @NonNull 2962 @Size(3) 2963 public float[] fromLinear(float r, float g, float b) { 2964 return fromLinear(new float[] { r, g, b }); 2965 } 2966 2967 /** 2968 * <p>Encodes an RGB value from linear space to this color space's 2969 * "gamma space". This is achieved by applying this color space's 2970 * opto-electronic transfer function to the first 3 values of the 2971 * supplied array. The result is stored back in the input array.</p> 2972 * 2973 * <p>Refer to the documentation of {@link ColorSpace.Rgb} for 2974 * more information about transfer functions and their use for 2975 * encoding and decoding RGB values.</p> 2976 * 2977 * @param v A non-null array of linear RGB values, its length 2978 * must be at least 3 2979 * @return A new array of 3 floats containing non-linear RGB values 2980 * 2981 * @see #fromLinear(float[]) 2982 * @see #toLinear(float, float, float) 2983 */ 2984 @NonNull 2985 @Size(min = 3) 2986 public float[] fromLinear(@NonNull @Size(min = 3) float[] v) { 2987 v[0] = (float) mClampedOetf.applyAsDouble(v[0]); 2988 v[1] = (float) mClampedOetf.applyAsDouble(v[1]); 2989 v[2] = (float) mClampedOetf.applyAsDouble(v[2]); 2990 return v; 2991 } 2992 2993 @Override 2994 @NonNull 2995 @Size(min = 3) 2996 public float[] toXyz(@NonNull @Size(min = 3) float[] v) { 2997 v[0] = (float) mClampedEotf.applyAsDouble(v[0]); 2998 v[1] = (float) mClampedEotf.applyAsDouble(v[1]); 2999 v[2] = (float) mClampedEotf.applyAsDouble(v[2]); 3000 return mul3x3Float3(mTransform, v); 3001 } 3002 3003 @Override 3004 @NonNull 3005 @Size(min = 3) 3006 public float[] fromXyz(@NonNull @Size(min = 3) float[] v) { 3007 mul3x3Float3(mInverseTransform, v); 3008 v[0] = (float) mClampedOetf.applyAsDouble(v[0]); 3009 v[1] = (float) mClampedOetf.applyAsDouble(v[1]); 3010 v[2] = (float) mClampedOetf.applyAsDouble(v[2]); 3011 return v; 3012 } 3013 3014 private double clamp(double x) { 3015 return x < mMin ? mMin : x > mMax ? mMax : x; 3016 } 3017 3018 @Override 3019 public boolean equals(Object o) { 3020 if (this == o) return true; 3021 if (o == null || getClass() != o.getClass()) return false; 3022 if (!super.equals(o)) return false; 3023 3024 Rgb rgb = (Rgb) o; 3025 3026 if (Float.compare(rgb.mMin, mMin) != 0) return false; 3027 if (Float.compare(rgb.mMax, mMax) != 0) return false; 3028 if (!Arrays.equals(mWhitePoint, rgb.mWhitePoint)) return false; 3029 if (!Arrays.equals(mPrimaries, rgb.mPrimaries)) return false; 3030 if (mTransferParameters != null) { 3031 return mTransferParameters.equals(rgb.mTransferParameters); 3032 } else if (rgb.mTransferParameters == null) { 3033 return true; 3034 } 3035 //noinspection SimplifiableIfStatement 3036 if (!mOetf.equals(rgb.mOetf)) return false; 3037 return mEotf.equals(rgb.mEotf); 3038 } 3039 3040 @Override 3041 public int hashCode() { 3042 int result = super.hashCode(); 3043 result = 31 * result + Arrays.hashCode(mWhitePoint); 3044 result = 31 * result + Arrays.hashCode(mPrimaries); 3045 result = 31 * result + (mMin != +0.0f ? Float.floatToIntBits(mMin) : 0); 3046 result = 31 * result + (mMax != +0.0f ? Float.floatToIntBits(mMax) : 0); 3047 result = 31 * result + 3048 (mTransferParameters != null ? mTransferParameters.hashCode() : 0); 3049 if (mTransferParameters == null) { 3050 result = 31 * result + mOetf.hashCode(); 3051 result = 31 * result + mEotf.hashCode(); 3052 } 3053 return result; 3054 } 3055 3056 /** 3057 * Computes whether a color space is the sRGB color space or at least 3058 * a close approximation. 3059 * 3060 * @param primaries The set of RGB primaries in xyY as an array of 6 floats 3061 * @param whitePoint The white point in xyY as an array of 2 floats 3062 * @param OETF The opto-electronic transfer function 3063 * @param EOTF The electro-optical transfer function 3064 * @param min The minimum value of the color space's range 3065 * @param max The minimum value of the color space's range 3066 * @param id The ID of the color space 3067 * @return True if the color space can be considered as the sRGB color space 3068 * 3069 * @see #isSrgb() 3070 */ 3071 @SuppressWarnings("RedundantIfStatement") 3072 private static boolean isSrgb( 3073 @NonNull @Size(6) float[] primaries, 3074 @NonNull @Size(2) float[] whitePoint, 3075 @NonNull DoubleUnaryOperator OETF, 3076 @NonNull DoubleUnaryOperator EOTF, 3077 float min, 3078 float max, 3079 @IntRange(from = MIN_ID, to = MAX_ID) int id) { 3080 if (id == 0) return true; 3081 if (!compare(primaries, SRGB_PRIMARIES)) { 3082 return false; 3083 } 3084 if (!compare(whitePoint, ILLUMINANT_D65)) { 3085 return false; 3086 } 3087 if (OETF.applyAsDouble(0.5) < 0.5001) return false; 3088 if (EOTF.applyAsDouble(0.5) > 0.5001) return false; 3089 if (min != 0.0f) return false; 3090 if (max != 1.0f) return false; 3091 return true; 3092 } 3093 3094 /** 3095 * Computes whether the specified CIE xyY or XYZ primaries (with Y set to 1) form 3096 * a wide color gamut. A color gamut is considered wide if its area is > 90% 3097 * of the area of NTSC 1953 and if it contains the sRGB color gamut entirely. 3098 * If the conditions above are not met, the color space is considered as having 3099 * a wide color gamut if its range is larger than [0..1]. 3100 * 3101 * @param primaries RGB primaries in CIE xyY as an array of 6 floats 3102 * @param min The minimum value of the color space's range 3103 * @param max The minimum value of the color space's range 3104 * @return True if the color space has a wide gamut, false otherwise 3105 * 3106 * @see #isWideGamut() 3107 * @see #area(float[]) 3108 */ 3109 private static boolean isWideGamut(@NonNull @Size(6) float[] primaries, 3110 float min, float max) { 3111 return (area(primaries) / area(NTSC_1953_PRIMARIES) > 0.9f && 3112 contains(primaries, SRGB_PRIMARIES)) || (min < 0.0f && max > 1.0f); 3113 } 3114 3115 /** 3116 * Computes the area of the triangle represented by a set of RGB primaries 3117 * in the CIE xyY space. 3118 * 3119 * @param primaries The triangle's vertices, as RGB primaries in an array of 6 floats 3120 * @return The area of the triangle 3121 * 3122 * @see #isWideGamut(float[], float, float) 3123 */ 3124 private static float area(@NonNull @Size(6) float[] primaries) { 3125 float Rx = primaries[0]; 3126 float Ry = primaries[1]; 3127 float Gx = primaries[2]; 3128 float Gy = primaries[3]; 3129 float Bx = primaries[4]; 3130 float By = primaries[5]; 3131 float det = Rx * Gy + Ry * Bx + Gx * By - Gy * Bx - Ry * Gx - Rx * By; 3132 float r = 0.5f * det; 3133 return r < 0.0f ? -r : r; 3134 } 3135 3136 /** 3137 * Computes the cross product of two 2D vectors. 3138 * 3139 * @param ax The x coordinate of the first vector 3140 * @param ay The y coordinate of the first vector 3141 * @param bx The x coordinate of the second vector 3142 * @param by The y coordinate of the second vector 3143 * @return The result of a x b 3144 */ 3145 private static float cross(float ax, float ay, float bx, float by) { 3146 return ax * by - ay * bx; 3147 } 3148 3149 /** 3150 * Decides whether a 2D triangle, identified by the 6 coordinates of its 3151 * 3 vertices, is contained within another 2D triangle, also identified 3152 * by the 6 coordinates of its 3 vertices. 3153 * 3154 * In the illustration below, we want to test whether the RGB triangle 3155 * is contained within the triangle XYZ formed by the 3 vertices at 3156 * the "+" locations. 3157 * 3158 * Y . 3159 * . + . 3160 * . .. 3161 * . . 3162 * . . 3163 * . G 3164 * * 3165 * * * 3166 * ** * 3167 * * ** 3168 * * * 3169 * ** * 3170 * * * 3171 * * * 3172 * ** * 3173 * * * 3174 * * ** 3175 * ** * R ... 3176 * * * ..... 3177 * * ***** .. 3178 * ** ************ . + 3179 * B * ************ . X 3180 * ......***** . 3181 * ...... . . 3182 * .. 3183 * + . 3184 * Z . 3185 * 3186 * RGB is contained within XYZ if all the following conditions are true 3187 * (with "x" the cross product operator): 3188 * 3189 * --> --> 3190 * GR x RX >= 0 3191 * --> --> 3192 * RX x BR >= 0 3193 * --> --> 3194 * RG x GY >= 0 3195 * --> --> 3196 * GY x RG >= 0 3197 * --> --> 3198 * RB x BZ >= 0 3199 * --> --> 3200 * BZ x GB >= 0 3201 * 3202 * @param p1 The enclosing triangle 3203 * @param p2 The enclosed triangle 3204 * @return True if the triangle p1 contains the triangle p2 3205 * 3206 * @see #isWideGamut(float[], float, float) 3207 */ 3208 @SuppressWarnings("RedundantIfStatement") 3209 private static boolean contains(@NonNull @Size(6) float[] p1, @NonNull @Size(6) float[] p2) { 3210 // Translate the vertices p1 in the coordinates system 3211 // with the vertices p2 as the origin 3212 float[] p0 = new float[] { 3213 p1[0] - p2[0], p1[1] - p2[1], 3214 p1[2] - p2[2], p1[3] - p2[3], 3215 p1[4] - p2[4], p1[5] - p2[5], 3216 }; 3217 // Check the first vertex of p1 3218 if (cross(p0[0], p0[1], p2[0] - p2[4], p2[1] - p2[5]) < 0 || 3219 cross(p2[0] - p2[2], p2[1] - p2[3], p0[0], p0[1]) < 0) { 3220 return false; 3221 } 3222 // Check the second vertex of p1 3223 if (cross(p0[2], p0[3], p2[2] - p2[0], p2[3] - p2[1]) < 0 || 3224 cross(p2[2] - p2[4], p2[3] - p2[5], p0[2], p0[3]) < 0) { 3225 return false; 3226 } 3227 // Check the third vertex of p1 3228 if (cross(p0[4], p0[5], p2[4] - p2[2], p2[5] - p2[3]) < 0 || 3229 cross(p2[4] - p2[0], p2[5] - p2[1], p0[4], p0[5]) < 0) { 3230 return false; 3231 } 3232 return true; 3233 } 3234 3235 /** 3236 * Computes the primaries of a color space identified only by 3237 * its RGB->XYZ transform matrix. This method assumes that the 3238 * range of the color space is [0..1]. 3239 * 3240 * @param toXYZ The color space's 3x3 transform matrix to XYZ 3241 * @return A new array of 6 floats containing the color space's 3242 * primaries in CIE xyY 3243 */ 3244 @NonNull 3245 @Size(6) 3246 private static float[] computePrimaries(@NonNull @Size(9) float[] toXYZ) { 3247 float[] r = mul3x3Float3(toXYZ, new float[] { 1.0f, 0.0f, 0.0f }); 3248 float[] g = mul3x3Float3(toXYZ, new float[] { 0.0f, 1.0f, 0.0f }); 3249 float[] b = mul3x3Float3(toXYZ, new float[] { 0.0f, 0.0f, 1.0f }); 3250 3251 float rSum = r[0] + r[1] + r[2]; 3252 float gSum = g[0] + g[1] + g[2]; 3253 float bSum = b[0] + b[1] + b[2]; 3254 3255 return new float[] { 3256 r[0] / rSum, r[1] / rSum, 3257 g[0] / gSum, g[1] / gSum, 3258 b[0] / bSum, b[1] / bSum, 3259 }; 3260 } 3261 3262 /** 3263 * Computes the white point of a color space identified only by 3264 * its RGB->XYZ transform matrix. This method assumes that the 3265 * range of the color space is [0..1]. 3266 * 3267 * @param toXYZ The color space's 3x3 transform matrix to XYZ 3268 * @return A new array of 2 floats containing the color space's 3269 * white point in CIE xyY 3270 */ 3271 @NonNull 3272 @Size(2) 3273 private static float[] computeWhitePoint(@NonNull @Size(9) float[] toXYZ) { 3274 float[] w = mul3x3Float3(toXYZ, new float[] { 1.0f, 1.0f, 1.0f }); 3275 float sum = w[0] + w[1] + w[2]; 3276 return new float[] { w[0] / sum, w[1] / sum }; 3277 } 3278 3279 /** 3280 * Converts the specified RGB primaries point to xyY if needed. The primaries 3281 * can be specified as an array of 6 floats (in CIE xyY) or 9 floats 3282 * (in CIE XYZ). If no conversion is needed, the input array is copied. 3283 * 3284 * @param primaries The primaries in xyY or XYZ 3285 * @return A new array of 6 floats containing the primaries in xyY 3286 */ 3287 @NonNull 3288 @Size(6) 3289 private static float[] xyPrimaries(@NonNull @Size(min = 6, max = 9) float[] primaries) { 3290 float[] xyPrimaries = new float[6]; 3291 3292 // XYZ to xyY 3293 if (primaries.length == 9) { 3294 float sum; 3295 3296 sum = primaries[0] + primaries[1] + primaries[2]; 3297 xyPrimaries[0] = primaries[0] / sum; 3298 xyPrimaries[1] = primaries[1] / sum; 3299 3300 sum = primaries[3] + primaries[4] + primaries[5]; 3301 xyPrimaries[2] = primaries[3] / sum; 3302 xyPrimaries[3] = primaries[4] / sum; 3303 3304 sum = primaries[6] + primaries[7] + primaries[8]; 3305 xyPrimaries[4] = primaries[6] / sum; 3306 xyPrimaries[5] = primaries[7] / sum; 3307 } else { 3308 System.arraycopy(primaries, 0, xyPrimaries, 0, 6); 3309 } 3310 3311 return xyPrimaries; 3312 } 3313 3314 /** 3315 * Converts the specified white point to xyY if needed. The white point 3316 * can be specified as an array of 2 floats (in CIE xyY) or 3 floats 3317 * (in CIE XYZ). If no conversion is needed, the input array is copied. 3318 * 3319 * @param whitePoint The white point in xyY or XYZ 3320 * @return A new array of 2 floats containing the white point in xyY 3321 */ 3322 @NonNull 3323 @Size(2) 3324 private static float[] xyWhitePoint(@Size(min = 2, max = 3) float[] whitePoint) { 3325 float[] xyWhitePoint = new float[2]; 3326 3327 // XYZ to xyY 3328 if (whitePoint.length == 3) { 3329 float sum = whitePoint[0] + whitePoint[1] + whitePoint[2]; 3330 xyWhitePoint[0] = whitePoint[0] / sum; 3331 xyWhitePoint[1] = whitePoint[1] / sum; 3332 } else { 3333 System.arraycopy(whitePoint, 0, xyWhitePoint, 0, 2); 3334 } 3335 3336 return xyWhitePoint; 3337 } 3338 3339 /** 3340 * Computes the matrix that converts from RGB to XYZ based on RGB 3341 * primaries and a white point, both specified in the CIE xyY space. 3342 * The Y component of the primaries and white point is implied to be 1. 3343 * 3344 * @param primaries The RGB primaries in xyY, as an array of 6 floats 3345 * @param whitePoint The white point in xyY, as an array of 2 floats 3346 * @return A 3x3 matrix as a new array of 9 floats 3347 */ 3348 @NonNull 3349 @Size(9) 3350 private static float[] computeXYZMatrix( 3351 @NonNull @Size(6) float[] primaries, 3352 @NonNull @Size(2) float[] whitePoint) { 3353 float Rx = primaries[0]; 3354 float Ry = primaries[1]; 3355 float Gx = primaries[2]; 3356 float Gy = primaries[3]; 3357 float Bx = primaries[4]; 3358 float By = primaries[5]; 3359 float Wx = whitePoint[0]; 3360 float Wy = whitePoint[1]; 3361 3362 float oneRxRy = (1 - Rx) / Ry; 3363 float oneGxGy = (1 - Gx) / Gy; 3364 float oneBxBy = (1 - Bx) / By; 3365 float oneWxWy = (1 - Wx) / Wy; 3366 3367 float RxRy = Rx / Ry; 3368 float GxGy = Gx / Gy; 3369 float BxBy = Bx / By; 3370 float WxWy = Wx / Wy; 3371 3372 float BY = 3373 ((oneWxWy - oneRxRy) * (GxGy - RxRy) - (WxWy - RxRy) * (oneGxGy - oneRxRy)) / 3374 ((oneBxBy - oneRxRy) * (GxGy - RxRy) - (BxBy - RxRy) * (oneGxGy - oneRxRy)); 3375 float GY = (WxWy - RxRy - BY * (BxBy - RxRy)) / (GxGy - RxRy); 3376 float RY = 1 - GY - BY; 3377 3378 float RYRy = RY / Ry; 3379 float GYGy = GY / Gy; 3380 float BYBy = BY / By; 3381 3382 return new float[] { 3383 RYRy * Rx, RY, RYRy * (1 - Rx - Ry), 3384 GYGy * Gx, GY, GYGy * (1 - Gx - Gy), 3385 BYBy * Bx, BY, BYBy * (1 - Bx - By) 3386 }; 3387 } 3388 } 3389 3390 /** 3391 * {@usesMathJax} 3392 * 3393 * <p>A connector transforms colors from a source color space to a destination 3394 * color space.</p> 3395 * 3396 * <p>A source color space is connected to a destination color space using the 3397 * color transform \(C\) computed from their respective transforms noted 3398 * \(T_{src}\) and \(T_{dst}\) in the following equation:</p> 3399 * 3400 * $$C = T^{-1}_{dst} . T_{src}$$ 3401 * 3402 * <p>The transform \(C\) shown above is only valid when the source and 3403 * destination color spaces have the same profile connection space (PCS). 3404 * We know that instances of {@link ColorSpace} always use CIE XYZ as their 3405 * PCS but their white points might differ. When they do, we must perform 3406 * a chromatic adaptation of the color spaces' transforms. To do so, we 3407 * use the von Kries method described in the documentation of {@link Adaptation}, 3408 * using the CIE standard illuminant {@link ColorSpace#ILLUMINANT_D50 D50} 3409 * as the target white point.</p> 3410 * 3411 * <p>Example of conversion from {@link Named#SRGB sRGB} to 3412 * {@link Named#DCI_P3 DCI-P3}:</p> 3413 * 3414 * <pre class="prettyprint"> 3415 * ColorSpace.Connector connector = ColorSpace.connect( 3416 * ColorSpace.get(ColorSpace.Named.SRGB), 3417 * ColorSpace.get(ColorSpace.Named.DCI_P3)); 3418 * float[] p3 = connector.transform(1.0f, 0.0f, 0.0f); 3419 * // p3 contains { 0.9473, 0.2740, 0.2076 } 3420 * </pre> 3421 * 3422 * @see Adaptation 3423 * @see ColorSpace#adapt(ColorSpace, float[], Adaptation) 3424 * @see ColorSpace#adapt(ColorSpace, float[]) 3425 * @see ColorSpace#connect(ColorSpace, ColorSpace, RenderIntent) 3426 * @see ColorSpace#connect(ColorSpace, ColorSpace) 3427 * @see ColorSpace#connect(ColorSpace, RenderIntent) 3428 * @see ColorSpace#connect(ColorSpace) 3429 */ 3430 @AnyThread 3431 public static class Connector { 3432 @NonNull private final ColorSpace mSource; 3433 @NonNull private final ColorSpace mDestination; 3434 @NonNull private final ColorSpace mTransformSource; 3435 @NonNull private final ColorSpace mTransformDestination; 3436 @NonNull private final RenderIntent mIntent; 3437 @NonNull @Size(3) private final float[] mTransform; 3438 3439 /** 3440 * Creates a new connector between a source and a destination color space. 3441 * 3442 * @param source The source color space, cannot be null 3443 * @param destination The destination color space, cannot be null 3444 * @param intent The render intent to use when compressing gamuts 3445 */ 3446 Connector(@NonNull ColorSpace source, @NonNull ColorSpace destination, 3447 @NonNull RenderIntent intent) { 3448 this(source, destination, 3449 source.getModel() == Model.RGB ? adapt(source, ILLUMINANT_D50_XYZ) : source, 3450 destination.getModel() == Model.RGB ? 3451 adapt(destination, ILLUMINANT_D50_XYZ) : destination, 3452 intent, computeTransform(source, destination, intent)); 3453 } 3454 3455 /** 3456 * To connect between color spaces, we might need to use adapted transforms. 3457 * This should be transparent to the user so this constructor takes the 3458 * original source and destinations (returned by the getters), as well as 3459 * possibly adapted color spaces used by transform(). 3460 */ 3461 private Connector( 3462 @NonNull ColorSpace source, @NonNull ColorSpace destination, 3463 @NonNull ColorSpace transformSource, @NonNull ColorSpace transformDestination, 3464 @NonNull RenderIntent intent, @Nullable @Size(3) float[] transform) { 3465 mSource = source; 3466 mDestination = destination; 3467 mTransformSource = transformSource; 3468 mTransformDestination = transformDestination; 3469 mIntent = intent; 3470 mTransform = transform; 3471 } 3472 3473 /** 3474 * Computes an extra transform to apply in XYZ space depending on the 3475 * selected rendering intent. 3476 */ 3477 @Nullable 3478 private static float[] computeTransform(@NonNull ColorSpace source, 3479 @NonNull ColorSpace destination, @NonNull RenderIntent intent) { 3480 if (intent != RenderIntent.ABSOLUTE) return null; 3481 3482 boolean srcRGB = source.getModel() == Model.RGB; 3483 boolean dstRGB = destination.getModel() == Model.RGB; 3484 3485 if (srcRGB && dstRGB) return null; 3486 3487 if (srcRGB || dstRGB) { 3488 ColorSpace.Rgb rgb = (ColorSpace.Rgb) (srcRGB ? source : destination); 3489 float[] srcXYZ = srcRGB ? xyYToXyz(rgb.mWhitePoint) : ILLUMINANT_D50_XYZ; 3490 float[] dstXYZ = dstRGB ? xyYToXyz(rgb.mWhitePoint) : ILLUMINANT_D50_XYZ; 3491 return new float[] { 3492 srcXYZ[0] / dstXYZ[0], 3493 srcXYZ[1] / dstXYZ[1], 3494 srcXYZ[2] / dstXYZ[2], 3495 }; 3496 } 3497 3498 return null; 3499 } 3500 3501 /** 3502 * Returns the source color space this connector will convert from. 3503 * 3504 * @return A non-null instance of {@link ColorSpace} 3505 * 3506 * @see #getDestination() 3507 */ 3508 @NonNull 3509 public ColorSpace getSource() { 3510 return mSource; 3511 } 3512 3513 /** 3514 * Returns the destination color space this connector will convert to. 3515 * 3516 * @return A non-null instance of {@link ColorSpace} 3517 * 3518 * @see #getSource() 3519 */ 3520 @NonNull 3521 public ColorSpace getDestination() { 3522 return mDestination; 3523 } 3524 3525 /** 3526 * Returns the render intent this connector will use when mapping the 3527 * source color space to the destination color space. 3528 * 3529 * @return A non-null {@link RenderIntent} 3530 * 3531 * @see RenderIntent 3532 */ 3533 public RenderIntent getIntent() { 3534 return mIntent; 3535 } 3536 3537 /** 3538 * <p>Transforms the specified color from the source color space 3539 * to a color in the destination color space. This convenience 3540 * method assumes a source color model with 3 components 3541 * (typically RGB). To transform from color models with more than 3542 * 3 components, such as {@link Model#CMYK CMYK}, use 3543 * {@link #transform(float[])} instead.</p> 3544 * 3545 * @param r The red component of the color to transform 3546 * @param g The green component of the color to transform 3547 * @param b The blue component of the color to transform 3548 * @return A new array of 3 floats containing the specified color 3549 * transformed from the source space to the destination space 3550 * 3551 * @see #transform(float[]) 3552 */ 3553 @NonNull 3554 @Size(3) 3555 public float[] transform(float r, float g, float b) { 3556 return transform(new float[] { r, g, b }); 3557 } 3558 3559 /** 3560 * <p>Transforms the specified color from the source color space 3561 * to a color in the destination color space.</p> 3562 * 3563 * @param v A non-null array of 3 floats containing the value to transform 3564 * and that will hold the result of the transform 3565 * @return The v array passed as a parameter, containing the specified color 3566 * transformed from the source space to the destination space 3567 * 3568 * @see #transform(float, float, float) 3569 */ 3570 @NonNull 3571 @Size(min = 3) 3572 public float[] transform(@NonNull @Size(min = 3) float[] v) { 3573 float[] xyz = mTransformSource.toXyz(v); 3574 if (mTransform != null) { 3575 xyz[0] *= mTransform[0]; 3576 xyz[1] *= mTransform[1]; 3577 xyz[2] *= mTransform[2]; 3578 } 3579 return mTransformDestination.fromXyz(xyz); 3580 } 3581 3582 /** 3583 * Optimized connector for RGB->RGB conversions. 3584 */ 3585 private static class Rgb extends Connector { 3586 @NonNull private final ColorSpace.Rgb mSource; 3587 @NonNull private final ColorSpace.Rgb mDestination; 3588 @NonNull private final float[] mTransform; 3589 3590 Rgb(@NonNull ColorSpace.Rgb source, @NonNull ColorSpace.Rgb destination, 3591 @NonNull RenderIntent intent) { 3592 super(source, destination, source, destination, intent, null); 3593 mSource = source; 3594 mDestination = destination; 3595 mTransform = computeTransform(source, destination, intent); 3596 } 3597 3598 @Override 3599 public float[] transform(@NonNull @Size(min = 3) float[] rgb) { 3600 rgb[0] = (float) mSource.mClampedEotf.applyAsDouble(rgb[0]); 3601 rgb[1] = (float) mSource.mClampedEotf.applyAsDouble(rgb[1]); 3602 rgb[2] = (float) mSource.mClampedEotf.applyAsDouble(rgb[2]); 3603 mul3x3Float3(mTransform, rgb); 3604 rgb[0] = (float) mDestination.mClampedOetf.applyAsDouble(rgb[0]); 3605 rgb[1] = (float) mDestination.mClampedOetf.applyAsDouble(rgb[1]); 3606 rgb[2] = (float) mDestination.mClampedOetf.applyAsDouble(rgb[2]); 3607 return rgb; 3608 } 3609 3610 /** 3611 * <p>Computes the color transform that connects two RGB color spaces.</p> 3612 * 3613 * <p>We can only connect color spaces if they use the same profile 3614 * connection space. We assume the connection space is always 3615 * CIE XYZ but we maye need to perform a chromatic adaptation to 3616 * match the white points. If an adaptation is needed, we use the 3617 * CIE standard illuminant D50. The unmatched color space is adapted 3618 * using the von Kries transform and the {@link Adaptation#BRADFORD} 3619 * matrix.</p> 3620 * 3621 * @param source The source color space, cannot be null 3622 * @param destination The destination color space, cannot be null 3623 * @param intent The render intent to use when compressing gamuts 3624 * @return An array of 9 floats containing the 3x3 matrix transform 3625 */ 3626 @NonNull 3627 @Size(9) 3628 private static float[] computeTransform( 3629 @NonNull ColorSpace.Rgb source, 3630 @NonNull ColorSpace.Rgb destination, 3631 @NonNull RenderIntent intent) { 3632 if (compare(source.mWhitePoint, destination.mWhitePoint)) { 3633 // RGB->RGB using the PCS of both color spaces since they have the same 3634 return mul3x3(destination.mInverseTransform, source.mTransform); 3635 } else { 3636 // RGB->RGB using CIE XYZ D50 as the PCS 3637 float[] transform = source.mTransform; 3638 float[] inverseTransform = destination.mInverseTransform; 3639 3640 float[] srcXYZ = xyYToXyz(source.mWhitePoint); 3641 float[] dstXYZ = xyYToXyz(destination.mWhitePoint); 3642 3643 if (!compare(source.mWhitePoint, ILLUMINANT_D50)) { 3644 float[] srcAdaptation = chromaticAdaptation( 3645 Adaptation.BRADFORD.mTransform, srcXYZ, 3646 Arrays.copyOf(ILLUMINANT_D50_XYZ, 3)); 3647 transform = mul3x3(srcAdaptation, source.mTransform); 3648 } 3649 3650 if (!compare(destination.mWhitePoint, ILLUMINANT_D50)) { 3651 float[] dstAdaptation = chromaticAdaptation( 3652 Adaptation.BRADFORD.mTransform, dstXYZ, 3653 Arrays.copyOf(ILLUMINANT_D50_XYZ, 3)); 3654 inverseTransform = inverse3x3(mul3x3(dstAdaptation, destination.mTransform)); 3655 } 3656 3657 if (intent == RenderIntent.ABSOLUTE) { 3658 transform = mul3x3Diag( 3659 new float[] { 3660 srcXYZ[0] / dstXYZ[0], 3661 srcXYZ[1] / dstXYZ[1], 3662 srcXYZ[2] / dstXYZ[2], 3663 }, transform); 3664 } 3665 3666 return mul3x3(inverseTransform, transform); 3667 } 3668 } 3669 } 3670 3671 /** 3672 * Returns the identity connector for a given color space. 3673 * 3674 * @param source The source and destination color space 3675 * @return A non-null connector that does not perform any transform 3676 * 3677 * @see ColorSpace#connect(ColorSpace, ColorSpace) 3678 */ 3679 static Connector identity(ColorSpace source) { 3680 return new Connector(source, source, RenderIntent.RELATIVE) { 3681 @Override 3682 public float[] transform(@NonNull @Size(min = 3) float[] v) { 3683 return v; 3684 } 3685 }; 3686 } 3687 } 3688 3689 /** 3690 * <p>A color space renderer can be used to visualize and compare the gamut and 3691 * white point of one or more color spaces. The output is an sRGB {@link Bitmap} 3692 * showing a CIE 1931 xyY or a CIE 1976 UCS chromaticity diagram.</p> 3693 * 3694 * <p>The following code snippet shows how to compare the {@link Named#SRGB} 3695 * and {@link Named#DCI_P3} color spaces in a CIE 1931 diagram:</p> 3696 * 3697 * <pre class="prettyprint"> 3698 * Bitmap bitmap = ColorSpace.createRenderer() 3699 * .size(768) 3700 * .clip(true) 3701 * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff) 3702 * .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845) 3703 * .render(); 3704 * </pre> 3705 * <p> 3706 * <img src="{@docRoot}reference/android/images/graphics/colorspace_clipped.png" /> 3707 * <figcaption style="text-align: center;">sRGB vs DCI-P3</figcaption> 3708 * </p> 3709 * 3710 * <p>A renderer can also be used to show the location of specific colors, 3711 * associated with a color space, in the CIE 1931 xyY chromaticity diagram. 3712 * See {@link #add(ColorSpace, float, float, float, int)} for more information.</p> 3713 * 3714 * @see ColorSpace#createRenderer() 3715 */ 3716 public static class Renderer { 3717 private static final int NATIVE_SIZE = 1440; 3718 private static final float UCS_SCALE = 9.0f / 6.0f; 3719 3720 // Number of subdivision of the inside of the spectral locus 3721 private static final int CHROMATICITY_RESOLUTION = 32; 3722 private static final double ONE_THIRD = 1.0 / 3.0; 3723 3724 @IntRange(from = 128, to = Integer.MAX_VALUE) 3725 private int mSize = 1024; 3726 3727 private boolean mShowWhitePoint = true; 3728 private boolean mClip = false; 3729 private boolean mUcs = false; 3730 3731 private final List<Pair<ColorSpace, Integer>> mColorSpaces = new ArrayList<>(2); 3732 private final List<Point> mPoints = new ArrayList<>(0); 3733 3734 private Renderer() { 3735 } 3736 3737 /** 3738 * <p>Defines whether the chromaticity diagram should be clipped by the first 3739 * registered color space. The default value is false.</p> 3740 * 3741 * <p>The following code snippet and image show the default behavior:</p> 3742 * <pre class="prettyprint"> 3743 * Bitmap bitmap = ColorSpace.createRenderer() 3744 * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff) 3745 * .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845) 3746 * .render(); 3747 * </pre> 3748 * <p> 3749 * <img src="{@docRoot}reference/android/images/graphics/colorspace_comparison.png" /> 3750 * <figcaption style="text-align: center;">Clipping disabled</figcaption> 3751 * </p> 3752 * 3753 * <p>Here is the same example with clipping enabled:</p> 3754 * <pre class="prettyprint"> 3755 * Bitmap bitmap = ColorSpace.createRenderer() 3756 * .clip(true) 3757 * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff) 3758 * .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845) 3759 * .render(); 3760 * </pre> 3761 * <p> 3762 * <img src="{@docRoot}reference/android/images/graphics/colorspace_clipped.png" /> 3763 * <figcaption style="text-align: center;">Clipping enabled</figcaption> 3764 * </p> 3765 * 3766 * @param clip True to clip the chromaticity diagram to the first registered color space, 3767 * false otherwise 3768 * @return This instance of {@link Renderer} 3769 */ 3770 @NonNull 3771 public Renderer clip(boolean clip) { 3772 mClip = clip; 3773 return this; 3774 } 3775 3776 /** 3777 * <p>Defines whether the chromaticity diagram should use the uniform 3778 * chromaticity scale (CIE 1976 UCS). When the uniform chromaticity scale 3779 * is used, the distance between two points on the diagram is approximately 3780 * proportional to the perceived color difference.</p> 3781 * 3782 * <p>The following code snippet shows how to enable the uniform chromaticity 3783 * scale. The image below shows the result:</p> 3784 * <pre class="prettyprint"> 3785 * Bitmap bitmap = ColorSpace.createRenderer() 3786 * .uniformChromaticityScale(true) 3787 * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff) 3788 * .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845) 3789 * .render(); 3790 * </pre> 3791 * <p> 3792 * <img src="{@docRoot}reference/android/images/graphics/colorspace_ucs.png" /> 3793 * <figcaption style="text-align: center;">CIE 1976 UCS diagram</figcaption> 3794 * </p> 3795 * 3796 * @param ucs True to render the chromaticity diagram as the CIE 1976 UCS diagram 3797 * @return This instance of {@link Renderer} 3798 */ 3799 @NonNull 3800 public Renderer uniformChromaticityScale(boolean ucs) { 3801 mUcs = ucs; 3802 return this; 3803 } 3804 3805 /** 3806 * Sets the dimensions (width and height) in pixels of the output bitmap. 3807 * The size must be at least 128px and defaults to 1024px. 3808 * 3809 * @param size The size in pixels of the output bitmap 3810 * @return This instance of {@link Renderer} 3811 */ 3812 @NonNull 3813 public Renderer size(@IntRange(from = 128, to = Integer.MAX_VALUE) int size) { 3814 mSize = Math.max(128, size); 3815 return this; 3816 } 3817 3818 /** 3819 * Shows or hides the white point of each color space in the output bitmap. 3820 * The default is true. 3821 * 3822 * @param show True to show the white point of each color space, false 3823 * otherwise 3824 * @return This instance of {@link Renderer} 3825 */ 3826 @NonNull 3827 public Renderer showWhitePoint(boolean show) { 3828 mShowWhitePoint = show; 3829 return this; 3830 } 3831 3832 /** 3833 * <p>Adds a color space to represent on the output CIE 1931 chromaticity 3834 * diagram. The color space is represented as a triangle showing the 3835 * footprint of its color gamut and, optionally, the location of its 3836 * white point.</p> 3837 * 3838 * <p class="note">Color spaces with a color model that is not RGB are 3839 * accepted but ignored.</p> 3840 * 3841 * <p>The following code snippet and image show an example of calling this 3842 * method to compare {@link Named#SRGB sRGB} and {@link Named#DCI_P3 DCI-P3}:</p> 3843 * <pre class="prettyprint"> 3844 * Bitmap bitmap = ColorSpace.createRenderer() 3845 * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff) 3846 * .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845) 3847 * .render(); 3848 * </pre> 3849 * <p> 3850 * <img src="{@docRoot}reference/android/images/graphics/colorspace_comparison.png" /> 3851 * <figcaption style="text-align: center;">sRGB vs DCI-P3</figcaption> 3852 * </p> 3853 * 3854 * <p>Adding a color space extending beyond the boundaries of the 3855 * spectral locus will alter the size of the diagram within the output 3856 * bitmap as shown in this example:</p> 3857 * <pre class="prettyprint"> 3858 * Bitmap bitmap = ColorSpace.createRenderer() 3859 * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff) 3860 * .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845) 3861 * .add(ColorSpace.get(ColorSpace.Named.ACES), 0xff097ae9) 3862 * .add(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB), 0xff000000) 3863 * .render(); 3864 * </pre> 3865 * <p> 3866 * <img src="{@docRoot}reference/android/images/graphics/colorspace_comparison2.png" /> 3867 * <figcaption style="text-align: center;">sRGB, DCI-P3, ACES and scRGB</figcaption> 3868 * </p> 3869 * 3870 * @param colorSpace The color space whose gamut to render on the diagram 3871 * @param color The sRGB color to use to render the color space's gamut and white point 3872 * @return This instance of {@link Renderer} 3873 * 3874 * @see #clip(boolean) 3875 * @see #showWhitePoint(boolean) 3876 */ 3877 @NonNull 3878 public Renderer add(@NonNull ColorSpace colorSpace, @ColorInt int color) { 3879 mColorSpaces.add(new Pair<>(colorSpace, color)); 3880 return this; 3881 } 3882 3883 /** 3884 * <p>Adds a color to represent as a point on the chromaticity diagram. 3885 * The color is associated with a color space which will be used to 3886 * perform the conversion to CIE XYZ and compute the location of the point 3887 * on the diagram. The point is rendered as a colored circle.</p> 3888 * 3889 * <p>The following code snippet and image show an example of calling this 3890 * method to render the location of several sRGB colors as white circles:</p> 3891 * <pre class="prettyprint"> 3892 * Bitmap bitmap = ColorSpace.createRenderer() 3893 * .clip(true) 3894 * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff) 3895 * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.0f, 0.1f, 0xffffffff) 3896 * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.1f, 0.1f, 0xffffffff) 3897 * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.2f, 0.1f, 0xffffffff) 3898 * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.3f, 0.1f, 0xffffffff) 3899 * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.4f, 0.1f, 0xffffffff) 3900 * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.5f, 0.1f, 0xffffffff) 3901 * .render(); 3902 * </pre> 3903 * <p> 3904 * <img src="{@docRoot}reference/android/images/graphics/colorspace_points.png" /> 3905 * <figcaption style="text-align: center;"> 3906 * Locating colors on the chromaticity diagram 3907 * </figcaption> 3908 * </p> 3909 * 3910 * @param colorSpace The color space of the color to locate on the diagram 3911 * @param r The first component of the color to locate on the diagram 3912 * @param g The second component of the color to locate on the diagram 3913 * @param b The third component of the color to locate on the diagram 3914 * @param pointColor The sRGB color to use to render the point on the diagram 3915 * @return This instance of {@link Renderer} 3916 */ 3917 @NonNull 3918 public Renderer add(@NonNull ColorSpace colorSpace, float r, float g, float b, 3919 @ColorInt int pointColor) { 3920 mPoints.add(new Point(colorSpace, new float[] { r, g, b }, pointColor)); 3921 return this; 3922 } 3923 3924 /** 3925 * <p>Renders the {@link #add(ColorSpace, int) color spaces} and 3926 * {@link #add(ColorSpace, float, float, float, int) points} registered 3927 * with this renderer. The output bitmap is an sRGB image with the 3928 * dimensions specified by calling {@link #size(int)} (1204x1024px by 3929 * default).</p> 3930 * 3931 * @return A new non-null {@link Bitmap} with the dimensions specified 3932 * by {@link #size(int)} (1024x1024 by default) 3933 */ 3934 @NonNull 3935 public Bitmap render() { 3936 Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); 3937 Bitmap bitmap = Bitmap.createBitmap(mSize, mSize, Bitmap.Config.ARGB_8888); 3938 Canvas canvas = new Canvas(bitmap); 3939 3940 float[] primaries = new float[6]; 3941 float[] whitePoint = new float[2]; 3942 3943 int width = NATIVE_SIZE; 3944 int height = NATIVE_SIZE; 3945 3946 Path path = new Path(); 3947 3948 setTransform(canvas, width, height, primaries); 3949 drawBox(canvas, width, height, paint, path); 3950 setUcsTransform(canvas, height); 3951 drawLocus(canvas, width, height, paint, path, primaries); 3952 drawGamuts(canvas, width, height, paint, path, primaries, whitePoint); 3953 drawPoints(canvas, width, height, paint); 3954 3955 return bitmap; 3956 } 3957 3958 /** 3959 * Draws registered points at their correct position in the xyY coordinates. 3960 * Each point is positioned according to its associated color space. 3961 * 3962 * @param canvas The canvas to transform 3963 * @param width Width in pixel of the final image 3964 * @param height Height in pixel of the final image 3965 * @param paint A pre-allocated paint used to avoid temporary allocations 3966 */ 3967 private void drawPoints(@NonNull Canvas canvas, int width, int height, 3968 @NonNull Paint paint) { 3969 3970 paint.setStyle(Paint.Style.FILL); 3971 3972 float radius = 4.0f / (mUcs ? UCS_SCALE : 1.0f); 3973 3974 float[] v = new float[3]; 3975 float[] xy = new float[2]; 3976 3977 for (final Point point : mPoints) { 3978 v[0] = point.mRgb[0]; 3979 v[1] = point.mRgb[1]; 3980 v[2] = point.mRgb[2]; 3981 point.mColorSpace.toXyz(v); 3982 3983 paint.setColor(point.mColor); 3984 3985 // XYZ to xyY, assuming Y=1.0, then to L*u*v* if needed 3986 float sum = v[0] + v[1] + v[2]; 3987 xy[0] = v[0] / sum; 3988 xy[1] = v[1] / sum; 3989 if (mUcs) xyYToUv(xy); 3990 3991 canvas.drawCircle(width * xy[0], height - height * xy[1], radius, paint); 3992 } 3993 } 3994 3995 /** 3996 * Draws the color gamuts and white points of all the registered color 3997 * spaces. Only color spaces with an RGB color model are rendered, the 3998 * others are ignored. 3999 * 4000 * @param canvas The canvas to transform 4001 * @param width Width in pixel of the final image 4002 * @param height Height in pixel of the final image 4003 * @param paint A pre-allocated paint used to avoid temporary allocations 4004 * @param path A pre-allocated path used to avoid temporary allocations 4005 * @param primaries A pre-allocated array of 6 floats to avoid temporary allocations 4006 * @param whitePoint A pre-allocated array of 2 floats to avoid temporary allocations 4007 */ 4008 private void drawGamuts( 4009 @NonNull Canvas canvas, int width, int height, 4010 @NonNull Paint paint, @NonNull Path path, 4011 @NonNull @Size(6) float[] primaries, @NonNull @Size(2) float[] whitePoint) { 4012 4013 float radius = 4.0f / (mUcs ? UCS_SCALE : 1.0f); 4014 4015 for (final Pair<ColorSpace, Integer> item : mColorSpaces) { 4016 ColorSpace colorSpace = item.first; 4017 int color = item.second; 4018 4019 if (colorSpace.getModel() != Model.RGB) continue; 4020 4021 Rgb rgb = (Rgb) colorSpace; 4022 getPrimaries(rgb, primaries, mUcs); 4023 4024 path.rewind(); 4025 path.moveTo(width * primaries[0], height - height * primaries[1]); 4026 path.lineTo(width * primaries[2], height - height * primaries[3]); 4027 path.lineTo(width * primaries[4], height - height * primaries[5]); 4028 path.close(); 4029 4030 paint.setStyle(Paint.Style.STROKE); 4031 paint.setColor(color); 4032 canvas.drawPath(path, paint); 4033 4034 // Draw the white point 4035 if (mShowWhitePoint) { 4036 rgb.getWhitePoint(whitePoint); 4037 if (mUcs) xyYToUv(whitePoint); 4038 4039 paint.setStyle(Paint.Style.FILL); 4040 paint.setColor(color); 4041 canvas.drawCircle( 4042 width * whitePoint[0], height - height * whitePoint[1], radius, paint); 4043 } 4044 } 4045 } 4046 4047 /** 4048 * Returns the primaries of the specified RGB color space. This method handles 4049 * the special case of the {@link Named#EXTENDED_SRGB} family of color spaces. 4050 * 4051 * @param rgb The color space whose primaries to extract 4052 * @param primaries A pre-allocated array of 6 floats that will hold the result 4053 * @param asUcs True if the primaries should be returned in Luv, false for xyY 4054 */ 4055 @NonNull 4056 @Size(6) 4057 private static float[] getPrimaries(@NonNull Rgb rgb, 4058 @NonNull @Size(6) float[] primaries, boolean asUcs) { 4059 // TODO: We should find a better way to handle these cases 4060 if (rgb.equals(ColorSpace.get(Named.EXTENDED_SRGB)) || 4061 rgb.equals(ColorSpace.get(Named.LINEAR_EXTENDED_SRGB))) { 4062 primaries[0] = 1.41f; 4063 primaries[1] = 0.33f; 4064 primaries[2] = 0.27f; 4065 primaries[3] = 1.24f; 4066 primaries[4] = -0.23f; 4067 primaries[5] = -0.57f; 4068 } else { 4069 rgb.getPrimaries(primaries); 4070 } 4071 if (asUcs) xyYToUv(primaries); 4072 return primaries; 4073 } 4074 4075 /** 4076 * Draws the CIE 1931 chromaticity diagram: the spectral locus and its inside. 4077 * This method respect the clip parameter. 4078 * 4079 * @param canvas The canvas to transform 4080 * @param width Width in pixel of the final image 4081 * @param height Height in pixel of the final image 4082 * @param paint A pre-allocated paint used to avoid temporary allocations 4083 * @param path A pre-allocated path used to avoid temporary allocations 4084 * @param primaries A pre-allocated array of 6 floats to avoid temporary allocations 4085 */ 4086 private void drawLocus( 4087 @NonNull Canvas canvas, int width, int height, @NonNull Paint paint, 4088 @NonNull Path path, @NonNull @Size(6) float[] primaries) { 4089 4090 int vertexCount = SPECTRUM_LOCUS_X.length * CHROMATICITY_RESOLUTION * 6; 4091 float[] vertices = new float[vertexCount * 2]; 4092 int[] colors = new int[vertices.length]; 4093 computeChromaticityMesh(vertices, colors); 4094 4095 if (mUcs) xyYToUv(vertices); 4096 for (int i = 0; i < vertices.length; i += 2) { 4097 vertices[i] *= width; 4098 vertices[i + 1] = height - vertices[i + 1] * height; 4099 } 4100 4101 // Draw the spectral locus 4102 if (mClip && mColorSpaces.size() > 0) { 4103 for (final Pair<ColorSpace, Integer> item : mColorSpaces) { 4104 ColorSpace colorSpace = item.first; 4105 if (colorSpace.getModel() != Model.RGB) continue; 4106 4107 Rgb rgb = (Rgb) colorSpace; 4108 getPrimaries(rgb, primaries, mUcs); 4109 4110 break; 4111 } 4112 4113 path.rewind(); 4114 path.moveTo(width * primaries[0], height - height * primaries[1]); 4115 path.lineTo(width * primaries[2], height - height * primaries[3]); 4116 path.lineTo(width * primaries[4], height - height * primaries[5]); 4117 path.close(); 4118 4119 int[] solid = new int[colors.length]; 4120 Arrays.fill(solid, 0xff6c6c6c); 4121 canvas.drawVertices(Canvas.VertexMode.TRIANGLES, vertices.length, vertices, 0, 4122 null, 0, solid, 0, null, 0, 0, paint); 4123 4124 canvas.save(); 4125 canvas.clipPath(path); 4126 4127 canvas.drawVertices(Canvas.VertexMode.TRIANGLES, vertices.length, vertices, 0, 4128 null, 0, colors, 0, null, 0, 0, paint); 4129 4130 canvas.restore(); 4131 } else { 4132 canvas.drawVertices(Canvas.VertexMode.TRIANGLES, vertices.length, vertices, 0, 4133 null, 0, colors, 0, null, 0, 0, paint); 4134 } 4135 4136 // Draw the non-spectral locus 4137 int index = (CHROMATICITY_RESOLUTION - 1) * 12; 4138 path.reset(); 4139 path.moveTo(vertices[index], vertices[index + 1]); 4140 for (int x = 2; x < SPECTRUM_LOCUS_X.length; x++) { 4141 index += CHROMATICITY_RESOLUTION * 12; 4142 path.lineTo(vertices[index], vertices[index + 1]); 4143 } 4144 path.close(); 4145 4146 paint.setStrokeWidth(4.0f / (mUcs ? UCS_SCALE : 1.0f)); 4147 paint.setStyle(Paint.Style.STROKE); 4148 paint.setColor(0xff000000); 4149 canvas.drawPath(path, paint); 4150 } 4151 4152 /** 4153 * Draws the diagram box, including borders, tick marks, grid lines 4154 * and axis labels. 4155 * 4156 * @param canvas The canvas to transform 4157 * @param width Width in pixel of the final image 4158 * @param height Height in pixel of the final image 4159 * @param paint A pre-allocated paint used to avoid temporary allocations 4160 * @param path A pre-allocated path used to avoid temporary allocations 4161 */ 4162 private void drawBox(@NonNull Canvas canvas, int width, int height, @NonNull Paint paint, 4163 @NonNull Path path) { 4164 4165 int lineCount = 10; 4166 float scale = 1.0f; 4167 if (mUcs) { 4168 lineCount = 7; 4169 scale = UCS_SCALE; 4170 } 4171 4172 // Draw the unit grid 4173 paint.setStyle(Paint.Style.STROKE); 4174 paint.setStrokeWidth(2.0f); 4175 paint.setColor(0xffc0c0c0); 4176 4177 for (int i = 1; i < lineCount - 1; i++) { 4178 float v = i / 10.0f; 4179 float x = (width * v) * scale; 4180 float y = height - (height * v) * scale; 4181 4182 canvas.drawLine(0.0f, y, 0.9f * width, y, paint); 4183 canvas.drawLine(x, height, x, 0.1f * height, paint); 4184 } 4185 4186 // Draw tick marks 4187 paint.setStrokeWidth(4.0f); 4188 paint.setColor(0xff000000); 4189 for (int i = 1; i < lineCount - 1; i++) { 4190 float v = i / 10.0f; 4191 float x = (width * v) * scale; 4192 float y = height - (height * v) * scale; 4193 4194 canvas.drawLine(0.0f, y, width / 100.0f, y, paint); 4195 canvas.drawLine(x, height, x, height - (height / 100.0f), paint); 4196 } 4197 4198 // Draw the axis labels 4199 paint.setStyle(Paint.Style.FILL); 4200 paint.setTextSize(36.0f); 4201 paint.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL)); 4202 4203 Rect bounds = new Rect(); 4204 for (int i = 1; i < lineCount - 1; i++) { 4205 String text = "0." + i; 4206 paint.getTextBounds(text, 0, text.length(), bounds); 4207 4208 float v = i / 10.0f; 4209 float x = (width * v) * scale; 4210 float y = height - (height * v) * scale; 4211 4212 canvas.drawText(text, -0.05f * width + 10, y + bounds.height() / 2.0f, paint); 4213 canvas.drawText(text, x - bounds.width() / 2.0f, 4214 height + bounds.height() + 16, paint); 4215 } 4216 paint.setStyle(Paint.Style.STROKE); 4217 4218 // Draw the diagram box 4219 path.moveTo(0.0f, height); 4220 path.lineTo(0.9f * width, height); 4221 path.lineTo(0.9f * width, 0.1f * height); 4222 path.lineTo(0.0f, 0.1f * height); 4223 path.close(); 4224 canvas.drawPath(path, paint); 4225 } 4226 4227 /** 4228 * Computes and applies the Canvas transforms required to make the color 4229 * gamut of each color space visible in the final image. 4230 * 4231 * @param canvas The canvas to transform 4232 * @param width Width in pixel of the final image 4233 * @param height Height in pixel of the final image 4234 * @param primaries Array of 6 floats used to avoid temporary allocations 4235 */ 4236 private void setTransform(@NonNull Canvas canvas, int width, int height, 4237 @NonNull @Size(6) float[] primaries) { 4238 4239 RectF primariesBounds = new RectF(); 4240 for (final Pair<ColorSpace, Integer> item : mColorSpaces) { 4241 ColorSpace colorSpace = item.first; 4242 if (colorSpace.getModel() != Model.RGB) continue; 4243 4244 Rgb rgb = (Rgb) colorSpace; 4245 getPrimaries(rgb, primaries, mUcs); 4246 4247 primariesBounds.left = Math.min(primariesBounds.left, primaries[4]); 4248 primariesBounds.top = Math.min(primariesBounds.top, primaries[5]); 4249 primariesBounds.right = Math.max(primariesBounds.right, primaries[0]); 4250 primariesBounds.bottom = Math.max(primariesBounds.bottom, primaries[3]); 4251 } 4252 4253 float max = mUcs ? 0.6f : 0.9f; 4254 4255 primariesBounds.left = Math.min(0.0f, primariesBounds.left); 4256 primariesBounds.top = Math.min(0.0f, primariesBounds.top); 4257 primariesBounds.right = Math.max(max, primariesBounds.right); 4258 primariesBounds.bottom = Math.max(max, primariesBounds.bottom); 4259 4260 float scaleX = max / primariesBounds.width(); 4261 float scaleY = max / primariesBounds.height(); 4262 float scale = Math.min(scaleX, scaleY); 4263 4264 canvas.scale(mSize / (float) NATIVE_SIZE, mSize / (float) NATIVE_SIZE); 4265 canvas.scale(scale, scale); 4266 canvas.translate( 4267 (primariesBounds.width() - max) * width / 2.0f, 4268 (primariesBounds.height() - max) * height / 2.0f); 4269 4270 // The spectrum extends ~0.85 vertically and ~0.65 horizontally 4271 // We shift the canvas a little bit to get nicer margins 4272 canvas.translate(0.05f * width, -0.05f * height); 4273 } 4274 4275 /** 4276 * Computes and applies the Canvas transforms required to render the CIE 4277 * 197 UCS chromaticity diagram. 4278 * 4279 * @param canvas The canvas to transform 4280 * @param height Height in pixel of the final image 4281 */ 4282 private void setUcsTransform(@NonNull Canvas canvas, int height) { 4283 if (mUcs) { 4284 canvas.translate(0.0f, (height - height * UCS_SCALE)); 4285 canvas.scale(UCS_SCALE, UCS_SCALE); 4286 } 4287 } 4288 4289 // X coordinates of the spectral locus in CIE 1931 4290 private static final float[] SPECTRUM_LOCUS_X = { 4291 0.175596f, 0.172787f, 0.170806f, 0.170085f, 0.160343f, 4292 0.146958f, 0.139149f, 0.133536f, 0.126688f, 0.115830f, 4293 0.109616f, 0.099146f, 0.091310f, 0.078130f, 0.068717f, 4294 0.054675f, 0.040763f, 0.027497f, 0.016270f, 0.008169f, 4295 0.004876f, 0.003983f, 0.003859f, 0.004646f, 0.007988f, 4296 0.013870f, 0.022244f, 0.027273f, 0.032820f, 0.038851f, 4297 0.045327f, 0.052175f, 0.059323f, 0.066713f, 0.074299f, 4298 0.089937f, 0.114155f, 0.138695f, 0.154714f, 0.192865f, 4299 0.229607f, 0.265760f, 0.301588f, 0.337346f, 0.373083f, 4300 0.408717f, 0.444043f, 0.478755f, 0.512467f, 0.544767f, 4301 0.575132f, 0.602914f, 0.627018f, 0.648215f, 0.665746f, 4302 0.680061f, 0.691487f, 0.700589f, 0.707901f, 0.714015f, 4303 0.719017f, 0.723016f, 0.734674f, 0.717203f, 0.699732f, 4304 0.682260f, 0.664789f, 0.647318f, 0.629847f, 0.612376f, 4305 0.594905f, 0.577433f, 0.559962f, 0.542491f, 0.525020f, 4306 0.507549f, 0.490077f, 0.472606f, 0.455135f, 0.437664f, 4307 0.420193f, 0.402721f, 0.385250f, 0.367779f, 0.350308f, 4308 0.332837f, 0.315366f, 0.297894f, 0.280423f, 0.262952f, 4309 0.245481f, 0.228010f, 0.210538f, 0.193067f, 0.175596f 4310 }; 4311 // Y coordinates of the spectral locus in CIE 1931 4312 private static final float[] SPECTRUM_LOCUS_Y = { 4313 0.005295f, 0.004800f, 0.005472f, 0.005976f, 0.014496f, 4314 0.026643f, 0.035211f, 0.042704f, 0.053441f, 0.073601f, 4315 0.086866f, 0.112037f, 0.132737f, 0.170464f, 0.200773f, 4316 0.254155f, 0.317049f, 0.387997f, 0.463035f, 0.538504f, 4317 0.587196f, 0.610526f, 0.654897f, 0.675970f, 0.715407f, 4318 0.750246f, 0.779682f, 0.792153f, 0.802971f, 0.812059f, 4319 0.819430f, 0.825200f, 0.829460f, 0.832306f, 0.833833f, 4320 0.833316f, 0.826231f, 0.814796f, 0.805884f, 0.781648f, 4321 0.754347f, 0.724342f, 0.692326f, 0.658867f, 0.624470f, 4322 0.589626f, 0.554734f, 0.520222f, 0.486611f, 0.454454f, 4323 0.424252f, 0.396516f, 0.372510f, 0.351413f, 0.334028f, 4324 0.319765f, 0.308359f, 0.299317f, 0.292044f, 0.285945f, 4325 0.280951f, 0.276964f, 0.265326f, 0.257200f, 0.249074f, 4326 0.240948f, 0.232822f, 0.224696f, 0.216570f, 0.208444f, 4327 0.200318f, 0.192192f, 0.184066f, 0.175940f, 0.167814f, 4328 0.159688f, 0.151562f, 0.143436f, 0.135311f, 0.127185f, 4329 0.119059f, 0.110933f, 0.102807f, 0.094681f, 0.086555f, 4330 0.078429f, 0.070303f, 0.062177f, 0.054051f, 0.045925f, 4331 0.037799f, 0.029673f, 0.021547f, 0.013421f, 0.005295f 4332 }; 4333 4334 /** 4335 * Computes a 2D mesh representation of the CIE 1931 chromaticity 4336 * diagram. 4337 * 4338 * @param vertices Array of floats that will hold the mesh vertices 4339 * @param colors Array of floats that will hold the mesh colors 4340 */ 4341 private static void computeChromaticityMesh(@NonNull float[] vertices, 4342 @NonNull int[] colors) { 4343 4344 ColorSpace colorSpace = get(Named.SRGB); 4345 4346 float[] color = new float[3]; 4347 4348 int vertexIndex = 0; 4349 int colorIndex = 0; 4350 4351 for (int x = 0; x < SPECTRUM_LOCUS_X.length; x++) { 4352 int nextX = (x % (SPECTRUM_LOCUS_X.length - 1)) + 1; 4353 4354 float a1 = (float) Math.atan2( 4355 SPECTRUM_LOCUS_Y[x] - ONE_THIRD, 4356 SPECTRUM_LOCUS_X[x] - ONE_THIRD); 4357 float a2 = (float) Math.atan2( 4358 SPECTRUM_LOCUS_Y[nextX] - ONE_THIRD, 4359 SPECTRUM_LOCUS_X[nextX] - ONE_THIRD); 4360 4361 float radius1 = (float) Math.pow( 4362 sqr(SPECTRUM_LOCUS_X[x] - ONE_THIRD) + 4363 sqr(SPECTRUM_LOCUS_Y[x] - ONE_THIRD), 4364 0.5); 4365 float radius2 = (float) Math.pow( 4366 sqr(SPECTRUM_LOCUS_X[nextX] - ONE_THIRD) + 4367 sqr(SPECTRUM_LOCUS_Y[nextX] - ONE_THIRD), 4368 0.5); 4369 4370 // Compute patches; each patch is a quad with a different 4371 // color associated with each vertex 4372 for (int c = 1; c <= CHROMATICITY_RESOLUTION; c++) { 4373 float f1 = c / (float) CHROMATICITY_RESOLUTION; 4374 float f2 = (c - 1) / (float) CHROMATICITY_RESOLUTION; 4375 4376 double cr1 = radius1 * Math.cos(a1); 4377 double sr1 = radius1 * Math.sin(a1); 4378 double cr2 = radius2 * Math.cos(a2); 4379 double sr2 = radius2 * Math.sin(a2); 4380 4381 // Compute the XYZ coordinates of the 4 vertices of the patch 4382 float v1x = (float) (ONE_THIRD + cr1 * f1); 4383 float v1y = (float) (ONE_THIRD + sr1 * f1); 4384 float v1z = 1 - v1x - v1y; 4385 4386 float v2x = (float) (ONE_THIRD + cr1 * f2); 4387 float v2y = (float) (ONE_THIRD + sr1 * f2); 4388 float v2z = 1 - v2x - v2y; 4389 4390 float v3x = (float) (ONE_THIRD + cr2 * f2); 4391 float v3y = (float) (ONE_THIRD + sr2 * f2); 4392 float v3z = 1 - v3x - v3y; 4393 4394 float v4x = (float) (ONE_THIRD + cr2 * f1); 4395 float v4y = (float) (ONE_THIRD + sr2 * f1); 4396 float v4z = 1 - v4x - v4y; 4397 4398 // Compute the sRGB representation of each XYZ coordinate of the patch 4399 colors[colorIndex ] = computeColor(color, v1x, v1y, v1z, colorSpace); 4400 colors[colorIndex + 1] = computeColor(color, v2x, v2y, v2z, colorSpace); 4401 colors[colorIndex + 2] = computeColor(color, v3x, v3y, v3z, colorSpace); 4402 colors[colorIndex + 3] = colors[colorIndex]; 4403 colors[colorIndex + 4] = colors[colorIndex + 2]; 4404 colors[colorIndex + 5] = computeColor(color, v4x, v4y, v4z, colorSpace); 4405 colorIndex += 6; 4406 4407 // Flip the mesh upside down to match Canvas' coordinates system 4408 vertices[vertexIndex++] = v1x; 4409 vertices[vertexIndex++] = v1y; 4410 vertices[vertexIndex++] = v2x; 4411 vertices[vertexIndex++] = v2y; 4412 vertices[vertexIndex++] = v3x; 4413 vertices[vertexIndex++] = v3y; 4414 vertices[vertexIndex++] = v1x; 4415 vertices[vertexIndex++] = v1y; 4416 vertices[vertexIndex++] = v3x; 4417 vertices[vertexIndex++] = v3y; 4418 vertices[vertexIndex++] = v4x; 4419 vertices[vertexIndex++] = v4y; 4420 } 4421 } 4422 } 4423 4424 @ColorInt 4425 private static int computeColor(@NonNull @Size(3) float[] color, 4426 float x, float y, float z, @NonNull ColorSpace cs) { 4427 color[0] = x; 4428 color[1] = y; 4429 color[2] = z; 4430 cs.fromXyz(color); 4431 return 0xff000000 | 4432 (((int) (color[0] * 255.0f) & 0xff) << 16) | 4433 (((int) (color[1] * 255.0f) & 0xff) << 8) | 4434 (((int) (color[2] * 255.0f) & 0xff) ); 4435 } 4436 4437 private static double sqr(double v) { 4438 return v * v; 4439 } 4440 4441 private static class Point { 4442 @NonNull final ColorSpace mColorSpace; 4443 @NonNull final float[] mRgb; 4444 final int mColor; 4445 4446 Point(@NonNull ColorSpace colorSpace, 4447 @NonNull @Size(3) float[] rgb, @ColorInt int color) { 4448 mColorSpace = colorSpace; 4449 mRgb = rgb; 4450 mColor = color; 4451 } 4452 } 4453 } 4454} 4455