Skip to content

refactor(texture): saving texture with tobytes, splitting optimized #34

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion xcoder/features/sc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def compile_sc(
sc.write(struct.pack("<BIBHH", file_type, file_size, pixel_type, width, height))

if file_type in (27, 28):
split_image(sheet)
sheet = split_image(sheet)

save_texture(sc, sheet, pixel_type)
print()
Expand Down
6 changes: 2 additions & 4 deletions xcoder/features/sc/decode.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,8 @@ def decode_textures_only():
output_folder, base_name
)

_save_meta_file(
swf, objects_output_folder, base_name.rsplit("_", 1)[0], signature
)
_save_textures(swf, objects_output_folder, base_name)
_save_meta_file(swf, objects_output_folder, base_name, signature)
except Exception as exception:
logger.exception(
locale.error
Expand Down Expand Up @@ -77,8 +75,8 @@ def decode_and_render_objects():
output_folder, base_name
)

_save_textures(swf, objects_output_folder / "textures", base_name)
render_objects(swf, objects_output_folder)
_save_textures(swf, objects_output_folder / "textures", base_name)
_save_meta_file(swf, objects_output_folder, base_name, signature)
except Exception as exception:
logger.exception(
Expand Down
90 changes: 46 additions & 44 deletions xcoder/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
if TYPE_CHECKING:
from PIL._imaging import PixelAccess # type: ignore[reportPrivateImportUsage]

from .bytestream import Reader, Writer
from .console import Console
from .localization import locale
from .math.point import Point
from .matrices import Matrix2x3
from .pixel_utils import get_pixel_encode_function, get_raw_mode
from xcoder.bytestream import Reader, Writer
from xcoder.console import Console
from xcoder.localization import locale
from xcoder.math.point import Point
from xcoder.matrices import Matrix2x3
from xcoder.pixel_utils import get_pixel_encode_function, get_raw_mode

CHUNK_SIZE = 32

Expand Down Expand Up @@ -47,11 +47,11 @@ def join_image(
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
chunk_x = (chunk_index % chunk_count_x) * CHUNK_SIZE
chunk_y = (chunk_index // chunk_count_x) * CHUNK_SIZE

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_height = min(height - chunk_y, CHUNK_SIZE)

sub_image = Image.frombuffer(
mode,
Expand All @@ -63,7 +63,7 @@ def join_image(
1,
)

image.paste(sub_image, (chunk_x * CHUNK_SIZE, chunk_y * CHUNK_SIZE))
image.paste(sub_image, (chunk_x, chunk_y))

Console.progress_bar(locale.join_pic, chunk_index, chunk_count)

Expand All @@ -76,40 +76,33 @@ def _add_pixel(
image[pixel_index % width, int(pixel_index / width)] = color


def split_image(img: Image.Image) -> None:
width, height = img.size
def split_image(image: Image.Image) -> Image.Image:
width, height = image.size

loaded_image = img.load()
if loaded_image is None:
raise Exception("loaded_image is None")
chunk_count_x = math.ceil(width / CHUNK_SIZE)
chunk_count_y = math.ceil(height / CHUNK_SIZE)
chunk_count = chunk_count_x * chunk_count_y

loaded_clone = img.copy().load()
if loaded_clone is None:
raise Exception("loaded_clone is None")
split_image_buffers = []

x_chunks_count = width // CHUNK_SIZE
y_chunks_count = height // CHUNK_SIZE
for chunk_index in range(chunk_count):
chunk_x = (chunk_index % chunk_count_x) * CHUNK_SIZE
chunk_y = (chunk_index // chunk_count_x) * CHUNK_SIZE

pixel_index = 0
chunk_width = min(width - chunk_x, CHUNK_SIZE)
chunk_height = min(height - chunk_y, CHUNK_SIZE)

for y_chunk in range(y_chunks_count + 1):
for x_chunk in range(x_chunks_count + 1):
for y in range(CHUNK_SIZE):
pixel_y = (y_chunk * CHUNK_SIZE) + y
if pixel_y >= height:
break
chunk = image.crop(
(chunk_x, chunk_y, chunk_x + chunk_width, chunk_y + chunk_height)
)

for x in range(CHUNK_SIZE):
pixel_x = (x_chunk * CHUNK_SIZE) + x
if pixel_x >= width:
break
split_image_buffers.append(chunk.tobytes("raw"))

_add_pixel(
loaded_image, pixel_index, width, loaded_clone[pixel_x, pixel_y]
)
pixel_index += 1
Console.progress_bar(locale.split_pic, chunk_index, chunk_count)

Console.progress_bar(locale.split_pic, y_chunk, y_chunks_count + 1)
return Image.frombuffer(
image.mode, image.size, b"".join(split_image_buffers), "raw"
)


def get_byte_count_by_pixel_type(pixel_type: int) -> int:
Expand All @@ -136,19 +129,28 @@ def get_format_by_pixel_type(pixel_type: int) -> str:


def save_texture(writer: Writer, image: Image.Image, pixel_type: int) -> None:
raw_mode = get_raw_mode(pixel_type)
encode_pixel = get_pixel_encode_function(pixel_type)
if encode_pixel is None:
raise Exception(locale.unknown_pixel_type % pixel_type)

width, height = image.size

pixels = image.getdata()
for y in range(height):
for x in range(width):
# noinspection PyTypeChecker
writer.write(encode_pixel(pixels[y * width + x]))

Console.progress_bar(locale.writing_pic, y, height)
# Some packers for raw_encoder are absent
# https://github.com/python-pillow/Pillow/blob/58e48745cc7b6c6f7dd26a50fe68d1a82ea51562/src/encode.c#L337
# https://github.com/python-pillow/Pillow/blob/main/src/libImaging/Pack.c#L668
if raw_mode != image.mode and encode_pixel is not None:
for y in range(height):
for x in range(width):
# noinspection PyTypeChecker
writer.write(encode_pixel(pixels[y * width + x]))

Console.progress_bar(locale.writing_pic, y, height)

return

writer.write(image.tobytes("raw", raw_mode, 0, 1))
Console.progress_bar(locale.writing_pic, height - 1, height)


def transform_image(
Expand Down
14 changes: 1 addition & 13 deletions xcoder/pixel_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,25 +57,13 @@ def _write_rgb565(pixel: PixelChannels) -> bytes:
return struct.pack("<H", b >> 3 | g >> 2 << 5 | r >> 3 << 11)


def _write_luminance8_alpha8(pixel: PixelChannels) -> bytes:
return struct.pack("2B", *pixel[::-1])


def _write_luminance8(pixel: PixelChannels) -> bytes:
return struct.pack("B", pixel)


_encode_functions: dict[int, EncodeFunction] = {
0: _write_rgba8,
1: _write_rgba8,
2: _write_rgba4,
3: _write_rgb5a1,
4: _write_rgb565,
6: _write_luminance8_alpha8,
10: _write_luminance8,
}

# here is a problem with this names https://github.com/python-pillow/Pillow/pull/8158
# here is a problem with these names https://github.com/python-pillow/Pillow/pull/8158
_raw_modes: dict[int, RawMode] = {
0: "RGBA",
1: "RGBA",
Expand Down