Working Python version for Commodore.
This commit is contained in:
commit
2a48f52979
51 changed files with 3095 additions and 0 deletions
151
c64view/convert/ifli.py
Normal file
151
c64view/convert/ifli.py
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
"""Interlace mode: two multicolor frames shown on alternating fields (50Hz each).
|
||||
|
||||
The eye averages the two frames, so each pixel can show the blend of its colour
|
||||
in frame A and frame B -- up to ~136 distinct apparent colours (16 base + 120
|
||||
pairs). Frame A is an ordinary multicolor conversion; frame B targets the
|
||||
*residual* (2*target - A) so that (A+B)/2 reconstructs the original. Both frames
|
||||
share the global background and the colour-RAM colour per cell (the only VIC state
|
||||
the viewer cannot cheaply swap per frame), and differ in bitmap + screen RAM.
|
||||
|
||||
Memory layout of the appended data (loads from $2000), matched to viewer/interlace.s:
|
||||
$2000 bitmap A 8000 (bank 0, VIC reads here)
|
||||
$3F40 screen A 1000 (copied to $0400)
|
||||
$4400 screen B 1000 (bank 1 video matrix, in place)
|
||||
$6000 bitmap B 8000 (bank 1, VIC reads here)
|
||||
$8000 colour RAM 1000 (shared, copied to $D800)
|
||||
$83E8 background 1
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from itertools import combinations
|
||||
|
||||
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_ADDR = 0x2000
|
||||
N_COLS, N_ROWS = 40, 25
|
||||
N_CELLS = N_COLS * N_ROWS
|
||||
|
||||
|
||||
def convert(img_rgb, palette_name="colodore", dither_mode="bayer", intensive=False):
|
||||
plab = pal.palette_lab(palette_name)
|
||||
prgb = pal.get_palette(palette_name)
|
||||
img_lab = pal.srgb_to_lab(img_rgb)
|
||||
|
||||
# ---- frame A: ordinary multicolor (bg + 3 free per cell) ----
|
||||
cellsA, _, _ = base.cells_lab(img_lab, CELL_W, CELL_H)
|
||||
distA = base.cell_distance(cellsA, plab)
|
||||
if intensive:
|
||||
bg, setsA, _ = base.optimize_background(distA, n_free=3)
|
||||
else:
|
||||
bg = base.best_global_color(img_lab, plab)
|
||||
avail = [i for i in range(16) if i != bg]
|
||||
setsA, _ = base.select_cell_sets(distA, avail, n_free=3, fixed=(bg,))
|
||||
# colour-RAM colour (shared by both frames) = third free colour of A.
|
||||
c11 = setsA[:, 3].astype(np.int64)
|
||||
|
||||
allowedA = base.per_pixel_allowed(setsA, N_ROWS, N_COLS, CELL_W, CELL_H, HEIGHT, WIDTH)
|
||||
idxA = dither.quantize(img_lab, allowedA, plab, dither_mode).astype(np.uint8)
|
||||
|
||||
# ---- frame B: match residual 2*target - A in linear light ----
|
||||
lin_target = pal.srgb_to_linear(img_rgb)
|
||||
lin_A = pal.srgb_to_linear(prgb[idxA])
|
||||
resid = np.clip(2.0 * lin_target - lin_A, 0.0, 1.0)
|
||||
resid_srgb = pal.linear_to_srgb(resid)
|
||||
resid_lab = pal.srgb_to_lab(resid_srgb)
|
||||
|
||||
setsB = _solve_frameB(resid_lab, plab, bg, c11)
|
||||
allowedB = base.per_pixel_allowed(setsB, N_ROWS, N_COLS, CELL_W, CELL_H, HEIGHT, WIDTH)
|
||||
idxB = dither.quantize(resid_lab, allowedB, plab, dither_mode).astype(np.uint8)
|
||||
|
||||
# ---- blended preview (linear average -> sRGB, widened to display aspect) ----
|
||||
blend_lin = (pal.srgb_to_linear(prgb[idxA]) + pal.srgb_to_linear(prgb[idxB])) / 2.0
|
||||
blend = pal.linear_to_srgb(blend_lin)
|
||||
preview = np.repeat(blend, int(round(PIXEL_ASPECT)), axis=1)
|
||||
blend_lab = pal.srgb_to_lab(blend)
|
||||
error = float(np.sqrt(np.sum((blend_lab - img_lab) ** 2, axis=-1)).mean())
|
||||
|
||||
data = _encode(idxA, idxB, setsA, setsB, bg, c11)
|
||||
|
||||
return base.Conversion(
|
||||
mode="interlace", width=WIDTH, height=HEIGHT, pixel_aspect=PIXEL_ASPECT,
|
||||
index_image=idxA, data=data, data_addr=DATA_ADDR, viewer="interlace",
|
||||
preview_rgb=preview, error=error,
|
||||
meta={"palette": palette_name, "dither": dither_mode, "background": int(bg)},
|
||||
)
|
||||
|
||||
|
||||
def _solve_frameB(resid_lab, plab, bg, c11):
|
||||
"""Per cell, pick the 2 free colours for frame B given shared {bg, c11[cell]}."""
|
||||
cells, _, _ = base.cells_lab(resid_lab, CELL_W, CELL_H)
|
||||
dist = base.cell_distance(cells, plab) # (n, P, 16)
|
||||
dbg = dist[:, :, bg] # (n, P)
|
||||
dc11 = np.take_along_axis(dist, c11[:, None, None], axis=2)[:, :, 0]
|
||||
sbase = np.minimum(dbg, dc11)
|
||||
|
||||
n = dist.shape[0]
|
||||
best = np.full(n, np.inf)
|
||||
b1 = np.zeros(n, dtype=np.int64)
|
||||
b2 = np.zeros(n, dtype=np.int64)
|
||||
for x, y in combinations(range(16), 2):
|
||||
e = np.minimum(np.minimum(sbase, dist[:, :, x]), dist[:, :, y]).sum(axis=1)
|
||||
better = e < best
|
||||
best = np.where(better, e, best)
|
||||
b1 = np.where(better, x, b1)
|
||||
b2 = np.where(better, y, b2)
|
||||
|
||||
bg_arr = np.full(n, bg, dtype=np.int64)
|
||||
return np.stack([bg_arr, b1, b2, c11], axis=1)
|
||||
|
||||
|
||||
def _pack_frame(index_image, screen_assign, colram_assign, bg, get_lut):
|
||||
"""Build (bitmap, screen) for one frame. ``get_lut`` maps cell index -> dict."""
|
||||
bitmap = np.zeros(8000, dtype=np.uint8)
|
||||
screen = np.zeros(1000, dtype=np.uint8)
|
||||
for cr in range(N_ROWS):
|
||||
for cc in range(N_COLS):
|
||||
ci = cr * N_COLS + cc
|
||||
hi, lo, lut = get_lut(ci)
|
||||
screen[ci] = ((hi & 0x0F) << 4) | (lo & 0x0F)
|
||||
base_addr = cr * 320 + cc * 8
|
||||
block = index_image[cr * 8:cr * 8 + 8, cc * 4:cc * 4 + 4]
|
||||
for r in range(8):
|
||||
byte = 0
|
||||
for x in range(4):
|
||||
byte = (byte << 2) | lut.get(int(block[r, x]), 0b00)
|
||||
bitmap[base_addr + r] = byte
|
||||
return bitmap, screen
|
||||
|
||||
|
||||
def _encode(idxA, idxB, setsA, setsB, bg, c11):
|
||||
def lutA(ci):
|
||||
cc11 = int(c11[ci])
|
||||
a01, a10 = int(setsA[ci, 1]), int(setsA[ci, 2])
|
||||
return a01, a10, {int(bg): 0b00, a01: 0b01, a10: 0b10, cc11: 0b11}
|
||||
|
||||
def lutB(ci):
|
||||
cc11 = int(c11[ci])
|
||||
b01, b10 = int(setsB[ci, 1]), int(setsB[ci, 2])
|
||||
return b01, b10, {int(bg): 0b00, b01: 0b01, b10: 0b10, cc11: 0b11}
|
||||
|
||||
bitmapA, screenA = _pack_frame(idxA, None, None, bg, lutA)
|
||||
bitmapB, screenB = _pack_frame(idxB, None, None, bg, lutB)
|
||||
colram = (c11 & 0x0F).astype(np.uint8)
|
||||
|
||||
block = bytearray()
|
||||
block += bytes(bitmapA) # $2000
|
||||
block += bytes(screenA) # $3F40
|
||||
block += bytes(0x4400 - (0x3F40 + 1000)) # pad to $4400
|
||||
block += bytes(screenB) # $4400
|
||||
block += bytes(0x6000 - (0x4400 + 1000)) # pad to $6000
|
||||
block += bytes(bitmapB) # $6000
|
||||
block += bytes(0x8000 - (0x6000 + 8000)) # pad to $8000
|
||||
block += bytes(colram) # $8000
|
||||
block += bytes([int(bg) & 0x0F]) # $83E8
|
||||
return bytes(block)
|
||||
Loading…
Add table
Add a link
Reference in a new issue