[orx-color, orx-fx] turbo_colormap and spectral_zucconi6 provided as shader phrases (#338)

This commit is contained in:
Kazik Pogoda
2024-06-10 11:52:25 +02:00
committed by GitHub
parent ab0970ce49
commit f43a322cb0
28 changed files with 503 additions and 115 deletions

View File

@@ -0,0 +1,91 @@
package org.openrndr.extra.color.colormaps
import org.openrndr.extra.shaderphrases.ShaderPhrase
import org.openrndr.extra.shaderphrases.ShaderPhraseBook
/**
* Colormaps represent a class of functions taking a value in the range `0.0..1.0` and returning particular RGB color
* values. Colormaps can be used in data visualization for representing additional information/dimension of the data
* e.g:
* - depth
* - elevation
* - heat
*
* Note: the [ShaderPhrase] GLSL functions gathered in this [ShaderPhraseBook] also have respective Kotlin
* implementations.
*
* @see org.openrndr.extra.color.colormaps.turboColormap
* @see org.openrndr.extra.color.colormaps.spectralZucconi6
*/
object ColormapPhraseBook : ShaderPhraseBook("colormap") {
// Copyright 2019 Google LLC.
// SPDX-License-Identifier: Apache-2.0
/**
* Polynomial approximation in GLSL for the Turbo colormap.
*
* See [Turbo, An Improved Rainbow Colormap for Visualization](https://research.google/blog/turbo-an-improved-rainbow-colormap-for-visualization/),
* [the source of this code](https://gist.github.com/mikhailov-work/0d177465a8151eb6ede1768d51d476c7),
*
* @author Anton Mikhailov (mikhailov@google.com) - Colormap Design
* @author Ruofei Du (ruofei@google.com) - GLSL Approximation
* @see org.openrndr.extra.color.colormaps.turboColormap
*/
val turboColormap = ShaderPhrase("""
|vec3 turbo_colormap(in float x) {
| const vec4 kRedVec4 = vec4(0.13572138, 4.61539260, -42.66032258, 132.13108234);
| const vec4 kGreenVec4 = vec4(0.09140261, 2.19418839, 4.84296658, -14.18503333);
| const vec4 kBlueVec4 = vec4(0.10667330, 12.64194608, -60.58204836, 110.36276771);
| const vec2 kRedVec2 = vec2(-152.94239396, 59.28637943);
| const vec2 kGreenVec2 = vec2(4.27729857, 2.82956604);
| const vec2 kBlueVec2 = vec2(-89.90310912, 27.34824973);
|
| x = clamp(x, 0.0, 1.0);
| vec4 v4 = vec4( 1.0, x, x * x, x * x * x);
| vec2 v2 = v4.zw * v4.z;
| return vec3(
| dot(v4, kRedVec4) + dot(v2, kRedVec2),
| dot(v4, kGreenVec4) + dot(v2, kGreenVec2),
| dot(v4, kBlueVec4) + dot(v2, kBlueVec2)
| );
|}""".trimMargin())
/**
* Accurate spectral colormap developed by Alan Zucconi.
*
* See [Improving the Rainbow](https://www.alanzucconi.com/2017/07/15/improving-the-rainbow/) article,
* [the source of this code](https://www.shadertoy.com/view/ls2Bz1)
*
* @author Alan Zucconi
* @see org.openrndr.extra.color.colormaps.spectralZucconi6
*/
val spectralZucconi6 = ShaderPhrase("""
|#pragma import colormap.bump3y
|
|vec3 spectral_zucconi6(in float x) {
|
| const vec3 c1 = vec3(3.54585104, 2.93225262, 2.41593945);
| const vec3 x1 = vec3(0.69549072, 0.49228336, 0.27699880);
| const vec3 y1 = vec3(0.02312639, 0.15225084, 0.52607955);
|
| const vec3 c2 = vec3(3.90307140, 3.21182957, 3.96587128);
| const vec3 x2 = vec3(0.11748627, 0.86755042, 0.66077860);
| const vec3 y2 = vec3(0.84897130, 0.88445281, 0.73949448);
|
| return
| bump3y(c1 * (x - x1), y1) +
| bump3y(c2 * (x - x2), y2) ;
|}""".trimMargin())
/**
* A function used internally by [spectralZucconi6].
*
* @author Alan Zucconi
*/
val bump3y = ShaderPhrase("""
|vec3 bump3y(in vec3 x, in vec3 yoffset) {
| vec3 y = vec3(1.0) - x * x;
| return clamp(y - yoffset, vec3(0.0), vec3(1.0));
|}""".trimMargin())
}

View File

@@ -0,0 +1,40 @@
package org.openrndr.extra.color.colormaps
import org.openrndr.color.ColorRGBa
import org.openrndr.math.Vector3
import org.openrndr.math.saturate
/**
* Accurate spectral colormap developed by Alan Zucconi.
*
* @see spectralZucconi6Vector
* @see ColormapPhraseBook.spectralZucconi6
*/
fun spectralZucconi6(
x: Double
): ColorRGBa = ColorRGBa.fromVector(
spectralZucconi6Vector(x)
)
/**
* Accurate spectral colormap developed by Alan Zucconi.
*
* @see ColormapPhraseBook.spectralZucconi6
*/
fun spectralZucconi6Vector(x: Double): Vector3 {
val v = Vector3(x)
return bump3y(c1 * (v - x1), y1) + bump3y(c2 * (v - x2), y2)
}
private fun bump3y(
x: Vector3,
yOffset: Vector3
) = (Vector3.ONE - x * x - yOffset).saturate()
private val c1 = Vector3(3.54585104, 2.93225262, 2.41593945)
private val x1 = Vector3(0.69549072, 0.49228336, 0.27699880)
private val y1 = Vector3(0.02312639, 0.15225084, 0.52607955)
private val c2 = Vector3(3.90307140, 3.21182957, 3.96587128)
private val x2 = Vector3(0.11748627, 0.86755042, 0.66077860)
private val y2 = Vector3(0.84897130, 0.88445281, 0.73949448)

View File

@@ -0,0 +1,39 @@
package org.openrndr.extra.color.colormaps
import org.openrndr.color.ColorRGBa
import org.openrndr.math.*
/**
* Polynomial approximation in GLSL for the Turbo colormap.
*
* @see turboColormapVector
* @see ColormapPhraseBook.turboColormap
*/
fun turboColormap(
x: Double
): ColorRGBa = ColorRGBa.fromVector(
turboColormapVector(x)
)
/**
* Polynomial approximation in GLSL for the Turbo colormap.
*
* @see ColormapPhraseBook.turboColormap
*/
fun turboColormapVector(x: Double): Vector3 {
val v = saturate(x)
val v4 = Vector4( 1.0, v, v * v, v * v * v)
val v2 = Vector2(v4.z, v4.w) * v4.z
return Vector3(
v4.dot(kRedVec4) + v2.dot(kRedVec2),
v4.dot(kGreenVec4) + v2.dot(kGreenVec2),
v4.dot(kBlueVec4) + v2.dot(kBlueVec2)
)
}
private val kRedVec4 = Vector4(0.13572138, 4.61539260, -42.66032258, 132.13108234)
private val kGreenVec4 = Vector4(0.09140261, 2.19418839, 4.84296658, -14.18503333)
private val kBlueVec4 = Vector4(0.10667330, 12.64194608, -60.58204836, 110.36276771)
private val kRedVec2 = Vector2(-152.94239396, 59.28637943)
private val kGreenVec2 = Vector2(4.27729857, 2.82956604)
private val kBlueVec2 = Vector2(-89.90310912, 27.34824973)

View File

@@ -0,0 +1,5 @@
// keeping this file here will stop IntelliJ from showing warning in nested relative packages
/**
* orx-color
*/
package org.openrndr.extra.color

View File

@@ -0,0 +1,29 @@
package org.openrndr.extra.color.colormaps
import io.kotest.matchers.shouldBe
import org.openrndr.color.ColorRGBa
import org.openrndr.color.Linearity
import org.openrndr.math.Vector3
import kotlin.test.Test
class TestSpectralZucconi6Colormap {
@Test
fun testSpectralZucconi6Vector() {
spectralZucconi6Vector(0.0) shouldBe Vector3(0.0, 0.0, 0.026075309353279508)
spectralZucconi6Vector(0.5) shouldBe Vector3(0.49637374891706215, 0.8472371726323733, 0.18366091774095827)
spectralZucconi6Vector(1.0) shouldBe Vector3(0.0, 0.0, 0.0)
spectralZucconi6Vector(-0.1) shouldBe Vector3(0.0, 0.0, 0.0)
spectralZucconi6Vector(1.1) shouldBe Vector3(0.0, 0.0, 0.0)
}
@Test
fun testSpectralZucconi6() {
spectralZucconi6(0.0) shouldBe ColorRGBa(0.0, 0.0, 0.026075309353279508, linearity = Linearity.SRGB)
spectralZucconi6(0.5) shouldBe ColorRGBa(0.49637374891706215, 0.8472371726323733, 0.18366091774095827, linearity = Linearity.SRGB)
spectralZucconi6(1.0) shouldBe ColorRGBa(0.0, 0.0, 0.0, linearity = Linearity.SRGB)
spectralZucconi6(-0.1) shouldBe ColorRGBa(0.0, 0.0, 0.0, linearity = Linearity.SRGB)
spectralZucconi6(1.1) shouldBe ColorRGBa(0.0, 0.0, 0.0, linearity = Linearity.SRGB)
}
}

View File

@@ -0,0 +1,29 @@
package org.openrndr.extra.color.colormaps
import io.kotest.matchers.shouldBe
import org.openrndr.color.ColorRGBa
import org.openrndr.color.Linearity
import org.openrndr.math.Vector3
import kotlin.test.Test
class TestTurboColormap {
@Test
fun testTurboColormapVector() {
turboColormapVector(0.0) shouldBe Vector3(0.13572138, 0.09140261, 0.1066733)
turboColormapVector(0.5) shouldBe Vector3(0.5885220621875007, 0.981864383125, 0.31316869781249856)
turboColormapVector(1.0) shouldBe Vector3(0.5658592099999993, 0.05038885999999998, -0.025520659999997974)
turboColormapVector(-0.1) shouldBe Vector3(0.13572138, 0.09140261, 0.1066733)
turboColormapVector(1.1) shouldBe Vector3(0.5658592099999993, 0.05038885999999998, -0.025520659999997974)
}
@Test
fun testTurboColormap() {
turboColormap(0.0) shouldBe ColorRGBa(0.13572138, 0.09140261, 0.1066733, linearity = Linearity.SRGB)
turboColormap(0.5) shouldBe ColorRGBa(0.5885220621875007, 0.981864383125, 0.31316869781249856, linearity = Linearity.SRGB)
turboColormap(1.0) shouldBe ColorRGBa(0.5658592099999993, 0.05038885999999998, -0.025520659999997974, linearity = Linearity.SRGB)
turboColormap(-0.1) shouldBe ColorRGBa(0.13572138, 0.09140261, 0.1066733, linearity = Linearity.SRGB)
turboColormap(1.1) shouldBe ColorRGBa(0.5658592099999993, 0.05038885999999998, -0.025520659999997974, linearity = Linearity.SRGB)
}
}

View File

@@ -1,7 +1,6 @@
package spaces
package org.openrndr.extra.color.spaces
import org.openrndr.color.ColorRGBa
import org.openrndr.extra.color.spaces.toHSLUVa
import kotlin.test.Test
import kotlin.test.assertTrue

View File

@@ -1,7 +1,6 @@
package spaces
package org.openrndr.extra.color.spaces
import org.openrndr.color.ColorRGBa
import org.openrndr.extra.color.spaces.toOKHSLa
import kotlin.test.Test
import kotlin.test.assertTrue

View File

@@ -1,8 +1,6 @@
package spaces
package org.openrndr.extra.color.spaces
import org.openrndr.color.ColorRGBa
import org.openrndr.extra.color.spaces.toOKHSLa
import org.openrndr.extra.color.spaces.toOKHSVa
import kotlin.test.Test
import kotlin.test.assertTrue

View File

@@ -1,8 +1,6 @@
package spaces
package org.openrndr.extra.color.spaces
import org.openrndr.color.ColorRGBa
import org.openrndr.extra.color.spaces.OKLCH
import org.openrndr.extra.color.spaces.toOKLCHa
import org.openrndr.extra.color.tools.chroma
import org.openrndr.extra.color.tools.withLuminosity
import kotlin.test.Test

View File

@@ -1,10 +1,6 @@
package spaces
package org.openrndr.extra.color.spaces
import org.openrndr.color.ColorRGBa
import org.openrndr.extra.color.spaces.hueToX
import org.openrndr.extra.color.spaces.toHSLUVa
import org.openrndr.extra.color.spaces.toXSLUVa
import org.openrndr.extra.color.spaces.xToHue
import kotlin.math.abs
import kotlin.test.Test
import kotlin.test.assertTrue

View File

@@ -1,9 +1,7 @@
package tools
package org.openrndr.extra.color.tools
import org.openrndr.color.ColorRGBa
import org.openrndr.extra.color.spaces.toOKLCHa
import org.openrndr.extra.color.tools.findMaxChroma
import org.openrndr.extra.color.tools.isOutOfGamut
import kotlin.test.Test
class TestChromaColorExtensions {

View File

@@ -1,10 +1,7 @@
package tools
package org.openrndr.extra.color.tools
import org.openrndr.color.*
import org.openrndr.extra.color.spaces.*
import org.openrndr.extra.color.tools.mixedWith
import org.openrndr.extra.color.tools.saturate
import org.openrndr.extra.color.tools.shiftHue
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue

View File

@@ -0,0 +1,22 @@
import org.openrndr.application
import org.openrndr.extra.color.colormaps.spectralZucconi6
import org.openrndr.extra.noise.fastFloor
import kotlin.math.sin
fun main() = application {
program {
extend {
drawer.stroke = null
val stripeCount = 32 + (sin(seconds) * 16.0).fastFloor()
repeat(stripeCount) { i ->
drawer.fill = spectralZucconi6(i / stripeCount.toDouble())
drawer.rectangle(
x = i * width / stripeCount.toDouble(),
y = 0.0,
width = width / stripeCount.toDouble(),
height = height.toDouble(),
)
}
}
}
}

View File

@@ -0,0 +1,20 @@
import org.openrndr.application
import org.openrndr.draw.shadeStyle
import org.openrndr.extra.color.colormaps.ColormapPhraseBook
import org.openrndr.extra.shaderphrases.preprocess
fun main() = application {
program {
ColormapPhraseBook.register()
val style = shadeStyle {
fragmentPreamble = "#pragma import colormap.spectral_zucconi6".preprocess()
fragmentTransform = "x_fill.rgb = spectral_zucconi6(c_boundsPosition.x);"
}
extend {
drawer.run {
shadeStyle = style
rectangle(bounds)
}
}
}
}

View File

@@ -0,0 +1,44 @@
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.shadeStyle
import org.openrndr.extra.color.colormaps.ColormapPhraseBook
import org.openrndr.extra.color.colormaps.spectralZucconi6
import org.openrndr.extra.shaderphrases.preprocess
import org.openrndr.math.Vector2
fun main() = application {
program {
ColormapPhraseBook.register()
val backgroundStyle = shadeStyle {
fragmentPreamble = "#pragma import colormap.spectral_zucconi6".preprocess()
fragmentTransform = "x_fill.rgb = spectral_zucconi6(c_boundsPosition.x);"
}
fun getColormapPoints(
block: ColorRGBa.() -> Double
) = List(width) { x ->
Vector2(
x = x.toDouble(),
y = height.toDouble()
- block(spectralZucconi6(x / width.toDouble()))
* height.toDouble()
)
}
val redPoints = getColormapPoints { r }
val greenPoints = getColormapPoints { g }
val bluePoints = getColormapPoints { b }
extend {
drawer.run {
shadeStyle = backgroundStyle
rectangle(bounds)
shadeStyle = null
strokeWeight = 1.0
stroke = ColorRGBa.RED
lineStrip(redPoints)
stroke = ColorRGBa.GREEN
lineStrip(greenPoints)
stroke = ColorRGBa.BLUE
lineStrip(bluePoints)
}
}
}
}

View File

@@ -0,0 +1,22 @@
import org.openrndr.application
import org.openrndr.extra.color.colormaps.turboColormap
import org.openrndr.extra.noise.fastFloor
import kotlin.math.sin
fun main() = application {
program {
extend {
drawer.stroke = null
val stripeCount = 32 + (sin(seconds) * 16.0).fastFloor()
repeat(stripeCount) { i ->
drawer.fill = turboColormap(i / stripeCount.toDouble())
drawer.rectangle(
x = i * width / stripeCount.toDouble(),
y = 0.0,
width = width / stripeCount.toDouble(),
height = height.toDouble(),
)
}
}
}
}

View File

@@ -0,0 +1,20 @@
import org.openrndr.application
import org.openrndr.draw.shadeStyle
import org.openrndr.extra.color.colormaps.ColormapPhraseBook
import org.openrndr.extra.shaderphrases.preprocess
fun main() = application {
program {
ColormapPhraseBook.register()
val style = shadeStyle {
fragmentPreamble = "#pragma import colormap.turbo_colormap".preprocess()
fragmentTransform = "x_fill.rgb = turbo_colormap(c_boundsPosition.x);"
}
extend {
drawer.run {
shadeStyle = style
rectangle(bounds)
}
}
}
}

View File

@@ -0,0 +1,44 @@
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.shadeStyle
import org.openrndr.extra.color.colormaps.ColormapPhraseBook
import org.openrndr.extra.color.colormaps.turboColormap
import org.openrndr.extra.shaderphrases.preprocess
import org.openrndr.math.Vector2
fun main() = application {
program {
ColormapPhraseBook.register()
val backgroundStyle = shadeStyle {
fragmentPreamble = "#pragma import colormap.turbo_colormap".preprocess()
fragmentTransform = "x_fill.rgb = turbo_colormap(c_boundsPosition.x);"
}
fun getColormapPoints(
block: ColorRGBa.() -> Double
) = List(width) { x ->
Vector2(
x = x.toDouble(),
y = height.toDouble()
- block(turboColormap(x / width.toDouble()))
* height.toDouble()
)
}
val redPoints = getColormapPoints { r }
val greenPoints = getColormapPoints { g }
val bluePoints = getColormapPoints { b }
extend {
drawer.run {
shadeStyle = backgroundStyle
rectangle(bounds)
shadeStyle = null
strokeWeight = 1.0
stroke = ColorRGBa.RED
lineStrip(redPoints)
stroke = ColorRGBa.GREEN
lineStrip(greenPoints)
stroke = ColorRGBa.BLUE
lineStrip(bluePoints)
}
}
}
}