[orx-color] Adjust ColorOKLABa / ColorOKLCHa luminosity and chroma ranges. Add maxChroma
This commit is contained in:
@@ -3,7 +3,9 @@ package org.openrndr.extra.color.spaces
|
|||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import org.openrndr.color.*
|
import org.openrndr.color.*
|
||||||
import org.openrndr.math.Vector4
|
import org.openrndr.math.Vector4
|
||||||
|
import kotlin.math.abs
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
|
import kotlin.math.sign
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Color in OKLab color space.
|
* Color in OKLab color space.
|
||||||
@@ -27,9 +29,10 @@ data class ColorOKLABa(val l: Double, val a: Double, val b: Double, override val
|
|||||||
val m = 0.2119034982 * c.r + 0.6806995451 * c.g + 0.1073969566 * c.b
|
val m = 0.2119034982 * c.r + 0.6806995451 * c.g + 0.1073969566 * c.b
|
||||||
val s = 0.0883024619 * c.r + 0.2817188376 * c.g + 0.6299787005 * c.b
|
val s = 0.0883024619 * c.r + 0.2817188376 * c.g + 0.6299787005 * c.b
|
||||||
|
|
||||||
val lnl = l.pow(1.0 / 3.0)
|
val lnl = abs(l).pow(1.0 / 3.0) * sign(l)
|
||||||
val mnl = m.pow(1.0 / 3.0)
|
val mnl = abs(m).pow(1.0 / 3.0) * sign(m)
|
||||||
val snl = s.pow(1.0 / 3.0)
|
val snl = abs(s).pow(1.0 / 3.0) * sign(s)
|
||||||
|
|
||||||
|
|
||||||
val L = 0.2104542553 * lnl + 0.7936177850 * mnl - 0.0040720468 * snl
|
val L = 0.2104542553 * lnl + 0.7936177850 * mnl - 0.0040720468 * snl
|
||||||
val a = 1.9779984951 * lnl - 2.4285922050 * mnl + 0.4505937099 * snl
|
val a = 1.9779984951 * lnl - 2.4285922050 * mnl + 0.4505937099 * snl
|
||||||
@@ -68,9 +71,9 @@ data class ColorOKLABa(val l: Double, val a: Double, val b: Double, override val
|
|||||||
|
|
||||||
override fun toVector4() = Vector4(l, a, b, alpha)
|
override fun toVector4() = Vector4(l, a, b, alpha)
|
||||||
override val luminosity: Double
|
override val luminosity: Double
|
||||||
get() = l
|
get() = l * 100.0
|
||||||
|
|
||||||
override fun withLuminosity(luminosity: Double): ColorOKLABa = copy(l = luminosity)
|
override fun withLuminosity(luminosity: Double): ColorOKLABa = copy(l = luminosity / 100.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ColorRGBa.toOKLABa() = ColorOKLABa.fromRGBa(this)
|
fun ColorRGBa.toOKLABa() = ColorOKLABa.fromRGBa(this)
|
||||||
@@ -51,16 +51,16 @@ data class ColorOKLCHa(val l: Double, val c: Double, val h: Double, override val
|
|||||||
override fun toRGBa(): ColorRGBa = toOKLABa().toRGBa()
|
override fun toRGBa(): ColorRGBa = toOKLABa().toRGBa()
|
||||||
override fun toVector4(): Vector4 = Vector4(l, c, h, alpha)
|
override fun toVector4(): Vector4 = Vector4(l, c, h, alpha)
|
||||||
override val chroma: Double
|
override val chroma: Double
|
||||||
get() = c
|
get() = c * 100.0
|
||||||
override fun withChroma(chroma: Double): ColorOKLCHa = copy(c = chroma)
|
override fun withChroma(chroma: Double): ColorOKLCHa = copy(c = chroma / 100.0)
|
||||||
override val hue: Double
|
override val hue: Double
|
||||||
get() = h
|
get() = h
|
||||||
|
|
||||||
override fun withHue(hue: Double): ColorOKLCHa = copy(h = hue)
|
override fun withHue(hue: Double): ColorOKLCHa = copy(h = hue)
|
||||||
override val luminosity: Double
|
override val luminosity: Double
|
||||||
get() = l
|
get() = l * 100.0
|
||||||
|
|
||||||
override fun withLuminosity(luminosity: Double): ColorOKLCHa = copy(l = luminosity)
|
override fun withLuminosity(luminosity: Double): ColorOKLCHa = copy(l = luminosity / 100.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun mix(left: ColorOKLCHa, right: ColorOKLCHa, x: Double): ColorOKLCHa {
|
fun mix(left: ColorOKLCHa, right: ColorOKLCHa, x: Double): ColorOKLCHa {
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package org.openrndr.extra.color.tools
|
||||||
|
|
||||||
|
import org.openrndr.color.ChromaColor
|
||||||
|
import org.openrndr.color.ConvertibleToColorRGBa
|
||||||
|
|
||||||
|
|
||||||
|
private fun binarySearchMax(min: Double, max: Double, start: Double, threshold: Double = 1E-5, f: (Double) -> Boolean): Double {
|
||||||
|
var low = min
|
||||||
|
var high = max
|
||||||
|
|
||||||
|
var best = min
|
||||||
|
var mid = start
|
||||||
|
while (low <= high) {
|
||||||
|
|
||||||
|
val res = f(mid)
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
best = mid
|
||||||
|
low = mid
|
||||||
|
} else {
|
||||||
|
high = mid
|
||||||
|
}
|
||||||
|
|
||||||
|
if (high - low < threshold) {
|
||||||
|
return best
|
||||||
|
}
|
||||||
|
mid = (low + high) / 2.0
|
||||||
|
}
|
||||||
|
return best
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> T.findMaxChroma(): Double
|
||||||
|
where T : ChromaColor<T>,
|
||||||
|
T : ConvertibleToColorRGBa {
|
||||||
|
return binarySearchMax(0.0, 200.0, chroma, 1E-5) {
|
||||||
|
val c = withChroma(it).toRGBa()
|
||||||
|
!c.isOutOfGamut
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun<T> T.clipChroma(): T
|
||||||
|
where T : ChromaColor<T>,
|
||||||
|
T : ConvertibleToColorRGBa {
|
||||||
|
|
||||||
|
val maxChroma = findMaxChroma()
|
||||||
|
return withChroma(maxChroma)
|
||||||
|
}
|
||||||
@@ -3,6 +3,10 @@ package org.openrndr.extra.color.tools
|
|||||||
import org.openrndr.color.*
|
import org.openrndr.color.*
|
||||||
import org.openrndr.extra.color.spaces.*
|
import org.openrndr.extra.color.spaces.*
|
||||||
|
|
||||||
|
val ColorRGBa.isOutOfGamut: Boolean
|
||||||
|
get() {
|
||||||
|
return (r !in -1E-3..1.0) || (g !in -1E-3..1.0) || (b !in -1E-3..1.0) || (alpha !in 0.0..1.0)
|
||||||
|
}
|
||||||
fun ColorRGBa.matchLinearity(other: ColorRGBa): ColorRGBa {
|
fun ColorRGBa.matchLinearity(other: ColorRGBa): ColorRGBa {
|
||||||
return if (other.linearity.isEquivalent(linearity)) {
|
return if (other.linearity.isEquivalent(linearity)) {
|
||||||
this
|
this
|
||||||
@@ -100,6 +104,31 @@ inline fun <reified T> ColorRGBa.mixedWith(other: ColorRGBa, factor: Double): Co
|
|||||||
return source.mix(target, factor).toRGBa().matchLinearity(this)
|
return source.mix(target, factor).toRGBa().matchLinearity(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> ColorRGBa.mixChroma(chroma: Double, factor: Double): ColorRGBa
|
||||||
|
where T : ChromaColor<T>,
|
||||||
|
T : ColorModel<T>,
|
||||||
|
T : ConvertibleToColorRGBa =
|
||||||
|
convertTo<T>().mixChroma(chroma, factor).toRGBa().matchLinearity(this)
|
||||||
|
|
||||||
|
inline fun <reified T> ColorRGBa.withChroma(chroma: Double): ColorRGBa
|
||||||
|
where T : ChromaColor<T>,
|
||||||
|
T : ColorModel<T>,
|
||||||
|
T : ConvertibleToColorRGBa =
|
||||||
|
convertTo<T>().withChroma(chroma).toRGBa().matchLinearity(this)
|
||||||
|
|
||||||
|
inline fun <reified T> ColorRGBa.chroma(): Double
|
||||||
|
where T : ChromaColor<T>,
|
||||||
|
T : ColorModel<T>,
|
||||||
|
T : ConvertibleToColorRGBa =
|
||||||
|
convertTo<T>().chroma
|
||||||
|
|
||||||
|
inline fun <reified T> ColorRGBa.modulateChroma(factor: Double): ColorRGBa
|
||||||
|
where T : ChromaColor<T>,
|
||||||
|
T : ColorModel<T>,
|
||||||
|
T : ConvertibleToColorRGBa =
|
||||||
|
convertTo<T>().modulateChroma(factor).toRGBa().matchLinearity(this)
|
||||||
|
|
||||||
|
|
||||||
inline fun <reified T> ColorRGBa.saturate(factor: Double): ColorRGBa
|
inline fun <reified T> ColorRGBa.saturate(factor: Double): ColorRGBa
|
||||||
where T : SaturatableColor<T>,
|
where T : SaturatableColor<T>,
|
||||||
T : ColorModel<T>,
|
T : ColorModel<T>,
|
||||||
@@ -110,3 +139,13 @@ inline fun <reified T> ColorRGBa.shiftHue(degrees: Double): ColorRGBa where
|
|||||||
T : ColorModel<T>,
|
T : ColorModel<T>,
|
||||||
T : ConvertibleToColorRGBa = convertTo<T>().shiftHue(degrees).toRGBa().matchLinearity(this)
|
T : ConvertibleToColorRGBa = convertTo<T>().shiftHue(degrees).toRGBa().matchLinearity(this)
|
||||||
|
|
||||||
|
inline fun <reified T> ColorRGBa.clipChroma(): ColorRGBa
|
||||||
|
where T : ChromaColor<T>,
|
||||||
|
T : ColorModel<T>,
|
||||||
|
T : ConvertibleToColorRGBa =
|
||||||
|
|
||||||
|
if (isOutOfGamut) {
|
||||||
|
convertTo<T>().clipChroma().toRGBa().matchLinearity(this).clip()
|
||||||
|
} else {
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|||||||
26
orx-color/src/commonTest/kotlin/spaces/TestOKLCHa.kt
Normal file
26
orx-color/src/commonTest/kotlin/spaces/TestOKLCHa.kt
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package spaces
|
||||||
|
|
||||||
|
import org.openrndr.color.ColorRGBa
|
||||||
|
import org.openrndr.extra.color.spaces.OKLCH
|
||||||
|
import org.openrndr.extra.color.spaces.toOKLCHa
|
||||||
|
import org.openrndr.extra.color.tools.chroma
|
||||||
|
import org.openrndr.extra.color.tools.withLuminosity
|
||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
|
class TestOKLCHa {
|
||||||
|
@Test
|
||||||
|
fun testLowLuminosity() {
|
||||||
|
val c = ColorRGBa.GREEN.withLuminosity<OKLCH>(10.0)
|
||||||
|
println(c)
|
||||||
|
println(c.chroma<OKLCH>())
|
||||||
|
|
||||||
|
|
||||||
|
val g = ColorRGBa.GREEN.toOKLCHa()
|
||||||
|
val gl10 = g.withLuminosity(10.0)
|
||||||
|
val rgb10 = gl10.toRGBa()
|
||||||
|
println(gl10)
|
||||||
|
println(rgb10)
|
||||||
|
println(rgb10.toOKLCHa())
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package tools
|
||||||
|
|
||||||
|
import org.openrndr.color.ColorRGBa
|
||||||
|
import org.openrndr.extra.color.spaces.toOKLCHa
|
||||||
|
import org.openrndr.extra.color.tools.findMaxChroma
|
||||||
|
import org.openrndr.extra.color.tools.isOutOfGamut
|
||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
|
class TestChromaColorExtensions {
|
||||||
|
@Test
|
||||||
|
fun testFindMaxChroma() {
|
||||||
|
|
||||||
|
run {
|
||||||
|
val i = ColorRGBa.BLUE.toLCHABa()//.withLuminosity(50.0)
|
||||||
|
val maxChroma = i.findMaxChroma()
|
||||||
|
println(i.chroma)
|
||||||
|
println(maxChroma)
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val i = ColorRGBa.BLUE.toOKLCHa()//.withLuminosity(50.0)
|
||||||
|
val maxChroma = i.findMaxChroma()
|
||||||
|
println(ColorRGBa.BLUE.isOutOfGamut)
|
||||||
|
println(i.chroma)
|
||||||
|
println(maxChroma)
|
||||||
|
println(i.toRGBa())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user