1 /*
2  * Copyright 2021 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 androidx.compose.ui.graphics.colorspace
18 
19 import androidx.compose.ui.graphics.Color
20 import androidx.compose.ui.util.fastCbrt
21 import androidx.compose.ui.util.fastCoerceIn
22 import androidx.compose.ui.util.packFloats
23 
24 /** Implementation of the Oklab color space. Oklab uses a D65 white point. */
25 internal class Oklab(name: String, id: Int) : ColorSpace(name, ColorModel.Lab, id) {
26 
27     override val isWideGamut: Boolean
28         get() = true
29 
getMinValuenull30     override fun getMinValue(component: Int): Float {
31         return if (component == 0) 0f else -0.5f
32     }
33 
getMaxValuenull34     override fun getMaxValue(component: Int): Float {
35         return if (component == 0) 1f else 0.5f
36     }
37 
toXyznull38     override fun toXyz(v: FloatArray): FloatArray {
39         v[0] = v[0].fastCoerceIn(0f, 1f)
40         v[1] = v[1].fastCoerceIn(-0.5f, 0.5f)
41         v[2] = v[2].fastCoerceIn(-0.5f, 0.5f)
42 
43         mul3x3Float3(InverseM2, v)
44         v[0] = v[0] * v[0] * v[0]
45         v[1] = v[1] * v[1] * v[1]
46         v[2] = v[2] * v[2] * v[2]
47         mul3x3Float3(InverseM1, v)
48 
49         return v
50     }
51 
toXynull52     override fun toXy(v0: Float, v1: Float, v2: Float): Long {
53         val v00 = v0.fastCoerceIn(0f, 1f)
54         val v10 = v1.fastCoerceIn(-0.5f, 0.5f)
55         val v20 = v2.fastCoerceIn(-0.5f, 0.5f)
56 
57         val v01 = mul3x3Float3_0(InverseM2, v00, v10, v20)
58         val v11 = mul3x3Float3_1(InverseM2, v00, v10, v20)
59         val v21 = mul3x3Float3_2(InverseM2, v00, v10, v20)
60 
61         val v02 = v01 * v01 * v01
62         val v12 = v11 * v11 * v11
63         val v22 = v21 * v21 * v21
64 
65         val v03 = mul3x3Float3_0(InverseM1, v02, v12, v22)
66         val v13 = mul3x3Float3_1(InverseM1, v02, v12, v22)
67 
68         return packFloats(v03, v13)
69     }
70 
toZnull71     override fun toZ(v0: Float, v1: Float, v2: Float): Float {
72         val v00 = v0.fastCoerceIn(0f, 1f)
73         val v10 = v1.fastCoerceIn(-0.5f, 0.5f)
74         val v20 = v2.fastCoerceIn(-0.5f, 0.5f)
75 
76         val v01 = mul3x3Float3_0(InverseM2, v00, v10, v20)
77         val v11 = mul3x3Float3_1(InverseM2, v00, v10, v20)
78         val v21 = mul3x3Float3_2(InverseM2, v00, v10, v20)
79 
80         val v02 = v01 * v01 * v01
81         val v12 = v11 * v11 * v11
82         val v22 = v21 * v21 * v21
83 
84         val v23 = mul3x3Float3_2(InverseM1, v02, v12, v22)
85 
86         return v23
87     }
88 
xyzaToColornull89     override fun xyzaToColor(
90         x: Float,
91         y: Float,
92         z: Float,
93         a: Float,
94         colorSpace: ColorSpace
95     ): Color {
96         var v0 = mul3x3Float3_0(M1, x, y, z)
97         var v1 = mul3x3Float3_1(M1, x, y, z)
98         var v2 = mul3x3Float3_2(M1, x, y, z)
99 
100         v0 = fastCbrt(v0)
101         v1 = fastCbrt(v1)
102         v2 = fastCbrt(v2)
103 
104         val v01 = mul3x3Float3_0(M2, v0, v1, v2)
105         val v11 = mul3x3Float3_1(M2, v0, v1, v2)
106         val v21 = mul3x3Float3_2(M2, v0, v1, v2)
107 
108         return Color(v01, v11, v21, a, colorSpace)
109     }
110 
fromXyznull111     override fun fromXyz(v: FloatArray): FloatArray {
112         mul3x3Float3(M1, v)
113 
114         v[0] = fastCbrt(v[0])
115         v[1] = fastCbrt(v[1])
116         v[2] = fastCbrt(v[2])
117 
118         mul3x3Float3(M2, v)
119         return v
120     }
121 
122     internal companion object {
123         /**
124          * This is the matrix applied before the nonlinear transform for (D50) XYZ-to-Oklab. This
125          * combines the D50-to-D65 white point transform with the normal transform matrix because
126          * this is always done together in [fromXyz].
127          */
128         private val M1 =
129             mul3x3(
130                 floatArrayOf(
131                     0.8189330101f,
132                     0.0329845436f,
133                     0.0482003018f,
134                     0.3618667424f,
135                     0.9293118715f,
136                     0.2643662691f,
137                     -0.1288597137f,
138                     0.0361456387f,
139                     0.6338517070f
140                 ),
141                 chromaticAdaptation(
142                     matrix = Adaptation.Bradford.transform,
143                     srcWhitePoint = Illuminant.D50.toXyz(),
144                     dstWhitePoint = Illuminant.D65.toXyz()
145                 )
146             )
147 
148         /** Matrix applied after the nonlinear transform. */
149         private val M2 =
150             floatArrayOf(
151                 0.2104542553f,
152                 1.9779984951f,
153                 0.0259040371f,
154                 0.7936177850f,
155                 -2.4285922050f,
156                 0.7827717662f,
157                 -0.0040720468f,
158                 0.4505937099f,
159                 -0.8086757660f
160             )
161 
162         /** The inverse of the [M1] matrix, transforming back to XYZ (D50) */
163         private val InverseM1 = inverse3x3(M1)
164 
165         /**
166          * The inverse of the [M2] matrix, doing the first linear transform in the Oklab-to-XYZ
167          * before doing the nonlinear transform.
168          */
169         private val InverseM2 = inverse3x3(M2)
170     }
171 }
172