[orx-shadestyle] Fix COVER and CONTAIN fit modes for BOUNDS units in GradientBase

This commit is contained in:
Edwin Jakobs
2025-02-27 10:20:04 +01:00
parent ee8a709cc0
commit fe87704efd
3 changed files with 170 additions and 114 deletions

View File

@@ -0,0 +1,120 @@
package org.openrndr.extra.shadestyles.fills.gradients
import org.openrndr.color.AlgebraicColor
import org.openrndr.color.ConvertibleToColorRGBa
import org.openrndr.draw.ShadeStyle
import org.openrndr.extra.color.phrases.linearRgbToOklabPhrase
import org.openrndr.extra.color.phrases.oklabToLinearRgbPhrase
import org.openrndr.extra.shadestyles.fills.FillFit
import org.openrndr.extra.shadestyles.fills.FillUnits
import org.openrndr.extra.shadestyles.fills.SpreadMethod
import org.openrndr.extra.shadestyles.generateColorTransform
import org.openrndr.math.CastableToVector4
import org.openrndr.math.Vector4
import kotlin.math.PI
import kotlin.reflect.KClass
class GradientBaseStructure(
val gradientFunction: String,
val domainWarpFunction: String,
val levelWarpFunction: String
)
open class GradientBase<C>(
colorType: KClass<C>,
colors: Array<Vector4>,
points: Array<Double> = Array(colors.size) { it / (colors.size - 1.0) },
structure: GradientBaseStructure
) : ShadeStyle()
where C : ConvertibleToColorRGBa, C : AlgebraicColor<C>, C : CastableToVector4 {
var quantization: Int by Parameter()
var colors: Array<Vector4> by Parameter()
var points: Array<Double> by Parameter()
var spreadMethod: Int by Parameter()
var fillUnits: Int by Parameter()
var fillFit: Int by Parameter()
init {
this.quantization = 0
this.colors = colors
this.points = points
this.fillUnits = FillUnits.BOUNDS.ordinal
this.spreadMethod = SpreadMethod.PAD.ordinal
this.fillFit = FillFit.STRETCH.ordinal
fragmentPreamble = """
|vec2 rotate2D(vec2 x, float angle){
| float rad = angle / 180.0 * $PI;
| mat2 m = mat2(cos(rad),-sin(rad), sin(rad),cos(rad));
| return m * x;
|}
|$oklabToLinearRgbPhrase
|$linearRgbToOklabPhrase
|${structure.gradientFunction}
|${structure.domainWarpFunction}
|${structure.levelWarpFunction}
|""".trimMargin()
fragmentTransform = """
vec2 coord = vec2(0.0);
if (p_fillUnits == 0) { // BOUNDS
coord = c_boundsPosition.xy;
if (p_fillFit == 1) { // COVER
float mx = max(c_boundsSize.x, c_boundsSize.y);
float ar = min(c_boundsSize.x, c_boundsSize.y) / mx;
if (c_boundsSize.x == mx) {
coord.y = (coord.y - 0.5) * ar + 0.5;
} else {
coord.x = (coord.x - 0.5) * ar + 0.5;
}
} else if (p_fillFit == 2) { // CONTAIN
float mx = max(c_boundsSize.x, c_boundsSize.y);
float ar = mx / min(c_boundsSize.x, c_boundsSize.y);
if (c_boundsSize.y == mx) {
coord.y = (coord.y - 0.5) * ar + 0.5;
} else {
coord.x = (coord.x - 0.5) * ar + 0.5;
}
}
} else if (p_fillUnits == 1) { // WORLD
coord = v_worldPosition.xy;
} else if (p_fillUnits == 2) { // VIEW
coord = v_viewPosition.xy;
} else if (p_fillUnits == 3) { // SCREEN
coord = c_screenPosition.xy;
coord.y = u_viewDimensions.y - coord.y;
}
coord = domainWarp(coord);
float f = gradientFunction(coord);
f = levelWarp(coord, f);
if (p_quantization != 0) {
f = floor(f * float(p_quantization)) / (float(p_quantization) - 1.0);
}
if (p_spreadMethod == 0) { // PAD
f = clamp(f, 0.0, 1.0);
} else if (p_spreadMethod == 1) { // REFLECT
f = 2.0 * abs(f / 2.0 - floor(f / 2.0 + 0.5));
} else if (p_spreadMethod == 2) { // REPEAT
f = mod(f, 1.0);
}
int i = 0;
while (i < p_points_SIZE - 1 && f >= p_points[i+1]) { i++; }
vec4 color0 = p_colors[i];
vec4 color1 = p_colors[i+1];
float g = (f - p_points[i]) / (p_points[i+1] - p_points[i]);
vec4 gradient = mix(color0, color1, clamp(g, 0.0, 1.0));
${generateColorTransform(colorType)}
x_fill *= gradient;
"""
}
}

View File

