93 lines
3.6 KiB
Python
93 lines
3.6 KiB
Python
"""C16 TED hires bitmap mode: 320x200, two colours per 8x8 cell.
|
|
|
|
Unlike the VIC-II, each of the two per-cell colours may be any of the TED's 128
|
|
colours. The colours are stored across two 1K matrices (see MAME's mos7360
|
|
draw_bitmap):
|
|
ch byte (video base + $400): high nibble = fg hue, low nibble = bg hue
|
|
attr byte (video base): bits 0-2 = fg luminance, bits 4-6 = bg lum
|
|
A bitmap bit of 1 selects the foreground colour, 0 the background.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import numpy as np
|
|
|
|
from ... import dither, palette as c64pal
|
|
from ...convert import base
|
|
from .. import palette as tedpal
|
|
|
|
WIDTH, HEIGHT = 320, 200
|
|
CELL_W, CELL_H = 8, 8
|
|
PIXEL_ASPECT = 1.0
|
|
|
|
|
|
def convert(img_rgb, palette_name="ted", dither_mode="floyd",
|
|
intensive=False, base_color=None):
|
|
plab = tedpal.palette_lab()
|
|
img_lab = c64pal.srgb_to_lab(img_rgb)
|
|
|
|
cells, rows, cols = base.cells_lab(img_lab, CELL_W, CELL_H)
|
|
dist = base.cell_distance(cells, plab)
|
|
sets = _select_pairs(dist)
|
|
|
|
allowed = base.per_pixel_allowed(sets, rows, cols, CELL_W, CELL_H, HEIGHT, WIDTH)
|
|
index_image = dither.quantize(img_lab, allowed, plab, dither_mode).astype(np.uint16)
|
|
|
|
bitmap, attr, ch = _encode(index_image, sets, rows, cols)
|
|
payload = bytes(bitmap) + bytes(attr) + bytes(ch)
|
|
|
|
prgb = tedpal.get_palette().astype(np.uint8)
|
|
return base.Conversion(
|
|
mode="hires", width=WIDTH, height=HEIGHT, pixel_aspect=PIXEL_ASPECT,
|
|
index_image=index_image, data=payload, viewer="c16",
|
|
preview_rgb=prgb[index_image],
|
|
error=base.perceptual_error(index_image, img_lab, plab),
|
|
meta={"palette": "ted", "dither": dither_mode, "border": 0},
|
|
)
|
|
|
|
|
|
def _select_pairs(dist, k=16):
|
|
"""Per-cell best 2 colours from the 128-colour TED palette.
|
|
|
|
A full C(128,2) search is 8128 combos/cell; instead we restrict each cell to
|
|
its ``k`` best single colours (the optimal pair is almost always among them)
|
|
and search only C(k,2) pairs -- ~100x faster with effectively identical
|
|
results. dist: (n_cells, P, 128) squared CIELAB distances.
|
|
"""
|
|
n_cells = dist.shape[0]
|
|
single = dist.sum(1) # (n_cells, 128)
|
|
cand = np.argsort(single, axis=1)[:, :k] # (n_cells, k)
|
|
dist_c = np.take_along_axis(dist, cand[:, None, :], axis=2) # (n_cells, P, k)
|
|
best_err = np.full(n_cells, np.inf)
|
|
best = np.zeros((n_cells, 2), dtype=np.int64)
|
|
for i in range(k):
|
|
for j in range(i + 1, k):
|
|
m = np.minimum(dist_c[:, :, i], dist_c[:, :, j]).sum(1)
|
|
upd = m < best_err
|
|
best_err[upd] = m[upd]
|
|
best[upd, 0] = cand[upd, i]
|
|
best[upd, 1] = cand[upd, j]
|
|
return best
|
|
|
|
|
|
def _encode(index_image, sets, rows, cols):
|
|
"""Return (bitmap 8000, attr 1000, ch 1000) for the TED hires layout."""
|
|
bitmap = np.zeros(8000, dtype=np.uint8)
|
|
attr = np.zeros(1000, dtype=np.uint8)
|
|
ch = np.zeros(1000, dtype=np.uint8)
|
|
for cr in range(rows):
|
|
for cc in range(cols):
|
|
ci = cr * cols + cc
|
|
bg, fg = int(sets[ci, 0]), int(sets[ci, 1])
|
|
fg_hue, fg_lum = fg & 0x0F, (fg >> 4) & 0x07
|
|
bg_hue, bg_lum = bg & 0x0F, (bg >> 4) & 0x07
|
|
ch[ci] = (fg_hue << 4) | bg_hue
|
|
attr[ci] = (bg_lum << 4) | fg_lum
|
|
base_addr = cr * 320 + cc * 8
|
|
block = index_image[cr * 8:cr * 8 + 8, cc * 8:cc * 8 + 8]
|
|
for r in range(8):
|
|
row = block[r]
|
|
byte = 0
|
|
for x in range(8):
|
|
byte = (byte << 1) | (1 if int(row[x]) == fg else 0)
|
|
bitmap[base_addr + r] = byte
|
|
return bitmap, attr, ch
|