mimono halftone

Live GPU square halftone for websites

mimono halftone uses the stable <halftone-fx> component to render an image or video as animated square marks, live in the browser, on the GPU. Download the result as an image or animation, or embed the tiny live program: it stays razor-sharp on any screen, costs ~0.1 ms of CPU per frame, and a whole animated hero background weighs a few hundred kilobytes.

Quick start

  1. Open the playground.
  2. Pick a clip from Source (the list mirrors this repo's webgl/assets/ folder). Frame follows its original ratio by default; choose Square crop only when you want a centered 1:1 cut. Choose a Look preset, then tune any slider until it looks right.
  3. Choose PNG, GIF, or the video format available in your browser under Format, then click Download. Use πŸ“‹ Export iframe when you want the live version instead.
  4. Use the downloaded file directly, or paste the iframe into your site builder's embed/HTML element.

Downloading PNG, MP4, and GIF

FormatOutputBest for
PNGFull preview resolution and selected frame ratio; preserves alpha when Transparent background is onStill artwork and layouts
MP4Up to 30 fps and 1080 px on the longest edge; 3, 6, or 12 secondsSocial, slides, and broadly compatible video
WebMAvailable when the browser supports it; same lengths and size limitA browser-friendly video fallback
GIF10 fps; up to 480 px on the longest edge; 3 or 6 seconds; loops foreverMessages, docs, and quick previews

Animation export records the live preview from its current pose and is silent. Keep the tab visible until capture finishes; the Download button becomes Cancel export while it works.

Transparency: PNG keeps full alpha when Transparent background is on. Video and GIF put the selected Background color behind transparent marks (Base Gray by default), which preserves the soft square edges instead of turning transparent pixels black.

MP4 encoding is provided by the browser. If the current browser cannot create a real MP4, the option is marked unavailable and WebM is offered when possible. The app never puts WebM data inside a file named .mp4.

Embedding in STUDIO (studio.design)

  1. In the STUDIO editor, open the θΏ½εŠ γƒ‘γƒγƒ« (Add panel) on the left and add an εŸ‹γ‚θΎΌγΏγƒœγƒƒγ‚―γ‚Ή (Embed box) to your page.
  2. Select the box, open the settings panel on the right, and paste the code from Export iframe into the εŸ‹γ‚θΎΌγΏγ‚³γƒΌγƒ‰ (embed code) field.
  3. Size and position the box like any element β€” for a hero background, make it fill the section and layer your text boxes on top. The exported ratio matches the source; choose Square crop before exporting when you want a centered 1:1 cut.
  4. The editor canvas may show a placeholder instead of the animation β€” that's normal. Check プレビγƒ₯γƒΌ (preview), then ε…¬ι–‹ (publish).
Keep the embed box to the single <iframe> tag. A standalone iframe gets STUDIO's clean "iframe type" embed; any extra HTML around it switches STUDIO to a restricted sandbox mode.
Backgrounds: the exported code includes pointer-events:none so clicks and scrolling pass through to your page. Remove it if the animation should be interactive. Set the bg param to your page's background color so the iframe edges are invisible. New exports include the source's numeric aspect-ratio; choosing Square crop emits 1/1 instead. For a custom crop, use the same ratio in both the URL's aspect parameter and the iframe style.

URL parameters

The embed page is configured entirely by its URL β€” edit params and reload to restyle, no code changes ever:

https://playbox-dev.github.io/halftone-dithering-fx/webgl/embed.html?src=./assets/horse.mp4&aspect=source&grid=80&shape=square&size=42&threshold=50
ParamValuesMeaning
srcURLImage or video. Relative (./assets/…) or any CORS-accessible absolute URL. Add type=video for video URLs without a file extension.
aspectsource / square / ratioOutput frame. New exports use the source ratio by default; ratios such as 4/5 are also accepted. Omitted on legacy embeds means square.
grid1–320 in the playgroundHalftone cell size in px. Smaller = more detail; larger = fewer marks. Default 80.
shapesquare / circleMark geometry. Default square; circle preserves the legacy look.
size0–100Maximum mark size within each cell. Lower values create more space between squares. Default 42.
threshold0–100Hides marks smaller than this percentage. Raise it to remove small peripheral squares. Default 50.
dothexMark color, without # (e.g. ff3110). Default mimono Play Red.
bghex / transparentBackground color. Default transparent.
brightness-100…100Raises or lowers source luminance. Default 12 for a lighter, sparser result.
contrast-100…100Default 0.
gamma0.1–3Default 1.
dithernone bayer4 bayer8 grain noiseSee dither modes. Default none.
multicolorflagRainbow marks colored by brightness.
invertflagFlip foreground and background: marks are drawn on the light areas of the source and the dark areas stay empty. Tone controls keep their meaning.
motionpulse radial sweep interference scanA symmetric mark-size field. Omit it for no motion.
amount0–100How strongly the field resizes marks. Default 35.
speed0–100Motion rate. Zero freezes the current field. Default 35.
phaseradiansStarting pose. Added automatically by the iframe exporter so a frozen field round-trips exactly.
Looks: Sparse, Detail, Grain, Bayer 4Γ—4, Bayer 8Γ—8, Poster, and Blocks are literal starting points for different rendering processes. Motion is independent, so changing a look does not turn it off.

Adding your own image / video

Assets live in the repo, so they're versioned and served with correct CORS headers. To add one:

  1. Open webgl/assets on GitHub β†’ Add file β†’ Upload files β†’ commit.
  2. Wait ~1 minute for GitHub Pages to redeploy.
  3. Reload the playground β€” the file appears in the Source dropdown automatically (the list is read live from the repo).
Keep files small β€” it's free. The halftone destroys fine detail, so a 640Γ—360 heavily-compressed clip looks identical to 1080p. The marks are drawn by the shader and stay sharp regardless. Recommended encode:
ffmpeg -i input.mp4 -vf scale=640:360 -c:v libx264 -pix_fmt yuv420p \
       -crf 30 -preset veryslow -movflags +faststart -an output.mp4
The two bundled clips are 97 KB (spiral) and 373 KB (horse).

Dither modes

For animation, temporal stability is what matters β€” a pattern that changes every frame reads as shimmer. All modes are stable except noise, which flickers on purpose:

ModeLook
noneSmooth tonal halftone β€” square size carries the tone. Best default.
bayer4Coarse retro print / CRT crosshatch.
bayer8Finer retro pattern, more tonal steps.
grainOrganic, editorial β€” blue-noise-like, no visible pattern.
noiseAnimated film grain, re-seeded ~30 fps.

Error-diffusion dithers (Floyd–Steinberg, Stucki…) are deliberately absent here: they're sequential (can't run on a GPU shader) and they shimmer on video. They live in the classic export tool for still images.

