Dithering tool.
  • TypeScript 65%
  • Rust 17.3%
  • CSS 11.1%
  • JavaScript 2.7%
  • Python 1.7%
  • Other 2.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Alex c2c1818952
Some checks failed
CI / frontend (push) Has been cancelled
CI / rust (push) Has been cancelled
Release / build-linux (push) Has been cancelled
Release / build-windows (push) Has been cancelled
Release / build-macos (push) Has been cancelled
CI / desktop-linux (push) Has been cancelled
CI / desktop-windows (push) Has been cancelled
CI / desktop-macos (push) Has been cancelled
Release / publish (push) Has been cancelled
feat: paint images out of text in 3.1.0
A new algorithm that renders the image as characters — any script, any font.

The engine is the single visual truth, but it has no fonts, so the split runs
where it should: JS rasterises each character into a coverage bitmap, and the
resulting atlas is shipped to the engine resident, like the pixel planes. It
re-ships only when the characters, font or cell size change, never per slider
tick, and preview, PNG export and video export all render through the same
path — so what the stage shows is what the file contains.

Fifty scripts: Latin, Greek, Cyrillic, Hebrew, Arabic, Syriac, Devanagari,
Bengali, Tamil, Telugu, Thai, Tibetan, Khmer, Hiragana, Katakana, Hangul, Han,
Ethiopic, Tifinagh, Vai, Cherokee, Runic, Braille, box drawing, block
elements, maths, arrows and the rest. Or everything the font can draw at once.
Or only the characters typed into the box.

Two filters are what make "everything" mean anything. A font asked for a
character it lacks draws the missing-glyph box, and every such character draws
the *same* box, so without filtering "all scripts" under a Latin font yields a
thousand identical rectangles; each candidate is compared against a known-
absent codepoint and dropped if it matches. And many characters are simply
indistinguishable at a 7x12 cell, so glyphs are merged on their quantised
coverage. What survives is spread evenly from lightest to darkest, because an
atlas that cannot reach paper-white loses the image's tonal range.

The default font chains the Noto families rather than naming one. CSS falls
back per character, so each glyph is drawn by whichever font actually has it —
without that, picking "everything" under Roboto Mono silently reduces the
repertoire to Latin, which is the trap this feature would otherwise walk into
on first use.

Matching is either shape, which picks the character whose strokes sit where
the cell's detail sits and keeps edges as edges, or brightness, the classic
ramp. Cell width and height are independent, and type size, weight, glyph
count, ink curve, inversion and ink colour are all exposed. The cheap knobs
are per-pass; anything that rebuilds the atlas is global, because one atlas is
resident and two passes cannot each have their own.

