"""
The image component that renders an image from the filesystem.
"""
import sdl2
import sdl2.ext
import sdl2.sdlgfx
from . import Component
from ... import Vector, Defaults, Display, Radio, Color
[docs]class Image(Component):
"""
A component that handles Images.
Attributes:
aa (bool): Whether or not to enable anti aliasing.
flipx (bool): Whether or not to flip the image along the x axis
flipy (bool): Whether or not to flip the image along the y axis
visible (bool): Whether or not the image is visible.
"""
[docs] def __init__(self, options: dict = {}):
"""
Initializes an Image.
Args:
options: A Image config. Defaults to the :ref:`Image defaults <imagedef>`.
"""
param = Defaults.image_defaults | options
super().__init__()
if param["rel_path"] == "":
self._image: sdl2.SDL_Surface = sdl2.SDL_CreateRGBSurfaceWithFormat(
0,
param["size"].x,
param["size"].y,
32,
sdl2.SDL_PIXELFORMAT_RGBA8888,
).contents
else:
try:
self._image: sdl2.SDL_Surface = sdl2.ext.load_img(param["rel_path"], False)
except sdl2.ext.SDLError as e:
fname = param["rel_path"].replace("\\", "/").split("/")[-1]
raise TypeError(f"{fname} is not a valid image file") from e
self.singular = False
self.aa: bool = param["anti_aliasing"]
self.flipx: bool = param["flipx"]
self.flipy: bool = param["flipy"]
self.offset: Vector = param["offset"]
self.visible: bool = param["visible"]
self._original = Display.clone_surface(self._image)
self._tx = sdl2.ext.Texture(Display.renderer, self.image)
self._rotation: float = param["rotation"]
self._scale: Vector = param["scale"]
self._update_rotozoom()
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 rotation(self) -> float:
"""The rotation of the image."""
return self._rotation
@rotation.setter
def rotation(self, new_rotation: float):
self._rotation = new_rotation
self._update_rotozoom()
@property
def scale(self) -> Vector:
"""The scale of the image in relation to it's original size."""
return self._scale
@scale.setter
def scale(self, new_scale: Vector):
self._scale = new_scale
self._update_rotozoom()
[docs] def get_size(self) -> Vector:
"""
Gets the current size of the image.
Returns:
Vector: The size of the image
"""
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"""
self._image = sdl2.sdlgfx.rotozoomSurfaceXY(
self._original,
self.rotation,
-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 draw_point(self, pos: Vector, color: Color = Color.black):
"""
Draws a point on the image.
Args:
pos: The position to draw the point.
color: The color of the point. Defaults to black.
"""
sdl2.ext.fill(
self.image,
sdl2.ext.rgba_to_color(color.rgba32),
(pos.x, pos.y, 1, 1),
)
[docs] def draw_line(self, start: Vector, end: Vector, color: Color = Color.black, width: int = 1):
"""
Draws a line on the image.
Args:
start: The start of the line.
end: The end of the line.
color: The color of the line. Defaults to black.
width: The width of the line. Defaults to 1.
"""
sdl2.ext.line(
self.image,
sdl2.ext.rgba_to_color(color.rgba32),
(start.x, start.y, end.x, end.y),
width,
)
[docs] def cam_update(self):
"""Updates the image sizing when the camera zoom changes."""
width, height = self.image.w, self.image.h
new_size = Vector(
round(self.gameobj.scale_value(width)),
round(self.gameobj.scale_value(height)),
)
self.resize(new_size)
[docs] def draw(self):
if self.visible:
Display.update(self._tx, self.gameobj.map_coord(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(
{
"rotation": self.rotation,
"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