Source code for arcade.texture.loading

from __future__ import annotations

import logging
from typing import Optional, List, Union, Tuple
from pathlib import Path

import PIL.Image
import PIL.ImageOps
import PIL.ImageDraw

from arcade.types import RectList
from arcade.resources import resolve
from arcade.hitbox import HitBoxAlgorithm
from arcade import cache as _cache
from arcade import hitbox
from .texture import Texture, ImageData

LOG = logging.getLogger(__name__)


[docs] def load_texture( file_path: Union[str, Path], *, x: int = 0, y: int = 0, width: int = 0, height: int = 0, hit_box_algorithm: Optional[HitBoxAlgorithm] = None, ) -> Texture: """ Load an image from disk and create a texture. The ``x``, ``y``, ``width``, and ``height`` parameters are used to specify a sub-rectangle of the image to load. If not specified, the entire image is loaded. :param file_name: Name of the file to that holds the texture. :param x: X coordinate of the texture in the image. :param y: Y coordinate of the texture in the image. :param width: Width of the texture in the image. :param height: Height of the texture in the image. :param hit_box_algorithm: :returns: New :class:`Texture` object. :raises: ValueError """ LOG.info("load_texture: %s ", file_path) file_path = resolve(file_path) crop = (x, y, width, height) return _load_or_get_texture( file_path, hit_box_algorithm=hit_box_algorithm, crop=crop, )
def _load_tilemap_texture( file_path: Path, *, x: int = 0, y: int = 0, width: int = 0, height: int = 0, hit_box_algorithm: Optional[HitBoxAlgorithm] = None, ) -> Texture: """ Load an image from disk and create a texture. The ``x``, ``y``, ``width``, and ``height`` parameters are used to specify a sub-rectangle of the image to load. If not specified, the entire image is loaded. :param file_name: Name of the file to that holds the texture. :param x: X coordinate of the texture in the image. :param y: Y coordinate of the texture in the image. :param width: Width of the texture in the image. :param height: Height of the texture in the image. :param hit_box_algorithm: :returns: New :class:`Texture` object. :raises: ValueError """ crop = (x, y, width, height) return _load_or_get_texture( file_path, hit_box_algorithm=hit_box_algorithm, crop=crop, ) def _load_or_get_texture( file_path: Path, hit_box_algorithm: Optional[HitBoxAlgorithm] = None, crop: Tuple[int, int, int, int] = (0, 0, 0, 0), hash: Optional[str] = None, ) -> Texture: """Load a texture, or return a cached version if it's already loaded.""" hit_box_algorithm = hit_box_algorithm or hitbox.algo_default image_data: Optional[ImageData] = None texture = None # Load the image data from disk or get from cache image_data, cached = _load_or_get_image(file_path, hash=hash) # If the image was fetched from cache we might have cached texture if cached: texture = _cache.texture_cache.get_with_config(image_data.hash, hit_box_algorithm) # If we still don't have a texture, create it if texture is None: texture = Texture(image_data, hit_box_algorithm=hit_box_algorithm) texture.file_path = file_path texture.crop_values = crop _cache.texture_cache.put(texture, file_path=file_path) # If we have crop values we need to dig deeper looking for cached versions if crop != (0, 0, 0, 0): image_data = _cache.image_data_cache.get(Texture.create_image_cache_name(file_path, crop)) # If we don't have and cached image data we can crop from the base texture if image_data is None: texture = texture.crop(*crop) _cache.texture_cache.put(texture) _cache.image_data_cache.put(Texture.create_image_cache_name(file_path, crop), texture.image_data) else: # We might have a texture for this image data texture = _cache.texture_cache.get_with_config(image_data.hash, hit_box_algorithm) if texture is None: texture = Texture(image_data, hit_box_algorithm=hit_box_algorithm) texture.file_path = file_path texture.crop_values = crop _cache.texture_cache.put(texture, file_path=file_path) return texture def _load_or_get_image( file_path: Path, hash: Optional[str] = None, ) -> Tuple[ImageData, bool]: """ Load an image, or return a cached version :param file_path: Path to image :param hash: Hash of the image :return: Tuple of image data and a boolean indicating if the image was fetched from cache """ file_path_str = str(file_path) cached = True # Do we have cached image data for this file? image_data = _cache.image_data_cache.get( Texture.create_image_cache_name(file_path_str) ) if not image_data: cached = False im = PIL.Image.open(file_path).convert("RGBA") image_data = ImageData(im, hash) _cache.image_data_cache.put( Texture.create_image_cache_name(file_path_str), image_data, ) return image_data, cached
[docs] def load_texture_pair( file_name: Union[str, Path], hit_box_algorithm: Optional[HitBoxAlgorithm] = None ) -> Tuple[Texture, Texture]: """ Load a texture pair, with the second being a mirror image of the first. Useful when doing animations and the character can face left/right. :param file_name: Path to texture :param hit_box_algorithm: The hit box algorithm """ LOG.info("load_texture_pair: %s ", file_name) texture = load_texture(file_name, hit_box_algorithm=hit_box_algorithm) return texture, texture.flip_left_right()
[docs] def load_textures( file_name: Union[str, Path], image_location_list: RectList, mirrored: bool = False, flipped: bool = False, hit_box_algorithm: Optional[HitBoxAlgorithm] = None, ) -> List[Texture]: """ Load a set of textures from a single image file. Note: If the code is to load only part of the image, the given `x`, `y` coordinates will start with the origin `(0, 0)` in the upper left of the image. When drawing, Arcade uses `(0, 0)` in the lower left corner. Be careful with this reversal. For a longer explanation of why computers sometimes start in the upper left, see: http://programarcadegames.com/index.php?chapter=introduction_to_graphics&lang=en#section_5 :param file_name: Name of the file. :param image_location_list: List of image sub-locations. Each rectangle should be a `List` of four floats: `[x, y, width, height]`. :param mirrored: If set to `True`, the image is mirrored left to right. :param flipped: If set to `True`, the image is flipped upside down. :param hit_box_algorithm: One of None, 'None', 'Simple' (default) or 'Detailed'. :param hit_box_detail: Float, defaults to 4.5. Used with 'Detailed' to hit box :returns: List of :class:`Texture`'s. :raises: ValueError """ LOG.info("load_textures: %s ", file_name) file_name = resolve(file_name) file_name_str = str(file_name) hit_box_algorithm = hit_box_algorithm or hitbox.algo_default image_cache_name = Texture.create_image_cache_name(file_name_str) # Do we have the image in the cache? image_data = _cache.image_data_cache.get(image_cache_name) if not image_data: image_data = ImageData(PIL.Image.open(resolve(file_name))) _cache.image_data_cache.put(image_cache_name, image_data) image = image_data.image texture_sections = [] for image_location in image_location_list: x, y, width, height = image_location # Check if we have already created this sub-image image_cache_name = Texture.create_image_cache_name(file_name_str, (x, y, width, height)) sub_image = _cache.image_data_cache.get(image_cache_name) if not sub_image: Texture.validate_crop(image, x, y, width, height) sub_image = ImageData(image.crop((x, y, x + width, y + height))) _cache.image_data_cache.put(image_cache_name, sub_image) # Do we have a texture for this sub-image? texture_cache_name = Texture.create_cache_name(hash=sub_image.hash, hit_box_algorithm=hit_box_algorithm) sub_texture = _cache.texture_cache.get(texture_cache_name) if not sub_texture: sub_texture = Texture(sub_image, hit_box_algorithm=hit_box_algorithm) _cache.texture_cache.put(sub_texture) if mirrored: sub_texture = sub_texture.flip_left_right() if flipped: sub_texture = sub_texture.flip_top_bottom() sub_texture.file_path = file_name sub_texture.crop_values = x, y, width, height texture_sections.append(sub_texture) return texture_sections
[docs] def load_spritesheet( file_name: Union[str, Path], sprite_width: int, sprite_height: int, columns: int, count: int, margin: int = 0, hit_box_algorithm: Optional[HitBoxAlgorithm] = None, ) -> List[Texture]: """ :param file_name: Name of the file to that holds the texture. :param sprite_width: Width of the sprites in pixels :param sprite_height: Height of the sprites in pixels :param columns: Number of tiles wide the image is. :param count: Number of tiles in the image. :param margin: Margin between images :param hit_box_algorithm: The hit box algorithm :returns List: List of :class:`Texture` objects. """ LOG.info("load_spritesheet: %s ", file_name) texture_list = [] # TODO: Support caching? file_name = resolve(file_name) source_image = PIL.Image.open(file_name).convert("RGBA") for sprite_no in range(count): row = sprite_no // columns column = sprite_no % columns start_x = (sprite_width + margin) * column start_y = (sprite_height + margin) * row image = source_image.crop( (start_x, start_y, start_x + sprite_width, start_y + sprite_height) ) texture = Texture( image, hit_box_algorithm=hit_box_algorithm, ) texture.file_path = file_name texture.crop_values = start_x, start_y, sprite_width, sprite_height texture_list.append(texture) return texture_list