diff --git a/atlasimagecomposer/atlas.py b/atlasimagecomposer/atlas.py index 8e03ac7..dbf5500 100644 --- a/atlasimagecomposer/atlas.py +++ b/atlasimagecomposer/atlas.py @@ -1,6 +1,6 @@ import requests -from .cli import Paths +from .cli import Paths, ExpressionDefaults def fetch_image(servantid: str, imageid: str): @@ -39,4 +39,9 @@ def fetch_data(servantid: str): print(response) data = response.json() - return { str(spritesheet["id"]): (spritesheet["faceX"], spritesheet["faceY"]) for spritesheet in data["mstSvtScript"] } + return { + str(spritesheet["id"]): { + "faceSize": spritesheet["extendData"].get("faceSize", ExpressionDefaults.faceSize), + "position": (spritesheet["faceX"], spritesheet["faceY"]) + } for spritesheet in data["mstSvtScript"] + } diff --git a/atlasimagecomposer/cli.py b/atlasimagecomposer/cli.py index e20c6c5..86660d0 100644 --- a/atlasimagecomposer/cli.py +++ b/atlasimagecomposer/cli.py @@ -36,6 +36,5 @@ class Paths: if _args.output: OUTPUT = pathlib.Path(_args.output) -class Expressions: - height = 256 - width = 256 +class ExpressionDefaults: + faceSize = 256 diff --git a/atlasimagecomposer/compose.py b/atlasimagecomposer/compose.py index 3707065..409bdca 100644 --- a/atlasimagecomposer/compose.py +++ b/atlasimagecomposer/compose.py @@ -1,3 +1,4 @@ +import math import pathlib import sys @@ -6,7 +7,7 @@ from PIL import Image from tqdm.contrib import itertools as tqdm_itertools from .atlas import fetch_data, fetch_image -from .cli import Expressions, Paths +from .cli import ExpressionDefaults, Paths def welcome(): @@ -35,10 +36,10 @@ def run(): path = Paths.IMAGES / servantid for f in path.iterdir(): - process_sprite(f, sprites[f.stem], servantid) + process_sprite(f, sprites[f.stem]["position"], sprites[f.stem]["faceSize"], servantid) print(f"Files have been saved at: {(Paths.OUTPUT / servantid).absolute()}") -def process_sprite(filepath: pathlib.Path, position: tuple, servantid: str): +def process_sprite(filepath: pathlib.Path, position: tuple, faceSize: int, servantid: str): im = Image.open(filepath) width, height = im.size @@ -46,13 +47,47 @@ def process_sprite(filepath: pathlib.Path, position: tuple, servantid: str): save_sprite(main_sprite, servantid, filepath.stem) expressions = im.crop((0, 768, width, height)) - _, expressionheight = expressions.size - rows = expressionheight // Expressions.height + expressionwidth, expressionheight = expressions.size + rows = expressionheight // faceSize + expressions_p_row = expressionwidth // faceSize - for x, y in tqdm_itertools.product(range(0, rows), range(0, 4, 1), ascii="-="): - img = generate_sprite(main_sprite, expressions, x, y, position) + exp_per_row = 4 + + if faceSize != ExpressionDefaults.faceSize: + w, h = expressions.size + composites = [] + + per_composite = (1280 - ExpressionDefaults.faceSize) // faceSize + exp_per_row = per_composite + + offset = (faceSize * ExpressionDefaults.faceSize) / 1280 + composite_count = math.ceil((h - 1280) / (offset + faceSize * per_composite)) + 1 + + expressions = expressions.crop((0, 256, w, h)) + floating_offset = 0 + for count in range(0, composite_count + 1): + composites.append(( + 0, + 3 * count * faceSize + floating_offset, + w, + (3 * count + 3) * faceSize + floating_offset - 1 + )) + floating_offset += offset + + overrides = Image.new("RGBA", (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) + expressions = overrides.copy() + + 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)) + 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')) @@ -62,12 +97,12 @@ def is_empty(img: Image.Image): return True return False -def generate_sprite(sprite: Image.Image, expressions: Image.Image, row: int, col: int, position: tuple) -> Image.Image | None: +def generate_sprite(sprite: Image.Image, expressions: Image.Image, row: int, col: int, position: tuple, faceSize: int) -> Image.Image | None: area = ( - col * Expressions.width, - row * Expressions.height, - (col + 1) * Expressions.width - 1, - (row + 1) * Expressions.height - 1 + col * faceSize, + row * faceSize, + (col + 1) * faceSize - 1, + (row + 1) * faceSize - 1 ) expression = expressions.crop(area) @@ -82,7 +117,7 @@ def save_sprite(image: Image.Image, folder: str, name: str, rowcol: tuple | 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" + 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)