119 lines
4 KiB
Python
119 lines
4 KiB
Python
import math
|
|
import pathlib
|
|
import sys
|
|
from typing import Tuple
|
|
|
|
import numpy as np
|
|
from PIL import Image
|
|
from tqdm.contrib import itertools as tqdm_itertools
|
|
|
|
from .atlas import fetch_data, fetch_image
|
|
from ..config import ExpressionDefaults, Paths
|
|
|
|
def compose():
|
|
Paths.IMAGES.mkdir(exist_ok=True)
|
|
Paths.OUTPUT.mkdir(exist_ok=True)
|
|
|
|
servantid = input("Enter servant ID: ")
|
|
try:
|
|
t = int(servantid)
|
|
if t <= 0:
|
|
raise ValueError
|
|
except ValueError:
|
|
print("Servant ID has to be a valid integer above 0")
|
|
sys.exit(1)
|
|
sprites = fetch_data(servantid)
|
|
|
|
for sprite in sprites:
|
|
fetch_image(servantid, sprite)
|
|
|
|
path = Paths.IMAGES / servantid
|
|
for f in path.iterdir():
|
|
process_sprite(f, sprites[f.stem]["position"], sprites[f.stem]["faceSize"], servantid)
|
|
print(f"Files have been saved at: {(Paths.OUTPUT / servantid).absolute()}")
|
|
|
|
|
|
def override_expressions(expressions: Image.Image, facesize: int) -> Tuple[Image.Image, int]:
|
|
exp_w, exp_h = expressions.size
|
|
composites, offset, floating_offset = [], facesize // 5, 0
|
|
per_composite = (ExpressionDefaults.faceSize * 4) // facesize
|
|
|
|
# Why ..........
|
|
composite_count = math.ceil((exp_h - 1280) / (offset + facesize * per_composite)) + 1
|
|
|
|
# Remove top offset
|
|
expressions = expressions.crop((0, 256, exp_w, exp_h))
|
|
|
|
for count in range(0, composite_count):
|
|
# width_topleft, height_topleft, width_botright, height_botright
|
|
composites.append((0, 3 * count * facesize + floating_offset, exp_w, (3 * count + 3) * facesize + floating_offset - 1 ))
|
|
floating_offset += offset
|
|
|
|
# Create new expression image, then add all compositions to it
|
|
overrides = Image.new("RGBA", (exp_w, facesize * composite_count * 3), (255, 0, 0, 0))
|
|
for idx, c in enumerate(composites):
|
|
crop = expressions.crop(c)
|
|
pos = (0, idx * 3 * facesize)
|
|
overrides.paste(crop, pos, crop)
|
|
|
|
# Override expressions sheet
|
|
return overrides.copy(), per_composite
|
|
|
|
|
|
# pylint: disable=too-many-locals
|
|
def process_sprite(filepath: pathlib.Path, position: tuple, facesize: int, servantid: str):
|
|
im = Image.open(filepath)
|
|
width, height = im.size
|
|
|
|
main_sprite = im.crop((0, 0, width, 768))
|
|
save_sprite(main_sprite, servantid, filepath.stem)
|
|
expressions = im.crop((0, 768, width, height))
|
|
|
|
expressionwidth, expressionheight = expressions.size
|
|
rows = expressionheight // facesize
|
|
expressions_p_row = expressionwidth // facesize
|
|
|
|
exp_per_row = 4
|
|
|
|
if facesize != ExpressionDefaults.faceSize:
|
|
expressions, exp_per_row = override_expressions(expressions, facesize)
|
|
|
|
for x, y in tqdm_itertools.product(range(0, rows), range(0, expressions_p_row, 1), ascii="-="):
|
|
img = generate_sprite(main_sprite, expressions, x, y, position, facesize)
|
|
if img is not None:
|
|
save_sprite(img, servantid, filepath.stem, (x, y, exp_per_row))
|
|
|
|
def is_empty(img: Image.Image):
|
|
data = np.asarray(img.crop((96, 96, 160, 160)).convert('LA'))
|
|
np.reshape(data, (-1, 1))
|
|
_, count_unique = np.unique(data, return_counts=True)
|
|
if count_unique.size < 10:
|
|
return True
|
|
return False
|
|
|
|
# pylint: disable=too-many-arguments
|
|
def generate_sprite(sprite: Image.Image, expressions: Image.Image, row: int, col: int, position: tuple, facesize: int) -> Image.Image | None:
|
|
area = (
|
|
col * facesize,
|
|
row * facesize,
|
|
(col + 1) * facesize - 1,
|
|
(row + 1) * facesize - 1
|
|
)
|
|
|
|
expression = expressions.crop(area)
|
|
if is_empty(expression):
|
|
return None
|
|
|
|
composition = sprite.copy()
|
|
composition.paste(expression, position, expression)
|
|
return composition
|
|
|
|
def save_sprite(image: Image.Image, folder: str, name: str, rowcol: tuple | None = None):
|
|
savefolder = Paths.OUTPUT / folder
|
|
if not savefolder.is_dir():
|
|
savefolder.mkdir()
|
|
postfix = f"_{rowcol[0] * rowcol[2] + rowcol[1] + 1}" if rowcol is not None else "_0"
|
|
outfile = savefolder / f"{name}{postfix}.png"
|
|
with open(outfile, 'wb') as file:
|
|
image.save(file)
|