Compare commits

..

No commits in common. "main" and "1.0.0-c.2" have entirely different histories.

8 changed files with 97 additions and 102 deletions

View file

@ -46,8 +46,9 @@ This would generate the expressions for Scathach (Servant Id 70) in the folder o
`skyeweave` can also be used in other Python scripts. `skyeweave` can also be used in other Python scripts.
```python ```python
from skyeweave import SkyeWeave from skyeweave import compose
SkyeWeave().compose(70)
compose(70)
``` ```
This feature will be expanded upon in future releases. This feature will be expanded upon in future releases.

View file

@ -1,6 +1,6 @@
[project] [project]
name = "skyeweave" name = "skyeweave"
version = "1.0.0-c.3" version = "1.0.0-c.2"
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"}]

View file

@ -2,4 +2,4 @@ import importlib.metadata
__version__ = importlib.metadata.version(__package__ or "skyeweave") __version__ = importlib.metadata.version(__package__ or "skyeweave")
from .service import SkyeWeave from .service import compose

View file

@ -5,7 +5,7 @@ import sys
from typing import List from typing import List
from .. import __version__ from .. import __version__
from ..service import SkyeWeave from ..service import compose
from ..config import AtlasAPIConfig, LoggingConfig, PathConfig from ..config import AtlasAPIConfig, LoggingConfig, PathConfig
from ..utils import rmdir, disable_tqdm from ..utils import rmdir, disable_tqdm
@ -91,4 +91,4 @@ def run():
rmdir(PathConfig.IMAGES) rmdir(PathConfig.IMAGES)
LOGGER.info("Successfully reset local storage") LOGGER.info("Successfully reset local storage")
SkyeWeave().compose(input_id, args.filter) compose(input_id, args.filter)

View file

@ -1 +1 @@
from .compose import SkyeWeave from .compose import compose

View file

@ -1,5 +1,4 @@
import logging import logging
import pathlib
from typing import Annotated, List, NotRequired, Tuple, TypedDict from typing import Annotated, List, NotRequired, Tuple, TypedDict
import requests import requests
@ -62,11 +61,12 @@ def fetch_mstsvtjson():
break break
handle.write(block) handle.write(block)
def fetch_expression_sheets(tempfolder: pathlib.Path, imageid: str): def fetch_expression_sheets(basefolder: str, imageid: str):
atlasurl_base = f"https://static.atlasacademy.io/{AtlasAPIConfig.REGION}/CharaFigure/{imageid}" atlasurl_base = f"https://static.atlasacademy.io/{AtlasAPIConfig.REGION}/CharaFigure/{imageid}"
savefolder = tempfolder / str(imageid) savefolder = PathConfig.IMAGES / basefolder / str(imageid)
savefolder.mkdir(exist_ok=True, parents=True) if not savefolder.is_dir():
savefolder.mkdir(exist_ok=True, parents=True)
idx, status = 0, 200 idx, status = 0, 200

View file

@ -11,115 +11,106 @@ from .atlas import SpritesheetData, fetch_data, fetch_expression_sheets, fetch_c
LOGGER = logging.getLogger(LoggingConfig.NAME) LOGGER = logging.getLogger(LoggingConfig.NAME)
class SkyeWeave: def compose(input_id: int, filters: Optional[List[str]] = None):
output_folder: pathlib.Path PathConfig.IMAGES.mkdir(exist_ok=True)
image_folder: pathlib.Path PathConfig.OUTPUT.mkdir(exist_ok=True)
def __init__(self, output: Optional[pathlib.Path] = None, assets: Optional[pathlib.Path] = None): if input_id < 10000:
self.output_folder = output or PathConfig.OUTPUT chara_ids = fetch_data(input_id)
self.image_folder = assets or PathConfig.IMAGES savefolder = PathConfig.OUTPUT / str(input_id)
else:
self.output_folder.mkdir(exist_ok=True) LOGGER.info(f"Processing manually uploaded charaId {input_id}")
self.image_folder.mkdir(exist_ok=True) savefolder = PathConfig.OUTPUT / "manual"
chara_ids = [str(input_id)]
def compose(self, input_id: int, filters: Optional[List[str]] = None):
if input_id < 10000:
chara_ids = fetch_data(input_id)
savefolder, tempfolder = self.output_folder / str(input_id), self.image_folder / str(input_id)
else:
LOGGER.info(f"Processing manually uploaded charaId {input_id}")
savefolder, tempfolder = self.output_folder / "manual", self.image_folder / "manual"
chara_ids = [str(input_id)]
if not savefolder.is_dir():
savefolder.mkdir(parents=True, exist_ok=True) savefolder.mkdir(parents=True, exist_ok=True)
tempfolder.mkdir(parents=True, exist_ok=True)
chara_ids = [ v for v in chara_ids if v in filters ] if filters else chara_ids if filters is not None:
LOGGER.debug(chara_ids) chara_ids = [ v for v in chara_ids if v in filters ]
LOGGER.debug(chara_ids)
for char_id in chara_ids: for char_id in chara_ids:
expfolder = fetch_expression_sheets(tempfolder, char_id) expfolder = fetch_expression_sheets(savefolder.stem, char_id)
config = fetch_config(char_id) config = fetch_config(char_id)
self.process_sprite(expfolder, config, savefolder) process_sprite(expfolder, config, savefolder)
LOGGER.info(f"Files have been saved at: {savefolder.absolute()}") LOGGER.info(f"Files have been saved at: {savefolder.absolute()}")
def process_sprite(self, images_folder: pathlib.Path, configdata: SpritesheetData, outputfolder: pathlib.Path):
main_sprite = self._gen_main_sprite(images_folder / "0.png")
image_idx = self._save_sprite(main_sprite, outputfolder, f"{images_folder.stem}")
for i in images_folder.iterdir(): def calculate_counts(width: int, height: int, facesize: tuple[int, int]):
LOGGER.debug(f"Idx: {image_idx}") rowcount, colcount = height // facesize[1], width // facesize[0]
initial_row = 0 LOGGER.debug(f"{height} | {facesize[1]} --> {rowcount}")
expressions = Image.open(i) LOGGER.debug(f"{width} | {facesize[0]} --> {colcount}")
return rowcount, colcount
rowcount, colcount = self._calculate_counts(*expressions.size, configdata["facesize"]) def gen_main_sprite(folder: pathlib.Path):
image = Image.open(folder / "0.png")
width, height = image.size
LOGGER.debug(f"Main sprite ({folder}): {width}:{height}")
return image.crop((0, 0, width, height - 256))
if i.name == "0.png" and 256 < configdata["facesize"][1]: def process_sprite(images_folder: pathlib.Path, configdata: SpritesheetData, outputfolder: pathlib.Path):
continue main_sprite = gen_main_sprite(images_folder)
image_idx = save_sprite(main_sprite, outputfolder, f"{images_folder.stem}")
if i.name == "0.png": for i in images_folder.iterdir():
initial_row = rowcount - 1 LOGGER.debug(f"Idx: {image_idx}")
initial_row = 0
expressions = Image.open(i)
for x, y in tqdm_itertools.product(range(initial_row, rowcount), range(0, colcount), ascii="-=", desc=f"[PROG] [{LoggingConfig.NAME}]", bar_format="{desc} {percentage:3.0f}% |{bar}|"): rowcount, colcount = calculate_counts(*expressions.size, configdata["facesize"])
img = self._generate_sprite(main_sprite, expressions, x, y, configdata)
if img is not None:
image_idx = self._save_sprite(img, outputfolder, f"{images_folder.stem}", image_idx)
LOGGER.debug(f"{x}/{y} - {'Invalid' if img is None else 'Valid'} image")
@staticmethod if i.name == "0.png" and 256 < configdata["facesize"][1]:
def _calculate_counts(width: int, height: int, facesize: tuple[int, int]): continue
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
@staticmethod if i.name == "0.png":
def _gen_main_sprite(imagepath: pathlib.Path): initial_row = rowcount - 1
image = Image.open(imagepath)
width, height = image.size
LOGGER.debug(f"Main sprite ({imagepath}): {width}:{height}")
return image.crop((0, 0, width, height - 256))
@staticmethod for x, y in tqdm_itertools.product(range(initial_row, rowcount), range(0, colcount), ascii="-=", desc=f"[PROG] [{LoggingConfig.NAME}]", bar_format="{desc} {percentage:3.0f}% |{bar}|"):
def _generate_sprite(main_sprite: Image.Image, expressions: Image.Image, row: int, col: int, configdata: SpritesheetData) -> Image.Image | None: img = generate_sprite(main_sprite, expressions, x, y, configdata)
position, facesize = configdata["position"], configdata["facesize"] if img is not None:
roi = ( image_idx = save_sprite(img, outputfolder, f"{images_folder.stem}", image_idx)
col * facesize[0], LOGGER.debug(f"{x}/{y} - {'Invalid' if img is None else 'Valid'} image")
row * facesize[1],
(col + 1) * facesize[0],
(row + 1) * facesize[1]
)
LOGGER.debug(roi)
expression = expressions.crop(roi)
if SkyeWeave._is_empty(expression):
LOGGER.debug("Image empty")
return None
mask = Image.new("RGBA", (facesize[0], facesize[1]), (255,255,255,255)) def generate_sprite(main_sprite: Image.Image, expressions: Image.Image, row: int, col: int, configdata: SpritesheetData) -> Image.Image | None:
composition = main_sprite.copy() position, facesize = configdata["position"], configdata["facesize"]
composition.paste(expression, position, mask) roi = (
return composition col * facesize[0],
row * facesize[1],
(col + 1) * facesize[0],
(row + 1) * facesize[1]
)
LOGGER.debug(roi)
expression = expressions.crop(roi)
@staticmethod if is_empty(expression):
def _save_sprite(image: Image.Image, outputfolder: pathlib.Path, name: str, idx: int = 0) -> int: LOGGER.debug("Image empty")
savefolder = outputfolder / name return None
if not savefolder.is_dir():
savefolder.mkdir()
outfile = savefolder / f"{idx}.png"
with open(outfile, 'wb') as file: mask = Image.new("RGBA", (facesize[0], facesize[1]), (255,255,255,255))
image.save(file) composition = main_sprite.copy()
composition.paste(expression, position, mask)
return composition
return idx + 1
@staticmethod def save_sprite(image: Image.Image, outputfolder: pathlib.Path, name: str, idx: int = 0) -> int:
def _is_empty(img: Image.Image): savefolder = outputfolder / name
w, h = img.size if not savefolder.is_dir():
croparea = (w * 0.375, h * 0.375, w * 0.625, h * 0.625 ) savefolder.mkdir()
data = Counter(img.crop(croparea).convert('LA').getdata()) outfile = savefolder / f"{idx}.png"
LOGGER.debug(f"Counts: {len(data)}")
if len(data) < 6: with open(outfile, 'wb') as file:
return True image.save(file)
return False
return idx + 1
def is_empty(img: Image.Image):
w, h = img.size
croparea = (w * 0.375, h * 0.375, w * 0.625, h * 0.625 )
data = Counter(img.crop(croparea).convert('LA').getdata())
LOGGER.debug(f"Counts: {len(data)}")
if len(data) < 6:
return True
return False

3
test.py Normal file
View file

@ -0,0 +1,3 @@
from skyeweave import compose
compose(70)