82 lines
2.2 KiB
Python
82 lines
2.2 KiB
Python
"""Cut black background only via edge flood-fill — keep black face screen."""
|
|||
|
|
from collections import deque
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
from PIL import Image
|
||
|
|
|
||
|
|
src = Path(r"e:\projeha\crm\apps\web\public\brand\Mento-ai.png")
|
||
|
|
dst = Path(r"e:\projeha\crm\apps\web\public\brand\mento-ai-avatar.png")
|
||
|
|
|
||
|
|
img = Image.open(src).convert("RGBA")
|
||
|
|
w, h = img.size
|
||
|
|
px = img.load()
|
||
|
|
|
||
|
|
def is_bg(r, g, b, a):
|
||
|
|
# near-black background plate (not cyan glow)
|
||
|
|
if a < 10:
|
||
|
|
return True
|
||
|
|
if r < 40 and g < 40 and b < 45:
|
||
|
|
return True
|
||
|
|
if r < 55 and g < 55 and b < 60 and (r + g + b) < 150:
|
||
|
|
# still bg if not cyan-tinted
|
||
|
|
if b < g + 15 and g < r + 15:
|
||
|
|
return True
|
||
|
|
return False
|
||
|
|
|
||
|
|
visited = [[False] * w for _ in range(h)]
|
||
|
|
q = deque()
|
||
|
|
|
||
|
|
# seed from all edge pixels that look like background
|
||
|
|
for x in range(w):
|
||
|
|
for y in (0, h - 1):
|
||
|
|
r, g, b, a = px[x, y]
|
||
|
|
if is_bg(r, g, b, a):
|
||
|
|
q.append((x, y))
|
||
|
|
visited[y][x] = True
|
||
|
|
for y in range(h):
|
||
|
|
for x in (0, w - 1):
|
||
|
|
if not visited[y][x]:
|
||
|
|
r, g, b, a = px[x, y]
|
||
|
|
if is_bg(r, g, b, a):
|
||
|
|
q.append((x, y))
|
||
|
|
visited[y][x] = True
|
||
|
|
|
||
|
|
while q:
|
||
|
|
x, y = q.popleft()
|
||
|
|
px[x, y] = (0, 0, 0, 0)
|
||
|
|
for nx, ny in ((x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1)):
|
||
|
|
if 0 <= nx < w and 0 <= ny < h and not visited[ny][nx]:
|
||
|
|
r, g, b, a = px[nx, ny]
|
||
|
|
if is_bg(r, g, b, a):
|
||
|
|
visited[ny][nx] = True
|
||
|
|
q.append((nx, ny))
|
||
|
|
|
||
|
|
bbox = img.getbbox()
|
||
|
|
if bbox:
|
||
|
|
pad = 20
|
||
|
|
l, t, r, btm = bbox
|
||
|
|
img = img.crop(
|
||
|
|
(max(0, l - pad), max(0, t - pad), min(w, r + pad), min(h, btm + pad))
|
||
|
|
)
|
||
|
|
|
||
|
|
# fit into square canvas keeping aspect
|
||
|
|
ow, oh = img.size
|
||
|
|
side = max(ow, oh)
|
||
|
|
canvas = Image.new("RGBA", (side, side), (0, 0, 0, 0))
|
||
|
|
canvas.paste(img, ((side - ow) // 2, (side - oh) // 2))
|
||
|
|
canvas = canvas.resize((512, 512), Image.Resampling.LANCZOS)
|
||
|
|
canvas.save(dst, "PNG", optimize=True)
|
||
|
|
|
||
|
|
# stats
|
||
|
|
a = [p[3] for p in canvas.getdata()]
|
||
|
|
print(
|
||
|
|
"saved",
|
||
|
|
dst,
|
||
|
|
"zero_a",
|
||
|
|
sum(1 for x in a if x == 0),
|
||
|
|
"corner",
|
||
|
|
canvas.getpixel((0, 0)),
|
||
|
|
"center",
|
||
|
|
canvas.getpixel((256, 256)),
|
||
|
|
)
|