ColorSpace.java revision 68bd5fdd1ad3cf0b74c225b31adf1f68393bfbb6
1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.graphics; 18 19import android.annotation.IntRange; 20import android.annotation.NonNull; 21import android.annotation.Size; 22 23import java.util.Arrays; 24import java.util.function.DoubleUnaryOperator; 25 26/** 27 * {@usesMathJax} 28 * 29 * <p>A {@link ColorSpace} is used to identify a specific organization of colors. 30 * Each color space is characterized by a {@link Model color model} that defines 31 * how a color value is represented (for instance the {@link Model#RGB RGB} color 32 * model defines a color value as a triplet of numbers).</p> 33 * 34 * <p>Each component of a color must fall within a valid range, specific to each 35 * color space, defined by {@link #getMinValue(int)} and {@link #getMaxValue(int)} 36 * This range is commonly \([0..1]\). While it is recommended to use values in the 37 * valid range, a color space always clamps input and output values when performing 38 * operations such as converting to a different color space.</p> 39 * 40 * <h3>Using color spaces</h3> 41 * 42 * <p>This implementation provides a pre-defined set of common color spaces 43 * described in the {@link Named} enum. To obtain an instance of one of the 44 * pre-defined color spaces, simply invoke {@link #get(Named)}:</p> 45 * 46 * <pre class="prettyprint"> 47 * ColorSpace sRgb = ColorSpace.get(ColorSpace.Named.SRGB); 48 * </pre> 49 * 50 * <p>The {@link #get(Named)} method always returns the same instance for a given 51 * name. Color spaces with an {@link Model#RGB RGB} color model can be safely 52 * cast to {@link Rgb}. Doing so gives you access to more APIs to query various 53 * properties of RGB color models: color gamut primaries, transfer functions, 54 * conversions to and from linear space, etc. Please refer to {@link Rgb} for 55 * more information.</p> 56 * 57 * <p>The documentation of {@link Named} provides a detailed description of the 58 * various characteristics of each available color space.</p> 59 * 60 * <h3>Color space conversions</h3> 61 62 * <p>To allow conversion between color spaces, this implementation uses the CIE 63 * XYZ profile connection space (PCS). Color values can be converted to and from 64 * this PCS using {@link #toXyz(float[])} and {@link #fromXyz(float[])}.</p> 65 * 66 * <p>For color space with a non-RGB color model, the white point of the PCS 67 * <em>must be</em> the CIE standard illuminant D50. RGB color spaces use their 68 * native white point (D65 for {@link Named#SRGB sRGB} for instance and must 69 * undergo {@link Adaptation chromatic adaptation} as necessary.</p> 70 * 71 * <p>Since the white point of the PCS is not defined for RGB color space, it is 72 * highly recommended to use the variants of the {@link #connect(ColorSpace, ColorSpace)} 73 * method to perform conversions between color spaces. A color space can be 74 * manually adapted to a specific white point using {@link #adapt(ColorSpace, float[])}. 75 * Please refer to the documentation of {@link Rgb RGB color spaces} for more 76 * information. Several common CIE standard illuminants are provided in this 77 * class as reference (see {@link #ILLUMINANT_D65} or {@link #ILLUMINANT_D50} 78 * for instance).</p> 79 * 80 * <p>Here is an example of how to convert from a color space to another:</p> 81 * 82 * <pre class="prettyprint"> 83 * // Convert from DCI-P3 to Rec.2020 84 * ColorSpace.Connector connector = ColorSpace.connect( 85 * ColorSpace.get(ColorSpace.Named.DCI_P3), 86 * ColorSpace.get(ColorSpace.Named.BT2020)); 87 * 88 * float[] bt2020 = connector.transform(p3r, p3g, p3b); 89 * </pre> 90 * 91 * <p>You can easily convert to {@link Named#SRGB sRGB} by omitting the second 92 * parameter:</p> 93 * 94 * <pre class="prettyprint"> 95 * // Convert from DCI-P3 to sRGB 96 * ColorSpace.Connector connector = ColorSpace.connect(ColorSpace.get(ColorSpace.Named.DCI_P3)); 97 * 98 * float[] sRGB = connector.transform(p3r, p3g, p3b); 99 * </pre> 100 * 101 * <p>Conversions also work between color spaces with different color models:</p> 102 * 103 * <pre class="prettyprint"> 104 * // Convert from CIE L*a*b* (color model Lab) to Rec.709 (color model RGB) 105 * ColorSpace.Connector connector = ColorSpace.connect( 106 * ColorSpace.get(ColorSpace.Named.CIE_LAB), 107 * ColorSpace.get(ColorSpace.Named.BT709)); 108 * </pre> 109 * 110 * <h3>Color spaces and multi-threading</h3> 111 * 112 * <p>Color spaces and other related classes ({@link Connector} for instance) 113 * are immutable and stateless. They can be safely used from multiple concurrent 114 * threads.</p> 115 * 116 * <p>Public static methods provided by this class, such as {@link #get(Named)} 117 * and {@link #connect(ColorSpace, ColorSpace)}, are also guaranteed to be 118 * thread-safe.</p> 119 * 120 * @see #get(Named) 121 * @see Named 122 * @see Model 123 * @see Connector 124 * @see Adaptation 125 */ 126@SuppressWarnings("StaticInitializerReferencesSubClass") 127public abstract class ColorSpace { 128 /** 129 * Standard CIE 1931 2° illuminant A, encoded in xyY. 130 * This illuminant has a color temperature of 2856K. 131 */ 132 public static final float[] ILLUMINANT_A = { 0.44757f, 0.40745f }; 133 /** 134 * Standard CIE 1931 2° illuminant B, encoded in xyY. 135 * This illuminant has a color temperature of 4874K. 136 */ 137 public static final float[] ILLUMINANT_B = { 0.34842f, 0.35161f }; 138 /** 139 * Standard CIE 1931 2° illuminant C, encoded in xyY. 140 * This illuminant has a color temperature of 6774K. 141 */ 142 public static final float[] ILLUMINANT_C = { 0.31006f, 0.31616f }; 143 /** 144 * Standard CIE 1931 2° illuminant D50, encoded in xyY. 145 * This illuminant has a color temperature of 5003K. This illuminant 146 * is used by the profile connection space in ICC profiles. 147 */ 148 public static final float[] ILLUMINANT_D50 = { 0.34567f, 0.35850f }; 149 /** 150 * Standard CIE 1931 2° illuminant D55, encoded in xyY. 151 * This illuminant has a color temperature of 5503K. 152 */ 153 public static final float[] ILLUMINANT_D55 = { 0.33242f, 0.34743f }; 154 /** 155 * Standard CIE 1931 2° illuminant D60, encoded in xyY. 156 * This illuminant has a color temperature of 6004K. 157 */ 158 public static final float[] ILLUMINANT_D60 = { 0.32168f, 0.33767f }; 159 /** 160 * Standard CIE 1931 2° illuminant D65, encoded in xyY. 161 * This illuminant has a color temperature of 6504K. This illuminant 162 * is commonly used in RGB color spaces such as sRGB, BT.209, etc. 163 */ 164 public static final float[] ILLUMINANT_D65 = { 0.31271f, 0.32902f }; 165 /** 166 * Standard CIE 1931 2° illuminant D75, encoded in xyY. 167 * This illuminant has a color temperature of 7504K. 168 */ 169 public static final float[] ILLUMINANT_D75 = { 0.29902f, 0.31485f }; 170 /** 171 * Standard CIE 1931 2° illuminant E, encoded in xyY. 172 * This illuminant has a color temperature of 5454K. 173 */ 174 public static final float[] ILLUMINANT_E = { 0.33333f, 0.33333f }; 175 176 /** 177 * The minimum ID value a color space can have. 178 * 179 * @see #getId() 180 */ 181 public static final int MIN_ID = -1; // Do not change 182 /** 183 * The maximum ID value a color space can have. 184 * 185 * @see #getId() 186 */ 187 public static final int MAX_ID = 64; // Do not change, used to encode in longs 188 189 private static final float[] SRGB_PRIMARIES = { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f }; 190 private static final float[] NTSC_1953_PRIMARIES = { 0.67f, 0.33f, 0.21f, 0.71f, 0.14f, 0.08f }; 191 private static final float[] ILLUMINANT_D50_XYZ = { 0.964212f, 1.0f, 0.825188f }; 192 193 // See static initialization block next to #get(Named) 194 private static final ColorSpace[] sNamedColorSpaces = new ColorSpace[Named.values().length]; 195 196 @NonNull private final String mName; 197 @NonNull private final Model mModel; 198 @IntRange(from = MIN_ID, to = MAX_ID) private final int mId; 199 200 /** 201 * {@usesMathJax} 202 * 203 * <p>List of common, named color spaces. A corresponding instance of 204 * {@link ColorSpace} can be obtained by calling {@link ColorSpace#get(Named)}:</p> 205 * 206 * <pre class="prettyprint"> 207 * ColorSpace cs = ColorSpace.get(ColorSpace.Named.DCI_P3); 208 * </pre> 209 * 210 * @see ColorSpace#get(Named) 211 */ 212 public enum Named { 213 // NOTE: Do NOT change the order of the enum 214 /** 215 * <p>{@link ColorSpace.Rgb RGB} color space sRGB standardized as IEC 61966-2.1:1999.</p> 216 * <table summary="Color space definition"> 217 * <tr> 218 * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> 219 * </tr> 220 * <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr> 221 * <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr> 222 * <tr><th>Property</th><th colspan="4">Value</th></tr> 223 * <tr><td>Name</td><td colspan="4">sRGB IEC61966-2.1</td></tr> 224 * <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr> 225 * <tr> 226 * <td>Opto-electronic transfer function</td> 227 * <td colspan="4">\(\begin{equation} 228 * C_{sRGB} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \le 0.0031308 \\ 229 * 1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \gt 0.0031308 \end{cases} 230 * \end{equation}\) 231 * </td> 232 * </tr> 233 * <tr> 234 * <td>Electro-optical transfer function</td> 235 * <td colspan="4">\(\begin{equation} 236 * C_{linear} = \begin{cases}\frac{C_{sRGB}}{12.92} & C_{sRGB} \le 0.04045 \\ 237 * \left( \frac{C_{sRGB} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \gt 0.04045 \end{cases} 238 * \end{equation}\) 239 * </td> 240 * </tr> 241 * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> 242 * </table> 243 */ 244 SRGB, 245 /** 246 * <p>{@link ColorSpace.Rgb RGB} color space sRGB standardized as IEC 61966-2.1:1999.</p> 247 * <table summary="Color space definition"> 248 * <tr> 249 * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> 250 * </tr> 251 * <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr> 252 * <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr> 253 * <tr><th>Property</th><th colspan="4">Value</th></tr> 254 * <tr><td>Name</td><td colspan="4">sRGB IEC61966-2.1 (Linear)</td></tr> 255 * <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr> 256 * <tr> 257 * <td>Opto-electronic transfer function</td> 258 * <td colspan="4">\(C_{sRGB} = C_{linear}\)</td> 259 * </tr> 260 * <tr> 261 * <td>Electro-optical transfer function</td> 262 * <td colspan="4">\(C_{linear} = C_{sRGB}\)</td> 263 * </tr> 264 * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> 265 * </table> 266 */ 267 LINEAR_SRGB, 268 /** 269 * <p>{@link ColorSpace.Rgb RGB} color space scRGB-nl standardized as IEC 61966-2-2:2003.</p> 270 * <table summary="Color space definition"> 271 * <tr> 272 * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> 273 * </tr> 274 * <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr> 275 * <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr> 276 * <tr><th>Property</th><th colspan="4">Value</th></tr> 277 * <tr><td>Name</td><td colspan="4">scRGB-nl IEC 61966-2-2:2003</td></tr> 278 * <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr> 279 * <tr> 280 * <td>Opto-electronic transfer function</td> 281 * <td colspan="4">\(\begin{equation} 282 * C_{scRGB} = \begin{cases} sign(C_{linear}) 12.92 \times \left| C_{linear} \right| & 283 * \left| C_{linear} \right| \le 0.0031308 \\ 284 * sign(C_{linear}) 1.055 \times \left| C_{linear} \right| ^{\frac{1}{2.4}} - 0.055 & 285 * \left| C_{linear} \right| \gt 0.0031308 \end{cases} 286 * \end{equation}\) 287 * </td> 288 * </tr> 289 * <tr> 290 * <td>Electro-optical transfer function</td> 291 * <td colspan="4">\(\begin{equation} 292 * C_{linear} = \begin{cases}sign(C_{scRGB}) \frac{\left| C_{scRGB} \right|}{12.92} & 293 * \left| C_{scRGB} \right| \le 0.04045 \\ 294 * sign(C_{scRGB}) \left( \frac{\left| C_{scRGB} \right| + 0.055}{1.055} \right) ^{2.4} & 295 * \left| C_{scRGB} \right| \gt 0.04045 \end{cases} 296 * \end{equation}\) 297 * </td> 298 * </tr> 299 * <tr><td>Range</td><td colspan="4">\([-0.5..7.5[\)</td></tr> 300 * </table> 301 */ 302 EXTENDED_SRGB, 303 /** 304 * <p>{@link ColorSpace.Rgb RGB} color space scRGB standardized as IEC 61966-2-2:2003.</p> 305 * <table summary="Color space definition"> 306 * <tr> 307 * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> 308 * </tr> 309 * <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr> 310 * <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr> 311 * <tr><th>Property</th><th colspan="4">Value</th></tr> 312 * <tr><td>Name</td><td colspan="4">scRGB IEC 61966-2-2:2003</td></tr> 313 * <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr> 314 * <tr> 315 * <td>Opto-electronic transfer function</td> 316 * <td colspan="4">\(C_{scRGB} = C_{linear}\)</td> 317 * </tr> 318 * <tr> 319 * <td>Electro-optical transfer function</td> 320 * <td colspan="4">\(C_{linear} = C_{scRGB}\)</td> 321 * </tr> 322 * <tr><td>Range</td><td colspan="4">\([-0.5..7.5[\)</td></tr> 323 * </table> 324 */ 325 LINEAR_EXTENDED_SRGB, 326 /** 327 * <p>{@link ColorSpace.Rgb RGB} color space BT.709 standardized as Rec. ITU-R BT.709-5.</p> 328 * <table summary="Color space definition"> 329 * <tr> 330 * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> 331 * </tr> 332 * <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr> 333 * <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr> 334 * <tr><th>Property</th><th colspan="4">Value</th></tr> 335 * <tr><td>Name</td><td colspan="4">Rec. ITU-R BT.709-5</td></tr> 336 * <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr> 337 * <tr> 338 * <td>Opto-electronic transfer function</td> 339 * <td colspan="4">\(\begin{equation} 340 * C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\ 341 * 1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases} 342 * \end{equation}\) 343 * </td> 344 * </tr> 345 * <tr> 346 * <td>Electro-optical transfer function</td> 347 * <td colspan="4">\(\begin{equation} 348 * C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\ 349 * \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases} 350 * \end{equation}\) 351 * </td> 352 * </tr> 353 * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> 354 * </table> 355 */ 356 BT709, 357 /** 358 * <p>{@link ColorSpace.Rgb RGB} color space BT.2020 standardized as Rec. ITU-R BT.2020-1.</p> 359 * <table summary="Color space definition"> 360 * <tr> 361 * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> 362 * </tr> 363 * <tr><td>x</td><td>0.708</td><td>0.170</td><td>0.131</td><td>0.3127</td></tr> 364 * <tr><td>y</td><td>0.292</td><td>0.797</td><td>0.046</td><td>0.3290</td></tr> 365 * <tr><th>Property</th><th colspan="4">Value</th></tr> 366 * <tr><td>Name</td><td colspan="4">Rec. ITU-R BT.2020-1</td></tr> 367 * <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr> 368 * <tr> 369 * <td>Opto-electronic transfer function</td> 370 * <td colspan="4">\(\begin{equation} 371 * C_{BT2020} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.0181 \\ 372 * 1.0993 \times C_{linear}^{\frac{1}{2.2}} - 0.0993 & C_{linear} \ge 0.0181 \end{cases} 373 * \end{equation}\) 374 * </td> 375 * </tr> 376 * <tr> 377 * <td>Electro-optical transfer function</td> 378 * <td colspan="4">\(\begin{equation} 379 * C_{linear} = \begin{cases}\frac{C_{BT2020}}{4.5} & C_{BT2020} \lt 0.08145 \\ 380 * \left( \frac{C_{BT2020} + 0.0993}{1.0993} \right) ^{2.2} & C_{BT2020} \ge 0.08145 \end{cases} 381 * \end{equation}\) 382 * </td> 383 * </tr> 384 * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> 385 * </table> 386 */ 387 BT2020, 388 /** 389 * <p>{@link ColorSpace.Rgb RGB} color space DCI-P3 standardized as SMPTE RP 431-2-2007.</p> 390 * <table summary="Color space definition"> 391 * <tr> 392 * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> 393 * </tr> 394 * <tr><td>x</td><td>0.680</td><td>0.265</td><td>0.150</td><td>0.314</td></tr> 395 * <tr><td>y</td><td>0.320</td><td>0.690</td><td>0.060</td><td>0.351</td></tr> 396 * <tr><th>Property</th><th colspan="4">Value</th></tr> 397 * <tr><td>Name</td><td colspan="4">SMPTE RP 431-2-2007 DCI (P3)</td></tr> 398 * <tr><td>CIE standard illuminant</td><td colspan="4">N/A</td></tr> 399 * <tr> 400 * <td>Opto-electronic transfer function</td> 401 * <td colspan="4">\(C_{P3} = C_{linear}^{\frac{1}{2.6}}\)</td> 402 * </tr> 403 * <tr> 404 * <td>Electro-optical transfer function</td> 405 * <td colspan="4">\(C_{linear} = C_{P3}^{2.6}\)</td> 406 * </tr> 407 * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> 408 * </table> 409 */ 410 DCI_P3, 411 /** 412 * <p>{@link ColorSpace.Rgb RGB} color space Display P3 based on SMPTE RP 431-2-2007 and IEC 61966-2.1:1999.</p> 413 * <table summary="Color space definition"> 414 * <tr> 415 * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> 416 * </tr> 417 * <tr><td>x</td><td>0.680</td><td>0.265</td><td>0.150</td><td>0.3127</td></tr> 418 * <tr><td>y</td><td>0.320</td><td>0.690</td><td>0.060</td><td>0.3290</td></tr> 419 * <tr><th>Property</th><th colspan="4">Value</th></tr> 420 * <tr><td>Name</td><td colspan="4">Display P3</td></tr> 421 * <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr> 422 * <tr> 423 * <td>Opto-electronic transfer function</td> 424 * <td colspan="4">\(\begin{equation} 425 * C_{sRGB} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \le 0.0031308 \\ 426 * 1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \gt 0.0031308 \end{cases} 427 * \end{equation}\) 428 * </td> 429 * </tr> 430 * <tr> 431 * <td>Electro-optical transfer function</td> 432 * <td colspan="4">\(\begin{equation} 433 * C_{linear} = \begin{cases}\frac{C_{sRGB}}{12.92} & C_{sRGB} \le 0.04045 \\ 434 * \left( \frac{C_{sRGB} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \gt 0.04045 \end{cases} 435 * \end{equation}\) 436 * </td> 437 * </tr> 438 * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> 439 * </table> 440 */ 441 DISPLAY_P3, 442 /** 443 * <p>{@link ColorSpace.Rgb RGB} color space NTSC, 1953 standard.</p> 444 * <table summary="Color space definition"> 445 * <tr> 446 * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> 447 * </tr> 448 * <tr><td>x</td><td>0.67</td><td>0.21</td><td>0.14</td><td>0.310</td></tr> 449 * <tr><td>y</td><td>0.33</td><td>0.71</td><td>0.08</td><td>0.316</td></tr> 450 * <tr><th>Property</th><th colspan="4">Value</th></tr> 451 * <tr><td>Name</td><td colspan="4">NTSC (1953)</td></tr> 452 * <tr><td>CIE standard illuminant</td><td colspan="4">C</td></tr> 453 * <tr> 454 * <td>Opto-electronic transfer function</td> 455 * <td colspan="4">\(\begin{equation} 456 * C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\ 457 * 1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases} 458 * \end{equation}\) 459 * </td> 460 * </tr> 461 * <tr> 462 * <td>Electro-optical transfer function</td> 463 * <td colspan="4">\(\begin{equation} 464 * C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\ 465 * \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases} 466 * \end{equation}\) 467 * </td> 468 * </tr> 469 * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> 470 * </table> 471 */ 472 NTSC_1953, 473 /** 474 * <p>{@link ColorSpace.Rgb RGB} color space SMPTE C.</p> 475 * <table summary="Color space definition"> 476 * <tr> 477 * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> 478 * </tr> 479 * <tr><td>x</td><td>0.630</td><td>0.310</td><td>0.155</td><td>0.3127</td></tr> 480 * <tr><td>y</td><td>0.340</td><td>0.595</td><td>0.070</td><td>0.3290</td></tr> 481 * <tr><th>Property</th><th colspan="4">Value</th></tr> 482 * <tr><td>Name</td><td colspan="4">SMPTE-C RGB</td></tr> 483 * <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr> 484 * <tr> 485 * <td>Opto-electronic transfer function</td> 486 * <td colspan="4">\(\begin{equation} 487 * C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\ 488 * 1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases} 489 * \end{equation}\) 490 * </td> 491 * </tr> 492 * <tr> 493 * <td>Electro-optical transfer function</td> 494 * <td colspan="4">\(\begin{equation} 495 * C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\ 496 * \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases} 497 * \end{equation}\) 498 * </td> 499 * </tr> 500 * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> 501 * </table> 502 */ 503 SMPTE_C, 504 /** 505 * <p>{@link ColorSpace.Rgb RGB} color space Adobe RGB (1998).</p> 506 * <table summary="Color space definition"> 507 * <tr> 508 * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> 509 * </tr> 510 * <tr><td>x</td><td>0.64</td><td>0.21</td><td>0.15</td><td>0.3127</td></tr> 511 * <tr><td>y</td><td>0.33</td><td>0.71</td><td>0.06</td><td>0.3290</td></tr> 512 * <tr><th>Property</th><th colspan="4">Value</th></tr> 513 * <tr><td>Name</td><td colspan="4">Adobe RGB (1998)</td></tr> 514 * <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr> 515 * <tr> 516 * <td>Opto-electronic transfer function</td> 517 * <td colspan="4">\(C_{RGB} = C_{linear}^{\frac{1}{2.2}}\)</td> 518 * </tr> 519 * <tr> 520 * <td>Electro-optical transfer function</td> 521 * <td colspan="4">\(C_{linear} = C_{RGB}^{2.2}\)</td> 522 * </tr> 523 * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> 524 * </table> 525 */ 526 ADOBE_RGB, 527 /** 528 * <p>{@link ColorSpace.Rgb RGB} color space ProPhoto RGB standardized as ROMM RGB ISO 22028-2:2013.</p> 529 * <table summary="Color space definition"> 530 * <tr> 531 * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> 532 * </tr> 533 * <tr><td>x</td><td>0.7347</td><td>0.1596</td><td>0.0366</td><td>0.3457</td></tr> 534 * <tr><td>y</td><td>0.2653</td><td>0.8404</td><td>0.0001</td><td>0.3585</td></tr> 535 * <tr><th>Property</th><th colspan="4">Value</th></tr> 536 * <tr><td>Name</td><td colspan="4">ROMM RGB ISO 22028-2:2013</td></tr> 537 * <tr><td>CIE standard illuminant</td><td colspan="4">D50</td></tr> 538 * <tr> 539 * <td>Opto-electronic transfer function</td> 540 * <td colspan="4">\(\begin{equation} 541 * C_{ROMM} = \begin{cases} 16 \times C_{linear} & C_{linear} \lt 0.001953 \\ 542 * C_{linear}^{\frac{1}{1.8}} & C_{linear} \ge 0.001953 \end{cases} 543 * \end{equation}\) 544 * </td> 545 * </tr> 546 * <tr> 547 * <td>Electro-optical transfer function</td> 548 * <td colspan="4">\(\begin{equation} 549 * C_{linear} = \begin{cases}\frac{C_{ROMM}}{16} & C_{ROMM} \lt 0.031248 \\ 550 * C_{ROMM}^{1.8} & C_{ROMM} \ge 0.031248 \end{cases} 551 * \end{equation}\) 552 * </td> 553 * </tr> 554 * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr> 555 * </table> 556 */ 557 PRO_PHOTO_RGB, 558 /** 559 * <p>{@link ColorSpace.Rgb RGB} color space ACES standardized as SMPTE ST 2065-1:2012.</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.73470</td><td>0.00000</td><td>0.00010</td><td>0.32168</td></tr> 565 * <tr><td>y</td><td>0.26530</td><td>1.00000</td><td>-0.07700</td><td>0.33767</td></tr> 566 * <tr><th>Property</th><th colspan="4">Value</th></tr> 567 * <tr><td>Name</td><td colspan="4">SMPTE ST 2065-1:2012 ACES</td></tr> 568 * <tr><td>CIE standard illuminant</td><td colspan="4">D60</td></tr> 569 * <tr> 570 * <td>Opto-electronic transfer function</td> 571 * <td colspan="4">\(C_{ACES} = C_{linear}\)</td> 572 * </tr> 573 * <tr> 574 * <td>Electro-optical transfer function</td> 575 * <td colspan="4">\(C_{linear} = C_{ACES}\)</td> 576 * </tr> 577 * <tr><td>Range</td><td colspan="4">\([-65504.0, 65504.0]\)</td></tr> 578 * </table> 579 */ 580 ACES, 581 /** 582 * <p>{@link ColorSpace.Rgb RGB} color space ACEScg standardized as Academy S-2014-004.</p> 583 * <table summary="Color space definition"> 584 * <tr> 585 * <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th> 586 * </tr> 587 * <tr><td>x</td><td>0.713</td><td>0.165</td><td>0.128</td><td>0.32168</td></tr> 588 * <tr><td>y</td><td>0.293</td><td>0.830</td><td>0.044</td><td>0.33767</td></tr> 589 * <tr><th>Property</th><th colspan="4">Value</th></tr> 590 * <tr><td>Name</td><td colspan="4">Academy S-2014-004 ACEScg</td></tr> 591 * <tr><td>CIE standard illuminant</td><td colspan="4">D60</td></tr> 592 * <tr> 593 * <td>Opto-electronic transfer function</td> 594 * <td colspan="4">\(C_{ACEScg} = C_{linear}\)</td> 595 * </tr> 596 * <tr> 597 * <td>Electro-optical transfer function</td> 598 * <td colspan="4">\(C_{linear} = C_{ACEScg}\)</td> 599 * </tr> 600 * <tr><td>Range</td><td colspan="4">\([-65504.0, 65504.0]\)</td></tr> 601 * </table> 602 */ 603 ACESCG, 604 /** 605 * <p>{@link Model#XYZ XYZ} color space CIE XYZ. This color space assumes standard 606 * illuminant D50 as its white point.</p> 607 * <table summary="Color space definition"> 608 * <tr><th>Property</th><th colspan="4">Value</th></tr> 609 * <tr><td>Name</td><td colspan="4">Generic XYZ</td></tr> 610 * <tr><td>CIE standard illuminant</td><td colspan="4">D50</td></tr> 611 * <tr><td>Range</td><td colspan="4">\([-2.0, 2.0]\)</td></tr> 612 * </table> 613 */ 614 CIE_XYZ, 615 /** 616 * <p>{@link Model#LAB Lab} color space CIE L*a*b*. This color space uses CIE XYZ D50 617 * as a profile conversion space.</p> 618 * <table summary="Color space definition"> 619 * <tr><th>Property</th><th colspan="4">Value</th></tr> 620 * <tr><td>Name</td><td colspan="4">Generic L*a*b*</td></tr> 621 * <tr><td>CIE standard illuminant</td><td colspan="4">D50</td></tr> 622 * <tr><td>Range</td><td colspan="4">\(L: [0.0, 100.0], a: [-128, 128], b: [-128, 128]\)</td></tr> 623 * </table> 624 */ 625 CIE_LAB 626 // Update the initialization block next to #get(Named) when adding new values 627 } 628 629 /** 630 * <p>A render intent determines how a {@link ColorSpace.Connector connector} 631 * maps colors from one color space to another. The choice of mapping is 632 * important when the source color space has a larger color gamut than the 633 * destination color space.</p> 634 * 635 * @see ColorSpace#connect(ColorSpace, ColorSpace, RenderIntent) 636 */ 637 public enum RenderIntent { 638 /** 639 * <p>Compresses the source gamut into the destination gamut. 640 * This render intent affects all colors, inside and outside 641 * of destination gamut. The goal of this render intent is 642 * to preserve the visual relationship between colors.</p> 643 * 644 * <p class="note">This render intent is currently not 645 * implemented and behaves like {@link #RELATIVE}.</p> 646 */ 647 PERCEPTUAL, 648 /** 649 * Similar to the {@link #ABSOLUTE} render intent, this render 650 * intent matches the closest color in the destination gamut 651 * but makes adjustments for the destination white point. 652 */ 653 RELATIVE, 654 /** 655 * <p>Attempts to maintain the relative saturation of colors 656 * from the source gamut to the destination gamut, to keep 657 * highly saturated colors as saturated as possible.</p> 658 * 659 * <p class="note">This render intent is currently not 660 * implemented and behaves like {@link #RELATIVE}.</p> 661 */ 662 SATURATION, 663 /** 664 * Colors that are in the destination gamut are left unchanged. 665 * Colors that fall outside of the destination gamut are mapped 666 * to the closest possible color within the gamut of the destination 667 * color space (they are clipped). 668 */ 669 ABSOLUTE 670 } 671 672 /** 673 * {@usesMathJax} 674 * 675 * <p>List of adaptation matrices that can be used for chromatic adaptation 676 * using the von Kries transform. These matrices are used to convert values 677 * in the CIE XYZ space to values in the LMS space (Long Medium Short).</p> 678 * 679 * <p>Given an adaptation matrix \(A\), the conversion from XYZ to 680 * LMS is straightforward:</p> 681 * 682 * $$\left[ \begin{array}{c} L\\ M\\ S \end{array} \right] = 683 * A \left[ \begin{array}{c} X\\ Y\\ Z \end{array} \right]$$ 684 * 685 * <p>The complete von Kries transform \(T\) uses a diagonal matrix 686 * noted \(D\) to perform the adaptation in LMS space. In addition 687 * to \(A\) and \(D\), the source white point \(W1\) and the destination 688 * white point \(W2\) must be specified:</p> 689 * 690 * $$\begin{align*} 691 * \left[ \begin{array}{c} L_1\\ M_1\\ S_1 \end{array} \right] &= 692 * A \left[ \begin{array}{c} W1_X\\ W1_Y\\ W1_Z \end{array} \right] \\ 693 * \left[ \begin{array}{c} L_2\\ M_2\\ S_2 \end{array} \right] &= 694 * A \left[ \begin{array}{c} W2_X\\ W2_Y\\ W2_Z \end{array} \right] \\ 695 * D &= \left[ \begin{matrix} \frac{L_2}{L_1} & 0 & 0 \\ 696 * 0 & \frac{M_2}{M_1} & 0 \\ 697 * 0 & 0 & \frac{S_2}{S_1} \end{matrix} \right] \\ 698 * T &= A^{-1}.D.A 699 * \end{align*}$$ 700 * 701 * <p>As an example, the resulting matrix \(T\) can then be used to 702 * perform the chromatic adaptation of sRGB XYZ transform from D65 703 * to D50:</p> 704 * 705 * $$sRGB_{D50} = T.sRGB_{D65}$$ 706 * 707 * @see ColorSpace.Connector 708 * @see ColorSpace#connect(ColorSpace, ColorSpace) 709 */ 710 public enum Adaptation { 711 /** 712 * Bradford matrix for the von Kries chromatic adaptation transform. 713 */ 714 BRADFORD(new float[] { 715 0.8951f, -0.7502f, 0.0389f, 716 0.2664f, 1.7135f, -0.0685f, 717 -0.1614f, 0.0367f, 1.0296f 718 }), 719 /** 720 * von Kries matrix for the von Kries chromatic adaptation transform. 721 */ 722 VON_KRIES(new float[] { 723 0.40024f, -0.22630f, 0.00000f, 724 0.70760f, 1.16532f, 0.00000f, 725 -0.08081f, 0.04570f, 0.91822f 726 }); 727 728 final float[] mTransform; 729 730 Adaptation(@NonNull @Size(9) float[] transform) { 731 mTransform = transform; 732 } 733 } 734 735 /** 736 * A color model is required by a {@link ColorSpace} to describe the 737 * way colors can be represented as tuples of numbers. A common color 738 * model is the {@link #RGB RGB} color model which defines a color 739 * as represented by a tuple of 3 numbers (red, green and blue). 740 */ 741 public enum Model { 742 /** 743 * The RGB model is a color model with 3 components that 744 * refer to the three additive primiaries: red, green 745 * andd blue. 746 */ 747 RGB(3), 748 /** 749 * The XYZ model is a color model with 3 components that 750 * are used to model human color vision on a basic sensory 751 * level. 752 */ 753 XYZ(3), 754 /** 755 * The Lab model is a color model with 3 components used 756 * to describe a color space that is more perceptually 757 * uniform than XYZ. 758 */ 759 LAB(3), 760 /** 761 * The CMYK model is a color model with 4 components that 762 * refer to four inks used in color printing: cyan, magenta, 763 * yellow and black (or key). CMYK is a subtractive color 764 * model. 765 */ 766 CMYK(4); 767 768 private final int mComponentCount; 769 770 Model(@IntRange(from = 1, to = 4) int componentCount) { 771 mComponentCount = componentCount; 772 } 773 774 /** 775 * Returns the number of components for this color model. 776 * 777 * @return An integer between 1 and 4 778 */ 779 @IntRange(from = 1, to = 4) 780 public int getComponentCount() { 781 return mComponentCount; 782 } 783 } 784 785 private ColorSpace( 786 @NonNull String name, 787 @NonNull Model model, 788 @IntRange(from = MIN_ID, to = MAX_ID) int id) { 789 790 if (name == null || name.length() < 1) { 791 throw new IllegalArgumentException("The name of a color space cannot be null and " + 792 "must contain at least 1 character"); 793 } 794 795 if (model == null) { 796 throw new IllegalArgumentException("A color space must have a model"); 797 } 798 799 if (id < MIN_ID || id > MAX_ID) { 800 throw new IllegalArgumentException("The id must be between " + 801 MIN_ID + " and " + MAX_ID); 802 } 803 804 mName = name; 805 mModel = model; 806 mId = id; 807 } 808 809 /** 810 * Returns the name of this color space. The name is never null 811 * and contains always at least 1 character. 812 * 813 * @return A non-null String of length >= 1 814 */ 815 @NonNull 816 public String getName() { 817 return mName; 818 } 819 820 /** 821 * Returns the ID of this color space. Positive IDs match the color 822 * spaces enumerated in {@link Named}. A negative ID indicates a 823 * color space created by calling one of the public constructors. 824 * 825 * @return An integer between {@link #MIN_ID} and {@link #MAX_ID} 826 */ 827 @IntRange(from = MIN_ID, to = MAX_ID) 828 public int getId() { 829 return mId; 830 } 831 832 /** 833 * Return the color model of this color space. 834 * 835 * @return A non-null {@link Model} 836 * 837 * @see Model 838 * @see #getComponentCount() 839 */ 840 @NonNull 841 public Model getModel() { 842 return mModel; 843 } 844 845 /** 846 * Returns the number of components that form a color value according 847 * to this color space's color model. 848 * 849 * @return An integer between 1 and 4 850 * 851 * @see Model 852 * @see #getModel() 853 */ 854 @IntRange(from = 1, to = 4) 855 public int getComponentCount() { 856 return mModel.getComponentCount(); 857 } 858 859 /** 860 * Returns whether this color space is a wide-gamut color space. 861 * An RGB color space is wide-gamut if its gamut entirely contains 862 * the {@link Named#SRGB sRGB} gamut and if the area of its gamut is 863 * 90% of greater than the area of the {@link Named#NTSC_1953 NTSC} 864 * gamut. 865 * 866 * @return True if this color space is a wide-gamut color space, 867 * false otherwise 868 */ 869 public abstract boolean isWideGamut(); 870 871 /** 872 * <p>Indicates whether this color space is the sRGB color space or 873 * equivalent to the sRGB color space.</p> 874 * <p>A color space is considered sRGB if it meets all the following 875 * conditions:</p> 876 * <ul> 877 * <li>Its color model is {@link Model#RGB}.</li> 878 * <li> 879 * Its primaries are within 1e-3 of the true 880 * {@link Named#SRGB sRGB} primaries. 881 * </li> 882 * <li> 883 * Its white point is withing 1e-3 of the CIE standard 884 * illuminant {@link #ILLUMINANT_D65 D65}. 885 * </li> 886 * <li>Its opto-electronic transfer function is not linear.</li> 887 * <li>Its electro-optical transfer function is not linear.</li> 888 * <li>Its range is \([0..1]\).</li> 889 * </ul> 890 * <p>This method always returns true for {@link Named#SRGB}.</p> 891 * 892 * @return True if this color space is the sRGB color space (or a 893 * close approximation), false otherwise 894 */ 895 public boolean isSrgb() { 896 return false; 897 } 898 899 /** 900 * Returns the minimum valid value for the specified component of this 901 * color space's color model. 902 * 903 * @param component The index of the component 904 * @return A floating point value less than {@link #getMaxValue(int)} 905 * 906 * @see #getMaxValue(int) 907 * @see Model#getComponentCount() 908 */ 909 public abstract float getMinValue(@IntRange(from = 0, to = 3) int component); 910 911 /** 912 * Returns the maximum valid value for the specified component of this 913 * color space's color model. 914 * 915 * @param component The index of the component 916 * @return A floating point value greater than {@link #getMinValue(int)} 917 * 918 * @see #getMinValue(int) 919 * @see Model#getComponentCount() 920 */ 921 public abstract float getMaxValue(@IntRange(from = 0, to = 3) int component); 922 923 /** 924 * <p>Converts a color value from this color space's model to 925 * tristimulus CIE XYZ values. If the color model of this color 926 * space is not {@link Model#RGB RGB}, it is assumed that the 927 * target CIE XYZ space uses a {@link #ILLUMINANT_D50 D50} 928 * standard illuminant.</p> 929 * 930 * <p>This method is a convenience for color spaces with a model 931 * of 3 components ({@link Model#RGB RGB} or {@link Model#LAB} 932 * for instance). With color spaces using fewer or more components, 933 * use {@link #toXyz(float[])} instead</p>. 934 * 935 * @param r The first component of the value to convert from (typically R in RGB) 936 * @param g The second component of the value to convert from (typically G in RGB) 937 * @param b The third component of the value to convert from (typically B in RGB) 938 * @return A new array of 3 floats, containing tristimulus XYZ values 939 * 940 * @see #toXyz(float[]) 941 * @see #fromXyz(float, float, float) 942 */ 943 @NonNull 944 @Size(3) 945 public float[] toXyz(float r, float g, float b) { 946 return toXyz(new float[] { r, g, b }); 947 } 948 949 /** 950 * <p>Converts a color value from this color space's model to 951 * tristimulus CIE XYZ values. If the color model of this color 952 * space is not {@link Model#RGB RGB}, it is assumed that the 953 * target CIE XYZ space uses a {@link #ILLUMINANT_D50 D50} 954 * standard illuminant.</p> 955 * 956 * <p class="note">The specified array's length must be at least 957 * equal to to the number of color components as returned by 958 * {@link Model#getComponentCount()}.</p> 959 * 960 * @param v An array of color components containing the color space's 961 * color value to convert to XYZ, and large enough to hold 962 * the resulting tristimulus XYZ values 963 * @return The array passed in parameter 964 * 965 * @see #toXyz(float, float, float) 966 * @see #fromXyz(float[]) 967 */ 968 @NonNull 969 @Size(min = 3) 970 public abstract float[] toXyz(@NonNull @Size(min = 3) float[] v); 971 972 /** 973 * <p>Converts tristimulus values from the CIE XYZ space to this 974 * color space's color model.</p> 975 * 976 * @param x The X component of the color value 977 * @param y The Y component of the color value 978 * @param z The Z component of the color value 979 * @return A new array whose size is equal to the number of color 980 * components as returned by {@link Model#getComponentCount()} 981 * 982 * @see #fromXyz(float[]) 983 * @see #toXyz(float, float, float) 984 */ 985 @NonNull 986 @Size(min = 3) 987 public float[] fromXyz(float x, float y, float z) { 988 float[] xyz = new float[mModel.getComponentCount()]; 989 xyz[0] = x; 990 xyz[1] = y; 991 xyz[2] = z; 992 return fromXyz(xyz); 993 } 994 995 /** 996 * <p>Converts tristimulus values from the CIE XYZ space to this color 997 * space's color model. The resulting value is passed back in the specified 998 * array.</p> 999 * 1000 * <p class="note>The specified array's length must be at least equal to 1001 * to the number of color components as returned by 1002 * {@link Model#getComponentCount()}, and its first 3 values must 1003 * be the XYZ components to convert from.</p> 1004 * 1005 * @param v An array of color components containing the XYZ values 1006 * to convert from, and large enough to hold the number 1007 * of components of this color space's model 1008 * @return The array passed in parameter 1009 * 1010 * @see #fromXyz(float, float, float) 1011 * @see #toXyz(float[]) 1012 */ 1013 @NonNull 1014 @Size(min = 3) 1015 public abstract float[] fromXyz(@NonNull @Size(min = 3) float[] v); 1016 1017 /** 1018 * <p>Returns a string representation of the object. This method returns 1019 * a string equal to the value of:</p> 1020 * 1021 * <pre class="prettyprint"> 1022 * getName() + "(id=" + getId() + ", model=" + getModel() + ")" 1023 * </pre> 1024 * 1025 * <p>For instance, the string representation of the {@link Named#SRGB sRGB} 1026 * color space is equal to the following value:</p> 1027 * 1028 * <pre> 1029 * sRGB IEC61966-2.1 (id=0, model=RGB) 1030 * </pre> 1031 * 1032 * @return A string representation of the object 1033 */ 1034 @Override 1035 public String toString() { 1036 return mName + " (id=" + mId + ", model=" + mModel + ")"; 1037 } 1038 1039 @Override 1040 public boolean equals(Object o) { 1041 if (this == o) return true; 1042 if (o == null || getClass() != o.getClass()) return false; 1043 1044 ColorSpace that = (ColorSpace) o; 1045 1046 if (mId != that.mId) return false; 1047 //noinspection SimplifiableIfStatement 1048 if (!mName.equals(that.mName)) return false; 1049 return mModel == that.mModel; 1050 1051 } 1052 1053 @Override 1054 public int hashCode() { 1055 int result = mName.hashCode(); 1056 result = 31 * result + mModel.hashCode(); 1057 result = 31 * result + mId; 1058 return result; 1059 } 1060 1061 /** 1062 * <p>Connects two color spaces to allow conversion from the source color 1063 * space to the destination color space. If the source and destination 1064 * color spaces do not have the same profile connection space (CIE XYZ 1065 * with the same white point), they are chromatically adapted to use the 1066 * CIE standard illuminant {@link #ILLUMINANT_D50 D50} as needed.</p> 1067 * 1068 * <p>If the source and destination are the same, an optimized connector 1069 * is returned to avoid unnecessary computations and loss of precision.</p> 1070 * 1071 * <p>Colors are mapped from the source color space to the destination color 1072 * space using the {@link RenderIntent#PERCEPTUAL perceptual} render intent.</p> 1073 * 1074 * @param source The color space to convert colors from 1075 * @param destination The color space to convert colors to 1076 * @return A non-null connector between the two specified color spaces 1077 * 1078 * @see #connect(ColorSpace) 1079 * @see #connect(ColorSpace, RenderIntent) 1080 * @see #connect(ColorSpace, ColorSpace, RenderIntent) 1081 */ 1082 @NonNull 1083 public static Connector connect(@NonNull ColorSpace source, @NonNull ColorSpace destination) { 1084 return connect(source, destination, RenderIntent.PERCEPTUAL); 1085 } 1086 1087 /** 1088 * <p>Connects two color spaces to allow conversion from the source color 1089 * space to the destination color space. If the source and destination 1090 * color spaces do not have the same profile connection space (CIE XYZ 1091 * with the same white point), they are chromatically adapted to use the 1092 * CIE standard illuminant {@link #ILLUMINANT_D50 D50} as needed.</p> 1093 * 1094 * <p>If the source and destination are the same, an optimized connector 1095 * is returned to avoid unnecessary computations and loss of precision.</p> 1096 * 1097 * @param source The color space to convert colors from 1098 * @param destination The color space to convert colors to 1099 * @param intent The render intent to map colors from the source to the destination 1100 * @return A non-null connector between the two specified color spaces 1101 * 1102 * @see #connect(ColorSpace) 1103 * @see #connect(ColorSpace, RenderIntent) 1104 * @see #connect(ColorSpace, ColorSpace) 1105 */ 1106 @NonNull 1107 @SuppressWarnings("ConstantConditions") 1108 public static Connector connect(@NonNull ColorSpace source, @NonNull ColorSpace destination, 1109 @NonNull RenderIntent intent) { 1110 if (source.equals(destination)) return Connector.identity(source); 1111 1112 if (source.getModel() == Model.RGB && destination.getModel() == Model.RGB) { 1113 return new Connector.RGB((Rgb) source, (Rgb) destination, intent); 1114 } 1115 1116 return new Connector(source, destination, intent); 1117 } 1118 1119 /** 1120 * <p>Connects the specified color spaces to sRGB. 1121 * If the source color space does not use CIE XYZ D65 as its profile 1122 * connection space, the two spaces are chromatically adapted to use the 1123 * CIE standard illuminant {@link #ILLUMINANT_D50 D50} as needed.</p> 1124 * 1125 * <p>If the source is the sRGB color space, an optimized connector 1126 * is returned to avoid unnecessary computations and loss of precision.</p> 1127 * 1128 * <p>Colors are mapped from the source color space to the destination color 1129 * space using the {@link RenderIntent#PERCEPTUAL perceptual} render intent.</p> 1130 * 1131 * @param source The color space to convert colors from 1132 * @return A non-null connector between the specified color space and sRGB 1133 * 1134 * @see #connect(ColorSpace, RenderIntent) 1135 * @see #connect(ColorSpace, ColorSpace) 1136 * @see #connect(ColorSpace, ColorSpace, RenderIntent) 1137 */ 1138 @NonNull 1139 public static Connector connect(@NonNull ColorSpace source) { 1140 return connect(source, RenderIntent.PERCEPTUAL); 1141 } 1142 1143 /** 1144 * <p>Connects the specified color spaces to sRGB. 1145 * If the source color space does not use CIE XYZ D65 as its profile 1146 * connection space, the two spaces are chromatically adapted to use the 1147 * CIE standard illuminant {@link #ILLUMINANT_D50 D50} as needed.</p> 1148 * 1149 * <p>If the source is the sRGB color space, an optimized connector 1150 * is returned to avoid unnecessary computations and loss of precision.</p> 1151 * 1152 * @param source The color space to convert colors from 1153 * @param intent The render intent to map colors from the source to the destination 1154 * @return A non-null connector between the specified color space and sRGB 1155 * 1156 * @see #connect(ColorSpace) 1157 * @see #connect(ColorSpace, ColorSpace) 1158 * @see #connect(ColorSpace, ColorSpace, RenderIntent) 1159 */ 1160 @NonNull 1161 public static Connector connect(@NonNull ColorSpace source, @NonNull RenderIntent intent) { 1162 if (source.isSrgb()) return Connector.identity(source); 1163 1164 if (source.getModel() == Model.RGB) { 1165 return new Connector.RGB((Rgb) source, (Rgb) get(Named.SRGB), intent); 1166 } 1167 1168 return new Connector(source, get(Named.SRGB), intent); 1169 } 1170 1171 /** 1172 * <p>Performs the chromatic adaptation of a color space from its native 1173 * white point to the specified white point.</p> 1174 * 1175 * <p>The chromatic adaptation is performed using the 1176 * {@link Adaptation#BRADFORD} matrix.</p> 1177 * 1178 * <p class="note">The color space returned by this method always has 1179 * an ID of {@link #MIN_ID}.</p> 1180 * 1181 * @param colorSpace The color space to chromatically adapt 1182 * @param whitePoint The new white point 1183 * @return A {@link ColorSpace} instance with the same name, primaries, 1184 * transfer functions and range as the specified color space 1185 * 1186 * @see Adaptation 1187 * @see #adapt(ColorSpace, float[], Adaptation) 1188 */ 1189 @NonNull 1190 public static ColorSpace adapt(@NonNull ColorSpace colorSpace, 1191 @NonNull @Size(min = 2, max = 3) float[] whitePoint) { 1192 return adapt(colorSpace, whitePoint, Adaptation.BRADFORD); 1193 } 1194 1195 /** 1196 * <p>Performs the chromatic adaptation of a color space from its native 1197 * white point to the specified white point. If the specified color space 1198 * does not have an {@link Model#RGB RGB} color model, or if the color 1199 * space already has the target white point, the color space is returned 1200 * unmodified.</p> 1201 * 1202 * <p>The chromatic adaptation is performed using the von Kries method 1203 * described in the documentation of {@link Adaptation}.</p> 1204 * 1205 * <p class="note">The color space returned by this method always has 1206 * an ID of {@link #MIN_ID}.</p> 1207 * 1208 * @param colorSpace The color space to chromatically adapt 1209 * @param whitePoint The new white point 1210 * @param adaptation The adaptation matrix 1211 * @return A new color space if the specified color space has an RGB 1212 * model and a white point different from the specified white 1213 * point; the specified color space otherwise 1214 * 1215 * @see Adaptation 1216 * @see #adapt(ColorSpace, float[]) 1217 */ 1218 @NonNull 1219 public static ColorSpace adapt(@NonNull ColorSpace colorSpace, 1220 @NonNull @Size(min = 2, max = 3) float[] whitePoint, 1221 @NonNull Adaptation adaptation) { 1222 if (colorSpace.getModel() == Model.RGB) { 1223 ColorSpace.Rgb rgb = (ColorSpace.Rgb) colorSpace; 1224 if (compare(rgb.mWhitePoint, whitePoint)) return colorSpace; 1225 1226 float[] xyz = whitePoint.length == 3 ? 1227 Arrays.copyOf(whitePoint, 3) : xyYToXyz(whitePoint); 1228 float[] adaptationTransform = chromaticAdaptation(adaptation.mTransform, 1229 xyYToXyz(rgb.getWhitePoint()), xyz); 1230 float[] transform = mul3x3(adaptationTransform, rgb.mTransform); 1231 1232 return new ColorSpace.Rgb(rgb, transform, whitePoint); 1233 } 1234 return colorSpace; 1235 } 1236 1237 /** 1238 * <p>Returns an instance of {@link ColorSpace} whose ID matches the specified 1239 * ID. If the ID is < 0 or > {@link #MAX_ID}, calling this method is equivalent 1240 * to calling <code>get(Named.SRGB)</code>.</p> 1241 * 1242 * <p>This method always returns the same instance for a given ID.</p> 1243 * 1244 * <p>This method is thread-safe.</p> 1245 * 1246 * @param index An integer ID between {@link #MIN_ID} and {@link #MAX_ID} 1247 * @return A non-null {@link ColorSpace} instance 1248 */ 1249 @NonNull 1250 static ColorSpace get(@IntRange(from = MIN_ID, to = MAX_ID) int index) { 1251 if (index < 0 || index > Named.values().length) { 1252 return get(Named.SRGB); 1253 } 1254 return sNamedColorSpaces[index]; 1255 } 1256 1257 /** 1258 * <p>Returns an instance of {@link ColorSpace} identified by the specified 1259 * name. The list of names provided in the {@link Named} enum gives access 1260 * to a variety of common RGB color spaces.</p> 1261 * 1262 * <p>This method always returns the same instance for a given name.</p> 1263 * 1264 * <p>This method is thread-safe.</p> 1265 * 1266 * @param name The name of the color space to get an instance of 1267 * @return A non-null {@link ColorSpace} instance 1268 */ 1269 @NonNull 1270 public static ColorSpace get(@NonNull Named name) { 1271 return sNamedColorSpaces[name.ordinal()]; 1272 } 1273 1274 static { 1275 sNamedColorSpaces[Named.SRGB.ordinal()] = new ColorSpace.Rgb( 1276 "sRGB IEC61966-2.1", 1277 SRGB_PRIMARIES, 1278 ILLUMINANT_D65, 1279 x -> rcpResponse(x, 2.4, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045), 1280 x -> response(x, 2.4, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045), 1281 0.0f, 1.0f, 1282 Named.SRGB.ordinal() 1283 ); 1284 sNamedColorSpaces[Named.LINEAR_SRGB.ordinal()] = new ColorSpace.Rgb( 1285 "sRGB IEC61966-2.1 (Linear)", 1286 SRGB_PRIMARIES, 1287 ILLUMINANT_D65, 1288 DoubleUnaryOperator.identity(), 1289 DoubleUnaryOperator.identity(), 1290 0.0f, 1.0f, 1291 Named.LINEAR_SRGB.ordinal() 1292 ); 1293 sNamedColorSpaces[Named.EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb( 1294 "scRGB-nl IEC 61966-2-2:2003", 1295 SRGB_PRIMARIES, 1296 ILLUMINANT_D65, 1297 x -> absRcpResponse(x, 2.4, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045), 1298 x -> absResponse(x, 2.4, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045), 1299 -0.5f, 7.5f, 1300 Named.EXTENDED_SRGB.ordinal() 1301 ); 1302 sNamedColorSpaces[Named.LINEAR_EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb( 1303 "scRGB- IEC 61966-2-2:2003", 1304 SRGB_PRIMARIES, 1305 ILLUMINANT_D65, 1306 DoubleUnaryOperator.identity(), 1307 DoubleUnaryOperator.identity(), 1308 -0.5f, 7.5f, 1309 Named.LINEAR_EXTENDED_SRGB.ordinal() 1310 ); 1311 sNamedColorSpaces[Named.BT709.ordinal()] = new ColorSpace.Rgb( 1312 "Rec. ITU-R BT.709-5", 1313 new float[] { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f }, 1314 ILLUMINANT_D65, 1315 x -> rcpResponse(x, 1 / 0.45, 1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081), 1316 x -> response(x, 1 / 0.45, 1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081), 1317 0.0f, 1.0f, 1318 Named.BT709.ordinal() 1319 ); 1320 sNamedColorSpaces[Named.BT2020.ordinal()] = new ColorSpace.Rgb( 1321 "Rec. ITU-R BT.2020-1", 1322 new float[] { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f }, 1323 ILLUMINANT_D65, 1324 x -> rcpResponse(x, 1 / 0.45, 1 / 1.0993, 0.0993 / 1.0993, 1 / 4.5, 0.08145), 1325 x -> response(x, 1 / 0.45, 1 / 1.0993, 0.099 / 1.0993, 1 / 4.5, 0.08145), 1326 0.0f, 1.0f, 1327 Named.BT2020.ordinal() 1328 ); 1329 sNamedColorSpaces[Named.DCI_P3.ordinal()] = new ColorSpace.Rgb( 1330 "SMPTE RP 431-2-2007 DCI (P3)", 1331 new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f }, 1332 new float[] { 0.314f, 0.351f }, 1333 x -> Math.pow(x, 1 / 2.6), 1334 x -> Math.pow(x, 2.6), 1335 0.0f, 1.0f, 1336 Named.DCI_P3.ordinal() 1337 ); 1338 sNamedColorSpaces[Named.DISPLAY_P3.ordinal()] = new ColorSpace.Rgb( 1339 "Display P3", 1340 new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f }, 1341 ILLUMINANT_D65, 1342 x -> rcpResponse(x, 2.4, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045), 1343 x -> response(x, 2.4, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045), 1344 0.0f, 1.0f, 1345 Named.DISPLAY_P3.ordinal() 1346 ); 1347 sNamedColorSpaces[Named.NTSC_1953.ordinal()] = new ColorSpace.Rgb( 1348 "NTSC (1953)", 1349 NTSC_1953_PRIMARIES, 1350 ILLUMINANT_C, 1351 x -> rcpResponse(x, 1 / 0.45, 1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081), 1352 x -> response(x, 1 / 0.45, 1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081), 1353 0.0f, 1.0f, 1354 Named.NTSC_1953.ordinal() 1355 ); 1356 sNamedColorSpaces[Named.SMPTE_C.ordinal()] = new ColorSpace.Rgb( 1357 "SMPTE-C RGB", 1358 new float[] { 0.630f, 0.340f, 0.310f, 0.595f, 0.155f, 0.070f }, 1359 ILLUMINANT_D65, 1360 x -> rcpResponse(x, 1 / 0.45, 1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081), 1361 x -> response(x, 1 / 0.45, 1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081), 1362 0.0f, 1.0f, 1363 Named.SMPTE_C.ordinal() 1364 ); 1365 sNamedColorSpaces[Named.ADOBE_RGB.ordinal()] = new ColorSpace.Rgb( 1366 "Adobe RGB (1998)", 1367 new float[] { 0.64f, 0.33f, 0.21f, 0.71f, 0.15f, 0.06f }, 1368 ILLUMINANT_D65, 1369 x -> Math.pow(x, 1 / 2.2), 1370 x -> Math.pow(x, 2.2), 1371 0.0f, 1.0f, 1372 Named.ADOBE_RGB.ordinal() 1373 ); 1374 sNamedColorSpaces[Named.PRO_PHOTO_RGB.ordinal()] = new ColorSpace.Rgb( 1375 "ROMM RGB ISO 22028-2:2013", 1376 new float[] { 0.7347f, 0.2653f, 0.1596f, 0.8404f, 0.0366f, 0.0001f }, 1377 ILLUMINANT_D50, 1378 x -> rcpResponse(x, 1.8, 1.0, 0.0, 1 / 16.0, 0.031248), 1379 x -> response(x, 1.8, 1.0, 0.0, 1 / 16.0, 0.031248), 1380 0.0f, 1.0f, 1381 Named.PRO_PHOTO_RGB.ordinal() 1382 ); 1383 sNamedColorSpaces[Named.ACES.ordinal()] = new ColorSpace.Rgb( 1384 "SMPTE ST 2065-1:2012 ACES", 1385 new float[] { 0.73470f, 0.26530f, 0.0f, 1.0f, 0.00010f, -0.0770f }, 1386 ILLUMINANT_D60, 1387 DoubleUnaryOperator.identity(), 1388 DoubleUnaryOperator.identity(), 1389 -65504.0f, 65504.0f, 1390 Named.ACES.ordinal() 1391 ); 1392 sNamedColorSpaces[Named.ACESCG.ordinal()] = new ColorSpace.Rgb( 1393 "Academy S-2014-004 ACEScg", 1394 new float[] { 0.713f, 0.293f, 0.165f, 0.830f, 0.128f, 0.044f }, 1395 ILLUMINANT_D60, 1396 DoubleUnaryOperator.identity(), 1397 DoubleUnaryOperator.identity(), 1398 -65504.0f, 65504.0f, 1399 Named.ACESCG.ordinal() 1400 ); 1401 sNamedColorSpaces[Named.CIE_XYZ.ordinal()] = new Xyz( 1402 "Generic XYZ", 1403 Named.CIE_XYZ.ordinal() 1404 ); 1405 sNamedColorSpaces[Named.CIE_LAB.ordinal()] = new ColorSpace.Lab( 1406 "Generic L*a*b*", 1407 Named.CIE_LAB.ordinal() 1408 ); 1409 } 1410 1411 // Reciprocal piecewise gamma response 1412 private static double rcpResponse(double x, double g,double a, double b, double c, double d) { 1413 return x >= d * c ? (Math.pow(x, 1.0 / g) - b) / a : x / c; 1414 } 1415 1416 // Piecewise gamma response 1417 private static double response(double x, double g, double a, double b, double c, double d) { 1418 return x >= d ? Math.pow(a * x + b, g) : c * x; 1419 } 1420 1421 // Reciprocal piecewise gamma response, encoded as sign(x).f(abs(x)) for color 1422 // spaces that allow negative values 1423 @SuppressWarnings("SameParameterValue") 1424 private static double absRcpResponse(double x, double g, double a, double b, double c, double d) { 1425 return Math.copySign(rcpResponse(x < 0.0 ? -x : x, g, a, b, c, d), x); 1426 } 1427 1428 // Piecewise gamma response, encoded as sign(x).f(abs(x)) for color spaces that 1429 // allow negative values 1430 @SuppressWarnings("SameParameterValue") 1431 private static double absResponse(double x, double g, double a, double b, double c, double d) { 1432 return Math.copySign(response(x < 0.0 ? -x : x, g, a, b, c, d), x); 1433 } 1434 1435 /** 1436 * Compares two arrays of float with a precision of 1e-3. 1437 * 1438 * @param a The first array to compare 1439 * @param b The second array to compare 1440 * @return True if the two arrays are equal, false otherwise 1441 */ 1442 private static boolean compare(@NonNull float[] a, @NonNull float[] b) { 1443 if (a == b) return true; 1444 for (int i = 0; i < a.length; i++) { 1445 if (Float.compare(a[i], b[i]) != 0 && Math.abs(a[i] - b[i]) > 1e-3f) return false; 1446 } 1447 return true; 1448 } 1449 1450 /** 1451 * Inverts a 3x3 matrix. This method assumes the matrix is invertible. 1452 * 1453 * @param m A 3x3 matrix as a non-null array of 9 floats 1454 * @return A new array of 9 floats containing the inverse of the input matrix 1455 */ 1456 @NonNull 1457 @Size(9) 1458 private static float[] inverse3x3(@NonNull @Size(9) float[] m) { 1459 float a = m[0]; 1460 float b = m[3]; 1461 float c = m[6]; 1462 float d = m[1]; 1463 float e = m[4]; 1464 float f = m[7]; 1465 float g = m[2]; 1466 float h = m[5]; 1467 float i = m[8]; 1468 1469 float A = e * i - f * h; 1470 float B = f * g - d * i; 1471 float C = d * h - e * g; 1472 1473 float det = a * A + b * B + c * C; 1474 1475 float inverted[] = new float[m.length]; 1476 inverted[0] = A / det; 1477 inverted[1] = B / det; 1478 inverted[2] = C / det; 1479 inverted[3] = (c * h - b * i) / det; 1480 inverted[4] = (a * i - c * g) / det; 1481 inverted[5] = (b * g - a * h) / det; 1482 inverted[6] = (b * f - c * e) / det; 1483 inverted[7] = (c * d - a * f) / det; 1484 inverted[8] = (a * e - b * d) / det; 1485 return inverted; 1486 } 1487 1488 /** 1489 * Multiplies two 3x3 matrices, represented as non-null arrays of 9 floats. 1490 * 1491 * @param lhs 3x3 matrix, as a non-null array of 9 floats 1492 * @param rhs 3x3 matrix, as a non-null array of 9 floats 1493 * @return A new array of 9 floats containing the result of the multiplication 1494 * of rhs by lhs 1495 */ 1496 @NonNull 1497 @Size(9) 1498 private static float[] mul3x3(@NonNull @Size(9) float[] lhs, @NonNull @Size(9) float[] rhs) { 1499 float[] r = new float[9]; 1500 r[0] = lhs[0] * rhs[0] + lhs[3] * rhs[1] + lhs[6] * rhs[2]; 1501 r[1] = lhs[1] * rhs[0] + lhs[4] * rhs[1] + lhs[7] * rhs[2]; 1502 r[2] = lhs[2] * rhs[0] + lhs[5] * rhs[1] + lhs[8] * rhs[2]; 1503 r[3] = lhs[0] * rhs[3] + lhs[3] * rhs[4] + lhs[6] * rhs[5]; 1504 r[4] = lhs[1] * rhs[3] + lhs[4] * rhs[4] + lhs[7] * rhs[5]; 1505 r[5] = lhs[2] * rhs[3] + lhs[5] * rhs[4] + lhs[8] * rhs[5]; 1506 r[6] = lhs[0] * rhs[6] + lhs[3] * rhs[7] + lhs[6] * rhs[8]; 1507 r[7] = lhs[1] * rhs[6] + lhs[4] * rhs[7] + lhs[7] * rhs[8]; 1508 r[8] = lhs[2] * rhs[6] + lhs[5] * rhs[7] + lhs[8] * rhs[8]; 1509 return r; 1510 } 1511 1512 /** 1513 * Multiplies a vector of 3 components by a 3x3 matrix and stores the 1514 * result in the input vector. 1515 * 1516 * @param lhs 3x3 matrix, as a non-null array of 9 floats 1517 * @param rhs Vector of 3 components, as a non-null array of 3 floats 1518 * @return The array of 3 passed as the rhs parameter 1519 */ 1520 @NonNull 1521 @Size(min = 3) 1522 private static float[] mul3x3Float3( 1523 @NonNull @Size(9) float[] lhs, @NonNull @Size(min = 3) float[] rhs) { 1524 float r0 = rhs[0]; 1525 float r1 = rhs[1]; 1526 float r2 = rhs[2]; 1527 rhs[0] = lhs[0] * r0 + lhs[3] * r1 + lhs[6] * r2; 1528 rhs[1] = lhs[1] * r0 + lhs[4] * r1 + lhs[7] * r2; 1529 rhs[2] = lhs[2] * r0 + lhs[5] * r1 + lhs[8] * r2; 1530 return rhs; 1531 } 1532 1533 /** 1534 * Multiplies a diagonal 3x3 matrix lhs, represented as an array of 3 floats, 1535 * by a 3x3 matrix represented as an array of 9 floats. 1536 * 1537 * @param lhs Diagonal 3x3 matrix, as a non-null array of 3 floats 1538 * @param rhs 3x3 matrix, as a non-null array of 9 floats 1539 * @return A new array of 9 floats containing the result of the multiplication 1540 * of rhs by lhs 1541 */ 1542 @NonNull 1543 @Size(9) 1544 private static float[] mul3x3Diag( 1545 @NonNull @Size(3) float[] lhs, @NonNull @Size(9) float[] rhs) { 1546 return new float[] { 1547 lhs[0] * rhs[0], lhs[1] * rhs[1], lhs[2] * rhs[2], 1548 lhs[0] * rhs[3], lhs[1] * rhs[4], lhs[2] * rhs[5], 1549 lhs[0] * rhs[6], lhs[1] * rhs[7], lhs[2] * rhs[8] 1550 }; 1551 } 1552 1553 /** 1554 * Converts a value from CIE xyY to CIE XYZ. Y is assumed to be 1 so the 1555 * input xyY array only contains the x and y components. 1556 * 1557 * @param xyY The xyY value to convert to XYZ, cannot be null, length must be 2 1558 * @return A new float array of length 3 containing XYZ values 1559 */ 1560 @NonNull 1561 @Size(3) 1562 private static float[] xyYToXyz(@NonNull @Size(2) float[] xyY) { 1563 return new float[] { xyY[0] / xyY[1], 1.0f, (1 - xyY[0] - xyY[1]) / xyY[1] }; 1564 } 1565 1566 /** 1567 * <p>Computes the chromatic adaptation transform from the specified 1568 * source white point to the specified destination white point.</p> 1569 * 1570 * <p>The transform is computed using the von Kris method, described 1571 * in more details in the documentation of {@link Adaptation}. The 1572 * {@link Adaptation} enum provides different matrices that can be 1573 * used to perform the adaptation.</p> 1574 * 1575 * @param matrix The adaptation matrix 1576 * @param srcWhitePoint The white point to adapt from, *will be modified* 1577 * @param dstWhitePoint The white point to adapt to, *will be modified* 1578 * @return A 3x3 matrix as a non-null array of 9 floats 1579 */ 1580 @NonNull 1581 @Size(9) 1582 private static float[] chromaticAdaptation(@NonNull @Size(9) float[] matrix, 1583 @NonNull @Size(3) float[] srcWhitePoint, @NonNull @Size(3) float[] dstWhitePoint) { 1584 float[] srcLMS = mul3x3Float3(matrix, srcWhitePoint); 1585 float[] dstLMS = mul3x3Float3(matrix, dstWhitePoint); 1586 // LMS is a diagonal matrix stored as a float[3] 1587 float[] LMS = { dstLMS[0] / srcLMS[0], dstLMS[1] / srcLMS[1], dstLMS[2] / srcLMS[2] }; 1588 return mul3x3(inverse3x3(matrix), mul3x3Diag(LMS, matrix)); 1589 } 1590 1591 /** 1592 * Implementation of the CIE XYZ color space. Assumes the white point is D50. 1593 */ 1594 private static final class Xyz extends ColorSpace { 1595 private Xyz(@NonNull String name, @IntRange(from = MIN_ID, to = MAX_ID) int id) { 1596 super(name, Model.XYZ, id); 1597 } 1598 1599 @Override 1600 public boolean isWideGamut() { 1601 return true; 1602 } 1603 1604 @Override 1605 public float getMinValue(@IntRange(from = 0, to = 3) int component) { 1606 return -2.0f; 1607 } 1608 1609 @Override 1610 public float getMaxValue(@IntRange(from = 0, to = 3) int component) { 1611 return 2.0f; 1612 } 1613 1614 @Override 1615 public float[] toXyz(@NonNull @Size(min = 3) float[] v) { 1616 v[0] = clamp(v[0]); 1617 v[1] = clamp(v[1]); 1618 v[2] = clamp(v[2]); 1619 return v; 1620 } 1621 1622 @Override 1623 public float[] fromXyz(@NonNull @Size(min = 3) float[] v) { 1624 v[0] = clamp(v[0]); 1625 v[1] = clamp(v[1]); 1626 v[2] = clamp(v[2]); 1627 return v; 1628 } 1629 1630 private static float clamp(float x) { 1631 return x < -2.0f ? -2.0f : x > 2.0f ? 2.0f : x; 1632 } 1633 } 1634 1635 /** 1636 * Implementation of the CIE L*a*b* color space. Its PCS is CIE XYZ 1637 * with a white point of D50. 1638 */ 1639 private static final class Lab extends ColorSpace { 1640 private static final float A = 216.0f / 24389.0f; 1641 private static final float B = 841.0f / 108.0f; 1642 private static final float C = 4.0f / 29.0f; 1643 private static final float D = 6.0f / 29.0f; 1644 1645 private Lab(@NonNull String name, @IntRange(from = MIN_ID, to = MAX_ID) int id) { 1646 super(name, Model.LAB, id); 1647 } 1648 1649 @Override 1650 public boolean isWideGamut() { 1651 return true; 1652 } 1653 1654 @Override 1655 public float getMinValue(@IntRange(from = 0, to = 3) int component) { 1656 return component == 0 ? 0.0f : -128.0f; 1657 } 1658 1659 @Override 1660 public float getMaxValue(@IntRange(from = 0, to = 3) int component) { 1661 return component == 0 ? 100.0f : 128.0f; 1662 } 1663 1664 @Override 1665 public float[] toXyz(@NonNull @Size(min = 3) float[] v) { 1666 v[0] = clamp(v[0], 0.0f, 100.0f); 1667 v[1] = clamp(v[1], -128.0f, 128.0f); 1668 v[2] = clamp(v[2], -128.0f, 128.0f); 1669 1670 float fy = (v[0] + 16.0f) / 116.0f; 1671 float fx = fy + (v[1] * 0.002f); 1672 float fz = fy - (v[2] * 0.005f); 1673 float X = fx > D ? fx * fx * fx : (1.0f / B) * (fx - C); 1674 float Y = fy > D ? fy * fy * fy : (1.0f / B) * (fy - C); 1675 float Z = fz > D ? fz * fz * fz : (1.0f / B) * (fz - C); 1676 1677 v[0] = X * ILLUMINANT_D50_XYZ[0]; 1678 v[1] = Y * ILLUMINANT_D50_XYZ[1]; 1679 v[2] = Z * ILLUMINANT_D50_XYZ[2]; 1680 1681 return v; 1682 } 1683 1684 @Override 1685 public float[] fromXyz(@NonNull @Size(min = 3) float[] v) { 1686 float X = v[0] / ILLUMINANT_D50_XYZ[0]; 1687 float Y = v[1] / ILLUMINANT_D50_XYZ[1]; 1688 float Z = v[2] / ILLUMINANT_D50_XYZ[2]; 1689 1690 float fx = X > A ? (float) Math.pow(X, 1.0 / 3.0) : B * X + C; 1691 float fy = Y > A ? (float) Math.pow(Y, 1.0 / 3.0) : B * Y + C; 1692 float fz = Z > A ? (float) Math.pow(Z, 1.0 / 3.0) : B * Z + C; 1693 1694 float L = 116.0f * fy - 16.0f; 1695 float a = 500.0f * (fx - fy); 1696 float b = 200.0f * (fy - fz); 1697 1698 v[0] = clamp(L, 0.0f, 100.0f); 1699 v[1] = clamp(a, -128.0f, 128.0f); 1700 v[2] = clamp(b, -128.0f, 128.0f); 1701 1702 return v; 1703 } 1704 1705 private static float clamp(float x, float min, float max) { 1706 return x < min ? min : x > max ? max : x; 1707 } 1708 } 1709 1710 /** 1711 * {@usesMathJax} 1712 * 1713 * <p>An RGB color space is an additive color space using the 1714 * {@link Model#RGB RGB} color model (a color is therefore represented 1715 * by a tuple of 3 numbers).</p> 1716 * 1717 * <p>A specific RGB color space is defined by the following properties:</p> 1718 * <ul> 1719 * <li>Three chromaticities of the red, green and blue primaries, which 1720 * define the gamut of the color space.</li> 1721 * <li>A white point chromaticity that defines the stimulus to which 1722 * color space values are normalized (also just called "white").</li> 1723 * <li>An opto-electronic transfer function, also called opto-electronic 1724 * conversion function or often, and approximately, gamma function.</li> 1725 * <li>An electro-optical transfer function, also called electo-optical 1726 * conversion function or often, and approximately, gamma function.</li> 1727 * <li>A range of valid RGB values (most commonly \([0..1]\)).</li> 1728 * </ul> 1729 * 1730 * <p>The most commonly used RGB color space is {@link Named#SRGB sRGB}.</p> 1731 * 1732 * <h3>Primaries and white point chromaticities</h3> 1733 * <p>In this implementation, the chromaticity of the primaries and the white 1734 * point of an RGB color space is defined in the CIE xyY color space. This 1735 * color space separates the chromaticity of a color, the x and y components, 1736 * and its luminance, the Y component. Since the primaries and the white 1737 * point have full brightness, the Y component is assumed to be 1 and only 1738 * the x and y components are needed to encode them.</p> 1739 * <p>For convenience, this implementation also allows to define the 1740 * primaries and white point in the CIE XYZ space. The tristimulus XYZ values 1741 * are internally converted to xyY.</p> 1742 * 1743 * <h3>Transfer functions</h3> 1744 * <p>A transfer function is a color component conversion function, defined as 1745 * a single variable, monotonic mathematical function. It is applied to each 1746 * individual component of a color. They are used to perform the mapping 1747 * between linear tristimulus values and non-linear electronic signal value.</p> 1748 * <p>The <em>opto-electronic transfer function</em> (OETF or OECF) encodes 1749 * tristimulus values in a scene to a non-linear electronic signal value. 1750 * An OETF is often expressed as a power function with an exponent between 1751 * 0.38 and 0.55 (the reciprocal of 1.8 to 2.6).</p> 1752 * <p>The <em>electro-optical transfer function</em> (EOTF or EOCF) decodes 1753 * a non-linear electronic signal value to a tristimulus value at the display. 1754 * An EOTF is often expressed as a power function with an exponent between 1755 * 1.8 and 2.6.</p> 1756 * <p>Transfer functions are used as a compression scheme. For instance, 1757 * linear sRGB values would normally require 11 to 12 bits of precision to 1758 * store all values that can be perceived by the human eye. When encoding 1759 * sRGB values using the appropriate OETF (see {@link Named#SRGB sRGB} for 1760 * an exact mathematical description of that OETF), the values can be 1761 * compressed to only 8 bits precision.</p> 1762 * <p>When manipulating RGB values, particularly sRGB values, it is safe 1763 * to assume that these values have been encoded with the appropriate 1764 * OETF (unless noted otherwise). Encoded values are often said to be in 1765 * "gamma space". They are therefore defined in a non-linear space. This 1766 * in turns means that any linear operation applied to these values is 1767 * going to yield mathematically incorrect results (any linear interpolation 1768 * such as gradient generation for instance, most image processing functions 1769 * such as blurs, etc.).</p> 1770 * <p>To properly process encoded RGB values you must first apply the 1771 * EOTF to decode the value into linear space. After processing, the RGB 1772 * value must be encoded back to non-linear ("gamma") space. Here is a 1773 * formal description of the process, where \(f\) is the processing 1774 * function to apply:</p> 1775 * 1776 * $$RGB_{out} = OETF(f(EOTF(RGB_{in})))$$ 1777 * 1778 * <p class="note">Some RGB color spaces, such as {@link Named#ACES} and 1779 * {@link Named#LINEAR_EXTENDED_SRGB scRGB}, are said to be linear because 1780 * their transfer functions are the identity function: \(f(x) = x\). 1781 * If the source and/or destination are known to be linear, it is not 1782 * necessary to invoke the transfer functions.</p> 1783 * 1784 * <h3>Range</h3> 1785 * <p>Most RGB color spaces allow RGB values in the range \([0..1]\). There 1786 * are however a few RGB color spaces that allow much larger ranges. For 1787 * instance, {@link Named#EXTENDED_SRGB scRGB} is used to manipulate the 1788 * range \([-0.5..7.5]\) while {@link Named#ACES ACES} can be used throughout 1789 * the range \([-65504, 65504]\).</p> 1790 * 1791 * <h3>Converting between RGB color spaces</h3> 1792 * <p>Conversion between two color spaces is achieved by using an intermediate 1793 * color space called the profile connection space (PCS). The PCS used by 1794 * this implementation is CIE XYZ. The conversion operation is defined 1795 * as such:</p> 1796 * 1797 * $$RGB_{out} = OETF(T_{dst}^{-1} \cdot T_{src} \cdot EOTF(RGB_{in}))$$ 1798 * 1799 * <p>Where \(T_{src}\) is the {@link #getTransform() RGB to XYZ transform} 1800 * of the source color space and \(T_{dst}^{-1}\) the {@link #getInverseTransform() 1801 * XYZ to RGB transform} of the destination color space.</p> 1802 * <p>Many RGB color spaces commonly used with electronic devices use the 1803 * standard illuminant {@link #ILLUMINANT_D65 D65}. Care must be take however 1804 * when converting between two RGB color spaces if their white points do not 1805 * match. This can be achieved by either calling 1806 * {@link #adapt(ColorSpace, float[])} to adapt one or both color spaces to 1807 * a single common white point. This can be achieved automatically by calling 1808 * {@link ColorSpace#connect(ColorSpace, ColorSpace)}, which also handles 1809 * non-RGB color spaces.</p> 1810 * <p>To learn more about the white point adaptation process, refer to the 1811 * documentation of {@link Adaptation}.</p> 1812 */ 1813 public static class Rgb extends ColorSpace { 1814 @NonNull private final float[] mWhitePoint; 1815 @NonNull private final float[] mPrimaries; 1816 @NonNull private final float[] mTransform; 1817 @NonNull private final float[] mInverseTransform; 1818 1819 @NonNull private final boolean mIsWideGamut; 1820 @NonNull private final boolean mIsSrgb; 1821 1822 @NonNull private final DoubleUnaryOperator mOetf; 1823 @NonNull private final DoubleUnaryOperator mEotf; 1824 @NonNull private final DoubleUnaryOperator mClampedOetf; 1825 @NonNull private final DoubleUnaryOperator mClampedEotf; 1826 1827 private final float mMin; 1828 private final float mMax; 1829 1830 /** 1831 * <p>Creates a new RGB color space using a 3x3 column-major transform matrix. 1832 * The transform matrix must convert from the RGB space to the profile connection 1833 * space CIE XYZ.</p> 1834 * 1835 * <p class="note">The range of the color space is imposed to be \([0..1]\).</p> 1836 * 1837 * @param name Name of the color space, cannot be null, its length must be >= 1 1838 * @param toXYZ 3x3 column-major transform matrix from RGB to the profile 1839 * connection space CIE XYZ as an array of 9 floats, cannot be null 1840 * @param oetf Opto-electronic transfer function, cannot be null 1841 * @param eotf Electro-optical transfer function, cannot be null 1842 * 1843 * @throws IllegalArgumentException If any of the following conditions is met: 1844 * <ul> 1845 * <li>The name is null or has a length of 0.</li> 1846 * <li>The OETF is null or the EOTF is null.</li> 1847 * <li>The minimum valid value is >= the maximum valid value.</li> 1848 * </ul> 1849 * 1850 * @see #get(Named) 1851 */ 1852 public Rgb( 1853 @NonNull @Size(min = 1) String name, 1854 @NonNull @Size(9) float[] toXYZ, 1855 @NonNull DoubleUnaryOperator oetf, 1856 @NonNull DoubleUnaryOperator eotf) { 1857 this(name,computePrimaries(toXYZ, eotf), computeWhitePoint(toXYZ, eotf), 1858 oetf, eotf, 0.0f, 1.0f, MIN_ID); 1859 } 1860 1861 /** 1862 * <p>Creates a new RGB color space using a specified set of primaries 1863 * and a specified white point.</p> 1864 * 1865 * <p>The primaries and white point can be specified in the CIE xyY space 1866 * or in CIE XYZ. The length of the arrays depends on the chosen space:</p> 1867 * 1868 * <table summary="Parameters length"> 1869 * <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr> 1870 * <tr><td>xyY</td><td>6</td><td>2</td></tr> 1871 * <tr><td>XYZ</td><td>9</td><td>3</td></tr> 1872 * </table> 1873 * 1874 * <p>When the primaries and/or white point are specified in xyY, the Y component 1875 * does not need to be specified and is assumed to be 1.0. Only the xy components 1876 * are required.</p> 1877 * 1878 * <p class="note">The ID, areturned by {@link #getId()}, of an object created by 1879 * this constructor is always {@link #MIN_ID}.</p> 1880 * 1881 * @param name Name of the color space, cannot be null, its length must be >= 1 1882 * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats 1883 * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats 1884 * @param oetf Opto-electronic transfer function, cannot be null 1885 * @param eotf Electro-optical transfer function, cannot be null 1886 * @param min The minimum valid value in this color space's RGB range 1887 * @param max The maximum valid value in this color space's RGB range 1888 * 1889 * @throws IllegalArgumentException <p>If any of the following conditions is met:</p> 1890 * <ul> 1891 * <li>The name is null or has a length of 0.</li> 1892 * <li>The primaries array is null or has a length that is neither 6 or 9.</li> 1893 * <li>The white point array is null or has a length that is neither 2 or 3.</li> 1894 * <li>The OETF is null or the EOTF is null.</li> 1895 * <li>The minimum valid value is >= the maximum valid value.</li> 1896 * </ul> 1897 * 1898 * @see #get(Named) 1899 */ 1900 public Rgb( 1901 @NonNull @Size(min = 1) String name, 1902 @NonNull @Size(min = 6, max = 9) float[] primaries, 1903 @NonNull @Size(min = 2, max = 3) float[] whitePoint, 1904 @NonNull DoubleUnaryOperator oetf, 1905 @NonNull DoubleUnaryOperator eotf, 1906 float min, 1907 float max) { 1908 this(name, primaries, whitePoint, oetf, eotf, min, max, MIN_ID); 1909 } 1910 1911 /** 1912 * <p>Creates a new RGB color space using a specified set of primaries 1913 * and a specified white point.</p> 1914 * 1915 * <p>The primaries and white point can be specified in the CIE xyY space 1916 * or in CIE XYZ. The length of the arrays depends on the chosen space:</p> 1917 * 1918 * <table summary="Parameters length"> 1919 * <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr> 1920 * <tr><td>xyY</td><td>6</td><td>2</td></tr> 1921 * <tr><td>XYZ</td><td>9</td><td>3</td></tr> 1922 * </table> 1923 * 1924 * <p>When the primaries and/or white point are specified in xyY, the Y component 1925 * does not need to be specified and is assumed to be 1.0. Only the xy components 1926 * are required.</p> 1927 * 1928 * @param name Name of the color space, cannot be null, its length must be >= 1 1929 * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats 1930 * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats 1931 * @param oetf Opto-electronic transfer function, cannot be null 1932 * @param eotf Electro-optical transfer function, cannot be null 1933 * @param min The minimum valid value in this color space's RGB range 1934 * @param max The maximum valid value in this color space's RGB range 1935 * @param id ID of this color space as an integer between {@link #MIN_ID} and {@link #MAX_ID} 1936 * 1937 * @throws IllegalArgumentException If any of the following conditions is met: 1938 * <ul> 1939 * <li>The name is null or has a length of 0.</li> 1940 * <li>The primaries array is null or has a length that is neither 6 or 9.</li> 1941 * <li>The white point array is null or has a length that is neither 2 or 3.</li> 1942 * <li>The OETF is null or the EOTF is null.</li> 1943 * <li>The minimum valid value is >= the maximum valid value.</li> 1944 * <li>The ID is not between {@link #MIN_ID} and {@link #MAX_ID}.</li> 1945 * </ul> 1946 * 1947 * @see #get(Named) 1948 */ 1949 private Rgb( 1950 @NonNull @Size(min = 1) String name, 1951 @NonNull @Size(min = 6, max = 9) float[] primaries, 1952 @NonNull @Size(min = 2, max = 3) float[] whitePoint, 1953 @NonNull DoubleUnaryOperator oetf, 1954 @NonNull DoubleUnaryOperator eotf, 1955 float min, 1956 float max, 1957 @IntRange(from = MIN_ID, to = MAX_ID) int id) { 1958 1959 super(name, Model.RGB, id); 1960 1961 if (primaries == null || (primaries.length != 6 && primaries.length != 9)) { 1962 throw new IllegalArgumentException("The color space's primaries must be " + 1963 "defined as an array of 6 floats in xyY or 9 floats in XYZ"); 1964 } 1965 1966 if (whitePoint == null || (whitePoint.length != 2 && whitePoint.length != 3)) { 1967 throw new IllegalArgumentException("The color space's white point must be " + 1968 "defined as an array of 2 floats in xyY or 3 float in XYZ"); 1969 } 1970 1971 if (oetf == null || eotf == null) { 1972 throw new IllegalArgumentException("The transfer functions of a color space " + 1973 "cannot be null"); 1974 } 1975 1976 if (min >= max) { 1977 throw new IllegalArgumentException("Invalid range: min=" + min + ", max=" + max + 1978 "; min must be strictly < max"); 1979 } 1980 1981 mWhitePoint = xyWhitePoint(whitePoint); 1982 mPrimaries = xyPrimaries(primaries); 1983 1984 mTransform = computeXYZMatrix(mPrimaries, mWhitePoint); 1985 mInverseTransform = inverse3x3(mTransform); 1986 1987 mOetf = oetf; 1988 mEotf = eotf; 1989 1990 mMin = min; 1991 mMax = max; 1992 1993 DoubleUnaryOperator clamp = this::clamp; 1994 mClampedOetf = oetf.andThen(clamp); 1995 mClampedEotf = clamp.andThen(eotf); 1996 1997 // A color space is wide-gamut if its area is >90% of NTSC 1953 and 1998 // if it entirely contains the Color space definition in xyY 1999 mIsWideGamut = isWideGamut(primaries, min, max); 2000 mIsSrgb = isSrgb(primaries, whitePoint, oetf, eotf, min, max, id); 2001 } 2002 2003 /** 2004 * Creates a copy of the specified color space with a new transform. 2005 * 2006 * @param colorSpace The color space to create a copy of 2007 */ 2008 private Rgb(Rgb colorSpace, 2009 @NonNull @Size(9) float[] transform, 2010 @NonNull @Size(min = 2, max = 3) float[] whitePoint) { 2011 super(colorSpace.getName(), Model.RGB, -1); 2012 2013 mWhitePoint = xyWhitePoint(whitePoint); 2014 mPrimaries = colorSpace.mPrimaries; 2015 2016 mTransform = transform; 2017 mInverseTransform = inverse3x3(transform); 2018 2019 mMin = colorSpace.mMin; 2020 mMax = colorSpace.mMax; 2021 2022 mOetf = colorSpace.mOetf; 2023 mEotf = colorSpace.mEotf; 2024 2025 mClampedOetf = colorSpace.mClampedOetf; 2026 mClampedEotf = colorSpace.mClampedEotf; 2027 2028 mIsWideGamut = colorSpace.mIsWideGamut; 2029 mIsSrgb = colorSpace.mIsSrgb; 2030 } 2031 2032 /** 2033 * Copies the non-adapted CIE xyY white point of this color space in 2034 * specified array. The Y component is assumed to be 1 and is therefore 2035 * not copied into the destination. The x and y components are written 2036 * in the array at positions 0 and 1 respectively. 2037 * 2038 * @param whitePoint The destination array, cannot be null, its length 2039 * must be >= 2 2040 * 2041 * @return The destination array passed as a parameter 2042 * 2043 * @see #getWhitePoint(float[]) 2044 */ 2045 @NonNull 2046 @Size(min = 2) 2047 public float[] getWhitePoint(@NonNull @Size(min = 2) float[] whitePoint) { 2048 whitePoint[0] = mWhitePoint[0]; 2049 whitePoint[1] = mWhitePoint[1]; 2050 return whitePoint; 2051 } 2052 2053 /** 2054 * Returns the non-adapted CIE xyY white point of this color space as 2055 * a new array of 2 floats. The Y component is assumed to be 1 and is 2056 * therefore not copied into the destination. The x and y components 2057 * are written in the array at positions 0 and 1 respectively. 2058 * 2059 * @return A new non-null array of 2 floats 2060 * 2061 * @see #getWhitePoint() 2062 */ 2063 @NonNull 2064 @Size(2) 2065 public float[] getWhitePoint() { 2066 return Arrays.copyOf(mWhitePoint, mWhitePoint.length); 2067 } 2068 2069 /** 2070 * Copies the primaries of this color space in specified array. The Y 2071 * component is assumed to be 1 and is therefore not copied into the 2072 * destination. The x and y components of the first primary are written 2073 * in the array at positions 0 and 1 respectively. 2074 * 2075 * @param primaries The destination array, cannot be null, its length 2076 * must be >= 6 2077 * 2078 * @return The destination array passed as a parameter 2079 * 2080 * @see #getPrimaries(float[]) 2081 */ 2082 @NonNull 2083 @Size(min = 6) 2084 public float[] getPrimaries(@NonNull @Size(min = 6) float[] primaries) { 2085 System.arraycopy(mPrimaries, 0, primaries, 0, mPrimaries.length); 2086 return primaries; 2087 } 2088 2089 /** 2090 * Returns the primaries of this color space as a new array of 6 floats. 2091 * The Y component is assumed to be 1 and is therefore not copied into 2092 * the destination. The x and y components of the first primary are 2093 * written in the array at positions 0 and 1 respectively. 2094 * 2095 * @return A new non-null array of 2 floats 2096 * 2097 * @see #getWhitePoint() 2098 */ 2099 @NonNull 2100 @Size(6) 2101 public float[] getPrimaries() { 2102 return Arrays.copyOf(mPrimaries, mPrimaries.length); 2103 } 2104 2105 /** 2106 * <p>Copies the transform of this color space in specified array. The 2107 * transform is used to convert from RGB to XYZ (with the same white 2108 * point as this color space). To connect color spaces, you must first 2109 * {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them to the 2110 * same white point.</p> 2111 * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)} 2112 * to convert between color spaces.</p> 2113 * 2114 * @param transform The destination array, cannot be null, its length 2115 * must be >= 9 2116 * 2117 * @return The destination array passed as a parameter 2118 * 2119 * @see #getInverseTransform() 2120 */ 2121 @NonNull 2122 @Size(min = 9) 2123 public float[] getTransform(@NonNull @Size(min = 9) float[] transform) { 2124 System.arraycopy(mTransform, 0, transform, 0, mTransform.length); 2125 return transform; 2126 } 2127 2128 /** 2129 * <p>Returns the transform of this color space as a new array. The 2130 * transform is used to convert from RGB to XYZ (with the same white 2131 * point as this color space). To connect color spaces, you must first 2132 * {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them to the 2133 * same white point.</p> 2134 * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)} 2135 * to convert between color spaces.</p> 2136 * 2137 * @return A new array of 9 floats 2138 * 2139 * @see #getInverseTransform(float[]) 2140 */ 2141 @NonNull 2142 @Size(9) 2143 public float[] getTransform() { 2144 return Arrays.copyOf(mTransform, mTransform.length); 2145 } 2146 2147 /** 2148 * <p>Copies the inverse transform of this color space in specified array. 2149 * The inverse transform is used to convert from XYZ to RGB (with the 2150 * same white point as this color space). To connect color spaces, you 2151 * must first {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them 2152 * to the same white point.</p> 2153 * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)} 2154 * to convert between color spaces.</p> 2155 * 2156 * @param inverseTransform The destination array, cannot be null, its length 2157 * must be >= 9 2158 * 2159 * @return The destination array passed as a parameter 2160 * 2161 * @see #getTransform() 2162 */ 2163 @NonNull 2164 @Size(min = 9) 2165 public float[] getInverseTransform(@NonNull @Size(min = 9) float[] inverseTransform) { 2166 System.arraycopy(mInverseTransform, 0, inverseTransform, 0, mInverseTransform.length); 2167 return inverseTransform; 2168 } 2169 2170 /** 2171 * <p>Returns the inverse transform of this color space as a new array. 2172 * The inverse transform is used to convert from XYZ to RGB (with the 2173 * same white point as this color space). To connect color spaces, you 2174 * must first {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them 2175 * to the same white point.</p> 2176 * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)} 2177 * to convert between color spaces.</p> 2178 * 2179 * @return A new array of 9 floats 2180 * 2181 * @see #getTransform(float[]) 2182 */ 2183 @NonNull 2184 @Size(9) 2185 public float[] getInverseTransform() { 2186 return Arrays.copyOf(mInverseTransform, mInverseTransform.length); 2187 } 2188 2189 /** 2190 * <p>Returns the opto-electronic transfer function (OETF) of this color space. 2191 * The inverse function is the electro-optical transfer function (EOTF) returned 2192 * by {@link #getEotf()}. These functions are defined to satisfy the following 2193 * equality for \(x \in [0..1]\):</p> 2194 * 2195 * $$OETF(EOTF(x)) = EOTF(OETF(x)) = x$$ 2196 * 2197 * <p>For RGB colors, this function can be used to convert from linear space 2198 * to "gamma space" (gamma encoded). The terms gamma space and gamma encoded 2199 * are frequently used because many OETFs can be closely approximated using 2200 * a simple power function of the form \(x^{\frac{1}{\gamma}}\) (the 2201 * approximation of the {@link Named#SRGB sRGB} EOTF uses \(\gamma=2.2\) 2202 * for instance).</p> 2203 * 2204 * @return A transfer function that converts from linear space to "gamma space" 2205 * 2206 * @see #getEotf() 2207 */ 2208 @NonNull 2209 public DoubleUnaryOperator getOetf() { 2210 return mOetf; 2211 } 2212 2213 /** 2214 * <p>Returns the electro-optical transfer function (EOTF) of this color space. 2215 * The inverse function is the opto-electronic transfer function (OETF) 2216 * returned by {@link #getOetf()}. These functions are defined to satisfy the 2217 * following equality for \(x \in [0..1]\):</p> 2218 * 2219 * $$OETF(EOTF(x)) = EOTF(OETF(x)) = x$$ 2220 * 2221 * <p>For RGB colors, this function can be used to convert from "gamma space" 2222 * (gamma encoded) to linear space. The terms gamma space and gamma encoded 2223 * are frequently used because many EOTFs can be closely approximated using 2224 * a simple power function of the form \(x^\gamma\) (the approximation of the 2225 * {@link Named#SRGB sRGB} EOTF uses \(\gamma=2.2\) for instance).</p> 2226 * 2227 * @return A transfer function that converts from "gamma space" to linear space 2228 * 2229 * @see #getOetf() 2230 */ 2231 @NonNull 2232 public DoubleUnaryOperator getEotf() { 2233 return mEotf; 2234 } 2235 2236 @Override 2237 public boolean isSrgb() { 2238 return mIsSrgb; 2239 } 2240 2241 @Override 2242 public boolean isWideGamut() { 2243 return mIsWideGamut; 2244 } 2245 2246 @Override 2247 public float getMinValue(int component) { 2248 return mMin; 2249 } 2250 2251 @Override 2252 public float getMaxValue(int component) { 2253 return mMax; 2254 } 2255 2256 /** 2257 * <p>Decodes an RGB value to linear space. This is achieved by 2258 * applying this color space's electro-optical transfer function 2259 * to the supplied values.</p> 2260 * 2261 * <p>Refer to the documentation of {@link ColorSpace.Rgb} for 2262 * more information about transfer functions and their use for 2263 * encoding and decoding RGB values.</p> 2264 * 2265 * @param r The red component to decode to linear space 2266 * @param g The green component to decode to linear space 2267 * @param b The blue component to decode to linear space 2268 * @return A new array of 3 floats containing linear RGB values 2269 * 2270 * @see #toLinear(float[]) 2271 * @see #fromLinear(float, float, float) 2272 */ 2273 @NonNull 2274 @Size(3) 2275 public float[] toLinear(float r, float g, float b) { 2276 return toLinear(new float[] { r, g, b }); 2277 } 2278 2279 /** 2280 * <p>Decodes an RGB value to linear space. This is achieved by 2281 * applying this color space's electro-optical transfer function 2282 * to the first 3 values of the supplied array. The result is 2283 * stored back in the input array.</p> 2284 * 2285 * <p>Refer to the documentation of {@link ColorSpace.Rgb} for 2286 * more information about transfer functions and their use for 2287 * encoding and decoding RGB values.</p> 2288 * 2289 * @param v A non-null array of non-linear RGB values, its length 2290 * must be at least 3 2291 * @return The specified array 2292 * 2293 * @see #toLinear(float, float, float) 2294 * @see #fromLinear(float[]) 2295 */ 2296 @NonNull 2297 @Size(min = 3) 2298 public float[] toLinear(@NonNull @Size(min = 3) float[] v) { 2299 v[0] = (float) mClampedEotf.applyAsDouble(v[0]); 2300 v[1] = (float) mClampedEotf.applyAsDouble(v[1]); 2301 v[2] = (float) mClampedEotf.applyAsDouble(v[2]); 2302 return v; 2303 } 2304 2305 /** 2306 * <p>Encodes an RGB value from linear space to this color space's 2307 * "gamma space". This is achieved by applying this color space's 2308 * opto-electronic transfer function to the supplied values.</p> 2309 * 2310 * <p>Refer to the documentation of {@link ColorSpace.Rgb} for 2311 * more information about transfer functions and their use for 2312 * encoding and decoding RGB values.</p> 2313 * 2314 * @param r The red component to encode from linear space 2315 * @param g The green component to encode from linear space 2316 * @param b The blue component to encode from linear space 2317 * @return A new array of 3 floats containing non-linear RGB values 2318 * 2319 * @see #fromLinear(float[]) 2320 * @see #toLinear(float, float, float) 2321 */ 2322 @NonNull 2323 @Size(3) 2324 public float[] fromLinear(float r, float g, float b) { 2325 return fromLinear(new float[] { r, g, b }); 2326 } 2327 2328 /** 2329 * <p>Encodes an RGB value from linear space to this color space's 2330 * "gamma space". This is achieved by applying this color space's 2331 * opto-electronic transfer function to the first 3 values of the 2332 * supplied array. The result is stored back in the input array.</p> 2333 * 2334 * <p>Refer to the documentation of {@link ColorSpace.Rgb} for 2335 * more information about transfer functions and their use for 2336 * encoding and decoding RGB values.</p> 2337 * 2338 * @param v A non-null array of linear RGB values, its length 2339 * must be at least 3 2340 * @return A new array of 3 floats containing non-linear RGB values 2341 * 2342 * @see #fromLinear(float[]) 2343 * @see #toLinear(float, float, float) 2344 */ 2345 @NonNull 2346 @Size(min = 3) 2347 public float[] fromLinear(@NonNull @Size(min = 3) float[] v) { 2348 v[0] = (float) mClampedOetf.applyAsDouble(v[0]); 2349 v[1] = (float) mClampedOetf.applyAsDouble(v[1]); 2350 v[2] = (float) mClampedOetf.applyAsDouble(v[2]); 2351 return v; 2352 } 2353 2354 @Override 2355 @NonNull 2356 @Size(min = 3) 2357 public float[] toXyz(@NonNull @Size(min = 3) float[] v) { 2358 v[0] = (float) mClampedEotf.applyAsDouble(v[0]); 2359 v[1] = (float) mClampedEotf.applyAsDouble(v[1]); 2360 v[2] = (float) mClampedEotf.applyAsDouble(v[2]); 2361 return mul3x3Float3(mTransform, v); 2362 } 2363 2364 @Override 2365 @NonNull 2366 @Size(min = 3) 2367 public float[] fromXyz(@NonNull @Size(min = 3) float[] v) { 2368 mul3x3Float3(mInverseTransform, v); 2369 v[0] = (float) mClampedOetf.applyAsDouble(v[0]); 2370 v[1] = (float) mClampedOetf.applyAsDouble(v[1]); 2371 v[2] = (float) mClampedOetf.applyAsDouble(v[2]); 2372 return v; 2373 } 2374 2375 private double clamp(double x) { 2376 return x < mMin ? mMin : x > mMax ? mMax : x; 2377 } 2378 2379 @Override 2380 public boolean equals(Object o) { 2381 if (this == o) return true; 2382 if (o == null || getClass() != o.getClass()) return false; 2383 if (!super.equals(o)) return false; 2384 2385 Rgb rgb = (Rgb) o; 2386 2387 if (Float.compare(rgb.mMin, mMin) != 0) return false; 2388 if (Float.compare(rgb.mMax, mMax) != 0) return false; 2389 if (!Arrays.equals(mWhitePoint, rgb.mWhitePoint)) return false; 2390 if (!Arrays.equals(mPrimaries, rgb.mPrimaries)) return false; 2391 //noinspection SimplifiableIfStatement 2392 if (!mOetf.equals(rgb.mOetf)) return false; 2393 return mEotf.equals(rgb.mEotf); 2394 } 2395 2396 @Override 2397 public int hashCode() { 2398 int result = super.hashCode(); 2399 result = 31 * result + Arrays.hashCode(mWhitePoint); 2400 result = 31 * result + Arrays.hashCode(mPrimaries); 2401 result = 31 * result + mOetf.hashCode(); 2402 result = 31 * result + mEotf.hashCode(); 2403 result = 31 * result + (mMin != +0.0f ? Float.floatToIntBits(mMin) : 0); 2404 result = 31 * result + (mMax != +0.0f ? Float.floatToIntBits(mMax) : 0); 2405 return result; 2406 } 2407 2408 /** 2409 * Computes whether a color space is the sRGB color space or at least 2410 * a close approximation. 2411 * 2412 * @param primaries The set of RGB primaries in xyY as an array of 6 floats 2413 * @param whitePoint The white point in xyY as an array of 2 floats 2414 * @param OETF The opto-electronic transfer function 2415 * @param EOTF The electro-optical transfer function 2416 * @param min The minimum value of the color space's range 2417 * @param max The minimum value of the color space's range 2418 * @param id The ID of the color space 2419 * @return True if the color space can be considered as the sRGB color space 2420 * 2421 * @see #isSrgb() 2422 */ 2423 @SuppressWarnings("RedundantIfStatement") 2424 private static boolean isSrgb( 2425 @NonNull @Size(6) float[] primaries, 2426 @NonNull @Size(2) float[] whitePoint, 2427 @NonNull DoubleUnaryOperator OETF, 2428 @NonNull DoubleUnaryOperator EOTF, 2429 float min, 2430 float max, 2431 @IntRange(from = MIN_ID, to = MAX_ID) int id) { 2432 if (id == 0) return true; 2433 if (!compare(primaries, SRGB_PRIMARIES)) { 2434 return false; 2435 } 2436 if (!compare(whitePoint, ILLUMINANT_D65)) { 2437 return false; 2438 } 2439 if (OETF.applyAsDouble(0.5) < 0.5001) return false; 2440 if (EOTF.applyAsDouble(0.5) > 0.5001) return false; 2441 if (min != 0.0f) return false; 2442 if (max != 1.0f) return false; 2443 return true; 2444 } 2445 2446 /** 2447 * Computes whether the specified CIE xyY or XYZ primaries (with Y set to 1) form 2448 * a wide color gamut. A color gamut is considered wide if its area is > 90% 2449 * of the area of NTSC 1953 and if it contains the sRGB color gamut entirely. 2450 * If the conditions above are not met, the color space is considered as having 2451 * a wide color gamut if its range is larger than [0..1]. 2452 * 2453 * @param primaries RGB primaries in CIE xyY or XYZ as an array of 6 or 9 floats 2454 * @param min The minimum value of the color space's range 2455 * @param max The minimum value of the color space's range 2456 * @return True if the color space has a wide gamut, false otherwise 2457 * 2458 * @see #isWideGamut() 2459 * @see #area(float[]) 2460 */ 2461 private static boolean isWideGamut(@NonNull @Size(min = 6, max = 9) float[] primaries, 2462 float min, float max) { 2463 return (area(primaries) / area(NTSC_1953_PRIMARIES) > 0.9f && 2464 contains(primaries, SRGB_PRIMARIES)) || (min < 0.0f && max > 1.0f); 2465 } 2466 2467 /** 2468 * Computes the area of the triangle represented by a set of RGB primaries 2469 * in the CIE xyY space. 2470 * 2471 * @param primaries The triangle's vertices, as RGB primaries in an array of 6 floats 2472 * @return The area of the triangle 2473 * 2474 * @see #isWideGamut(float[], float, float) 2475 */ 2476 private static float area(@NonNull @Size(6) float[] primaries) { 2477 float Rx = primaries[0]; 2478 float Ry = primaries[1]; 2479 float Gx = primaries[2]; 2480 float Gy = primaries[3]; 2481 float Bx = primaries[4]; 2482 float By = primaries[5]; 2483 float det = Rx * Gy + Ry * Bx + Gx * By - Gy * Bx - Ry * Gx - Rx * By; 2484 float r = 0.5f * det; 2485 return r < 0.0f ? -r : r; 2486 } 2487 2488 /** 2489 * Computes the cross product of two 2D vectors. 2490 * 2491 * @param ax The x coordinate of the first vector 2492 * @param ay The y coordinate of the first vector 2493 * @param bx The x coordinate of the second vector 2494 * @param by The y coordinate of the second vector 2495 * @return The result of a x b 2496 */ 2497 private static float cross(float ax, float ay, float bx, float by) { 2498 return ax * by - ay * bx; 2499 } 2500 2501 /** 2502 * Decides whether a 2D triangle, identified by the 6 coordinates of its 2503 * 3 vertices, is contained within another 2D triangle, also identified 2504 * by the 6 coordinates of its 3 vertices. 2505 * 2506 * In the illustration below, we want to test whether the RGB triangle 2507 * is contained within the triangle XYZ formed by the 3 vertices at 2508 * the "+" locations. 2509 * 2510 * Y . 2511 * . + . 2512 * . .. 2513 * . . 2514 * . . 2515 * . G 2516 * * 2517 * * * 2518 * ** * 2519 * * ** 2520 * * * 2521 * ** * 2522 * * * 2523 * * * 2524 * ** * 2525 * * * 2526 * * ** 2527 * ** * R ... 2528 * * * ..... 2529 * * ***** .. 2530 * ** ************ . + 2531 * B * ************ . X 2532 * ......***** . 2533 * ...... . . 2534 * .. 2535 * + . 2536 * Z . 2537 * 2538 * RGB is contained within XYZ if all the following conditions are true 2539 * (with "x" the cross product operator): 2540 * 2541 * --> --> 2542 * GR x RX >= 0 2543 * --> --> 2544 * RX x BR >= 0 2545 * --> --> 2546 * RG x GY >= 0 2547 * --> --> 2548 * GY x RG >= 0 2549 * --> --> 2550 * RB x BZ >= 0 2551 * --> --> 2552 * BZ x GB >= 0 2553 * 2554 * @param p1 The enclosing triangle 2555 * @param p2 The enclosed triangle 2556 * @return True if the triangle p1 contains the triangle p2 2557 * 2558 * @see #isWideGamut(float[], float, float) 2559 */ 2560 @SuppressWarnings("RedundantIfStatement") 2561 private static boolean contains(@NonNull @Size(6) float[] p1, @NonNull @Size(6) float[] p2) { 2562 // Translate the vertices p1 in the coordinates system 2563 // with the vertices p2 as the origin 2564 float[] p0 = new float[] { 2565 p1[0] - p2[0], p1[1] - p2[1], 2566 p1[2] - p2[2], p1[3] - p2[3], 2567 p1[4] - p2[4], p1[5] - p2[5], 2568 }; 2569 // Check the first vertex of p1 2570 if (cross(p0[0], p0[1], p2[0] - p2[4], p2[1] - p2[5]) < 0 || 2571 cross(p2[0] - p2[2], p2[1] - p2[3], p0[0], p0[1]) < 0) { 2572 return false; 2573 } 2574 // Check the second vertex of p1 2575 if (cross(p0[2], p0[3], p2[2] - p2[0], p2[3] - p2[1]) < 0 || 2576 cross(p2[2] - p2[4], p2[3] - p2[5], p0[2], p0[3]) < 0) { 2577 return false; 2578 } 2579 // Check the third vertex of p1 2580 if (cross(p0[4], p0[5], p2[4] - p2[2], p2[5] - p2[3]) < 0 || 2581 cross(p2[4] - p2[0], p2[5] - p2[1], p0[4], p0[5]) < 0) { 2582 return false; 2583 } 2584 return true; 2585 } 2586 2587 /** 2588 * Computes the primaries of a color space identified only by 2589 * its RGB->XYZ transform matrix. This method assumes that the 2590 * range of the color space is [0..1]. 2591 * 2592 * @param toXYZ The color space's 3x3 transform matrix to XYZ 2593 * @param EOTF The color space's electro-optical transfer function 2594 * @return A new array of 6 floats containing the color space's 2595 * primaries in CIE xyY 2596 */ 2597 @NonNull 2598 @Size(6) 2599 private static float[] computePrimaries(@NonNull @Size(9) float[] toXYZ, 2600 DoubleUnaryOperator EOTF) { 2601 float one = (float) EOTF.applyAsDouble(1.0); 2602 float[] r = mul3x3Float3(toXYZ, new float[] { one, 0.0f, 0.0f }); 2603 float[] g = mul3x3Float3(toXYZ, new float[] { 0.0f, one, 0.0f }); 2604 float[] b = mul3x3Float3(toXYZ, new float[] { 0.0f, 0.0f, one }); 2605 2606 float rSum = r[0] + r[1] + r[2]; 2607 float gSum = g[0] + g[1] + g[2]; 2608 float bSum = b[0] + b[1] + b[2]; 2609 2610 return new float[] { 2611 r[0] / rSum, r[1] / rSum, 2612 g[0] / gSum, g[1] / gSum, 2613 b[0] / bSum, b[1] / bSum, 2614 }; 2615 } 2616 2617 /** 2618 * Computes the white point of a color space identified only by 2619 * its RGB->XYZ transform matrix. This method assumes that the 2620 * range of the color space is [0..1]. 2621 * 2622 * @param toXYZ The color space's 3x3 transform matrix to XYZ 2623 * @param EOTF The color space's electro-optical transfer function 2624 * @return A new array of 2 floats containing the color space's 2625 * white point in CIE xyY 2626 */ 2627 @NonNull 2628 @Size(2) 2629 private static float[] computeWhitePoint(@NonNull @Size(9) float[] toXYZ, 2630 @NonNull DoubleUnaryOperator EOTF) { 2631 float one = (float) EOTF.applyAsDouble(1.0); 2632 float[] w = mul3x3Float3(toXYZ, new float[] { one, one, one }); 2633 float sum = w[0] + w[1] + w[2]; 2634 return new float[] { w[0] / sum, w[1] / sum }; 2635 } 2636 2637 /** 2638 * Converts the specified RGB primaries point to xyY if needed. The primaries 2639 * can be specified as an array of 6 floats (in CIE xyY) or 9 floats 2640 * (in CIE XYZ). If no conversion is needed, the input array is copied. 2641 * 2642 * @param primaries The primaries in xyY or XYZ 2643 * @return A new array of 6 floats containing the primaries in xyY 2644 */ 2645 @NonNull 2646 @Size(2) 2647 private static float[] xyPrimaries(@NonNull @Size(min = 6, max = 9) float[] primaries) { 2648 float[] xyPrimaries = new float[6]; 2649 2650 // XYZ to xyY 2651 if (primaries.length == 9) { 2652 float sum; 2653 2654 sum = primaries[0] + primaries[1] + primaries[2]; 2655 xyPrimaries[0] = primaries[0] / sum; 2656 xyPrimaries[1] = primaries[1] / sum; 2657 2658 sum = primaries[3] + primaries[4] + primaries[5]; 2659 xyPrimaries[2] = primaries[3] / sum; 2660 xyPrimaries[3] = primaries[4] / sum; 2661 2662 sum = primaries[6] + primaries[7] + primaries[8]; 2663 xyPrimaries[4] = primaries[6] / sum; 2664 xyPrimaries[5] = primaries[7] / sum; 2665 } else { 2666 System.arraycopy(primaries, 0, xyPrimaries, 0, 6); 2667 } 2668 2669 return xyPrimaries; 2670 } 2671 2672 /** 2673 * Converts the specified white point to xyY if needed. The white point 2674 * can be specified as an array of 2 floats (in CIE xyY) or 3 floats 2675 * (in CIE XYZ). If no conversion is needed, the input array is copied. 2676 * 2677 * @param whitePoint The white point in xyY or XYZ 2678 * @return A new array of 2 floats containing the white point in xyY 2679 */ 2680 @NonNull 2681 @Size(2) 2682 private static float[] xyWhitePoint(@Size(min = 2, max = 3) float[] whitePoint) { 2683 float[] xyWhitePoint = new float[2]; 2684 2685 // XYZ to xyY 2686 if (whitePoint.length == 3) { 2687 float sum = whitePoint[0] + whitePoint[1] + whitePoint[2]; 2688 xyWhitePoint[0] = whitePoint[0] / sum; 2689 xyWhitePoint[1] = whitePoint[1] / sum; 2690 } else { 2691 System.arraycopy(whitePoint, 0, xyWhitePoint, 0, 2); 2692 } 2693 2694 return xyWhitePoint; 2695 } 2696 2697 /** 2698 * Computes the matrix that converts from RGB to XYZ based on RGB 2699 * primaries and a white point, both specified in the CIE xyY space. 2700 * The Y component of the primaries and white point is implied to be 1. 2701 * 2702 * @param primaries The RGB primaries in xyY, as an array of 6 floats 2703 * @param whitePoint The white point in xyY, as an array of 2 floats 2704 * @return A 3x3 matrix as a new array of 9 floats 2705 */ 2706 @NonNull 2707 @Size(9) 2708 private static float[] computeXYZMatrix( 2709 @NonNull @Size(6) float[] primaries, 2710 @NonNull @Size(2) float[] whitePoint) { 2711 float Rx = primaries[0]; 2712 float Ry = primaries[1]; 2713 float Gx = primaries[2]; 2714 float Gy = primaries[3]; 2715 float Bx = primaries[4]; 2716 float By = primaries[5]; 2717 float Wx = whitePoint[0]; 2718 float Wy = whitePoint[1]; 2719 2720 float oneRxRy = (1 - Rx) / Ry; 2721 float oneGxGy = (1 - Gx) / Gy; 2722 float oneBxBy = (1 - Bx) / By; 2723 float oneWxWy = (1 - Wx) / Wy; 2724 2725 float RxRy = Rx / Ry; 2726 float GxGy = Gx / Gy; 2727 float BxBy = Bx / By; 2728 float WxWy = Wx / Wy; 2729 2730 float BY = 2731 ((oneWxWy - oneRxRy) * (GxGy - RxRy) - (WxWy - RxRy) * (oneGxGy - oneRxRy)) / 2732 ((oneBxBy - oneRxRy) * (GxGy - RxRy) - (BxBy - RxRy) * (oneGxGy - oneRxRy)); 2733 float GY = (WxWy - RxRy - BY * (BxBy - RxRy)) / (GxGy - RxRy); 2734 float RY = 1 - GY - BY; 2735 2736 float RYRy = RY / Ry; 2737 float GYGy = GY / Gy; 2738 float BYBy = BY / By; 2739 2740 return new float[] { 2741 RYRy * Rx, RY, RYRy * (1 - Rx - Ry), 2742 GYGy * Gx, GY, GYGy * (1 - Gx - Gy), 2743 BYBy * Bx, BY, BYBy * (1 - Bx - By) 2744 }; 2745 } 2746 } 2747 2748 /** 2749 * {@usesMathJax} 2750 * 2751 * <p>A connector transforms colors from a source color space to a destination 2752 * color space.</p> 2753 * 2754 * <p>A source color space is connected to a destination color space using the 2755 * color transform \(C\) computed from their respective transforms noted 2756 * \(T_{src}\) and \(T_{dst}\) in the following equation:</p> 2757 * 2758 * $$C = T^{-1}_{dst} . T_{src}$$ 2759 * 2760 * <p>The transform \(C\) shown above is only valid when the source and 2761 * destination color spaces have the same profile connection space (PCS). 2762 * We know that instances of {@link ColorSpace} always use CIE XYZ as their 2763 * PCS but their white points might differ. When they do, we must perform 2764 * a chromatic adaptation of the color spaces' transforms. To do so, we 2765 * use the von Kries method described in the documentation of {@link Adaptation}, 2766 * using the CIE standard illuminant {@link ColorSpace#ILLUMINANT_D50 D50} 2767 * as the target white point.</p> 2768 * 2769 * <p>Example of conversion from {@link Named#SRGB sRGB} to 2770 * {@link Named#DCI_P3 DCI-P3}:</p> 2771 * 2772 * <pre class="prettyprint"> 2773 * ColorSpace.Connector connector = ColorSpace.connect( 2774 * ColorSpace.get(ColorSpace.Named.SRGB), 2775 * ColorSpace.get(ColorSpace.Named.DCI_P3)); 2776 * float[] p3 = connector.transform(1.0f, 0.0f, 0.0f); 2777 * // p3 contains { 0.9473, 0.2740, 0.2076 } 2778 * </pre> 2779 * 2780 * @see Adaptation 2781 * @see ColorSpace#adapt(ColorSpace, float[], Adaptation) 2782 * @see ColorSpace#adapt(ColorSpace, float[]) 2783 * @see ColorSpace#connect(ColorSpace, ColorSpace, RenderIntent) 2784 * @see ColorSpace#connect(ColorSpace, ColorSpace) 2785 * @see ColorSpace#connect(ColorSpace, RenderIntent) 2786 * @see ColorSpace#connect(ColorSpace) 2787 */ 2788 public static class Connector { 2789 @NonNull private final ColorSpace mSource; 2790 @NonNull private final ColorSpace mDestination; 2791 @NonNull private final ColorSpace mTransformSource; 2792 @NonNull private final ColorSpace mTransformDestination; 2793 @NonNull private final RenderIntent mIntent; 2794 @NonNull @Size(3) private final float[] mTransform; 2795 2796 /** 2797 * Creates a new connector between a source and a destination color space. 2798 * 2799 * @param source The source color space, cannot be null 2800 * @param destination The destination color space, cannot be null 2801 * @param intent The render intent to use when compressing gamuts 2802 */ 2803 Connector(@NonNull ColorSpace source, @NonNull ColorSpace destination, 2804 @NonNull RenderIntent intent) { 2805 this(source, destination, 2806 source.getModel() == Model.RGB ? adapt(source, ILLUMINANT_D50_XYZ) : source, 2807 destination.getModel() == Model.RGB ? 2808 adapt(destination, ILLUMINANT_D50_XYZ) : destination, 2809 intent, computeTransform(source, destination, intent)); 2810 } 2811 2812 /** 2813 * To connect between color spaces, we might need to use adapted transforms. 2814 * This should be transparent to the user so this constructor takes the 2815 * original source and destinations (returned by the getters), as well as 2816 * possibly adapted color spaces used by transform(). 2817 */ 2818 private Connector( 2819 @NonNull ColorSpace source, @NonNull ColorSpace destination, 2820 @NonNull ColorSpace transformSource, @NonNull ColorSpace transformDestination, 2821 @NonNull RenderIntent intent, @NonNull @Size(3) float[] transform) { 2822 mSource = source; 2823 mDestination = destination; 2824 mTransformSource = transformSource; 2825 mTransformDestination = transformDestination; 2826 mIntent = intent; 2827 mTransform = transform; 2828 } 2829 2830 /** 2831 * Computes an extra transform to apply in XYZ space depending on the 2832 * selected rendering intent. 2833 */ 2834 private static float[] computeTransform(@NonNull ColorSpace source, 2835 @NonNull ColorSpace destination, @NonNull RenderIntent intent) { 2836 if (intent != RenderIntent.ABSOLUTE) return null; 2837 2838 boolean srcRGB = source.getModel() == Model.RGB; 2839 boolean dstRGB = destination.getModel() == Model.RGB; 2840 2841 if (srcRGB && dstRGB) return null; 2842 2843 if (srcRGB || dstRGB) { 2844 ColorSpace.Rgb rgb = (ColorSpace.Rgb) (srcRGB ? source : destination); 2845 float[] srcXYZ = srcRGB ? xyYToXyz(rgb.mWhitePoint) : ILLUMINANT_D50_XYZ; 2846 float[] dstXYZ = dstRGB ? xyYToXyz(rgb.mWhitePoint) : ILLUMINANT_D50_XYZ; 2847 return new float[] { 2848 srcXYZ[0] / dstXYZ[0], 2849 srcXYZ[1] / dstXYZ[1], 2850 srcXYZ[2] / dstXYZ[2], 2851 }; 2852 } 2853 2854 return null; 2855 } 2856 2857 /** 2858 * Returns the source color space this connector will convert from. 2859 * 2860 * @return A non-null instance of {@link ColorSpace} 2861 * 2862 * @see #getDestination() 2863 */ 2864 @NonNull 2865 public ColorSpace getSource() { 2866 return mSource; 2867 } 2868 2869 /** 2870 * Returns the destination color space this connector will convert to. 2871 * 2872 * @return A non-null instance of {@link ColorSpace} 2873 * 2874 * @see #getSource() 2875 */ 2876 @NonNull 2877 public ColorSpace getDestination() { 2878 return mDestination; 2879 } 2880 2881 /** 2882 * Returns the render intent this connector will use when mapping the 2883 * source color space to the destination color space. 2884 * 2885 * @return A non-null {@link RenderIntent} 2886 * 2887 * @see RenderIntent 2888 */ 2889 public RenderIntent getIntent() { 2890 return mIntent; 2891 } 2892 2893 /** 2894 * <p>Transforms the specified color from the source color space 2895 * to a color in the destination color space. This convenience 2896 * method assumes a source color model with 3 components 2897 * (typically RGB). To transform from color models with more than 2898 * 3 components, such as {@link Model#CMYK CMYK}, use 2899 * {@link #transform(float[])} instead.</p> 2900 * 2901 * @param r The red component of the color to transform 2902 * @param g The green component of the color to transform 2903 * @param b The blue component of the color to transform 2904 * @return A new array of 3 floats containing the specified color 2905 * transformed from the source space to the destination space 2906 * 2907 * @see #transform(float[]) 2908 */ 2909 @NonNull 2910 @Size(3) 2911 public float[] transform(float r, float g, float b) { 2912 return transform(new float[] { r, g, b }); 2913 } 2914 2915 /** 2916 * <p>Transforms the specified color from the source color space 2917 * to a color in the destination color space.</p> 2918 * 2919 * @param v A non-null array of 3 floats containing the value to transform 2920 * and that will hold the result of the transform 2921 * @return The v array passed as a parameter, containing the specified color 2922 * transformed from the source space to the destination space 2923 * 2924 * @see #transform(float, float, float) 2925 */ 2926 @NonNull 2927 @Size(min = 3) 2928 public float[] transform(@NonNull @Size(min = 3) float[] v) { 2929 float[] xyz = mTransformSource.toXyz(v); 2930 if (mTransform != null) { 2931 xyz[0] *= mTransform[0]; 2932 xyz[1] *= mTransform[1]; 2933 xyz[2] *= mTransform[2]; 2934 } 2935 return mTransformDestination.fromXyz(xyz); 2936 } 2937 2938 /** 2939 * Optimized connector for RGB->RGB conversions. 2940 */ 2941 private static class RGB extends Connector { 2942 @NonNull private final ColorSpace.Rgb mSource; 2943 @NonNull private final ColorSpace.Rgb mDestination; 2944 @NonNull private final float[] mTransform; 2945 2946 RGB(@NonNull ColorSpace.Rgb source, 2947 @NonNull ColorSpace.Rgb destination, 2948 @NonNull RenderIntent intent) { 2949 super(source, destination, source, destination, intent, null); 2950 mSource = source; 2951 mDestination = destination; 2952 mTransform = computeTransform(source, destination, intent); 2953 } 2954 2955 @Override 2956 public float[] transform(@NonNull @Size(min = 3) float[] rgb) { 2957 rgb[0] = (float) mSource.mClampedEotf.applyAsDouble(rgb[0]); 2958 rgb[1] = (float) mSource.mClampedEotf.applyAsDouble(rgb[1]); 2959 rgb[2] = (float) mSource.mClampedEotf.applyAsDouble(rgb[2]); 2960 mul3x3Float3(mTransform, rgb); 2961 rgb[0] = (float) mDestination.mClampedOetf.applyAsDouble(rgb[0]); 2962 rgb[1] = (float) mDestination.mClampedOetf.applyAsDouble(rgb[1]); 2963 rgb[2] = (float) mDestination.mClampedOetf.applyAsDouble(rgb[2]); 2964 return rgb; 2965 } 2966 2967 /** 2968 * <p>Computes the color transform that connects two RGB color spaces.</p> 2969 * 2970 * <p>We can only connect color spaces if they use the same profile 2971 * connection space. We assume the connection space is always 2972 * CIE XYZ but we maye need to perform a chromatic adaptation to 2973 * match the white points. If an adaptation is needed, we use the 2974 * CIE standard illuminant D50. The unmatched color space is adapted 2975 * using the von Kries transform and the {@link Adaptation#BRADFORD} 2976 * matrix.</p> 2977 * 2978 * @param source The source color space, cannot be null 2979 * @param destination The destination color space, cannot be null 2980 * @param intent The render intent to use when compressing gamuts 2981 * @return An array of 9 floats containing the 3x3 matrix transform 2982 */ 2983 @NonNull 2984 @Size(9) 2985 private static float[] computeTransform( 2986 @NonNull ColorSpace.Rgb source, 2987 @NonNull ColorSpace.Rgb destination, 2988 @NonNull RenderIntent intent) { 2989 if (compare(source.mWhitePoint, destination.mWhitePoint)) { 2990 // RGB->RGB using the PCS of both color spaces since they have the same 2991 return mul3x3(destination.mInverseTransform, source.mTransform); 2992 } else { 2993 // RGB->RGB using CIE XYZ D50 as the PCS 2994 float[] transform = source.mTransform; 2995 float[] inverseTransform = destination.mInverseTransform; 2996 2997 float[] srcXYZ = xyYToXyz(source.mWhitePoint); 2998 float[] dstXYZ = xyYToXyz(destination.mWhitePoint); 2999 3000 if (!compare(source.mWhitePoint, ILLUMINANT_D50)) { 3001 float[] srcAdaptation = chromaticAdaptation( 3002 Adaptation.BRADFORD.mTransform, srcXYZ, 3003 Arrays.copyOf(ILLUMINANT_D50_XYZ, 3)); 3004 transform = mul3x3(srcAdaptation, source.mTransform); 3005 } 3006 3007 if (!compare(destination.mWhitePoint, ILLUMINANT_D50)) { 3008 float[] dstAdaptation = chromaticAdaptation( 3009 Adaptation.BRADFORD.mTransform, dstXYZ, 3010 Arrays.copyOf(ILLUMINANT_D50_XYZ, 3)); 3011 inverseTransform = inverse3x3(mul3x3(dstAdaptation, destination.mTransform)); 3012 } 3013 3014 if (intent == RenderIntent.ABSOLUTE) { 3015 transform = mul3x3Diag( 3016 new float[] { 3017 srcXYZ[0] / dstXYZ[0], 3018 srcXYZ[1] / dstXYZ[1], 3019 srcXYZ[2] / dstXYZ[2], 3020 }, transform); 3021 } 3022 3023 return mul3x3(inverseTransform, transform); 3024 } 3025 } 3026 } 3027 3028 /** 3029 * Returns the identity connector for a given color space. 3030 * 3031 * @param source The source and destination color space 3032 * @return A non-null connector that does not perform any transform 3033 * 3034 * @see ColorSpace#connect(ColorSpace, ColorSpace) 3035 */ 3036 static Connector identity(ColorSpace source) { 3037 return new Connector(source, source, RenderIntent.RELATIVE) { 3038 @Override 3039 public float[] transform(@NonNull @Size(min = 3) float[] v) { 3040 return v; 3041 } 3042 }; 3043 } 3044 } 3045} 3046