Skip to content
sefatlmnPublic

About

Halftone Tools is a free, browser-based print-effects workshop designed for real-time image manipulation. Built using p5.js, it operates entirely client-side as a static site, meaning all image processing happens locally on your machine with total privacy. No accounts, no backend, and no uploads required.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Halftone Tools

A free, browser-based print-effects workshop built with p5.js.

Load an image, animated GIF, or short video and run it through halftone screens, dithering, risograph separations, ASCII, glitch effects and more, all with live controls. You can stack two effects and export stills, separations, vectors, processed GIFs, or silent WebM video.

There's no backend, no framework, no build step. Just static files. Everything runs in your browser and your media never leaves your machine.

p5.js 1.11.x No build step

Effects

# Effect What it does
00 None Pass-through. Shows the source after pre-adjust with no screen applied. Selecting this in slot B clears the second effect.
01 Halftone Classic rotated dot screen where the dot radius maps to darkness. Supports circle, square, line, and star dots in a single mono ink or full CMYK separation with proper screen angles and multiply blending.
02 Dither Ordered dithering (2x2, 4x4, 8x8 Bayer) and Floyd-Steinberg error diffusion with optional serpentine scan. Multiple palettes (1-bit, duotone, 3/4-tone, CMYK), chunky pixel scaling, and a modulation field.
03 ASCII Converts the image to a grid of characters from an editable brightness ramp. Mono or source-coloured, rendered in a real monospace font. There's an optional selectable-text overlay so you can copy the output.
04 Risograph Simulates 2 or 3 spot-colour separations using luminance bands or RGB channels. Each layer gets its own halftone or grain screen, with misregistration offset and multiply compositing.
05 Xerox High-contrast photocopy look. Cranks up contrast, applies a soft blur, adds a noisy threshold, and sprinkles in toner dropouts and dust.
06 Analog A MiniDV camera signal on a CRT. Resamples onto a virtual raster of scanlines, applies DV 4:1:1 chroma bleed, highlight clip and edge enhancement (8×8 DCT blocking at high Signal), then draws brightness-dependent beams through an aperture-grille or slot mask, with beam defocus (Blur) and halation.
07 Glitch Static, seeded datamosh. Random-height band shifts, block smears, and channel tears. Works horizontal or vertical.
08 RGB Shift Chromatic aberration in linear or radial (lens fringing) mode. Amount/angle (or radial strength) control with edge bias.
09 Pixel Sort Sorts pixel runs by brightness, hue, or saturation. Several interval modes: threshold (Asendorf-style), edge detection, seeded random, or full row/column.
10 Emboss Raised-relief look mapping luminance gradients along a light angle around mid-grey. Supports grey, colour, and tinted modes.
11 Tiles Cuts the frame into a dynamic grid of multi-spanning rectangular tiles with partial shuffle and rotation.

Analog recipes: the defaults are a clean broadcast monitor. For a consumer TV, set Mask to Slot, Lines 280, Bloom 0.45, Blur 0.45, Signal 0.5. For a PVM close-up, use Grille, Lines 480, Mask strength 0.55, Scanlines 0.8, Bloom 0.15, Blur 0.05. Fine phosphor and scanline structure only shows when the screen has the pixels for it, so it is clearer in 2× PNG exports and at low Lines.

Effects 07 to 09 are the glitch family. Glitch and Pixel Sort (random intervals) take a seed, so the same seed always gives you the same result. Hit Rnd (randomize) or press R to reroll the active effect's settings.

Gradient Map, Tone, and Hue / Sat are colour modules: set them as the Colour pre-stage under Pre-adjust to recolour the source before the effect runs (e.g. gradient-map into a halftone).

How to run it

It's a static site, so any file server works. From this folder:

# Node (same as `npm start`, which serves on port 8000)
npx serve .

# or Python 3
python3 -m http.server 8000

