Metadata-Version: 2.4
Name: tuimg
Version: 0.1.1
Summary: Render images in the terminal via a VQ-VAE codebook turned into a glyph font
Home-page: https://github.com/volotat/tuimg
Author: Alexey Borsky
License: MIT
Project-URL: Author, https://github.com/volotat
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Environment :: Console
Classifier: Topic :: Multimedia :: Graphics
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.23
Requires-Dist: Pillow>=9.0
Provides-Extra: build
Requires-Dist: torch>=2.0; extra == "build"
Requires-Dist: scikit-learn>=1.1; extra == "build"
Requires-Dist: scipy>=1.7; extra == "build"
Requires-Dist: fonttools>=4.30; extra == "build"
Requires-Dist: huggingface_hub>=0.20; extra == "build"
Dynamic: license-file

# 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).*

![photo](previews/photo_compare.png)
![ui](previews/ui_compare.png)
![bear](previews/bear_compare.png)
---

## The idea

1. 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**).
2. 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.
3. 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**. 
4. 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:

```bash
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:

```bash
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
> print `tokimg.ttf`), or skip the font entirely with `--mode block` / `--save *.png`.
> Tip: install to `~/.fonts`, which fontconfig scans even when `XDG_DATA_HOME` is
> redirected by a snap/flatpak sandbox.

## Use

```bash
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:

![how it really looks in the terminal](previews/flower_terminal.png)

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](LICENSE). The vendored model code (`tuimg/vendor/llamagen_vq.py`) and the
downloaded weights are from [LlamaGen](https://github.com/FoundationVision/LlamaGen),
also MIT.
