Fixed a lot of issues
Some checks failed
/ pylint (push) Successful in 3m13s
/ mypy (push) Has been cancelled
/ lint-and-typing (push) Failing after 4m57s
/ publish-artifacts (push) Has been skipped
/ build-artifacts (push) Has been skipped
/ release (push) Successful in 1m43s

This commit is contained in:
Firq 2024-10-04 20:18:56 +02:00
parent fdc62c04a6
commit 3d546df0e0
Signed by: Firq
GPG key ID: 3ACC61C8CEC83C20
9 changed files with 83 additions and 71 deletions

View file

@ -1,3 +0,0 @@
from .compose import run
run()

View file

@ -0,0 +1 @@
from .compose import compose

View file

@ -1,10 +1,10 @@
import requests import requests
from .cli import Paths, ExpressionDefaults from ..config import Paths, ExpressionDefaults, AtlasDefaults
def fetch_image(servantid: str, imageid: str): def fetch_image(servantid: str, imageid: str):
atlasurl = f"https://static.atlasacademy.io/JP/CharaFigure/{imageid}/{imageid}_merged.png" atlasurl = f"https://static.atlasacademy.io/{AtlasDefaults.REGION}/CharaFigure/{imageid}/{imageid}_merged.png"
savefolder = Paths.IMAGES / servantid savefolder = Paths.IMAGES / servantid
if not savefolder.is_dir(): if not savefolder.is_dir():
@ -21,7 +21,7 @@ def fetch_image(servantid: str, imageid: str):
handle.write(block) handle.write(block)
def fetch_info(servantid: str): def fetch_info(servantid: str):
atlasurl = f"https://api.atlasacademy.io/basic/JP/servant/{servantid}?lang=en" atlasurl = f"https://api.atlasacademy.io/basic/{AtlasDefaults.REGION}/servant/{servantid}?lang=en"
response = requests.get(atlasurl, timeout=10) response = requests.get(atlasurl, timeout=10)
if not response.ok: if not response.ok:
@ -32,7 +32,7 @@ def fetch_info(servantid: str):
def fetch_data(servantid: str): def fetch_data(servantid: str):
fetch_info(servantid) fetch_info(servantid)
atlasurl = f"https://api.atlasacademy.io/raw/JP/servant/{servantid}?lore=false&expand=true&lang=en" atlasurl = f"https://api.atlasacademy.io/raw/{AtlasDefaults.REGION}/servant/{servantid}?lore=false&expand=true&lang=en"
response = requests.get(atlasurl, timeout=10) response = requests.get(atlasurl, timeout=10)
if not response.ok: if not response.ok:

View file

