1 /* <lambda>null2 * Copyright 2019 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 @file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress") 18 19 package androidx.compose.ui.graphics.colorspace 20 21 import androidx.annotation.Size 22 import androidx.compose.ui.graphics.Color 23 import androidx.compose.ui.graphics.colorspace.ColorSpaces.transferHlgEotf 24 import androidx.compose.ui.graphics.colorspace.ColorSpaces.transferHlgOetf 25 import androidx.compose.ui.graphics.colorspace.ColorSpaces.transferSt2048Eotf 26 import androidx.compose.ui.graphics.colorspace.ColorSpaces.transferSt2048Oetf 27 import androidx.compose.ui.util.packFloats 28 import kotlin.math.abs 29 import kotlin.math.pow 30 31 /** 32 * An RGB color space is an additive color space using the [RGB][ColorModel.Rgb] color model (a 33 * color is therefore represented by a tuple of 3 numbers). 34 * 35 * A specific RGB color space is defined by the following properties: 36 * * Three chromaticities of the red, green and blue primaries, which define the gamut of the color 37 * space. 38 * * A white point chromaticity that defines the stimulus to which color space values are normalized 39 * (also just called "white"). 40 * * An opto-electronic transfer function, also called opto-electronic conversion function or often, 41 * and approximately, gamma function. 42 * * An electro-optical transfer function, also called electo-optical conversion function or often, 43 * and approximately, gamma function. 44 * * A range of valid RGB values (most commonly `[0..1]`). 45 * 46 * The most commonly used RGB color space is [sRGB][ColorSpaces.Srgb]. 47 * 48 * ### Primaries and white point chromaticities 49 * 50 * In this implementation, the chromaticity of the primaries and the white point of an RGB color 51 * space is defined in the CIE xyY color space. This color space separates the chromaticity of a 52 * color, the x and y components, and its luminance, the Y component. Since the primaries and the 53 * white point have full brightness, the Y component is assumed to be 1 and only the x and y 54 * components are needed to encode them. 55 * 56 * For convenience, this implementation also allows to define the primaries and white point in the 57 * CIE XYZ space. The tristimulus XYZ values are internally converted to xyY. 58 * 59 * [sRGB primaries and white 60 * point](https://developer.android.com/reference/android/images/graphics/colorspace_srgb.png) 61 * 62 * ### Transfer functions 63 * 64 * A transfer function is a color component conversion function, defined as a single variable, 65 * monotonic mathematical function. It is applied to each individual component of a color. They are 66 * used to perform the mapping between linear tristimulus values and non-linear electronic signal 67 * value. 68 * 69 * The *opto-electronic transfer function* (OETF or OECF) encodes tristimulus values in a scene to a 70 * non-linear electronic signal value. An OETF is often expressed as a power function with an 71 * exponent between 0.38 and 0.55 (the reciprocal of 1.8 to 2.6). 72 * 73 * The *electro-optical transfer function* (EOTF or EOCF) decodes a non-linear electronic signal 74 * value to a tristimulus value at the display. An EOTF is often expressed as a power function with 75 * an exponent between 1.8 and 2.6. 76 * 77 * Transfer functions are used as a compression scheme. For instance, linear sRGB values would 78 * normally require 11 to 12 bits of precision to store all values that can be perceived by the 79 * human eye. When encoding sRGB values using the appropriate OETF (see [sRGB][ColorSpaces.Srgb] for 80 * an exact mathematical description of that OETF), the values can be compressed to only 8 bits 81 * precision. 82 * 83 * When manipulating RGB values, particularly sRGB values, it is safe to assume that these values 84 * have been encoded with the appropriate OETF (unless noted otherwise). Encoded values are often 85 * said to be in "gamma space". They are therefore defined in a non-linear space. This in turns 86 * means that any linear operation applied to these values is going to yield mathematically 87 * incorrect results (any linear interpolation such as gradient generation for instance, most image 88 * processing functions such as blurs, etc.). 89 * 90 * To properly process encoded RGB values you must first apply the EOTF to decode the value into 91 * linear space. After processing, the RGB value must be encoded back to non-linear ("gamma") space. 92 * Here is a formal description of the process, where `f` is the processing function to apply: 93 * 94 * [See RGB equation](https://developer.android.com/reference/android/graphics/ColorSpace.Rgb) 95 * 96 * If the transfer functions of the color space can be expressed as an ICC parametric curve as 97 * defined in ICC.1:2004-10, the numeric parameters can be retrieved from [transferParameters]. This 98 * can be useful to match color spaces for instance. 99 * 100 * Some RGB color spaces, such as [ColorSpaces.Aces] and [scRGB][ColorSpaces.LinearExtendedSrgb], 101 * are said to be linear because their transfer functions are the identity function: `f(x) = x`. If 102 * the source and/or destination are known to be linear, it is not necessary to invoke the transfer 103 * functions. 104 * 105 * ### Range 106 * 107 * Most RGB color spaces allow RGB values in the range `[0..1]`. There are however a few RGB color 108 * spaces that allow much larger ranges. For instance, [scRGB][ColorSpaces.ExtendedSrgb] is used to 109 * manipulate the range `[-0.5..7.5]` while [ACES][ColorSpaces.Aces] can be used throughout the 110 * range `[-65504, 65504]`. 111 * 112 * [Extended sRGB and its large 113 * range](https://developer.android.com/reference/android/images/graphics/colorspace_scrgb.png) 114 * 115 * ### Converting between RGB color spaces 116 * 117 * Conversion between two color spaces is achieved by using an intermediate color space called the 118 * profile connection space (PCS). The PCS used by this implementation is CIE XYZ. The conversion 119 * operation is defined as such: 120 * 121 * [See RGB equation](https://developer.android.com/reference/android/graphics/ColorSpace.Rgb) 122 * 123 * Where `Tsrc` is the [RGB to XYZ transform][getTransform] of the source color space and `Tdst^-1` 124 * the [XYZ to RGB transform][getInverseTransform] of the destination color space. 125 * 126 * Many RGB color spaces commonly used with electronic devices use the standard illuminant 127 * [D65][Illuminant.D65]. Care must be take however when converting between two RGB color spaces if 128 * their white points do not match. This can be achieved by either calling [adapt] to adapt one or 129 * both color spaces to a single common white point. This can be achieved automatically by calling 130 * [ColorSpace.connect], which also handles non-RGB color spaces. 131 * 132 * To learn more about the white point adaptation process, refer to the documentation of 133 * [Adaptation]. 134 */ 135 class Rgb 136 /** 137 * Creates a new RGB color space using a specified set of primaries and a specified white point. 138 * 139 * The primaries and white point can be specified in the CIE xyY space or in CIE XYZ. The length of 140 * the arrays depends on the chosen space: 141 * ``` 142 * | Spaces | Primaries length | White point length | 143 * |--------|------------------|--------------------| 144 * | xyY | 6 | 2 | 145 * | XYZ | 9 | 3 | 146 * ``` 147 * 148 * When the primaries and/or white point are specified in xyY, the Y component does not need to be 149 * specified and is assumed to be 1.0. Only the xy components are required. 150 * 151 * @param name Name of the color space, cannot be null, its length must be >= 1 152 * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats 153 * @param whitePoint Reference white as a [WhitePoint] 154 * @param transform Computed transform matrix that converts from RGB to XYZ, or `null` to compute it 155 * from `primaries` and `whitePoint`. 156 * @param oetf Opto-electronic transfer function, cannot be null 157 * @param eotf Electro-optical transfer function, cannot be null 158 * @param min The minimum valid value in this color space's RGB range 159 * @param max The maximum valid value in this color space's RGB range 160 * @param transferParameters Parameters for the transfer functions 161 * @param id ID of this color space as an integer between [ColorSpace.MinId] and [ColorSpace.MaxId] 162 * @throws IllegalArgumentException If any of the following conditions is met: 163 * * The name is null or has a length of 0. 164 * * The primaries array is null or has a length that is neither 6 or 9. 165 * * The white point array is null or has a length that is neither 2 or 3. 166 * * The OETF is null or the EOTF is null. 167 * * The minimum valid value is >= the maximum valid value. 168 * * The ID is not between [ColorSpace.MinId] and [ColorSpace.MaxId]. 169 */ 170 internal constructor( 171 name: String, 172 primaries: FloatArray, 173 val whitePoint: WhitePoint, 174 transform: FloatArray?, 175 oetf: DoubleFunction, 176 eotf: DoubleFunction, 177 private val min: Float, 178 private val max: Float, 179 /** 180 * Returns the parameters used by the [electro-optical][eotf] and [opto-electronic][oetf] 181 * transfer functions. If the transfer functions do not match the ICC parametric curves defined 182 * in ICC.1:2004-10 (section 10.15), this method returns null. 183 * 184 * See [TransferParameters] for a full description of the transfer functions. 185 * 186 * @return An instance of [TransferParameters] or null if this color space's transfer functions 187 * do not match the equation defined in [TransferParameters] 188 */ 189 val transferParameters: TransferParameters?, 190 id: Int 191 ) : ColorSpace(name, ColorModel.Rgb, id) { 192 193 internal val primaries: FloatArray 194 internal val transform: FloatArray 195 internal val inverseTransform: FloatArray 196 internal val oetfOrig = oetf 197 198 /** 199 * Returns the opto-electronic transfer function (OETF) of this color space. The inverse 200 * function is the electro-optical transfer function (EOTF) returned by [eotf]. These functions 201 * are defined to satisfy the following equality for x ∈ `[0..1]`: 202 * 203 * OETF(EOTF(x) = EOTF(OETF(x)) = x 204 * 205 * For RGB colors, this function can be used to convert from linear space to "gamma space" 206 * (gamma encoded). The terms gamma space and gamma encoded are frequently used because many 207 * OETFs can be closely approximated using a simple power function of the form x^γ (the 208 * approximation of the [sRGB][ColorSpaces.Srgb] OETF uses γ = 2.2 for instance). 209 * 210 * @return A transfer function that converts from linear space to "gamma space" 211 * @see eotf 212 * @see Rgb.transferParameters 213 */ 214 val oetf: (Double) -> Double = { x -> oetfOrig(x).coerceIn(min.toDouble(), max.toDouble()) } 215 216 internal val oetfFunc: DoubleFunction = DoubleFunction { x -> 217 oetfOrig(x).coerceIn(min.toDouble(), max.toDouble()) 218 } 219 220 internal val eotfOrig = eotf 221 222 /** 223 * Returns the electro-optical transfer function (EOTF) of this color space. The inverse 224 * function is the opto-electronic transfer function (OETF) returned by [oetf]. These functions 225 * are defined to satisfy the following equality for x in `[0..1]`: 226 * 227 * OETF(EOTF(x) = EOTF(OETF(x)) = x 228 * 229 * For RGB colors, this function can be used to convert from "gamma space" (gamma encoded) to 230 * linear space. The terms gamma space and gamma encoded are frequently used because many EOTFs 231 * can be closely approximated using a simple power function of the form x^γ (the approximation 232 * of the [sRGB][ColorSpaces.Srgb] EOTF uses γ = 2.2 for instance). 233 * 234 * @return A transfer function that converts from "gamma space" to linear space 235 * @see oetf 236 * @see Rgb.transferParameters 237 */ 238 val eotf: (Double) -> Double = { x -> eotfOrig(x.coerceIn(min.toDouble(), max.toDouble())) } 239 240 internal val eotfFunc = DoubleFunction { x -> 241 eotfOrig(x.coerceIn(min.toDouble(), max.toDouble())) 242 } 243 244 override val isWideGamut: Boolean 245 override val isSrgb: Boolean 246 247 init { 248 if (primaries.size != 6 && primaries.size != 9) { 249 throw IllegalArgumentException( 250 ("The color space's primaries must be " + 251 "defined as an array of 6 floats in xyY or 9 floats in XYZ") 252 ) 253 } 254 255 if (min >= max) { 256 throw IllegalArgumentException( 257 "Invalid range: min=$min, max=$max; min must be strictly < max" 258 ) 259 } 260 this.primaries = xyPrimaries(primaries) 261 262 if (transform == null) { 263 this.transform = computeXYZMatrix(this.primaries, this.whitePoint) 264 } else { 265 if (transform.size != 9) { 266 throw IllegalArgumentException( 267 "Transform must have 9 entries! Has ${transform.size}" 268 ) 269 } 270 this.transform = transform 271 } 272 inverseTransform = inverse3x3(this.transform) 273 274 // A color space is wide-gamut if its area is >90% of NTSC 1953 and 275 // if it entirely contains the Color space definition in xyY 276 isWideGamut = isWideGamut(this.primaries, min, max) 277 isSrgb = isSrgb(this.primaries, this.whitePoint, oetf, eotf, min, max, id) 278 } 279 280 /** 281 * Returns the primaries of this color space as a new array of 6 floats. The Y component is 282 * assumed to be 1 and is therefore not copied into the destination. The x and y components of 283 * the first primary are written in the array at positions 0 and 1 respectively. 284 * 285 * @return A new non-null array of 2 floats 286 * @see whitePoint 287 */ 288 @Size(6) fun getPrimaries(): FloatArray = primaries.copyOf() 289 290 /** 291 * Returns the transform of this color space as a new array. The transform is used to convert 292 * from RGB to XYZ (with the same white point as this color space). To connect color spaces, you 293 * must first [adapt][ColorSpace.adapt] them to the same white point. 294 * 295 * It is recommended to use [ColorSpace.connect] to convert between color spaces. 296 * 297 * @return A new array of 9 floats 298 * @see getInverseTransform 299 */ 300 @Size(9) fun getTransform(): FloatArray = transform.copyOf() 301 302 /** 303 * Returns the inverse transform of this color space as a new array. The inverse transform is 304 * used to convert from XYZ to RGB (with the same white point as this color space). To connect 305 * color spaces, you must first [adapt][ColorSpace.adapt] them to the same white point. 306 * 307 * It is recommended to use [ColorSpace.connect] to convert between color spaces. 308 * 309 * @return A new array of 9 floats 310 * @see getTransform 311 */ 312 @Size(9) fun getInverseTransform(): FloatArray = inverseTransform.copyOf() 313 314 /** 315 * Creates a new RGB color space using a 3x3 column-major transform matrix. The transform matrix 316 * must convert from the RGB space to the profile connection space CIE XYZ. 317 * 318 * The range of the color space is imposed to be `[0..1]`. 319 * 320 * @param name Name of the color space, cannot be null, its length must be >= 1 321 * @param toXYZ 3x3 column-major transform matrix from RGB to the profile connection space CIE 322 * XYZ as an array of 9 floats, cannot be null 323 * @param oetf Opto-electronic transfer function, cannot be null 324 * @param eotf Electro-optical transfer function, cannot be null 325 * @throws IllegalArgumentException If any of the following conditions is met: 326 * * The name is null or has a length of 0. 327 * * The OETF is null or the EOTF is null. 328 * * The minimum valid value is >= the maximum valid value. 329 */ 330 constructor( 331 @Size(min = 1) name: String, 332 @Size(9) toXYZ: FloatArray, 333 oetf: (Double) -> Double, 334 eotf: (Double) -> Double 335 ) : this( 336 name, 337 computePrimaries(toXYZ), 338 computeWhitePoint(toXYZ), 339 null, 340 DoubleFunction { x -> oetf(x) }, 341 DoubleFunction { x -> eotf(x) }, 342 0.0f, 343 1.0f, 344 null, 345 MinId 346 ) 347 348 /** 349 * Creates a new RGB color space using a specified set of primaries and a specified white point. 350 * 351 * The primaries and white point can be specified in the CIE xyY space or in CIE XYZ. The length 352 * of the arrays depends on the chosen space: 353 * ``` 354 * | Spaces | Primaries length | White point length | 355 * |--------|------------------|--------------------| 356 * | xyY | 6 | 2 | 357 * | XYZ | 9 | 3 | 358 * ``` 359 * 360 * When the primaries and/or white point are specified in xyY, the Y component does not need to 361 * be specified and is assumed to be 1.0. Only the xy components are required. 362 * 363 * @param name Name of the color space, cannot be null, its length must be >= 1 364 * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats 365 * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats 366 * @param oetf Opto-electronic transfer function, cannot be null 367 * @param eotf Electro-optical transfer function, cannot be null 368 * @param min The minimum valid value in this color space's RGB range 369 * @param max The maximum valid value in this color space's RGB range 370 * @throws IllegalArgumentException If any of the following conditions is met: 371 * * The name is null or has a length of 0. 372 * * The primaries array is null or has a length that is neither 6 or 9. 373 * * The white point array is null or has a length that is neither 2 or 3. 374 * * The OETF is null or the EOTF is null. 375 * * The minimum valid value is >= the maximum valid value. 376 */ 377 constructor( 378 @Size(min = 1) name: String, 379 @Size(min = 6, max = 9) primaries: FloatArray, 380 whitePoint: WhitePoint, 381 oetf: (Double) -> Double, 382 eotf: (Double) -> Double, 383 min: Float, 384 max: Float 385 ) : this( 386 name, 387 primaries, 388 whitePoint, 389 null, 390 DoubleFunction { x -> oetf(x) }, 391 DoubleFunction { x -> eotf(x) }, 392 min, 393 max, 394 null, 395 MinId 396 ) 397 398 /** 399 * Creates a new RGB color space using a 3x3 column-major transform matrix. The transform matrix 400 * must convert from the RGB space to the profile connection space CIE XYZ. 401 * 402 * The range of the color space is imposed to be `[0..1]`. 403 * 404 * @param name Name of the color space, cannot be null, its length must be >= 1 405 * @param toXYZ 3x3 column-major transform matrix from RGB to the profile connection space CIE 406 * XYZ as an array of 9 floats, cannot be null 407 * @param function Parameters for the transfer functions 408 * @throws IllegalArgumentException If any of the following conditions is met: 409 * * The name is null or has a length of 0. 410 * * Gamma is negative. 411 */ 412 constructor( 413 @Size(min = 1) name: String, 414 @Size(9) toXYZ: FloatArray, 415 function: TransferParameters 416 ) : this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ), function, MinId) 417 418 /** 419 * Creates a new RGB color space using a specified set of primaries and a specified white point. 420 * 421 * The primaries and white point can be specified in the CIE xyY space or in CIE XYZ. The length 422 * of the arrays depends on the chosen space: 423 * ``` 424 * | Spaces | Primaries length | White point length | 425 * |--------|------------------|--------------------| 426 * | xyY | 6 | 2 | 427 * | XYZ | 9 | 3 | 428 * ``` 429 * 430 * When the primaries and/or white point are specified in xyY, the Y component does not need to 431 * be specified and is assumed to be 1.0. Only the xy components are required. 432 * 433 * @param name Name of the color space, cannot be null, its length must be >= 1 434 * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats 435 * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats 436 * @param function Parameters for the transfer functions 437 * @throws IllegalArgumentException If any of the following conditions is met: 438 * * The name is null or has a length of 0. 439 * * The primaries array is null or has a length that is neither 6 or 9. 440 * * The white point array is null or has a length that is neither 2 or 3. 441 * * The transfer parameters are invalid. 442 */ 443 constructor( 444 @Size(min = 1) name: String, 445 @Size(min = 6, max = 9) primaries: FloatArray, 446 whitePoint: WhitePoint, 447 function: TransferParameters 448 ) : this(name, primaries, whitePoint, function, MinId) 449 450 /** 451 * Creates a new RGB color space using a specified set of primaries and a specified white point. 452 * 453 * The primaries and white point can be specified in the CIE xyY space or in CIE XYZ. The length 454 * of the arrays depends on the chosen space: 455 * ``` 456 * | Spaces | Primaries length | White point length | 457 * |--------|------------------|--------------------| 458 * | xyY | 6 | 2 | 459 * | XYZ | 9 | 3 | 460 * ``` 461 * 462 * When the primaries and/or white point are specified in xyY, the Y component does not need to 463 * be specified and is assumed to be 1.0. Only the xy components are required. 464 * 465 * @param name Name of the color space, cannot be null, its length must be >= 1 466 * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats 467 * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats 468 * @param function Parameters for the transfer functions 469 * @param id ID of this color space as an integer between [ColorSpace.MinId] and 470 * [ColorSpace.MaxId] 471 * @throws IllegalArgumentException If any of the following conditions is met: 472 * * The name is null or has a length of 0. 473 * * The primaries array is null or has a length that is neither 6 or 9. 474 * * The white point array is null or has a length that is neither 2 or 3. 475 * * The ID is not between [ColorSpace.MinId] and [ColorSpace.MaxId]. 476 * * The transfer parameters are invalid. 477 * 478 * @see get 479 */ 480 internal constructor( 481 name: String, 482 primaries: FloatArray, 483 whitePoint: WhitePoint, 484 function: TransferParameters, 485 id: Int 486 ) : this( 487 name, 488 primaries, 489 whitePoint, 490 null, 491 generateOetf(function), 492 generateEotf(function), 493 0.0f, 494 1.0f, 495 function, 496 id 497 ) 498 499 /** 500 * Creates a new RGB color space using a 3x3 column-major transform matrix. The transform matrix 501 * must convert from the RGB space to the profile connection space CIE XYZ. 502 * 503 * The range of the color space is imposed to be `[0..1]`. 504 * 505 * @param name Name of the color space, cannot be null, its length must be >= 1 506 * @param toXYZ 3x3 column-major transform matrix from RGB to the profile connection space CIE 507 * XYZ as an array of 9 floats, cannot be null 508 * @param gamma Gamma to use as the transfer function 509 * @throws IllegalArgumentException If any of the following conditions is met: 510 * * The name is null or has a length of 0. 511 * * Gamma is negative. 512 * 513 * @see get 514 */ 515 constructor( 516 @Size(min = 1) name: String, 517 @Size(9) toXYZ: FloatArray, 518 gamma: Double 519 ) : this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ), gamma, 0.0f, 1.0f, MinId) 520 521 /** 522 * Creates a new RGB color space using a specified set of primaries and a specified white point. 523 * 524 * The primaries and white point can be specified in the CIE xyY space or in CIE XYZ. The length 525 * of the arrays depends on the chosen space: 526 * ``` 527 * | Spaces | Primaries length | White point length | 528 * |--------|------------------|--------------------| 529 * | xyY | 6 | 2 | 530 * | XYZ | 9 | 3 | 531 * ``` 532 * 533 * When the primaries and/or white point are specified in xyY, the Y component does not need to 534 * be specified and is assumed to be 1.0. Only the xy components are required. 535 * 536 * @param name Name of the color space, cannot be null, its length must be >= 1 537 * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats 538 * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats 539 * @param gamma Gamma to use as the transfer function 540 * @throws IllegalArgumentException If any of the following conditions is met: 541 * * The name is null or has a length of 0. 542 * * The primaries array is null or has a length that is neither 6 or 9. 543 * * The white point array is null or has a length that is neither 2 or 3. 544 * * Gamma is negative. 545 * 546 * @see get 547 */ 548 constructor( 549 @Size(min = 1) name: String, 550 @Size(min = 6, max = 9) primaries: FloatArray, 551 whitePoint: WhitePoint, 552 gamma: Double 553 ) : this(name, primaries, whitePoint, gamma, 0.0f, 1.0f, MinId) 554 555 /** 556 * Creates a new RGB color space using a specified set of primaries and a specified white point. 557 * 558 * The primaries and white point can be specified in the CIE xyY space or in CIE XYZ. The length 559 * of the arrays depends on the chosen space: 560 * ``` 561 * | Spaces | Primaries length | White point length | 562 * |--------|------------------|--------------------| 563 * | xyY | 6 | 2 | 564 * | XYZ | 9 | 3 | 565 * ``` 566 * 567 * When the primaries and/or white point are specified in xyY, the Y component does not need to 568 * be specified and is assumed to be 1.0. Only the xy components are required. 569 * 570 * @param name Name of the color space, cannot be null, its length must be >= 1 571 * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats 572 * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats 573 * @param gamma Gamma to use as the transfer function 574 * @param min The minimum valid value in this color space's RGB range 575 * @param max The maximum valid value in this color space's RGB range 576 * @param id ID of this color space as an integer between [ColorSpace.MinId] and 577 * [ColorSpace.MaxId] 578 * @throws IllegalArgumentException If any of the following conditions is met: 579 * * The name is null or has a length of 0. 580 * * The primaries array is null or has a length that is neither 6 or 9. 581 * * The white point array is null or has a length that is neither 2 or 3. 582 * * The minimum valid value is >= the maximum valid value. 583 * * The ID is not between [ColorSpace.MinId] and [ColorSpace.MaxId]. 584 * * Gamma is negative. 585 * 586 * @see get 587 */ 588 internal constructor( 589 name: String, 590 primaries: FloatArray, 591 whitePoint: WhitePoint, 592 gamma: Double, 593 min: Float, 594 max: Float, 595 id: Int 596 ) : this( 597 name, 598 primaries, 599 whitePoint, 600 null, 601 if (gamma == 1.0) DoubleIdentity 602 else DoubleFunction { x -> (if (x < 0.0) 0.0 else x).pow(1.0 / gamma) }, 603 if (gamma == 1.0) DoubleIdentity 604 else DoubleFunction { x -> (if (x < 0.0) 0.0 else x).pow(gamma) }, 605 min, 606 max, 607 TransferParameters(gamma, 1.0, 0.0, 0.0, 0.0), 608 id 609 ) 610 611 /** 612 * Creates a copy of the specified color space with a new transform. 613 * 614 * @param colorSpace The color space to create a copy of 615 */ 616 internal constructor( 617 colorSpace: Rgb, 618 transform: FloatArray, 619 whitePoint: WhitePoint 620 ) : this( 621 colorSpace.name, 622 colorSpace.primaries, 623 whitePoint, 624 transform, 625 colorSpace.oetfOrig, 626 colorSpace.eotfOrig, 627 colorSpace.min, 628 colorSpace.max, 629 colorSpace.transferParameters, 630 MinId 631 ) 632 633 /** 634 * Copies the primaries of this color space in specified array. The Y component is assumed to be 635 * 1 and is therefore not copied into the destination. The x and y components of the first 636 * primary are written in the array at positions 0 and 1 respectively. 637 * 638 * @param primaries The destination array, cannot be null, its length must be >= 6 639 * @return [primaries] array, modified to contain the primaries of this color space. 640 * @see getPrimaries 641 */ 642 @Size(min = 6) 643 fun getPrimaries(@Size(min = 6) primaries: FloatArray): FloatArray { 644 return this.primaries.copyInto(primaries) 645 } 646 647 /** 648 * Copies the transform of this color space in specified array. The transform is used to convert 649 * from RGB to XYZ (with the same white point as this color space). To connect color spaces, you 650 * must first [adapt][ColorSpace.adapt] them to the same white point. 651 * 652 * It is recommended to use [ColorSpace.connect] to convert between color spaces. 653 * 654 * @param transform The destination array, cannot be null, its length must be >= 9 655 * @return [transform], modified to contain the transform for this color space. 656 * @see getInverseTransform 657 */ 658 @Size(min = 9) 659 fun getTransform(@Size(min = 9) transform: FloatArray): FloatArray { 660 return this.transform.copyInto(transform) 661 } 662 663 /** 664 * Copies the inverse transform of this color space in specified array. The inverse transform is 665 * used to convert from XYZ to RGB (with the same white point as this color space). To connect 666 * color spaces, you must first [adapt][ColorSpace.adapt] them to the same white point. 667 * 668 * It is recommended to use [ColorSpace.connect] to convert between color spaces. 669 * 670 * @param inverseTransform The destination array, cannot be null, its length must be >= 9 671 * @return The [inverseTransform] array passed as a parameter, modified to contain the inverse 672 * transform of this color space. 673 * @see getTransform 674 */ 675 @Size(min = 9) 676 fun getInverseTransform(@Size(min = 9) inverseTransform: FloatArray): FloatArray { 677 return this.inverseTransform.copyInto(inverseTransform) 678 } 679 680 override fun getMinValue(component: Int): Float { 681 return min 682 } 683 684 override fun getMaxValue(component: Int): Float { 685 return max 686 } 687 688 /** 689 * Decodes an RGB value to linear space. This is achieved by applying this color space's 690 * electro-optical transfer function to the supplied values. 691 * 692 * Refer to the documentation of [Rgb] for more information about transfer functions and their 693 * use for encoding and decoding RGB values. 694 * 695 * @param r The red component to decode to linear space 696 * @param g The green component to decode to linear space 697 * @param b The blue component to decode to linear space 698 * @return A new array of 3 floats containing linear RGB values 699 * @see toLinear 700 * @see fromLinear 701 */ 702 @Size(3) 703 fun toLinear(r: Float, g: Float, b: Float): FloatArray { 704 return toLinear(floatArrayOf(r, g, b)) 705 } 706 707 /** 708 * Decodes an RGB value to linear space. This is achieved by applying this color space's 709 * electro-optical transfer function to the first 3 values of the supplied array. The result is 710 * stored back in the input array. 711 * 712 * Refer to the documentation of [Rgb] for more information about transfer functions and their 713 * use for encoding and decoding RGB values. 714 * 715 * @param v A non-null array of non-linear RGB values, its length must be at least 3 716 * @return [v], containing linear RGB values 717 * @see toLinear 718 * @see fromLinear 719 */ 720 @Size(min = 3) 721 fun toLinear(@Size(min = 3) v: FloatArray): FloatArray { 722 // Compiler hint to avoid extra bounds checks 723 if (v.size < 3) return v 724 v[0] = eotfFunc(v[0].toDouble()).toFloat() 725 v[1] = eotfFunc(v[1].toDouble()).toFloat() 726 v[2] = eotfFunc(v[2].toDouble()).toFloat() 727 return v 728 } 729 730 /** 731 * Encodes an RGB value from linear space to this color space's "gamma space". This is achieved 732 * by applying this color space's opto-electronic transfer function to the supplied values. 733 * 734 * Refer to the documentation of [Rgb] for more information about transfer functions and their 735 * use for encoding and decoding RGB values. 736 * 737 * @param r The red component to encode from linear space 738 * @param g The green component to encode from linear space 739 * @param b The blue component to encode from linear space 740 * @return A new array of 3 floats containing non-linear RGB values 741 * @see fromLinear 742 * @see toLinear 743 */ 744 @Size(3) 745 fun fromLinear(r: Float, g: Float, b: Float): FloatArray { 746 return fromLinear(floatArrayOf(r, g, b)) 747 } 748 749 /** 750 * Encodes an RGB value from linear space to this color space's "gamma space". This is achieved 751 * by applying this color space's opto-electronic transfer function to the first 3 values of the 752 * supplied array. The result is stored back in the input array. 753 * 754 * Refer to the documentation of [Rgb] for more information about transfer functions and their 755 * use for encoding and decoding RGB values. 756 * 757 * @param v A non-null array of linear RGB values, its length must be at least 3 758 * @return [v], containing non-linear RGB values 759 * @see fromLinear 760 * @see toLinear 761 */ 762 @Size(min = 3) 763 fun fromLinear(@Size(min = 3) v: FloatArray): FloatArray { 764 // Compiler hint to avoid extra bounds checks 765 if (v.size < 3) return v 766 v[0] = oetfFunc(v[0].toDouble()).toFloat() 767 v[1] = oetfFunc(v[1].toDouble()).toFloat() 768 v[2] = oetfFunc(v[2].toDouble()).toFloat() 769 return v 770 } 771 772 override fun toXyz(v: FloatArray): FloatArray { 773 // Compiler hint to avoid extra bounds checks 774 if (v.size < 3) return v 775 v[0] = eotfFunc(v[0].toDouble()).toFloat() 776 v[1] = eotfFunc(v[1].toDouble()).toFloat() 777 v[2] = eotfFunc(v[2].toDouble()).toFloat() 778 return mul3x3Float3(transform, v) 779 } 780 781 override fun toXy(v0: Float, v1: Float, v2: Float): Long { 782 val v00 = eotfFunc(v0.toDouble()).toFloat() 783 val v10 = eotfFunc(v1.toDouble()).toFloat() 784 val v20 = eotfFunc(v2.toDouble()).toFloat() 785 786 // Compiler hint to skip bounds checks 787 if (transform.size < 9) return 0L 788 val x = mul3x3Float3_0(transform, v00, v10, v20) 789 val y = mul3x3Float3_1(transform, v00, v10, v20) 790 791 return packFloats(x, y) 792 } 793 794 override fun toZ(v0: Float, v1: Float, v2: Float): Float { 795 val v00 = eotfFunc(v0.toDouble()).toFloat() 796 val v10 = eotfFunc(v1.toDouble()).toFloat() 797 val v20 = eotfFunc(v2.toDouble()).toFloat() 798 799 val z = mul3x3Float3_2(transform, v00, v10, v20) 800 801 return z 802 } 803 804 override fun xyzaToColor( 805 x: Float, 806 y: Float, 807 z: Float, 808 a: Float, 809 colorSpace: ColorSpace 810 ): Color { 811 var v0 = mul3x3Float3_0(inverseTransform, x, y, z) 812 var v1 = mul3x3Float3_1(inverseTransform, x, y, z) 813 var v2 = mul3x3Float3_2(inverseTransform, x, y, z) 814 815 v0 = oetfFunc(v0.toDouble()).toFloat() 816 v1 = oetfFunc(v1.toDouble()).toFloat() 817 v2 = oetfFunc(v2.toDouble()).toFloat() 818 819 return Color(v0, v1, v2, a, colorSpace) 820 } 821 822 override fun fromXyz(v: FloatArray): FloatArray { 823 mul3x3Float3(inverseTransform, v) 824 // Compiler hint to avoid extra bounds checks 825 if (v.size < 3) return v 826 v[0] = oetfFunc(v[0].toDouble()).toFloat() 827 v[1] = oetfFunc(v[1].toDouble()).toFloat() 828 v[2] = oetfFunc(v[2].toDouble()).toFloat() 829 return v 830 } 831 832 override fun equals(other: Any?): Boolean { 833 if (this === other) return true 834 if (other == null || this::class != other::class) return false 835 if (!super.equals(other)) return false 836 837 val rgb = other as Rgb 838 839 if (rgb.min.compareTo(min) != 0) return false 840 if (rgb.max.compareTo(max) != 0) return false 841 if (whitePoint != rgb.whitePoint) return false 842 if (!(primaries contentEquals rgb.primaries)) return false 843 if (transferParameters != null) { 844 return transferParameters == rgb.transferParameters 845 } else if (rgb.transferParameters == null) { 846 return true 847 } 848 849 return if (oetfOrig != rgb.oetfOrig) false else eotfOrig == rgb.eotfOrig 850 } 851 852 override fun hashCode(): Int { 853 var result = super.hashCode() 854 result = 31 * result + whitePoint.hashCode() 855 result = 31 * result + primaries.contentHashCode() 856 result = 31 * result + (if (min != 0.0f) min.toBits() else 0) 857 result = 31 * result + (if (max != 0.0f) max.toBits() else 0) 858 result = (31 * result + (transferParameters?.hashCode() ?: 0)) 859 if (transferParameters == null) { 860 result = 31 * result + oetfOrig.hashCode() 861 result = 31 * result + eotfOrig.hashCode() 862 } 863 return result 864 } 865 866 // internal so that current.txt doesn't expose it: a 'private' companion object 867 // is marked deprecated 868 internal companion object { 869 private val DoubleIdentity = DoubleFunction { d -> d } 870 871 /** 872 * Computes whether a color space is the sRGB color space or at least a close approximation. 873 * 874 * @param primaries The set of RGB primaries in xyY as an array of 6 floats 875 * @param whitePoint The white point in xyY as an array of 2 floats 876 * @param OETF The opto-electronic transfer function 877 * @param EOTF The electro-optical transfer function 878 * @param min The minimum value of the color space's range 879 * @param max The minimum value of the color space's range 880 * @param id The ID of the color space 881 * @return True if the color space can be considered as the sRGB color space 882 * @see isSrgb 883 */ 884 private fun isSrgb( 885 primaries: FloatArray, 886 whitePoint: WhitePoint, 887 OETF: DoubleFunction, 888 EOTF: DoubleFunction, 889 min: Float, 890 max: Float, 891 id: Int 892 ): Boolean { 893 if (id == 0) return true 894 if (!compare(primaries, ColorSpaces.SrgbPrimaries)) { 895 return false 896 } 897 if (!compare(whitePoint, Illuminant.D65)) { 898 return false 899 } 900 901 if (min != 0.0f) return false 902 if (max != 1.0f) return false 903 904 // We would have already returned true if this was SRGB itself, so 905 // it is safe to reference it here. 906 val srgb = ColorSpaces.Srgb 907 908 var x = 0.0 909 while (x <= 1.0) { 910 if (!compare(x, OETF, srgb.oetfOrig)) return false 911 if (!compare(x, EOTF, srgb.eotfOrig)) return false 912 x += 1 / 255.0 913 } 914 915 return true 916 } 917 918 private fun compare(point: Double, a: DoubleFunction, b: DoubleFunction): Boolean { 919 val rA = a(point) 920 val rB = b(point) 921 return abs(rA - rB) <= 1e-3 922 } 923 924 /** 925 * Computes whether the specified CIE xyY or XYZ primaries (with Y set to 1) form a wide 926 * color gamut. A color gamut is considered wide if its area is > 90% of the area of NTSC 927 * 1953 and if it contains the sRGB color gamut entirely. If the conditions above are not 928 * met, the color space is considered as having a wide color gamut if its range is larger 929 * than [0..1]. 930 * 931 * @param primaries RGB primaries in CIE xyY as an array of 6 floats 932 * @param min The minimum value of the color space's range 933 * @param max The minimum value of the color space's range 934 * @return True if the color space has a wide gamut, false otherwise 935 * @see isWideGamut 936 * @see area 937 */ 938 private fun isWideGamut(primaries: FloatArray, min: Float, max: Float): Boolean { 939 return (((area(primaries) / area(ColorSpaces.Ntsc1953Primaries) > 0.9f && 940 contains(primaries, ColorSpaces.SrgbPrimaries))) || (min < 0.0f && max > 1.0f)) 941 } 942 943 /** 944 * Computes the area of the triangle represented by a set of RGB primaries in the CIE xyY 945 * space. 946 * 947 * If [primaries] does not contain at least 6 elements, returns 0.0. 948 * 949 * @param primaries The triangle's vertices, as RGB primaries in an array of 6 floats 950 * @return The area of the triangle 951 * @see isWideGamut 952 */ 953 private fun area(primaries: FloatArray): Float { 954 // Compiler hint to remove bound checks 955 if (primaries.size < 6) return 0.0f 956 val rx = primaries[0] 957 val ry = primaries[1] 958 val gx = primaries[2] 959 val gy = primaries[3] 960 val bx = primaries[4] 961 val by = primaries[5] 962 val det = rx * gy + ry * bx + gx * by - gy * bx - ry * gx - rx * by 963 val r = 0.5f * det 964 return if (r < 0.0f) -r else r 965 } 966 967 /** 968 * Computes the cross product of two 2D vectors. 969 * 970 * @param ax The x coordinate of the first vector 971 * @param ay The y coordinate of the first vector 972 * @param bx The x coordinate of the second vector 973 * @param by The y coordinate of the second vector 974 * @return The result of a x b 975 */ 976 private inline fun cross(ax: Float, ay: Float, bx: Float, by: Float): Float { 977 return ax * by - ay * bx 978 } 979 980 /** 981 * Decides whether a 2D triangle, identified by the 6 coordinates of its 3 vertices, is 982 * contained within another 2D triangle, also identified by the 6 coordinates of its 3 983 * vertices. 984 * 985 * In the illustration below, we want to test whether the RGB triangle is contained within 986 * the triangle XYZ formed by the 3 vertices at the "+" locations. 987 * 988 * Y . 989 * . + . 990 * . .. 991 * . . 992 * . . 993 * . G 994 * * 995 * * * 996 * ** * 997 * * ** 998 * * * 999 * ** * 1000 * * * 1001 * * * 1002 * ** * 1003 * * * 1004 * * ** 1005 * ** * R ... 1006 * * * ..... 1007 * * ***** .. 1008 * ** ************ . + 1009 * B * ************ . X 1010 * ......***** . 1011 * ...... . . 1012 * .. 1013 * + . 1014 * Z . 1015 * 1016 * RGB is contained within XYZ if all the following conditions are true (with "x" the cross 1017 * product operator): 1018 * --> --> 1019 * GR x RX >= 0 1020 * --> --> 1021 * RX x BR >= 0 1022 * --> --> 1023 * RG x GY >= 0 1024 * --> --> 1025 * GY x RG >= 0 1026 * --> --> 1027 * RB x BZ >= 0 1028 * --> --> 1029 * BZ x GB >= 0 1030 * 1031 * @param p1 The enclosing triangle as 6 floats 1032 * @param p2 The enclosed triangle as 6 floats 1033 * @return True if the triangle p1 contains the triangle p2 1034 * @see isWideGamut 1035 */ 1036 private fun contains(p1: FloatArray, p2: FloatArray): Boolean { 1037 // Translate the vertices p1 in the coordinates system 1038 // with the vertices p2 as the origin 1039 val p0 = 1040 floatArrayOf( 1041 p1[0] - p2[0], 1042 p1[1] - p2[1], 1043 p1[2] - p2[2], 1044 p1[3] - p2[3], 1045 p1[4] - p2[4], 1046 p1[5] - p2[5] 1047 ) 1048 // Check the first vertex of p1 1049 if ( 1050 cross(p0[0], p0[1], p2[0] - p2[4], p2[1] - p2[5]) < 0 || 1051 cross(p2[0] - p2[2], p2[1] - p2[3], p0[0], p0[1]) < 0 1052 ) { 1053 return false 1054 } 1055 // Check the second vertex of p1 1056 if ( 1057 cross(p0[2], p0[3], p2[2] - p2[0], p2[3] - p2[1]) < 0 || 1058 cross(p2[2] - p2[4], p2[3] - p2[5], p0[2], p0[3]) < 0 1059 ) { 1060 return false 1061 } 1062 // Check the third vertex of p1 1063 return !(cross(p0[4], p0[5], p2[4] - p2[2], p2[5] - p2[3]) < 0 || 1064 cross(p2[4] - p2[0], p2[5] - p2[1], p0[4], p0[5]) < 0) 1065 } 1066 1067 /** 1068 * Computes the primaries of a color space identified only by its RGB->XYZ transform matrix. 1069 * This method assumes that the range of the color space is [0..1]. 1070 * 1071 * @param toXYZ The color space's 3x3 transform matrix to XYZ 1072 * @return A new array of 6 floats containing the color space's primaries in CIE xyY 1073 */ 1074 internal fun computePrimaries(toXYZ: FloatArray): FloatArray { 1075 val r = mul3x3Float3(toXYZ, floatArrayOf(1.0f, 0.0f, 0.0f)) 1076 val g = mul3x3Float3(toXYZ, floatArrayOf(0.0f, 1.0f, 0.0f)) 1077 val b = mul3x3Float3(toXYZ, floatArrayOf(0.0f, 0.0f, 1.0f)) 1078 1079 val rSum = r[0] + r[1] + r[2] 1080 val gSum = g[0] + g[1] + g[2] 1081 val bSum = b[0] + b[1] + b[2] 1082 1083 return floatArrayOf( 1084 r[0] / rSum, 1085 r[1] / rSum, 1086 g[0] / gSum, 1087 g[1] / gSum, 1088 b[0] / bSum, 1089 b[1] / bSum 1090 ) 1091 } 1092 1093 /** 1094 * Computes the white point of a color space identified only by its RGB->XYZ transform 1095 * matrix. This method assumes that the range of the color space is [0..1]. 1096 * 1097 * @param toXYZ The color space's 3x3 transform matrix to XYZ 1098 * @return A new array of 2 floats containing the color space's white point in CIE xyY 1099 */ 1100 private fun computeWhitePoint(toXYZ: FloatArray): WhitePoint { 1101 val w = mul3x3Float3(toXYZ, floatArrayOf(1.0f, 1.0f, 1.0f)) 1102 val sum = w[0] + w[1] + w[2] 1103 return WhitePoint(w[0] / sum, w[1] / sum) 1104 } 1105 1106 /** 1107 * Converts the specified RGB primaries point to xyY if needed. The primaries can be 1108 * specified as an array of 6 floats (in CIE xyY) or 9 floats (in CIE XYZ). If no conversion 1109 * is needed, the input array is copied. 1110 * 1111 * @param primaries The primaries in xyY or XYZ, in an array of 6 floats. 1112 * @return A new array of 6 floats containing the primaries in xyY 1113 */ 1114 private fun xyPrimaries(primaries: FloatArray): FloatArray { 1115 val xyPrimaries = FloatArray(6) 1116 1117 // XYZ to xyY 1118 if (primaries.size == 9) { 1119 var sum: Float = primaries[0] + primaries[1] + primaries[2] 1120 xyPrimaries[0] = primaries[0] / sum 1121 xyPrimaries[1] = primaries[1] / sum 1122 1123 sum = primaries[3] + primaries[4] + primaries[5] 1124 xyPrimaries[2] = primaries[3] / sum 1125 xyPrimaries[3] = primaries[4] / sum 1126 1127 sum = primaries[6] + primaries[7] + primaries[8] 1128 xyPrimaries[4] = primaries[6] / sum 1129 xyPrimaries[5] = primaries[7] / sum 1130 } else { 1131 primaries.copyInto(xyPrimaries, endIndex = 6) 1132 } 1133 1134 return xyPrimaries 1135 } 1136 1137 /** 1138 * Computes the matrix that converts from RGB to XYZ based on RGB primaries and a white 1139 * point, both specified in the CIE xyY space. The Y component of the primaries and white 1140 * point is implied to be 1. 1141 * 1142 * @param primaries The RGB primaries in xyY, as an array of 6 floats 1143 * @param whitePoint The white point in xyY, as an array of 2 floats 1144 * @return A 3x3 matrix as a new array of 9 floats 1145 */ 1146 private fun computeXYZMatrix(primaries: FloatArray, whitePoint: WhitePoint): FloatArray { 1147 val rx = primaries[0] 1148 val ry = primaries[1] 1149 val gx = primaries[2] 1150 val gy = primaries[3] 1151 val bx = primaries[4] 1152 val by = primaries[5] 1153 val wx = whitePoint.x 1154 val wy = whitePoint.y 1155 1156 val oneRxRy = (1 - rx) / ry 1157 val oneGxGy = (1 - gx) / gy 1158 val oneBxBy = (1 - bx) / by 1159 val oneWxWy = (1 - wx) / wy 1160 1161 val rxRy = rx / ry 1162 val gxGy = gx / gy 1163 val bxBy = bx / by 1164 val wxWy = wx / wy 1165 1166 val byNumerator = 1167 (oneWxWy - oneRxRy) * (gxGy - rxRy) - (wxWy - rxRy) * (oneGxGy - oneRxRy) 1168 val byDenominator = 1169 (oneBxBy - oneRxRy) * (gxGy - rxRy) - (bxBy - rxRy) * (oneGxGy - oneRxRy) 1170 val bY = byNumerator / byDenominator 1171 val gY = (wxWy - rxRy - bY * (bxBy - rxRy)) / (gxGy - rxRy) 1172 val rY = 1f - gY - bY 1173 1174 val rYRy = rY / ry 1175 val gYGy = gY / gy 1176 val bYBy = bY / by 1177 1178 return floatArrayOf( 1179 rYRy * rx, 1180 rY, 1181 rYRy * (1f - rx - ry), 1182 gYGy * gx, 1183 gY, 1184 gYGy * (1f - gx - gy), 1185 bYBy * bx, 1186 bY, 1187 bYBy * (1f - bx - by) 1188 ) 1189 } 1190 1191 private fun generateOetf(function: TransferParameters): DoubleFunction { 1192 return if (function.isHLGish) { 1193 DoubleFunction { x: Double -> transferHlgOetf(function, x) } 1194 } else if (function.isPQish) { 1195 DoubleFunction { x: Double -> transferSt2048Oetf(function, x) } 1196 } else { 1197 if (function.e == 0.0 && function.f == 0.0) 1198 DoubleFunction { x: Double -> 1199 rcpResponse( 1200 x, 1201 function.a, 1202 function.b, 1203 function.c, 1204 function.d, 1205 function.gamma 1206 ) 1207 } 1208 else 1209 DoubleFunction { x: Double -> 1210 rcpResponse( 1211 x, 1212 function.a, 1213 function.b, 1214 function.c, 1215 function.d, 1216 function.e, 1217 function.f, 1218 function.gamma 1219 ) 1220 } 1221 } 1222 } 1223 1224 private fun generateEotf(function: TransferParameters): DoubleFunction { 1225 return if (function.isHLGish) { 1226 DoubleFunction { x: Double -> transferHlgEotf(function, x) } 1227 } else if (function.isPQish) { 1228 DoubleFunction { x: Double -> transferSt2048Eotf(function, x) } 1229 } else { 1230 if (function.e == 0.0 && function.f == 0.0) 1231 DoubleFunction { x: Double -> 1232 response(x, function.a, function.b, function.c, function.d, function.gamma) 1233 } 1234 else 1235 DoubleFunction { x: Double -> 1236 response( 1237 x, 1238 function.a, 1239 function.b, 1240 function.c, 1241 function.d, 1242 function.e, 1243 function.f, 1244 function.gamma 1245 ) 1246 } 1247 } 1248 } 1249 } 1250 } 1251 1252 /** 1253 * Java's DoubleUnaryOperator isn't available until API 24, so we'll use a substitute. When we bump 1254 * minimum SDK versions, this should be removed and we should use Java's version. 1255 */ 1256 internal fun interface DoubleFunction { invokenull1257 operator fun invoke(double: Double): Double 1258 } 1259