Source code for rubato.classes.components.image

"""
The image component that renders an image from the filesystem.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Dict
import sdl2, sdl2.ext, sdl2.sdlgfx, sdl2.surface, sdl2.sdlimage

from . import Component
from ... import Vector, Display, Radio, get_path

if TYPE_CHECKING:
    from .. import Camera


[docs]class Image(Component): """ A component that handles Images. Args: offset: The offset of the image from the gameobject. Defaults to Vector(0, 0). rot_offset: The rotation offset of the image. Defaults to 0. rel_path: The relative path to the image. Defaults to "". size: The size of the image. Defaults to Vector(32, 32). scale: The scale of the image. Defaults to Vector(1, 1). anti_aliasing: Whether or not to use anti-aliasing. Defaults to False. flipx: Whether or not to flip the image horizontally. Defaults to False. flipy: Whether or not to flip the image vertically. Defaults to False. visible: Whether or not the image is visible. Defaults to True. Attributes: visible (bool): Whether or not the image is visible. """ def __init__( self, offset: Vector = Vector(0, 0), rot_offset: float = 0, rel_path: str = "", size: Vector = Vector(32, 32), scale: Vector = Vector(1, 1), anti_aliasing: bool = False, flipx: bool = False, flipy: bool = False, visible: bool = True, ): super().__init__(offset=offset, rot_offset=rot_offset) if rel_path == "": self._image: sdl2.SDL_Surface = sdl2.SDL_CreateRGBSurfaceWithFormat( 0, size.x, size.y, 32, sdl2.SDL_PIXELFORMAT_RGBA8888, ).contents else: try: self._image: sdl2.SDL_Surface = sdl2.ext.load_img(rel_path, False) except OSError: self._image = sdl2.ext.load_img(get_path(rel_path), False) except sdl2.ext.SDLError as e: fname = rel_path.replace("\\", "/").split("/")[-1] raise TypeError(f"{fname} is not a valid image file") from e self.singular = False self.visible: bool = visible self._aa: bool = anti_aliasing self._flipx: bool = flipx self._flipy: bool = flipy self._scale: Vector = scale self._rot = self.rotation_offset self._original = Display.clone_surface(self._image) self._tx = sdl2.ext.Texture(Display.renderer, self.image) self._changed = True self._update_rotozoom() self._go_rotation = 0 Radio.listen("ZOOM", self.cam_update) @property def image(self) -> sdl2.SDL_Surface: """The SDL Surface of the image.""" return self._image @image.setter def image(self, new: sdl2.SDL_Surface): self._image = sdl2.SDL_ConvertSurfaceFormat(new, sdl2.SDL_PIXELFORMAT_RGBA8888, 0).contents self._original = Display.clone_surface(self._image) self._update_rotozoom() @property def scale(self) -> Vector: """The scale of the image.""" return self._scale @scale.setter def scale(self, new: Vector): self._scale = new self._changed = True @property def rotation_offset(self) -> float: """The rotation offset of the image.""" return self._rot @rotation_offset.setter def rotation_offset(self, new: float): self._rot = new self._changed = True @property def flipx(self) -> bool: """Whether or not the image is flipped horizontally.""" return self._flipx @flipx.setter def flipx(self, new: bool): self._flipx = new self._changed = True @property def flipy(self) -> bool: """Whether or not the image is flipped vertically.""" return self._flipy @flipy.setter def flipy(self, new: bool): self._flipy = new self._changed = True @property def aa(self) -> bool: """Whether or not the image is anti-aliased.""" return self._aa @aa.setter def aa(self, new: bool): self._aa = new self._changed = True
[docs] def get_size(self) -> Vector: """ Gets the current size of the image. Returns: Vector: The size of the image """ if self.image.w == self._original.w and self.image.h == self._original.h: return Vector(self.image.w, self.image.h) * self.scale return Vector(self.image.w, self.image.h)
[docs] def get_size_original(self) -> Vector: """ Gets the original size of the image. Returns: Vector: The original size of the image. """ return Vector(self._original.w, self._original.h)
def _update_rotozoom(self): """Updates the image surface. Called automatically when image scale or rotation are updated""" if self.gameobj: self._image = sdl2.sdlgfx.rotozoomSurfaceXY( self._original, self.gameobj.rotation + self.rotation_offset, -self.scale.x if self.flipx else self.scale.x, -self.scale.y if self.flipy else self.scale.y, int(self.aa), ).contents self._tx = sdl2.ext.Texture(Display.renderer, self.image)
[docs] def resize(self, new_size: Vector): """ Resize the image to a given size in pixels. Args: new_size: The new size of the image in pixels. """ if -1 < new_size.x < 1: new_size.x = 1 if -1 < new_size.y < 1: new_size.y = 1 image_scaled = sdl2.surface.SDL_CreateRGBSurfaceWithFormat( 0, new_size.x, new_size.y, 32, sdl2.SDL_PIXELFORMAT_RGBA8888, ).contents sdl2.surface.SDL_BlitScaled( self._original, None, image_scaled, sdl2.SDL_Rect(0, 0, new_size.x, new_size.y), ) self._image = image_scaled self._tx = sdl2.ext.Texture(Display.renderer, self.image)
[docs] def cam_update(self, info: Dict[str, Camera]): """Updates the image sizing when the camera zoom changes.""" width, height = self.image.w, self.image.h new_size = Vector( round(info["camera"].scale(width)), round(info["camera"].scale(height)), ) self.resize(new_size)
[docs] def draw(self, camera: Camera): if self._changed or self._go_rotation != self.gameobj.rotation: self._go_rotation = self.gameobj.rotation self._changed = False self._update_rotozoom() if self.visible: Display.update(self._tx, camera.transform(self.gameobj.pos - Vector(*self._tx.size) / 2))
[docs] def delete(self): """Deletes the image component""" self._tx.destroy() sdl2.SDL_FreeSurface(self._image) sdl2.SDL_FreeSurface(self._original) self._image = None self._tx = None self._original = None
[docs] def clone(self) -> "Image": """ Clones the current image. Returns: Image: The cloned image. """ new = Image( { "scale": self.scale, "anti_aliasing": self.aa, "flipx": self.flipx, "flipy": self.flipy, "offset": self.offset, "visible": self.visible, } ) new.image = Display.clone_surface(self.image) return new
[docs] @staticmethod def from_surface(surface: sdl2.surface.SDL_Surface) -> "Image": """ Creates an image from an SDL surface. Args: surface: the surface to create the image from. Returns: The created image. """ # untested image = Image() image.image = surface return image
[docs] @staticmethod def from_buffer(buffer: bytes) -> "Image": """ Creates an image from a buffer. Args: buffer: bytes containing the image data. Returns: The image created from the buffer. """ # untested rw = sdl2.SDL_RWFromMem(buffer, len(buffer)) surface_temp = sdl2.sdlimage.IMG_Load_RW(rw, 1) if surface_temp is None: raise Exception(sdl2.sdlimage.IMG_GetError()) surface = sdl2.SDL_ConvertSurfaceFormat(surface_temp, sdl2.SDL_PIXELFORMAT_RGBA8888, 0).contents sdl2.SDL_FreeSurface(surface_temp) return Image.from_SDL_Surface(surface)