First public commit.
This commit is contained in:
parent
2a48f52979
commit
4bac9d83ed
288 changed files with 18417 additions and 1076 deletions
87
lenser/intv/convert/mono.py
Normal file
87
lenser/intv/convert/mono.py
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
"""Mattel Intellivision monochrome / tinted-mono mode.
|
||||
|
||||
160x96 matched by luminance to two greys (black + white, or black + a tinted base
|
||||
colour). With the colours fixed, the whole image budget goes into the 64-tile
|
||||
GRAM shapes, which are optimised by the same vector-quantisation reshape/reassign
|
||||
used by the colour encoder -- a clean two-tone Intellivision picture.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
|
||||
from ... import dither, palette as c64pal
|
||||
from ...convert import base
|
||||
from .. import palette as ipal
|
||||
from . import stic
|
||||
|
||||
BLACK = 0
|
||||
WHITE = 7 # white is a foreground-legal colour (0-7)
|
||||
|
||||
|
||||
def convert(img_rgb, palette_name="stic", dither_mode="none",
|
||||
intensive=False, base_color=None):
|
||||
# mono is carried entirely by dithering, so it needs error diffusion -- the
|
||||
# colour mode's "none" default would give a stark, high-contrast result.
|
||||
if dither_mode not in base.DIFFUSION_DITHERS:
|
||||
dither_mode = "floyd"
|
||||
plab = ipal.palette_lab()
|
||||
prgb = ipal.get_palette().astype(np.uint8)
|
||||
fg = base_color if base_color in range(1, 8) else WHITE
|
||||
bg = BLACK
|
||||
|
||||
# luminance image + luminance-only palette
|
||||
L = c64pal.srgb_to_lab(img_rgb)[..., 0]
|
||||
img_mono = np.zeros((stic.HEIGHT, stic.WIDTH, 3))
|
||||
img_mono[..., 0] = L
|
||||
plab_mono = np.zeros_like(plab)
|
||||
plab_mono[:, 0] = plab[:, 0]
|
||||
|
||||
cells, rows, cols = base.cells_lab(img_mono, stic.CELL_W, stic.CELL_H)
|
||||
|
||||
sets = np.tile(np.array([bg, fg], np.int64), (stic.N_ROWS * stic.N_COLS, 1))
|
||||
allowed = base.per_pixel_allowed(sets, rows, cols, stic.CELL_W, stic.CELL_H,
|
||||
stic.HEIGHT, stic.WIDTH)
|
||||
idx = dither.quantize(img_mono, allowed, plab_mono, dither_mode).astype(np.uint8)
|
||||
|
||||
bitmaps = np.zeros((stic.N_ROWS * stic.N_COLS, 64), np.uint8)
|
||||
for cr in range(rows):
|
||||
for cc in range(cols):
|
||||
ci = cr * cols + cc
|
||||
block = idx[cr * 8:cr * 8 + 8, cc * 8:cc * 8 + 8]
|
||||
bitmaps[ci] = (block == fg).astype(np.uint8).reshape(-1)
|
||||
|
||||
tiles, labels = base.mono_codebook(bitmaps, stic.N_TILES)
|
||||
|
||||
prev_idx = np.empty((stic.HEIGHT, stic.WIDTH), np.uint8)
|
||||
cards = np.zeros(stic.N_ROWS * stic.N_COLS, np.uint16)
|
||||
for ci in range(stic.N_ROWS * stic.N_COLS):
|
||||
cr, cc = divmod(ci, cols)
|
||||
tile = tiles[labels[ci]].reshape(8, 8)
|
||||
prev_idx[cr * 8:cr * 8 + 8, cc * 8:cc * 8 + 8] = np.where(tile == 1, fg, bg)
|
||||
cards[ci] = (fg & 0x07) | stic.GRAM_BIT | ((labels[ci] & 0x3F) << 3) \
|
||||
| stic._bg_bits(bg)
|
||||
|
||||
gram = np.zeros(stic.N_TILES * 8, np.uint16)
|
||||
for t in range(stic.N_TILES):
|
||||
rb = tiles[t].reshape(8, 8)
|
||||
for r in range(8):
|
||||
byte = 0
|
||||
for x in range(8):
|
||||
byte = (byte << 1) | int(rb[r, x])
|
||||
gram[t * 8 + r] = byte
|
||||
|
||||
data = {"gram": gram, "cards": cards}
|
||||
preview = prgb[prev_idx]
|
||||
disp_w = int(round(stic.WIDTH * stic.PIXEL_ASPECT))
|
||||
xs = (np.arange(disp_w) * stic.WIDTH) // disp_w
|
||||
preview = preview[:, xs]
|
||||
|
||||
return base.Conversion(
|
||||
mode="mono", width=stic.WIDTH, height=stic.HEIGHT,
|
||||
pixel_aspect=stic.PIXEL_ASPECT, index_image=prev_idx.astype(np.uint16),
|
||||
data=data, data_addr=0, viewer="stic", preview_rgb=preview,
|
||||
error=base.perceptual_error(prev_idx, img_mono, plab_mono),
|
||||
meta={"palette": "stic", "dither": dither_mode, "base_color": base_color,
|
||||
"mode": "fgbg"},
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue