8bitlenser/lenser/cpc/palette.py
2026-07-03 19:35:35 -07:00

60 lines
2.4 KiB
Python

"""Amstrad CPC (Gate Array) colour palette.
The CPC has a fixed palette of 27 colours (3 levels -- 0x00, 0x60, 0xFF -- of
R, G and B). The Gate Array selects them by a 5-bit "hardware ink" number 0-31;
those 32 numbers map onto the 27 colours (a few duplicates). RGB values and the
hardware-ink ordering are taken verbatim from MAME's ``amstrad_palette[32]`` so
the encoder matches exactly what the emulator renders.
A pen (the value stored per pixel: 0-15 in mode 0, 0-3 in mode 1, 0-1 in mode 2)
is assigned an ink via the Gate Array palette; the .sna stores those 17 ink
numbers (16 pens + border). We therefore index colours by HARDWARE INK NUMBER,
so ``HW_INK[k]`` is both this palette's RGB and the byte written to the snapshot.
"""
from __future__ import annotations
import numpy as np
from ..palette import srgb_to_lab
# amstrad_palette[32] from MAME (src/mame/amstrad/amstrad_m.cpp), indexed by the
# Gate Array hardware ink number.
HW_INK = np.array([
(96, 96, 96), (96, 96, 96), (0, 255, 96), (255, 255, 96),
(0, 0, 96), (255, 0, 96), (0, 96, 96), (255, 96, 96),
(255, 0, 96), (255, 255, 96), (255, 255, 0), (255, 255, 255),
(255, 0, 0), (255, 0, 255), (255, 96, 0), (255, 96, 255),
(0, 0, 96), (0, 255, 96), (0, 255, 0), (0, 255, 255),
(0, 0, 0), (0, 0, 255), (0, 96, 0), (0, 96, 255),
(96, 0, 96), (96, 255, 96), (96, 255, 0), (96, 255, 255),
(96, 0, 0), (96, 0, 255), (96, 96, 0), (96, 96, 255),
], dtype=np.float64)
# The 27 unique colours, each as the FIRST hardware ink number that produces it
# (so encoders work with a clean 27-entry palette, and INK[i] gives the snapshot
# byte for unique colour i).
INK = []
_seen = {}
for _k, _rgb in enumerate(map(tuple, HW_INK.astype(int))):
if _rgb not in _seen:
_seen[_rgb] = _k
INK.append(_k)
INK = np.array(INK, dtype=np.int64) # 27 hardware ink numbers
PALETTE = HW_INK[INK] # 27 unique RGB colours
# Neutral grey ramp (for the monochrome mode): black, grey, bright white.
GREYS = [i for i, (r, g, b) in enumerate(map(tuple, PALETTE.astype(int)))
if r == g == b] # indices into PALETTE
def get_palette() -> np.ndarray:
return PALETTE
def palette_lab() -> np.ndarray:
return srgb_to_lab(PALETTE)
def ink_byte(unique_index: int) -> int:
"""Hardware ink number (snapshot byte) for a unique-palette colour index."""
return int(INK[unique_index])