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