Created
April 4, 2026 03:32
-
-
Save rgchris/396b34ded42f3ff1b22e4d6b68e42e7f to your computer and use it in GitHub Desktop.
Mesh Gradients Generator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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