merge: 像素化 v5 批落籍——白边/噪点三连防线(α腐蚀1环+despeckle+边缘色内插),深底测试条八只全过;PNG透明通道答疑=对话式生图输出压平RGB无解,继续算法路线
This commit is contained in:
commit
f43d4e65e0
1 file changed
+61
-3
@@ -13,7 +13,7 @@ v3 演进(2026-09-27 用户四问题:脏点/模糊/信息丢失/白边):
|
|||||||
"""
|
"""
|
||||||
import sys, os
|
import sys, os
|
||||||
from collections import deque, Counter
|
from collections import deque, Counter
|
||||||
from PIL import Image
|
from PIL import Image, ImageFilter
|
||||||
|
|
||||||
SIZES = {"goblin": 96, "giant_spider": 96, "hook_horror": 96,
|
SIZES = {"goblin": 96, "giant_spider": 96, "hook_horror": 96,
|
||||||
"bugbear": 128, "duergar": 128,
|
"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)
|
r, b = min(im.width, r + pad), min(im.height, b + pad)
|
||||||
return im.crop((l, t, r, b))
|
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):
|
def pixelize(im, target):
|
||||||
"""BOX 预滤到 4x → 量化 → NEAREST 到 1x(最长边=档位值)。"""
|
"""BOX 预滤到 4x(α腐蚀2px灭混合带) → 量化 → NEAREST → 降噪点 → 边缘内插。"""
|
||||||
src = crop_content(im)
|
src = crop_content(im)
|
||||||
scale = target * 4 / max(src.size)
|
scale = target * 4 / max(src.size)
|
||||||
pre = src.resize((max(1, round(src.width * scale)), max(1, round(src.height * scale))), Image.BOX)
|
pre = src.resize((max(1, round(src.width * scale)), max(1, round(src.height * scale))), Image.BOX)
|
||||||
rgb = pre.convert("RGB")
|
rgb = pre.convert("RGB")
|
||||||
flat = rgb.quantize(QUANT_COLORS, method=Image.MEDIANCUT).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)
|
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)
|
out.putalpha(a)
|
||||||
|
despeckle(out)
|
||||||
|
edge_inpaint(out)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def strip(spr, path):
|
def strip(spr, path):
|
||||||
|
|||||||
Reference in new issue
Block a user