# Colorizing strokes along its length and width

**URL:** <https://openrndr.discourse.group/t/colorizing-strokes-along-its-length-and-width/764>\
**Category:** Tutorials\
**Created:** [January 9, 2026, 7:33am UTC](https://openrndr.discourse.group/t/colorizing-strokes-along-its-length-and-width/764 "2026-01-09T07:33:50Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![abe](https://yyz2.discourse-cdn.com/free1/user_avatar/openrndr.discourse.group/abe/32/700_2.png) [@abe](https://openrndr.discourse.group/u/abe)\
**Post date:** [January 9, 2026, 7:33am UTC](https://openrndr.discourse.group/t/colorizing-strokes-along-its-length-and-width/764/1 "2026-01-09T07:33:50Z")

</div>

Happy new year 🙂

This week there was a question asked in Slack about applying colors in thick strokes. I learned something new, which I wanted to share here.

I already knew how to create a stroke that starts with one color and ends with another. This was a related question:

> [@Add gradient colors to lines](https://openrndr.discourse.group/t/add-gradient-colors-to-lines/754/3):
>
> By the way… there are many shade styles available in [orx/orx-shade-styles at master · openrndr/orx · GitHub](https://github.com/openrndr/orx/tree/master/orx-shade-styles) so you don’t need to write shaders yourself (many of them only available when using version 0.4.5). I opened [this issue](https://github.com/openrndr/orx/issues/359) to be able to apply those same shade styles to strokes, not only fills. Meanwhile I just figured out a little hack: val img = loadImage("data/images/cheeta.jpg") val ss = imageFill { image = img } ss.fragmentTransform = ss.fragmentTransform?.replace("x\_fill", "x\_stroke…

In that example, we just modulated the color between two colors. The trick was to use the `c_contourPosition` shadeStyle variable as the parameter for a sine function.

Since `c_contourPosition` contains the contour position **in pixels** , if you want to start the contour with one color and end with another, you need to know the length of the contour in the shadeStyle. We can pass that length to the shader using `parameter("len", c.length)`, like this:

```kotlin
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.shadeStyle
import org.openrndr.extra.imageFit.FitMethod
import org.openrndr.extra.imageFit.fit
import org.openrndr.extra.noise.scatter
import org.openrndr.extra.shapes.hobbycurve.hobbyCurve

fun main() = org.openrndr.application {
    program {
        val points = drawer.bounds.scatter(100.0).take(7)
        val c = hobbyCurve(points, false)
        val cFit = c.transform(
            c.bounds.fit(
                drawer.bounds.offsetEdges(-150.0),
                fitMethod = FitMethod.Contain
            )
        )
        val shdr = shadeStyle {
            fragmentTransform = """
                x_stroke.rgb = mix(x_stroke.rgb, p_endColor.rgb, c_contourPosition / p_len);
            """.trimIndent()
        }
        extend {
            drawer.clear(ColorRGBa.WHITE)
            drawer.strokeWeight = 75.0
            drawer.shadeStyle = shdr
            drawer.stroke = ColorRGBa.PINK
            shdr.parameter("len", c.length)
            shdr.parameter("endColor", ColorRGBa.CYAN)
            drawer.contour(cFit)
        }
    }
}

```

Notice how we divide `c_contourPosition / p_len` to obtain a value that is 0.0 at the start of the contour and 1.0 where it ends. The program creates something like this:

 ![ContourGradient-2026-01-09-08.08.54](https://global.discourse-cdn.com/free1/uploads/openrndr/original/1X/0d63a4cd1c4ccc7e57d7a9a92818b364f00fd791.png)

Now what if you also want to interpolate colors across the width of the stroke? That’s what I discovered this week. What you can do is to use the `va_texCoord0.x` variable, which contains a value that is 0.0 in one side of the contour and 1.0 in the opposite side.

For instance, we could make one side 0.5 darker like this:

```kotlin
        val shdr = shadeStyle {
            fragmentTransform = """
                x_stroke.rgb = mix(x_stroke.rgb, p_endColor.rgb, c_contourPosition / p_len);
                x_stroke.rgb *= (va_texCoord0.x * 0.5 + 0.5);
            """.trimIndent()
        }

```

What we do there is to multiply the red, green and blue components of each pixel by a value between 0.5 and 1.0, resulting in a gradient that goes from PINK to CYAN on one edge of the thick contour, and darker versions of those colors in the opposite edge.

It looks like this:

 ![ContourGradient-2026-01-09-08.15.53](https://global.discourse-cdn.com/free1/uploads/openrndr/original/1X/6a0fbb6f506a7b9d3559ed71a53f703b962c3983.jpeg)

By twisting the shader code a bit we can darken both edges making it look somewhat 3D:

```kotlin
        val shdr = shadeStyle {
            fragmentTransform = """
                x_stroke.rgb = mix(x_stroke.rgb, p_endColor.rgb, c_contourPosition / p_len);
                x_stroke.rgb *= 1.0 - pow(abs(va_texCoord0.x * 2.0 - 1.0), 2.0) * 0.5;
            """.trimIndent()
        }

```

I’ll try to explain what goes on in the second line of the shader code: we take `va_texCoord0.x` which is a number between 0 and 1. By multiplying it by 2.0 and subtracting 1.0, we now have numbers between -1.0 on one edge and 1.0 in the opposite edge. If we then take its absolute value, we go from 1.0 to 0.0 and then back to 1.0, shaped like a `v`. To make this v more like a u, we do `pow` with a value greater than 1.0, in this case, 2.0. You could go higher to make the dark edges thinner. I reverse that with `1.0 -` because I want dark at the sides, not at the center. Finally I multiply by 0.5 to avoid full black on the sides, getting something more subtle. It looks like this:

 ![ContourGradient-2026-01-09-08.23.17](https://global.discourse-cdn.com/free1/uploads/openrndr/original/1X/2009465870d185f46818f7906069dc5aa48ba8ec.jpeg)
