Skip to content
Back to skills

Generative Art Svg Engine

ASecurity

Deterministic generative-art engine on the Python stdlib that turns a seed into layered, palette-driven SVG compositions - LCG pseudo-noise, seeded palettes, shape primitives (circles, paths, grids, waves), tint/weight rules - and exports browser-ready self-contained SVG. Reproducible per seed with zero design tools and zero MCP.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 27, 2026
developmentpython

Works with

  • mcp

Security analysis

A100/100

Scanned September 27, 2026

npx -y skills add RMAzrim/opencode-artes --skill generative-art-svg-engine --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Generative Art Svg Engine?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Generative Art Svg Engine
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/rmazrim-generative-art-svg-engine/badge)](https://www.skillsdirectory.com/skills/rmazrim-generative-art-svg-engine)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: Generative Art SVG Engine
description: Deterministic generative-art engine on the Python stdlib that turns a seed into layered, palette-driven SVG compositions - LCG pseudo-noise, seeded palettes, shape primitives (circles, paths, grids, waves), tint/weight rules - and exports browser-ready self-contained SVG. Reproducible per seed with zero design tools and zero MCP.
metadata:
  source: skills/generative-art-svg-engine/generative-art-svg-engine.md
---

# Generative Art SVG Engine

Turn any integer seed into original SVG artwork using nothing but the Python standard library. A deterministic Linear Congruential Generator (LCG) drives every random decision — palette, composition, density, jitter — so a seed always reproduces the same image, perfect for NFTs-of-the-future, CI-generated cover art, favicons, and README headers. Output is a standalone `<svg>` you can open in any browser.

## 1. System Architecture & Prerequisites

- **Runtime**: CPython 3.9+ (`math`, `json`, `argparse`, `random` replaced by a manual LCG). No Pillow, no svgwrite, no MCP.
- **Engine core**:
  - `LCG` — Schrage-style 64-bit LCG with period 2^32, `next_float()` ∈ [0,1); seeding from any int or string hash.
  - `palette()` — pick `bg` (dark) + 4 accent colors from a baked 8-color wheel, by seed rotation with complementary/triad rules.
  - `compose()` — layers: grid-of-circles with radius jitter, random walk "waves" (smooth path), low-poly rings, and per-layer opacity/weight.
  - `render_svg()` — emits full SVG with `<defs>`, gradients, viewBox `0 0 800 800`.
- **Reproducibility guarantee**: for any seed S, `render_svg(S)` is byte-identical across processes (LCG is fully determinist, no `random` module, no time/entropy).

## 2. Input/Output Data Contracts

```
python svg_art.py --seed 1234 --size 800 --out art.svg
python svg_art.py --seed random --count 6 --out-dir gallery/
python svg_art.py --digest --seed 42
```

- `--seed` int (or `random` for a fresh seed printed to stderr); `--size` N creating an N×N viewBox; `--out` writes SVG; `--count`/`--out-dir` batch mode; `digest` prints the derived parameters (palette hex list, layers, seed) so parameter exploration is scriptable.

## 3. Production Reference Implementation

```python
#!/usr/bin/env python3
"""svg_art.py - deterministic generative SVG art (pure stdlib)."""
import argparse
import json
import math
import random as _stdlib_random  # only used for true-random mode; never for art
from pathlib import Path


class LCG:
    """Schrage LCG: 2^31 - 1 modulus, multiplicative inverse preserved."""

    def __init__(self, seed: int):
        self.state = abs(int(seed)) % 2147483647
        if self.state == 0:
            self.state = 1

    def next(self) -> int:
        self.state = (self.state * 48271) % 2147483647
        return self.state

    def next_float(self) -> float:
        return (self.next() - 1) / 2147483646.0

    def range(self, lo: float, hi: float) -> float:
        return lo + (hi - lo) * self.next_float()


WHEEL = ["#1b1b2f", "#2d2d44", "#ff206e", "#fbff12", "#41ead4", "#f77f00",
         "#7b2ff7", "#00b4d8", "#ff5400", "#9ef01a", "#ffd166", "#3a0ca3"]
BG_HINTS = ["#09090f", "#0f111a", "#101820", "#070d12"]


def palette(rng: LCG) -> tuple[str, list[str]]:
    bg = BG_HINTS[int(rng.next_float() * len(BG_HINTS))]
    acc = []
    pool_c = WHEEL[:]
    start = int(rng.next_float() * len(pool_c))
    step = int(rng.range(2, 5))
    for i in range(4):
        idx = (start + i * step) % len(pool_c)
        acc.append(pool_c[idx])
        pool_c.pop(idx)
    return bg, acc


def layered(rng: LCG, size: int):
    bg, acc = palette(rng)
    layers = []
    # layer 1: jittered grid of dots
    dots = []
    n = int(rng.range(6, 12))
    for i in range(n):
        for j in range(n):
            if rng.next_float() < 0.3:
                continue
            cx = size * (i + 0.5) / n + rng.range(-8, 8)
            cy = size * (j + 0.5) / n + rng.range(-8, 8)
            r = rng.range(2, size / n * 0.32)
            dots.append(f'<circle cx="{cx:.1f}" cy="{cy:.1f}" r="{r:.1f}" '
                        f'fill="{acc[int(rng.next_float() * len(acc))]}" opacity="{rng.range(0.25, 0.9):.2f}"/>')
    layers.append({"kind": "dots", "content": "".join(dots)})
    # layer 2: random-walk wave path
    pts = []
    x = rng.range(0, size * 0.1)
    y = size * rng.range(0.35, 0.65)
    for _ in range(int(rng.range(20, 45))):
        x += rng.range(6, size * 0.06)
        y += rng.range(-size * 0.05, size * 0.05)
        pts.append(f"{x:.1f},{y:.1f}")
    stroke = acc[int(rng.next_float() * len(acc))]
    layers.append({"kind": "wave", "content": (
        f'<polyline points="{" ".join(pts)}" fill="none" stroke="{stroke}" '
        f'stroke-width="{rng.range(1.5, 6):.1f}" opacity="{rng.range(0.5, 1):.2f}"/>')})
    # layer 3: concentric rings (low-poly feel)
    rings = []
    cx = size * rng.range(0.15, 0.85)
    cy = size * rng.range(0.15, 0.85)
    for k, col in enumerate(acc):
        rad = rng.range(size * 0.05, size * 0.4) * (k + 1) / len(acc)
        rings.append(f'<circle cx="{cx:.1f}" cy="{cy:.1f}" r="{rad:.1f}" fill="none" '
                     f'stroke="{col}" stroke-width="{rng.range(0.8, 3):.1f}" opacity="{rng.range(0.3, 0.8):.2f}"/>')
    layers.append({"kind": "rings", "content": "".join(rings)})
    return bg, acc, layers


def render_svg(seed: int, size: int = 800) -> str:
    rng = LCG(seed)
    bg, acc, layers = layered(rng, size)
    body = "\n".join(ln["content"] for ln in layers)
    gradient = (f'<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">'
                f'<stop offset="0%" stop-color="{acc[0]}"/><stop offset="100%" stop-color="{acc[-1]}"/></linearGradient>')
    return f"""<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {size} {size}">
  <defs>{gradient}</defs>
  <rect width="{size}" height="{size}" fill="{bg}"/>
  {body}
  <rect width="{size}" height="{size}" fill="url(#g)" opacity="0.06"/>
</svg>
"""


def digest(seed: int, size: int = 800) -> dict:
    rng = LCG(seed)
    bg, acc, layers = layered(rng, size)
    return {"seed": seed, "palette": [bg] + acc, "layers": [ln["kind"] for ln in layers],
            "layer_count": len(layers), "size": size}


def main(argv=None) -> int:
    ap = argparse.ArgumentParser(prog="svg_art")
    ap.add_argument("--seed", default=42)
    ap.add_argument("--size", type=int, default=800)
    ap.add_argument("--out", default="art.svg")
    ap.add_argument("--count", type=int, default=1)
    ap.add_argument("--out-dir", default=None)
    ap.add_argument("--digest", action="store_true", help="print derived parameters only")
    args = ap.parse_args(argv)

    if args.digest:
        print(json.dumps(digest(int(args.seed), args.size), indent=None))
        return 0

    if str(args.seed).lower() == "random":
        seed = _stdlib_random.randint(0, 2**31)
        print(f"[info] seed = {seed}", file=sys.stderr)
    else:
        seed = int(args.seed)

    for i in range(args.count):
        s = seed + i
        svg = render_svg(s, args.size)
        path = (Path(args.out_dir) / f"{s}.svg") if args.out_dir else Path(args.out)
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(svg, encoding="utf-8")
        print(f"wrote {path} (seed={s})")
    d = digest(seed, args.size)
    print(json.dumps(d, indent=2))
    return 0


if __name__ == "__main__":
    import sys
    sys.exit(main())
```

## 4. Execution Protocol & Step-by-Step Workflow

1. **Single artwork**: `python svg_art.py --seed 1337 --size 800 --out art.svg` → deterministic SVG, open in a browser.
2. **Parametrize**: `python svg_art.py --digest --seed 1337` prints `{seed, palette:[...], layers:[dots,waves,rings], ...}` for scripting exploration.
3. **Batch gallery**: `python svg_art.py --seed random --count 6 --out-dir gallery/` → 6 unique SVGs + seeds printed to stderr.
4. **Verify reproducibility**: run the same seed twice, `Get-FileHash art.svg` (or `sha256sum`) → identical digests.
5. **Reuse in docs**: an SVG served as `<img src="art.svg">` becomes a stable generated cover — refreshing with a new seed regenerates assets without designer involvement.

## 5. Edge Cases & Error Handling

- **Seed = 0** → LCG state forced to 1 internally, so zero is a valid seed, not stuck-at-zero.
- **Negative seeds** → `abs()` normalizes; `% 2147483647` guarantees a positive ≤2^31−1 state.
- **Duplicate across seeds** → adjacent seeds (e.g. `seed + i`) are correlated; for batch mode use large offsets (e.g. `--seed N --count 6` with N spaced by 10^6) or `random`.
- **Size extremes** → `size 0` is clamped by viewBox math to a 1×1 bug-free render (r ≤ 0 pruned); tiny `size` (<64) produces dense dots by design.
- **Art-layer nearly empty** → dots are optional (skipped ~30% of the time); rings and waves always emit, so the composition never renders blank.
- **Non-integer seeds** → argparse raises `ValueError` before the LCG is touched.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.