Then open the URL it prints (usually http://localhost:8000). Opening index.html directly via file:// won't work because the app uses ES modules, which browsers only load over HTTP.

Tested on current Chrome, Firefox, and Safari.

Tests

npm test

Runs the Node test suite in tests/*.test.mjs: media helpers, animated-export guards, the filmstrip wheel, and a headless-Chromium pass that renders every effect across its modes, checks the pre-adjust curves and grain, and enforces a 50 ms warm-frame budget per effect. It needs Playwright's Chromium (npx playwright install chromium).

Browser smoke tests

The deterministic animated-media smoke pages can be run against all supported browser engines:

npm ci
npx playwright install --with-deps chromium firefox webkit
npm run test:browser

The command runs both tests/browser-smoke.html and tests/app-integration-smoke.html in Chromium (Chrome), Firefox, and WebKit. WebKit is the Safari-equivalent target used by Linux CI, where Apple Safari cannot run. Each target checks disposal 2 and disposal 3 forward and backward seeks, transparent-pixel compositing, and canvas cleanup. Engine-specific assertion or page errors identify the browser and smoke page in the failure output. The same matrix runs automatically in GitHub Actions.

How to use it

  1. Load an image, animated GIF, or short video (or try the Demo plate). You can also drag and drop onto the stage.
    • Image: up to 20 MB and 8192 px per side (larger images are downscaled to 2560 px on load).
    • GIF: up to 12 MB, 1600 px per side, 240 frames, 30 seconds, and a 48 MB decoded-patch budget.
    • Video: MP4, WebM, MOV, or OGV up to 60 MB, 1920 px per side, and 60 seconds.
    • Animated playback runs at a constant 15 processed frames per second (or the media's original frame rate if 15 fps or lower) to keep motion smooth and the browser responsive.
  2. Pre-adjust brightness, contrast, gamma, grain, and invert. Optionally pick a Colour pre-stage (Gradient Map, Tone, or Hue/Sat) that gets applied to the working buffer before the effect runs.
  3. Pick Effect A from the filmstrip at the bottom and tweak its parameters. The preview updates live.
  4. Want to stack a second effect? Click B in the filmstrip toggle, then pick any effect. B runs on A's rendered output. Picking 00 None in slot B clears the stack.
  5. Export (button or press E) opens the overlay. Every export is free, full-resolution:
    • PNG at 2x device resolution
    • Colour separation ZIP with one plate per ink (for Halftone CMYK, RGB Shift, and Risograph)
    • SVG with true vectors (for Halftone, ASCII, Dither, and Risograph). Not available for raster effects or while two effects are stacked.
    • Animated GIF for GIF sources, encoded in a worker at a constant 15 fps (or original if ≤ 15 fps) and capped at 720 px.
    • Silent WebM for video sources, recorded from the processed canvas at a constant 15 fps (or original if ≤ 15 fps). Source audio is never captured. Like most browser recordings, the file has no duration in its header, so some players show an unknown length.

PNG, SVG, and separation exports capture the animated source's current frame when that export is compatible with the selected effect.

Keyboard shortcuts: R randomize the active effect, E open Export, Esc close it, Space play or pause animated media.

Architecture

index.html          markup + p5 CDN + Google Fonts + GoatCounter analytics
style.css           five-colour token system, no rounded corners, Swiss styling
src/
  main.js           p5 instance mode, app state, all wiring
  input.js          image loading, fitting, pre-adjust, working buffer + sampling helpers
  media.js          bounded GIF/video loading, playback, seeking, and disposal
  controls.js       auto-generates UI controls from each effect's param schema
  export.js         PNG (2x) + SVG + colour-separation ZIP export
  animated-export.js WebM recording + worker-backed GIF export orchestration
  gif-worker.js     per-frame GIF palette and encoding work
  zip.js            minimal STORE-only ZIP writer for separation plates
  effects/
    index.js        effect registry (array of modules, in UI order)
    stack.js        rendering pipeline: pre-adjust → colour pre-stage → A → [B]
    _shared.js      colour/SVG helpers, re-exports the PRNG
    none.js halftone.js dither.js ascii.js riso.js xerox.js analog.js
    glitch.js rgb-shift.js pixel-sort.js emboss.js tiles.js
    gradient-map.js tone.js hue-sat.js
  controls/
    curve-editor.js gradient-editor.js
  color/
    curve.js ramps.js
  ui/
    filmstrip-wheel.js  mouse-wheel horizontal scrolling for the effect strip
  util/
    prng.js          mulberry32 seeded PRNG
    scratch.js       pooled scratch canvases + zero-readback pixel buffers
tests/              Node test suite and browser smoke pages (see Tests)
DESIGN.md           design system (tokens, rules, components); mirrored in .impeccable/design.json

Every effect is a self-describing module with an id, name, params array (which drives the control panel automatically), and render() / optional renderSVG() methods. The pipeline in stack.js caches each stage by signature and only recomputes when inputs actually change.

Privacy

Your images, GIFs, and videos never leave your machine. There is no backend and no upload. The page loads p5.js from cdnjs and its fonts from Google Fonts, and the GIF decoding and encoding libraries are loaded on demand from jsDelivr; all processing stays local. The site loads GoatCounter, a lightweight, cookieless analytics script that only records anonymous page-view counts. No personal data, no cross-site tracking, no fingerprinting.

Support

If you find Halftone Tools useful, consider buying me a coffee ☕️

License

Personal Use Only. See LICENSE.md for the full terms.

You're free to use and modify Halftone Tools for personal, non-commercial purposes. Commercial use and redistribution (of the original or modified versions) are not permitted. Credit is required: Halftone Tools by Sefa Tolaman.

About

Halftone Tools is a free, browser-based print-effects workshop designed for real-time image manipulation. Built using p5.js, it operates entirely client-side as a static site, meaning all image processing happens locally on your machine with total privacy. No accounts, no backend, and no uploads required.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages