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