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