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