-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[orx-color] Add ColorSequence.toColorBuffer
- Loading branch information
Showing
3 changed files
with
214 additions
and
63 deletions.
There are no files selected for viewing
143 changes: 143 additions & 0 deletions
143
orx-color/src/commonMain/kotlin/palettes/ColorSequence.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |