"""Atari GR.9 (GTIA): 80x192, 16 luminance shades of one hue. Excellent greyscale (hue 0) or tinted monochrome (any of 16 hues) -- 16 real shades, not just dithered. ``base_color`` selects the hue (0..15); None = grey. """ from __future__ import annotations import numpy as np from ...convert.base import Conversion, perceptual_error from .. import palette as apal from . import _common WIDTH, HEIGHT = 80, 192 PIXEL_ASPECT = 4.0 def convert(img_rgb, palette_name="ntsc", dither_mode="floyd", intensive=False, base_color=None): hue = 0 if base_color is None else (int(base_color) & 0x0F) plab = apal.palette_lab(palette_name) prgb = apal.get_palette(palette_name).astype(np.uint8) img_mono, plab_mono = _common.luminance_lab(img_rgb, plab) ramp = apal.hue_ramp(hue) # 16 register values of this hue idx = _common.quantize_global(img_mono, plab_mono, ramp, dither_mode) val = (idx & 0x0F).astype(np.uint8) # GR.9 pixel = 4-bit luminance # GTIA mode 9 takes the hue from COLBK and the luminance from each pixel. A # COLBK of exactly $00, though, blanks the whole playfield to black -- the # register must be non-zero to enable the display. For a tinted hue that is # automatic ((hue<<4) != 0); for greyscale (hue 0) force a non-zero luminance # nibble, which the mode ignores for output (luminance still comes from the # pixels) but which switches the 16-shade display on. colbk = (hue & 0x0F) << 4 if colbk == 0: colbk = 0x0E data = _common.split_screen(_common.pack_4bpp(val)) + bytes([colbk]) preview = np.repeat(prgb[idx], int(PIXEL_ASPECT), axis=1) return Conversion( mode="gr9", width=WIDTH, height=HEIGHT, pixel_aspect=PIXEL_ASPECT, index_image=idx.astype(np.uint16), data=data, data_addr=_common.DATA_ADDR, viewer="gr9", preview_rgb=preview, error=perceptual_error(idx, img_mono, plab_mono), meta={"palette": palette_name, "dither": dither_mode, "hue": hue}, )