"""TRS-80 Color Computer MC6847 VDG palette + pixel packing. PMODE 4 (256x192) is 2-colour: black + the foreground of the selected colour set (CSS=0 green, CSS=1 "buff" ~ off-white). We use CSS=1 (buff on black) for clean monochrome, like Apple HGR mono. """ from __future__ import annotations import numpy as np from ..palette import srgb_to_lab # Approximate sRGB for the MC6847 colours. BLACK = (0, 0, 0) BUFF = (255, 255, 255) GREEN = (38, 194, 64) YELLOW = (255, 240, 112) BLUE = (40, 62, 211) RED = (180, 38, 40) CYAN = (52, 198, 160) MAGENTA = (200, 70, 180) ORANGE = (224, 116, 36) # PMODE 4 monochrome (CSS=1): index 0 = black, 1 = buff. MONO = np.array([BLACK, BUFF], dtype=np.float64) # PMODE 3 (CG6) 4-colour sets, selected by the CSS bit. The 2-bit pixel value # 0..3 indexes the set. VDG byte ($FF22) high nibble = E (A/G,GM2,GM1=1, GM0=0). PMODE3_SETS = { 0xE0: np.array([GREEN, YELLOW, BLUE, RED], dtype=np.float64), # CSS=0 0xE8: np.array([BUFF, CYAN, MAGENTA, ORANGE], dtype=np.float64), # CSS=1 } def mono_lab() -> np.ndarray: return srgb_to_lab(MONO) def pack_pmode3(val: np.ndarray) -> bytes: """Pack a (192,128) 0..3 array into 6144 bytes, 4 pixels/byte (2bpp), leftmost pixel in the high bits.""" h, w = val.shape out = bytearray(w // 4 * h) k = 0 for y in range(h): row = val[y] for x in range(0, w, 4): out[k] = ((row[x] & 3) << 6) | ((row[x + 1] & 3) << 4) | \ ((row[x + 2] & 3) << 2) | (row[x + 3] & 3) k += 1 return bytes(out) def pack_pmode4(idx: np.ndarray) -> bytes: """Pack a (192,256) 0/1 array into 6144 bytes, 8 pixels/byte, bit7 leftmost, 1 = foreground (the VDG reads this straight from video RAM).""" h, w = idx.shape out = bytearray(w // 8 * h) k = 0 for y in range(h): row = idx[y] for x in range(0, w, 8): b = 0 for i in range(8): b = (b << 1) | (1 if row[x + i] else 0) out[k] = b k += 1 return bytes(out)