Convert images to Commodore 64 VIC-II formats.
Color operations in OKLab. Native CLI (multithreaded) and in-browser version at png2c64.app (WASM).
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
| Input | PETSCII (40x25 text mode) |
All 30 modes shown with --gamma 1 --dither-strength 0.7 --error-clamp 0.2. Click to expand:
- Bitmap modes: hires (320×200, 2 colors/cell), multicolor (160×200, 4 colors/cell)
- FLI / AFLI: per-row color attributes, PRG export with displayer
- PETSCII: image approximation using the C64 ROM character set (brute-force glyph + fg/bg per cell)
- Sprite sheets: hires / multicolor, arbitrary grid dimensions
- Character sets: hires, multicolor, and mixed; dedup, pattern merging, k-means refinement, C header export
- 7 VIC-II palettes: Pepto, VICE, Colodore, Deekay, Godot, C64 Wiki, Levy
- 30 dither methods: ordered (Bayer, checker, clustered, line), halftone/non-rectangular (halftone8×8, spiral5×5, hex8×8), analytical (IGN, R2, blue noise, crosshatch, radial, value noise), error diffusion (Floyd-Steinberg, Atkinson, Sierra, Jarvis, Ostromoukhov), 2:1 and line-biased variants
- Preprocessing: OKLab brightness/contrast/saturation/gamma/hue, sharpen/blur, black/white point clipping, automatic palette-range matching
- Per-cell metric: MSE (default), Pappas-Neuhoff blur, or Wang SSIM — useful for PETSCII and charset modes
- Interactive mode (iTerm2, POSIX): live parameter tuning with inline preview
- Gallery mode (iTerm2): inline previews of dither or parameter sweeps
- Export: PNG preview, PRG with embedded displayer (bitmap / FLI / AFLI / PETSCII), Koala and Art Studio raw, C header (charset / sprite / PETSCII data)
- GCC 15+ (uses C++26 /
-std=c++2c) - CMake 3.28+
- macOS, Linux, or Windows (MSYS2 UCRT64)
Prebuilt Linux / macOS / Windows binaries are attached to each GitHub release.
cmake -B build -DCMAKE_C_COMPILER=gcc-15 -DCMAKE_CXX_COMPILER=g++-15 .
cmake --build build --parallel
ctest --test-dir build --output-on-failureOn Windows, the same commands work under MSYS2 UCRT64 (mingw-w64-ucrt-x86_64-gcc, mingw-w64-ucrt-x86_64-cmake).
Check the build number:
./build/png2c64 --versionpng2c64 input.jpg output.png
png2c64 --gamma 2.0 --dither jarvis input.jpg output.pngpng2c64 --mode hires input.jpg output.png# 6x2 multicolor sprite sheet
png2c64 --mode sprite-mc --sprites-x 6 --sprites-y 2 input.png output.png
# Single hires sprite
png2c64 --mode sprite-hi input.png output.pngpng2c64 --mode fli input.jpg output.png # multicolor FLI, per-row colors
png2c64 --mode afli input.jpg output.png # hires FLI, per-row colors# Uses C64 ROM charset to approximate the image in text mode
png2c64 --mode petscii --gamma 3.3 --dither-strength 0.7 input.jpg output.png# Multicolor charset from full-screen image -> C header
png2c64 --mode charset-mc --width 320 --height 200 --dither checker input.jpg output.h
# Hires charset, no dithering (full k-means optimization)
png2c64 --mode charset-hi --width 320 --height 200 --dither none input.jpg output.hThe charset output is a C header file containing:
#define name_COLS 40
#define name_ROWS 25
#define name_BACKGROUND 0
#define name_MULTICOLOR1 6
#define name_MULTICOLOR2 14
static const unsigned char name_charset[2048] = { ... };
static const unsigned char name_screen[1000] = { ... };
static const unsigned char name_color[1000] = { ... };Live parameter tuning with instant preview in iTerm2:
png2c64 --interactive input.jpg output.png
png2c64 --mode charset-mc --width 320 --height 200 --interactive input.jpg output.h| Key | Action | Key | Action |
|---|---|---|---|
p/P |
palette next/prev | d/D |
dither next/prev |
g/G |
gamma +/- | s/S |
strength +/- |
b/B |
brightness +/- | c/C |
contrast +/- |
t/T |
saturation +/- | e/E |
error clamp +/- |
x |
serpentine toggle | r |
reset all |
w |
save output | q |
quit |
Preview parameter variations inline in iTerm2:
png2c64 --gallery dither input.jpg
png2c64 --gallery gamma input.jpg
# Available: dither, brightness, contrast, saturation, gamma,
# error-clamp, dither-strength, adaptiveGallery and interactive work with all modes including charset.
--mode <mode> hires, multicolor, fli, afli, petscii,
sprite-hi, sprite-mc,
charset-hi, charset-mc, charset-mixed
(default: multicolor)
--palette <name> pepto, vice, colodore, deekay, godot,
c64wiki, levy (default: colodore)
--dither <method> Dithering method (default: checker)
Square-pixel ordered: none, bayer4, bayer8
2:1 multicolor ordered: checker, bayer2x2, h2x4, clustered
Horizontal lines: line2, line-checker, line4, line8
Halftone / analytical: halftone8, diagonal8, spiral5, hex8, hex5,
blue-noise, ign, r2, white-noise,
crosshatch, radial, value-noise
Error diffusion: fs, atkinson, sierra, fs-wide, jarvis,
line-fs, ostromoukhov
--dither-strength <float> 0.0–2.0 (default: 0.7)
--error-clamp <float> Max error per OKLab channel (default: 0.1)
--adaptive <float> Contrast-adaptive error diffusion 0.0–1.0
(default: 0.0; reduces dither in detail,
keeps it in flat areas)
--no-serpentine Disable serpentine error-diffusion scan
--match-range OKLab extent → palette extent remapping
--brightness <float> –1.0 to 1.0 (default: 0.0)
--contrast <float> 0.0–2.0 (default: 1.0)
--saturation <float> 0.0–2.0 (default: 1.0)
--gamma <float> 0.1–8.0 (default: 1.5)
--hue-shift <float> –180 to 180 degrees (default: 0)
--sharpen <float> –1 to 2 (negative blurs, positive sharpens)
--black-point <float> 0.0–0.4 (clip darkest fraction)
--white-point <float> 0.0–0.4 (clip brightest fraction)
--metric mse|blur|ssim Per-cell error metric. MSE is the default
and pairs with dither; blur / ssim score
against the continuous source (useful for
PETSCII and charset modes).
--graphics-only PETSCII: restrict to graphics glyphs (no text)
--width <int> Override output width
--height <int> Override output height
--sprites-x <int> Sprite sheet columns (default: 1)
--sprites-y <int> Sprite sheet rows (default: 1)
--gallery <param> dither | brightness | contrast | saturation |
gamma | error-clamp | dither-strength |
adaptive
--interactive Live parameter tuning (iTerm2, POSIX only)
--version Print version and exit
Load image (PNG/JPEG/BMP/TGA via stb_image)
-> Bicubic scale (separable Mitchell-Netravali)
-> Preprocess (gamma, brightness, contrast, saturation)
-> Match palette range (remap OKLab extent to target palette)
-> Brute-force quantize (per-cell, all valid color combinations)
-> Dither (error diffusion in OKLab space)
-> Output (PNG + iTerm2 inline preview)
Each cell is quantized by exhaustively testing all valid color combinations:
| Mode | Combinations per cell | Total for screen |
|---|---|---|
| Hires | C(16,2) = 120 pairs | ~15M evaluations |
| Multicolor | 16 backgrounds x C(15,3) = 455 triples | ~931M evaluations |
| FLI | 16 bg x 15 colorram x C(14,2) x 8 rows | ~44K/cell |
| PETSCII | 16 bg x 256 chars x 15 fg | ~61M evaluations |
| Charset MC | C(16,3) = 560 shared triples x 13 per-cell | ~300M evaluations |
All distance computations use squared OKLab perceptual distance. Cell processing is parallelized across all CPU cores via std::jthread.
- Color selection -- brute-force optimal shared colors
- Dithering -- error diffusion within cell color constraints
- Pattern encoding -- 8-byte binary patterns from dithered assignments
- Deduplication -- identical patterns share charset entries
- Merging -- if > 256 unique: precompute pairwise OKLab distances, sort, merge closest pairs
- K-means refinement -- iteratively reassign cells to best-matching patterns. With dithering: preserves dither patterns (assignments only). Without: full centroid recomputation.
Error diffusion operates entirely in OKLab space -- the error buffer, accumulation, and distribution all use OKLab values. This prevents the visible cell-boundary artifacts that occur when mixing RGB error propagation with OKLab color matching.
The 2:1 dither modes account for the double-wide multicolor pixel:
- h2x4 -- 2x4 Bayer matrix that tiles as a perceptually square block at 2:1 display
- clustered -- dot pattern shaped for round appearance at 2:1
- fs-wide -- Floyd-Steinberg with vertical-biased weights
- jarvis -- Jarvis-Judice-Ninke 5x3 kernel, naturally handles 2:1 via wider reach
src/
main.cpp CLI, pipeline, iTerm2 display, gallery
types.hpp Color3f, Image, Palette, Result<T>
color_space.hpp sRGB/linear/OKLab (constexpr)
vic2.hpp VIC-II modes and constraints
palette.hpp 7 palettes (constexpr, registry)
png_io.hpp/.cpp Load/save/encode via stb_image
scale.hpp/.cpp Bicubic scaling
preprocess.hpp/.cpp Color adjustments + OKLab range matching
quantize.hpp/.cpp Brute-force quantization (multithreaded)
dither.hpp/.cpp 30 dither algorithms (OKLab)
charset.hpp/.cpp Charset conversion + C header export
petscii_rom.hpp C64 character ROM (256 chars, precomputed bit tables)
prg.hpp/.cpp PRG export with embedded displayers
displayer_data.hpp Koala/hires/FLI/AFLI/PETSCII displayer binaries
third_party/
stb_image.h, stb_image_write.h
MIT








































