sextants
Convert raster images (JPEG, PNG, …) to teletext / viewdata block-mosaic graphics — the 40×25 character page rendered by the SAA5050 and its relatives (BBC Micro Mode 7, Ceefax/Oracle teletext, Prestel viewdata, CEPT videotex).
The name comes from Unicode's own term for the 2×3 sub-pixel mosaic cell these displays draw — the sextants of the "Symbols for Legacy Computing" block (U+1FB00…). Each cell is six sub-pixels sharing a two-colour sub-palette, and a colour change is an in-band control code that costs a whole cell. Encoding an image well is therefore a constrained optimisation, not a simple resample — which is what this library does, row by row, with an optimal dynamic-programming solver.
Attribution
sextants is a derivative work of image2mode7 / image2teletext by
Kieran Connell. The conversion algorithm at its heart — the per-row
dynamic-programming solver, the sub-pixel error model, and the preprocessing
pipeline — is Kieran's work, used here under its MIT licence. This project
repackages that code as an installable, src-layout Python library with a split
module structure, a Click CLI, and a test suite, and is the basis for further
development. Please see LICENSE for the full notice.
Install
uv add sextants # or: pip install sextants
Optional numba acceleration for the DP solver:
uv add "sextants[fast]"
Library usage
from pathlib import Path
from PIL import Image
from sextants import convert, convert_image, render_preview
from sextants import PreprocessOptions, SolveOptions
# Preset-aware entry point: a named bundle configures the whole pipeline.
page = convert(Image.open("photo.jpg"), preset="photo") # 1000-byte teletext page
Path("photo.bin").write_bytes(page) # load at &7C00 on a BBC Micro
render_preview(page).save("preview.png") # what it looks like on-screen
# Or drive every knob directly with the two typed option groups.
page = convert_image(
"logo.png",
preprocess=PreprocessOptions(quant_colors=8, snap=40, saturation=2.0),
solve=SolveOptions(edge_weight=3.0),
)
convert(image, preset=None, *, preprocess=None, solve=None) is the preset-aware
entry point; convert_image(image, preprocess=None, solve=None) takes the options
directly. Configuration lives in two frozen, validated dataclasses —
PreprocessOptions (tone, colour, palette, resize) and
SolveOptions (the per-row solver and its error
metric). Passing preprocess= or solve= to convert replaces that whole
stage's options; the preset's other stage still applies. Presets bundle sensible
options for common sources:
| Preset | For |
|---|---|
photo |
Portraits, landscapes, general photos |
clean |
Safe universal default; light denoise + snap for unknown/BBS images |
smooth |
Noisy JPEGs, soft gradients |
vivid / graphic / flat |
Punchy colour, logos/cartoons, bold posterised |
retro / art |
Authentic Ceefax look, hand-crafted teletext-art look |
level1 |
Source is already a Mode 7 / Level 1 teletext image |
dark / tv / crt |
Exposure lift; LCD (PAR 1.2); CRT (PAR 1.22) |
Command line
sextants photo.jpg -o photo.bin --preset photo
sextants photo.jpg --preview preview.png --url # also print an edit.tf URL
sextants --help
The output is a raw 1000-byte page (25 rows × 40 bytes of teletext character
codes). It loads directly at &7C00 on a BBC Micro, or paste the --url into
edit.tf.
Development
uv sync # create the environment
uv run pytest # run the test suite
Licence
MIT — see LICENSE. Original algorithm © 2020 Kieran Connell;
packaging and modifications © 2026 Robert Smallshire.
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 sextants-0.3.0.tar.gz.
File metadata
- Download URL: sextants-0.3.0.tar.gz
- Upload date:
- Size: 31.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
52c8585b800d9b6dd81775dfac92b91ea6a04c1663f526d07c44a8af8fa5b41a
|
|
| MD5 |
95880f35149fee6627606d433381303a
|
|
| BLAKE2b-256 |
ebcd9c10251ec8d066ef49bf116c0f6a9fd9d814e89e63dc54fb76d9dd1fd0a0
|
File details
Details for the file sextants-0.3.0-py3-none-any.whl.
File metadata
- Download URL: sextants-0.3.0-py3-none-any.whl
- Upload date:
- Size: 37.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e05b4fdf54c77f40bdf789cea3afae862c5497e834d30ccf8114359ab035124b
|
|
| MD5 |
7c6737138eb40ebe21df1e14f8f4c575
|
|
| BLAKE2b-256 |
fb69225a09cacf803902949d042a987c025c69488a186eafff9f84568fba8cbc
|