Fixed a lot of issues
This commit is contained in:
parent
fdc62c04a6
commit
3d546df0e0
9 changed files with 83 additions and 71 deletions
atlasimagecomposer/backend
1
atlasimagecomposer/backend/__init__.py
Normal file
1
atlasimagecomposer/backend/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from .compose import compose
|
47
atlasimagecomposer/backend/atlas.py
Normal file
47
atlasimagecomposer/backend/atlas.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
import requests
|
||||
|
||||
from ..config import Paths, ExpressionDefaults, AtlasDefaults
|
||||
|
||||
|
||||
def fetch_image(servantid: str, imageid: str):
|
||||
atlasurl = f"https://static.atlasacademy.io/{AtlasDefaults.REGION}/CharaFigure/{imageid}/{imageid}_merged.png"
|
||||
|
||||
savefolder = Paths.IMAGES / servantid
|
||||
if not savefolder.is_dir():
|
||||
savefolder.mkdir()
|
||||
filelocation = savefolder / f"{imageid}.png"
|
||||
|
||||
with open(filelocation, 'wb') as handle:
|
||||
response = requests.get(atlasurl, stream=True, timeout=10)
|
||||
if not response.ok:
|
||||
print(response)
|
||||
for block in response.iter_content(1024):
|
||||
if not block:
|
||||
break
|
||||
handle.write(block)
|
||||
|
||||
def fetch_info(servantid: str):
|
||||
atlasurl = f"https://api.atlasacademy.io/basic/{AtlasDefaults.REGION}/servant/{servantid}?lang=en"
|
||||
|
||||
response = requests.get(atlasurl, timeout=10)
|
||||
if not response.ok:
|
||||
print(response)
|
||||
|
||||
data = response.json()
|
||||
print(f"Fetching data and sprites for {data['name']} (ID: {servantid})")
|
||||
|
||||
def fetch_data(servantid: str):
|
||||
fetch_info(servantid)
|
||||
atlasurl = f"https://api.atlasacademy.io/raw/{AtlasDefaults.REGION}/servant/{servantid}?lore=false&expand=true&lang=en"
|
||||
|
||||
response = requests.get(atlasurl, timeout=10)
|
||||
if not response.ok:
|
||||
print(response)
|
||||
|
||||
data = response.json()
|
||||
return {
|
||||
str(spritesheet["id"]): {
|
||||
"faceSize": spritesheet["extendData"].get("faceSize", ExpressionDefaults.faceSize),
|
||||
"position": (spritesheet["faceX"], spritesheet["faceY"])
|
||||
} for spritesheet in data["mstSvtScript"]
|
||||
}
|
118
atlasimagecomposer/backend/compose.py
Normal file
118
atlasimagecomposer/backend/compose.py
Normal file
|
@ -0,0 +1,118 @@
|
|||
import math
|
||||
import pathlib
|
||||
import sys
|
||||
from typing import Tuple
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
from tqdm.contrib import itertools as tqdm_itertools
|
||||
|
||||
from .atlas import fetch_data, fetch_image
|
||||
from ..config import ExpressionDefaults, Paths
|
||||
|
||||
def compose():
|
||||
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]["position"], sprites[f.stem]["faceSize"], servantid)
|
||||
print(f"Files have been saved at: {(Paths.OUTPUT / servantid).absolute()}")
|
||||
|
||||
|
||||
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)
|
||||
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))
|
||||
|
||||
expressionwidth, expressionheight = expressions.size
|
||||
rows = expressionheight // facesize
|
||||
expressions_p_row = expressionwidth // facesize
|
||||
|
||||
exp_per_row = 4
|
||||
|
||||
if facesize != ExpressionDefaults.faceSize:
|
||||
expressions, exp_per_row = override_expressions(expressions, facesize)
|
||||
|
||||
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, exp_per_row))
|
||||
|
||||
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
|
||||
|
||||
# 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 = (
|
||||
col * facesize,
|
||||
row * facesize,
|
||||
(col + 1) * facesize - 1,
|
||||
(row + 1) * facesize - 1
|
||||
)
|
||||
|
||||
expression = expressions.crop(area)
|
||||
if is_empty(expression):
|
||||
return None
|
||||
|
||||
composition = sprite.copy()
|
||||
composition.paste(expression, position, expression)
|
||||
return composition
|
||||
|
||||
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] * 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)
|
Loading…
Add table
Add a link
Reference in a new issue