First public commit.

This commit is contained in:
The Dust Council 2026-07-03 19:35:35 -07:00
parent 2a48f52979
commit 4bac9d83ed
288 changed files with 18417 additions and 1076 deletions

140
lenser/gallery.py Normal file
View file

@ -0,0 +1,140 @@
"""Render every Mode x Palette x Dither variation of an image, for the GUI's
"Explore variations" contact sheet. Kept import-light enough to run in
ProcessPoolExecutor worker processes; platform-aware (C64 or Atari).
"""
from __future__ import annotations
from . import imageprep
from .convert import render_preview
DITHERS = ["bayer", "floyd", "atkinson", "none"]
def combos(platform: str):
"""(mode, palette, dither) triples for a platform."""
if platform == "apple":
from .apple.convert import MODES as PMODES
return [(m, "mono", d) for m in PMODES for d in DITHERS]
if platform == "atari":
from .atari.convert import MODES as AMODES
# floyd first: gr15/gr15dli use dither-aware palette selection
adith = ["floyd", "atkinson", "bayer", "none"]
return [(m, "ntsc", d) for m in AMODES for d in adith]
if platform == "ti99":
from .ti99.convert import MODES as TMODES
# atkinson first: gm2 selection is dither-aware, and atkinson's lighter
# diffusion bleeds less across the tight 8x8 two-colour cells than floyd
tdith = ["atkinson", "floyd", "bayer", "none"]
return [(m, "tms9918", d) for m in TMODES for d in tdith]
if platform == "coco":
from .coco.convert import MODES as COMODES
return [(m, "mc6847", d) for m in COMODES for d in DITHERS]
if platform == "bbc":
from .bbc.convert import MODES as BMODES
return [(m, "bbc", d) for m in BMODES for d in DITHERS]
if platform == "coleco":
from .coleco.convert import MODES as CVMODES
# same TMS9918A GM2 as the TI-99 -> dither-aware, atkinson first
cvdith = ["atkinson", "floyd", "bayer", "none"]
return [(m, "tms9918", d) for m in CVMODES for d in cvdith]
if platform == "a2600":
from .a2600.convert import MODES as VMODES
# atkinson/none first: bayer fares poorly on the 40px playfield
adith = ["atkinson", "none", "floyd", "bayer"]
return [(m, "tia", d) for m in VMODES for d in adith]
if platform == "intv":
from .intv.convert import MODES as IMODES
# none first: the 64-tile GRAM limit makes error-diffusion counterproductive
idith = ["none", "atkinson", "bayer", "floyd"]
return [(m, "stic", d) for m in IMODES for d in idith]
if platform == "vic20":
from .vic20.convert import MODES as VMODES
# floyd first: the colour selection is dither-aware (segment metric)
vdith = ["floyd", "atkinson", "bayer", "none"]
return [(m, "vic", d) for m in VMODES for d in vdith]
if platform == "spectrum":
from .spectrum.convert import MODES as ZMODES
# atkinson first: 2-colour-per-cell (attribute clash), lighter diffusion
# bleeds less across cells (as on the TI-99)
zdith = ["atkinson", "floyd", "bayer", "none"]
return [(m, "spectrum", d) for m in ZMODES for d in zdith]
if platform == "a5200":
from .a5200.convert import MODES as XMODES
# floyd first: reuses the dither-aware Atari GTIA encoders
xdith = ["floyd", "atkinson", "bayer", "none"]
return [(m, "ntsc", d) for m in XMODES for d in xdith]
if platform == "a7800":
from .a7800.convert import MODES as SMODES
sdith = ["floyd", "atkinson", "bayer", "none"]
return [(m, "ntsc", d) for m in SMODES for d in sdith]
if platform == "c128":
from .c128.convert import MODES as C128_MODES
# floyd first: the 640x200 two-tone is pure luminance dither, so error
# diffusion yields the smoothest tonal gradients at this high resolution.
c128dith = ["floyd", "atkinson", "bayer", "none"]
return [(m, "vdc", d) for m in C128_MODES for d in c128dith]
if platform in ("c16", "plus4"):
from .c16.convert import MODES as C16_MODES
# floyd first: TED hires picks 2 of 128 colours per cell, so error
# diffusion brackets each cell and blends to the true shade.
c16dith = ["floyd", "atkinson", "bayer", "none"]
return [(m, "ted", d) for m in C16_MODES for d in c16dith]
if platform == "cpc":
from .cpc.convert import MODES as CPC_MODES
# floyd first: a flat N-colour palette dithered -> error diffusion gives
# the smoothest gradients.
cpcdith = ["floyd", "atkinson", "bayer", "none"]
return [(m, "cpc", d) for m in CPC_MODES for d in cpcdith]
if platform == "coco3":
from .coco3.convert import MODES as COCO3_MODES
# floyd first: flat palette chosen from 64 colours, dithered.
c3dith = ["floyd", "atkinson", "bayer", "none"]
return [(m, "gime", d) for m in COCO3_MODES for d in c3dith]
if platform == "nes":
from .nes.convert import MODES as NES_MODES
# atkinson first: lighter diffusion bleeds less across the NES's tight
# 16x16 attribute-palette regions (like the TI/Spectrum cell platforms).
nesdith = ["atkinson", "floyd", "bayer", "none"]
return [(m, "nes", d) for m in NES_MODES for d in nesdith]
if platform == "iigs":
from .iigs.convert import MODES as IIGS_MODES
# floyd first: per-line 16-of-4096 palette, dithered -> smoothest result.
gsdith = ["floyd", "atkinson", "bayer", "none"]
return [(m, "iigs", d) for m in IIGS_MODES for d in gsdith]
if platform in ("pet2001", "pet4032", "pet8032", "superpet"):
# one-bit quadrant-block mono; floyd carries the most tone at low res.
return [("mono", "pet", d) for d in ("floyd", "atkinson", "bayer", "none")]
if platform == "sms":
from .sms.convert import MODES as SMS_MODES
# floyd first: 16 colours/tile from 2 palettes -> error diffusion blends
# to the smoothest result.
smsdith = ["floyd", "atkinson", "bayer", "none"]
return [(m, "sms", d) for m in SMS_MODES for d in smsdith]
if platform == "amiga":
from .amiga.convert import MODES as AMIGA_MODES
# floyd first: flat 32-of-4096 palette dithered -> smoothest gradients.
amdith = ["floyd", "atkinson", "bayer", "none"]
return [(m, "amiga", d) for m in AMIGA_MODES for d in amdith]
if platform == "ansi":
from .ansi.convert import MODES as ANSI_MODES
# floyd first: a free 16-colour quantise, so error diffusion blends best.
ansidith = ["floyd", "atkinson", "bayer", "none"]
return [(m, "vga", d) for m in ANSI_MODES for d in ansidith]
from .convert import MODES as CMODES
# floyd first: C64 colour selection is dither-aware, so error diffusion gives
# the smoothest, most accurate result.
cdith = ["floyd", "atkinson", "bayer", "none"]
return [(m, p, d) for m in CMODES for p in ["colodore", "pepto"] for d in cdith]
def render_variation(args):
"""Worker entry. args = (path, platform, mode, palette, dither, prep_kwargs,
base_name). Returns (mode, palette, dither, error, rgb)."""
path, platform, mode, palette, dither, prep_kwargs, base_name = args
from . import platforms
prep = imageprep.PrepOptions(**prep_kwargs)
conv = platforms.convert(platform, path, mode, palette, dither,
False, prep, base_name)
rgb = render_preview(conv, palette, scale=1)
return (mode, palette, dither, conv.error, rgb)