[orx-color] Add XSLUV a combination of HSLUV and XSV (Kuler hue remapping)
This commit is contained in:
68
orx-color/src/demo/kotlin/DemoXSLUV01.kt
Normal file
68
orx-color/src/demo/kotlin/DemoXSLUV01.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,7 @@
|
||||
package org.openrndr.extras.color.palettes
|
||||
|
||||
import org.openrndr.color.*
|
||||
import org.openrndr.extras.color.spaces.ColorHPLUVa
|
||||
import org.openrndr.extras.color.spaces.ColorHSLUVa
|
||||
import org.openrndr.extras.color.spaces.toHPLUVa
|
||||
import org.openrndr.extras.color.spaces.toHSLUVa
|
||||
|
||||
import org.openrndr.extras.color.spaces.*
|
||||
|
||||
|
||||
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 ColorHSLUVa -> right.second.toRGBa().toHSLUVa().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 ColorLCHABa -> right.second.toRGBa().toLCHABa().mix(l, nt).toRGBa()
|
||||
else -> error("unsupported color space: ${l::class}")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
fun toXSLUVa() : ColorXSLUVa {
|
||||
return ColorXSLUVa(hueToX(h), s, l, a)
|
||||
}
|
||||
|
||||
override fun shiftHue(shiftInDegrees: Double) = copy(h = h + (shiftInDegrees))
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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) :
|
||||
ConvertibleToColorRGBa,
|
||||
|
||||
Reference in New Issue
Block a user