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