spritesheet.py

Sprite Sheet System with Alpha Support and Time-Based Animations

LOCATION: lunaengine/graphics/spritesheet.py

DESCRIPTION:
Advanced sprite sheet management system with alpha channel support and
time-based animation system. Provides efficient sprite extraction from
texture atlases with flexible positioning and frame-rate independent animations.

KEY FEATURES:
- Full alpha channel support for transparent sprites
- Flexible sprite extraction using Rect coordinates
- Batch sprite extraction for multiple regions
- Time-based animation system (frame-rate independent)
- Support for padding, margin, and scaling
- Animation sequencing with configurable durations
- Automatic frame extraction based on parameters
- Image manipulation: scaling, resizing, masking, color replacement, tinting

LIBRARIES USED:
- pygame: Image loading, surface manipulation, and alpha processing
- typing: Type hints for better code documentation
- time: Animation timing calculations
- pathlib: Modern path handling

! WARN:
- Ensure pygame is initialized before using this module

USAGE:
>>> # Basic sprite sheet
>>> spritesheet = SpriteSheet("characters.png")
>>> single_sprite = spritesheet.get_sprite_at_rect(pygame.Rect(0, 0, 64, 64))
>>>
>>> # Multiple sprites
>>> regions = [pygame.Rect(0, 0, 64, 64), pygame.Rect(64, 0, 64, 64)]
>>> sprites = spritesheet.get_sprites_at_regions(regions)
>>>
>>> # Animation - automatically extracts frames
>>> walk_animation = Animation("tiki_texture.png", (70, 70), (0, 0), frame_count=6,
>>>                           scale=(2, 2), duration=1.0)
>>> current_frame = walk_animation.get_current_frame()
>>>
>>> # Color replacement
>>> recolored = SpriteSheet.replace_color(sprite, (255,0,0), (0,255,0), tolerance=10)
>>>
>>> # Tinting
>>> tinted = SpriteSheet.tint(sprite, (100,100,255))

class SpriteSheet

Description

Main sprite sheet class for managing and extracting sprites from texture atlases.

This class handles loading sprite sheets with alpha support and provides
multiple methods for extracting individual sprites or sprite sequences,
as well as various image manipulation utilities.

Attributes:
   sheet (pygame.Surface): The loaded sprite sheet surface with alpha
   filepath (Path): Path to the sprite sheet file
   width (int): Width of the sprite sheet
   height (int): Height of the sprite sheet

Methods
def __init__(self: Any, filename: Union[str, Path]) -> Any
Initialize the sprite sheet with alpha support.

Args:
   filename (Union[str, Path]): Path to the sprite sheet image file

Raises:
   FileNotFoundError: If the file does not exist
def get_sprite_at_rect(self: Any, rect: Union[pygame.Rect, Tuple[int, int, int, int]]) -> pygame.Surface
Extract a sprite from a specific rectangular region.

Args:
   rect (Union[pygame.Rect, Tuple[int, int, int, int]]): Rectangle defining the sprite region
       (x, y, width, height)

Returns:
   pygame.Surface: The extracted sprite surface with alpha

Raises:
   ValueError: If the rect is outside the sprite sheet bounds
def get_sprites_at_regions(self: Any, regions: List[Union[pygame.Rect, Tuple[int, int, int, int]]]) -> List[pygame.Surface]
Extract multiple sprites from a list of rectangular regions.

Args:
   regions (List[Union[pygame.Rect, Tuple[int, int, int, int]]]): List of rectangles defining sprite regions

Returns:
   List[pygame.Surface]: List of extracted sprite surfaces
def get_sprite_grid(self: Any, cell_size: Tuple[int, int], grid_pos: Tuple[int, int]) -> pygame.Surface
Extract a sprite from a grid-based sprite sheet.

Args:
   cell_size (Tuple[int, int]): Width and height of each grid cell
   grid_pos (Tuple[int, int]): Grid coordinates (col, row)

Returns:
   pygame.Surface: The extracted sprite surface
def get_surface_drawn_area(self: Any, surface: pygame.Surface, threshold: int = 1) -> pygame.Rect
Get the bounding rectangle of the non-transparent (drawn) area of a surface.

This function analyzes the alpha channel of the surface to find the smallest
rectangle that contains all non-transparent pixels, creating a tight hitbox.

Args:
   surface (pygame.Surface): Surface to analyze (must have alpha channel)
   threshold (int): Alpha threshold value (0-255). Pixels with alpha >= threshold
                   are considered drawn. Default is 1 (any non-fully-transparent).

Returns:
   pygame.Rect: Tight bounding rectangle around the non-transparent area.
               Returns empty Rect (0,0,0,0) if surface is fully transparent.

Raises:
   ValueError: If surface doesn't have per-pixel alpha
def scale_surface(surface: pygame.Surface, scale_x: float, scale_y: float, smooth: bool = True) -> pygame.Surface
Scale a surface by the given factors.

Args:
   surface (pygame.Surface): Surface to scale
   scale_x (float): Horizontal scale factor (>0)
   scale_y (float): Vertical scale factor (>0)
   smooth (bool): If True, use smooth scaling (SmoothScale)

Returns:
   pygame.Surface: The scaled surface
def resize_surface(surface: pygame.Surface, new_width: int, new_height: int, smooth: bool = True) -> pygame.Surface
Resize a surface to exact dimensions.

Args:
   surface (pygame.Surface): Surface to resize
   new_width (int): Target width in pixels
   new_height (int): Target height in pixels
   smooth (bool): If True, use smooth scaling

Returns:
   pygame.Surface: The resized surface
def create_mask(surface: pygame.Surface, color_key: Optional[Tuple[int, int, int]] = None, threshold: int = 0) -> pygame.Mask
Create a collision mask from a surface.

Args:
   surface (pygame.Surface): Surface to create mask from
   color_key (Optional[Tuple[int, int, int]]): If provided, treat this RGB color as transparent.
       Otherwise use alpha channel.
   threshold (int): Alpha threshold for non-transparent pixels (0-255). Ignored if color_key is used.

Returns:
   pygame.Mask: The generated mask
def replace_color(surface: pygame.Surface, old_color: Union[Tuple[int, int, int], Tuple[int, int, int, int]], new_color: Union[Tuple[int, int, int], Tuple[int, int, int, int]], tolerance: int = 0) -> pygame.Surface
Replace a specific color (or color range) in a surface with another color.

Args:
   surface (pygame.Surface): Source surface
   old_color (Union[Tuple[int, int, int], Tuple[int, int, int, int]]): RGB or RGBA color to replace
   new_color (Union[Tuple[int, int, int], Tuple[int, int, int, int]]): RGB or RGBA replacement color
   tolerance (int): Color matching tolerance (0-255). Higher values replace similar colors.

Returns:
   pygame.Surface: A new surface with colors replaced
def replace_color_range(surface: pygame.Surface, color_low: Tuple[int, int, int], color_high: Tuple[int, int, int], new_color: Union[Tuple[int, int, int], Tuple[int, int, int, int]], alpha_tolerance: int = 0) -> pygame.Surface
Replace all colors within a bounding box in RGB space.

Args:
   surface (pygame.Surface): Source surface
   color_low (Tuple[int, int, int]): Minimum RGB values (inclusive)
   color_high (Tuple[int, int, int]): Maximum RGB values (inclusive)
   new_color (Union[Tuple[int, int, int], Tuple[int, int, int, int]]): Replacement RGB or RGBA
   alpha_tolerance (int): Alpha channel tolerance (0-255)

Returns:
   pygame.Surface: A new surface with colors replaced
def tint(surface: pygame.Surface, tint_color: Tuple[int, int, int], intensity: float = 1.0, blend_mode: str = 'multiply') -> pygame.Surface
Apply a color tint to a surface.

Args:
   surface (pygame.Surface): Source surface
   tint_color (Tuple[int, int, int]): RGB tint color
   intensity (float): Tint intensity (0.0 = original, 1.0 = full tint)
   blend_mode (str): Blend mode - "multiply", "add", or "overlay"

Returns:
   pygame.Surface: Tinted surface

Raises:
   ValueError: If blend_mode is not supported
def paint(surface: pygame.Surface, color: Union[Tuple[int, int, int], Tuple[int, int, int, int]], preserve_alpha: bool = True) -> pygame.Surface
Paint the entire surface (or all non-transparent pixels) with a solid color.

Args:
   surface (pygame.Surface): Source surface
   color (Union[Tuple[int, int, int], Tuple[int, int, int, int]]): RGB or RGBA fill color
   preserve_alpha (bool): If True, only paint non-transparent pixels (preserve alpha shape).
                          If False, fill entire surface including transparent areas.

Returns:
   pygame.Surface: Painted surface
def color_to_alpha(surface: pygame.Surface, color: Tuple[int, int, int], threshold: int = 0) -> pygame.Surface
Convert a specific RGB color to transparent (alpha = 0).

Args:
   surface (pygame.Surface): Source surface
   color (Tuple[int, int, int]): RGB color to make transparent
   threshold (int): Color matching tolerance

Returns:
   pygame.Surface: Surface with the specified color made transparent

class Animation

Description

Time-based animation system for sprite sequences with fade effects.

This class automatically extracts frames from a sprite sheet based on
the provided parameters and manages animation timing with alpha transitions.

