Lab.kt

/*
 * Copyright 2019 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 kotlin.math.pow

/**
 * Implementation of the CIE L*a*b* color space. Its PCS is CIE XYZ
 * with a white point of D50.
 */
internal class Lab(
    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) 0.0f else -128.0f
    }

    override fun getMaxValue(component: Int): Float {
        return if (component == 0) 100.0f else 128.0f
    }

    override fun toXyz(v: FloatArray): FloatArray {
        v[0] = v[0].coerceIn(0.0f, 100.0f)
        v[1] = v[1].coerceIn(-128.0f, 128.0f)
        v[2] = v[2].coerceIn(-128.0f, 128.0f)

        val fy = (v[0] + 16.0f) / 116.0f
        val fx = fy + (v[1] * 0.002f)
        val fz = fy - (v[2] * 0.005f)
        val x = if (fx > D) fx * fx * fx else (1.0f / B) * (fx - C)
        val y = if (fy > D) fy * fy * fy else (1.0f / B) * (fy - C)
        val z = if (fz > D) fz * fz * fz else (1.0f / B) * (fz - C)

        v[0] = x * Illuminant.D50Xyz[0]
        v[1] = y * Illuminant.D50Xyz[1]
        v[2] = z * Illuminant.D50Xyz[2]

        return v
    }

    override fun fromXyz(v: FloatArray): FloatArray {
        val x = v[0] / Illuminant.D50Xyz[0]
        val y = v[1] / Illuminant.D50Xyz[1]
        val z = v[2] / Illuminant.D50Xyz[2]

        val fx = if (x > A) x.pow(1f / 3f) else B * x + C
        val fy = if (y > A) y.pow(1f / 3f) else B * y + C
        val fz = if (z > A) z.pow(1f / 3f) else B * z + C

        val l = 116.0f * fy - 16.0f
        val a = 500.0f * (fx - fy)
        val b = 200.0f * (fy - fz)

        v[0] = l.coerceIn(0.0f, 100.0f)
        v[1] = a.coerceIn(-128.0f, 128.0f)
        v[2] = b.coerceIn(-128.0f, 128.0f)

        return v
    }

    internal companion object {
        private const val A = 216.0f / 24389.0f
        private const val B = 841.0f / 108.0f
        private const val C = 4.0f / 29.0f
        private const val D = 6.0f / 29.0f
    }
}