@@ -2,124 +2,11 @@ package org.openrndr.extra.shadestyles.fills.gradients
import org.openrndr.color.AlgebraicColor
import org.openrndr.color.ConvertibleToColorRGBa
import org.openrndr.draw.ShadeStyle
import org.openrndr.extra.color.phrases.linearRgbToOklabPhrase
import org.openrndr.extra.color.phrases.oklabToLinearRgbPhrase
import org.openrndr.extra.shadestyles.fills.FillFit
import org.openrndr.extra.shadestyles.fills.FillUnits
import org.openrndr.extra.shadestyles.fills.SpreadMethod
import org.openrndr.extra.shadestyles.generateColorTransform
import org.openrndr.math.CastableToVector4
import org.openrndr.math.Vector2
import org.openrndr.math.Vector4
import kotlin.math.PI
import kotlin.reflect.KClass
class GradientBaseStructure(
val gradientFunction: String,
val domainWarpFunction: String,
val levelWarpFunction: String
)
open class GradientBase<C>(
colorType: KClass<C>,
colors: Array<Vector4>,
points: Array<Double> = Array(colors.size) { it / (colors.size - 1.0) },
structure: GradientBaseStructure
) : ShadeStyle()
where C : ConvertibleToColorRGBa, C : AlgebraicColor<C>, C : CastableToVector4 {
var quantization: Int by Parameter()
var colors: Array<Vector4> by Parameter()
var points: Array<Double> by Parameter()
var spreadMethod: Int by Parameter()
var fillUnits: Int by Parameter()
var fillFit: Int by Parameter()
init {
this.quantization = 0
this.colors = colors
this.points = points
this.fillUnits = FillUnits.BOUNDS.ordinal
this.spreadMethod = SpreadMethod.PAD.ordinal
this.fillFit = FillFit.STRETCH.ordinal
fragmentPreamble = """
|vec2 rotate2D(vec2 x, float angle){
| float rad = angle / 180.0 * $PI;
| mat2 m = mat2(cos(rad),-sin(rad), sin(rad),cos(rad));
| return m * x;
|}
|$oklabToLinearRgbPhrase
|$linearRgbToOklabPhrase
|${structure.gradientFunction}
|${structure.domainWarpFunction}
|${structure.levelWarpFunction}
|""".trimMargin()
fragmentTransform = """
vec2 coord = vec2(0.0);
if (p_fillUnits == 0) { // BOUNDS
coord = c_boundsPosition.xy;
if (p_fillFit == 1) { // COVER
float mx = max(c_boundsSize.x, c_boundsSize.y);
float ar = mx / min(c_boundsSize.x, c_boundsSize.y);
if (c_boundsSize.x == mx) {
coord.y = (coord.y - 0.5) * ar + 0.5;
} else {
coord.x = (coord.x - 0.5) * ar + 0.5;
}
} else if (p_fillFit == 2) { // CONTAIN
float mx = max(c_boundsSize.x, c_boundsSize.y);
float ar = min(c_boundsSize.x, c_boundsSize.y) / mx;
if (c_boundsSize.y == mx) {
coord.y = (coord.y - 0.5) * ar + 0.5;
} else {
coord.x = (coord.x - 0.5) * ar + 0.5;
}
}
} else if (p_fillUnits == 1) { // WORLD
coord = v_worldPosition.xy;
} else if (p_fillUnits == 2) { // VIEW
coord = v_viewPosition.xy;
} else if (p_fillUnits == 3) { // SCREEN
coord = c_screenPosition.xy;
coord.y = u_viewDimensions.y - coord.y;
}
coord = domainWarp(coord);
float f = gradientFunction(coord);
f = levelWarp(coord, f);
if (p_quantization != 0) {
f = floor(f * float(p_quantization)) / (float(p_quantization) - 1.0);
}
if (p_spreadMethod == 0) { // PAD
f = clamp(f, 0.0, 1.0);
} else if (p_spreadMethod == 1) { // REFLECT
f = 2.0 * abs(f / 2.0 - floor(f / 2.0 + 0.5));
} else if (p_spreadMethod == 2) { // REPEAT
f = mod(f, 1.0);
}
int i = 0;
while (i < p_points_SIZE - 1 && f >= p_points[i+1]) { i++; }
vec4 color0 = p_colors[i];
vec4 color1 = p_colors[i+1];
float g = (f - p_points[i]) / (p_points[i+1] - p_points[i]);
vec4 gradient = mix(color0, color1, clamp(g, 0.0, 1.0));
${generateColorTransform(colorType)}
x_fill *= gradient;
"""
}
}
open class LinearGradient<C>(
colorType: KClass<C>,
start: Vector2 = Vector2.ZERO,
@@ -127,7 +14,7 @@ open class LinearGradient<C>(
colors: Array<Vector4>,
points: Array<Double> = Array(colors.size) { it / (colors.size - 1.0) },
structure: GradientBaseStructure,
) : GradientBase<C>(
) : GradientBase<C>(
colorType,
colors,
points,

View File

@@ -0,0 +1,49 @@
package gradients
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.extra.color.spaces.OKHSV
import org.openrndr.extra.color.tools.shiftHue
import org.openrndr.extra.shadestyles.fills.FillFit
import org.openrndr.extra.shadestyles.fills.SpreadMethod
import org.openrndr.extra.shadestyles.fills.gradients.gradient
import org.openrndr.extra.shapes.primitives.grid
import org.openrndr.extra.shapes.primitives.placeIn
import org.openrndr.math.Vector2
fun main() {
application {
configure {
width = 720
height = 720
}
program {
extend {
val grid = drawer.bounds.grid(3, 3)
for ((index, row) in grid.withIndex()) {
drawer.shadeStyle = gradient<ColorRGBa> {
for (i in 0..10) {
stops[i / 10.0] = ColorRGBa.RED.shiftHue<OKHSV>(i * 36.0)
}
spreadMethod = SpreadMethod.PAD
this.fillFit = FillFit.entries[index]
radial {
center = Vector2(0.5, 0.5)
}
}
for ((x, cell) in row.withIndex()) {
val paddedCell = cell.offsetEdges(-10.0)
when (x) {
0 -> drawer.rectangle(paddedCell.sub(0.0..0.5, 0.0..1.0).placeIn(paddedCell))
1 -> drawer.rectangle(paddedCell)
2 -> drawer.rectangle(paddedCell.sub(0.0..1.0, 0.0..0.5).placeIn(paddedCell))
}
}
}
}
}
}
}