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