From ae7528f34b2f50faed1e73e2c17db97055144c74 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Sun, 27 Sep 2026 03:18:11 +0800 Subject: [PATCH] =?UTF-8?q?tools:=20=E5=83=8F=E7=B4=A0=E5=8C=96=20v5=3D?= =?UTF-8?q?=E7=99=BD=E8=BE=B9/=E5=99=AA=E7=82=B9=E4=B8=89=E8=BF=9E?= =?UTF-8?q?=E9=98=B2=E7=BA=BF=E2=80=94=E2=80=94=E9=A2=84=E6=BB=A4=CE=B1?= =?UTF-8?q?=E8=85=90=E8=9A=80MinFilter(3)=E7=81=AD=E6=B7=B7=E5=90=88?= =?UTF-8?q?=E5=B8=A6=E7=8E=AF(5=E6=9B=BE=E5=95=83=E6=96=AD=E9=92=A9?= =?UTF-8?q?=E7=88=AA=E6=81=90=E9=AD=94=E7=BB=86=E8=82=A2=E5=9B=9E=E9=80=80?= =?UTF-8?q?)+despeckle<10px=E5=AD=A4=E7=AB=8B=E5=9F=9F+edge=5Finpaint?= =?UTF-8?q?=E4=B8=A4=E8=BD=AE=E8=BE=B9=E7=BC=98=E8=89=B2=E5=86=85=E6=8F=92?= =?UTF-8?q?(=E7=81=AD=E7=99=BD=E8=BE=B9=E4=B8=8D=E5=89=8A=E8=BD=AE?= =?UTF-8?q?=E5=BB=93);=E6=B7=B1=E5=BA=95=E6=B5=8B=E8=AF=95=E6=9D=A1?= =?UTF-8?q?=E5=85=AB=E5=8F=AA=E5=85=A8=E8=BF=87,=E4=BD=99=E8=9C=98?= =?UTF-8?q?=E8=9B=9B=E8=84=9A=E5=B0=961-2px=E6=AE=8B=E7=82=B9=E6=89=8B?= =?UTF-8?q?=E4=BF=AE=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/pixelize_candidates.py | 64 ++++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 3 deletions(-) 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):