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") 18 19 package androidx.compose.ui.graphics.colorspace 20 21 import androidx.annotation.Size 22 import kotlin.math.exp 23 import kotlin.math.ln 24 import kotlin.math.max 25 import kotlin.math.pow 26 27 object ColorSpaces { 28 internal val SrgbPrimaries = floatArrayOf(0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f) 29 internal val Ntsc1953Primaries = floatArrayOf(0.67f, 0.33f, 0.21f, 0.71f, 0.14f, 0.08f) 30 internal val Bt2020Primaries = floatArrayOf(0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f) 31 internal val SrgbTransferParameters = 32 TransferParameters(2.4, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045) 33 private val NoneTransferParameters = 34 TransferParameters(2.2, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045) 35 36 // HLG transfer with an SDR whitepoint of 203 nits 37 internal val Bt2020HlgTransferParameters = 38 TransferParameters( 39 gamma = TypeHLGish, 40 a = 2.0, 41 b = 2.0, 42 c = 1 / 0.17883277, 43 d = 0.28466892, 44 e = 0.55991073, 45 f = -0.685490157 46 ) 47 48 // PQ transfer with an SDR whitepoint of 203 nits 49 internal val Bt2020PqTransferParameters = 50 TransferParameters( 51 gamma = TypePQish, 52 a = -1.555223, 53 b = 1.860454, 54 c = 32 / 2523.0, 55 d = 2413 / 128.0, 56 e = -2392 / 128.0, 57 f = 8192 / 1305.0 58 ) 59 60 /** 61 * [RGB][Rgb] color space sRGB standardized as IEC 61966-2.1:1999. 62 * [See details on sRGB color space](https://d.android.com/reference/android/graphics/ColorSpace.Named.html#SRGB) 63 */ 64 val Srgb = 65 Rgb("sRGB IEC61966-2.1", SrgbPrimaries, Illuminant.D65, SrgbTransferParameters, id = 0) 66 67 /** 68 * [RGB][Rgb] color space sRGB standardized as IEC 61966-2.1:1999. 69 * [See details on Linear sRGB color space](https://d.android.com/reference/android/graphics/ColorSpace.Named.html#LINEAR_SRGB) 70 */ 71 val LinearSrgb = 72 Rgb("sRGB IEC61966-2.1 (Linear)", SrgbPrimaries, Illuminant.D65, 1.0, 0.0f, 1.0f, id = 1) 73 74 /** 75 * [RGB][Rgb] color space scRGB-nl standardized as IEC 61966-2-2:2003. 76 * [See details on Extended sRGB color space](https://d.android.com/reference/android/graphics/ColorSpace.Named.html#EXTENDED_SRGB) 77 */ 78 val ExtendedSrgb = 79 Rgb( 80 "scRGB-nl IEC 61966-2-2:2003", 81 SrgbPrimaries, 82 Illuminant.D65, 83 null, 84 { x -> absRcpResponse(x, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4) }, 85 { x -> absResponse(x, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4) }, 86 -0.799f, 87 2.399f, 88 SrgbTransferParameters, 89 id = 2 90 ) 91 92 /** 93 * [RGB][Rgb] color space scRGB standardized as IEC 61966-2-2:2003. 94 * [See details on Linear Extended sRGB color space](https://d.android.com/reference/android/graphics/ColorSpace.Named.html#LINEAR_EXTENDED_SRGB) 95 */ 96 val LinearExtendedSrgb = 97 Rgb("scRGB IEC 61966-2-2:2003", SrgbPrimaries, Illuminant.D65, 1.0, -0.5f, 7.499f, id = 3) 98 99 /** 100 * [RGB][Rgb] color space BT.709 standardized as Rec. ITU-R BT.709-5. 101 * [See details on BT.709 color space](https://d.android.com/reference/android/graphics/ColorSpace.Named.html#BT_709) 102 */ 103 val Bt709 = 104 Rgb( 105 "Rec. ITU-R BT.709-5", 106 floatArrayOf(0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f), 107 Illuminant.D65, 108 TransferParameters(1 / 0.45, 1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081), 109 id = 4 110 ) 111 112 /** 113 * [RGB][Rgb] color space BT.2020 standardized as Rec. ITU-R BT.2020-1. 114 * [See details on BT.2020 color space](https://d.android.com/reference/android/graphics/ColorSpace.Named.html#BT_2020) 115 */ 116 val Bt2020 = 117 Rgb( 118 "Rec. ITU-R BT.2020-1", 119 floatArrayOf(0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f), 120 Illuminant.D65, 121 TransferParameters(1 / 0.45, 1 / 1.0993, 0.0993 / 1.0993, 1 / 4.5, 0.08145), 122 id = 5 123 ) 124 125 /** 126 * [RGB][Rgb] color space DCI-P3 standardized as SMPTE RP 431-2-2007. 127 * [See details on DCI-P3 color space](https://d.android.com/reference/android/graphics/ColorSpace.Named.html#DCI_P3) 128 */ 129 val DciP3 = 130 Rgb( 131 "SMPTE RP 431-2-2007 DCI (P3)", 132 floatArrayOf(0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f), 133 WhitePoint(0.314f, 0.351f), 134 2.6, 135 0.0f, 136 1.0f, 137 id = 6 138 ) 139 140 /** 141 * [RGB][Rgb] color space Display P3 based on SMPTE RP 431-2-2007 and IEC 61966-2.1:1999. 142 * [See details on Display P3 color space](https://d.android.com/reference/android/graphics/ColorSpace.Named.html#DISPLAY_P3) 143 */ 144 val DisplayP3 = 145 Rgb( 146 "Display P3", 147 floatArrayOf(0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f), 148 Illuminant.D65, 149 SrgbTransferParameters, 150 id = 7 151 ) 152 153 /** 154 * [RGB][Rgb] color space NTSC, 1953 standard. 155 * [See details on NTSC 1953 color space](https://d.android.com/reference/android/graphics/ColorSpace.Named.html#NTSC_1953) 156 */ 157 val Ntsc1953 = 158 Rgb( 159 "NTSC (1953)", 160 Ntsc1953Primaries, 161 Illuminant.C, 162 TransferParameters(1 / 0.45, 1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081), 163 id = 8 164 ) 165 166 /** 167 * [RGB][Rgb] color space SMPTE C. 168 * [See details on SMPTE C color space](https://d.android.com/reference/android/graphics/ColorSpace.Named.html#SMPTE_C) 169 */ 170 val SmpteC = 171 Rgb( 172 "SMPTE-C RGB", 173 floatArrayOf(0.630f, 0.340f, 0.310f, 0.595f, 0.155f, 0.070f), 174 Illuminant.D65, 175 TransferParameters(1 / 0.45, 1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081), 176 id = 9 177 ) 178 179 /** 180 * [RGB][Rgb] color space Adobe RGB (1998). 181 * [See details on Adobe RGB (1998) color space](https://d.android.com/reference/android/graphics/ColorSpace.Named.html#ADOBE_RGB) 182 */ 183 val AdobeRgb = 184 Rgb( 185 "Adobe RGB (1998)", 186 floatArrayOf(0.64f, 0.33f, 0.21f, 0.71f, 0.15f, 0.06f), 187 Illuminant.D65, 188 2.2, 189 0.0f, 190 1.0f, 191 id = 10 192 ) 193 194 /** 195 * [RGB][Rgb] color space ProPhoto RGB standardized as ROMM RGB ISO 22028-2:2013. 196 * [See details on ProPhoto RGB color space](https://d.android.com/reference/android/graphics/ColorSpace.Named.html#PRO_PHOTO_RGB) 197 */ 198 val ProPhotoRgb = 199 Rgb( 200 "ROMM RGB ISO 22028-2:2013", 201 floatArrayOf(0.7347f, 0.2653f, 0.1596f, 0.8404f, 0.0366f, 0.0001f), 202 Illuminant.D50, 203 TransferParameters(1.8, 1.0, 0.0, 1 / 16.0, 0.031248), 204 id = 11 205 ) 206 207 /** 208 * [RGB][Rgb] color space ACES standardized as SMPTE ST 2065-1:2012. 209 * [See details on ACES color space](https://d.android.com/reference/android/graphics/ColorSpace.Named.html#ACES) 210 */ 211 val Aces = 212 Rgb( 213 "SMPTE ST 2065-1:2012 ACES", 214 floatArrayOf(0.73470f, 0.26530f, 0.0f, 1.0f, 0.00010f, -0.0770f), 215 Illuminant.D60, 216 1.0, 217 -65504.0f, 218 65504.0f, 219 id = 12 220 ) 221 222 /** 223 * [RGB][Rgb] color space ACEScg standardized as Academy S-2014-004. 224 * [See details on ACEScg color space](https://d.android.com/reference/android/graphics/ColorSpace.Named.html#ACES_CG) 225 */ 226 val Acescg = 227 Rgb( 228 "Academy S-2014-004 ACEScg", 229 floatArrayOf(0.713f, 0.293f, 0.165f, 0.830f, 0.128f, 0.044f), 230 Illuminant.D60, 231 1.0, 232 -65504.0f, 233 65504.0f, 234 id = 13 235 ) 236 237 /** 238 * [XYZ][ColorModel.Xyz] color space CIE XYZ. This color space assumes standard illuminant D50 239 * as its white point. 240 * 241 * ``` 242 * | Property | Value | 243 * |-------------------------|-----------------------| 244 * | Name | Generic XYZ | 245 * | CIE standard illuminant | [D50][Illuminant.D50] | 246 * | Range | `[-2.0, 2.0]` | 247 * ``` 248 */ 249 val CieXyz: ColorSpace = Xyz("Generic XYZ", id = 14) 250 251 /** 252 * [Lab][ColorModel.Lab] color space CIE L*a*b*. This color space uses CIE XYZ D50 as a profile 253 * conversion space. 254 * 255 * ``` 256 * | Property | Value | 257 * |-------------------------|---------------------------------------------------------| 258 * | Name | Generic L*a*b* | 259 * | CIE standard illuminant | [D50][Illuminant.D50] | 260 * | Range | (L: `[0.0, 100.0]`, a: `[-128, 128]`, b: `[-128, 128]`) | 261 * ``` 262 */ 263 val CieLab: ColorSpace = Lab("Generic L*a*b*", id = 15) 264 265 /** This identifies the 'None' color. */ 266 internal val Unspecified = 267 Rgb("None", SrgbPrimaries, Illuminant.D65, NoneTransferParameters, id = 16) 268 269 /** 270 * [RGB][Rgb] color space BT.2100 standardized as Hybrid Log Gamma encoding 271 * 272 * ``` 273 * | Property | Value | 274 * |-------------------------|---------------------------------------------------------| 275 * | Name | Hybrid Log Gamma encoding | 276 * | CIE standard illuminant | [D65][Illuminant.D65] | 277 * | Range | `[0.0, 1.0]` | 278 * ``` 279 */ 280 val Bt2020Hlg = 281 Rgb( 282 "Hybrid Log Gamma encoding", 283 Bt2020Primaries, 284 Illuminant.D65, 285 null, 286 { x -> transferHlgOetf(Bt2020HlgTransferParameters, x) }, 287 { x -> transferHlgEotf(Bt2020HlgTransferParameters, x) }, 288 0.0f, 289 1.0f, 290 Bt2020HlgTransferParameters, 291 id = 17 292 ) 293 294 /** 295 * [RGB][Rgb] color space BT.2100 standardized as Perceptual Quantizer encoding 296 * 297 * ``` 298 * | Property | Value | 299 * |-------------------------|---------------------------------------------------------| 300 * | Name | Perceptual Quantizer encoding | 301 * | CIE standard illuminant | [D65][Illuminant.D65] | 302 * | Range | `[0.0, 1.0]` | 303 * ``` 304 */ 305 val Bt2020Pq = 306 Rgb( 307 "Perceptual Quantizer encoding", 308 Bt2020Primaries, 309 Illuminant.D65, 310 null, 311 { x -> transferSt2048Oetf(Bt2020PqTransferParameters, x) }, 312 { x -> transferSt2048Eotf(Bt2020PqTransferParameters, x) }, 313 0.0f, 314 1.0f, 315 Bt2020PqTransferParameters, 316 id = 18 317 ) 318 319 /** 320 * [Lab][ColorModel.Lab] color space Oklab. This color space uses Oklab D65 as a profile 321 * conversion space. 322 * 323 * ``` 324 * | Property | Value | 325 * |-------------------------|---------------------------------------------------------| 326 * | Name | Oklab | 327 * | CIE standard illuminant | [D65][Illuminant.D65] | 328 * | Range | (L: `[0.0, 1.0]`, a: `[-2, 2]`, b: `[-2, 2]`) | 329 * ``` 330 */ 331 val Oklab: ColorSpace = Oklab("Oklab", id = 19) 332 333 /** 334 * Returns a [ColorSpaces] instance of [ColorSpace] that matches the specified RGB to CIE XYZ 335 * transform and transfer functions. If no instance can be found, this method returns null. 336 * 337 * The color transform matrix is assumed to target the CIE XYZ space a [D50][Illuminant.D50] 338 * standard illuminant. 339 * 340 * @param toXYZD50 3x3 column-major transform matrix from RGB to the profile connection space 341 * CIE XYZ as an array of 9 floats, cannot be null 342 * @param function Parameters for the transfer functions 343 * @return A non-null [ColorSpace] if a match is found, null otherwise 344 */ 345 fun match(@Size(9) toXYZD50: FloatArray, function: TransferParameters): ColorSpace? { 346 for (colorSpace in ColorSpacesArray) { 347 if (colorSpace.model == ColorModel.Rgb) { 348 val rgb = colorSpace.adapt(Illuminant.D50) as Rgb 349 if ( 350 (compare(toXYZD50, rgb.transform) && compare(function, rgb.transferParameters)) 351 ) { 352 return colorSpace 353 } 354 } 355 } 356 357 return null 358 } 359 360 internal inline fun getColorSpace(id: Int): ColorSpace = ColorSpacesArray[id] 361 362 /** These MUST be in the order of their IDs */ 363 internal val ColorSpacesArray = 364 arrayOf( 365 Srgb, 366 LinearSrgb, 367 ExtendedSrgb, 368 LinearExtendedSrgb, 369 Bt709, 370 Bt2020, 371 DciP3, 372 DisplayP3, 373 Ntsc1953, 374 SmpteC, 375 AdobeRgb, 376 ProPhotoRgb, 377 Aces, 378 Acescg, 379 CieXyz, 380 CieLab, 381 Unspecified, 382 Bt2020Hlg, 383 Bt2020Pq, 384 Oklab 385 ) 386 387 internal fun transferHlgOetf(params: TransferParameters, x: Double): Double { 388 val sign = if (x < 0) -1.0 else 1.0 389 var absX = x * sign 390 391 // Unpack the transfer params matching skia's packing & invert R, G, and a 392 val R = 1.0 / params.a 393 val G = 1.0 / params.b 394 val a = 1.0 / params.c 395 val b = params.d 396 val c = params.e 397 val K = params.f + 1.0 398 399 absX /= K 400 val result = 401 if (absX <= 1) { 402 R * absX.pow(G) 403 } else { 404 a * ln(absX - b) + c 405 } 406 return sign * result 407 } 408 409 internal fun transferHlgEotf(params: TransferParameters, x: Double): Double { 410 val sign = if (x < 0) -1.0 else 1.0 411 val absX = x * sign 412 413 // Unpack the transfer params matching skia's packing 414 val R = params.a 415 val G = params.b 416 val a = params.c 417 val b = params.d 418 val c = params.e 419 val K = params.f + 1.0 420 421 val result = 422 if (absX * R <= 1) { 423 (absX * R).pow(G) 424 } else { 425 exp((absX - c) * a) + b 426 } 427 return K * sign * result 428 } 429 430 internal fun transferSt2048Oetf(params: TransferParameters, x: Double): Double { 431 val sign = if (x < 0) -1.0 else 1.0 432 val absX = x * sign 433 434 val a = -params.a 435 val b = params.d 436 val c = 1.0 / params.f 437 val d = params.b 438 val e = -params.e 439 val f = 1.0 / params.c 440 441 val tmp = max(a + b * absX.pow(c), 0.0) 442 return sign * (tmp / (d + e * absX.pow(c))).pow(f) 443 } 444 445 internal fun transferSt2048Eotf(pq: TransferParameters, x: Double): Double { 446 val sign = if (x < 0) -1.0 else 1.0 447 val absX = x * sign 448 449 val tmp = (pq.a + pq.b * absX.pow(pq.c)).coerceAtLeast(0.0) 450 return sign * (tmp / (pq.d + pq.e * absX.pow(pq.c))).pow(pq.f) 451 } 452 } 453