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