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