Working Python version for Commodore.

This commit is contained in:
The Dust Council 2026-06-14 17:43:12 -07:00
commit 2a48f52979
51 changed files with 3095 additions and 0 deletions

View file

@ -0,0 +1,80 @@
"""Multicolor bitmap mode ("Koala"): 160x200, one shared background plus three
freely chosen colours per 4x8 cell.
Data file layout (PRG, load $2000), matched to viewer/multicolor.s:
$2000 bitmap 8000 bytes (VIC reads here directly)
$3F40 screen RAM 1000 bytes (viewer copies to $0400)
$4328 colour RAM 1000 bytes (viewer copies to $D800)
$4710 background 1 byte (viewer writes to $D021)
"""
from __future__ import annotations
import numpy as np
from .. import dither, palette as pal
from . import base
WIDTH, HEIGHT = 160, 200
CELL_W, CELL_H = 4, 8
PIXEL_ASPECT = 2.0
DATA_LOAD = 0x2000
# bit-pair -> colour source: 01 screen hi nibble, 10 screen lo nibble, 11 colour RAM
_SLOT_BITS = {1: 0b01, 2: 0b10, 3: 0b11}
def convert(img_rgb: np.ndarray, palette_name="colodore",
dither_mode="bayer", intensive=False) -> base.Conversion:
plab = pal.palette_lab(palette_name)
img_lab = pal.srgb_to_lab(img_rgb)
cells, rows, cols = base.cells_lab(img_lab, CELL_W, CELL_H)
dist = base.cell_distance(cells, plab)
if intensive:
bg, sets, _ = base.optimize_background(dist, n_free=3)
else:
bg = base.best_global_color(img_lab, plab)
avail = [i for i in range(16) if i != bg]
sets, _ = base.select_cell_sets(dist, avail, n_free=3, fixed=(bg,))
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.uint8)
bitmap, screen, colram = _encode(index_image, sets, bg, rows, cols)
# This block also *is* a Koala body: bitmap, screen, colram, background.
payload = bytes(bitmap) + bytes(screen) + bytes(colram) + bytes([bg])
conv = base.Conversion(
mode="multicolor", width=WIDTH, height=HEIGHT, pixel_aspect=PIXEL_ASPECT,
index_image=index_image, data=payload, viewer="multicolor",
error=base.mean_error(index_image, img_lab, plab),
meta={"palette": palette_name, "dither": dither_mode, "background": bg},
)
# Standard "Koala Painter" file (load $6000) for use in other C64 art tools.
conv.extra_files = [("picture.koa", base.prg(0x6000, payload))]
return conv
def _encode(index_image, sets, bg, rows, cols):
bitmap = np.zeros(8000, dtype=np.uint8)
screen = np.zeros(1000, dtype=np.uint8)
colram = np.zeros(1000, dtype=np.uint8)
for cr in range(rows):
for cc in range(cols):
ci = cr * cols + cc
# sets[ci] = [bg, c1, c2, c3]; assign the three non-bg colours to slots.
c1, c2, c3 = int(sets[ci, 1]), int(sets[ci, 2]), int(sets[ci, 3])
screen[ci] = ((c1 & 0x0F) << 4) | (c2 & 0x0F)
colram[ci] = c3 & 0x0F
color_to_bits = {bg: 0b00, c1: 0b01, c2: 0b10, c3: 0b11}
base_addr = cr * 320 + cc * 8
block = index_image[cr * 8:cr * 8 + 8, cc * 4:cc * 4 + 4]
for r in range(8):
row = block[r]
byte = 0
for x in range(4):
byte = (byte << 2) | color_to_bits.get(int(row[x]), 0b00)
bitmap[base_addr + r] = byte
return bitmap, screen, colram