First public commit.
This commit is contained in:
parent
2a48f52979
commit
4bac9d83ed
288 changed files with 18417 additions and 1076 deletions
138
lenser/cli.py
Normal file
138
lenser/cli.py
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
"""Headless entry point: convert an image, write a disk image and/or a preview PNG."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from . import imageprep
|
||||
from .convert import MODES, convert_image, render_preview
|
||||
from .palette import COLOR_NAMES
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
p = argparse.ArgumentParser(prog="8bitlenser", description=__doc__)
|
||||
p.add_argument("image", help="source image (png/jpg/gif/bmp/webp)")
|
||||
p.add_argument("-o", "--output", help="disk image path (.d64/.d71/.d81/.atr)")
|
||||
p.add_argument("--platform", default="c64",
|
||||
choices=["c64", "atari", "apple", "ti99", "coco", "bbc",
|
||||
"coleco", "a2600", "intv", "vic20", "spectrum", "a5200",
|
||||
"a7800", "c128", "c16", "plus4", "cpc", "coco3",
|
||||
"nes", "iigs", "pet2001", "pet4032", "pet8032",
|
||||
"superpet", "sms", "amiga", "ansi"],
|
||||
help="target machine (coleco = ColecoVision/Adam, "
|
||||
"a2600 = Atari 2600/VCS, intv = Mattel Intellivision, "
|
||||
"vic20 = Commodore VIC-20, spectrum = Sinclair ZX Spectrum, "
|
||||
"a5200 = Atari 5200, a7800 = Atari 7800, "
|
||||
"c128 = Commodore 128 VDC 80-column, "
|
||||
"c16 = Commodore 16 TED hires, "
|
||||
"plus4 = Commodore Plus/4 TED hires, "
|
||||
"cpc = Amstrad CPC, "
|
||||
"coco3 = Tandy CoCo 3 GIME, "
|
||||
"nes = Nintendo NES/Famicom, "
|
||||
"iigs = Apple IIGS Super Hi-Res, "
|
||||
"pet2001/pet4032 = 40-col PET/CBM, "
|
||||
"pet8032/superpet = 80-col PET/CBM, "
|
||||
"sms = Sega Master System, "
|
||||
"amiga = Commodore Amiga)")
|
||||
p.add_argument("-m", "--mode", default="auto",
|
||||
choices=["auto", *MODES, "gr15", "gr9", "gr8", "gr15dli",
|
||||
"hgr_mono", "dhgr", "hgr_color", "gm2",
|
||||
"pmode4", "pmode3",
|
||||
"mode0", "mode1", "mode2", "mode5", "stic", "pf_il",
|
||||
"c160", "color", "hicolor", "gr16", "gr4", "bg",
|
||||
"shr", "lowres", "80x25", "80x50"],
|
||||
help="display mode (c64: hires/multicolor/fli/interlace/mono; "
|
||||
"atari: gr15/gr9/gr8/gr15dli; apple: hgr_mono/dhgr; "
|
||||
"ti99: gm2; coco: pmode4/pmode3; bbc: mode0/1/2/5; "
|
||||
"intv: stic; ansi: 80x25/80x50)")
|
||||
p.add_argument("-f", "--format", default=None,
|
||||
choices=["d64", "d71", "d81", "crt", "atr", "car", "ccc",
|
||||
"ssd", "col", "a26", "int", "a0", "sna", "a52", "a78",
|
||||
"prg", "nes", "sms", "adf", "ans"],
|
||||
help="output format: c64 disk d64/d71/d81 or cartridge crt; "
|
||||
"atari disk atr or cartridge car (default: from -o ext)")
|
||||
p.add_argument("-p", "--palette", default="colodore",
|
||||
choices=["colodore", "pepto"])
|
||||
p.add_argument("-d", "--dither", default=None,
|
||||
choices=["bayer", "bluenoise", "yliluoma", "floyd",
|
||||
"atkinson", "stucki", "jarvis", "sierra",
|
||||
"sierra_lite", "burkes", "riemersma",
|
||||
"ostromoukhov", "none"],
|
||||
help="dithering (default: atkinson; none for intv -- "
|
||||
"per-platform defaults that suit each format)")
|
||||
p.add_argument("--mono-base", default="grayscale",
|
||||
choices=["grayscale", *COLOR_NAMES],
|
||||
help="base colour for 'mono' mode (default greyscale)")
|
||||
p.add_argument("-a", "--aspect", default="fit",
|
||||
choices=["fit", "fill", "stretch"])
|
||||
p.add_argument("--video", default="pal", choices=["pal", "ntsc"],
|
||||
help="target video standard (affects the FLI viewer timing)")
|
||||
p.add_argument("--intensive", action="store_true",
|
||||
help="exhaustive background search + slower, higher-quality passes")
|
||||
p.add_argument("--brightness", type=float, default=1.0)
|
||||
p.add_argument("--contrast", type=float, default=1.0)
|
||||
p.add_argument("--saturation", type=float, default=1.0)
|
||||
p.add_argument("--gamma", type=float, default=1.0)
|
||||
p.add_argument("--tint", type=float, default=0.0,
|
||||
help="hue rotation in degrees (-180..180)")
|
||||
p.add_argument("--red", type=float, default=1.0, help="red level (gain)")
|
||||
p.add_argument("--green", type=float, default=1.0, help="green level (gain)")
|
||||
p.add_argument("--blue", type=float, default=1.0, help="blue level (gain)")
|
||||
p.add_argument("--preview", help="also write a PNG preview to this path")
|
||||
p.add_argument("--disk-name", default=None, help="disk + viewer name (PETSCII)")
|
||||
p.add_argument("--display", default="key",
|
||||
choices=["forever", "key", "seconds"],
|
||||
help="how long the viewer holds the picture (C64): forever, "
|
||||
"until a key, or --seconds N then exit to BASIC")
|
||||
p.add_argument("--seconds", type=int, default=10,
|
||||
help="seconds to display when --display seconds")
|
||||
p.add_argument("--viewer", default="unified",
|
||||
choices=["unified", "separate"],
|
||||
help="unified = one self-contained file; separate = viewer "
|
||||
"binary + a standalone 'data' image file (C64 disk)")
|
||||
return p
|
||||
|
||||
|
||||
def main(argv=None) -> int:
|
||||
args = build_parser().parse_args(argv)
|
||||
prep = imageprep.PrepOptions(
|
||||
aspect=args.aspect, brightness=args.brightness, contrast=args.contrast,
|
||||
saturation=args.saturation, gamma=args.gamma,
|
||||
tint=args.tint, red=args.red, green=args.green, blue=args.blue,
|
||||
)
|
||||
from . import platforms
|
||||
# Atkinson is the default dither (only used when -d is not given): its lighter
|
||||
# error diffusion bleeds less across constrained colour cells and keeps clean
|
||||
# local contrast. The one exception is the Intellivision, whose 64-tile GRAM
|
||||
# dictionary bloats under any diffusion, so it defaults to no dithering.
|
||||
_def_dither = {"intv": "none"}
|
||||
dither = args.dither or _def_dither.get(args.platform, "atkinson")
|
||||
conv = platforms.convert(args.platform, args.image, args.mode, args.palette,
|
||||
dither, args.intensive, prep, args.mono_base)
|
||||
_dsz = len(conv.data) if isinstance(conv.data, (bytes, bytearray)) else None
|
||||
print(f"platform={args.platform} mode={conv.mode} mean dE={conv.error:.2f}"
|
||||
+ (f" data={_dsz}B" if _dsz is not None else ""))
|
||||
|
||||
if args.preview:
|
||||
rgb = render_preview(conv, args.palette, scale=2)
|
||||
Image.fromarray(rgb, "RGB").save(args.preview)
|
||||
print(f"wrote preview {args.preview}")
|
||||
|
||||
if args.output:
|
||||
path = platforms.export(args.platform, conv, args.output, args.format,
|
||||
args.image, args.video, display=args.display,
|
||||
seconds=args.seconds, layout=args.viewer)
|
||||
kind = "ANSI art" if args.platform == "ansi" else "disk image"
|
||||
print(f"wrote {kind} {path}")
|
||||
|
||||
if not args.output and not args.preview:
|
||||
print("nothing to do: pass -o DISK and/or --preview PNG", file=sys.stderr)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue