/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.ui.graphics.colorspace
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.util.packFloats
import kotlin.math.abs
import kotlin.math.pow
import kotlin.math.sign
/**
* Implementation of the Oklab color space. Oklab uses
* a D65 white point.
*/
internal class Oklab(
name: String,
id: Int
) : ColorSpace(
name,
ColorModel.Lab, id
) {
override val isWideGamut: Boolean
get() = true
override fun getMinValue(component: Int): Float {
return if (component == 0) 0f else -0.5f
}
override fun getMaxValue(component: Int): Float {
return if (component == 0) 1f else 0.5f
}
override fun toXyz(v: FloatArray): FloatArray {
v[0] = v[0].coerceIn(0f, 1f)
v[1] = v[1].coerceIn(-0.5f, 0.5f)
v[2] = v[2].coerceIn(-0.5f, 0.5f)
mul3x3Float3(InverseM2, v)
v[0] = v[0] * v[0] * v[0]
v[1] = v[1] * v[1] * v[1]
v[2] = v[2] * v[2] * v[2]
mul3x3Float3(InverseM1, v)
return v
}
override fun toXy(v0: Float, v1: Float, v2: Float): Long {
val v00 = v0.coerceIn(0f, 1f)
val v10 = v1.coerceIn(-0.5f, 0.5f)
val v20 = v2.coerceIn(-0.5f, 0.5f)
val v01 = mul3x3Float3_0(InverseM2, v00, v10, v20)
val v11 = mul3x3Float3_1(InverseM2, v00, v10, v20)
val v21 = mul3x3Float3_2(InverseM2, v00, v10, v20)
val v02 = v01 * v01 * v01
val v12 = v11 * v11 * v11
val v22 = v21 * v21 * v21
val v03 = mul3x3Float3_0(InverseM1, v02, v12, v22)
val v13 = mul3x3Float3_1(InverseM1, v02, v12, v22)
return packFloats(v03, v13)
}
override fun toZ(v0: Float, v1: Float, v2: Float): Float {
val v00 = v0.coerceIn(0f, 1f)
val v10 = v1.coerceIn(-0.5f, 0.5f)
val v20 = v2.coerceIn(-0.5f, 0.5f)
val v01 = mul3x3Float3_0(InverseM2, v00, v10, v20)
val v11 = mul3x3Float3_1(InverseM2, v00, v10, v20)
val v21 = mul3x3Float3_2(InverseM2, v00, v10, v20)
val v02 = v01 * v01 * v01
val v12 = v11 * v11 * v11
val v22 = v21 * v21 * v21
val v23 = mul3x3Float3_2(InverseM1, v02, v12, v22)
return v23
}
override fun xyzaToColor(
x: Float,
y: Float,
z: Float,
a: Float,
colorSpace: ColorSpace
): Color {
var v0 = mul3x3Float3_0(M1, x, y, z)
var v1 = mul3x3Float3_1(M1, x, y, z)
var v2 = mul3x3Float3_2(M1, x, y, z)
v0 = sign(v0) * abs(v0).pow(1.0f / 3.0f)
v1 = sign(v1) * abs(v1).pow(1.0f / 3.0f)
v2 = sign(v2) * abs(v2).pow(1.0f / 3.0f)
val v01 = mul3x3Float3_0(M2, v0, v1, v2)
val v11 = mul3x3Float3_1(M2, v0, v1, v2)
val v21 = mul3x3Float3_2(M2, v0, v1, v2)
return Color(v01, v11, v21, a, colorSpace)
}
override fun fromXyz(v: FloatArray): FloatArray {
mul3x3Float3(M1, v)
v[0] = sign(v[0]) * abs(v[0]).pow(1.0f / 3.0f)
v[1] = sign(v[1]) * abs(v[1]).pow(1.0f / 3.0f)
v[2] = sign(v[2]) * abs(v[2]).pow(1.0f / 3.0f)
mul3x3Float3(M2, v)
return v
}
internal companion object {
/**
* This is the matrix applied before the nonlinear transform for (D50) XYZ-to-Oklab.
* This combines the D50-to-D65 white point transform with the normal transform matrix
* because this is always done together in [fromXyz].
*/
private val M1 = mul3x3(
floatArrayOf(
0.8189330101f, 0.0329845436f, 0.0482003018f,
0.3618667424f, 0.9293118715f, 0.2643662691f,
-0.1288597137f, 0.0361456387f, 0.6338517070f
),
chromaticAdaptation(
matrix = Adaptation.Bradford.transform,
srcWhitePoint = Illuminant.D50.toXyz(),
dstWhitePoint = Illuminant.D65.toXyz()
)
)
/**
* Matrix applied after the nonlinear transform.
*/
private val M2 = floatArrayOf(
0.2104542553f, 1.9779984951f, 0.0259040371f,
0.7936177850f, -2.4285922050f, 0.7827717662f,
-0.0040720468f, 0.4505937099f, -0.8086757660f
)
/**
* The inverse of the [M1] matrix, transforming back to XYZ (D50)
*/
private val InverseM1 = inverse3x3(M1)
/**
* The inverse of the [M2] matrix, doing the first linear transform in the
* Oklab-to-XYZ before doing the nonlinear transform.
*/
private val InverseM2 = inverse3x3(M2)
}
}