Generative Art skill for Codex
Seeded algorithms that make keepers, not demos. Codex builds generative pieces on a seeded skeleton so every seed reproduces exactly, with parameters that scale with canvas size and palettes chosen rather than randomised. It picks the system for the look you want, sweeps dozens of seeds on a contact sheet to tune ranges until most outputs are keepers, and exports print-resolution PNGs or plotter-ready SVG layered by pen.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- generative-art Installs to ~/.agents/skills/generative-art/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install generative-art The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project generative-art Installs to .agents/skills/generative-art/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/generative-art.zipunzip -o generative-art.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) generative-art PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $generative-art, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Builds seeded, reproducible generative and algorithmic art with p5.js or the plain Canvas 2D API - flow fields, noise, recursive subdivision, circle packing, particle systems and L-systems - with a curated palette, composition rules, URL-driven seeds and parameters, high-resolution PNG export and plotter-ready SVG output. Use when asked for generative art, creative coding, a p5.js sketch, algorithmic patterns, procedural backgrounds, or a series of unique outputs from one algorithm.
Use it when
- You want algorithmic or procedural artwork from code
- A sketch needs reproducible seeds and parameter control
- Outputs must export at print resolution or as plotter SVG
- A generative background or series needs better composition and palette
Use something else when
- Drawing a specific, hand-designed illustration: use svg-illustration
- Building interactive 3D scenes: use threejs-scenes
How to check the result
Loading the same seed twice produces identical output, a 24-seed contact sheet shows mostly keepers, and exported files have the intended pixel size and margins.
The mistake it prevents
Seeding random() but not noise() in p5.js, or calling Math.random() somewhere, so a favourite seed can never be reproduced.
A generative piece is finished when the same seed always produces the same image, most seeds produce something worth keeping, and the output exports at print resolution. The algorithm is a means; composition, palette and restraint decide whether it looks like art or a demo.
Workflow
- Agree the output: screen loop, still image (size, DPI), plotter SVG, or web background.
- Choose the system (table below) and a palette of 3-6 colors.
- Build a seeded skeleton: seed from URL, one PRNG, parameters in one object.
- Get one good image, then sweep 20-50 seeds and tune ranges.
- Add export (PNG at target size, SVG for plotters); record seed and params in file names.
Pick the system
| Look | System | Key parameters |
|---|---|---|
| Flowing hair, wind, marble | Flow field from 2D noise; particles follow angle | noise scale, step length, line count, angle multiplier |
| Mondrian-like or architectural | Recursive subdivision of rectangles | min size, split probability, ratio range |
| Organic dots, cells | Circle packing with collision checks | radius range, attempts, padding |
| Growth, branches, plants | L-system or recursive branching | rules, angle, depth, length decay |
| Topography | Marching squares contours over noise | levels, noise octaves |
| Grids with variation | Tiled motifs with per-cell rules (Truchet tiles) | tile set, rotation probability |
| Dynamic, living | Particle system with forces | count, drag, attraction, lifetime |
Seeded skeleton (Canvas 2D, no dependencies)
Never call Math.random() directly; route all randomness through one seeded generator so
outputs are reproducible.
<!doctype html><meta charset="utf-8"><title>Flow</title>
<style>html,body{margin:0;background:#111;display:grid;place-items:center;min-height:100vh}canvas{max-width:96vw;max-height:96vh}</style>
<canvas id="c"></canvas>
<script>
const qs = new URLSearchParams(location.search);
const seed = Number(qs.get("seed") ?? Math.floor(Math.random() * 1e9));
const P = { size: Number(qs.get("size") ?? 2000), lines: 1800, step: 2, steps: 260, freq: 3.2 };
function sfc32(a, b, c, d) { return () => { a|=0;b|=0;c|=0;d|=0; const t=(a+b|0)+d|0; d=d+1|0;
a=b^b>>>9; b=c+(c<<3)|0; c=(c<<21|c>>>11); c=c+t|0; return (t>>>0)/4294967296; }; }
const rand = sfc32(0x9e3779b9, 0x243f6a88, 0xb7e15162, seed >>> 0);
for (let i = 0; i < 15; i++) rand(); // warm up
const R = (a, b) => a + (b - a) * rand();
const pick = (arr) => arr[Math.floor(rand() * arr.length)];
// value noise, seeded, smooth enough for flow fields
const G = 256, grid = Float32Array.from({ length: G * G }, () => rand());
const fade = (t) => t * t * (3 - 2 * t);
function noise(x, y) {
const xi = Math.floor(x), yi = Math.floor(y), xf = fade(x - xi), yf = fade(y - yi);
const g = (i, j) => grid[((yi + j) & (G - 1)) * G + ((xi + i) & (G - 1))];
const a = g(0,0) + (g(1,0) - g(0,0)) * xf, b = g(0,1) + (g(1,1) - g(0,1)) * xf;
return a + (b - a) * yf;
}
const palette = pick([["#f2e8cf","#386641","#6a994e","#a7c957","#bc4749"],
["#fefae0","#283618","#606c38","#dda15e","#bc6c25"]]);
const cv = document.getElementById("c"), ctx = cv.getContext("2d");
cv.width = cv.height = P.size;
ctx.fillStyle = palette[0]; ctx.fillRect(0, 0, P.size, P.size);
ctx.lineCap = "round";
const m = P.size * 0.08; // margin: composition breathes
for (let i = 0; i < P.lines; i++) {
let x = R(m, P.size - m), y = R(m, P.size - m);
ctx.strokeStyle = pick(palette.slice(1)); ctx.globalAlpha = R(0.35, 0.9);
ctx.lineWidth = P.size * R(0.0006, 0.0025);
ctx.beginPath(); ctx.moveTo(x, y);
for (let s = 0; s < P.steps; s++) {
const a = noise(x / P.size * P.freq, y / P.size * P.freq) * Math.PI * 4;
x += Math.cos(a) * P.step * P.size / 1000; y += Math.sin(a) * P.step * P.size / 1000;
if (x < m || x > P.size - m || y < m || y > P.size - m) break;
ctx.lineTo(x, y);
}
ctx.stroke();
}
document.title = `flow seed=${seed}`;
addEventListener("keydown", (e) => { if (e.key === "s") cv.toBlob((b) => {
const a = document.createElement("a"); a.href = URL.createObjectURL(b);
a.download = `flow_seed-${seed}_${P.size}px.png`; a.click(); }); });
</script>
Parameters scale with P.size, so a 2000px preview and an 8000px print look the same.
p5.js version
p5.js 2.x is current; many tutorials and the older editor default use 1.x. Check which
the project pins. In 2.x, preload() is gone: load assets with await inside an
async setup().
let seed;
function setup() {
createCanvas(2000, 2000);
pixelDensity(1); // export exactly the canvas size
seed = int(new URLSearchParams(location.search).get("seed") ?? random(1e9));
randomSeed(seed); noiseSeed(seed); // seed BOTH: random() and noise()
noLoop();
}
function draw() {
background("#f2e8cf");
// ... all randomness via random(), noise(), never Math.random()
}
function keyPressed() { if (key === "s") saveCanvas(`piece_seed-${seed}`, "png"); }
Loading p5 from a CDN needs network; for offline sandboxes vendor p5.min.js into the
project or use the plain Canvas skeleton.
Composition and palette rules
- Margins of 5-10% make most pieces look intentional; full-bleed only on purpose.
- One dominant color (background), one or two supporting, one accent used in under 10% of marks.
- Vary one or two things strongly (size, density) and keep the rest consistent. Everything random at once reads as noise.
- Create focal density: weight probabilities toward a region (rule of thirds), or leave a deliberate void.
- Pull palettes from references or a curated list, not random HSL; random hues clash.
- Line weight relative to canvas size, not absolute pixels.
Seed sweeps
Generate a contact sheet to tune parameter ranges instead of judging one seed:
// sweep.html?from=1&count=24 renders thumbnails in a grid, each linking to ?seed=n
Adjust ranges until at least 70-80% of seeds are good; then curate. Record favourite
seeds with their parameters in seeds.json.
Plotter-ready SVG
For pen plotters, output paths, not pixels: build an SVG string with one <path> per
line, no fills, stroke width equal to the pen width, grouped by color (<g id="pen-1">)
so each pen is one layer. Merge collinear segments and sort paths to reduce pen travel.
Size the SVG in mm (width="297mm" height="210mm" viewBox="0 0 297 210") so the plotter
software gets real dimensions.
Performance
- Thousands of strokes: batch into one path per color where possible; avoid changing
globalAlpha/strokeStyleper segment. - Heavy computation (packing, long simulations): run in a Web Worker with
OffscreenCanvas, or precompute and draw once. - Animated loops: target 60 fps at preview size; drop particle counts on small screens;
respect
prefers-reduced-motionby rendering a still frame.
Gotchas
noise()in p5 is not reseeded byrandomSeed(); you neednoiseSeed()too.pixelDensitydefaults to the display density; a 2000px canvas exports at 4000px on a Retina screen unless you setpixelDensity(1).- Browser canvas size limits (often around 16k px per side and a total area cap) cause blank exports at very large sizes. Render in tiles if you need more.
- Async loading order,
Date.now(), window size and a second unseededMath.random()call break reproducibility. Derive everything from the seed and the parameter object.
Verification
- Load
?seed=123twice (and in another browser): byte-identical PNG or visually identical output. - Contact sheet of 24 seeds: count the keepers; tune if under about 70%.
- Export at target size; open the file and check margins, line weights and DPI (pixels = inches x DPI, e.g. 12 in at 300 DPI = 3600 px).
Deliverable
art/<piece>/
index.html # ?seed=&size= parameters, "s" saves PNG
sweep.html # seed contact sheet
seeds.json # curated seeds with params and notes
out/<piece>_seed-<n>_<px>px.png | .svg
Related: hand-drawn (non-random) SVG art in $svg-illustration; 3D scenes in
$threejs-scenes.
What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Generative Art"
short_description: "Seeded algorithms that make keepers, not demos"
default_prompt: "$generative-art Make a p5.js flow field sketch with an earthy palette and a seed in the URL" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.