Attributes:
   spritesheet (SpriteSheet): The source sprite sheet
   frames (List[pygame.Surface]): List of animation frames
   frame_count (int): Total number of frames in the animation
   current_frame_index (int): Current frame index in the animation
   duration (float): Total animation duration in seconds
   frame_duration (float): Duration of each frame in seconds
   last_update_time (float): Last time the animation was updated
   scale (Tuple[float, float]): Scale factors for the animation
   loop (bool): Whether the animation should loop
   playing (bool): Whether the animation is currently playing
   fade_in_duration (float): Duration of fade-in effect in seconds
   fade_out_duration (float): Duration of fade-out effect in seconds
   fade_alpha (int): Current alpha value for fade effects (0-255)
   fade_mode (str): Current fade mode: 'in', 'out', or None
   flip (Tuple[bool, bool]): Flip flags for horizontal and vertical flipping

Methods
def __init__(self: Any, spritesheet_file: Union[str, Path, SpriteSheet], size: Tuple[int, int], start_pos: Tuple[int, int] = (0, 0), frame_count: int = 1, padding: Tuple[int, int] = (0, 0), margin: Tuple[int, int] = (0, 0), scale: Tuple[float, float] = (1.0, 1.0), duration: float = 1.0, loop: bool = True, fade_in_duration: float = 0.0, fade_out_duration: float = 0.0, flip: Tuple[bool, bool] = (False, False)) -> Any
Initialize the animation and automatically extract frames from sprite sheet.

Args:
   spritesheet_file (Union[str, Path, SpriteSheet]): Path to the sprite sheet file or SpriteSheet instance
   size (Tuple[int, int]): Size of each sprite (width, height)
   start_pos (Tuple[int, int]): Starting position in the sprite sheet (x, y)
   frame_count (int): Number of frames to extract for the animation
   padding (Tuple[int, int]): Padding between sprites (x, y)
   margin (Tuple[int, int]): Margin around the sprite sheet (x, y)
   scale (Tuple[float, float]): Scale factors for the animation
   duration (float): Total animation duration in seconds
   loop (bool): Whether the animation should loop
   fade_in_duration (float): Duration of fade-in effect in seconds
   fade_out_duration (float): Duration of fade-out effect in seconds
   flip (Tuple[bool, bool]): Flip the animation horizontally and vertically
def _extract_animation_frames(self: Any) -> List[pygame.Surface]
Automatically extract animation frames based on parameters.

Creates a sequence of rectangles and extracts the corresponding sprites
from the sprite sheet.

Returns:
   List[pygame.Surface]: List of extracted frames
def _apply_scaling(self: Any) -> Any
Apply scaling to all animation frames.
def set_duration(self: Any, new_duration: float) -> Any
Change the animation duration.

Args:
   new_duration (float): New total duration in seconds
def play(self: Any) -> Any
Start or resume the animation.
def pause(self: Any) -> Any
Pause the animation.
def get_frame_count(self: Any) -> int
Get the number of frames in the animation.

Returns:
   int: Number of frames
def get_progress(self: Any) -> float
Get the current progress of the animation (0.0 to 1.0).

Returns:
   float: Animation progress from start (0.0) to end (1.0)
def _apply_fade_effect(self: Any, surface: pygame.Surface) -> pygame.Surface
Apply current fade alpha to a surface.

Args:
   surface (pygame.Surface): Original surface

Returns:
   pygame.Surface: Surface with fade effect applied
def update_fade(self: Any) -> Any
Update fade-in and fade-out effects based on elapsed time.
def update(self: Any) -> Any
Update the animation based on elapsed time including fade effects.

This method uses time-based animation rather than frame-based,
making it frame-rate independent.
def get_current_frame(self: Any) -> pygame.Surface
Get the current animation frame with fade effects applied.

Returns:
   pygame.Surface: The current frame surface with fade alpha
def start_fade_in(self: Any, duration: Optional[float] = None) -> Any
Start a fade-in effect.

Args:
   duration (float, optional): Override fade-in duration. If None, uses initialized value.
def start_fade_out(self: Any, duration: Optional[float] = None) -> Any
Start a fade-out effect.

Args:
   duration (float, optional): Override fade-out duration. If None, uses initialized value.
def set_fade_alpha(self: Any, alpha: int) -> Any
Manually set the fade alpha value.

Args:
   alpha (int): Alpha value from 0 (transparent) to 255 (opaque)
def is_fade_complete(self: Any) -> bool
Check if the current fade effect is complete.

Returns:
   bool: True if no fade effect is active or fade is complete
def reset(self: Any) -> Any
Reset the animation to the first frame and reset fade effects.
Back to Graphics Module