Skip to content

Instantly share code, notes, and snippets.

@rgchris
Created April 4, 2026 03:32
Show Gist options
  • Select an option

  • Save rgchris/396b34ded42f3ff1b22e4d6b68e42e7f to your computer and use it in GitHub Desktop.

Select an option

Save rgchris/396b34ded42f3ff1b22e4d6b68e42e7f to your computer and use it in GitHub Desktop.
Mesh Gradients Generator
Rebol [
Title: "Mesh Gradients"
Author: "Christopher Ross-Gill"
Date: 26-Mar-2026
Version: 0.1.0
File: %meshes.reb
Type: module
Name: rgchris.meshes
Exports: [
meshes
]
Needs: [
r3:rgchris:uuid
r3:rgchris:easings
r3:rgchris:color-lab
r3:rgchris:svg
]
Comment: [
https://github.com/sFrady20/easy-mesh-gradient/tree/main
{
Adapted from 'Easy Mesh Gradient' by @sFrady20
Copyright (c) 2026, Steven Frady. MIT License
}
]
]
clamp: func [
"Validates that a number is within a given range."
value [number!]
floor [number!]
ceiling [number!]
][
max floor min ceiling value
]
next-id: func [
"Generate a unique ID for the life cycle of this script"
base [issue!]
/local count
][
count: #[]
if not find count base [
put count base 0
]
count/:base: count/:base + 1
to issue! rejoin [
to string! base #"-" count/:base
]
]
validate-options: func [
"Validates point generation options and returns normalized values."
options [map!]
"Options to validate"
/local out
][
also out: copy #[]
for-each key unique compose [
seed generator points scale hue lightness saturation easing stops
(keys-of options: copy options)
][
switch/default key [
generator [
; Custom function to generate points
; If not provided, uses the default generator
; WORD! references to functions only
out/generator: case [
not word? options/generator [
in meshes/generators 'default
]
function? get/any options/generator [
options/generator
]
in meshes/generators options/generator [
in meshes/generators options/generator
]
#else [
do make error! "Could not locate provided Meshes Generator"
]
]
]
points [
out/points: max 2 any [
options/points 5
]
]
scale [
; Range [min max] for the scale property of generated points
; @default [0.5 2]
out/scale: neaten/flat either block? options/scale [
sort reduce [
max .1 options/scale/1
max .1 options/scale/2
]
][
copy [
.5 2
]
]
]
hue [
; Range [min max] for the hue property of generated points (-180-540)
; @default [0 360]
out/hue: neaten/flat either block? options/hue [
sort reduce [
clamp options/hue/1 -180 540
clamp options/hue/2 -180 540
]
][
copy [
0 360
]
]
]
saturation [
; Range [min max] for the saturation property of generated points (0-1)
; @default [0.5 1]
out/saturation: neaten/flat either block? options/saturation [
sort reduce [
clamp options/saturation/1 0 1
clamp options/saturation/2 0 1
]
][
copy [
.5 1
]
]
]
lightness [
; Range [min max] for the lightness property of generated points (0-1)
; @default [0.5 1]
out/lightness: neaten/flat either block? options/lightness [
sort reduce [
clamp options/lightness/1 0 1
clamp options/lightness/2 0 1
]
][
copy [
.5 1
]
]
]
seed [
; Seed string for reproducible random point generation
; Same seed will always generate the same points
out/seed: any [
options/seed
uuid/to-text uuid/generate
]
]
easing [
; Easing function that controls the transition curve
; Accepts a value between 0 and 1, returns a value between 0 and 1
; WORD! references to functions only
out/easing: case [
not word? options/easing [
in easings 'cubic-ease-in-out
]
function? get/any options/easing [
options/easing
]
in easings options/easing [
in easings options/easing
]
#else [
do make error! "Could not locate provided Meshes Easing"
]
]
]
stops [
; Number of intermediate color stops in the gradient transitions.
; Higher values create smoother gradients but increase output size.
; @default 10
; @minimum 2
out/stops: max 2 any [
options/stops 10
]
]
][
put out key select options key
]
]
]
meshes: context [
generators: context [
default: func [
"Generates random points for a mesh gradient"
"Uses validated options to ensure all values are within valid ranges"
options [map!]
"Options for point generation"
/local random-01 color
][
random/seed options/seed
random-01: func [] [
random 0.999999999999999
]
neaten collect [
repeat count options/points [
color: reduce [
(options/hue/2 - options/hue/1 * random-01 + options/hue/1)
(options/saturation/2 - options/saturation/1 * random-01 + options/saturation/1)
(options/lightness/2 - options/lightness/1 * random-01 + options/lightness/1)
]
keep compose/deep #[
at: (as-pair random-01 random-01)
scale: (options/scale/2 - options/scale/1 * random-01 + options/scale/1)
hsl: (color)
color: (color-lab/hsl/to-rgb color)
]
]
]
]
]
create: func [
options [map!]
/local params ease generator stops points progress background
][
params: options
options: validate-options options
params/seed: options/seed
ease: get options/easing
generator: get options/generator
points: generator options
either empty? points: generator options [
do make error! "No points in Spec"
][
background: copy/deep points/1
stops: max 2 any [
options/stops 10
]
for-each point points [
point/id: next-id #gradient
point/stops: neaten collect [
repeat stop stops [
progress: divide stop - 1 stops - 1
keep compose #[
at: (to percent! progress)
color: (point/color)
alpha: (ease 1 - progress)
]
]
]
]
compose #[
background: (background)
points: (points)
options: (options)
params: (params)
]
]
]
as-svg: func [
size [pair!]
mesh [map!]
/with
custom-filter [block! none!]
/local rounded radius filter-id
][
rounded: find #(typeset! [integer! pair!]) type-of mesh/options/rounded
svg/create size [
comment mold mesh/params
if custom-filter [
filter filter-id: next-id #filter _ custom-filter
]
if mesh/options/circles [
filter #shadow 40x40 [
; set @shadow drop-shadow 20 0x0 'black .2
; composite :alpha 'out
; blur 3
blur 10
composite alpha 'out
]
]
rectangle/:rounded #[
fill: (mesh/background/color)
]
0x0 size
mesh/options/rounded
for-each point mesh/points [
radial-gradient point/id [
center 100 * point/at
radius to percent! point/scale
relative
for-each stop point/stops [
add-stop stop/at stop/alpha stop/color
]
]
rectangle/:rounded #[
fill: (point/id)
filter: (if custom-filter :filter-id)
]
0x0 size
mesh/options/rounded
]
if mesh/options/circles [
for-each point mesh/points [
circle #[
fill: #fff
filter: #shadow
opacity: (random 30%)
]
point/at * canvas-size
to percent! point/scale / 1.5
]
]
]
]
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment