snatch is a plugin-driven bitmap processing pipeline for retro/pixel workflows.
It is still great for bitmap fonts, but the architecture is now more generic:
- extract bitmap data from a source
- transform that data (optional)
- export in a target format
snatch runs in three stages:
- Extractor plugin
- Reads the input source (
ttf,image, etc.) - Produces a
snatch_fontbitmap representation
- Transformer plugin (optional)
- Reads
snatch_font - Can annotate or replace data through
font->user_data
- Exporter plugin
- Reads
snatch_fontand optional transformed data - Writes the final artifact (
.png,.s,.bin,.c, ...)
Pipeline examples:
TTF -> ttf_extractor -> tiny_font_transformer -> sdcc_asm_tiny_font_exporter -> .s
image -> image_extractor -> (no transform) -> raw_bin_exporter -> .bin
TTF -> ttf_extractor -> tiny_font_transformer -> raw_bin_exporter -> tiny_font_bin_extractor -> tiny_bmp_transformer -> png_exporter
This is why the new model is more flexible: with appropriate plugins, you can run non-font flows too (for example: extract one glyph/region from color image -> dither transform -> 1bpp image export).
- Extract bitmap font glyphs from image sheets (
image_extractor) - Rasterize TTF fonts to 1bpp glyph bitmaps (
ttf_extractor) - Run optional transformers before export
- Export to PNG grid, Partner SDCC ASM (tiny or bitmap), raw binary, and raw C array
- Control ASCII range, colors, margins/padding, font size, fixed/proportional mode
Detailed format references:
- Tiny binary/font layout:
docs/tiny-bin.md - Tiny replay/raster semantics:
docs/tiny-raster.md - Bitmap assembly/font layout:
docs/bitmap-asm.md
Quick header reference (font_t, both tiny and bitmap streams):
byte 0: flags
byte 1: first_ascii
byte 2: last_ascii
byte 3: empty_width
byte 4: max_glyph_width
byte 5: glyph_height
byte 6: advance
byte 7: descent
Quick tiny move-byte reference:
bit: 7 6 5 4 3 2 1 0
c0 |dx| |dy| sy sx c1
color = c0 | (c1 << 1)
dx = sx ? -|dx| : |dx| (|dx| in 0..3)
dy = sy ? -|dy| : |dy| (|dy| in 0..3)
git clone https://github.com/retro-vault/snatch.git
cd snatch
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build -j$(nproc)ctest --test-dir build --output-on-failure./bin/snatch \
--plugin-dir ./bin/plugins \
--extractor-parameters "input=fonts/Retro.ttf,first_ascii=32,last_ascii=126,font_size=16" \
--exporter png_exporter \
--exporter-parameters "output=out/font-grid.png,columns=16,rows=6"./bin/snatch \
--plugin-dir ./bin/plugins \
--extractor-parameters "input=assets/fontsheet.png,margins_left=2,margins_top=2,margins_right=2,margins_bottom=2,padding_left=1,padding_top=1,padding_right=1,padding_bottom=1,columns=16,rows=6,first_ascii=32,last_ascii=126,fore_color=#000000,back_color=#FFFFFF" \
--exporter raw_bin_exporter \
--exporter-parameters "output=out/font.bin"./bin/snatch \
--plugin-dir ./bin/plugins \
--extractor-parameters "input=fonts/Retro.ttf,first_ascii=32,last_ascii=127,font_size=16" \
--transformer tiny_font_transformer \
--exporter sdcc_asm_tiny_font_exporter \
--exporter-parameters "output=out/my_font.s,module=my_font,symbol=my_font,proportional=true,empty_width=3,advance=2"./bin/snatch \
--plugin-dir ./bin/plugins \
--extractor-parameters "input=fonts/Retro.ttf,first_ascii=32,last_ascii=127,font_size=16" \
--transformer bitmap_font_transformer \
--transformer-parameters "font_mode=proportional,empty_width=3,advance=2" \
--exporter raw_c_exporter \
--exporter-parameters "output=out/font_raw.c,bytes_per_line=8,symbol=font_data"| Name | Input | Purpose |
|---|---|---|
ttf_extractor |
ttf |
Rasterize TTF glyphs into 1bpp bitmap glyphs |
image_extractor |
image |
Extract glyph bitmaps from grid image sheets |
image_passthrough_extractor |
image |
Load full image as grayscale passthrough payload in user_data |
tiny_font_bin_extractor |
bin |
Load Partner Tiny binary stream into user_data for raster decoding |
| Name | Purpose | Notes |
|---|---|---|
tiny_font_transformer |
Vectorize bitmap glyphs into Partner Tiny move streams | Segment-coverage optimizer (optimize=true) + optional mask pass (include_mask=true) |
tiny_bmp_transformer |
Interpret Partner Tiny moves and rebuild bitmap glyphs | Intended for tiny_font_bin_extractor + png_exporter |
bitmap_font_transformer |
Serialize bitmap font to Partner bitmap byte stream | Intended for sdcc_asm_bitmap_font_exporter, raw_bin_exporter, raw_c_exporter |
fzx_transformer |
Compute ZX Spectrum FZX-style glyph metadata | Stores metadata in font->user_data |
dither_1bpp_transformer |
Dither grayscale passthrough image to 1bpp bitmap glyph | Intended for image_passthrough_extractor + png_exporter |
| Name | Format | Standard | Purpose |
|---|---|---|---|
png_exporter |
png |
snatch-grid |
Render bitmap font as PNG grid |
sdcc_asm_tiny_font_exporter |
asm |
sdcc-asm-tiny-font |
SDCC assembly export for Partner tiny format |
sdcc_asm_bitmap_font_exporter |
asm |
sdcc-asm-bitmap-font |
SDCC assembly export for Partner bitmap format |
raw_bin_exporter |
bin |
raw-1bpp |
Raw continuous byte stream (or Partner Tiny stream when input is tiny_font_transformer) |
raw_c_exporter |
c |
raw-1bpp |
Raw byte stream as const uint8_t[] |
dummy_exporter |
txt |
debug-dump |
Diagnostic exporter |
| Option | Alias | Description |
|---|---|---|
--extractor |
-q |
Optional extractor plugin override |
--extractor-parameters |
-v |
Extractor params (k=v,...) |
--plugin-dir |
-d |
Plugin search directory override |
--transformer |
-w |
Optional transformer plugin name |
--transformer-parameters |
-y |
Transformer params (k=v,...) |
--exporter |
-e |
Exporter plugin name |
--exporter-parameters |
-x |
Exporter params (k=v,...) |
Stage-specific tuning should be passed to the owning plugin:
- extractor options via
--extractor-parameters - transformer options via
--transformer-parameters - exporter options via
--exporter-parameters
If --extractor is omitted, snatch infers it from input extension:
.ttf,.otf->ttf_extractor- common image extensions (
.png,.jpg,.jpeg, ...) ->image_extractor
Required ownership split:
- extractor owns input path: set
input=...in--extractor-parameters - exporter owns output path: set
output=...in--exporter-parameters - there is no positional input argument and no root
--outputoption anymore
Use concrete exporter names directly (no separate format parameter):
sdcc_asm_tiny_font_exportersdcc_asm_bitmap_font_exporterraw_c_exporterraw_bin_exporterpng_exporterdummy_exporter
Backward-compatible aliases are still accepted (e.g. raw_bin, raw_c, png, dummy, sdcc_asm_tiny_font, sdcc_asm_bitmap_font).
Concept example (full image passthrough -> dither -> PNG):
./bin/snatch \
--plugin-dir ./bin/plugins \
--extractor image_passthrough_extractor \
--extractor-parameters "input=test/data/font-sheets/tut.png" \
--transformer dither_1bpp_transformer \
--transformer-parameters "threshold=128" \
--exporter png_exporter \
--exporter-parameters "output=out/tut_dither_1bpp.png,columns=1,rows=1,padding=0,grid_thickness=0"Partner Tiny roundtrip (binary -> rasterized grid):
# 1) Create Partner Tiny binary stream.
./bin/snatch \
--plugin-dir ./bin/plugins \
--extractor-parameters "input=fonts/Retro.ttf,first_ascii=65,last_ascii=70,font_size=16" \
--transformer tiny_font_transformer \
--exporter raw_bin_exporter \
--exporter-parameters "output=out/retro_tiny.bin,font_mode=proportional,empty_width=3,advance=1"
# 2) Load tiny stream and rasterize it back to bitmap grid.
./bin/snatch \
--plugin-dir ./bin/plugins \
--extractor tiny_font_bin_extractor \
--extractor-parameters "input=out/retro_tiny.bin" \
--transformer tiny_bmp_transformer \
--exporter png_exporter \
--exporter-parameters "output=out/retro_tiny_roundtrip.png,columns=3,rows=2,padding=1,grid_thickness=1"snatch searches plugin directories in this order and stops at the first one that provides the requested plugins:
--plugin-dir <dir>SNATCH_PLUGIN_DIR${CMAKE_INSTALL_FULL_LIBDIR}/snatch/plugins~/.local/lib/snatch/plugins
Plugins export:
int snatch_plugin_get(const snatch_plugin_info** out);Plugin ABI: include/snatch/plugin.h
snatch_plugin_info.kind:
SNATCH_PLUGIN_KIND_EXTRACTOR->extract_fontSNATCH_PLUGIN_KIND_TRANSFORMER->transform_fontSNATCH_PLUGIN_KIND_EXPORTER->export_font
- Create a plugin folder and CMake file under
plugins/<name>/. - Add it to
plugins/CMakeLists.txtwithadd_subdirectory(<name>). - Implement
snatch_plugin_get(...)and a staticsnatch_plugin_info. - Build and run with
--plugin-dir ./bin/plugins.
Extractor skeleton:
int my_extract(
const char* input_path,
const snatch_kv* options,
unsigned options_count,
snatch_font* out_font,
char* errbuf,
unsigned errbuf_len
);Transformer skeleton:
int my_transform(
snatch_font* font,
const snatch_kv* options,
unsigned options_count,
char* errbuf,
unsigned errbuf_len
);Exporter skeleton:
int my_export(
const snatch_font* font,
const char* output_path,
const snatch_kv* options,
unsigned options_count,
char* errbuf,
unsigned errbuf_len
);Minimal plugin descriptor shape:
const snatch_plugin_info k_info = {
"my_plugin",
"Short description",
"author",
"format-or-input",
"standard-or-profile",
SNATCH_PLUGIN_ABI_VERSION,
SNATCH_PLUGIN_KIND_EXPORTER, // or EXTRACTOR/TRANSFORMER
nullptr, // transform callback if transformer
nullptr, // export callback if exporter
nullptr // extract callback if extractor
};Notes:
- For transformers, use
font->user_datafor stage-to-stage contracts. - For exporters,
format/standardshould be non-empty. - Keep plugin-owned buffers alive for as long as
snatchmay read them.
- FreeType (
freetype): FreeType License (FTL) or GPLv2 - stb (
stb_image,stb_image_write): public domain or MIT - argparse (
cofyc/argparse): MIT - GoogleTest (
googletest): BSD 3-Clause
TTF fonts under test/data/ttfs are freeware and remain copyright of their authors.