[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 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)
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.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
|
||||
}
|
||||
|
||||
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