Bump to OPENRNDR 0.3.44-rc.11, add colorSequence
This commit is contained in:
@@ -18,7 +18,7 @@ def openrndrUseSnapshot = false
|
||||
apply plugin: 'org.jetbrains.dokka'
|
||||
|
||||
project.ext {
|
||||
openrndrVersion = openrndrUseSnapshot? "0.4.0-SNAPSHOT" : "0.3.44-rc.10"
|
||||
openrndrVersion = openrndrUseSnapshot? "0.4.0-SNAPSHOT" : "0.3.44-rc.11"
|
||||
kotlinVersion = "1.4.0"
|
||||
spekVersion = "2.0.12"
|
||||
libfreenectVersion = "0.5.7-1.5.3"
|
||||
|
||||
@@ -15,6 +15,35 @@ val histogram = calculateHistogramRGB(image)
|
||||
val colors = histogram.sortedColors()
|
||||
```
|
||||
|
||||
## Color sequences
|
||||
|
||||
Easy ways of creating blends between colors.
|
||||
|
||||
Using the `rangeTo` operator:
|
||||
```
|
||||
for (c in ColorRGBa.PINK..ColorRGBa.BLUE.toHSVa() blend 10) {
|
||||
drawer.fill = c
|
||||
drawer.rectangle(0.0, 0.0, 40.0, 40.0)
|
||||
drawer.translate(0.0, 40.0)
|
||||
}
|
||||
```
|
||||
|
||||
Or blends for multiple color stops using `colorSequence`. Blending takes place in the colorspace of the input arguments.
|
||||
```
|
||||
val cs = colorSequence(0.0 to ColorRGBa.PINK,
|
||||
0.5 to ColorRGBa.BLUE,
|
||||
1.0 to ColorRGBa.PINK.toHSLUVa()) // <-- note this one is in hsluv
|
||||
|
||||
for (c in cs blend (width / 40)) {
|
||||
drawer.fill = c
|
||||
drawer.stroke = null
|
||||
drawer.rectangle(0.0, 0.0, 40.0, height.toDouble())
|
||||
drawer.translate(40.0, 0.0)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## HSLUVa and HPLUVa colorspaces
|
||||
|
||||
Two color spaces are added: `ColorHSLUVa` and `ColorHPLUVa`, they are an implementation of the colorspaces presented at [hsluv.org](http://www.hsluv.org)
|
||||
|
||||
77
orx-color/src/demo/kotlin/DemoColorRange01.kt
Normal file
77
orx-color/src/demo/kotlin/DemoColorRange01.kt
Normal file
@@ -0,0 +1,77 @@
|
||||
// Create a simple rectangle composition based on colors sampled from image
|
||||
|
||||
import org.openrndr.application
|
||||
import org.openrndr.color.ColorRGBa
|
||||
import org.openrndr.draw.isolated
|
||||
import org.openrndr.extensions.SingleScreenshot
|
||||
import org.openrndr.extras.color.palettes.rangeTo
|
||||
import org.openrndr.extras.color.presets.CORAL
|
||||
import org.openrndr.extras.color.spaces.toHSLUVa
|
||||
|
||||
fun main() = application {
|
||||
program {
|
||||
// -- this block is for automation purposes only
|
||||
if (System.getProperty("takeScreenshot") == "true") {
|
||||
extend(SingleScreenshot()) {
|
||||
this.outputFile = System.getProperty("screenshotPath")
|
||||
}
|
||||
}
|
||||
extend {
|
||||
|
||||
drawer.isolated {
|
||||
for (c in ColorRGBa.PINK..ColorRGBa.BLUE.toHSVa() blend 10) {
|
||||
drawer.fill = c
|
||||
drawer.rectangle(0.0, 0.0, 40.0, 40.0)
|
||||
drawer.translate(0.0, 40.0)
|
||||
}
|
||||
}
|
||||
drawer.translate(50.0, 0.0)
|
||||
drawer.isolated {
|
||||
for (c in ColorRGBa.PINK..ColorRGBa.BLUE blend 10) {
|
||||
drawer.fill = c
|
||||
drawer.rectangle(0.0, 0.0, 40.0, 40.0)
|
||||
drawer.translate(0.0, 40.0)
|
||||
}
|
||||
}
|
||||
drawer.translate(50.0, 0.0)
|
||||
|
||||
drawer.isolated {
|
||||
for (c in ColorRGBa.PINK..ColorRGBa.BLUE.toHSLUVa() blend 10) {
|
||||
drawer.fill = c.toSRGB()
|
||||
drawer.rectangle(0.0, 0.0, 40.0, 40.0)
|
||||
drawer.translate(0.0, 40.0)
|
||||
}
|
||||
}
|
||||
|
||||
drawer.translate(50.0, 0.0)
|
||||
|
||||
drawer.isolated {
|
||||
for (c in ColorRGBa.PINK..ColorRGBa.BLUE.toXSVa() blend 10) {
|
||||
drawer.fill = c.toSRGB()
|
||||
drawer.rectangle(0.0, 0.0, 40.0, 40.0)
|
||||
drawer.translate(0.0, 40.0)
|
||||
}
|
||||
}
|
||||
|
||||
drawer.translate(50.0, 0.0)
|
||||
|
||||
drawer.isolated {
|
||||
for (c in ColorRGBa.PINK..ColorRGBa.BLUE.toLUVa() blend 10) {
|
||||
drawer.fill = c.toSRGB()
|
||||
drawer.rectangle(0.0, 0.0, 40.0, 40.0)
|
||||
drawer.translate(0.0, 40.0)
|
||||
}
|
||||
}
|
||||
|
||||
drawer.translate(50.0, 0.0)
|
||||
|
||||
drawer.isolated {
|
||||
for (c in ColorRGBa.PINK..ColorRGBa.BLUE.toLCHUVa() blend 10) {
|
||||
drawer.fill = c.toSRGB()
|
||||
drawer.rectangle(0.0, 0.0, 40.0, 40.0)
|
||||
drawer.translate(0.0, 40.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
33
orx-color/src/demo/kotlin/DemoColorRange02.kt
Normal file
33
orx-color/src/demo/kotlin/DemoColorRange02.kt
Normal file
@@ -0,0 +1,33 @@
|
||||
// Create a colorSequence with multiple color models
|
||||
|
||||
import org.openrndr.application
|
||||
import org.openrndr.color.ColorRGBa
|
||||
import org.openrndr.draw.isolated
|
||||
import org.openrndr.extensions.SingleScreenshot
|
||||
import org.openrndr.extras.color.palettes.colorSequence
|
||||
import org.openrndr.extras.color.palettes.rangeTo
|
||||
import org.openrndr.extras.color.presets.CORAL
|
||||
import org.openrndr.extras.color.spaces.toHSLUVa
|
||||
|
||||
fun main() = application {
|
||||
program {
|
||||
// -- this block is for automation purposes only
|
||||
if (System.getProperty("takeScreenshot") == "true") {
|
||||
extend(SingleScreenshot()) {
|
||||
this.outputFile = System.getProperty("screenshotPath")
|
||||
}
|
||||
}
|
||||
extend {
|
||||
val cs = colorSequence(0.0 to ColorRGBa.PINK,
|
||||
0.5 to ColorRGBa.BLUE,
|
||||
1.0 to ColorRGBa.PINK.toHSLUVa()) // <-- note this one is in hsluv
|
||||
|
||||
for (c in cs blend (width / 40)) {
|
||||
drawer.fill = c
|
||||
drawer.stroke = null
|
||||
drawer.rectangle(0.0, 0.0, 40.0, height.toDouble())
|
||||
drawer.translate(40.0, 0.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
77
orx-color/src/main/kotlin/palettes/Palettes.kt
Normal file
77
orx-color/src/main/kotlin/palettes/Palettes.kt
Normal file
@@ -0,0 +1,77 @@
|
||||
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
|
||||
|
||||
|
||||
fun main() {
|
||||
for (c in ColorRGBa.PINK..ColorRGBa.GRAY blend 10) {
|
||||
|
||||
}
|
||||
for (c in ColorRGBa.PINK..ColorHSVa(20.0, 0.5, 0.5) blend 10) {
|
||||
println(c)
|
||||
}
|
||||
|
||||
colorSequence(
|
||||
0.0 to ColorRGBa.PINK,
|
||||
0.5 to ColorRGBa.RED,
|
||||
1.0 to hsv(360.0, 1.0, 1.0)
|
||||
).index(0.8)
|
||||
|
||||
}
|
||||
|
||||
fun <T> colorSequence(vararg offsets: Pair<Double, T>): ColorSequence
|
||||
where T : ConvertibleToColorRGBa {
|
||||
return ColorSequence(offsets.sortedBy { it.first })
|
||||
}
|
||||
|
||||
class ColorSequence(val colors: List<Pair<Double, ConvertibleToColorRGBa>>) {
|
||||
infix fun blend(steps: Int): List<ColorRGBa> = index(0.0, 1.0, steps)
|
||||
|
||||
fun index(t0: Double, t1: Double, steps: Int) = (0 until steps).map {
|
||||
val f = (it / (steps+0.0))
|
||||
val t = t0 * (1.0 - f) + t1 * f
|
||||
index(t)
|
||||
}
|
||||
|
||||
fun index(t: Double): ColorRGBa {
|
||||
if (colors.size == 1) {
|
||||
return colors.first().second.toRGBa()
|
||||
}
|
||||
if (t < colors[0].first) {
|
||||
return colors[0].second.toRGBa()
|
||||
}
|
||||
if (t >= colors.last().first) {
|
||||
return colors.last().second.toRGBa()
|
||||
}
|
||||
val rightIndex = colors.indexOfLast { it.first <= t }
|
||||
val leftIndex = (rightIndex + 1).coerceIn(0, colors.size - 1)
|
||||
|
||||
val right = colors[rightIndex]
|
||||
val left = colors[leftIndex]
|
||||
|
||||
val rt = t - right.first
|
||||
val dt = left.first - right.first
|
||||
val nt = rt / dt
|
||||
|
||||
return when (val l = left.second) {
|
||||
is ColorRGBa -> right.second.toRGBa().mix(l, nt)
|
||||
is ColorHSVa -> right.second.toRGBa().toHSVa().mix(l, nt).toRGBa()
|
||||
is ColorHSLa -> right.second.toRGBa().toHSLa().mix(l, nt).toRGBa()
|
||||
is ColorXSVa -> right.second.toRGBa().toXSVa().mix(l, nt).toRGBa()
|
||||
is ColorXSLa -> right.second.toRGBa().toXSLa().mix(l, nt).toRGBa()
|
||||
is ColorLABa -> right.second.toRGBa().toLABa().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 ColorHPLUVa -> right.second.toRGBa().toHPLUVa().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}")
|
||||
}.toSRGB()
|
||||
}
|
||||
}
|
||||
|
||||
operator fun ConvertibleToColorRGBa.rangeTo(end: ConvertibleToColorRGBa) = colorSequence(0.0 to this, 1.0 to end)
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.openrndr.extras.color.spaces
|
||||
|
||||
import org.openrndr.color.ColorLCHUVa
|
||||
import org.openrndr.color.ColorRGBa
|
||||
import org.openrndr.color.*
|
||||
import org.openrndr.math.mixAngle
|
||||
import java.util.*
|
||||
import kotlin.math.min
|
||||
import kotlin.math.pow
|
||||
@@ -17,7 +17,7 @@ private val epsilon = 0.0088564516
|
||||
|
||||
private fun getBounds(L: Double): List<DoubleArray>? {
|
||||
val result = ArrayList<DoubleArray>()
|
||||
val sub1 = Math.pow(L + 16, 3.0) / 1560896
|
||||
val sub1 = (L + 16).pow(3.0) / 1560896
|
||||
val sub2 = if (sub1 > epsilon) sub1 else L / kappa
|
||||
for (c in 0..2) {
|
||||
val m1 = m[c][0]
|
||||
@@ -77,35 +77,66 @@ fun maxChromaForLH(L: Double, H: Double): Double {
|
||||
return min
|
||||
}
|
||||
|
||||
data class ColorHSLUVa(val h: Double, val s: Double, val l: Double) {
|
||||
data class ColorHSLUVa(val h: Double, val s: Double, val l: Double, val a: Double = 1.0) :
|
||||
ConvertibleToColorRGBa,
|
||||
HueShiftableColor<ColorHSLUVa>,
|
||||
SaturatableColor<ColorHSLUVa>,
|
||||
ShadableColor<ColorHSLUVa>,
|
||||
OpacifiableColor<ColorHSLUVa>,
|
||||
AlgebraicColor<ColorHSLUVa> {
|
||||
fun toLCHUVa(): ColorLCHUVa {
|
||||
if (l > 99.9999999) {
|
||||
ColorLCHUVa(100.0, 0.0, h)
|
||||
ColorLCHUVa(100.0, 0.0, h, a)
|
||||
}
|
||||
|
||||
if (l < 0.00000001) {
|
||||
ColorLCHUVa(0.0, 0.0, h)
|
||||
ColorLCHUVa(0.0, 0.0, h, a)
|
||||
}
|
||||
val max = maxChromaForLH(l, h)
|
||||
val c: Double = max / 100 * s
|
||||
|
||||
return ColorLCHUVa(l, c, h)
|
||||
return ColorLCHUVa(l, c, h, a)
|
||||
}
|
||||
|
||||
fun shiftHue(shiftInDegrees: Double): ColorHSLUVa {
|
||||
return copy(h = h + (shiftInDegrees))
|
||||
}
|
||||
override fun shiftHue(shiftInDegrees: Double) = copy(h = h + (shiftInDegrees))
|
||||
|
||||
fun shade(factor: Double): ColorHSLUVa = copy(l = l * factor)
|
||||
override fun shade(factor: Double) = copy(l = l * factor)
|
||||
|
||||
fun saturate(factor: Double): ColorHSLUVa = copy(s = s * factor)
|
||||
override fun saturate(factor: Double) = copy(s = s * factor)
|
||||
|
||||
fun toRGBa(): ColorRGBa {
|
||||
override fun toRGBa(): ColorRGBa {
|
||||
return toLCHUVa().toRGBa()
|
||||
}
|
||||
|
||||
override fun opacify(factor: Double) = copy(a = a * factor)
|
||||
|
||||
override fun minus(other: ColorHSLUVa) = copy(h = h - other.h, s = s - other.s, l = l - other.l, a = a - other.a)
|
||||
|
||||
override fun plus(other: ColorHSLUVa) = copy(h = h + other.h, s = s + other.s, l = l + other.l, a = a + other.a)
|
||||
|
||||
override fun times(factor: Double) = copy(h = h * factor, s = s * factor, l = l * factor, a = a * factor)
|
||||
|
||||
override fun mix(other: ColorHSLUVa, factor: Double) = mix(this, other, factor)
|
||||
|
||||
}
|
||||
|
||||
data class ColorHPLUVa(val h: Double, val s: Double, val l: Double) {
|
||||
fun mix(left: ColorHSLUVa, right: ColorHSLUVa, x: Double): ColorHSLUVa {
|
||||
val sx = x.coerceIn(0.0, 1.0)
|
||||
return ColorHSLUVa(
|
||||
mixAngle(left.h, right.h, 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)
|
||||
}
|
||||
|
||||
|
||||
data class ColorHPLUVa(val h: Double, val s: Double, val l: Double, val a: Double = 1.0) :
|
||||
ConvertibleToColorRGBa,
|
||||
HueShiftableColor<ColorHPLUVa>,
|
||||
SaturatableColor<ColorHPLUVa>,
|
||||
ShadableColor<ColorHPLUVa>,
|
||||
OpacifiableColor<ColorHPLUVa>,
|
||||
AlgebraicColor<ColorHPLUVa> {
|
||||
fun toLCHUVa(): ColorLCHUVa {
|
||||
if (l > 99.9999999) {
|
||||
return ColorLCHUVa(100.0, 0.0, h)
|
||||
@@ -117,17 +148,39 @@ data class ColorHPLUVa(val h: Double, val s: Double, val l: Double) {
|
||||
val c = max / 100 * s
|
||||
return ColorLCHUVa(l, c, h)
|
||||
}
|
||||
fun shiftHue(shiftInDegrees: Double): ColorHPLUVa {
|
||||
|
||||
override fun shiftHue(shiftInDegrees: Double): ColorHPLUVa {
|
||||
return copy(h = h + (shiftInDegrees))
|
||||
}
|
||||
|
||||
fun shade(factor: Double): ColorHPLUVa = copy(l = l * factor)
|
||||
override fun shade(factor: Double): ColorHPLUVa = copy(l = l * factor)
|
||||
|
||||
fun saturate(factor: Double): ColorHPLUVa = copy(s = s * factor)
|
||||
override fun saturate(factor: Double): ColorHPLUVa = copy(s = s * factor)
|
||||
|
||||
override fun toRGBa(): ColorRGBa = toLCHUVa().toRGBa()
|
||||
|
||||
override fun opacify(factor: Double) = copy(a = a * factor)
|
||||
|
||||
override fun minus(other: ColorHPLUVa) = copy(h = h - other.h, s = s - other.s, l = l - other.l, a = a - other.a)
|
||||
|
||||
override fun plus(other: ColorHPLUVa) = copy(h = h + other.h, s = s + other.s, l = l + other.l, a = a + other.a)
|
||||
|
||||
override fun times(factor: Double) = copy(h = h * factor, s = s * factor, l = l * factor, a = a * factor)
|
||||
|
||||
override fun mix(other: ColorHPLUVa, factor: Double) = mix(this, other, factor)
|
||||
|
||||
fun toRGBa(): ColorRGBa = toLCHUVa().toRGBa()
|
||||
}
|
||||
|
||||
fun mix(left: ColorHPLUVa, right: ColorHPLUVa, x: Double): ColorHPLUVa {
|
||||
val sx = x.coerceIn(0.0, 1.0)
|
||||
return ColorHPLUVa(
|
||||
mixAngle(left.h, right.h, 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)
|
||||
}
|
||||
|
||||
|
||||
fun ColorLCHUVa.toHPLUVa(): ColorHPLUVa {
|
||||
if (l > 99.9999999) {
|
||||
return ColorHPLUVa(h, 0.0, 100.0)
|
||||
@@ -150,7 +203,7 @@ fun ColorLCHUVa.toHSLUVa(): ColorHSLUVa {
|
||||
}
|
||||
val max = maxChromaForLH(l, h)
|
||||
val s = c / max * 100.0
|
||||
return ColorHSLUVa(h, s, l)
|
||||
return ColorHSLUVa(h, s, l, alpha)
|
||||
}
|
||||
|
||||
fun ColorRGBa.toHSLUVa(): ColorHSLUVa = toLCHUVa().toHSLUVa()
|
||||
|
||||
@@ -39,7 +39,7 @@ fun main() = application {
|
||||
for (x in -1..1) {
|
||||
drawer.stroke = ColorRGBa.RED.toHSVa()
|
||||
.shiftHue(0.0 + simplex(500+x+y,seconds)*5.0)
|
||||
.scaleValue(0.5 + 0.5 * simplex(300+x+y,seconds*4.0).absoluteValue)
|
||||
.shade(0.5 + 0.5 * simplex(300+x+y,seconds*4.0).absoluteValue)
|
||||
.toRGBa()
|
||||
val r = simplex(400+x+y, seconds) * 150.0 + 150.0
|
||||
drawer.circle(width / 2.0 + x * 100.0, height / 2.0 + y * 100.0, r)
|
||||
|
||||
@@ -38,7 +38,7 @@ fun main() {
|
||||
gradients.forEachIndexed { gradientId, gradient ->
|
||||
for (column in 0 until 10) {
|
||||
val color1 = ColorRGBa.PINK.toHSVa().shiftHue(column * 12.0)
|
||||
.scaleValue(0.5).toRGBa()
|
||||
.shade(0.5).toRGBa()
|
||||
|
||||
val w = width.toDouble() / 10.0
|
||||
val h = height.toDouble() / gradients.size
|
||||
|
||||
@@ -16,7 +16,7 @@ fun main() {
|
||||
extend {
|
||||
drawer.shadeStyle = radialGradient(
|
||||
ColorRGBa.PINK,
|
||||
ColorRGBa.PINK.toHSVa().shiftHue(180.0).scaleValue(0.5).toRGBa(),
|
||||
ColorRGBa.PINK.toHSVa().shiftHue(180.0).shade(0.5).toRGBa(),
|
||||
exponent = cos(seconds)*0.5+0.5
|
||||
)
|
||||
drawer.rectangle(120.0, 40.0, 400.0, 400.0)
|
||||
|
||||
Reference in New Issue
Block a user