From 226a36d4401dd4a93307d2218b87fc1fea4be5c2 Mon Sep 17 00:00:00 2001 From: Edwin Jakobs Date: Mon, 22 Mar 2021 10:52:01 +0100 Subject: [PATCH] [orx-shade-styles] Add OKLab color space support to gradients --- orx-shade-styles/build.gradle | 3 +- .../src/demo/kotlin/DemoLinearGradient.kt | 34 ++++++++ .../src/main/kotlin/ColorspaceHelper.kt | 13 +++ .../src/main/kotlin/LinearGradient.kt | 82 +++++++++++++------ .../src/main/kotlin/NPointGradient.kt | 1 - .../src/main/kotlin/NPointLinearGradient.kt | 44 +++++++--- .../src/main/kotlin/NPointRadialGradient.kt | 5 +- .../src/main/kotlin/RadialGradient.kt | 65 ++++++++++----- .../src/main/kotlin/ShaderPreprocessor.kt | 2 + 9 files changed, 189 insertions(+), 60 deletions(-) create mode 100644 orx-shade-styles/src/demo/kotlin/DemoLinearGradient.kt create mode 100644 orx-shade-styles/src/main/kotlin/ColorspaceHelper.kt diff --git a/orx-shade-styles/build.gradle b/orx-shade-styles/build.gradle index 8220384d..c3918ba4 100644 --- a/orx-shade-styles/build.gradle +++ b/orx-shade-styles/build.gradle @@ -9,7 +9,8 @@ sourceSets { } dependencies { api project(":orx-parameters") - + implementation project(":orx-color") + implementation project(":orx-shader-phrases") demoImplementation("org.openrndr:openrndr-core:$openrndrVersion") demoImplementation("org.openrndr:openrndr-extensions:$openrndrVersion") demoRuntimeOnly("org.openrndr:openrndr-gl3:$openrndrVersion") diff --git a/orx-shade-styles/src/demo/kotlin/DemoLinearGradient.kt b/orx-shade-styles/src/demo/kotlin/DemoLinearGradient.kt new file mode 100644 index 00000000..1d50a48b --- /dev/null +++ b/orx-shade-styles/src/demo/kotlin/DemoLinearGradient.kt @@ -0,0 +1,34 @@ +import org.openrndr.application +import org.openrndr.color.ColorRGBa +import org.openrndr.extensions.SingleScreenshot +import org.openrndr.extra.shadestyles.linearGradient + +import org.openrndr.extra.shadestyles.radialGradient +import org.openrndr.extras.color.spaces.toOKLABa +import kotlin.math.cos + +fun main() { + application { + program { + if (System.getProperty("takeScreenshot") == "true") { + extend(SingleScreenshot()) { + this.outputFile = System.getProperty("screenshotPath") + } + } + extend { + drawer.shadeStyle = linearGradient( + ColorRGBa.RED.toOKLABa(), + ColorRGBa.BLUE.toOKLABa(), + ) + drawer.rectangle(120.0, 40.0, 200.0, 400.0) + + drawer.shadeStyle = linearGradient( + ColorRGBa.RED, + ColorRGBa.BLUE + ) + + drawer.rectangle(120.0+200.0, 40.0, 200.0, 400.0) + } + } + } +} \ No newline at end of file diff --git a/orx-shade-styles/src/main/kotlin/ColorspaceHelper.kt b/orx-shade-styles/src/main/kotlin/ColorspaceHelper.kt new file mode 100644 index 00000000..9dc78e23 --- /dev/null +++ b/orx-shade-styles/src/main/kotlin/ColorspaceHelper.kt @@ -0,0 +1,13 @@ +package org.openrndr.extra.shadestyles + +import org.openrndr.color.ColorRGBa +import org.openrndr.extras.color.spaces.ColorOKLABa +import kotlin.reflect.KClass + +internal fun generateColorTransform(kClass: KClass<*>): String { + return when (kClass) { + ColorRGBa::class -> """""" + ColorOKLABa::class -> """gradient = linear_rgb_to_srgb(oklab_to_linear_rgb(gradient));""" + else -> error("color space not supported $kClass") + } +} \ No newline at end of file diff --git a/orx-shade-styles/src/main/kotlin/LinearGradient.kt b/orx-shade-styles/src/main/kotlin/LinearGradient.kt index 3bcf61c7..64d3cdf5 100644 --- a/orx-shade-styles/src/main/kotlin/LinearGradient.kt +++ b/orx-shade-styles/src/main/kotlin/LinearGradient.kt @@ -1,25 +1,32 @@ package org.openrndr.extra.shadestyles -import org.openrndr.color.ColorRGBa +import org.openrndr.color.* import org.openrndr.draw.ShadeStyle import org.openrndr.extra.parameters.ColorParameter import org.openrndr.extra.parameters.Description import org.openrndr.extra.parameters.DoubleParameter +import org.openrndr.extra.shaderphrases.preprocess +import org.openrndr.extras.color.phrases.ColorPhraseBook +import org.openrndr.extras.color.spaces.ColorOKLABa +import org.openrndr.math.CastableToVector4 import org.openrndr.math.Vector2 +import kotlin.reflect.KClass @Description("Linear gradient") -class LinearGradient( - color0: ColorRGBa, - color1: ColorRGBa, - offset: Vector2 = Vector2.ZERO, - rotation: Double = 0.0, - exponent: Double = 1.0) : ShadeStyle() { +open class LinearGradientBase( + color0: C, + color1: C, + offset: Vector2 = Vector2.ZERO, + rotation: Double = 0.0, + exponent: Double = 1.0 +) : ShadeStyle() + where C : ConvertibleToColorRGBa, C : AlgebraicColor, C: CastableToVector4 { @ColorParameter("start color", order = 0) - var color0: ColorRGBa by Parameter() + var color0: C by Parameter() @ColorParameter("end color", order = 1) - var color1: ColorRGBa by Parameter() + var color1: C by Parameter() var offset: Vector2 by Parameter() @DoubleParameter("rotation", -180.0, 180.0, order = 2) @@ -29,15 +36,19 @@ class LinearGradient( var exponent: Double by Parameter() init { + ColorPhraseBook.register() this.color0 = color0 this.color1 = color1 this.offset = offset this.rotation = rotation this.exponent = exponent + fragmentPreamble = """ + |#pragma import color.oklab_to_linear_rgb + |#pragma import color.linear_rgb_to_srgb""".trimMargin().preprocess() fragmentTransform = """ vec2 coord = (c_boundsPosition.xy - 0.5 + p_offset); - + float cr = cos(radians(p_rotation)); float sr = sin(radians(p_rotation)); mat2 rm = mat2(cr, -sr, sr, cr); @@ -45,29 +56,48 @@ class LinearGradient( float f = clamp(rc.y + 0.5, 0.0, 1.0); vec4 color0 = p_color0; - color0.rgb *= color0.a; - vec4 color1 = p_color1; - color1.rgb *= color1.a; vec4 gradient = mix(color0, color1, pow(f, p_exponent)); - - vec4 fn = vec4(x_fill.rgb, 1.0) * x_fill.a; - - x_fill = fn * gradient; - if (x_fill.a != 0) { - x_fill.rgb /= x_fill.a; - } + ${generateColorTransform(color0::class)} + x_fill *= gradient; """ } } +class LinearGradient( + color0: ColorRGBa, + color1: ColorRGBa, + offset: Vector2 = Vector2.ZERO, + rotation: Double = 0.0, + exponent: Double = 1.0 +): LinearGradientBase(color0, color1, offset, rotation, exponent) + +class LinearGradientOKLab( + color0: ColorOKLABa, + color1: ColorOKLABa, + offset: Vector2 = Vector2.ZERO, + rotation: Double = 0.0, + exponent: Double = 1.0 +): LinearGradientBase(color0, color1, offset, rotation, exponent) + + fun linearGradient( - color0: ColorRGBa, - color1: ColorRGBa, - offset: Vector2 = Vector2.ZERO, - rotation: Double = 0.0, - exponent: Double = 1.0 -): ShadeStyle { + color0: ColorRGBa, + color1: ColorRGBa, + offset: Vector2 = Vector2.ZERO, + rotation: Double = 0.0, + exponent: Double = 1.0 +): LinearGradient { return LinearGradient(color0, color1, offset, rotation, exponent) +} + +fun linearGradient( + color0: ColorOKLABa, + color1: ColorOKLABa, + offset: Vector2 = Vector2.ZERO, + rotation: Double = 0.0, + exponent: Double = 1.0 +): LinearGradientOKLab { + return LinearGradientOKLab(color0, color1, offset, rotation, exponent) } \ No newline at end of file diff --git a/orx-shade-styles/src/main/kotlin/NPointGradient.kt b/orx-shade-styles/src/main/kotlin/NPointGradient.kt index efac14bc..7e48a9cb 100644 --- a/orx-shade-styles/src/main/kotlin/NPointGradient.kt +++ b/orx-shade-styles/src/main/kotlin/NPointGradient.kt @@ -4,7 +4,6 @@ import org.openrndr.color.ColorRGBa import org.openrndr.draw.ShadeStyle import org.openrndr.extra.parameters.Description import org.openrndr.math.Vector2 -import org.openrndr.math.Vector3 @Description("N-Point gradient") class NPointGradient( diff --git a/orx-shade-styles/src/main/kotlin/NPointLinearGradient.kt b/orx-shade-styles/src/main/kotlin/NPointLinearGradient.kt index 896d4b4f..91b18659 100644 --- a/orx-shade-styles/src/main/kotlin/NPointLinearGradient.kt +++ b/orx-shade-styles/src/main/kotlin/NPointLinearGradient.kt @@ -1,18 +1,25 @@ package org.openrndr.extra.shadestyles +import org.openrndr.color.AlgebraicColor import org.openrndr.color.ColorRGBa +import org.openrndr.color.ConvertibleToColorRGBa import org.openrndr.draw.ShadeStyle import org.openrndr.extra.parameters.Description +import org.openrndr.extras.color.spaces.ColorOKLABa +import org.openrndr.math.CastableToVector4 import org.openrndr.math.Vector2 @Description("Multicolor linear gradient") -class NPointLinearGradient( - colors: Array, - points: Array = Array(colors.size) { it / (colors.size - 1.0) }, - offset: Vector2 = Vector2.ZERO, - rotation: Double = 0.0) : ShadeStyle() { +open class NPointLinearGradientBase( + colors: Array, + points: Array = Array(colors.size) { it / (colors.size - 1.0) }, + offset: Vector2 = Vector2.ZERO, + rotation: Double = 0.0 +) : ShadeStyle() + where C : ConvertibleToColorRGBa, C : AlgebraicColor, C : CastableToVector4 { + + var colors: Array by Parameter() - var colors: Array by Parameter() // Sorted normalized values defining relative positions of colors var points: Array by Parameter() var offset: Vector2 by Parameter() @@ -37,17 +44,14 @@ class NPointLinearGradient( while(i < p_points_SIZE - 1 && f >= p_points[i+1]) { i++; } vec4 color0 = p_colors[i]; - color0.rgb *= color0.a; - vec4 color1 = p_colors[i+1]; - color1.rgb *= color1.a; float g = (f - p_points[i]) / (p_points[i+1] - p_points[i]); vec4 gradient = mix(color0, color1, clamp(g, 0.0, 1.0)); - - vec4 fn = vec4(x_fill.rgb, 1.0) * x_fill.a; - x_fill = fn * gradient; + ${generateColorTransform(colors[0]::class)} + + x_fill *= gradient; if (x_fill.a != 0) { x_fill.rgb /= x_fill.a; } @@ -55,3 +59,19 @@ class NPointLinearGradient( """ } } + +class NPointLinearGradient( + colors: Array, + points: Array = Array(colors.size) { it / (colors.size - 1.0) }, + offset: Vector2 = Vector2.ZERO, + rotation: Double = 0.0 +) : NPointLinearGradientBase(colors, points, offset, rotation) + +class NPointLinearGradientOKLab( + colors: Array, + points: Array = Array(colors.size) { it / (colors.size - 1.0) }, + offset: Vector2 = Vector2.ZERO, + rotation: Double = 0.0 +) : NPointLinearGradientBase(colors, points, offset, rotation) + + diff --git a/orx-shade-styles/src/main/kotlin/NPointRadialGradient.kt b/orx-shade-styles/src/main/kotlin/NPointRadialGradient.kt index 1e0e5529..de514d37 100644 --- a/orx-shade-styles/src/main/kotlin/NPointRadialGradient.kt +++ b/orx-shade-styles/src/main/kotlin/NPointRadialGradient.kt @@ -11,7 +11,10 @@ class NPointRadialGradient( points: Array = Array(colors.size) { it / (colors.size - 1.0) }, offset: Vector2 = Vector2.ZERO, rotation: Double = 0.0, - length: Double = 1.0) : ShadeStyle() { + length: Double = 1.0) : ShadeStyle() + + +{ var colors: Array by Parameter() diff --git a/orx-shade-styles/src/main/kotlin/RadialGradient.kt b/orx-shade-styles/src/main/kotlin/RadialGradient.kt index cb8f3f44..9ef4bcab 100644 --- a/orx-shade-styles/src/main/kotlin/RadialGradient.kt +++ b/orx-shade-styles/src/main/kotlin/RadialGradient.kt @@ -1,26 +1,29 @@ package org.openrndr.extra.shadestyles -import org.openrndr.color.ColorRGBa +import org.openrndr.color.* import org.openrndr.draw.ShadeStyle import org.openrndr.draw.shadeStyle import org.openrndr.extra.parameters.ColorParameter import org.openrndr.extra.parameters.Description import org.openrndr.extra.parameters.DoubleParameter +import org.openrndr.extras.color.spaces.ColorOKLABa +import org.openrndr.math.CastableToVector4 import org.openrndr.math.Vector2 @Description("Radial gradient") -class RadialGradient( - color0: ColorRGBa, - color1: ColorRGBa, +open class RadialGradientBase( + color0: C, + color1: C, offset: Vector2 = Vector2.ZERO, rotation: Double = 0.0, length: Double = 1.0, - exponent: Double = 1.0) : ShadeStyle() { - + exponent: Double = 1.0) + : ShadeStyle() +where C : ConvertibleToColorRGBa, C : AlgebraicColor, C: CastableToVector4 { @ColorParameter("start color", order = 0) - var color0 : ColorRGBa by Parameter() + var color0 : C by Parameter() @ColorParameter("end color", order = 1) - var color1 : ColorRGBa by Parameter() + var color1 : C by Parameter() var offset : Vector2 by Parameter() @DoubleParameter("rotation", -180.0, 180.0, order = 2) var rotation : Double by Parameter() @@ -47,23 +50,34 @@ class RadialGradient( float f = clamp(p_length * length(rc), 0.0, 1.0); vec4 color0 = p_color0; - color0.rgb *= color0.a; - vec4 color1 = p_color1; - color1.rgb *= color1.a; vec4 gradient = mix(color0, color1, pow(f, p_exponent)); - - vec4 fn = vec4(x_fill.rgb, 1.0) * x_fill.a; - - x_fill = fn * gradient; - if (x_fill.a != 0) { - x_fill.rgb /= x_fill.a; - } + ${generateColorTransform(color0::class)} + + x_fill *= gradient; """ } } +class RadialGradient( + color0: ColorRGBa, + color1: ColorRGBa, + offset: Vector2 = Vector2.ZERO, + rotation: Double = 0.0, + length: Double = 1.0, + exponent: Double = 1.0 +): RadialGradientBase(color0, color1, offset, rotation, length, exponent) + +class RadialGradientOKLab( + color0: ColorOKLABa, + color1: ColorOKLABa, + offset: Vector2 = Vector2.ZERO, + rotation: Double = 0.0, + length: Double = 1.0, + exponent: Double = 1.0 +): RadialGradientBase(color0, color1, offset, rotation, length, exponent) + fun radialGradient( color0: ColorRGBa, color1: ColorRGBa, @@ -71,6 +85,19 @@ fun radialGradient( rotation: Double = 0.0, length: Double = 1.0, exponent: Double = 1.0 -): ShadeStyle { +): RadialGradient { return RadialGradient(color0, color1, offset, rotation, length, exponent) } + +fun radialGradient( + color0: ColorOKLABa, + color1: ColorOKLABa, + offset: Vector2 = Vector2.ZERO, + rotation: Double = 0.0, + length: Double = 1.0, + exponent: Double = 1.0 +): RadialGradientOKLab { + return RadialGradientOKLab(color0, color1, offset, rotation, length, exponent) +} + + diff --git a/orx-shader-phrases/src/main/kotlin/ShaderPreprocessor.kt b/orx-shader-phrases/src/main/kotlin/ShaderPreprocessor.kt index 42fa31c1..8ce31f5d 100644 --- a/orx-shader-phrases/src/main/kotlin/ShaderPreprocessor.kt +++ b/orx-shader-phrases/src/main/kotlin/ShaderPreprocessor.kt @@ -128,6 +128,7 @@ fun preprocessShader(source: String, symbols: Set = emptySet()): String newSymbols ) } catch (e: NoSuchFieldException) { + println(source) error("field \"$fieldName\" not found in \"#pragma import $packageClass.$fieldName\" on line ${index + 1}") } } @@ -137,6 +138,7 @@ fun preprocessShader(source: String, symbols: Set = emptySet()): String throw IllegalArgumentException("class $packageClass has no ShaderPhrases annotation") } } catch (e: ClassNotFoundException) { + println(source) error("class \"$packageClass\" not found in \"#pragma import $packageClass\" on line ${index + 1}") } } else {