Source code for arcade.texture.spritesheet

from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING, Literal

from PIL import Image
from PIL.Image import Transpose

from arcade.resources import resolve

# from arcade import Texture
from arcade.texture import Texture
from arcade.types.rect import Rect

if TYPE_CHECKING:
    from arcade.hitbox import HitBoxAlgorithm

OriginChoices = Literal["upper_left", "lower_left"]


[docs] class SpriteSheet: """ A sprite sheet is a single image containing multiple smaller images, or frames. The class is used to load the image providing methods to slice out parts of the image as separate images or textures. Note that the default coordinate system used for slicing is using image coordinates (0, 0) in the upper left corner. This matches the coordinate system used by PIL. Args: path Path to the image to load. image: PIL image to use. """ def __init__( self, path: str | Path | None = None, image: Image.Image | None = None, ): self._path = None if path: self._path = resolve(path) self._image = Image.open(self._path).convert("RGBA") elif image: self._image = image else: raise ValueError("Must provide either path or image") self._flip_flags = (False, False)
[docs] @classmethod def from_image(cls, image: Image.Image) -> SpriteSheet: """ Create a sprite sheet from a PIL image. Args: image: PIL image to use. """ return cls(image=image)
@property def image(self) -> Image.Image: """ Get or set the PIL image for this sprite sheet. """ return self._image @image.setter def image(self, image: Image.Image): self._image = image @property def path(self) -> Path | None: """ The path to the sprite sheet if it was loaded from disk. """ return self._path @property def flip_flags(self) -> tuple[bool, bool]: """ Query the orientation of the sprite sheet. This can be used to determine if the sprite sheet needs to be flipped. Default values are ``(False, False)``. Will be modified when :py:meth:`flip_left_right` or :py:meth:`flip_top_bottom` is called. Tuple of booleans ``(flip_left_right, flip_top_bottom)``. """ return self._flip_flags
[docs] def flip_left_right(self) -> None: """ Flips the internal image left to right. """ self._image = self._image.transpose(Transpose.FLIP_LEFT_RIGHT) self._flip_flags = (not self._flip_flags[0], self._flip_flags[1])
[docs] def flip_top_bottom(self) -> None: """ Flip the internal image top to bottom. """ self._image = self._image.transpose(Transpose.FLIP_TOP_BOTTOM) self._flip_flags = (self._flip_flags[0], not self._flip_flags[1])
[docs] def get_image(self, rect: Rect, y_up=False) -> Image.Image: """ Slice out an image from the sprite sheet. Args: rect: The rectangle to crop out. y_up: Sets the coordinate space of the image to assert (0, 0) in the bottom left. """ # PIL box is a 4-tuple: left, upper, right, and lower if y_up: return self.image.crop( ( rect.left, self.image.height - rect.bottom - rect.height, rect.right, self.image.height - rect.bottom, ) ) else: return self.image.crop( ( rect.left, rect.bottom, rect.right, rect.top, ) )
# slice an image out of the sprite sheet
[docs] def get_texture( self, rect: Rect, hit_box_algorithm: HitBoxAlgorithm | None = None, y_up=False ) -> Texture: """ Slice out texture from the sprite sheet. Args: rect: The rectangle to crop out. hit_box_algorithm: Hit box algorithm to use for the texture. If not provided, the default hit box algorithm will be used. y_up: Sets the coordinate space of the image to assert (0, 0) in the bottom left. """ im = self.get_image(rect, y_up) texture = Texture(im, hit_box_algorithm=hit_box_algorithm) texture.file_path = self._path texture.crop_values = rect.lbwh_int return texture
[docs] def get_image_grid( self, size: tuple[int, int], columns: int, count: int, margin: tuple[int, int, int, int] = (0, 0, 0, 0), ) -> list[Image.Image]: """ Slice a grid of textures from the sprite sheet. Args: size: Size of each texture ``(width, height)`` columns: Number of columns in the grid count: Number of textures to crop margin: The margin around each texture ``(left, right, bottom, top)`` """ images = [] width, height = size left, right, bottom, top = margin for sprite_no in range(count): row = sprite_no // columns column = sprite_no % columns x = (width + left + right) * column y = (height + top + bottom) * row im = self.image.crop((x, y, x + width, y + height)) images.append(im) return images
[docs] def get_texture_grid( self, size: tuple[int, int], columns: int, count: int, margin: tuple[int, int, int, int] = (0, 0, 0, 0), hit_box_algorithm: HitBoxAlgorithm | None = None, ) -> list[Texture]: """ Slice a grid of textures from the sprite sheet. Args: size: Size of each texture ``(width, height)`` columns: Number of columns in the grid count: Number of textures to crop margin: The margin around each texture ``(left, right, bottom, top)`` hit_box_algorithm: Hit box algorithm to use for the textures. If not provided, the default hit box algorithm will be used. """ textures = [] width, height = size left, right, bottom, top = margin for sprite_no in range(count): row = sprite_no // columns column = sprite_no % columns x = (width + left + right) * column y = (height + top + bottom) * row im = self.image.crop((x, y, x + width, y + height)) texture = Texture(im, hit_box_algorithm=hit_box_algorithm) texture.file_path = self._path texture.crop_values = x, y, width, height textures.append(texture) return textures