tools: 像素化 v5=白边/噪点三连防线——预滤α腐蚀MinFilter(3)灭混合带环(5曾啃断钩爪恐魔细肢回退)+despeckle<10px孤立域+edge_inpaint两轮边缘色内插(灭白边不削轮廓);深底测试条八只全过,余蜘蛛脚尖1-2px残点手修级
This commit is contained in:
1 parent
b17db23524
commit
ae7528f34b
1 file changed
+61
-3
@@ -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):
|
||||
|
||||
Reference in new issue
Block a user