[orx-color] Add XSLUV a combination of HSLUV and XSV (Kuler hue remapping)

This commit is contained in:
Edwin Jakobs
2020-08-22 15:44:13 +02:00
parent 6d83972d03
commit ab67579250
3 changed files with 152 additions and 5 deletions

View File

@@ -0,0 +1,68 @@
// Visualize XSLUV color space by drawing a recursively subdivided arcs
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.extensions.SingleScreenshot
import org.openrndr.extras.color.spaces.ColorXSLUVa
import org.openrndr.extras.color.spaces.toHSLUVa
import org.openrndr.math.Polar
import org.openrndr.shape.contour
fun main() {
class Arc(val start: Double, val radius: Double, val length: Double, val height: Double) {
fun split(offset: Double = 0.0): List<Arc> {
val hl = length / 2.0
return listOf(Arc(start, radius + offset, hl, height), Arc(start + hl, radius + offset, hl, height))
}
val contour
get() = contour {
moveTo(Polar(start, radius).cartesian)
arcTo(radius, radius, length, false, true, Polar(start + length, radius).cartesian)
lineTo(Polar(start + length, radius + height).cartesian)
arcTo(radius + height, radius + height, length, false, false, Polar(start, radius + height).cartesian)
lineTo(anchor)
close()
}
}
fun List<Arc>.split(depth: Int): List<Arc> = if (depth == 0) {
this
} else {
this + flatMap { it.split(it.height) }.split(depth - 1)
}
application {
configure {
width = 720
height = 720
}
program {
// -- this block is for automation purposes only
if (System.getProperty("takeScreenshot") == "true") {
extend(SingleScreenshot()) {
this.outputFile = System.getProperty("screenshotPath")
}
}
val arcs = (0..4).map { Arc(it * 90.0 - 45.0, 50.0, 90.0, 50.0) }.split(5)
extend {
drawer.clear(ColorRGBa.GRAY)
val color = ColorRGBa.RED
val hc = color.toHSLUVa()
drawer.stroke = ColorRGBa.BLACK
drawer.strokeWeight = 1.0
drawer.translate(drawer.bounds.center)
val l = if (System.getProperty("takeScreenshot") == "true") 0.7 else mouse.position.y / height
val s = if (System.getProperty("takeScreenshot") == "true") 1.0 else mouse.position.x / width
for (arc in arcs) {
val xsluv = ColorXSLUVa(arc.start + arc.length / 2.0, s, l, 1.0)
drawer.fill = xsluv.toRGBa()
drawer.contour(arc.contour)
}
}
}
}
}

View File

