negative-space

negative-space

Docker app from Stoatworks Labs' Repository

Overview

Lays out LED walls and projection surfaces with their real dimensions and gaps, and works out the composite canvas including the blank pixels between them, with slice rectangles, a Resolume advanced-output file, a guide plate, PowerPoint geometry and a PDF plan. Runs entirely in the browser.

AI-assisted project. This codebase was created with Claude Code. The geometry is arithmetic and is covered by tests, including conservation invariants, and the generated .pptx has been opened in real PowerPoint. The Resolume advanced-output XML has never been round-tripped through a running Arena, and no array laid out here has been built on site — check one slice against the wall before trusting a show to it. See Status.

Negative Space

A negative-space calculator for LED and projection arrays. Lay out display surfaces with their real physical dimensions and resolutions, say how far apart they are, and get the composite canvas including the blank pixels in the gaps — so content laid out across the array accounts for the space between the screens as though it were pixel surface.

Runs entirely in your browser. No account, no backend, nothing uploaded.

The problem

Three LED walls in a row, 100 mm apart. Each is 576 × 384 px. Lay them out as a 1728 × 384 composition and anything moving across the array jumps: it leaves the first wall and instantly appears on the second, because the composition has no idea the gap exists.

What you actually want is a canvas that includes the gap — 1805 × 384 — where the 77 blank pixels between the walls are composited into the dark. An object crossing the array now disappears behind the gap and re-emerges the far side, at the speed it was really going.

Working out those blank pixel counts by hand means dividing physical gaps by a pixel pitch that is rarely a round number (a "P2.6" cabinet is 2.604166… mm), for every gap, and then offsetting every slice by the running total. That is the job this does.

What it gives you

  • The composite canvas size, and what fraction of it is gap.
  • Per-surface slice rectangles, gap offsets already applied.
  • Resolume Arena advanced output — both the AdvancedOutput.xml and preset forms.
  • A guide image (SVG and PNG) at exactly the canvas resolution, with the gaps shaded — drop it behind a PowerPoint slide or into After Effects and design against the real negative space.
  • PowerPoint slide geometry, plus a starter .pptx with the surfaces and gaps drawn on it.
  • CSV of every surface and gap, and a JSON project file to save and reload.
  • A PDF report with a scale plan of the canvas, for taking to site.
  • LivePremier pitch compensation — the H and V ratios an Analog Way Aquilon needs to drive surfaces of different pitches from one screen.

Using it

Add surfaces from the cabinet presets or type real numbers in, drag them around the canvas, and click any gap to set its spacing exactly. Mixed pitches are handled: the canvas defaults to the finest pitch present, so no surface is ever asked to show fewer pixels than it has.

See docs/USER-GUIDE.md.

LivePremier pitch compensation

If the array is going on an Analog Way LivePremier, one screen spanning surfaces of different pitches needs each output told how much canvas its raster is worth — or a layer crossing between them changes physical size as it goes. The panel gives the two numbers per surface, for Preconfig > Canvas > Pitch.

The arithmetic is aquilon-pitch's engine, vendored as source into src/vendor/aquilon-pitch/. It carries four things about the device that are easy to get backwards and still look right, each established there by driving a simulator rather than reading the manual: the ratio multiplies a raster to give its canvas footprint, so a coarser wall goes above 1.000; the field is an integer in thousandths, 0.100 to 10.000; an out-of-range write is discarded, not clamped; and the footprint floors.

The ratios are right; the positions are not sent anywhere. A LivePremier lays its canvas out from each output's own area of interest, which this tool cannot reach into — so the gaps that are the entire point of a Negative Space canvas still have to be built there by hand, from the canvas rectangles shown beside each ratio.

One thing worth watching: those ratios are always built on the finest pitch, because a reference below 1.000 would set the whole screen upscaling. If the project's canvas pitch is set to coarsest or a manual value, the two canvases are different sizes and the panel says so — the rectangles it shows are rescaled to match the ratios, and are deliberately not the pixel numbers shown elsewhere on the page.

Development

npm install
npm run dev      # vite dev server
npm test         # vitest
npm run build    # tsc -b && vite build -> dist/

AGENTS.md explains the model and the traps. CLAUDE.md is the command reference.

Status

The geometry is arithmetic and is covered by tests, including conservation invariants. The .pptx has been opened in real PowerPoint. The Resolume XML has never been round-tripped through a running Arena — the structure conforms to real Arena files and is asserted in CI, but check one slice against the wall before trusting a show to it. See docs/resolume-export.md.

Run your own copy

Negative Space is a static page, so hosting it yourself is one container serving the built files — the same files the hosted copy serves, running somewhere that still works when the venue has no internet.

Docker. The image is built by this repo's docker.yml workflow on every push and published as ghcr.io/stoatworks-labs/negative-space:

docker run -d --name negative-space --restart unless-stopped -p 8541:80 ghcr.io/stoatworks-labs/negative-space:latest

Or docker compose up -d with the docker-compose.yml in this repo, which maps the same port. Either way it is then at http://localhost:8541/.

Unraid. Search Community Applications for Negative Space — the template is templates/negative-space.xml in stoatworks-unraid, which is what the CA feed reads.

Stoatworks Burrow lists it under Self-hosted, with the compose file a click away.

The Dockerfile, docker-compose.yml, docker/ and the workflow are generated from fleet.json in stoatworks-unraid. Change them there and regenerate rather than editing them here.

Licence

MIT. See LICENSE.

Install negative-space on Unraid in a few clicks.

Find negative-space in Community Apps on your Unraid server, review the template, and click Install. Unraid handles the Docker app or plugin setup from the published template.

Open the Apps tab on your Unraid server Search Community Apps for negative-space Review the template variables and paths Click Install

Related apps

Details

Repository
ghcr.io/stoatworks-labs/negative-space:latest
Last Updated2026-09-07
First Seen2026-09-07

Runtime arguments

Web UI
http://[IP]:[PORT:80]/
Network
bridge
Shell
sh
Privileged
false

Template configuration

WebUI PortPorttcp

Host port for the web interface.

Target
80
Default
8541
Value
8541