diff --git a/tools/pixelize_candidates.py b/tools/pixelize_candidates.py index 935297d3..0244cd2b 100644 --- a/tools/pixelize_candidates.py +++ b/tools/pixelize_candidates.py @@ -13,7 +13,7 @@ v3 演进(2026-09-27 用户四问题:脏点/模糊/信息丢失/白边): """ import sys, os from collections import deque, Counter -from PIL import Image +from PIL import Image, ImageFilter SIZES = {"goblin": 96, "giant_spider": 96, "hook_horror": 96, "bugbear": 128, "duergar": 128, @@ -237,16 +237,74 @@ def crop_content(im, pad=2): r, b = min(im.width, r + pad), min(im.height, b + pad) return im.crop((l, t, r, b)) +def despeckle(spr, min_px=10): + """删小于 min_px 的不透明孤立连通域(噪点)。""" + w, hgt = spr.size + pa = spr.load() + seen = [[False] * w for _ in range(hgt)] + for y0 in range(hgt): + for x0 in range(w): + if seen[y0][x0] or pa[x0, y0][3] == 0: + continue + comp = [] + q = deque([(x0, y0)]) + seen[y0][x0] = True + while q: + x, y = q.popleft() + comp.append((x, y)) + for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)): + nx, ny = x + dx, y + dy + if 0 <= nx < w and 0 <= ny < hgt and not seen[ny][nx] and pa[nx, ny][3] != 0: + seen[ny][nx] = True + q.append((nx, ny)) + if len(comp) < min_px: + for x, y in comp: + pa[x, y] = (0, 0, 0, 0) + return spr + +def edge_inpaint(spr, passes=2): + """边缘色内插:与透明相邻的不透明像素改色为其内部邻居均值(灭白边,不削轮廓)。""" + w, hgt = spr.size + for _ in range(passes): + pa = spr.load() + fixes = [] + for y in range(hgt): + for x in range(w): + c = pa[x, y] + if c[3] == 0: + continue + inner, boundary = [], False + for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1), (1, 1), (1, -1), (-1, 1), (-1, -1)): + nx, ny = x + dx, y + dy + if not (0 <= nx < w and 0 <= ny < hgt): + boundary = True + continue + nc = pa[nx, ny] + if nc[3] == 0: + boundary = True + elif all(pa[nx + ddx, ny + ddy][3] != 0 + for ddx, ddy in ((1, 0), (-1, 0), (0, 1), (0, -1)) + if 0 <= nx + ddx < w and 0 <= ny + ddy < hgt): + inner.append(nc) + if boundary and inner: + fixes.append((x, y, tuple(sum(ch[i] for ch in inner) // len(inner) for i in range(3)))) + for x, y, c in fixes: + r, g, b, a = pa[x, y] + pa[x, y] = (c[0], c[1], c[2], a) + return spr + def pixelize(im, target): - """BOX 预滤到 4x → 量化 → NEAREST 到 1x(最长边=档位值)。""" + """BOX 预滤到 4x(α腐蚀2px灭混合带) → 量化 → NEAREST → 降噪点 → 边缘内插。""" src = crop_content(im) scale = target * 4 / max(src.size) pre = src.resize((max(1, round(src.width * scale)), max(1, round(src.height * scale))), Image.BOX) rgb = pre.convert("RGB") flat = rgb.quantize(QUANT_COLORS, method=Image.MEDIANCUT).convert("RGB") out = flat.resize((max(1, round(pre.width / 4)), max(1, round(pre.height / 4))), Image.NEAREST) - a = pre.getchannel("A").resize(out.size, Image.NEAREST).point(lambda v: 255 if v > 128 else 0) + a = pre.getchannel("A").filter(ImageFilter.MinFilter(3)).resize(out.size, Image.NEAREST).point(lambda v: 255 if v > 128 else 0) out.putalpha(a) + despeckle(out) + edge_inpaint(out) return out def strip(spr, path):