"""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