#!/usr/bin/env python3 """Generate multi-cell "box" sprites for CHROMA SOLSTICE. Each cell is 16x16. A single box has a 1px transparent margin on every edge (so a 14x14 body inside 16x16). For multi-cell blocks we keep that 1px margin only on the *outer* silhouette of the block -- edges that could touch other things in the level -- while cells that are part of the same block stay truly connected (no seam, no internal margin). The per-channel fill is the same concentric-ring format as the shipped box_r / box_g / box_b: a pixel is decided purely by k, its inset depth (chebyshev distance in from the nearest outside pixel, minus 1; k==0 is the margin ring and is always transparent). The three channels each leave their own gaps so the colors underneath stay visible when drawn additively. red ON: k in {1,2} or k>=5 (2px ring, 2px gap, solid core) green ON: k odd or k>=5 (alternating 1px rings, solid core) blue ON: k==1 or k>=4 (1px ring, 2px gap, solid core) Run from inside art/game/ : python3 gen_composite_boxes.py """ from itertools import product from collections import deque from PIL import Image CELL = 16 RULES = { 'r': lambda k: (1 <= k <= 2) or (k >= 5), 'g': lambda k: (k >= 5) or (k >= 1 and k % 2 == 1), 'b': lambda k: (k == 1) or (k >= 4), } def inset_grid(cells, W, H): """Return k[y][x] for the W*CELL x H*CELL pixel grid (-1 = outside).""" Wp, Hp = W * CELL, H * CELL occ = [[False] * Wp for _ in range(Hp)] for (cx, cy) in cells: for y in range(cy * CELL, cy * CELL + CELL): for x in range(cx * CELL, cx * CELL + CELL): occ[y][x] = True INF = 10 ** 9 dist = [[INF] * Wp for _ in range(Hp)] dq = deque() for y in range(Hp): for x in range(Wp): if not occ[y][x]: dist[y][x] = 0 dq.append((x, y)) while dq: # multi-source BFS with chebyshev (8-neighbour) steps x, y = dq.popleft() for dy in (-1, 0, 1): for dx in (-1, 0, 1): if dx == 0 and dy == 0: continue nx, ny = x + dx, y + dy if 0 <= nx < Wp and 0 <= ny < Hp and dist[ny][nx] > dist[y][x] + 1: dist[ny][nx] = dist[y][x] + 1 dq.append((nx, ny)) k = [[-1] * Wp for _ in range(Hp)] for y in range(Hp): for x in range(Wp): if occ[y][x]: # the region beyond the image edge counts as outside too edged = min(x + 1, Wp - x, y + 1, Hp - y) k[y][x] = min(dist[y][x], edged) - 1 return k def render(cells, W, H, channel): k = inset_grid(cells, W, H) rule = RULES[channel] img = Image.new('RGBA', (W * CELL, H * CELL), (0, 0, 0, 0)) px = img.load() for y in range(H * CELL): for x in range(W * CELL): if k[y][x] >= 0 and rule(k[y][x]): px[x, y] = (255, 255, 255, 255) return img def connected(cells): cells = set(cells) start = next(iter(cells)) seen = {start} stack = [start] while stack: x, y = stack.pop() for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)): n = (x + dx, y + dy) if n in cells and n not in seen: seen.add(n) stack.append(n) return len(seen) == len(cells) def enum(W, H): """All connected polyominoes whose bounding box is exactly W x H.""" coords = [(x, y) for y in range(H) for x in range(W)] out = [] for bits in range(1, 1 << (W * H)): cells = [coords[i] for i in range(W * H) if bits >> i & 1] if len(cells) < 2: continue xs = [c[0] for c in cells] ys = [c[1] for c in cells] if min(xs) != 0 or max(xs) != W - 1 or min(ys) != 0 or max(ys) != H - 1: continue if not connected(cells): continue out.append(cells) return out def mask_name(cells, W, H): rows = [['0'] * W for _ in range(H)] for x, y in cells: rows[y][x] = '1' return '-'.join(''.join(r) for r in rows) BBOXES = [(1, 2), (2, 1), (2, 2), (3, 2), (2, 3)] if __name__ == '__main__': total = 0 for (W, H) in BBOXES: for cells in enum(W, H): mask = mask_name(cells, W, H) for ch in 'rgb': fn = 'box_%dx%d_%s_%s.png' % (W, H, mask, ch) render(cells, W, H, ch).save(fn) total += 1 print('wrote %d files' % total)