From 747bea140fdc61115824fcd4d8cd9839dfffea1a Mon Sep 17 00:00:00 2001 From: Danila Date: Sun, 17 Apr 2022 13:56:27 +0300 Subject: [PATCH] Place sprites fix, refactoring --- system/languages/en-EU.json | 19 +++---- system/languages/ru-RU.json | 17 +++--- system/lib/features/place_sprites.py | 67 +++++++++++------------ system/lib/features/sc/__init__.py | 51 ++++++----------- system/lib/features/sc/assembly_encode.py | 6 +- system/lib/features/sc/sc_encode.py | 15 +++-- system/lib/features/update/check.py | 1 + system/lib/images.py | 2 +- system/lib/xcod.py | 49 +++++++++++++++++ system/localization.py | 3 +- 10 files changed, 131 insertions(+), 99 deletions(-) create mode 100644 system/lib/xcod.py diff --git a/system/languages/en-EU.json b/system/languages/en-EU.json index ab058eb..fb16877 100644 --- a/system/languages/en-EU.json +++ b/system/languages/en-EU.json @@ -26,7 +26,7 @@ "encode_sc_description": "Converts PNG to SC", "decode_by_parts_description": "Converts SC in PNG and cuts texture to sprites", "encode_by_parts_description": "Puts sprites on the texture and converts PNG in SC", - "overwrite_by_parts_description": "Repeats the action of the previous function but consider the \"overwrite\" folder", + "overwrite_by_parts_description": "Do the same as the previous but consider the \"overwrite\" folder", "csv_label": "CSV", "decompress_csv": "Decompress the CSV", @@ -51,15 +51,14 @@ "collecting_inf": "Collecting information...", "about_sc": "About texture. Filename: %s (%d), Pixel type: %d, Size: %sx%s", "decompression_error": "Error while decompressing! Trying to decode as is...", - "skip_not_installed": "%s isn\"t installed! Reinitialize", + "skip_not_installed": "%s isn't installed! Reinitialize", "detected_comp": "Detected %s compression!", "unk_type": "Unknown pixel type: %s", "crt_pic": "Creating picture...", "join_pic": "Joining picture...", "png_save": "Saving to png...", "saved": "Saving completed!", - "not_xcod": ".xcod file doesn\"t exist!", - "default_types": "We will use default fileType and subType (1, 0).\n And also LZMA compression (as in Brawl Stars sc)", + "xcod_not_found": "File '%s.xcod' doesn't exist!", "illegal_size": "Illegal image size! Expected %sx%s but we got %sx%s", "resize_qu": "Would you like to resize an image?", "resizing": "Resizing...", @@ -69,16 +68,16 @@ "compressing_with": "Compressing texture with %s...", "compression_error": "Compression failed", "compression_done": "Compression done!", - "dir_empty": "Dir \"%s\" is empty!", - "not_found": "\"%s\" file isn\"t found!", - "cut_sprites_process": "Cutting sprites... (%s/%s)", - "place_sprites_process": "Placing sprites... (%s/%s)", + "dir_empty": "Dir '%s' is empty!", + "not_found": "File '%s' not found!", + "cut_sprites_process": "Cutting sprites... (%d/%d)", + "place_sprites_process": "Placing sprites... (%d/%d)", "not_implemented": "This feature will be added in future updates.\nYou can follow XCoder updates here: github.com/Vorono4ka/XCoder", "want_exit": "Want to exit?", "dec_sc": "Decoding .sc file...", "error": "ERROR! (%s.%s: %s)", "e1sc1": "Overwrite SC sprites", - "cgl": "Changelog:\n%s\n", + "cgl": "Changelog:\n%s", "upd_av": "\nUpdate is available!\nVersion: %s\n", "upd_qu": "Do you want to update?", "upd": "Updating...", @@ -89,5 +88,5 @@ "enabled": "Enabled", "disabled": "Disabled", - "install_to_unlock": "Install \"%s\" to unlock more functions!" + "install_to_unlock": "Install '%s' to unlock more functions!" } \ No newline at end of file diff --git a/system/languages/ru-RU.json b/system/languages/ru-RU.json index c375d33..86f6b80 100644 --- a/system/languages/ru-RU.json +++ b/system/languages/ru-RU.json @@ -26,7 +26,7 @@ "encode_sc_description": "Конвертирует PNG в SC", "decode_by_parts_description": "Конвертирует SC в PNG и разрезает текстуру на части", "encode_by_parts_description": "Собирает части текстуры и конвертирует PNG в SC", - "overwrite_by_parts_description": "Повторяет действие предыдущей функции, но учитывает папку \"overwrite\"", + "overwrite_by_parts_description": "Повторяет действие предыдущей функции, но учитывает папку 'overwrite'", "csv_label": "CSV - Таблицы", "decompress_csv": "Разжать CSV", @@ -58,8 +58,7 @@ "join_pic": "Соединяем картинку...", "png_save": "Сохраняем в png...", "saved": "Сохранение прошло успешно!", - "not_xcod": ".xcod файл не обнаружен!", - "default_types": "Мы используем стандартные типы текстур (1, 0).\n И ещё LZMA сжатие (как в SC из Brawl Stars)", + "xcod_not_found": "Файл '%s.xcod' не обнаружен!", "illegal_size": "Размер картинки не совпадает с оригиналом! Ожидалось %sx%s, но мы получили %sx%s", "resize_qu": "Хотите изменить размер?", "resizing": "Изменяем размер...", @@ -69,16 +68,16 @@ "compressing_with": "Сохраняем с применением %s сжатия...", "compression_error": "Сжатие не удалось", "compression_done": "Сжатие прошло успешно!", - "dir_empty": "Папка \"%s\" пуста!", - "not_found": "Не был найден \"%s\" файл!", - "cut_sprites_process": "Вырезаем спрайты... (%s/%s)", - "place_sprites_process": "Ставим спрайты на место... (%s/%s)", + "dir_empty": "Папка '%s' пуста!", + "not_found": "Файл '%s' не найден!", + "cut_sprites_process": "Вырезаем спрайты... (%d/%d)", + "place_sprites_process": "Ставим спрайты на место... (%d/%d)", "not_implemented": "Данная возможность будет добавлена в будущих обновлениях.\nЗа обновлениями XCoder вы можете следить здесь: github.com/Vorono4ka/XCoder", "want_exit": "Хотите выйти?", "dec_sc": "Декодируем SC...", "error": "ОШИБКА! (%s.%s: %s)", "e1sc1": "Перезапись спрайтов", - "cgl": "Список изменений:\n%s\n", + "cgl": "Список изменений: \n%s", "upd_av": "\nДоступно обновление!\nВерсия: %s\n", "upd_qu": "Обновить?", "upd": "Обновляем...", @@ -89,5 +88,5 @@ "enabled": "Включено", "disabled": "Выключено", - "install_to_unlock": "Установите \"%s\" чтобы открыть больше функций!" + "install_to_unlock": "Установите '%s' чтобы открыть больше функций!" } \ No newline at end of file diff --git a/system/lib/features/place_sprites.py b/system/lib/features/place_sprites.py index d41d5c9..706ebc0 100644 --- a/system/lib/features/place_sprites.py +++ b/system/lib/features/place_sprites.py @@ -2,52 +2,47 @@ from PIL import Image, ImageDraw -from system.bytestream import Reader from system.lib import Console +from system.lib.images import pixel_type2str +from system.lib.xcod import parse_info, FileInfo from system.localization import locale -def place_sprites(xcod, folder, overwrite=False): - xcod = Reader(open(xcod, 'rb').read(), 'big') - files = os.listdir(f'{folder}{"/overwrite" if overwrite else ""}') - tex = os.listdir(f'{folder}/textures') - - xcod.read(4) - use_lzham, pictures_count = xcod.read_ubyte(), xcod.read_ubyte() - sheet_image = [] - sheet_image_data = {'use_lzham': use_lzham, 'data': []} - for i in range(pictures_count): - file_type, sub_type, width, height = xcod.read_ubyte(), \ - xcod.read_ubyte(), \ - xcod.read_uint16(), \ - xcod.read_uint16() - sheet_image.append( - Image.open(f'{folder}/textures/{tex[i]}') +def place_sprites(xcod_path: str, folder: str, overwrite: bool = False) -> (list, FileInfo): + file_info, xcod = parse_info(xcod_path) + + files_to_overwrite = os.listdir(f'{folder}{"/overwrite" if overwrite else ""}') + texture_files = os.listdir(f'{folder}/textures') + + sheets = [] + for i in range(len(file_info.sheets)): + sheet_info = file_info.sheets[i] + + sheets.append( + Image.open(f'{folder}/textures/{texture_files[i]}') if overwrite else - Image.new('RGBA', (width, height))) - sheet_image_data['data'].append({'file_type': file_type, 'pixel_type': sub_type}) + Image.new(pixel_type2str(sheet_info.pixel_type), sheet_info.size) + ) shapes_count = xcod.read_uint16() for shape_index in range(shapes_count): Console.progress_bar(locale.place_sprites_process % (shape_index + 1, shapes_count), shape_index, shapes_count) shape_id = xcod.read_uint16() - regions_count = xcod.read_uint16() + regions_count = xcod.read_uint16() for region_index in range(regions_count): texture_id, points_count = xcod.read_ubyte(), xcod.read_ubyte() - texture_width, texture_height = sheet_image[texture_id].width, sheet_image[texture_id].height + texture_width, texture_height = sheets[texture_id].width, sheets[texture_id].height polygon = [(xcod.read_uint16(), xcod.read_uint16()) for _ in range(points_count)] mirroring, rotation = xcod.read_ubyte() == 1, xcod.read_ubyte() * 90 filename = f'shape_{shape_id}_{region_index}.png' - if filename not in files: + if filename not in files_to_overwrite: continue tmp_region = Image.open( f'{folder}{"/overwrite" if overwrite else ""}/{filename}' - ) \ - .convert('RGBA') \ - .rotate(360 - rotation, expand=True) + ).convert('RGBA').rotate(360 - rotation, expand=True) img_mask = Image.new('L', (texture_width, texture_height), 0) color = 255 @@ -71,23 +66,25 @@ def place_sprites(xcod, folder, overwrite=False): img_mask.putpixel((max_x - 1, max_y - 1), color) bbox = img_mask.getbbox() - a, b, c, d = bbox - if c - a - 1: - c -= 1 - if d - b - 1: - d -= 1 + left, top, right, bottom = bbox + if right - left - 1: + right -= 1 + if bottom - top - 1: + bottom -= 1 - bbox = a, b, c, d + bbox = left, top, right, bottom - region_size = bbox[2] - bbox[0], bbox[3] - bbox[1] + width = right - left + height = bottom - top + region_size = width, height tmp_region = tmp_region.resize(region_size, Image.ANTIALIAS) if mirroring: tmp_region = tmp_region.transform(region_size, Image.EXTENT, (tmp_region.width, 0, 0, tmp_region.height)) - sheet_image[texture_id].paste(Image.new('RGBA', region_size), bbox[:2], img_mask.crop(bbox)) - sheet_image[texture_id].paste(tmp_region, bbox[:2], tmp_region) + sheets[texture_id].paste(Image.new('RGBA', region_size), (left, top), img_mask.crop(bbox)) + sheets[texture_id].paste(tmp_region, (left, top), tmp_region) print() - return sheet_image, sheet_image_data + return sheets, file_info diff --git a/system/lib/features/sc/__init__.py b/system/lib/features/sc/__init__.py index a414822..4ef1f16 100644 --- a/system/lib/features/sc/__init__.py +++ b/system/lib/features/sc/__init__.py @@ -8,15 +8,15 @@ from system.lib.console import Console from system.lib.features.files import write_sc from system.lib.images import get_pixel_size, split_image, rgba2bytes +from system.lib.xcod import FileInfo from system.localization import locale -def compile_sc(_dir, from_memory=None, img_data=None, folder_export=None): - sc_data = None - +def compile_sc(_dir, file_info: FileInfo, sheets: list = None, output_folder: str = None): name = _dir.split('/')[-2] - if from_memory: - files = from_memory + + if sheets: + files = sheets else: files = [] [files.append(i) if i.endswith('.png') else None for i in os.listdir(_dir)] @@ -28,39 +28,22 @@ def compile_sc(_dir, from_memory=None, img_data=None, folder_export=None): logger.info(locale.collecting_inf) sc = Writer() - has_xcod = False - use_lzham = False - if from_memory: - use_lzham = img_data['use_lzham'] - else: - try: - sc_data = open(f'{_dir}/{name}.xcod', 'rb') - sc_data.read(4) - use_lzham, = struct.unpack('?', sc_data.read(1)) - sc_data.read(1) - has_xcod = True - except OSError: - logger.info(locale.not_xcod) - logger.info(locale.default_types) + use_lzham = file_info.use_lzham for picture_index in range(len(files)): + sheet_info = file_info.sheets[picture_index] img = files[picture_index] print() - if from_memory: - file_type = img_data['data'][picture_index]['file_type'] - pixel_type = img_data['data'][picture_index]['pixel_type'] - else: - if has_xcod: - file_type, pixel_type, width, height = struct.unpack('>BBHH', sc_data.read(6)) - - if (width, height) != img.size: - logger.info(locale.illegal_size % (width, height, img.width, img.height)) - if Console.question(locale.resize_qu): - logger.info(locale.resizing) - img = img.resize((width, height), Image.ANTIALIAS) - else: - file_type, pixel_type = 1, 0 + file_type = sheet_info.file_type + pixel_type = sheet_info.pixel_type + + if img.size != sheet_info.size: + logger.info(locale.illegal_size % (sheet_info.width, sheet_info.height, img.width, img.height)) + + if Console.question(locale.resize_qu): + logger.info(locale.resizing) + img = img.resize(sheet_info.size, Image.ANTIALIAS) width, height = img.size pixel_size = get_pixel_size(pixel_type) @@ -81,4 +64,4 @@ def compile_sc(_dir, from_memory=None, img_data=None, folder_export=None): sc.write(bytes(5)) print() - write_sc(f'{folder_export}/{name}.sc', sc.getvalue(), use_lzham) + write_sc(f'{output_folder}/{name}.sc', sc.getvalue(), use_lzham) diff --git a/system/lib/features/sc/assembly_encode.py b/system/lib/features/sc/assembly_encode.py index 546d1de..c0ae976 100644 --- a/system/lib/features/sc/assembly_encode.py +++ b/system/lib/features/sc/assembly_encode.py @@ -9,7 +9,7 @@ def sc1_encode(overwrite: bool = False): folder = './SC/In-Sprites/' - folder_export = './SC/Out-Compressed/' + output_folder = './SC/Out-Compressed/' files = os.listdir(folder) for file in files: @@ -19,9 +19,9 @@ def sc1_encode(overwrite: bool = False): else: try: logger.info(locale.dec_sc) - sheet_image, sheet_image_data = place_sprites(f'{folder}{file}/{xcod}', f'{folder}{file}', overwrite) + sheets, file_info = place_sprites(f'{folder}{file}/{xcod}', f'{folder}{file}', overwrite) logger.info(locale.dec_sc) - compile_sc(f'{folder}{file}/', sheet_image, sheet_image_data, folder_export) + compile_sc(f'{folder}{file}/', file_info, sheets, output_folder) except Exception as exception: logger.exception(locale.error % (exception.__class__.__module__, exception.__class__.__name__, exception)) print() diff --git a/system/lib/features/sc/sc_encode.py b/system/lib/features/sc/sc_encode.py index cbfb7cc..966fb11 100644 --- a/system/lib/features/sc/sc_encode.py +++ b/system/lib/features/sc/sc_encode.py @@ -3,17 +3,22 @@ from loguru import logger from system.lib.features.sc import compile_sc +from system.lib.xcod import parse_info from system.localization import locale def sc_encode(): - folder = './SC/In-Decompressed' - folder_export = './SC/Out-Compressed' + input_folder = './SC/In-Decompressed' + output_folder = './SC/Out-Compressed' - for file in os.listdir(folder): + for folder in os.listdir(input_folder): try: - compile_sc(f'{folder}/{file}/', folder_export=folder_export) + file_info, _ = parse_info(f'{input_folder}/{folder}/{folder}.xcod') + + compile_sc(f'{input_folder}/{folder}/', file_info, output_folder=output_folder) + except FileNotFoundError: + logger.info(locale.xcod_not_found % folder) except Exception as exception: logger.exception(locale.error % (exception.__class__.__module__, exception.__class__.__name__, exception)) - print() \ No newline at end of file + print() diff --git a/system/lib/features/update/check.py b/system/lib/features/update/check.py index 2ac3120..eb0d185 100644 --- a/system/lib/features/update/check.py +++ b/system/lib/features/update/check.py @@ -63,6 +63,7 @@ def check_update(): check_for_outdated() + logger.info(locale.check_update) if config.version != latest_tag_name: logger.error(locale.not_latest) diff --git a/system/lib/images.py b/system/lib/images.py index e6c7504..ecc7ba5 100644 --- a/system/lib/images.py +++ b/system/lib/images.py @@ -111,7 +111,7 @@ def get_pixel_size(_type): def pixel_type2str(_type): - if _type in range(4): + if _type in (0, 1, 2, 3): return 'RGBA' elif _type == 4: return 'RGB' diff --git a/system/lib/xcod.py b/system/lib/xcod.py new file mode 100644 index 0000000..5600884 --- /dev/null +++ b/system/lib/xcod.py @@ -0,0 +1,49 @@ +from dataclasses import dataclass +from typing import List, Tuple + +from system.bytestream import Reader + + +@dataclass +class SheetInfo: + file_type: int + pixel_type: int + size: Tuple[int, int] + + @property + def width(self) -> int: + return self.size[0] + + @property + def height(self) -> int: + return self.size[1] + + +@dataclass +class FileInfo: + use_lzham: bool + sheets: List[SheetInfo] + + +def parse_info(xcod_path: str) -> (FileInfo, Reader): + with open(xcod_path, 'rb') as file: + xcod = Reader(file.read(), 'big') + + magic = xcod.read(4) + if magic != b'XCOD': + raise IOError('Unknown file MAGIC: ' + magic.hex()) + + use_lzham = xcod.read_ubyte() + + file_info = FileInfo(use_lzham, []) + + sheets_count = xcod.read_ubyte() + for i in range(sheets_count): + file_type = xcod.read_ubyte() + pixel_type = xcod.read_ubyte() + width = xcod.read_uint16() + height = xcod.read_uint16() + + file_info.sheets.append(SheetInfo(file_type, pixel_type, (width, height))) + + return file_info, xcod diff --git a/system/localization.py b/system/localization.py index 9f74ac6..4b3c181 100644 --- a/system/localization.py +++ b/system/localization.py @@ -62,8 +62,7 @@ def __init__(self): self.join_pic = None self.png_save = None self.saved = None - self.not_xcod = None - self.default_types = None + self.xcod_not_found = None self.illegal_size = None self.resize_qu = None self.resizing = None