Skip to content

Commit 5e800f7

Browse files
authored
Rectify spritesheet (#2481)
* rectify spritesheet * the ghost of henry ford haunts me in my IDE * typo * rectify load_or_get_spritesheet_texture * re-add y-down support * I will paint a Model T fluorescent orange out of spite * fix typing
1 parent 0d0ee61 commit 5e800f7

File tree

5 files changed

+51
-68
lines changed

5 files changed

+51
-68
lines changed

arcade/texture/manager.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
TextureCache,
1515
)
1616
from arcade.texture import ImageData, SpriteSheet
17+
from arcade.types.rect import Rect
1718

1819
from .texture import Texture
1920

@@ -128,10 +129,7 @@ def load_or_get_spritesheet(self, path: str | Path) -> SpriteSheet:
128129
def load_or_get_spritesheet_texture(
129130
self,
130131
path: str | Path,
131-
x: int,
132-
y: int,
133-
width: int,
134-
height: int,
132+
rect: Rect,
135133
hit_box_algorithm: hitbox.HitBoxAlgorithm | None = None,
136134
) -> Texture:
137135
"""
@@ -155,22 +153,22 @@ def load_or_get_spritesheet_texture(
155153
Hit box algorithm to use. If not specified, the global default will be used.
156154
"""
157155
real_path = self._get_real_path(path)
158-
texture = self._texture_cache.get_texture_by_filepath(real_path, crop=(x, y, width, height))
156+
texture = self._texture_cache.get_texture_by_filepath(real_path, crop=rect.lbwh_int)
159157
if texture:
160158
return texture
161159

162160
# check if sprite sheet is cached and load if not
163161
sprite_sheet = self.load_or_get_spritesheet(real_path)
164162

165163
# slice out the texture and cache + return
166-
texture = sprite_sheet.get_texture(x, y, width, height, hit_box_algorithm=hit_box_algorithm)
164+
texture = sprite_sheet.get_texture(rect, hit_box_algorithm=hit_box_algorithm)
167165
self._texture_cache.put(texture)
168166
if texture.image_cache_name:
169167
self._image_data_cache.put(texture.image_cache_name, texture.image_data)
170168

171169
# Add to image data cache
172170
self._image_data_cache.put(
173-
Texture.create_image_cache_name(real_path, (x, y, width, height)),
171+
Texture.create_image_cache_name(real_path, rect.lbwh_int),
174172
texture.image_data,
175173
)
176174

arcade/texture/spritesheet.py

Lines changed: 27 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
# from arcade import Texture
1212
from arcade.texture import Texture
13+
from arcade.types.rect import Rect
1314

1415
if TYPE_CHECKING:
1516
from arcade.hitbox import HitBoxAlgorithm
@@ -102,68 +103,55 @@ def flip_top_bottom(self) -> None:
102103
self._image = self._image.transpose(Transpose.FLIP_TOP_BOTTOM)
103104
self._flip_flags = (self._flip_flags[0], not self._flip_flags[1])
104105

105-
def get_image(
106-
self, x: int, y: int, width: int, height: int, origin: OriginChoices = "upper_left"
107-
) -> Image.Image:
106+
def get_image(self, rect: Rect, y_up=False) -> Image.Image:
108107
"""
109108
Slice out an image from the sprite sheet.
110109
111110
Args:
112-
x:
113-
X position of the image
114-
y:
115-
Y position of the image
116-
width:
117-
Width of the image.
118-
height:
119-
Height of the image.
120-
origin:
121-
Origin of the image. Default is "upper_left".
122-
Options are "upper_left" or "lower_left".
111+
rect:
112+
The rectangle to crop out.
113+
y_up:
114+
Sets the coordinate space of the image to assert (0, 0)
115+
in the bottom left.
123116
"""
124117
# PIL box is a 4-tuple: left, upper, right, and lower
125-
if origin == "upper_left":
126-
return self.image.crop((x, y, x + width, y + height))
127-
elif origin == "lower_left":
118+
if y_up:
128119
return self.image.crop(
129-
(x, self.image.height - y - height, x + width, self.image.height - y)
120+
(
121+
rect.left,
122+
self.image.height - rect.bottom - rect.height,
123+
rect.right,
124+
self.image.height - rect.bottom,
125+
)
130126
)
131127
else:
132-
raise ValueError("Invalid value for origin. Must be 'upper_left' or 'lower_left'.")
128+
return self.image.crop(
129+
(
130+
rect.left,
131+
rect.bottom,
132+
rect.right,
133+
rect.top,
134+
)
135+
)
133136

134137
# slice an image out of the sprite sheet
135138
def get_texture(
136-
self,
137-
x: int,
138-
y: int,
139-
width: int,
140-
height: int,
141-
hit_box_algorithm: HitBoxAlgorithm | None = None,
142-
origin: OriginChoices = "upper_left",
139+
self, rect: Rect, hit_box_algorithm: HitBoxAlgorithm | None = None, y_up=False
143140
) -> Texture:
144141
"""
145142
Slice out texture from the sprite sheet.
146143
147144
Args:
148-
x:
149-
X position of the texture (lower left corner).
150-
y:
151-
Y position of the texture (lower left corner).
152-
width:
153-
Width of the texture.
154-
height:
155-
Height of the texture.
145+
rect:
146+
The rectangle to crop out.
156147
hit_box_algorithm:
157148
Hit box algorithm to use for the texture.
158149
If not provided, the default hit box algorithm will be used.
159-
origin:
160-
Origin of the texture. Default is "upper_left".
161-
Options are "upper_left" or "lower_left".
162150
"""
163-
im = self.get_image(x, y, width, height, origin=origin)
151+
im = self.get_image(rect, y_up)
164152
texture = Texture(im, hit_box_algorithm=hit_box_algorithm)
165153
texture.file_path = self._path
166-
texture.crop_values = x, y, width, height
154+
texture.crop_values = rect.lbwh_int
167155
return texture
168156

