1 /*
2  * 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 package androidx.compose.ui.graphics.colorspace
18 
19 import androidx.compose.ui.graphics.Color
20 import androidx.compose.ui.util.fastCoerceIn
21 import androidx.compose.ui.util.packFloats
22 import kotlin.math.cbrt
23 
24 /** Implementation of the CIE L*a*b* color space. Its PCS is CIE XYZ with a white point of D50. */
25 internal class Lab(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) 0.0f else -128.0f
32     }
33 
getMaxValuenull34     override fun getMaxValue(component: Int): Float {
35         return if (component == 0) 100.0f else 128.0f
36     }
37 
toXyznull38     override fun toXyz(v: FloatArray): FloatArray {
39         v[0] = v[0].fastCoerceIn(0.0f, 100.0f)
40         v[1] = v[1].fastCoerceIn(-128.0f, 128.0f)
41         v[2] = v[2].fastCoerceIn(-128.0f, 128.0f)
42 
43         val fy = (v[0] + 16.0f) / 116.0f
44         val fx = fy + (v[1] * 0.002f)
45         val fz = fy - (v[2] * 0.005f)
46         val x = if (fx > D) fx * fx * fx else (1.0f / B) * (fx - C)
47         val y = if (fy > D) fy * fy * fy else (1.0f / B) * (fy - C)
48         val z = if (fz > D) fz * fz * fz else (1.0f / B) * (fz - C)
49 
50         v[0] = x * Illuminant.D50Xyz[0]
51         v[1] = y * Illuminant.D50Xyz[1]
52         v[2] = z * Illuminant.D50Xyz[2]
53 
54         return v
55     }
56 
toXynull57     override fun toXy(v0: Float, v1: Float, v2: Float): Long {
58         val v00 = v0.fastCoerceIn(0.0f, 100.0f)
59         val v10 = v1.fastCoerceIn(-128.0f, 128.0f)
60 
61         val fy = (v00 + 16.0f) / 116.0f
62         val fx = fy + (v10 * 0.002f)
63         val x = if (fx > D) fx * fx * fx else (1.0f / B) * (fx - C)
64         val y = if (fy > D) fy * fy * fy else (1.0f / B) * (fy - C)
65 
66         return packFloats(x * Illuminant.D50Xyz[0], y * Illuminant.D50Xyz[1])
67     }
68 
toZnull69     override fun toZ(v0: Float, v1: Float, v2: Float): Float {
70         val v00 = v0.fastCoerceIn(0.0f, 100.0f)
71         val v20 = v2.fastCoerceIn(-128.0f, 128.0f)
72         val fy = (v00 + 16.0f) / 116.0f
73         val fz = fy - (v20 * 0.005f)
74         val z = if (fz > D) fz * fz * fz else (1.0f / B) * (fz - C)
75         return z * Illuminant.D50Xyz[2]
76     }
77 
xyzaToColornull78     override fun xyzaToColor(
79         x: Float,
80         y: Float,
81         z: Float,
82         a: Float,
83         colorSpace: ColorSpace
84     ): Color {
85         val x1 = x / Illuminant.D50Xyz[0]
86         val y1 = y / Illuminant.D50Xyz[1]
87         val z1 = z / Illuminant.D50Xyz[2]
88 
89         val fx = if (x1 > A) cbrt(x1) else B * x1 + C
90         val fy = if (y1 > A) cbrt(y1) else B * y1 + C
91         val fz = if (z1 > A) cbrt(z1) else B * z1 + C
92 
93         val l = 116.0f * fy - 16.0f
94         val a1 = 500.0f * (fx - fy)
95         val b = 200.0f * (fy - fz)
96 
97         return Color(
98             l.fastCoerceIn(0.0f, 100.0f),
99             a1.fastCoerceIn(-128.0f, 128.0f),
100             b.fastCoerceIn(-128.0f, 128.0f),
101             a,
102             colorSpace
103         )
104     }
105 
fromXyznull106     override fun fromXyz(v: FloatArray): FloatArray {
107         val x = v[0] / Illuminant.D50Xyz[0]
108         val y = v[1] / Illuminant.D50Xyz[1]
109         val z = v[2] / Illuminant.D50Xyz[2]
110 
111         val fx = if (x > A) cbrt(x) else B * x + C
112         val fy = if (y > A) cbrt(y) else B * y + C
113         val fz = if (z > A) cbrt(z) else B * z + C
114 
115         val l = 116.0f * fy - 16.0f
116         val a = 500.0f * (fx - fy)
117         val b = 200.0f * (fy - fz)
118 
119         v[0] = l.fastCoerceIn(0.0f, 100.0f)
120         v[1] = a.fastCoerceIn(-128.0f, 128.0f)
121         v[2] = b.fastCoerceIn(-128.0f, 128.0f)
122 
123         return v
124     }
125 
126     internal companion object {
127         private const val A = 216.0f / 24389.0f
128         private const val B = 841.0f / 108.0f
129         private const val C = 4.0f / 29.0f
130         private const val D = 6.0f / 29.0f
131     }
132 }
133