Strings translated into all seventeen other locales.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-28 20:30:29 +02:00
.cargo chore: add windows cross-compilation target config 2026-08-21 14:46:23 +02:00
.github/workflows feat: stack dither passes and repair desktop CI updater 2026-09-22 10:07:55 +02:00
assets feat: add Donate button and prepare public GitHub release 2026-09-07 19:17:01 +02:00
dither-wasm feat: paint images out of text in 3.1.0 2026-09-28 20:30:29 +02:00
packaging fix: build Arch package from Forgejo 2026-09-20 21:27:30 +02:00
public Rewrite the dither engine around palette layers 2026-08-21 01:46:00 +02:00
screenshots Add screenshot gallery to README. 2026-08-23 03:09:58 +02:00
src feat: paint images out of text in 3.1.0 2026-09-28 20:30:29 +02:00
src-tauri feat: paint images out of text in 3.1.0 2026-09-28 20:30:29 +02:00
templates chore: organize loose files into folders and add windows CI 2026-08-21 14:45:32 +02:00
test test: lock the error taxonomy to its copy, and cover the video helpers 2026-09-28 06:46:33 +02:00
testdata Golden harness, engine parity port, pipeline v2, and app improvements 2026-08-22 01:07:30 +02:00
tools fix: make video frames readable on WebKitGTK, and show import progress 2026-09-28 20:06:56 +02:00
.gitignore feat: video support, typed failures and a render-decoupled canvas in 3.0 2026-09-28 02:32:39 +02:00
.npmrc feat: add Donate button and prepare public GitHub release 2026-09-07 19:17:01 +02:00
build-all.sh fix: AppImage EGL crash, donate fallback, disable wasm-opt 2026-09-07 20:17:13 +02:00
build-win.ps1 build(win): revamp build-win.ps1 for dual binary deployment (standalone & installer) 2026-08-23 13:26:52 +02:00
eslint.config.js Golden harness, engine parity port, pipeline v2, and app improvements 2026-08-22 01:07:30 +02:00
index.html fix: disable WebKitGTK native pinch page-zoom on Linux 2026-09-06 22:27:27 +02:00
LICENSE feat: add Donate button and prepare public GitHub release 2026-09-07 19:17:01 +02:00
package.json feat: paint images out of text in 3.1.0 2026-09-28 20:30:29 +02:00
pnpm-lock.yaml feat: GitHub Releases auto-updater for Windows and macOS 2026-09-07 20:49:36 +02:00
pnpm-workspace.yaml fix: repair build chain for pnpm 11 (PART 1) 2026-08-23 13:15:20 +02:00
README.md feat: video support, typed failures and a render-decoupled canvas in 3.0 2026-09-28 02:32:39 +02:00
THIRD-PARTY.md docs: fix License typo and add comprehensive licensing info 2026-08-23 12:19:35 +02:00
tsconfig.json Rewrite the dither engine around palette layers 2026-08-21 01:46:00 +02:00
vite.config.ts feat: video support, typed failures and a render-decoupled canvas in 3.0 2026-09-28 02:32:39 +02:00
vitest.config.ts Golden harness, engine parity port, pipeline v2, and app improvements 2026-08-22 01:07:30 +02:00

Dizako

Dizako

A dithering studio. Load an image or a video, pick from 30 algorithms and 130 palettes, and decide which colours land in the shadows and which in the highlights.

Built with Tauri, React, and Material 3. Everything runs locally. No uploads, no tracking.

Algorithm picker
Algorithms
Palette editor
Palettes
Image controls
Image

What it does

Open an image or a video, choose an algorithm and a palette, tune until it looks right, then export. Sliders re-render live. A compare wipe shows original against dithered.

Area Includes
Error diffusion Floyd-Steinberg, Jarvis, Stucki, Sierra, Atkinson, and friends
Ordered Bayer, halftone screens, line screens, blue noise, checkerboard
Threshold / experimental Hard threshold, random noise, Riemersma, dot diffusion, Omino-like
Palette layers Colours stacked by tonal position, so you choose what goes where
Image controls Exposure, contrast, gamma, saturation, hue, blur, sharpen (before dither)
Video Scrub, play and dither footage frame by frame; export MP4, WebM or a PNG sequence

Video

Drop in an MP4, MOV, WebM or MKV and the whole studio applies to it. The transport under the canvas scrubs and plays; playback dithers every displayed frame live at preview resolution and snaps back to full detail the moment you stop. The two handles on the scrub track set the export range.

Export renders in two phases. Every frame in range is dithered through a pool of workers - one per core, so a clip renders several times faster than a single pass could - and kept as a lossless PNG. The frames are then pushed into the system's own encoder on a fixed clock, so the result runs at exactly the source frame rate rather than at whatever speed the render managed.

Format Notes
MP4 H.264 via the host encoder. The most portable result; offered when the system supports it
WebM VP8/VP9. Open codecs, slightly larger files
PNG frames One lossless PNG per frame in a folder. Always available, and the right input for ffmpeg or a compositor

Which of the three you get is decided by what the machine can actually do, not assumed - the export dialog lists what is available, says why anything is not, and shows the frame count, working-set size and estimated file size before you commit to the job. Exported video is silent; audio is not carried through.

Limits: 20 minutes and 4 MP per frame. Longer or larger sources are refused with an explanation rather than attempted and abandoned.

Building

