[orx-color] Add ColorSequence.toColorBuffer
This commit is contained in:
143
orx-color/src/commonMain/kotlin/palettes/ColorSequence.kt
Normal file
143
orx-color/src/commonMain/kotlin/palettes/ColorSequence.kt
Normal file
@@ -0,0 +1,143 @@
|
||||
package org.openrndr.extra.color.palettes
|
||||
|
||||
import org.openrndr.color.*
|
||||
import org.openrndr.draw.*
|
||||
import org.openrndr.extra.color.spaces.*
|
||||
|
||||
|
||||
/**
|
||||
* Creates a `ColorSequence` by accepting a variable number of pairs, where each pair consists of
|
||||
* a position (Double) and a color (T). The positions represent the normalized range `[0.0, 1.0]`.
|
||||
* The resulting `ColorSequence` can be used for creating interpolated colors between the specified positions.
|
||||
*
|
||||
* @param offsets Vararg parameter of pairs, where each pair includes a position (Double) and a color (of type T).
|
||||
* The position defines the location along a normalized sequence `[0.0, 1.0]`, and the color must implement `ConvertibleToColorRGBa`.
|
||||
* Typically, positions must be sorted, but the function will sort them internally based on their position values.
|
||||
* @return A `ColorSequence` containing the sorted sequence of colors and positions.
|
||||
*/
|
||||
fun <T> colorSequence(vararg offsets: Pair<Double, T>): ColorSequence
|
||||
where T : ConvertibleToColorRGBa {
|
||||
return ColorSequence(offsets.sortedBy { it.first })
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a sequence of colors along with their corresponding positions in a normalized range [0.0, 1.0].
|
||||
* The `ColorSequence` allows for creating interpolated colors between the specified color points.
|
||||
*
|
||||
* @property colors A list of pairs where the first value is a position (ranging from 0.0 to 1.0)
|
||||
* and the second value is a color that implements `ConvertibleToColorRGBa`.
|
||||
*/
|
||||
class ColorSequence(val colors: List<Pair<Double, ConvertibleToColorRGBa>>) {
|
||||
infix fun blend(steps: Int): List<ColorRGBa> = color(0.0, 1.0, steps)
|
||||
|
||||
/**
|
||||
* Converts a color sequence into a color buffer with a gradient representation.
|
||||
*
|
||||
* @param drawer The Drawer used to render the gradient into the color buffer.
|
||||
* @param width The width of the resulting color buffer in pixels. Defaults to 256.
|
||||
* @param height The height of the resulting color buffer in pixels. Defaults to 16.
|
||||
* @param type The ColorType of the resulting color buffer. Defaults to UINT8_SRGB.
|
||||
* @param format The ColorFormat of the resulting color buffer. Defaults to RGBa.
|
||||
* @return A ColorBuffer containing the rendered color gradient.
|
||||
*/
|
||||
fun toColorBuffer(
|
||||
drawer: Drawer,
|
||||
width: Int = 256,
|
||||
height: Int = 16,
|
||||
type: ColorType = ColorType.UINT8_SRGB,
|
||||
format: ColorFormat = ColorFormat.RGBa
|
||||
): ColorBuffer {
|
||||
val cb = colorBuffer(width, height, type = type, format = format)
|
||||
val rt = renderTarget(width, height) {
|
||||
colorBuffer(cb)
|
||||
}
|
||||
|
||||
drawer.isolatedWithTarget(rt) {
|
||||
defaults()
|
||||
ortho(rt)
|
||||
drawer.rectangles {
|
||||
for (i in 0 until width) {
|
||||
fill = color(i / (width.toDouble() - 1.0))
|
||||
stroke = null
|
||||
rectangle(i * 1.0, 0.0, 1.0, height.toDouble())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rt.destroy()
|
||||
return cb
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a sequence of interpolated colors between two specified values.
|
||||
*
|
||||
* @param t0 A Double representing the start value for interpolation.
|
||||
* @param t1 A Double representing the end value for interpolation.
|
||||
* @param steps An Int representing the number of colors to generate in the sequence.
|
||||
* @return A List of interpolated colors.
|
||||
*/
|
||||
fun color(t0: Double, t1: Double, steps: Int) = (0 until steps).map {
|
||||
val f = (it / (steps - 1.0))
|
||||
val t = t0 * (1.0 - f) + t1 * f
|
||||
color(t)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a color using interpolation based on the provided parameter `t`.
|
||||
*
|
||||
* @param t A Double representing the position along the color sequence, typically ranging from 0.0 to 1.0.
|
||||
* It indicates how far between the sequence colors the interpolation should occur,
|
||||
* with 0.0 being the start of the sequence and 1.0 being the end.
|
||||
* @return A ColorRGBa instance representing the interpolated color in the sRGB color space.
|
||||
* If the provided `t` is outside the range of the sequence, the color at the nearest boundary will be returned.
|
||||
*/
|
||||
fun color(t: Double): ColorRGBa {
|
||||
if (colors.size == 1) {
|
||||
return colors.first().second.toRGBa().toSRGB()
|
||||
}
|
||||
if (t < colors[0].first) {
|
||||
return colors[0].second.toRGBa().toSRGB()
|
||||
}
|
||||
if (t >= colors.last().first) {
|
||||
return colors.last().second.toRGBa().toSRGB()
|
||||
}
|
||||
val rightIndex = colors.binarySearch { it.first.compareTo(t) }.let { if (it < 0) -it - 2 else it }
|
||||
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 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()
|
||||
is ColorOKLABa -> right.second.toRGBa().toOKLABa().mix(l, nt).toRGBa()
|
||||
is ColorOKLCHa -> right.second.toRGBa().toOKLCHa().mix(l, nt).toRGBa()
|
||||
is ColorOKHSLa -> right.second.toRGBa().toOKHSLa().mix(l, nt).toRGBa()
|
||||
is ColorOKHSVa -> right.second.toRGBa().toOKHSVa().mix(l, nt).toRGBa()
|
||||
else -> error("unsupported color space: ${l::class}")
|
||||
}.toSRGB()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a range between two colors by creating a sequence of colors
|
||||
* that transition smoothly from the start color to the end color.
|
||||
*
|
||||
* @param end The end color of the range. Both start and end colors must implement `ConvertibleToColorRGBa`.
|
||||
* The start color is implicitly the color on which this operator is called.
|
||||
*/
|
||||
operator fun ConvertibleToColorRGBa.rangeTo(end: ConvertibleToColorRGBa) = colorSequence(0.0 to this, 1.0 to end)
|
||||
@@ -1,63 +0,0 @@
|
||||
package org.openrndr.extra.color.palettes
|
||||
|
||||
import org.openrndr.color.*
|
||||
import org.openrndr.extra.color.spaces.*
|
||||
|
||||
|
||||
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 - 1.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().toSRGB()
|
||||
}
|
||||
if (t < colors[0].first) {
|
||||
return colors[0].second.toRGBa().toSRGB()
|
||||
}
|
||||
if (t >= colors.last().first) {
|
||||
return colors.last().second.toRGBa().toSRGB()
|
||||
}
|
||||
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 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()
|
||||
is ColorOKLABa -> right.second.toRGBa().toOKLABa().mix(l, nt).toRGBa()
|
||||
is ColorOKLCHa -> right.second.toRGBa().toOKLCHa().mix(l, nt).toRGBa()
|
||||
is ColorOKHSLa -> right.second.toRGBa().toOKHSLa().mix(l, nt).toRGBa()
|
||||
is ColorOKHSVa -> right.second.toRGBa().toOKHSVa().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)
|
||||
71
orx-color/src/jvmDemo/kotlin/DemoColorSequence01.kt
Normal file
71
orx-color/src/jvmDemo/kotlin/DemoColorSequence01.kt
Normal file
@@ -0,0 +1,71 @@
|
||||
import org.openrndr.application
|
||||
import org.openrndr.color.ColorRGBa
|
||||
import org.openrndr.draw.DrawPrimitive
|
||||
import org.openrndr.draw.isolated
|
||||
import org.openrndr.draw.shadeStyle
|
||||
import org.openrndr.extra.camera.Orbital
|
||||
import org.openrndr.extra.color.palettes.ColorSequence
|
||||
import org.openrndr.extra.color.presets.MEDIUM_AQUAMARINE
|
||||
import org.openrndr.extra.color.presets.ORANGE
|
||||
import org.openrndr.extra.color.spaces.toOKLABa
|
||||
import org.openrndr.extra.meshgenerators.sphereMesh
|
||||
import org.openrndr.math.Vector3
|
||||
|
||||
/**
|
||||
* A demo that demonstrates 3D objects with custom shading and color gradients.
|
||||
*
|
||||
* The application setup involves:
|
||||
* - Configuring the application window dimensions.
|
||||
* - Creating a color gradient using `ColorSequence` and converting it to a `ColorBuffer` for shading purposes.
|
||||
* - Defining a 3D sphere mesh with specified resolution.
|
||||
*
|
||||
* The rendering process includes:
|
||||
* - Setting up an orbital camera extension to provide an interactive 3D view.
|
||||
* - Applying a custom fragment shader with a palette-based shading style.
|
||||
* - Rendering a grid of 3D spheres, each transformed and rotated to create a dynamic pattern.
|
||||
*/
|
||||
fun main() {
|
||||
application {
|
||||
configure {
|
||||
width = 720
|
||||
height = 720
|
||||
}
|
||||
program {
|
||||
val cs = ColorSequence(
|
||||
listOf(
|
||||
0.0 to ColorRGBa.PINK,
|
||||
0.25 to ColorRGBa.ORANGE.toOKLABa(),
|
||||
0.27 to ColorRGBa.WHITE.toOKLABa(),
|
||||
0.32 to ColorRGBa.BLUE,
|
||||
1.0 to ColorRGBa.MEDIUM_AQUAMARINE
|
||||
)
|
||||
)
|
||||
val palette = cs.toColorBuffer(drawer, 256, 16)
|
||||
val sphere = sphereMesh(sides = 48, segments = 48)
|
||||
|
||||
extend(Orbital()) {
|
||||
fov = 50.0
|
||||
eye = Vector3(0.0, 0.0, 13.0)
|
||||
}
|
||||
extend {
|
||||
drawer.shadeStyle = shadeStyle {
|
||||
fragmentTransform = """
|
||||
float d = normalize(va_normal).z;
|
||||
x_fill = texture(p_palette, vec2(1.0-d, 0.0));
|
||||
""".trimIndent()
|
||||
parameter("palette", palette)
|
||||
}
|
||||
for (j in -2..2) {
|
||||
for (i in -2..2) {
|
||||
drawer.isolated {
|
||||
drawer.translate(i * 2.0, j * 2.0, 0.0)
|
||||
drawer.rotate(Vector3.UNIT_Y, j * 30.0)
|
||||
drawer.rotate(Vector3.UNIT_X, i * 30.0)
|
||||
drawer.vertexBuffer(sphere, DrawPrimitive.TRIANGLES)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user