8bitlenser/README.md
2026-07-03 19:35:35 -07:00

552 lines
30 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 8 Bit Lenser
Convert a modern image (PNG/JPG/GIF/BMP/WEBP) into a **Commodore 64 disk image**
(`.d64` / `.d71` / `.d81`) containing a self-contained viewer that displays the
picture on a real C64 or in an emulator. The converter works hard to preserve
quality within the VIC-II's tight colour and resolution limits.
The Commodore 64 is the reference target, but **25+ other retro machines** are
supported too — Atari 8-bit, Apple II / IIgs, BBC Micro, ZX Spectrum, Amstrad CPC,
TI-99/4A, NES, Sega Master System, Commodore 128 / 16 / Plus/4 / VIC-20 / PET,
Amiga, and more — each writing a self-contained, emulator-verified cartridge or
disk. There's also an **ANSI / CP437** text-art export for BBSes. Pick the target
with `--platform` (CLI) or the **Platform** selector (GUI). See the sections below.
![8 Bit Lenser GUI](docs/gui.png)
## Highlights
- **Five display modes** (auto-selectable):
- **Hires** — 320×200, 2 colours per 8×8 cell. Best for sharp line art.
- **Multicolor** — 160×200, 1 shared background + 3 colours per 4×8 cell
(the classic "Koala" format). Best general-purpose photo mode.
- **FLI** — re-points the video matrix every scanline for per-line (4×1) colour.
- **Interlace** — two multicolor frames blended at 50 Hz for ~136 apparent colours.
- **Mono** — highest-resolution path: 320×200 matched by *luminance* to a colour
ramp, so detail is carried by intense dithering. Greyscale by default, or pick
any palette colour as the base for a tinted monochrome (black → blue → light
blue → white, etc.).
- **Perceptual, dither-aware conversion.** All colour decisions are made in CIELAB.
Each screen cell's colour set is chosen by an exhaustive, vectorised search; for
error-diffusion dithering the search is **dither-aware** — it scores each colour
pair by distance to the *segment between* the colours (not just the nearest
colour), so the chosen colours bracket the cell and dithering blends to the true
shade. The result is dramatically smoother, more accurate images (perceptual ΔE
roughly halved). An *Intensive* mode additionally searches the global background.
- **Selectable dithering** — **Atkinson** (default), FloydSteinberg, the larger
Stucki / Jarvis / Sierra-3 / Burkes kernels, fast Sierra-Lite, tone-adaptive
Ostromoukhov and Hilbert-curve Riemersma (all paired with dither-aware
selection — best for photos), plus the ordered modes ordered Bayer, organic
**blue-noise** (no grid) and **Yliluoma** (mixes >2 palette entries per cell —
superb on the constrained flat-palette machines), and none — every one
constrained so a cell never shows a colour it can't.
- **Explore variations.** One click renders every Mode × Palette × Dither
combination as a contact sheet (parallelised across CPU cores); pick the best,
then fine-tune brightness/contrast/saturation/gamma on that choice.
- **PAL / NTSC.** Choose the target video standard. Static and interlace viewers
work on both (interlace flips per frame, so it flickers at the standard's field
rate automatically); FLI ships a separately-timed viewer for each.
- **Run in an emulator.** One click builds the image and boots it (every platform
runs under **MAME**): for the C64 it attaches the disk, then `LOAD"*",8,1` + `RUN`
to show the picture. Runs in warp mode except where a mode needs real-time (e.g.
interlace flicker).
- **On-disk info program.** When there's room, a colourful BASIC program is added
that prints the original name, dimensions, format, colour depth, oldest EXIF date,
file date, EXIF comment, when the C64 version was made, the host platform, and the
Linux distribution/version.
- **Self-contained viewers.** Each disk's first program embeds the picture and loads
in a single pass, so `LOAD"*",8,1` then `RUN` just works — no second disk access,
no emulator-config surprises.
- **Standard interchange files.** Multicolor exports also drop a `PICTURE.KOA`
(Koala) and hires a `PICTURE.ART` (OCP Art Studio) file for use in other C64 tools.
- **GUI and CLI.**
## Atari 8-bit support
A second target platform is built in (`--platform atari`, or the **Platform**
selector in the GUI). It produces a **self-booting `.atr` disk** (written natively
— no external tools) whose boot sectors load a viewer that shows the picture.
![8 Bit Lenser targeting the Atari 8-bit](docs/gui_atari.png)
Both GR.15 modes pick their colours with the same **dither-aware** search as the
C64 (for error-diffusion dithers): the 4 register colours are chosen so their
*blends* span the image gamut, so floyd dithering reproduces a rich, smooth image
instead of the muddy result of plain k-means centroids (perceptual ΔE ~17 → ~2.7
on a portrait). Defaults to FloydSteinberg.
Atari display modes:
- **GR.15** (ANTIC E) — 160×192, 4 colours chosen globally from 256 (no per-cell
limit, so cleaner than C64 multicolor).
- **GR.9** (GTIA) — 80×192, **16 real luminance shades of one hue** — superb
greyscale (hue 0) or tinted monochrome (pick any hue). Choose the hue via the
Mono/hue base control.
- **GR.8** — 320×192 hi-res, two tones.
- **GR.15+DLI** — a display-list interrupt rewrites the 4 colours every 2 scanlines
(96 colour bands) for far more colour — the Atari analogue of C64 FLI. (Every
*single* line is impossible: four register writes don't fit the inter-DLI window.)
The Atari palette is generated (NTSC YIQ), or — if the `atari800` emulator's bundled
palette file is installed (see *Requirements*) — read from that so the preview
matches exactly. "Run in emulator" boots the `.atr` in **MAME** (`a800xl` /
`a800xlp`).
> Status: all four Atari modes are **boot-verified** in MAME (`a800xl`), in
> addition to decode round-trips, previews and disk-structure checks.
## Apple II support
A third platform (`--platform apple`, or the GUI **Platform** selector) targets the
Apple II+ / //e. It writes a **self-booting `.dsk`** (DOS 3.3 sector order) with a
native, no-DOS boot loader: the boot sector reads the 8K HGR bitmap across three
tracks (stepping the drive head) and switches on graphics.
- **HGR mono** — 280×192, 1-bit black & white, universal across II+ and //e.
(On a colour monitor a finely-dithered HGR image shows NTSC artifact fringing;
on a mono monitor it's clean B&W.) **Boot-verified in MAME.**
- **HGR colour** (`hgr_color`) — 140×192 NTSC artifact colour (~6 colours), the
iconic II+ colour mode; reuses the HGR loader. **Boot-verified in MAME** (real
green/violet/blue/orange on the emulated Apple).
- **DHGR** (`dhgr`) — //e Double Hi-Res, 140×192, **16 colours**, the best Apple
photo mode. **Boot-verified in MAME** (`apple2ee`). The 16-colour palette is
measured from MAME's own DHGR output so the on-screen colours match the preview.
All three boot via a self-written multi-track loader (DOS-free): it uses the
standard phase-overlap head seek and sets both the sector (`$3D`) and track (`$41`)
the Disk II boot ROM verifies. "Run in emulator" boots the `.dsk` in **MAME**
(`apple2p` for II+, `apple2ee` for //e).
## More platforms
The same converter targets several other machines (each writes a self-contained,
MAME-verified cartridge or disk; pick with `--platform` or the GUI selector).
Every platform reports a consistent **perceptual** ΔE (measured after a light blur
that models how the eye/CRT averages a dither), so quality is comparable across
machines; and every machine with a constrained colour mode also offers a
**monochrome** mode (luminance-matched, detail carried by dithering — the
highest-detail path, and tintable via a base colour):
- **TI-99/4A** (`ti99`) — TMS9918A Graphics Mode 2, 256×192 / 2 colours per 8×8
cell (like C64 hires); 8 KB `.rpk` cartridge. Each cell's colour pair is chosen
by the same **dither-aware** segment search as the C64 (for error-diffusion
dithers), roughly halving perceptual error on photos. Defaults to **Atkinson**
dithering — its lighter diffusion bleeds less across the tight two-colour cell
boundaries than FloydSteinberg. Also a **mono** mode (luminance-matched grey
ramp — every cell neutral, so no colour clash and maximum detail; or tinted via
a base colour). Verified in MAME (`ti99_4a`).
- **TRS-80 Color Computer** (`coco`) — MC6847 PMODE 4 (256×192 mono) / PMODE 3
(128×192, 4 colours); `.ccc` Program Pak.
- **BBC Micro Model B** (`bbc`) — MODE 0/1/2/5 (up to 8 colours); DFS `.ssd` disk.
- **ColecoVision / Adam** (`coleco`) — TMS9918A GM2 (same chip as the TI-99/4A, so
it reuses that encoder, including the **dither-aware** colour-pair search and the
**mono** mode; defaults to **Atkinson**); `.col` cartridge. Verified in MAME
(`coleco`).
- **Atari 2600 / VCS** (`a2600`) — racing-the-beam 40×192 playfield kernel, **3
colours per scanline** (the background is rewritten mid-line so the left and
right halves differ, plus a shared foreground); `.a26` cartridge. Defaults to
Atkinson dithering (Bayer fares poorly on the 40px playfield). An optional
**interlace** mode (`pf_il`) ships an 8K bank-switched cart that alternates two
frames at 60Hz so each scanline shows ~4-6 *perceived* colours — much smoother,
at the cost of flicker (best on emulators / LCDs).
- **Mattel Intellivision** (`intv`) — STIC Foreground/Background mode, 160×96 =
20×12 cells of 8×8, **two colours per cell** (foreground from 8, background from
all 16) like C64 hires, drawn from a 64-tile GRAM dictionary; a hand-written
**CP1610** viewer on a clean cartridge (no copyrighted EXEC/game data). `.int`
cartridge, verified in MAME (`intv`). The 64 tile shapes and the per-cell
(tile, foreground, background) choices are optimised **jointly** by a
block-truncation / vector-quantisation iteration (alternately re-assign each
cell to its best tile, re-pick its two colours, and re-cut every tile's shape),
which drives the picture to within ~1.5 % of the theoretical floor of the
two-colour-per-cell model. Defaults to *no* dithering — within the 64-tile
budget error-diffusion is counterproductive (the clustering destroys it). A
**mono** mode (two greys, k-medoids tile codebook) gives a cleaner two-tone
picture than the colour mode at this resolution.
- **Commodore VIC-20** (`vic20`) — the VIC-20 has no bitmap mode, so images are
drawn from a **programmable 256-character set** clustered (k-means) from the
screen's 8×8 cells. Two modes:
- **Multicolor** (default) — 88×184, **four colours per 4×8 cell**: three global
registers (background and auxiliary from all 16, border from 07) plus one
per-cell colour (07). Because the warm tones (orange/pink) live only in the
global colours, this is the strong photo mode. The three globals are chosen by
a dither-aware nearest-colour-ranked search; defaults to FloydSteinberg.
- **Hires** — 176×184, one global background (any of 16) + one per-cell
foreground (07); best for high-contrast line art.
- **Mono** — 176×184 luminance-matched two-tone (black + white, or a tinted
base); the 256-char dictionary is built by a k-medoids codebook so dithered
detail survives.
Self-programming 6502 viewer (the KERNAL leaves the VIC uninitialised for an
autostart cart, so the viewer sets every register and copies the data to RAM).
8K `.a0` autostart cartridge, verified in MAME (`vic20`).
- **Sinclair ZX Spectrum** (`spectrum`) — 256×192, **two colours per 8×8 cell**
(ink + paper) like C64 hires, with the Spectrum's quirk that a cell's two
colours must share the BRIGHT bit (so the pair is chosen from one brightness
group). Uses the same **dither-aware** segment search; defaults to **Atkinson**
(lighter diffusion suits the attribute cells, as on the TI-99). Output is a 48K
`.sna` snapshot that bakes the picture into screen RAM plus a 3-byte idle loop,
so it appears instantly and holds — plus the standard 6912-byte `.scr` screen
file alongside. Also a **mono** mode (crisp black/white halftone at 256×192,
free of attribute clash, or a tinted ramp). Verified in MAME (`spectrum`).
- **Atari 5200** (`a5200`) — the 5200 is an Atari 8-bit (ANTIC + GTIA, 6502) in a
console, so it **reuses the Atari converters** unchanged: **GR.15** (160×192,
4 colours, dither-aware), **GR.8** (320×192 two-tone) and **GR.9** (80×192,
16 real luminance shades = the greyscale / tinted-mono mode). Having no OS, the
self-contained 6502 viewer programs ANTIC/GTIA hardware directly (GTIA is at
$C000 on the 5200) and ANTIC DMAs the bitmap + display list straight from the
cartridge ROM — nothing is copied to RAM. All display durations are supported
(hold forever, until a controller button, or for *N* seconds — timed off ANTIC's
VCOUNT, input read from the POKEY keypad / GTIA triggers). 32 KB `.a52`
cartridge, verified in MAME (`a5200`).
- **Atari 7800** (`a7800`) — the 7800's **MARIA** display processor is nothing like
ANTIC/GTIA (display-list-list → per-zone display lists → objects, 8 palettes of
3 colours + a shared background = 25 colours on screen). Its 160A mode is 2bpp
though, the same packing as GR.15, so the Atari encoder helpers are reused.
**c160** (160×192, **25 colours**) splits each line into objects and clusters the
image's segments into 8 palettes so each region gets a tuned 4-colour set;
**mono** restricts those palettes to one hue's luminances for a smooth greyscale.
A self-contained 6502 viewer loads MARIA's colour registers and points it at the
display-list list; MARIA DMAs the bitmap + display lists straight from the
cartridge ROM. 48 KB `.a78` cartridge, verified in MAME (`a7800`).
- **Commodore 128** (`c128`) — drives the **VDC 8563** 80-column chip, with three
display modes that trade resolution against colour:
- **mono** — **640×200** greyscale, the **highest resolution** of any target
here. MAME's VDC has no true linear bitmap (its bitmap path emits only one
bit per 8-pixel cell), so — like `hicolor` — this draws through a per-image
custom character set, restricted to the VDC's four greys, for smooth
multi-level greyscale. `--mono-base` tints the grey ramp toward a colour.
- **hicolor** — **640×200** in colour via a per-image **custom character set**.
Each 8×8 cell draws a custom glyph (full per-pixel detail) in its own **ink**
colour (attribute RAM) over one **global background**, a ZX-Spectrum-like
colour model at double the Spectrum's horizontal resolution. The ~2000 cell
glyphs are vector-quantised to a **512-glyph** set (two VDC charset banks,
the second selected per cell by the attribute's alternate-charset bit).
- **color** — a chunky **80×100** image in all **16 VDC colours**, one free
solid colour per 8×2 cell (full colour, or smooth greyscale on the four
greys), the coarsest but most colourful option.
The 8502 viewer banks the data in over the BASIC ROM (`$FF00`), programs the
VDC, and copies into the VDC's own RAM with an explicit per-byte update address
(MAME's 8563 corrupts the auto-increment stream). Delivered as an autobooting
**`.d64`** that loads and runs with BASIC 7.0 `RUN"PIC"`, verified in MAME
(`c128`).
- **Commodore 16** (`c16`) — drives the **TED** (7360/8360) chip, whose palette
is **121 colours** (8 luminance levels × 16 hues) — far richer than the VIC-II.
**hires** is the TED's **320×200** bitmap: two colours per 8×8 cell, but each
may be any of the 121 colours (vs the VIC-II's 16), so photos come out
noticeably more colourful than C64 hires. The two per-cell colours are stored
across the TED's two colour matrices (a hue matrix + a luminance matrix). A tiny
7501 viewer programs the TED registers; the whole picture (matrices + bitmap)
loads into the C16's 16 KB RAM. Also a **mono** mode (the TED's neutral grey
ramp — 8 luminances — for smooth greyscale, tintable via `--mono-base`).
Delivered as a **`.prg`** (MAME quickload, or `LOAD`+`RUN` on real hardware),
verified in MAME (`c16`).
- **Commodore Plus/4** (`plus4`) — the C16's bigger sibling: same **TED** chip
and BASIC 3.5, just 64 KB RAM instead of 16 KB. The TED's bitmap hardware is
identical, so the Plus/4 uses the C16 encoder unchanged (same **hires** and
**mono** modes, same 121-colour palette) and the same **`.prg`** is binary
compatible across both machines. Verified in MAME (`plus4`).
- **Amstrad CPC** (`cpc`) — Z80 + Gate Array, with a fixed 27-colour palette and
three true bitmap modes (no per-cell colour limit). **mode0** (160×200) is the
flagship photo mode: a flat **16-colour** palette chosen from the 27 — the
encoder greedily picks the best 16 for each image, then dithers, giving clean,
colourful results. **mode1** (320×200, 4 colours) trades colour for resolution;
**mono** (mode 2, 640×200, 2 colours) is the highest-resolution black & white.
Like the ZX Spectrum, it's delivered as a **`.sna`** snapshot that bakes the
screen, palette, mode and an idle CPU, so the picture appears instantly with no
loader. Verified in MAME (`cpc6128`).
- **Tandy CoCo 3** (`coco3`) — the CoCo 2's big upgrade: the **GIME** chip with a
**64-colour** palette and true bitmap modes (no per-cell colour limit). **gr16**
(160×192) is the flagship: a flat **16-colour** palette picked from the 64 per
image, then dithered — clean, colourful results. **gr4** (320×192, 4 colours)
trades colour for resolution; **mono** (640×192, 2 colours) is the highest-res
black & white. A 6809 viewer in a 16 KB Program Pak copies the image to RAM and
programs the GIME (palette, mode, geometry, video base), then idles. Delivered
as a **`.ccc`** cartridge, verified in MAME (`coco3`; the app forces the RGB
monitor, since MAME defaults to the composite artifact palette).
- **Nintendo NES / Famicom** (`nes`) — the 2C02 PPU is **tile-based** (not a
bitmap), so this is the most constrained target: a 256×240 background built from
a **32×30 grid of 8×8 tiles** (≤256 unique in CHR), coloured by **4 sub-palettes**
(a shared universal background + 3 colours each) with one chosen per **16×16**
region via the attribute table, all from the NES's 54-colour master palette.
The encoder picks the universal bg, clusters the image into 4 sub-palettes,
assigns each region its best one, dithers, then vector-quantises the tile
patterns to 256 CHR tiles. **bg** is the colour mode; **mono** uses the PPU's
grey ramp. A 6502 viewer programs the PPU (palette, nametable, attributes) and
enables the background. Delivered as an **iNES `.nes`** (NROM) cartridge,
verified in MAME (`nes`). The blockiness is the NES's inherent per-16×16
attribute-clash and 256-tile limits, not the encoder.
- **Apple IIGS** (`iigs`) — its **Super Hi-Res** mode is the highest-fidelity
target here: **320×200** with **16 colours per scanline**, each line choosing
one of **16 palettes** of 16 colours drawn from a **4096-colour** master — so up
to ~256 colours on screen, and photos come out near-photographic. **shr** is the
colour mode (the encoder clusters the 200 lines into 16 palettes, assigns each
line its best, and dithers); **mono** is a single 16-level grey palette (the
smoothest greyscale in lenser). The picture data is the 32 KB SHR block; a
small loader (6502 + a 65C816 block move into bank `$E1`) fills the SHR screen
and turns it on. Delivered as a bootable **5.25" `.dsk`** (boots via slot 6 like
an Apple II), verified in MAME (`apple2gs`). Note: the 32 KB loads off the
emulated floppy, so the picture takes ~30 s to appear.
- **Commodore PET / CBM** (`pet2001`, `pet4032`, `pet8032`, `superpet`) — the PET
has no bitmap or colour at all, just a fixed-character monochrome text screen.
Images are rendered with the PETSCII **2×2 quadrant-block** graphics characters
(16 patterns, derived from the character ROM), giving a one-bit pseudo-bitmap of
**80×50** on the 40-column models (`pet2001` = PET 2001 / CBM 3000, `pet4032` =
PET 40xx / CBM 40xx) and **160×50** on the 80-column ones (`pet8032` = PET 80xx /
CBM 80xx, `superpet` = SuperPET SP9000 / MMF 9000). The `mono` mode dithers to
one bit and maps each 2×2 block to its quadrant character; a 6502 loader pokes
the screen codes into screen RAM (`$8000`). Delivered as a **`.prg`** (quickload
/ `LOAD`+`RUN`), verified in MAME. (MAME's `superpet` driver is incomplete, so
that platform targets the identical-display `cbm8032`.) Necessarily very low
resolution — it's a text terminal — but the dithered portrait is recognisable.
- **Sega Master System** (`sms`) — tile-based like the NES but far less
constrained: each 8×8 tile is **4bpp (16 colours)** and picks one of **two
16-colour palettes** from a 64-colour master, so up to **32 colours** on screen
at 256×192. The encoder builds palette 0 for the whole image and palette 1 for
the colours it serves worst, assigns each tile its better palette, dithers, and
vector-quantises the patterns to the 448 tiles that fit VRAM. **bg** is the
colour mode (near-photographic — much better than the NES); **mono** uses the
VDP's 4 true greys. A Z80 viewer programs the VDP and uploads the
tiles/name-table/palette. Delivered as an iNES-free **`.sms`** cartridge (with a
valid "TMR SEGA" header), verified in MAME (`sms`).
- **Commodore Amiga** (`amiga`) — the 68000 graphics machine. **lowres** is the
flagship: 320×200 with **32 colours from 4096** (a true flat-palette bitmap, no
per-cell limit) — clean, near-photographic. **mono** uses the Amiga's 16 real
grey levels. A 68000 boot block loads the bitplanes into chip RAM via the boot
trackdisk request and points the Copper at them. Delivered as a bootable
**`.adf`** floppy (valid boot-block checksum), verified in MAME (`a500`). (The
Amiga's famous **HAM** 4096-colour mode was also implemented and looks superb,
but MAME's preliminary Amiga can't render 6-bitplane/HAM modes cleanly — black
bands at the screen edges — so only the MAME-verified 32-colour and mono modes
ship.)
## ANSI / BBS art (CP437)
Not a machine at all: `--platform ansi` (or the GUI **Platform** selector) emits a
**`.ANS` text file** — CP437 characters plus ANSI colour escapes — for display on a
BBS or in any ANSI art viewer. There's **no disk, no emulator, and no slideshow**;
it's a single text artefact, and it's fully **previewable** in the GUI.
Every cell picks its colours from the **16 EGA/VGA colours**, using **iCE colours**
(a bright background via the blink bit) so photos have a full palette. Two encoders,
chosen by the **Intensive** toggle:
- **Full glyph** (Intensive on, the default) — matches every **8×16** character cell
to the best of the *whole* CP437 repertoire (letters, punctuation, box-drawing and
block glyphs) together with an optimal foreground/background colour pair, by
minimum CIELAB error. Gradients turn into shade characters, so the result is far
richer than blocks alone. A blue-noise pre-dither kills banding.
- **Half-block** (Intensive off, fast) — every cell is the upper-half block `▀` with
an independent foreground (top pixel) and background (bottom pixel), giving two
freely-coloured pixel rows per text row with no cell-colour clash.
Modes set the canvas: **`80x25`** (one classic screen), **`80x50`** (taller, more
detail) and **`mono`** (a greyscale ramp, tintable toward any hue via the mono base
control). The output is verified byte-faithful — an independent CP437 re-render of
the `.ANS` reproduces the preview exactly.
```sh
python -m lenser.cli photo.jpg --platform ansi -m 80x50 -o photo.ans
```
## Requirements
**Required**
- **Python 3.9+**, with **`numpy`** and **`Pillow`**. That's all the ANSI/CP437
export needs.
**Optional external tools.** These are located by name on your **`PATH`** — the app
runs `xa`, `c1541`, `mame` as bare commands (via `shutil.which`), with no config
file or environment-variable override. If a tool isn't on `PATH`, the feature that
needs it is disabled (the GUI greys it out; the CLI raises a clear error naming the
missing command). Install them so the command is on `PATH`, or symlink the binary
into a `PATH` directory.
- **`PyQt5`** — only for the GUI (`import`ed at GUI start-up); the CLI works without
it. Install into the same Python environment you run lenser with.
- **[`xa`](https://www.floodgap.com/retrotech/xa/) (xa65)** — the 6502 assembler
that builds the on-machine viewers. Required to export for any real machine
(C64, Atari, Apple, …); looked up as the command **`xa`** on `PATH`. Not needed
for the ANSI export or for image previews.
- **[VICE](https://vice-emu.sourceforge.io/)'s `c1541`** — builds the Commodore
disk images (`.d64` / `.d71` / `.d81`, and the C128 `.d64`). Looked up as the
command **`c1541`** on `PATH` (it ships with VICE). Only needed when exporting
those Commodore disk formats.
- **[MAME](https://www.mamedev.org/)** — powers the **Run in emulator** button for
**every** platform (the C64 and Atari included; there is no VICE/atari800 runner).
Looked up as the command **`mame`** on `PATH`. MAME finds its own system ROMs via
its normal `rompath`; lenser only launches it. Purely optional — it's never needed
to *produce* a disk or cartridge, only to preview one.
- **[atari800](https://atari800.github.io/)** — *optional palette source only* (it
is **not** used to run anything). If its bundled NTSC palette file is present,
lenser reads it so the Atari preview matches the emulator exactly; otherwise a
generated YIQ palette is used. It is searched for at these fixed paths (not on
`PATH`), first match wins:
- `/usr/share/atari800/Palettes/Real.act`
- `/usr/share/atari800/default.pal`
- `/usr/local/share/atari800/default.pal`
On Debian/Ubuntu, installing the packages puts every command on `PATH`:
```sh
sudo apt install python3-numpy python3-pil python3-pyqt5 xa65 vice mame atari800
```
## Usage
### GUI
```sh
python -m lenser.gui # or: 8bitlenser
```
Open an image (via **Open image…** or by **dragging an image file onto the
window**), pick a mode / disk format / dithering, watch the live C64 preview,
then **Export**.
### Slideshow (multiple images on one disk)
Tune an image, click **Add to slideshow**, repeat for as many images as you like
(each keeps its own mode / dithering / palette / adjustments), then open
**Slideshow…** to arrange the queue and **Export** one drive image. The dialog
shows a **live storage meter** and refuses to export once the queue exceeds the
chosen disk format's capacity (or its directory limit), so you always know how
many more images fit. Choose how the viewer advances: on a **keypress**,
automatically after **N seconds**, or **both** (keys still work, but it
auto-advances on the timer), and whether it **loops**. On the C64 the exported
disk boots with `LOAD"*",8,1` then `RUN` and steps through the pictures.
On the **C64**, hires, multicolor and mono may be **mixed freely** in one
slideshow; **FLI** and **interlace** are supported too, but as *uniform* shows
(all-FLI or all-interlace) since each needs its own raster engine. The
**Commodore 128** (640×200 VDC hicolor/mono, `.d64` booted with `RUN"PIC"`), the
**Atari** (GR.15 / GR.9 / GR.8 / GR.15+DLI, self-booting `.atr` that SIO-loads each slide), the **BBC
Micro** (single screen mode, DFS `.ssd` that `*LOAD`s each slide), the
**Apple II** (HGR, self-booting `.dsk`; up to 4 images loaded into RAM at once),
the **Apple IIgs** (Super Hi-Res, self-booting `.dsk` with a two-stage 65816
loader that banks each 32 KB image; up to 4), and the **Commodore Amiga**
(lo-res/HAM, bootable `.adf` whose 68000 boot block loads every image into chip
RAM and cycles them) are also supported — **all seven disk platforms**.
Headless equivalent — a JSON manifest fed to `8bitlenser-slideshow`:
```sh
python -m lenser.slideshow_cli show.json -o show.d64
```
where `show.json` lists the global options (`platform`, `format`, `advance`,
`seconds`, `loop`) and an `items` array, each item an `image` path plus any
per-image `mode` / `dither` / `palette` / `brightness` / `contrast` / `tint` /
… overrides.
### Command line
```sh
# Multicolor picture onto a .d64, plus a preview PNG of how it will look:
python -m lenser.cli photo.jpg -m multicolor -o photo.d64 --preview photo.png
# Let the tool pick the best standard mode, write a .d81:
python -m lenser.cli photo.jpg -m auto -o photo.d81
# Best quality (slower) FLI with error-diffusion dithering:
python -m lenser.cli photo.jpg -m fli -d floyd --intensive -o photo.d64
# Atari: 16-shade greyscale, and a 4-colour-per-line image:
python -m lenser.cli photo.jpg --platform atari -m gr9 -d floyd -o photo.atr
python -m lenser.cli photo.jpg --platform atari -m gr15dli -o photo.atr
# High-res greyscale with smooth Stucki dithering:
python -m lenser.cli photo.jpg -m mono -d stucki -o photo.d64
# ...or tinted monochrome in blue:
python -m lenser.cli photo.jpg -m mono --mono-base blue -d jarvis -o photo.d64
```
Options: `-m/--mode {auto,hires,multicolor,fli,interlace,mono}`,
`-f/--format {d64,d71,d81}`, `-p/--palette {colodore,pepto}`,
`-d/--dither {bayer,bluenoise,yliluoma,floyd,atkinson,stucki,jarvis,sierra,sierra_lite,burkes,riemersma,ostromoukhov,none}`,
`--mono-base {grayscale,<colour name>}`, `--video {pal,ntsc}`,
`-a/--aspect {fit,fill,stretch}`, `--intensive`,
`--brightness/--contrast/--saturation/--gamma`, `--preview`.
### On the C64 / in an emulator
Attach the disk to drive 8 and:
```
LOAD"*",8,1
RUN
```
Press any key to return to BASIC (hires/multicolor). For FLI/interlace, reset to exit.
## Notes on the advanced modes
- **FLI** is timing-critical: the viewer runs a cycle-stable raster loop. Expect a
small settling artifact in the top rows (a well-known FLI characteristic) and the
leftmost few pixels reserved by the hardware "FLI bug".
- **Interlace** flickers at 25 Hz on a CRT; it looks best in an emulator or on an LCD.
- Multicolor and Hires are the universally safe, flicker-free choices.
## How conversion works
`lenser/convert/` holds one encoder per mode on top of a shared core
(`convert/base.py` + `dither.py`). The pipeline: prepare & resize the image to the
mode's pixel grid (`imageprep.py`), convert to CIELAB (`palette.py`), choose each
cell's legal colour set by exhaustive search, dither within those sets, then pack the
VIC-II bytes. `viewer/*.s` are the 6502 viewers (assembled by `xa`), combined with the
picture data and written to a disk image by `diskimage.py` / `exporter.py`.