"""ZX Spectrum monochrome / tinted-mono mode. 256x192 matched by luminance. The Spectrum has no grey, so greyscale is a 2-level black/white halftone (bright black + bright white) -- which at 256x192 with dithering is a crisp, high-detail image free of attribute clash. A base colour gives a 3-level tinted ramp (black -> colour -> white), all within one brightness group so the per-cell BRIGHT constraint is satisfied. Reuses the hires packing. """ from __future__ import annotations import numpy as np from ...convert import base from .. import palette as spal from . import hires def _ramp(base_color): """Luminance ramp kept inside ONE brightness group (shared BRIGHT bit).""" if base_color is None: return [8, 15] # bright black + bright white c = base_color & 7 # base hue 0-7 if c in (0, 7): return [8, 15] return [8, 8 | c, 15] # bright black, bright hue, bright white def convert(img_rgb, palette_name="spectrum", dither_mode="atkinson", intensive=False, base_color=None): plab = spal.palette_lab() prgb = spal.get_palette().astype(np.uint8) ramp = _ramp(base_color) idx, sets, rows, cols, err = base.mono_render( img_rgb, plab, ramp, hires.WIDTH, hires.HEIGHT, hires.CELL_W, hires.CELL_H, dither_mode, n_free=2) scr = hires._encode(idx, sets, rows, cols) return base.Conversion( mode="mono", width=hires.WIDTH, height=hires.HEIGHT, pixel_aspect=hires.PIXEL_ASPECT, index_image=idx.astype(np.uint16), data=bytes(scr), data_addr=0x4000, viewer="spectrum", preview_rgb=prgb[idx], error=err, meta={"palette": "spectrum", "dither": dither_mode, "base_color": base_color}, )