First public commit.

This commit is contained in:
The Dust Council 2026-07-03 19:35:35 -07:00
parent 2a48f52979
commit 4bac9d83ed
288 changed files with 18417 additions and 1076 deletions

107
lenser/apple/palette.py Normal file
View file

@ -0,0 +1,107 @@
"""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)