First public commit.
This commit is contained in:
parent
2a48f52979
commit
4bac9d83ed
288 changed files with 18417 additions and 1076 deletions
19
lenser/c16/convert/__init__.py
Normal file
19
lenser/c16/convert/__init__.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
"""Commodore 16 (TED) conversion dispatch."""
|
||||
from __future__ import annotations
|
||||
|
||||
from ... import imageprep
|
||||
from . import hires, mono
|
||||
|
||||
_MODULES = {"hires": hires, "mono": mono}
|
||||
MODES = list(_MODULES.keys())
|
||||
|
||||
|
||||
def convert_image(path_or_img, mode="hires", palette_name="ted",
|
||||
dither_mode="floyd", intensive=False, prep_opt=None,
|
||||
base_color=None):
|
||||
prep_opt = prep_opt or imageprep.PrepOptions()
|
||||
module = _MODULES.get(mode, hires)
|
||||
img_rgb = imageprep.prepare(path_or_img, module.WIDTH, module.HEIGHT,
|
||||
module.PIXEL_ASPECT, prep_opt, border_rgb=(0, 0, 0))
|
||||
return module.convert(img_rgb, palette_name, dither_mode, intensive,
|
||||
base_color=base_color)
|
||||
93
lenser/c16/convert/hires.py
Normal file
93
lenser/c16/convert/hires.py
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
"""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
|
||||
35
lenser/c16/convert/mono.py
Normal file
35
lenser/c16/convert/mono.py
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
"""C16 TED monochrome: 320x200, restricted to the TED's neutral grey ramp (8
|
||||
luminance levels of hue 1, plus black) for smooth greyscale. ``--mono-base``
|
||||
tints it toward one hue (black -> hue -> white). Two greys per 8x8 cell, packed
|
||||
into the same TED hires layout as the colour mode."""
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
|
||||
from ...convert import base
|
||||
from .. import palette as tedpal
|
||||
from . import hires
|
||||
|
||||
WIDTH, HEIGHT = hires.WIDTH, hires.HEIGHT
|
||||
CELL_W, CELL_H = hires.CELL_W, hires.CELL_H
|
||||
PIXEL_ASPECT = hires.PIXEL_ASPECT
|
||||
|
||||
|
||||
def convert(img_rgb, palette_name="ted", dither_mode="floyd",
|
||||
intensive=False, base_color=None):
|
||||
plab = tedpal.palette_lab()
|
||||
prgb = tedpal.get_palette().astype(np.uint8)
|
||||
|
||||
ramp = base.luminance_ramp(plab, tedpal.GREYS, base_color)
|
||||
idx, sets, rows, cols, err = base.mono_render(
|
||||
img_rgb, plab, ramp, WIDTH, HEIGHT, CELL_W, CELL_H, dither_mode, n_free=2)
|
||||
|
||||
bitmap, attr, ch = hires._encode(idx, sets, rows, cols)
|
||||
payload = bytes(bitmap) + bytes(attr) + bytes(ch)
|
||||
|
||||
return base.Conversion(
|
||||
mode="mono", width=WIDTH, height=HEIGHT, pixel_aspect=PIXEL_ASPECT,
|
||||
index_image=idx.astype(np.uint16), data=payload, viewer="c16",
|
||||
preview_rgb=prgb[idx], error=err,
|
||||
meta={"palette": "ted", "dither": dither_mode, "border": 0},
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue