Skip to content

Commit d6bc436

Browse files
refactor(texture): texture loading is now blazingly fast
1 parent 5052e07 commit d6bc436

15 files changed

+96
-140
lines changed

.pre-commit-config.yaml

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
11
repos:
22
- repo: https://github.com/pre-commit/pre-commit-hooks
3-
rev: v4.0.1
3+
rev: v5.0.0
44
hooks:
55
- id: end-of-file-fixer
66
- repo: https://github.com/PyCQA/isort
7-
rev: 5.12.0
7+
rev: 6.0.1
88
hooks:
99
- id: isort
1010
args: ['--profile','black']
11-
- repo: https://github.com/psf/black
12-
rev: 22.12.0
13-
hooks:
14-
- id: black
15-
language_version: python3.11
1611
- repo: https://github.com/charliermarsh/ruff-pre-commit
17-
rev: 'v0.0.261'
12+
rev: v0.11.6
1813
hooks:
14+
# Run the linter.
1915
- id: ruff
20-
args: [--fix, --exit-non-zero-on-fix]
16+
args: [ --fix ]
17+
# Run the formatter.
18+
- id: ruff-format

xcoder/features/place_sprites.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def place_sprites(
7979
bbox = int(rect.left), int(rect.top), int(rect.right), int(rect.bottom)
8080

8181
region_image = Image.open(
82-
f'{folder}{"/overwrite" if overwrite else ""}/{filename}'
82+
f"{folder}{'/overwrite' if overwrite else ''}/{filename}"
8383
).convert("RGBA")
8484

8585
sheets[region_info.texture_id].paste(

xcoder/features/sc/__init__.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,13 @@ def compile_sc(
4242
file_size = width * height * pixel_size + 5
4343

4444
logger.info(
45-
locale.about_sc % (file_info.name, picture_index, pixel_type, width, height)
45+
locale.about_sc.format(
46+
filename=file_info.name,
47+
index=picture_index,
48+
pixel_type=pixel_type,
49+
width=width,
50+
height=height,
51+
)
4652
)
4753

4854
sc.write(struct.pack("<BIBHH", file_type, file_size, pixel_type, width, height))

xcoder/features/update/check.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def get_run_output(command: str):
2727

2828
def get_pip_info(outdated: bool = False) -> list:
2929
output = get_run_output(
30-
f'pip --disable-pip-version-check list {"-o" if outdated else ""}'
30+
f"pip --disable-pip-version-check list {'-o' if outdated else ''}"
3131
)
3232
output = output.splitlines()
3333
output = output[2:]

xcoder/images.py

Lines changed: 40 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -11,51 +11,61 @@
1111
from .localization import locale
1212
from .math.point import Point
1313
from .matrices import Matrix2x3
14-
from .pixel_utils import (
15-
get_channel_count_by_pixel_type,
16-
get_pixel_encode_function,
17-
get_read_function,
18-
)
14+
from .pixel_utils import get_pixel_encode_function, get_raw_mode
1915

2016
CHUNK_SIZE = 32
2117

2218

23-
def load_image_from_buffer(pixel_type: int, width: int, height: int) -> Image.Image:
24-
with open("pixel_buffer", "rb") as pixel_buffer:
25-
pixel_buffer.read(1)
26-
27-
return Image.frombuffer(
28-
get_format_by_pixel_type(pixel_type), (width, height), pixel_buffer.read()
29-
)
19+
def load_image_from_buffer(
20+
pixel_type: int, width: int, height: int, pixel_buffer: Reader
21+
) -> Image.Image:
22+
raw_mode = get_raw_mode(pixel_type)
23+
bytes_per_pixel = get_byte_count_by_pixel_type(pixel_type)
24+
25+
return Image.frombuffer(
26+
get_format_by_pixel_type(pixel_type),
27+
(width, height),
28+
pixel_buffer.read(width * height * bytes_per_pixel),
29+
"raw",
30+
raw_mode,
31+
0,
32+
1,
33+
)
3034

3135

32-
def join_image(pixel_type: int, width: int, height: int) -> Image.Image:
36+
def join_image(
37+
pixel_type: int, width: int, height: int, pixel_buffer: Reader
38+
) -> Image.Image:
3339
mode = get_format_by_pixel_type(pixel_type)
40+
bytes_per_pixel = get_byte_count_by_pixel_type(pixel_type)
3441
image = Image.new(mode, (width, height))
3542

36-
with open("pixel_buffer", "rb") as pixel_buffer:
37-
channel_count = int.from_bytes(pixel_buffer.read(1), "little")
43+
chunk_count_x = math.ceil(width / CHUNK_SIZE)
44+
chunk_count_y = math.ceil(height / CHUNK_SIZE)
45+
chunk_count = chunk_count_x * chunk_count_y
3846

39-
chunk_count_x = math.ceil(width / CHUNK_SIZE)
40-
chunk_count_y = math.ceil(height / CHUNK_SIZE)
41-
chunk_count = chunk_count_x * chunk_count_y
47+
raw_mode = get_raw_mode(pixel_type)
4248

43-
for chunk_index in range(chunk_count):
44-
chunk_x = chunk_index % chunk_count_x
45-
chunk_y = chunk_index // chunk_count_x
49+
for chunk_index in range(chunk_count):
50+
chunk_x = chunk_index % chunk_count_x
51+
chunk_y = chunk_index // chunk_count_x
4652

47-
chunk_width = min(width - chunk_x * CHUNK_SIZE, CHUNK_SIZE)
48-
chunk_height = min(height - chunk_y * CHUNK_SIZE, CHUNK_SIZE)
53+
chunk_width = min(width - chunk_x * CHUNK_SIZE, CHUNK_SIZE)
54+
chunk_height = min(height - chunk_y * CHUNK_SIZE, CHUNK_SIZE)
4955

50-
sub_image = Image.frombuffer(
51-
mode,
52-
(chunk_width, chunk_height),
53-
pixel_buffer.read(channel_count * chunk_width * chunk_height),
54-
)
56+
sub_image = Image.frombuffer(
57+
mode,
58+
(chunk_width, chunk_height),
59+
pixel_buffer.read(bytes_per_pixel * chunk_width * chunk_height),
60+
"raw",
61+
raw_mode,
62+
0,
63+
1,
64+
)
5565

56-
image.paste(sub_image, (chunk_x * CHUNK_SIZE, chunk_y * CHUNK_SIZE))
66+
image.paste(sub_image, (chunk_x * CHUNK_SIZE, chunk_y * CHUNK_SIZE))
5767

58-
Console.progress_bar(locale.join_pic, chunk_index, chunk_count)
68+
Console.progress_bar(locale.join_pic, chunk_index, chunk_count)
5969

6070
return image
6171

@@ -125,23 +135,6 @@ def get_format_by_pixel_type(pixel_type: int) -> str:
125135
raise Exception(locale.unknown_pixel_type % pixel_type)
126136

127137

128-
def load_texture(reader: Reader, pixel_type: int, width: int, height: int) -> None:
129-
channel_count = get_channel_count_by_pixel_type(pixel_type)
130-
read_pixel = get_read_function(pixel_type)
131-
if read_pixel is None:
132-
raise Exception(locale.unknown_pixel_type % pixel_type)
133-
134-
with open("pixel_buffer", "wb") as pixel_buffer:
135-
pixel_buffer.write(channel_count.to_bytes(1, "little"))
136-
137-
for y in range(height):
138-
pixel_buffer.write(
139-
b"".join([bytearray(read_pixel(reader)) for _ in range(width)])
140-
)
141-
142-
Console.progress_bar(locale.crt_pic, y, height)
143-
144-
145138
def save_texture(writer: Writer, image: Image.Image, pixel_type: int) -> None:
146139
encode_pixel = get_pixel_encode_function(pixel_type)
147140
if encode_pixel is None:

xcoder/languages/en-EU.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454

5555
"not_latest": "Not the latest version installed",
5656
"collecting_inf": "File: %s. Collecting information...",
57-
"about_sc": "About texture. Filename: %s (%d), Pixel type: %d, Size: %sx%s",
57+
"about_sc": "About texture. Filename: {filename} ({index}), Pixel type: {pixel_type}, Size: {width}x{height}",
5858
"decompression_error": "Error while decompressing! Trying to decode as is...",
5959
"skip_not_installed": "%s isn't installed! Reinitialize",
6060
"detected_comp": "%s compression detected",

xcoder/languages/ua-UA.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454

5555
"not_latest": "Встановлена не остання версія",
5656
"collecting_inf": "Файл: %s. Збираємо інформацію...",
57-
"about_sc": "Про текстуру. Назва файлу: %s (%d), Тип пікселів: %d, Величина: %sx%s",
57+
"about_sc": "Про текстуру. Назва файлу: {filename} ({index}), Тип пікселів: {pixel_type}, Величина: {width}x{height}",
5858
"decompression_error": "Помилка при розпакуванні! Намагаємся розпакувати як є...",
5959
"skip_not_installed": "%s не встановлений, повторіть налаштування",
6060
"detected_comp": "Виявлено компресію: %s",

xcoder/languages/zh-CN.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454

5555
"not_latest": "安装的不是最新版本",
5656
"collecting_inf": "文件:%s。正在收集信息...",
57-
"about_sc": "关于纹理。文件名:%s (%d),像素类型:%d,尺寸:%sx%s",
57+
"about_sc": "关于纹理。文件名:{filename} ({index}),像素类型:{pixel_type},尺寸:{width}x{height}",
5858
"decompression_error": "解压时出错!尝试直接解码...",
5959
"skip_not_installed": "%s 未安装!请重新初始化",
6060
"detected_comp": "检测到 %s 压缩",

xcoder/main_menu.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def check_auto_update():
2323

2424
def check_files_updated():
2525
if config.has_update:
26-
logger.opt(colors=True).info(f'<green>{locale.update_done % ""}</green>')
26+
logger.opt(colors=True).info(f"<green>{locale.update_done % ''}</green>")
2727
if Console.question(locale.done_qu):
2828
latest_tag = get_tags(config.repo_owner, config.repo_name)[0]
2929
latest_tag_name = latest_tag["name"][1:]

xcoder/objects/plain_object.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,4 @@
99

1010
class PlainObject(ABC):
1111
@abstractmethod
12-
def load(self, swf: SupercellSWF, tag: int):
13-
...
12+
def load(self, swf: SupercellSWF, tag: int): ...

xcoder/objects/renderable/display_object.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,10 @@ def __init__(self):
1414
self._color_transform = ColorTransform()
1515

1616
@abstractmethod
17-
def calculate_bounds(self, matrix: Matrix2x3) -> Rect:
18-
...
17+
def calculate_bounds(self, matrix: Matrix2x3) -> Rect: ...
1918

2019
@abstractmethod
21-
def render(self, matrix: Matrix2x3) -> Image.Image:
22-
...
20+
def render(self, matrix: Matrix2x3) -> Image.Image: ...
2321

2422
def set_matrix(self, matrix: Matrix2x3):
2523
self._matrix = matrix

xcoder/objects/shape/region.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ def get_image(self) -> Image.Image:
9393
height = max(int(rect.height), 1)
9494
if width + height <= 2: # The same speed as without this return
9595
return Image.new(
96-
"RGBA",
96+
self._texture.image.mode,
9797
(1, 1),
9898
color=self._texture.image.getpixel((int(rect.left), int(rect.top))),
9999
)
@@ -102,12 +102,13 @@ def get_image(self) -> Image.Image:
102102
"L", self._texture.width, self._texture.height, self._uv_points, 0xFF
103103
)
104104

105-
rendered_region = Image.new("RGBA", (width, height))
105+
rendered_region = Image.new(self._texture.image.mode, (width, height))
106106
rendered_region.paste(
107107
self._texture.image.crop(rect.as_tuple()),
108108
(0, 0),
109109
mask_image.crop(rect.as_tuple()),
110110
)
111+
rendered_region = rendered_region.convert("RGBA")
111112

112113
self._cache_image = rendered_region
113114

xcoder/objects/texture.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
from __future__ import annotations
22

3-
import os
43
from typing import TYPE_CHECKING
54

65
import zstandard
76
from PIL import Image
87

9-
from xcoder.images import join_image, load_image_from_buffer, load_texture
8+
from xcoder.bytestream import Reader
9+
from xcoder.images import join_image, load_image_from_buffer
1010
from xcoder.pvr_tex_tool import get_image_from_ktx_data
1111

1212
if TYPE_CHECKING:
@@ -54,14 +54,10 @@ def load(self, swf: SupercellSWF, tag: int, has_texture: bool):
5454
)
5555
return
5656

57-
try:
58-
load_texture(swf.reader, self.pixel_type, self.width, self.height)
57+
self.image = self._load_texture(swf.reader, tag)
5958

60-
if tag in (27, 28, 29):
61-
image = join_image(self.pixel_type, self.width, self.height)
62-
else:
63-
image = load_image_from_buffer(self.pixel_type, self.width, self.height)
64-
finally:
65-
os.remove("pixel_buffer")
59+
def _load_texture(self, reader: Reader, tag: int) -> Image.Image:
60+
if tag in (27, 28, 29):
61+
return join_image(self.pixel_type, self.width, self.height, reader)
6662

67-
self.image = image
63+
return load_image_from_buffer(self.pixel_type, self.width, self.height, reader)

xcoder/pixel_utils.py

Lines changed: 17 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import struct
2-
from typing import Callable, TypeAlias
2+
from typing import Callable, Literal, TypeAlias
33

4-
from xcoder.bytestream import Reader
4+
from xcoder.localization import locale
55

66
PixelChannels: TypeAlias = tuple[int, ...]
77
EncodeFunction: TypeAlias = Callable[[PixelChannels], bytes]
8-
DecodeFunction: TypeAlias = Callable[[Reader], PixelChannels]
8+
RawMode: TypeAlias = Literal["RGBA", "RGBA;4B", "RGBA;15", "BGR;16", "LA", "L"]
99

1010

11-
def get_read_function(pixel_type: int) -> DecodeFunction | None:
12-
return _decode_functions.get(pixel_type, None)
11+
def get_raw_mode(pixel_type: int) -> RawMode:
12+
if pixel_type in _raw_modes:
13+
return _raw_modes[pixel_type]
14+
15+
raise Exception(locale.unknown_pixel_type % pixel_type)
1316

1417

1518
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:
2629
return 4
2730

2831

29-
def _read_rgba8(reader: Reader) -> PixelChannels:
30-
return tuple(reader.read(4))
31-
32-
33-
def _read_rgba4(reader: Reader) -> PixelChannels:
34-
p = reader.read_ushort()
35-
return (
36-
(p >> 12 & 15) << 4,
37-
(p >> 8 & 15) << 4,
38-
(p >> 4 & 15) << 4,
39-
(p >> 0 & 15) << 4,
40-
)
41-
42-
43-
def _read_rgb5a1(reader: Reader) -> PixelChannels:
44-
p = reader.read_ushort()
45-
return (
46-
(p >> 11 & 31) << 3,
47-
(p >> 6 & 31) << 3,
48-
(p >> 1 & 31) << 3,
49-
(p & 255) << 7,
50-
)
51-
52-
53-
def _read_rgb565(reader: Reader) -> PixelChannels:
54-
p = reader.read_ushort()
55-
return (p >> 11 & 31) << 3, (p >> 5 & 63) << 2, (p & 31) << 3
56-
57-
58-
def _read_luminance8_alpha8(reader: Reader) -> PixelChannels:
59-
return tuple(reader.read(2))[::-1]
60-
61-
62-
def _read_luminance8(reader: Reader) -> PixelChannels:
63-
return tuple(reader.read(1))
64-
65-
6632
def _write_rgba8(pixel: PixelChannels) -> bytes:
6733
return struct.pack("4B", *pixel)
6834

@@ -109,12 +75,13 @@ def _write_luminance8(pixel: PixelChannels) -> bytes:
10975
10: _write_luminance8,
11076
}
11177

112-
_decode_functions: dict[int, DecodeFunction] = {
113-
0: _read_rgba8,
114-
1: _read_rgba8,
115-
2: _read_rgba4,
116-
3: _read_rgb5a1,
117-
4: _read_rgb565,
118-
6: _read_luminance8_alpha8,
119-
10: _read_luminance8,
78+
# here is a problem with this names https://github.com/python-pillow/Pillow/pull/8158
79+
_raw_modes: dict[int, RawMode] = {
80+
0: "RGBA",
81+
1: "RGBA",
82+
2: "RGBA;4B", # ABGR;4
83+
3: "RGBA;15", # ABGR;1555
84+
4: "BGR;16", # RGB;565
85+
6: "LA",
86+
10: "L",
12087
}

0 commit comments

Comments
 (0)