86 lines
3 KiB
Python
86 lines
3 KiB
Python
"""Conversion dispatch + preview rendering."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import numpy as np
|
|
|
|
from .. import imageprep, palette as pal
|
|
from . import base, hires, mono, multicolor
|
|
|
|
# mode name -> module
|
|
_MODULES = {
|
|
"hires": hires,
|
|
"multicolor": multicolor,
|
|
"mono": mono,
|
|
}
|
|
|
|
# Registered lazily so FLI/IFLI can be added without import cycles.
|
|
try:
|
|
from . import fli # noqa: E402
|
|
_MODULES["fli"] = fli
|
|
except Exception:
|
|
pass
|
|
try:
|
|
from . import ifli # noqa: E402
|
|
_MODULES["interlace"] = ifli
|
|
except Exception:
|
|
pass
|
|
|
|
MODES = list(_MODULES.keys())
|
|
|
|
|
|
def convert_image(path_or_img, mode="multicolor", palette_name="colodore",
|
|
dither_mode="bayer", intensive=False,
|
|
prep_opt: imageprep.PrepOptions | None = None,
|
|
base_color=None) -> base.Conversion:
|
|
"""Prepare an image for ``mode`` and convert it. ``mode='auto'`` tries every
|
|
standard mode and returns the lowest-error result. ``base_color`` (palette
|
|
index, or None for grayscale) only applies to the ``mono`` mode."""
|
|
prep_opt = prep_opt or imageprep.PrepOptions()
|
|
|
|
if mode == "auto":
|
|
best = None
|
|
for m in ("multicolor", "hires"):
|
|
c = convert_image(path_or_img, m, palette_name, dither_mode, intensive, prep_opt)
|
|
if best is None or c.error < best.error:
|
|
best = c
|
|
return best
|
|
|
|
module = _MODULES[mode]
|
|
border_rgb = pal.get_palette(palette_name)[prep_opt.border_index]
|
|
img_rgb = imageprep.prepare(
|
|
path_or_img, module.WIDTH, module.HEIGHT, module.PIXEL_ASPECT,
|
|
prep_opt, border_rgb=border_rgb,
|
|
)
|
|
if mode == "mono":
|
|
return module.convert(img_rgb, palette_name, dither_mode, intensive,
|
|
base_color=base_color)
|
|
return module.convert(img_rgb, palette_name, dither_mode, intensive)
|
|
|
|
|
|
def render_preview(conv: base.Conversion, palette_name="colodore",
|
|
scale: int = 2) -> np.ndarray:
|
|
"""Render the conversion's index image to a displayed-resolution RGB array.
|
|
|
|
Logical pixels are widened by the mode's pixel aspect (so multicolor pixels
|
|
are twice as wide), giving a uniform 320x200 base which is then integer-scaled.
|
|
|
|
NOTE: ``pixel_aspect`` is applied here only for the index-image fallback path.
|
|
A converter that supplies ``preview_rgb`` MUST pre-widen it to display
|
|
resolution itself (e.g. repeat columns by the pixel aspect); otherwise modes
|
|
with wide pixels render as a narrow sliver. atari/apple/a2600/intv do this.
|
|
"""
|
|
if conv.preview_rgb is not None:
|
|
rgb = conv.preview_rgb
|
|
if scale > 1:
|
|
rgb = np.repeat(np.repeat(rgb, scale, axis=0), scale, axis=1)
|
|
return rgb
|
|
|
|
prgb = pal.get_palette(palette_name).astype(np.uint8)
|
|
rgb = prgb[conv.index_image] # (H, W, 3)
|
|
xrep = int(round(conv.pixel_aspect))
|
|
if xrep > 1:
|
|
rgb = np.repeat(rgb, xrep, axis=1)
|
|
if scale > 1:
|
|
rgb = np.repeat(np.repeat(rgb, scale, axis=0), scale, axis=1)
|
|
return rgb
|