Fixed a lot of issues
Some checks failed
Some checks failed
This commit is contained in:
parent
fdc62c04a6
commit
3d546df0e0
9 changed files with 83 additions and 71 deletions
|
@ -1,3 +0,0 @@
|
||||||
from .compose import run
|
|
||||||
|
|
||||||
run()
|
|
1
atlasimagecomposer/backend/__init__.py
Normal file
1
atlasimagecomposer/backend/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
from .compose import compose
|
|
@ -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:
|
|
@ -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
|
0
atlasimagecomposer/cli/__init__.py
Normal file
0
atlasimagecomposer/cli/__init__.py
Normal 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()
|
1
atlasimagecomposer/config/__init__.py
Normal file
1
atlasimagecomposer/config/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
from .config import AtlasDefaults, ExpressionDefaults, Paths
|
13
atlasimagecomposer/config/config.py
Normal file
13
atlasimagecomposer/config/config.py
Normal 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"
|
|
@ -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 = ["."]
|
||||||
|
|
Loading…
Reference in a new issue