Initial Dansori character workspace

This commit is contained in:
eKeerar
2026-07-04 10:34:46 +09:00
commit 5a419816ff
2480 changed files with 38692 additions and 0 deletions
+107
View File
@@ -0,0 +1,107 @@
from __future__ import annotations
from collections import deque
from pathlib import Path
import sys
import numpy as np
from PIL import Image, ImageFilter
def connected_components(mask: np.ndarray) -> np.ndarray:
h, w = mask.shape
labels = np.zeros((h, w), dtype=np.int32)
current = 0
for y in range(h):
for x in range(w):
if not mask[y, x] or labels[y, x] != 0:
continue
current += 1
queue: deque[tuple[int, int]] = deque([(x, y)])
labels[y, x] = current
while queue:
cx, cy = queue.popleft()
for nx, ny in (
(cx + 1, cy),
(cx - 1, cy),
(cx, cy + 1),
(cx, cy - 1),
):
if nx < 0 or ny < 0 or nx >= w or ny >= h:
continue
if mask[ny, nx] and labels[ny, nx] == 0:
labels[ny, nx] = current
queue.append((nx, ny))
return labels
def main() -> None:
if len(sys.argv) != 3:
raise SystemExit("usage: make_hairmask.py <input-head.png> <output-mask.png>")
src = Path(sys.argv[1])
out = Path(sys.argv[2])
image = Image.open(src).convert("RGBA")
arr = np.asarray(image)
rgb = arr[:, :, :3].astype(np.int16)
alpha = arr[:, :, 3]
r, g, b = rgb[:, :, 0], rgb[:, :, 1], rgb[:, :, 2]
opaque = alpha > 32
brown_hair = (
opaque
& (r > 34)
& (g > 18)
& (r >= g - 8)
& (g >= b - 4)
& ((r - b) > 22)
& ((r < 205) | (g < 138) | (b < 105))
)
dark_hair_lines = opaque & (r > 18) & (r < 115) & (g < 100) & (b < 85) & ((r - b) > 8)
mask = brown_hair | dark_hair_lines
labels = connected_components(mask)
keep = np.zeros_like(mask)
for label in range(1, labels.max() + 1):
ys, xs = np.where(labels == label)
if xs.size == 0:
continue
area = xs.size
width = int(xs.max() - xs.min() + 1)
height = int(ys.max() - ys.min() + 1)
left = int(xs.min())
right = int(xs.max())
top = int(ys.min())
bottom = int(ys.max())
h, w = arr.shape[:2]
touches_hair_zone = (
left < w * 0.25
or right > w * 0.75
or top < h * 0.32
or bottom > h * 0.78
)
if area >= 5000 or (
area >= 1200
and touches_hair_zone
and (width >= 45 or height >= 45)
):
keep[ys, xs] = True
mask_img = Image.fromarray((keep.astype(np.uint8) * 255), "L")
mask_img = mask_img.filter(ImageFilter.MaxFilter(5))
mask_arr = np.asarray(mask_img) > 0
mask_arr &= opaque
out_arr = np.zeros_like(arr)
out_arr[:, :, :3] = 255
out_arr[:, :, 3] = (mask_arr.astype(np.uint8) * 255)
out.parent.mkdir(parents=True, exist_ok=True)
Image.fromarray(out_arr, "RGBA").save(out)
print(f"Wrote {out}")
if __name__ == "__main__":
main()