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