Fixed issues with sprite overlap, fixed naming of files, added id parameter to CLI, removed numpy dependency
All checks were successful
/ pylint (push) Successful in 10s
/ mypy (push) Successful in 13s
/ lint-and-typing (push) Successful in 15s
/ build-artifacts (push) Successful in 7s
/ publish-artifacts (push) Successful in 8s
/ release (push) Successful in 6s

This commit is contained in:
Firq 2024-10-13 01:31:16 +02:00
parent 941b4c5614
commit db406adfdc
Signed by: Firq
GPG key ID: 4DE1059A4666E89F
3 changed files with 40 additions and 52 deletions

View file

@ -1,7 +1,7 @@
import pathlib import pathlib
from typing import List, Optional from typing import List, Optional
from collections import Counter
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
@ -44,10 +44,10 @@ def gen_main_sprite(folder: pathlib.Path):
def process_sprite(images_folder: pathlib.Path, configdata: SpritesheetData, outputfolder: pathlib.Path): def process_sprite(images_folder: pathlib.Path, configdata: SpritesheetData, outputfolder: pathlib.Path):
main_sprite = gen_main_sprite(images_folder) main_sprite = gen_main_sprite(images_folder)
save_sprite(main_sprite, outputfolder, f"{images_folder.stem}") image_idx = save_sprite(main_sprite, outputfolder, f"{images_folder.stem}")
for i in images_folder.iterdir(): for i in images_folder.iterdir():
initial_row, index = 0, int(i.stem) initial_row = 0
expressions = Image.open(i) expressions = Image.open(i)
rowcount, colcount = calculate_counts(*expressions.size, configdata["facesize"]) rowcount, colcount = calculate_counts(*expressions.size, configdata["facesize"])
@ -61,7 +61,7 @@ def process_sprite(images_folder: pathlib.Path, configdata: SpritesheetData, out
for x, y in tqdm_itertools.product(range(initial_row, rowcount), range(0, colcount), ascii="-="): for x, y in tqdm_itertools.product(range(initial_row, rowcount), range(0, colcount), ascii="-="):
img = generate_sprite(main_sprite, expressions, x, y, configdata) img = generate_sprite(main_sprite, expressions, x, y, configdata)
if img is not None: if img is not None:
save_sprite(img, outputfolder, f"{images_folder.stem}", (x, y, colcount, index)) image_idx = save_sprite(img, outputfolder, f"{images_folder.stem}", image_idx)
def generate_sprite(main_sprite: Image.Image, expressions: Image.Image, row: int, col: int, configdata: SpritesheetData) -> Image.Image | None: def generate_sprite(main_sprite: Image.Image, expressions: Image.Image, row: int, col: int, configdata: SpritesheetData) -> Image.Image | None:
@ -69,53 +69,34 @@ def generate_sprite(main_sprite: Image.Image, expressions: Image.Image, row: int
roi = ( roi = (
col * facesize[0], col * facesize[0],
row * facesize[1], row * facesize[1],
(col + 1) * facesize[0] - 1, (col + 1) * facesize[0],
(row + 1) * facesize[1] - 1 (row + 1) * facesize[1]
) )
expression = expressions.crop(roi) expression = expressions.crop(roi)
if is_empty(expression): if is_empty(expression):
return None return None
mask = Image.new("RGBA", (facesize[0], facesize[1]), (255,255,255,255))
composition = main_sprite.copy() composition = main_sprite.copy()
composition.paste(expression, position, expression) composition.paste(expression, position, mask)
return composition return composition
def save_sprite(image: Image.Image, outputfolder: pathlib.Path, name: str, info: tuple | None = None): def save_sprite(image: Image.Image, outputfolder: pathlib.Path, name: str, idx: int = 0) -> int:
savefolder = outputfolder / name savefolder = outputfolder / name
if not savefolder.is_dir(): if not savefolder.is_dir():
savefolder.mkdir() savefolder.mkdir()
outfile = savefolder / f"{idx}.png"
postfix = "0"
if info is not None:
(row, col, column_count, file_idx) = info
if file_idx == 0 and column_count == 4:
postfix = str(col + 1)
elif file_idx == 0:
raise ValueError("Should not have any faces")
elif column_count == 4:
postfix = str((column_count * row + col + 1) + pow(column_count, 2) * (file_idx - 1) + column_count)
elif column_count == 1:
postfix = str((file_idx - 1) * 16 + 1 if file_idx >= 2 else file_idx * 4 + 1)
elif column_count < 4:
postfix = str((column_count * row + col + 1) + pow(column_count, 2) * (file_idx - 1))
else:
raise ValueError("Unaccounted case")
outfile = savefolder / f"{postfix}.png"
with open(outfile, 'wb') as file: with open(outfile, 'wb') as file:
image.save(file) image.save(file)
idx += 1
return idx
def is_empty(img: Image.Image): def is_empty(img: Image.Image):
data = np.asarray(img.crop((96, 96, 160, 160)).convert('LA')) data = Counter(img.crop((96, 96, 160, 160)).convert('LA').getdata())
np.reshape(data, (-1, 1)) if len(data) < 10:
_, count_unique = np.unique(data, return_counts=True)
if count_unique.size < 10:
return True return True
return False return False

View file

@ -15,6 +15,7 @@ class Arguments(argparse.Namespace):
Default Arguments when calling the CLI Default Arguments when calling the CLI
""" """
output: str output: str
id: str
cacheclear: bool cacheclear: bool
filter: List[str] filter: List[str]
timeout: int timeout: int
@ -31,9 +32,10 @@ def parse_arguments():
parser.add_argument("--version", action="version", version=f"atlasimagecomposer {__version__}") parser.add_argument("--version", action="version", version=f"atlasimagecomposer {__version__}")
parser.add_argument("--output", action="store", type=str, default=None, dest="output", help="Set the output location. This can be an absolute or relative path") parser.add_argument("--output", action="store", type=str, default=None, dest="output", help="Set the output location. This can be an absolute or relative path")
parser.add_argument("--id", action="store", type=str, default=None, dest="id", help="Set the servantId/charaId - Skips user prompt when provided")
parser.add_argument("--filter", action="extend", nargs="+", dest="filter", help='Specify one or more spritesheet ids that will be fetched') parser.add_argument("--filter", action="extend", nargs="+", dest="filter", help='Specify one or more spritesheet ids that will be fetched')
parser.add_argument("--timeout", action="store", type=int, default=None, dest="timeout", help="Set the timeout for all requests towards AtlasAcademy, default is 10s") parser.add_argument("--timeout", action="store", type=int, default=None, dest="timeout", help="Set the timeout for all requests towards AtlasAcademy, default is 10s")
parser.add_argument("--clear-cache", action="store_true", default=False, dest="cacheclear", help="Clear cached assets before downloading files for a servant") parser.add_argument("--clear-cache", action="store_true", default=False, dest="cacheclear", help="Clear cached assets before downloading files")
args = Arguments() args = Arguments()
@ -56,7 +58,10 @@ def run_cli():
welcome() welcome()
input_id = args.id
if not input_id:
input_id = input("Enter servantId/charaId: ") input_id = input("Enter servantId/charaId: ")
try: try:
t = int(input_id) t = int(input_id)
if t <= 0: if t <= 0:
@ -65,6 +70,8 @@ def run_cli():
print("Servant ID has to be a valid integer above 0") print("Servant ID has to be a valid integer above 0")
sys.exit(1) sys.exit(1)
input_id = int(input_id)
if args.cacheclear: if args.cacheclear:
cachepath = Paths.IMAGES / str(input_id) cachepath = Paths.IMAGES / str(input_id)
if input_id > 10000: if input_id > 10000:
@ -75,4 +82,4 @@ def run_cli():
else: else:
print("No cache to clear was found, continuing") print("No cache to clear was found, continuing")
compose(int(input_id), args.filter) compose(input_id, args.filter)

View file

@ -1,12 +1,6 @@
[project] [project]
name = "atlasimagecomposer" name = "atlasimagecomposer"
version = "0.1.0-c.2" version = "0.1.0-c.3"
dependencies = [
"numpy~=2.0.1",
"pillow~=10.4.0",
"requests~=2.32.3",
"tqdm~=4.66.5",
]
requires-python = ">= 3.10" requires-python = ">= 3.10"
authors = [{name = "Firq", email = "firelp42@gmail.com"}] authors = [{name = "Firq", email = "firelp42@gmail.com"}]
maintainers = [{name = "Firq", email = "firelp42@gmail.com"}] maintainers = [{name = "Firq", email = "firelp42@gmail.com"}]
@ -19,15 +13,11 @@ classifiers = [
"Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.12",
] ]
[project.scripts] dependencies = [
atlasimagecomposer = "atlasimagecomposer.cli.cli:run_cli" "pillow~=10.4.0",
"requests~=2.32.3",
[tool.setuptools.packages.find] "tqdm~=4.66.5",
where = ["."] ]
include = ["atlasimagecomposer*"]
[tool.setuptools.package-data]
"*" = ["py.typed"]
[project.optional-dependencies] [project.optional-dependencies]
lint = [ lint = [
@ -39,6 +29,16 @@ typing = [
"types-requests~=2.32.0", "types-requests~=2.32.0",
] ]
[project.scripts]
atlasimagecomposer = "atlasimagecomposer.cli.cli:run_cli"
[tool.setuptools.packages.find]
where = ["."]
include = ["atlasimagecomposer*"]
[tool.setuptools.package-data]
"*" = ["py.typed"]
[tool.pylint."MAIN"] [tool.pylint."MAIN"]
disable = [ disable = [
"line-too-long", "line-too-long",