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