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