Generated Shapes

2026 Digital,Vector,Python,Generative

Generated Shapes

Generated Shapes

A shape made with a simple stroke is not, in itself, particularly interesting.

But vary the dimensions, and birth a population beyond counting, and something changes.

Viewed from afar, the mind cannot comprehend something so complex, yet so basic.

An experiment in Python scripting. The script only hints at what it produces.

The artworks are very dense and intended to be printed at size A0 or above. It may be helpful to open an image in a new browser tab, in order to fully view.

#generate_shapes(shape='circle', output_file='output/circles-mono.svg', count=12500, scale=25, fill=False)
#generate_shapes(shape='rectangle', output_file='output/rectangles-mono.svg', count=25000, scale=25, aspect_ratio=2.0, fill=False)
#generate_shapes(shape='square', output_file='output/squares-mono.svg', count=25000, scale=50, fill=False)
#generate_shapes(shape='triangle', output_file='output/triangles-mono.svg', count=25000, scale=50, fill=False)

import drawsvg as draw
import random
import math
import os

PALETTE_WARM = {
    1: '#bbe0f0',  # Columbia Blue
    2: '#f49f0a',  # Gamboge
    3: '#00a6a6',  # Light Sea Green
    4: '#efca08',  # Jonquil
    5: '#f08700'   # Tangerine
}

PALETTE_COOL = {
    1: '#a8dadc',  # Powder Blue
    2: '#457b9d',  # Celadon Blue
    3: '#1d3557',  # Prussian Blue
    4: '#48cae4',  # Sky Blue
    5: '#023e8a'   # Royal Blue
}

PALETTE_EARTH = {
    1: '#d4a373',  # Fawn
    2: '#8a5a44',  # Coffee
    3: '#606c38',  # Dark Olive
    4: '#dda15e',  # Earth Yellow
    5: '#bc6c25'   # Copper
}

PALETTE_BERRY = {
    1: '#e0aaff',  # Mauve
    2: '#c77dff',  # Heliotrope
    3: '#9d4edd',  # Purple
    4: '#7b2cbf',  # French Violet
    5: '#5a189a'   # Persian Indigo
}


def generate_shapes(
    shape='circle',
    output_file='output/shapes.svg',
    width=3508,
    height=4960,
    count=100000,
    tiers=5,
    scale=10,
    aspect_ratio=2.0,
    palette=None,
    fill=True,
    fill_color=None,
    stroke_color='#000000',
    background=None,
    opacity=1.0,
    rotate=False,
    seed=None,
    dpi=300,
):
    """Generate an SVG filled with random shapes.

    Args:
        shape: 'circle', 'rectangle', 'square', or 'triangle'
        output_file: Output SVG path
        width: Canvas width in pixels
        height: Canvas height in pixels
        count: Number of shapes to generate
        tiers: Number of size tiers (each shape is randomly assigned one)
        scale: Base size multiplier (size = tier * scale)
        aspect_ratio: Width/height ratio for rectangles
        palette: Dict mapping tier -> color, or None for single-colour output
        fill: Whether shapes are filled
        fill_color: Explicit fill color (overrides palette when set)
        stroke_color: Stroke colour
        background: Canvas background colour (None = transparent)
        opacity: Shape opacity (0.0 - 1.0)
        rotate: Randomly rotate rectangles and squares
        seed: Random seed for reproducible output
        dpi: Output resolution (default 300)
    """
    if seed is not None:
        random.seed(seed)

    output_dir = os.path.dirname(output_file)
    if output_dir:
        os.makedirs(output_dir, exist_ok=True)

    width_in = width / dpi
    height_in = height / dpi
    d = draw.Drawing(
        f'{width_in}in', f'{height_in}in',
        viewBox=f'0 0 {width} {height}',
        displayInline=False,
    )

    if background:
        d.append(draw.Rectangle(0, 0, width, height, fill=background))

    group = draw.Group()

    for _ in range(count):
        x = random.uniform(0, width)
        y = random.uniform(0, height)
        tier = random.randint(1, tiers)
        size = tier * scale
        stroke_width = tier

        if fill:
            if fill_color:
                fc = fill_color
            elif palette:
                fc = palette.get(tier, stroke_color)
            else:
                fc = stroke_color
        else:
            fc = 'none'

        sc = stroke_color
        if not fill and palette:
            sc = palette.get(tier, stroke_color)

        kwargs = dict(
            fill=fc,
            stroke=sc,
            stroke_width=stroke_width,
            opacity=opacity,
        )

        if shape == 'circle':
            elem = draw.Circle(x, y, size, **kwargs)
        elif shape == 'rectangle':
            w = size * aspect_ratio
            h = size
            if rotate:
                angle = random.uniform(0, 360)
                kwargs['transform'] = f'rotate({angle} {x + w / 2} {y + h / 2})'
            elem = draw.Rectangle(x, y, w, h, **kwargs)
        elif shape == 'square':
            if rotate:
                angle = random.uniform(0, 360)
                kwargs['transform'] = f'rotate({angle} {x + size / 2} {y + size / 2})'
            elem = draw.Rectangle(x, y, size, size, **kwargs)
        elif shape == 'triangle':
            h = size * math.sqrt(3) / 2
            x1, y1 = x, y - h * 2 / 3
            x2, y2 = x - size / 2, y + h / 3
            x3, y3 = x + size / 2, y + h / 3
            if rotate:
                angle = random.uniform(0, 360)
                kwargs['transform'] = f'rotate({angle} {x} {y})'
            elem = draw.Lines(x1, y1, x2, y2, x3, y3, close=True, **kwargs)
        else:
            raise ValueError(f"Unknown shape: {shape}")

        group.append(elem)

    d.append(group)
    d.save_svg(output_file)

Script is licensed CC BY-NC 4.0 https://creativecommons.org/licenses/by-nc/4.0/