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) 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], 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)