From 7e4b3c5b23690e6d9a0076ab6835293d47d0589f Mon Sep 17 00:00:00 2001 From: Einar Forselv Date: Fri, 9 Jun 2023 00:06:53 +0200 Subject: [PATCH 1/3] Allow syncing pixel data in the atlas into an arcade.Texture --- arcade/texture_atlas/base.py | 41 +++++++++++++++++++ doc/programming_guide/release_notes.rst | 3 ++ tests/unit/atlas/test_update_texture_image.py | 22 ++++++++++ 3 files changed, 66 insertions(+) create mode 100644 tests/unit/atlas/test_update_texture_image.py diff --git a/arcade/texture_atlas/base.py b/arcade/texture_atlas/base.py index f5f7883b8..683219b45 100644 --- a/arcade/texture_atlas/base.py +++ b/arcade/texture_atlas/base.py @@ -969,6 +969,47 @@ def calculate_minimum_size(cls, textures: Sequence["Texture"], border: int = 1): return size, size + def get_texture_image(self, texture: "Texture") -> Image.Image: + """ + Get a Pillow image of a texture's region in the atlas. + This can be used to inspect the contents of the atlas + or to save the texture to disk. + + :param Texture texture: The texture to get the image for + :return: A pillow image containing the pixel data in the atlas + """ + region = self.get_image_region_info(texture.image_data.hash) + viewport = ( + region.x + self._border, + region.y + self._border, + region.width, + region.height, + ) + data = self.fbo.read(viewport=viewport, components=4) + return Image.frombytes("RGBA", (region.width, region.height), data) + + def sync_texture_image(self, texture: "Texture") -> None: + """ + Updates a texture's image with the contents in the + texture atlas. This is usually not needed, but if + you have altered a texture in the atlas directly + this can be used to copy the image data back into + the texture. + + Updating the image will not change the texture's + hash or the texture's hit box points. + + .. warning:: + + This method is somewhat expensive and should be used sparingly. + Altering the internal image of a texture is not recommended + unless you know exactly what you're doing. Textures are + supposed to be immutable. + + :param Texture texture: The texture to update + """ + texture.image_data.image = self.get_texture_image(texture) + def to_image( self, flip: bool = False, diff --git a/doc/programming_guide/release_notes.rst b/doc/programming_guide/release_notes.rst index 7b4fa8749..d97511c38 100644 --- a/doc/programming_guide/release_notes.rst +++ b/doc/programming_guide/release_notes.rst @@ -204,6 +204,9 @@ Changes ``layer_options`` dictionary. If no custom atlas is provided, then the global default atlas will be used (This is how it works pre-Arcade 3.0). * Fix for animated tiles from sprite sheets + * TextureAtlas: Added ``sync_texture_image`` method to sync the texture in the atlas back into + the internal pillow image in the ``arcade.Texture``. + * TextureAtlas: Added ``get_texture_image`` method to get pixel data of a texture in the atlas as a pillow image. * Collision Detection diff --git a/tests/unit/atlas/test_update_texture_image.py b/tests/unit/atlas/test_update_texture_image.py new file mode 100644 index 000000000..c2a1762c4 --- /dev/null +++ b/tests/unit/atlas/test_update_texture_image.py @@ -0,0 +1,22 @@ +""" +Test syncing atlas textures back into PIL images. +""" +import arcade + + +def test_sync(ctx): + tex_1 = arcade.load_texture(":assets:images/cards/cardClubs2.png") + tex_2 = arcade.load_texture(":assets:images/cards/cardDiamonds8.png") + + atlas = arcade.TextureAtlas((256, 256)) + atlas.add(tex_1) + + # Write the second image over the first one + region = atlas.get_image_region_info(tex_1.image_data.hash) + atlas.write_image(tex_2.image, region.x, region.y) + + # Sync the texture back into the PIL image. + # It should contain the same image as tex_2 + atlas.sync_texture_image(tex_1) + + assert tex_1.image.tobytes() == tex_2.image.tobytes() From 8b499f825c9ca1baa7d3ca38d759c03e7f80d211 Mon Sep 17 00:00:00 2001 From: Einar Forselv Date: Fri, 9 Jun 2023 01:31:12 +0200 Subject: [PATCH 2/3] Run gc between tests --- tests/conftest.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index c65d1668d..7d7bc406b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,6 @@ import os from pathlib import Path +import gc if os.environ.get("ARCADE_PYTEST_USE_RUST"): import arcade_accelerate # pyright: ignore [reportMissingImports] @@ -42,6 +43,7 @@ def prepare_window(window: arcade.Window): window.clear() ctx.gc_mode = "context_gc" ctx.gc() + gc.collect() # Ensure no old functions are lingering window.on_draw = lambda: None From 23bdbbc3bea5bff0a5c1a95696e053ef98f97ded Mon Sep 17 00:00:00 2001 From: Einar Forselv Date: Fri, 9 Jun 2023 02:40:15 +0200 Subject: [PATCH 3/3] Rename test + fixes --- tests/unit/atlas/test_sync_texture_image.py | 25 +++++++++++++++++++ tests/unit/atlas/test_update_texture_image.py | 22 ---------------- 2 files changed, 25 insertions(+), 22 deletions(-) create mode 100644 tests/unit/atlas/test_sync_texture_image.py delete mode 100644 tests/unit/atlas/test_update_texture_image.py diff --git a/tests/unit/atlas/test_sync_texture_image.py b/tests/unit/atlas/test_sync_texture_image.py new file mode 100644 index 000000000..49646c6d4 --- /dev/null +++ b/tests/unit/atlas/test_sync_texture_image.py @@ -0,0 +1,25 @@ +""" +Test syncing atlas textures back into PIL images. +""" +from PIL import Image +import arcade +from arcade.resources import resolve + + +def test_sync(ctx): + im_1 = Image.open(resolve(":assets:images/cards/cardClubs2.png")) + im_2 = Image.open(resolve(":assets:images/cards/cardDiamonds8.png")) + tex = arcade.Texture(im_1) + + atlas = arcade.TextureAtlas((256, 256)) + atlas.add(tex) + + # Write the second image over the first one + region = atlas.get_image_region_info(tex.image_data.hash) + atlas.write_image(im_2, region.x, region.y) + + # Sync the texture back into the PIL image. + # It should contain the same image as tex_2 + atlas.sync_texture_image(tex) + + assert tex.image.tobytes() == im_2.tobytes() diff --git a/tests/unit/atlas/test_update_texture_image.py b/tests/unit/atlas/test_update_texture_image.py deleted file mode 100644 index c2a1762c4..000000000 --- a/tests/unit/atlas/test_update_texture_image.py +++ /dev/null @@ -1,22 +0,0 @@ -""" -Test syncing atlas textures back into PIL images. -""" -import arcade - - -def test_sync(ctx): - tex_1 = arcade.load_texture(":assets:images/cards/cardClubs2.png") - tex_2 = arcade.load_texture(":assets:images/cards/cardDiamonds8.png") - - atlas = arcade.TextureAtlas((256, 256)) - atlas.add(tex_1) - - # Write the second image over the first one - region = atlas.get_image_region_info(tex_1.image_data.hash) - atlas.write_image(tex_2.image, region.x, region.y) - - # Sync the texture back into the PIL image. - # It should contain the same image as tex_2 - atlas.sync_texture_image(tex_1) - - assert tex_1.image.tobytes() == tex_2.image.tobytes()