@@ -1,11 +1,7 @@
package org.openrndr.extras.color.palettes package org.openrndr.extras.color.palettes
import org.openrndr.color.* import org.openrndr.color.*
import org.openrndr.extras.color.spaces.ColorHPLUVa import org.openrndr.extras.color.spaces.*
import org.openrndr.extras.color.spaces.ColorHSLUVa
import org.openrndr.extras.color.spaces.toHPLUVa
import org.openrndr.extras.color.spaces.toHSLUVa
fun <T> colorSequence(vararg offsets: Pair<Double, T>): ColorSequence fun <T> colorSequence(vararg offsets: Pair<Double, T>): ColorSequence
@@ -52,6 +48,7 @@ class ColorSequence(val colors: List<Pair<Double, ConvertibleToColorRGBa>>) {
is ColorLUVa -> right.second.toRGBa().toLUVa().mix(l, nt).toRGBa() is ColorLUVa -> right.second.toRGBa().toLUVa().mix(l, nt).toRGBa()
is ColorHSLUVa -> right.second.toRGBa().toHSLUVa().mix(l, nt).toRGBa() is ColorHSLUVa -> right.second.toRGBa().toHSLUVa().mix(l, nt).toRGBa()
is ColorHPLUVa -> right.second.toRGBa().toHPLUVa().mix(l, nt).toRGBa() is ColorHPLUVa -> right.second.toRGBa().toHPLUVa().mix(l, nt).toRGBa()
is ColorXSLUVa -> right.second.toRGBa().toXSLUVa().mix(l, nt).toRGBa()
is ColorLCHUVa -> right.second.toRGBa().toLCHUVa().mix(l, nt).toRGBa() is ColorLCHUVa -> right.second.toRGBa().toLCHUVa().mix(l, nt).toRGBa()
is ColorLCHABa -> right.second.toRGBa().toLCHABa().mix(l, nt).toRGBa() is ColorLCHABa -> right.second.toRGBa().toLCHABa().mix(l, nt).toRGBa()
else -> error("unsupported color space: ${l::class}") else -> error("unsupported color space: ${l::class}")

View File

@@ -105,6 +105,10 @@ data class ColorHSLUVa(val h: Double, val s: Double, val l: Double, val a: Doubl
return ColorLCHUVa(l100, c, h, a) return ColorLCHUVa(l100, c, h, a)
} }
fun toXSLUVa() : ColorXSLUVa {
return ColorXSLUVa(hueToX(h), s, l, a)
}
override fun shiftHue(shiftInDegrees: Double) = copy(h = h + (shiftInDegrees)) override fun shiftHue(shiftInDegrees: Double) = copy(h = h + (shiftInDegrees))
override fun shade(factor: Double) = copy(l = l * factor) override fun shade(factor: Double) = copy(l = l * factor)
@@ -136,6 +140,84 @@ fun mix(left: ColorHSLUVa, right: ColorHSLUVa, x: Double): ColorHSLUVa {
(1.0 - sx) * left.a + sx * right.a) (1.0 - sx) * left.a + sx * right.a)
} }
data class ColorXSLUVa(val x: Double, val s: Double, val l: Double, val a: Double):
ConvertibleToColorRGBa,
HueShiftableColor<ColorXSLUVa>,
SaturatableColor<ColorXSLUVa>,
ShadableColor<ColorXSLUVa>,
OpacifiableColor<ColorXSLUVa>,
AlgebraicColor<ColorXSLUVa> {
override fun shiftHue(shiftInDegrees: Double) = copy(x = x + (shiftInDegrees))
override fun shade(factor: Double) = copy(l = l * factor)
override fun saturate(factor: Double) = copy(s = s * factor)
override fun toRGBa(): ColorRGBa {
return toHSLUVa().toRGBa()
}
fun toHSLUVa(): ColorHSLUVa = ColorHSLUVa(xToHue(x), s, l, a)
override fun opacify(factor: Double) = copy(a = a * factor)
override fun minus(other: ColorXSLUVa) = copy(x = x - other.x, s = s - other.s, l = l - other.l, a = a - other.a)
override fun plus(other: ColorXSLUVa) = copy(x = x + other.x, s = s + other.s, l = l + other.l, a = a + other.a)
override fun times(factor: Double) = copy(x = x * factor, s = s * factor, l = l * factor, a = a * factor)
override fun mix(other: ColorXSLUVa, factor: Double) = mix(this, other, factor)
}
fun ColorRGBa.toXSLUVa() = toHSLUVa().toXSLUVa()
fun mix(left: ColorXSLUVa, right: ColorXSLUVa, x: Double): ColorXSLUVa {
val sx = x.coerceIn(0.0, 1.0)
return ColorXSLUVa(
mixAngle(left.x, right.x, sx),
(1.0 - sx) * left.s + sx * right.s,
(1.0 - sx) * left.l + sx * right.l,
(1.0 - sx) * left.a + sx * right.a)
}
private fun map(x: Double, a: Double, b: Double, c: Double, d: Double): Double {
return ((x - a) / (b - a)) * (d - c) + c
}
private fun hueToX(hue:Double): Double {
val h = ((hue % 360.0) + 360.0) % 360.0
return if (0 <= h && h < 35) {
map(h, 0.0, 35.0, 0.0, 60.0)
} else if (35 <= h && h < 60) {
map(h, 35.0, 60.0, 60.0, 120.0)
} else if (60 <= h && h < 135.0) {
map(h, 60.0, 135.0, 120.0, 180.0)
} else if (135.0 <= h && h < 225.0) {
map(h, 135.0, 225.0, 180.0, 240.0)
} else if (225.0 <= h && h < 275.0) {
map(h, 225.0, 275.0, 240.0, 300.0)
} else {
map(h, 276.0, 360.0, 300.0, 360.0)
}
}
private fun xToHue(x:Double) : Double {
val x = x % 360.0
return if (0.0 <= x && x < 60.0) {
map(x, 0.0, 60.0, 0.0, 35.0)
} else if (60.0 <= x && x < 120.0) {
map(x, 60.0, 120.0, 35.0, 60.0)
} else if (120.0 <= x && x < 180.0) {
map(x, 120.0, 180.0, 60.0, 135.0)
} else if (180.0 <= x && x < 240.0) {
map(x, 180.0, 240.0, 135.0, 225.0)
} else if (240.0 <= x && x < 300.0) {
map(x, 240.0, 300.0, 225.0, 275.0)
} else {
map(x, 300.0, 360.0, 276.0, 360.0)
}
}
data class ColorHPLUVa(val h: Double, val s: Double, val l: Double, val a: Double = 1.0) : data class ColorHPLUVa(val h: Double, val s: Double, val l: Double, val a: Double = 1.0) :
ConvertibleToColorRGBa, ConvertibleToColorRGBa,