"""Commodore VIC-20 monochrome / tinted-mono mode. 176x184 hi-res matched by luminance: a single global background (black) and one foreground colour (white, or a tinted base) used by every cell, so the picture is carried entirely by the custom character shapes + dithering -- a clean two-tone image with no per-cell colour budget to spend. Reuses the hires char clustering, data layout and viewer. """ from __future__ import annotations import numpy as np from ... import palette as c64pal from ...convert import base from .. import palette as vpal from . import hires WIDTH, HEIGHT, PIXEL_ASPECT = hires.WIDTH, hires.HEIGHT, hires.PIXEL_ASPECT def convert(img_rgb, palette_name="vic", dither_mode="floyd", intensive=False, base_color=None): plab = vpal.palette_lab() prgb = vpal.get_palette().astype(np.uint8) bg = 0 # black background fg = base_color if base_color in range(1, 8) else 1 # white (or tinted) ramp = sorted([bg, fg], key=lambda i: plab[i, 0]) idx, sets, rows, cols, err = base.mono_render( img_rgb, plab, ramp, hires.WIDTH, hires.HEIGHT, hires.CELL_W, hires.CELL_H, dither_mode, n_free=2) # cell bitmaps (1 = foreground) reduced to the 256-char dictionary by a # frequency codebook -- keeps the dithered detail (k-means centroids would # invent near-solid 'average' chars -> block artefacts). bitmaps = np.zeros((hires.N_CELLS, 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) chars, labels = base.mono_codebook(bitmaps, hires.N_CHARS) img_mono = np.zeros((hires.HEIGHT, hires.WIDTH, 3)) img_mono[..., 0] = c64pal.srgb_to_lab(img_rgb)[..., 0] plab_mono = np.zeros_like(plab) plab_mono[:, 0] = plab[:, 0] prev_idx = np.empty((hires.HEIGHT, hires.WIDTH), np.uint8) screen = np.zeros(hires.N_CELLS, np.uint8) color = np.zeros(hires.N_CELLS, np.uint8) for ci in range(hires.N_CELLS): cr, cc = divmod(ci, cols) ch = chars[labels[ci]].reshape(8, 8) prev_idx[cr * 8:cr * 8 + 8, cc * 8:cc * 8 + 8] = np.where(ch == 1, fg, bg) screen[ci] = labels[ci] & 0xFF color[ci] = fg & 0x07 chardata = np.zeros(hires.N_CHARS * 8, np.uint8) for t in range(hires.N_CHARS): rb = chars[t].reshape(8, 8) for r in range(8): byte = 0 for x in range(8): byte = (byte << 1) | int(rb[r, x]) chardata[t * 8 + r] = byte data = {"chardata": chardata, "screen": screen, "color": color, "bg": int(bg), "border": 0, "aux": 0} preview = prgb[prev_idx] disp_w = int(round(hires.WIDTH * hires.PIXEL_ASPECT)) xs = (np.arange(disp_w) * hires.WIDTH) // disp_w preview = preview[:, xs] return base.Conversion( mode="mono", width=hires.WIDTH, height=hires.HEIGHT, pixel_aspect=hires.PIXEL_ASPECT, index_image=prev_idx.astype(np.uint16), data=data, data_addr=0, viewer="hires", preview_rgb=preview, error=base.perceptual_error(prev_idx, img_mono, plab_mono), meta={"palette": "vic", "dither": dither_mode, "base_color": base_color}, )