skyeweave/atlasimagecomposer/backend/compose.py

115 lines
3.9 KiB
Python
Raw Normal View History

2024-10-14 18:23:09 +00:00
import logging
2024-08-09 16:57:33 +00:00
import pathlib
2024-10-05 12:01:50 +00:00
from typing import List, Optional
from collections import Counter
2024-08-09 16:57:33 +00:00
from PIL import Image
from tqdm.contrib import itertools as tqdm_itertools
2024-10-14 18:23:09 +00:00
from ..config import Paths, Logging
from .atlas import SpritesheetData, fetch_data, fetch_expression_sheets, fetch_config
2024-08-09 16:57:33 +00:00
2024-10-14 18:23:09 +00:00
LOGGER = logging.getLogger(Logging.NAME)
def compose(input_id: int, filters: Optional[List[str]] = None):
2024-08-09 16:57:33 +00:00
Paths.IMAGES.mkdir(exist_ok=True)
Paths.OUTPUT.mkdir(exist_ok=True)
if input_id < 10000:
chara_ids = fetch_data(input_id)
savefolder = Paths.OUTPUT / str(input_id)
else:
2024-10-14 18:23:09 +00:00
LOGGER.info(f"Processing manually uploaded charaId {input_id}")
savefolder = Paths.OUTPUT / "manual"
2024-10-10 23:06:58 +00:00
chara_ids = [str(input_id)]
2024-08-09 16:57:33 +00:00
2024-10-05 12:01:50 +00:00
if not savefolder.is_dir():
savefolder.mkdir(parents=True, exist_ok=True)
2024-10-04 18:18:56 +00:00
if filters is not None:
chara_ids = [ v for v in chara_ids if v in filters ]
2024-10-14 18:23:09 +00:00
LOGGER.debug(chara_ids)
2024-10-04 18:18:56 +00:00
for char_id in chara_ids:
expfolder = fetch_expression_sheets(savefolder.stem, char_id)
config = fetch_config(char_id)
process_sprite(expfolder, config, savefolder)
2024-10-04 18:18:56 +00:00
2024-10-14 18:23:09 +00:00
LOGGER.info(f"Files have been saved at: {savefolder.absolute()}")
2024-10-04 18:18:56 +00:00
def calculate_counts(width: int, height: int, facesize: tuple[int, int]):
2024-10-14 18:23:09 +00:00
rowcount, colcount = height // facesize[1], width // facesize[0]
LOGGER.debug(f"{height} | {facesize[1]} --> {rowcount}")
LOGGER.debug(f"{width} | {facesize[0]} --> {colcount}")
return rowcount, colcount
2024-10-04 18:18:56 +00:00
def gen_main_sprite(folder: pathlib.Path):
image = Image.open(folder / "0.png")
width, height = image.size
2024-10-14 18:23:09 +00:00
LOGGER.debug(f"Main sprite ({folder}): {width}:{height}")
return image.crop((0, 0, width, height - 256))
2024-10-04 18:18:56 +00:00
def process_sprite(images_folder: pathlib.Path, configdata: SpritesheetData, outputfolder: pathlib.Path):
main_sprite = gen_main_sprite(images_folder)
image_idx = save_sprite(main_sprite, outputfolder, f"{images_folder.stem}")
2024-08-09 16:57:33 +00:00
2024-10-05 12:01:50 +00:00
for i in images_folder.iterdir():
2024-10-14 18:23:09 +00:00
LOGGER.debug(f"Idx: {image_idx}")
initial_row = 0
2024-10-05 12:01:50 +00:00
expressions = Image.open(i)
2024-08-09 16:57:33 +00:00
2024-10-05 12:01:50 +00:00
rowcount, colcount = calculate_counts(*expressions.size, configdata["facesize"])
2024-10-04 17:30:40 +00:00
if i.name == "0.png" and 256 < configdata["facesize"][1]:
continue
2024-10-10 23:06:58 +00:00
if i.name == "0.png":
initial_row = rowcount - 1
2024-10-04 17:30:40 +00:00
2024-10-05 12:01:50 +00:00
for x, y in tqdm_itertools.product(range(initial_row, rowcount), range(0, colcount), ascii="-="):
img = generate_sprite(main_sprite, expressions, x, y, configdata)
if img is not None:
image_idx = save_sprite(img, outputfolder, f"{images_folder.stem}", image_idx)
2024-10-14 18:23:09 +00:00
LOGGER.debug(f"{x}/{y} - {'Invalid' if img is None else 'Valid'} image")
2024-10-04 17:30:40 +00:00
2024-08-09 16:57:33 +00:00
2024-10-05 12:01:50 +00:00
def generate_sprite(main_sprite: Image.Image, expressions: Image.Image, row: int, col: int, configdata: SpritesheetData) -> Image.Image | None:
position, facesize = configdata["position"], configdata["facesize"]
roi = (
col * facesize[0],
row * facesize[1],
(col + 1) * facesize[0],
(row + 1) * facesize[1]
2024-08-09 16:57:33 +00:00
)
2024-10-14 18:23:09 +00:00
LOGGER.debug(roi)
2024-10-05 12:01:50 +00:00
expression = expressions.crop(roi)
2024-08-09 16:57:33 +00:00
if is_empty(expression):
2024-10-14 18:23:09 +00:00
LOGGER.debug("Image empty")
2024-08-09 16:57:33 +00:00
return None
mask = Image.new("RGBA", (facesize[0], facesize[1]), (255,255,255,255))
2024-10-05 12:01:50 +00:00
composition = main_sprite.copy()
composition.paste(expression, position, mask)
2024-10-04 18:18:56 +00:00
return composition
2024-08-09 16:57:33 +00:00
2024-10-05 12:01:50 +00:00
def save_sprite(image: Image.Image, outputfolder: pathlib.Path, name: str, idx: int = 0) -> int:
savefolder = outputfolder / name
2024-08-09 16:57:33 +00:00
if not savefolder.is_dir():
savefolder.mkdir()
outfile = savefolder / f"{idx}.png"
2024-10-05 12:01:50 +00:00
2024-08-09 16:57:33 +00:00
with open(outfile, 'wb') as file:
image.save(file)
2024-10-05 12:01:50 +00:00
2024-10-14 18:23:09 +00:00
return idx + 1
2024-10-05 12:01:50 +00:00
def is_empty(img: Image.Image):
data = Counter(img.crop((96, 96, 160, 160)).convert('LA').getdata())
2024-10-14 18:23:09 +00:00
LOGGER.debug(f"Counts: {len(data)}")
if len(data) < 10:
2024-10-05 12:01:50 +00:00
return True
return False