Render images in the terminal via a VQ-VAE codebook turned into a glyph font
Project description
TUI-Image
Show an image in your terminal as colored text, using a pretrained image tokenizer's codebook as a custom 4096-glyph font.
Left: original. Right: rendered in the terminal (using TokImg Mono font).
The idea
- A pretrained VQ-VAE (LlamaGen, MIT licensed) has 16384 little 16×16 image patches. We keep the 4096 most shape-diverse as a custom font (TokImg Mono).
- Each glyph is just a binary shape; its two colors come from the image — so a shape and its color-swapped mirror are the same, and duplicates are dropped.
- To draw an image: split it into a grid of terminal cells — each cell is a tall rectangle (~1:2) and for each cell pick the glyph whose shape fits best, drawn (stretched to fill the cell) in that cell's two main colors.
- The grid of colored glyphs approximates the image.
The VQ-VAE is used only once, to build the font. Rendering is pure NumPy on
CPU — tuimg never imports torch and needs no GPU/VRAM.
A handful of Unicode block/box characters (█ ▀ ▌ ▖ ▒ ─ ╱ …) are mixed into
the alphabet too: the terminal draws those procedurally, so they render
seam-free and need no font. They win a cell whenever they fit about as well
as the best glyph, trading a seam away at ~no fidelity cost.
Install
The font is prebuilt and bundled, so no GPU is needed to use it. Install it
as a tuimg command that's always on your PATH:
git clone https://github.com/volotat/tuimg
cd tuimg
pip install . # or: pip install --user .
tuimg --install-font # copies the bundled font to ~/.fonts (+ refreshes cache)
tuimg path/to/image.jpg
Or run from the cloned repo without installing:
pip install numpy Pillow # only runtime deps
cp tuimg/assets/tokimg.ttf ~/.fonts/ && fc-cache -f # install the font
python -m tuimg samples/photo.jpg
Rebuilding the font from the model is optional and the only step that needs a
GPU (~3 min): pip install ".[build]" then make assets.
Why you don't need to select the font: our glyphs live in plane-16 PUA (U+100000+), a Unicode range no normal font defines. Your terminal can't find them in your current font, so it automatically falls back to TokImg just for those characters — your own font keeps drawing all your normal text. Installing the font is enough.
If the tiles don't appear (a few terminals don't fall back for these codepoints): either set your terminal font to "TokImg Mono" (it includes normal ASCII, so your shell still looks normal —
fc-match "TokImg Mono"should printtokimg.ttf), or skip the font entirely with--mode block/--save *.png. Tip: install to~/.fonts, which fontconfig scans even whenXDG_DATA_HOMEis redirected by a snap/flatpak sandbox.
Use
python -m tuimg samples/photo.jpg # color (default)
python -m tuimg samples/photo.jpg --save pic.png # save the stitched picture
python -m tuimg samples/photo.jpg --no-color --save pic.txt # plain text, opens anywhere
python -m tuimg samples/ui.png --mode block # colored blocks, NO font needed
python -m tuimg --demo # render all sample images
--save FILE saves the perfectly-stitched picture when FILE ends in
.png/.jpg (no font needed), otherwise the rendered text. --no-color drops
all ANSI color codes and renders a
1-bit brightness halftone of plain glyph characters — clean to open in any
editor (it suits a light/white background by default; add --invert for a dark
terminal, and --mono-threshold T to shift the brightness pivot — raise it for
more ink in a bright image, lower it for a dark one). --blocks matches using
only the Unicode block/box characters
(no codebook font) — a baseline to see how much the font actually adds. Other
flags: --width N, --cell-aspect R (if the image looks stretched;
auto-detected when possible), --debug. Run python -m tuimg -h for all.
Terminal rendering limitations (and why they can't be "fixed" here)
What you actually see in the terminal is not pixel-perfect, and this is a limit of the medium, not a bug in this project:
A terminal lays out discrete text glyphs, one per cell — it was never built to tile glyphs into a seamless image. Two consequences are unavoidable from inside the application:
- Hairline seams between cells. A cell's size in device pixels is almost always fractional, so the terminal snaps each cell to an integer pixel boundary and rasterises every glyph independently. Edges that should meet don't share pixels, leaving ~1-px seams. We mitigate this with glyph "ink bleed" (overfilling right/bottom) and by preferring seam-free block characters, but it can't be eliminated — the rounding happens in the terminal, below us.
- No antialiasing control. Whether glyph edges are soft or hard is the terminal/font-renderer's decision.
Why can't we get the flawless alignment that box-drawing (─ │ █ ▀) has? Because
terminals special-case those ranges and draw them procedurally, filling the
exact cell rectangle and bypassing the font. Arbitrary custom shapes — the whole
premise of a glyph alphabet — go through normal font rasterisation, where the
snapping is inherent. You can have a rich shaped alphabet or box-drawing-grade
alignment, not both. For a pixel-perfect result, export the picture with
--save out.png (rasterised seam-free) or use --mode block.
Within those constraints, this project is about as good as terminal image printing gets with a predefined character set: instead of a fixed handful of ASCII/box glyphs, it builds a 4096-glyph alphabet straight from a learned image tokenizer, selects it to match real-image statistics, mixes in the seam-free block characters, and fits each cell's two colors to the block — squeezing the most detail the two-colors-per-cell ceiling allows.
License
MIT. The vendored model code (tuimg/vendor/llamagen_vq.py) and the
downloaded weights are from LlamaGen,
also MIT.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tuimg-0.1.1.tar.gz.
File metadata
- Download URL: tuimg-0.1.1.tar.gz
- Upload date:
- Size: 491.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
31a11dd254f7646a7b9c3728937c891a9c0f9dbb02b406340c57c3a95dcc4cbc
|
|
| MD5 |
0a28cf540b6f177de220996939a78106
|
|
| BLAKE2b-256 |
35b270bd7809409726bdeb17be74d30bc8bbd0d04db8c74b55047d9f99d54543
|
Provenance
The following attestation bundles were made for tuimg-0.1.1.tar.gz:
Publisher:
release.yml on volotat/tuimg
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tuimg-0.1.1.tar.gz -
Subject digest:
31a11dd254f7646a7b9c3728937c891a9c0f9dbb02b406340c57c3a95dcc4cbc - Sigstore transparency entry: 1971749996
- Sigstore integration time:
-
Permalink:
volotat/tuimg@b831f0773ddfb91e5f1f8d15ddc49d708613a949 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/volotat
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b831f0773ddfb91e5f1f8d15ddc49d708613a949 -
Trigger Event:
release
-
Statement type:
File details
Details for the file tuimg-0.1.1-py3-none-any.whl.
File metadata
- Download URL: tuimg-0.1.1-py3-none-any.whl
- Upload date:
- Size: 507.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6b6d36d4894f70e6355af14fcd01c00fcab33733d69acdac254dabc60e6be8f4
|
|
| MD5 |
ab8b44117813e9f6df6059e3fe50b2d6
|
|
| BLAKE2b-256 |
296c66bd51e8b17e12a054a2194de669b16fe7c7467bdb350e16b1c19e26b760
|
Provenance
The following attestation bundles were made for tuimg-0.1.1-py3-none-any.whl:
Publisher:
release.yml on volotat/tuimg
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tuimg-0.1.1-py3-none-any.whl -
Subject digest:
6b6d36d4894f70e6355af14fcd01c00fcab33733d69acdac254dabc60e6be8f4 - Sigstore transparency entry: 1971750051
- Sigstore integration time:
-
Permalink:
volotat/tuimg@b831f0773ddfb91e5f1f8d15ddc49d708613a949 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/volotat
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b831f0773ddfb91e5f1f8d15ddc49d708613a949 -
Trigger Event:
release
-
Statement type: