[orx-color] Adjust ColorOKLABa / ColorOKLCHa luminosity and chroma ranges. Add maxChroma

This commit is contained in:
Edwin Jakobs
2023-12-13 14:48:38 +01:00
parent 9ed92b5b19
commit b243952c7a
6 changed files with 155 additions and 9 deletions

View File

@@ -3,7 +3,9 @@ package org.openrndr.extra.color.spaces
import kotlinx.serialization.Serializable
import org.openrndr.color.*
import org.openrndr.math.Vector4
import kotlin.math.abs
import kotlin.math.pow
import kotlin.math.sign
/**
* 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 s = 0.0883024619 * c.r + 0.2817188376 * c.g + 0.6299787005 * c.b
val lnl = l.pow(1.0 / 3.0)
val mnl = m.pow(1.0 / 3.0)
val snl = s.pow(1.0 / 3.0)
val lnl = abs(l).pow(1.0 / 3.0) * sign(l)
val mnl = abs(m).pow(1.0 / 3.0) * sign(m)
val snl = abs(s).pow(1.0 / 3.0) * sign(s)
val L = 0.2104542553 * lnl + 0.7936177850 * mnl - 0.0040720468 * 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 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)

View File

@@ -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 toVector4(): Vector4 = Vector4(l, c, h, alpha)
override val chroma: Double
get() = c
override fun withChroma(chroma: Double): ColorOKLCHa = copy(c = chroma)
get() = c * 100.0
override fun withChroma(chroma: Double): ColorOKLCHa = copy(c = chroma / 100.0)
override val hue: Double
get() = h
override fun withHue(hue: Double): ColorOKLCHa = copy(h = hue)
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 {

View File

@@ -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)
}

View File

@@ -3,6 +3,10 @@ package org.openrndr.extra.color.tools
import org.openrndr.color.*
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 {
return if (other.linearity.isEquivalent(linearity)) {
this
@@ -100,6 +104,31 @@ inline fun <reified T> ColorRGBa.mixedWith(other: ColorRGBa, factor: Double): Co
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
where T : SaturatableColor<T>,
T : ColorModel<T>,
@@ -110,3 +139,13 @@ inline fun <reified T> ColorRGBa.shiftHue(degrees: Double): ColorRGBa where
T : ColorModel<T>,
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
}

View 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())
}
}

View File

@@ -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())
}
}
}