Motion fields

Motion changes mark size only. It does not change the source tone, grid, color, or threshold. Every field uses normalized mirrored coordinates, so it keeps the same geometry at sparse and detailed grid settings and keeps the field itself vertically and horizontally symmetric.

ModeMovement
pulseAll marks expand and contract together.
radialConcentric circular fronts move through the source.
sweepA square front travels from the centre to the edge.
interferenceTwo perpendicular waves form a shifting lattice.
scanA thin mirrored cross travels outwards and back.

Amount sets the resize depth and Speed sets the rate. The operating system's reduced-motion setting disables the field and shows the untouched look. Previously exported overdrive, flux, and seed params remain accepted for compatibility.

embed.html?src=./assets/horse.mp4&aspect=source&grid=3&dither=grain&motion=radial&amount=35&speed=35

Performance

Measured head-to-head against the CPU original (same video, 1920Γ—1080, Apple M2 Pro): CPU version 51.7 ms/frame (~19 fps max, one core pinned) vs WebGL ~0.07 ms/frame β€” about 700Γ— faster, using under 1% of a 60 fps frame budget. Reproduce it: open bench.html, wait a few seconds, and read window.__results in the console.

FAQ

The animation doesn't show inside the STUDIO editor.
Expected β€” embeds render in preview and on the published site, not on the editor canvas.

bg=transparent looks white when I open the embed URL directly.
Expected β€” a standalone browser tab has nothing behind the page, so you see the browser's own default backdrop. The transparency is real: put the iframe on a page (or in STUDIO) and the page shows through between the marks. Both bg and background work as the param name.

The video doesn't autoplay.
Keep allow="autoplay" on the iframe. Playback is muted + inline, which all browsers permit.

Colors in the URL don't work.
Drop the # β€” write dot=ff3300, not dot=#ff3300.

Can I use a clip hosted elsewhere (not in this repo)?
Yes, if the host sends CORS headers (Access-Control-Allow-Origin). GitHub Pages does; most random hosts don't. When in doubt, commit it to webgl/assets/.

Why did animation export stop when I switched tabs?
Browsers pause visual animation in hidden tabs. Keep this tab visible while recording so the saved timing matches the preview.

Does it work on phones?
The live preview works on current phones and is far lighter than a CSS blur. Available video formats depend on the browser, and longer GIFs may be limited by device memory. The component also pauses itself when scrolled offscreen.