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.

  • Design & UX
  • $generative-art
  • 188 lines
  • 2 files
  • ~2,050 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- generative-art

Installs to ~/.agents/skills/generative-art/ for every project on this machine.

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

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.

~/.agents/skills/generative-art/SKILL.md

--- name: generative-art description: Builds seeded, reproducible generative and algorithmic art with p5.js or the plain Canvas 2D API - flow fields, noise, recursive subdivision, circle… license: MIT ---

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

LookSystemKey parameters
Flowing hair, wind, marbleFlow field from 2D noise; particles follow anglenoise scale, step length, line count, angle multiplier
Mondrian-like or architecturalRecursive subdivision of rectanglesmin size, split probability, ratio range
Organic dots, cellsCircle packing with collision checksradius range, attempts, padding
Growth, branches, plantsL-system or recursive branchingrules, angle, depth, length decay
TopographyMarching squares contours over noiselevels, noise octaves
Grids with variationTiled motifs with per-cell rules (Truchet tiles)tile set, rotation probability
Dynamic, livingParticle system with forcescount, 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/strokeStyle per 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-motion by rendering a still frame.

Gotchas

  • noise() in p5 is not reseeded by randomSeed(); you need noiseSeed() too.
  • pixelDensity defaults to the display density; a 2000px canvas exports at 4000px on a Retina screen unless you set pixelDensity(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 unseeded Math.random() call break reproducibility. Derive everything from the seed and the parameter object.

Verification

  1. Load ?seed=123 twice (and in another browser): byte-identical PNG or visually identical output.
  2. Contact sheet of 24 seeds: count the keepers; tune if under about 70%.
  3. 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.yamlgenerated from this page

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.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem