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