2024-08-09 16:57:33 +00:00
|
|
|
import pathlib
|
|
|
|
import sys
|
|
|
|
|
|
|
|
import numpy as np
|
|
|
|
from PIL import Image
|
|
|
|
from tqdm.contrib import itertools as tqdm_itertools
|
|
|
|
|
|
|
|
from .atlas import fetch_data, fetch_image
|
|
|
|
from .cli import Expressions, Paths
|
|
|
|
|
|
|
|
|
|
|
|
def welcome():
|
|
|
|
print("-------------------------------------------------")
|
|
|
|
print(" Welcome to the FGO Sprite loader and composer ")
|
|
|
|
print(" developed by Firq ")
|
|
|
|
print("-------------------------------------------------")
|
|
|
|
|
|
|
|
def run():
|
|
|
|
welcome()
|
|
|
|
Paths.IMAGES.mkdir(exist_ok=True)
|
|
|
|
Paths.OUTPUT.mkdir(exist_ok=True)
|
|
|
|
|
|
|
|
servantid = input("Enter servant ID: ")
|
|
|
|
try:
|
|
|
|
t = int(servantid)
|
2024-08-09 17:09:05 +00:00
|
|
|
if t <= 0:
|
2024-08-09 16:57:33 +00:00
|
|
|
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], servantid)
|
|
|
|
print(f"Files have been saved at: {Paths.OUTPUT / servantid}")
|
|
|
|
|
|
|
|
def process_sprite(filepath: pathlib.Path, position: tuple, 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))
|
|
|
|
|
|
|
|
_, expressionheight = expressions.size
|
|
|
|
rows = expressionheight // Expressions.height
|
|
|
|
|
|
|
|
for x, y in tqdm_itertools.product(range(0, rows), range(0, 4, 1), ascii="-="):
|
|
|
|
img = generate_sprite(main_sprite, expressions, x, y, position)
|
|
|
|
if img is not None:
|
|
|
|
save_sprite(img, servantid, filepath.stem, (x, y))
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
def generate_sprite(sprite: Image.Image, expressions: Image.Image, row: int, col: int, position: tuple) -> Image.Image | None:
|
|
|
|
area = (
|
|
|
|
col * Expressions.width,
|
|
|
|
row * Expressions.height,
|
|
|
|
(col + 1) * Expressions.width - 1,
|
|
|
|
(row + 1) * Expressions.height - 1
|
|
|
|
)
|
|
|
|
|
|
|
|
expression = expressions.crop(area)
|
|
|
|
if is_empty(expression):
|
|
|
|
return None
|
|
|
|
|
|
|
|
compose = sprite.copy()
|
|
|
|
compose.paste(expression, position, expression)
|
|
|
|
return compose
|
|
|
|
|
|
|
|
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] * 4 + 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)
|