@ -1,23 +1,16 @@
import math import math
import pathlib import pathlib
import sys import sys
from typing import Tuple
import numpy as np import numpy as np
from PIL import Image from PIL import Image
from tqdm.contrib import itertools as tqdm_itertools from tqdm.contrib import itertools as tqdm_itertools
from .atlas import fetch_data, fetch_image from .atlas import fetch_data, fetch_image
from .cli import ExpressionDefaults, Paths from ..config import ExpressionDefaults, Paths
def compose():
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.IMAGES.mkdir(exist_ok=True)
Paths.OUTPUT.mkdir(exist_ok=True) Paths.OUTPUT.mkdir(exist_ok=True)
@ -39,7 +32,36 @@ def run():
process_sprite(f, sprites[f.stem]["position"], sprites[f.stem]["faceSize"], servantid) process_sprite(f, sprites[f.stem]["position"], sprites[f.stem]["faceSize"], servantid)
print(f"Files have been saved at: {(Paths.OUTPUT / servantid).absolute()}") print(f"Files have been saved at: {(Paths.OUTPUT / servantid).absolute()}")
def process_sprite(filepath: pathlib.Path, position: tuple, faceSize: int, servantid: str):
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) im = Image.open(filepath)
width, height = im.size width, height = im.size
@ -48,44 +70,16 @@ def process_sprite(filepath: pathlib.Path, position: tuple, faceSize: int, serva
expressions = im.crop((0, 768, width, height)) expressions = im.crop((0, 768, width, height))
expressionwidth, expressionheight = expressions.size expressionwidth, expressionheight = expressions.size
rows = expressionheight // faceSize rows = expressionheight // facesize
expressions_p_row = expressionwidth // faceSize expressions_p_row = expressionwidth // facesize
exp_per_row = 4 exp_per_row = 4
if faceSize != ExpressionDefaults.faceSize: if facesize != ExpressionDefaults.faceSize:
w, h = expressions.size expressions, exp_per_row = override_expressions(expressions, facesize)
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="-="): 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) img = generate_sprite(main_sprite, expressions, x, y, position, facesize)
if img is not None: if img is not None:
save_sprite(img, servantid, filepath.stem, (x, y, exp_per_row)) save_sprite(img, servantid, filepath.stem, (x, y, exp_per_row))
@ -97,21 +91,22 @@ def is_empty(img: Image.Image):
return True return True
return False return False
def generate_sprite(sprite: Image.Image, expressions: Image.Image, row: int, col: int, position: tuple, faceSize: int) -> Image.Image | None: # 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 = ( area = (
col * faceSize, col * facesize,
row * faceSize, row * facesize,
(col + 1) * faceSize - 1, (col + 1) * facesize - 1,
(row + 1) * faceSize - 1 (row + 1) * facesize - 1
) )
expression = expressions.crop(area) expression = expressions.crop(area)
if is_empty(expression): if is_empty(expression):
return None return None
compose = sprite.copy() composition = sprite.copy()
compose.paste(expression, position, expression) composition.paste(expression, position, expression)
return compose return composition
def save_sprite(image: Image.Image, folder: str, name: str, rowcol: tuple | None = None): def save_sprite(image: Image.Image, folder: str, name: str, rowcol: tuple | None = None):
savefolder = Paths.OUTPUT / folder savefolder = Paths.OUTPUT / folder

View file

View file

@ -2,7 +2,9 @@ import argparse
import pathlib import pathlib
import sys import sys
from . import __version__ from .. import __version__
from ..backend import compose
from ..config import Paths
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods
@ -28,13 +30,16 @@ def parse_arguments():
args, extra_args = parser.parse_known_args(sys.argv[1:], namespace=args) args, extra_args = parser.parse_known_args(sys.argv[1:], namespace=args)
return args, extra_args return args, extra_args
class Paths: def welcome():
_root = pathlib.Path(__file__).parent print("-------------------------------------------------")
_args, _ = parse_arguments() print(" Welcome to the FGO Sprite loader and composer ")
IMAGES = _root / ".temp" print(" developed by Firq ")
OUTPUT = _root / ".out" print("-------------------------------------------------")
if _args.output:
OUTPUT = pathlib.Path(_args.output)
class ExpressionDefaults: def run():
faceSize = 256 args, _ = parse_arguments()
if args.output:
Paths.OUTPUT = pathlib.Path(args.output)
welcome()
compose()

View file

@ -0,0 +1 @@
from .config import AtlasDefaults, ExpressionDefaults, Paths

View file

@ -0,0 +1,13 @@
# pylint: disable=too-few-public-methods
import pathlib
class Paths:
_root = pathlib.Path(__file__).parents[1]
IMAGES = _root / ".temp"
OUTPUT = _root / ".out"
class ExpressionDefaults:
faceSize = 256
class AtlasDefaults:
REGION = "JP"

View file

@ -1,6 +1,6 @@
[project] [project]
name = "atlasimagecomposer" name = "atlasimagecomposer"
version = "0.1.0-a.4" version = "0.1.0-a.5"
dependencies = [ dependencies = [
"numpy~=2.0.1", "numpy~=2.0.1",
"pillow~=10.4.0", "pillow~=10.4.0",
@ -20,7 +20,7 @@ classifiers = [
] ]
[project.scripts] [project.scripts]
atlasimagecomposer = "atlasimagecomposer.compose:run" atlasimagecomposer = "atlasimagecomposer.cli.cli:run"
[tool.setuptools.packages.find] [tool.setuptools.packages.find]
where = ["."] where = ["."]