diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2c04275..21def41 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,20 +1,18 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 + rev: v5.0.0 hooks: - id: end-of-file-fixer - repo: https://github.com/PyCQA/isort - rev: 5.12.0 + rev: 6.0.1 hooks: - id: isort args: ['--profile','black'] - - repo: https://github.com/psf/black - rev: 22.12.0 - hooks: - - id: black - language_version: python3.11 - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: 'v0.0.261' + rev: v0.11.6 hooks: + # Run the linter. - id: ruff - args: [--fix, --exit-non-zero-on-fix] + args: [ --fix ] + # Run the formatter. + - id: ruff-format diff --git a/xcoder/features/place_sprites.py b/xcoder/features/place_sprites.py index 747a7f3..647e01b 100644 --- a/xcoder/features/place_sprites.py +++ b/xcoder/features/place_sprites.py @@ -79,7 +79,7 @@ def place_sprites( bbox = int(rect.left), int(rect.top), int(rect.right), int(rect.bottom) region_image = Image.open( - f'{folder}{"/overwrite" if overwrite else ""}/{filename}' + f"{folder}{'/overwrite' if overwrite else ''}/{filename}" ).convert("RGBA") sheets[region_info.texture_id].paste( diff --git a/xcoder/features/sc/__init__.py b/xcoder/features/sc/__init__.py index cccde9f..2faad0b 100644 --- a/xcoder/features/sc/__init__.py +++ b/xcoder/features/sc/__init__.py @@ -42,7 +42,13 @@ def compile_sc( file_size = width * height * pixel_size + 5 logger.info( - locale.about_sc % (file_info.name, picture_index, pixel_type, width, height) + locale.about_sc.format( + filename=file_info.name, + index=picture_index, + pixel_type=pixel_type, + width=width, + height=height, + ) ) sc.write(struct.pack(" list: output = get_run_output( - f'pip --disable-pip-version-check list {"-o" if outdated else ""}' + f"pip --disable-pip-version-check list {'-o' if outdated else ''}" ) output = output.splitlines() output = output[2:] diff --git a/xcoder/images.py b/xcoder/images.py index 4d5a15c..3575aa8 100644 --- a/xcoder/images.py +++ b/xcoder/images.py @@ -11,51 +11,61 @@ from .localization import locale from .math.point import Point from .matrices import Matrix2x3 -from .pixel_utils import ( - get_channel_count_by_pixel_type, - get_pixel_encode_function, - get_read_function, -) +from .pixel_utils import get_pixel_encode_function, get_raw_mode CHUNK_SIZE = 32 -def load_image_from_buffer(pixel_type: int, width: int, height: int) -> Image.Image: - with open("pixel_buffer", "rb") as pixel_buffer: - pixel_buffer.read(1) - - return Image.frombuffer( - get_format_by_pixel_type(pixel_type), (width, height), pixel_buffer.read() - ) +def load_image_from_buffer( + pixel_type: int, width: int, height: int, pixel_buffer: Reader +) -> Image.Image: + raw_mode = get_raw_mode(pixel_type) + bytes_per_pixel = get_byte_count_by_pixel_type(pixel_type) + + return Image.frombuffer( + get_format_by_pixel_type(pixel_type), + (width, height), + pixel_buffer.read(width * height * bytes_per_pixel), + "raw", + raw_mode, + 0, + 1, + ) -def join_image(pixel_type: int, width: int, height: int) -> Image.Image: +def join_image( + pixel_type: int, width: int, height: int, pixel_buffer: Reader +) -> Image.Image: mode = get_format_by_pixel_type(pixel_type) + bytes_per_pixel = get_byte_count_by_pixel_type(pixel_type) image = Image.new(mode, (width, height)) - with open("pixel_buffer", "rb") as pixel_buffer: - channel_count = int.from_bytes(pixel_buffer.read(1), "little") + chunk_count_x = math.ceil(width / CHUNK_SIZE) + chunk_count_y = math.ceil(height / CHUNK_SIZE) + chunk_count = chunk_count_x * chunk_count_y - chunk_count_x = math.ceil(width / CHUNK_SIZE) - chunk_count_y = math.ceil(height / CHUNK_SIZE) - chunk_count = chunk_count_x * chunk_count_y + raw_mode = get_raw_mode(pixel_type) - for chunk_index in range(chunk_count): - chunk_x = chunk_index % chunk_count_x - chunk_y = chunk_index // chunk_count_x + for chunk_index in range(chunk_count): + chunk_x = chunk_index % chunk_count_x + chunk_y = chunk_index // chunk_count_x - chunk_width = min(width - chunk_x * CHUNK_SIZE, CHUNK_SIZE) - chunk_height = min(height - chunk_y * CHUNK_SIZE, CHUNK_SIZE) + chunk_width = min(width - chunk_x * CHUNK_SIZE, CHUNK_SIZE) + chunk_height = min(height - chunk_y * CHUNK_SIZE, CHUNK_SIZE) - sub_image = Image.frombuffer( - mode, - (chunk_width, chunk_height), - pixel_buffer.read(channel_count * chunk_width * chunk_height), - ) + sub_image = Image.frombuffer( + mode, + (chunk_width, chunk_height), + pixel_buffer.read(bytes_per_pixel * chunk_width * chunk_height), + "raw", + raw_mode, + 0, + 1, + ) - image.paste(sub_image, (chunk_x * CHUNK_SIZE, chunk_y * CHUNK_SIZE)) + image.paste(sub_image, (chunk_x * CHUNK_SIZE, chunk_y * CHUNK_SIZE)) - Console.progress_bar(locale.join_pic, chunk_index, chunk_count) + Console.progress_bar(locale.join_pic, chunk_index, chunk_count) return image @@ -125,23 +135,6 @@ def get_format_by_pixel_type(pixel_type: int) -> str: raise Exception(locale.unknown_pixel_type % pixel_type) -def load_texture(reader: Reader, pixel_type: int, width: int, height: int) -> None: - channel_count = get_channel_count_by_pixel_type(pixel_type) - read_pixel = get_read_function(pixel_type) - if read_pixel is None: - raise Exception(locale.unknown_pixel_type % pixel_type) - - with open("pixel_buffer", "wb") as pixel_buffer: - pixel_buffer.write(channel_count.to_bytes(1, "little")) - - for y in range(height): - pixel_buffer.write( - b"".join([bytearray(read_pixel(reader)) for _ in range(width)]) - ) - - Console.progress_bar(locale.crt_pic, y, height) - - def save_texture(writer: Writer, image: Image.Image, pixel_type: int) -> None: encode_pixel = get_pixel_encode_function(pixel_type) if encode_pixel is None: diff --git a/xcoder/languages/en-EU.json b/xcoder/languages/en-EU.json index a26d6b4..5214fea 100644 --- a/xcoder/languages/en-EU.json +++ b/xcoder/languages/en-EU.json @@ -54,7 +54,7 @@ "not_latest": "Not the latest version installed", "collecting_inf": "File: %s. Collecting information...", - "about_sc": "About texture. Filename: %s (%d), Pixel type: %d, Size: %sx%s", + "about_sc": "About texture. Filename: {filename} ({index}), Pixel type: {pixel_type}, Size: {width}x{height}", "decompression_error": "Error while decompressing! Trying to decode as is...", "skip_not_installed": "%s isn't installed! Reinitialize", "detected_comp": "%s compression detected", diff --git a/xcoder/languages/ua-UA.json b/xcoder/languages/ua-UA.json index 357c8c2..7b69056 100644 --- a/xcoder/languages/ua-UA.json +++ b/xcoder/languages/ua-UA.json @@ -54,7 +54,7 @@ "not_latest": "Встановлена не остання версія", "collecting_inf": "Файл: %s. Збираємо інформацію...", - "about_sc": "Про текстуру. Назва файлу: %s (%d), Тип пікселів: %d, Величина: %sx%s", + "about_sc": "Про текстуру. Назва файлу: {filename} ({index}), Тип пікселів: {pixel_type}, Величина: {width}x{height}", "decompression_error": "Помилка при розпакуванні! Намагаємся розпакувати як є...", "skip_not_installed": "%s не встановлений, повторіть налаштування", "detected_comp": "Виявлено компресію: %s", diff --git a/xcoder/languages/zh-CN.json b/xcoder/languages/zh-CN.json index d080bad..aebf14f 100644 --- a/xcoder/languages/zh-CN.json +++ b/xcoder/languages/zh-CN.json @@ -54,7 +54,7 @@ "not_latest": "安装的不是最新版本", "collecting_inf": "文件:%s。正在收集信息...", - "about_sc": "关于纹理。文件名:%s (%d),像素类型:%d,尺寸:%sx%s", + "about_sc": "关于纹理。文件名:{filename} ({index}),像素类型:{pixel_type},尺寸:{width}x{height}", "decompression_error": "解压时出错!尝试直接解码...", "skip_not_installed": "%s 未安装!请重新初始化", "detected_comp": "检测到 %s 压缩", diff --git a/xcoder/main_menu.py b/xcoder/main_menu.py index a5974be..416cfdc 100644 --- a/xcoder/main_menu.py +++ b/xcoder/main_menu.py @@ -23,7 +23,7 @@ def check_auto_update(): def check_files_updated(): if config.has_update: - logger.opt(colors=True).info(f'{locale.update_done % ""}') + logger.opt(colors=True).info(f"{locale.update_done % ''}") if Console.question(locale.done_qu): latest_tag = get_tags(config.repo_owner, config.repo_name)[0] latest_tag_name = latest_tag["name"][1:] diff --git a/xcoder/objects/plain_object.py b/xcoder/objects/plain_object.py index 033b694..f3071ab 100644 --- a/xcoder/objects/plain_object.py +++ b/xcoder/objects/plain_object.py @@ -9,5 +9,4 @@ class PlainObject(ABC): @abstractmethod - def load(self, swf: SupercellSWF, tag: int): - ... + def load(self, swf: SupercellSWF, tag: int): ... diff --git a/xcoder/objects/renderable/display_object.py b/xcoder/objects/renderable/display_object.py index b79db36..350b912 100644 --- a/xcoder/objects/renderable/display_object.py +++ b/xcoder/objects/renderable/display_object.py @@ -14,12 +14,10 @@ def __init__(self): self._color_transform = ColorTransform() @abstractmethod - def calculate_bounds(self, matrix: Matrix2x3) -> Rect: - ... + def calculate_bounds(self, matrix: Matrix2x3) -> Rect: ... @abstractmethod - def render(self, matrix: Matrix2x3) -> Image.Image: - ... + def render(self, matrix: Matrix2x3) -> Image.Image: ... def set_matrix(self, matrix: Matrix2x3): self._matrix = matrix diff --git a/xcoder/objects/shape/region.py b/xcoder/objects/shape/region.py index 0d2a085..b286657 100644 --- a/xcoder/objects/shape/region.py +++ b/xcoder/objects/shape/region.py @@ -93,7 +93,7 @@ def get_image(self) -> Image.Image: height = max(int(rect.height), 1) if width + height <= 2: # The same speed as without this return return Image.new( - "RGBA", + self._texture.image.mode, (1, 1), color=self._texture.image.getpixel((int(rect.left), int(rect.top))), ) @@ -102,12 +102,13 @@ def get_image(self) -> Image.Image: "L", self._texture.width, self._texture.height, self._uv_points, 0xFF ) - rendered_region = Image.new("RGBA", (width, height)) + rendered_region = Image.new(self._texture.image.mode, (width, height)) rendered_region.paste( self._texture.image.crop(rect.as_tuple()), (0, 0), mask_image.crop(rect.as_tuple()), ) + rendered_region = rendered_region.convert("RGBA") self._cache_image = rendered_region diff --git a/xcoder/objects/texture.py b/xcoder/objects/texture.py index 1838430..24b4049 100644 --- a/xcoder/objects/texture.py +++ b/xcoder/objects/texture.py @@ -1,12 +1,12 @@ from __future__ import annotations -import os from typing import TYPE_CHECKING import zstandard from PIL import Image -from xcoder.images import join_image, load_image_from_buffer, load_texture +from xcoder.bytestream import Reader +from xcoder.images import join_image, load_image_from_buffer from xcoder.pvr_tex_tool import get_image_from_ktx_data if TYPE_CHECKING: @@ -54,14 +54,10 @@ def load(self, swf: SupercellSWF, tag: int, has_texture: bool): ) return - try: - load_texture(swf.reader, self.pixel_type, self.width, self.height) + self.image = self._load_texture(swf.reader, tag) - if tag in (27, 28, 29): - image = join_image(self.pixel_type, self.width, self.height) - else: - image = load_image_from_buffer(self.pixel_type, self.width, self.height) - finally: - os.remove("pixel_buffer") + def _load_texture(self, reader: Reader, tag: int) -> Image.Image: + if tag in (27, 28, 29): + return join_image(self.pixel_type, self.width, self.height, reader) - self.image = image + return load_image_from_buffer(self.pixel_type, self.width, self.height, reader) diff --git a/xcoder/pixel_utils.py b/xcoder/pixel_utils.py index 20ace88..110fbd3 100644 --- a/xcoder/pixel_utils.py +++ b/xcoder/pixel_utils.py @@ -1,15 +1,18 @@ import struct -from typing import Callable, TypeAlias +from typing import Callable, Literal, TypeAlias -from xcoder.bytestream import Reader +from xcoder.localization import locale PixelChannels: TypeAlias = tuple[int, ...] EncodeFunction: TypeAlias = Callable[[PixelChannels], bytes] -DecodeFunction: TypeAlias = Callable[[Reader], PixelChannels] +RawMode: TypeAlias = Literal["RGBA", "RGBA;4B", "RGBA;15", "BGR;16", "LA", "L"] -def get_read_function(pixel_type: int) -> DecodeFunction | None: - return _decode_functions.get(pixel_type, None) +def get_raw_mode(pixel_type: int) -> RawMode: + if pixel_type in _raw_modes: + return _raw_modes[pixel_type] + + raise Exception(locale.unknown_pixel_type % pixel_type) def get_pixel_encode_function(pixel_type: int) -> EncodeFunction | None: @@ -26,43 +29,6 @@ def get_channel_count_by_pixel_type(pixel_type: int) -> int: return 4 -def _read_rgba8(reader: Reader) -> PixelChannels: - return tuple(reader.read(4)) - - -def _read_rgba4(reader: Reader) -> PixelChannels: - p = reader.read_ushort() - return ( - (p >> 12 & 15) << 4, - (p >> 8 & 15) << 4, - (p >> 4 & 15) << 4, - (p >> 0 & 15) << 4, - ) - - -def _read_rgb5a1(reader: Reader) -> PixelChannels: - p = reader.read_ushort() - return ( - (p >> 11 & 31) << 3, - (p >> 6 & 31) << 3, - (p >> 1 & 31) << 3, - (p & 255) << 7, - ) - - -def _read_rgb565(reader: Reader) -> PixelChannels: - p = reader.read_ushort() - return (p >> 11 & 31) << 3, (p >> 5 & 63) << 2, (p & 31) << 3 - - -def _read_luminance8_alpha8(reader: Reader) -> PixelChannels: - return tuple(reader.read(2))[::-1] - - -def _read_luminance8(reader: Reader) -> PixelChannels: - return tuple(reader.read(1)) - - def _write_rgba8(pixel: PixelChannels) -> bytes: return struct.pack("4B", *pixel) @@ -109,12 +75,13 @@ def _write_luminance8(pixel: PixelChannels) -> bytes: 10: _write_luminance8, } -_decode_functions: dict[int, DecodeFunction] = { - 0: _read_rgba8, - 1: _read_rgba8, - 2: _read_rgba4, - 3: _read_rgb5a1, - 4: _read_rgb565, - 6: _read_luminance8_alpha8, - 10: _read_luminance8, +# here is a problem with this names https://github.com/python-pillow/Pillow/pull/8158 +_raw_modes: dict[int, RawMode] = { + 0: "RGBA", + 1: "RGBA", + 2: "RGBA;4B", # ABGR;4 + 3: "RGBA;15", # ABGR;1555 + 4: "BGR;16", # RGB;565 + 6: "LA", + 10: "L", } diff --git a/xcoder/swf.py b/xcoder/swf.py index cee103c..b52661d 100644 --- a/xcoder/swf.py +++ b/xcoder/swf.py @@ -163,16 +163,14 @@ def _load_tags(self, is_texture_file: bool) -> bool: if has_texture: logger.info( - locale.about_sc - % ( - self.filename, - texture_id, - texture.pixel_type, - texture.width, - texture.height, + locale.about_sc.format( + filename=self.filename, + index=texture_id, + pixel_type=texture.pixel_type, + width=texture.width, + height=texture.height, ) ) - print() self.xcod_writer.write_ubyte(tag) self.xcod_writer.write_ubyte(texture.pixel_type)