169157
def get_image_grid(

doc/tutorials/crt_filter/crt_filter_example.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import arcade
22
from arcade.experimental.crt_filter import CRTFilter
3+
from arcade.types.rect import LBWH
34
from pyglet.math import Vec2
45

56

@@ -35,11 +36,11 @@ def __init__(self, width, height, title):
3536

3637
# Slice out some textures from the sprite sheet
3738
spritesheet = arcade.load_spritesheet("pac_man_sprite_sheet.png")
38-
ghost_red = spritesheet.get_texture(x=4, y=65, width=13, height=15)
39-
pink_ghost = spritesheet.get_texture(x=4, y=81, width=13, height=15)
40-
pacman_1 = spritesheet.get_texture(x=4, y=1, width=13, height=15)
41-
pacman_2 = spritesheet.get_texture(x=20, y=1, width=13, height=15)
42-
pacman_3 = spritesheet.get_texture(x=36, y=1, width=13, height=15)
39+
ghost_red = spritesheet.get_texture(LBWH(4, 65, 13, 15))
40+
pink_ghost = spritesheet.get_texture(LBWH(4, 81, 13, 15))
41+
pacman_1 = spritesheet.get_texture(LBWH(4, 1, 13, 15))
42+
pacman_2 = spritesheet.get_texture(LBWH(20, 1, 13, 15))
43+
pacman_3 = spritesheet.get_texture(LBWH(36, 1, 13, 15))
4344

4445
# Create sprite for the red ghost with some movement and add it to the sprite list
4546
sprite = arcade.Sprite(ghost_red, center_x=100, center_y=300, scale=5.0)

tests/unit/texture/test_manager.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Test the TextureCacheManager"""
22
import arcade
3+
from arcade.types.rect import LBWH
34

45
SPRITESHEET_PATH = ":assets:images/spritesheets/codepage_437.png"
56
TEST_TEXTURE = ":assets:images/test_textures/test_texture.png"
@@ -23,17 +24,17 @@ def test_load_spritesheet():
2324
def test_load_spritesheet_texture():
2425
"""Load spritesheet and test caching"""
2526
manager = arcade.texture.TextureCacheManager()
26-
texture = manager.load_or_get_spritesheet_texture(SPRITESHEET_PATH, 0, 0, 8, 16)
27+
texture = manager.load_or_get_spritesheet_texture(SPRITESHEET_PATH, LBWH(0, 0, 8, 16))
2728
# This should have cached the spritesheet
2829
assert len(manager._sprite_sheets) == 1
2930
assert isinstance(list(manager._sprite_sheets.values())[0], arcade.SpriteSheet)
3031
# The same texture should be returned the second time
31-
assert manager.load_or_get_spritesheet_texture(SPRITESHEET_PATH, 0, 0, 8, 16) == texture
32+
assert manager.load_or_get_spritesheet_texture(SPRITESHEET_PATH, LBWH(0, 0, 8, 16)) == texture
3233

3334
# Load a few more textures
3435
for i in range(10):
35-
texture = manager.load_or_get_spritesheet_texture(SPRITESHEET_PATH, i * 9, 0, 8, 16)
36-
assert manager.load_or_get_spritesheet_texture(SPRITESHEET_PATH, i * 9, 0, 8, 16) == texture
36+
texture = manager.load_or_get_spritesheet_texture(SPRITESHEET_PATH, LBWH(i * 9, 0, 8, 16))
37+
assert manager.load_or_get_spritesheet_texture(SPRITESHEET_PATH, LBWH(i * 9, 0, 8, 16)) == texture
3738

3839
# We should still have 1 spritesheet
3940
assert len(manager._sprite_sheets) == 1

tests/unit/texture/test_sprite_sheet.py

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from arcade.types.rect import LBWH
12
import pytest
23
from PIL import Image
34
import arcade
@@ -74,32 +75,26 @@ def test_get_image(sprite_sheet):
7475

7576
# Crop out the dollar sign using upper left origin
7677
im = sprite_sheet.get_image(
77-
x=9 * 4, # 4th column
78-
y=16, # second row
79-
width=8,
80-
height=16,
81-
origin="upper_left",
82-
)
78+
LBWH(9 * 4, # 4th column
79+
16, # second row
80+
8, 16))
8381
assert isinstance(im, Image.Image)
8482
assert im.size == (8, 16)
8583
assert im.tobytes() == dollar_sign.tobytes()
8684

8785
# Crop out the dollar sign using lower left origin
8886
im = sprite_sheet.get_image(
89-
x=9 * 4, # 4th column
90-
y=16 * 6, # 6th row
91-
width=8,
92-
height=16,
93-
origin="lower_left",
94-
)
87+
LBWH(9 * 4, # 4th column
88+
16 * 6, # 6th row
89+
8,16), True)
9590
assert isinstance(im, Image.Image)
9691
assert im.size == (8, 16)
9792
assert im.tobytes() == dollar_sign.tobytes()
9893

9994

10095
def test_get_texture(sprite_sheet):
10196
"""Get a texture from the sprite sheet."""
102-
texture = sprite_sheet.get_texture(0, 0, 8, 16)
97+
texture = sprite_sheet.get_texture(LBWH(0, 0, 8, 16))
10398
assert isinstance(texture, arcade.Texture)
10499
assert texture.image.size == (8, 16)
105100

0 commit comments

Comments
 (0)