Each platform has its own build script. Both compile the WebAssembly engine, build the frontend, and then bundle the app; neither cross-compiles, so run the one that matches the machine you are on.

Platform Script Produces
Linux ./build-all.sh .deb, .rpm, .AppImage, Arch .pkg.tar.zst
Windows .\build-win.ps1 portable dizako.exe, NSIS installer
macOS ./build-all.sh macos prints the steps only, see below

Shared dependencies

Needed on every platform:

  • Core build tools: Node 22+, pnpm, Rust (cargo), and wasm-pack.
  • Rust Wasm target: rustup target add wasm32-unknown-unknown, or the rust-wasm package via pacman.

Linux

./build-all.sh

The script checks for missing dependencies, builds every bundle, and finally installs the Arch package it just produced via pkexec pacman -U. Each bundle is best effort, so one failing packager does not kill the rest.

Additional Tauri dependencies: webkit2gtk-4.1, base-devel, curl, wget, openssl, appmenu-gtk-module, gtk3, libvips, libayatana-appindicator. (On Arch, ./build-all.sh offers to install these for you via pkexec pacman.)

Windows

Run from PowerShell on a Windows machine:

.\build-win.ps1

The script validates its tools, compiles the WASM engine, installs dependencies, runs the Tauri build, then collects both binaries into a fresh release-win/ directory and prints their sizes. You get two artifacts, and they are not interchangeable:

Artifact What it is
dizako.exe Portable standalone build. Launches straight into the GUI with no console window, needs no installation.
Dizako_<version>_x64-setup.exe NSIS installer. Setup wizard, Start menu entry, uninstaller.

Additional Windows requirements:

  • Rust MSVC toolchain plus the Visual Studio Build Tools (C++ workload). Windows builds use MSVC by default.
  • WebView2, which is preinstalled on Windows 11 and on current Windows 10.

.cargo/config.toml pins a mingw linker for x86_64-pc-windows-gnu. That only applies when you explicitly target the GNU toolchain and is ignored by the default MSVC build; delete that section if you want to build the GNU target without mingw present.

Cross-building Windows binaries from Linux needs the NSIS and mingw toolchains (pacman -S mingw-w64-gcc nsis or equivalent) and pnpm tauri build --runner cargo --target x86_64-pc-windows-gnu --bundles nsis. This is untested, which is why running the script on Windows is the supported path.

macOS

Scaffolding only, never exercised. ./build-all.sh macos prints the steps: install the Xcode command line tools, then pnpm tauri build --bundles dmg,app.

Manual build

If you prefer not to use the scripts:

# Compile the WebAssembly engine first
wasm-pack build dither-wasm --release --target web --out-dir pkg

# Install frontend dependencies
pnpm install

# Run the app
pnpm tauri dev            # development mode
pnpm tauri build          # bundle into src-tauri/target/release/bundle

Frontend only: pnpm dev / pnpm build.

Tests

pnpm test                 # golden parity: the wasm engine against the committed manifest
pnpm lint

tools/smoke.py drives the running dev server through a real browser - image import, video import, playback, canvas navigation, a full video export, and the error dialog - and checks the canvas actually painted. It needs Playwright and a Chromium binary and is not part of CI:

pnpm dev &
python tools/smoke.py http://localhost:1420/

Building the AppImage by hand? Set NO_STRIP=1. bundle.targets is "all", so a bare pnpm tauri build on Linux will try the AppImage, and linuxdeploy ships an old strip that chokes on the .relr.dyn sections in current system libraries. Without it the bundle fails with an unhelpful failed to run linuxdeploy. ./build-all.sh already sets this for you.

Matugen theming

Dizako can take its accent colour from matugen:

  1. Copy templates/matugen-template.json into your matugen templates directory.
  2. Point matugen at it:
    [templates.dizako]
    input_path = "~/.config/matugen/templates/dizako-matugen.json"
    output_path = "~/.config/dizako/colors.json"
    
  3. Run matugen, then pick Settings → Accent → Matugen in Dizako.

License

MIT — free to use, modify, and distribute.