"""Apple II colour palettes and the HGR memory-layout helper. - HGR mono: black/white (the 280x192 1-bit bitmap displayed as monochrome). - HGR colour: 6 NTSC "artifact" colours (added later). - DHGR: 16 colours (added later). """ from __future__ import annotations import numpy as np from ..palette import srgb_to_lab # Monochrome (white phosphor) pair. MONO = np.array([(0, 0, 0), (255, 255, 255)], dtype=np.float64) # Double-Hi-Res 16-colour palette, indexed by the 4-bit value -- measured from # MAME's apple2ee DHGR output so the encoder's nibble values map to exactly what # the //e displays. DHGR16 = np.array([ (0x00, 0x00, 0x00), # 0 black (0x40, 0x1c, 0xf7), # 1 blue (0x00, 0x74, 0x40), # 2 dark green (0x19, 0x90, 0xff), # 3 medium blue (0x40, 0x63, 0x00), # 4 olive / dark green (0x80, 0x80, 0x80), # 5 grey (0x19, 0xd7, 0x00), # 6 green (0x58, 0xf4, 0xbf), # 7 aqua (0xa7, 0x0b, 0x40), # 8 dark red / magenta (0xe6, 0x28, 0xff), # 9 magenta / violet (0x80, 0x80, 0x80), # 10 grey (0xbf, 0x9c, 0xff), # 11 lavender (0xe6, 0x6f, 0x00), # 12 orange (0xff, 0x8b, 0xbf), # 13 pink (0xbf, 0xe3, 0x08), # 14 yellow-green (0xff, 0xff, 0xff), # 15 white ], dtype=np.float64) # HGR NTSC "artifact" colours. Per 7-pixel byte a palette bit selects one of two # colour pairs; the displayed colour of an "on" pixel also depends on its column # parity (and two adjacent on-pixels read as white). # palette 0: even column -> violet, odd column -> green # palette 1: even column -> blue, odd column -> orange HGR_BLACK, HGR_VIOLET, HGR_GREEN, HGR_WHITE, HGR_BLUE, HGR_ORANGE = 0, 1, 2, 3, 4, 5 HGR6 = np.array([ (0x00, 0x00, 0x00), # black (0xd0, 0x3a, 0xff), # violet (0x20, 0xc8, 0x00), # green (0xff, 0xff, 0xff), # white (0x20, 0x9a, 0xff), # blue (0xff, 0x6a, 0x20), # orange ], dtype=np.float64) def mono_lab() -> np.ndarray: return srgb_to_lab(MONO) def hgr6_lab() -> np.ndarray: return srgb_to_lab(HGR6) def pack_hgr_color(bits280: np.ndarray, pal_byte: np.ndarray) -> bytes: """280x192 mono bits + (192x40) per-byte palette bit -> 8192 HGR buffer.""" buf = bytearray(0x2000) H = bits280.shape[0] for y in range(H): base = hgr_row_addr(y) row = bits280[y] for bx in range(40): b = 0 for i in range(7): if row[bx * 7 + i]: b |= (1 << i) if pal_byte[y, bx]: b |= 0x80 buf[base + bx] = b return bytes(buf) def dhgr_lab() -> np.ndarray: return srgb_to_lab(DHGR16) def hgr_row_addr(y: int) -> int: """Offset (from $2000) of HGR row ``y`` (0..191) in the interleaved layout.""" return (y & 7) * 0x400 + ((y >> 3) & 7) * 0x80 + (y >> 6) * 0x28 def pack_hgr_mono(val_image: np.ndarray) -> bytes: """280x192 1-bit image -> 8192-byte HGR page 1 buffer. 7 pixels per byte, bit 0 = leftmost, bit 7 (palette bit) = 0 for mono. """ buf = bytearray(0x2000) H, W = val_image.shape for y in range(H): base = hgr_row_addr(y) row = val_image[y] for bx in range(40): b = 0 for i in range(7): if row[bx * 7 + i]: b |= (1 << i) buf[base + bx] = b return bytes(buf)