552 lines
30 KiB
Markdown
552 lines
30 KiB
Markdown
# 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.
|
||
|
||

|
||
|
||
## 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), Floyd–Steinberg, 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.
|
||
|
||

|
||
|
||
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 Floyd–Steinberg.
|
||
|
||
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 Floyd–Steinberg. 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 0–7) plus one
|
||
per-cell colour (0–7). 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 Floyd–Steinberg.
|
||
|
||
- **Hires** — 176×184, one global background (any of 16) + one per-cell
|
||
foreground (0–7); 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`.
|