CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pygame

Cross-platform library for developing multimedia applications and video games in Python built on top of SDL

Overview
Eval results
Files

transform-image.mddocs/

Transform and Image Processing

Image transformation operations and advanced image processing. Pygame's transform module provides functions for scaling, rotating, flipping images, and applying various filters and effects to surfaces.

Capabilities

Basic Transformations

Core image transformation operations for resizing and orientation changes.

def scale(surface: pygame.Surface, size: tuple[int, int]) -> pygame.Surface:
    """
    Resize surface to new dimensions.

    Args:
        surface (pygame.Surface): Surface to scale
        size (tuple[int, int]): New (width, height)

    Returns:
        pygame.Surface: Scaled surface
    """

def smoothscale(surface: pygame.Surface, size: tuple[int, int]) -> pygame.Surface:
    """
    Scale surface using smooth interpolation (slower but higher quality).

    Args:
        surface (pygame.Surface): Surface to scale
        size (tuple[int, int]): New (width, height)

    Returns:
        pygame.Surface: Smoothly scaled surface
    """

def scale2x(surface: pygame.Surface) -> pygame.Surface:
    """
    Double surface size using 2xSaI algorithm (good for pixel art).

    Args:
        surface (pygame.Surface): Surface to scale

    Returns:
        pygame.Surface: Surface scaled to 2x size with smoothing
    """

def rotate(surface: pygame.Surface, angle: float) -> pygame.Surface:
    """
    Rotate surface by angle in degrees.

    Args:
        surface (pygame.Surface): Surface to rotate
        angle (float): Rotation angle in degrees (positive = counterclockwise)

    Returns:
        pygame.Surface: Rotated surface (may be larger to fit rotation)
    """

def rotozoom(surface: pygame.Surface, angle: float, scale: float) -> pygame.Surface:
    """
    Rotate and scale surface simultaneously (more efficient than separate operations).

    Args:
        surface (pygame.Surface): Surface to transform
        angle (float): Rotation angle in degrees
        scale (float): Scale factor (1.0 = original size)

    Returns:
        pygame.Surface: Rotated and scaled surface
    """

def flip(surface: pygame.Surface, xbool: bool, ybool: bool) -> pygame.Surface:
    """
    Flip surface horizontally and/or vertically.

    Args:
        surface (pygame.Surface): Surface to flip
        xbool (bool): Flip horizontally
        ybool (bool): Flip vertically

    Returns:
        pygame.Surface: Flipped surface
    """

Advanced Transformations

More complex transformation operations for special effects.

def chop(surface: pygame.Surface, rect: pygame.Rect) -> pygame.Surface:
    """
    Remove a rectangular area from surface.

    Args:
        surface (pygame.Surface): Source surface
        rect (pygame.Rect): Area to remove

    Returns:
        pygame.Surface: Surface with area removed
    """

def laplacian(surface: pygame.Surface) -> pygame.Surface:
    """
    Apply Laplacian edge detection filter.

    Args:
        surface (pygame.Surface): Surface to filter

    Returns:
        pygame.Surface: Filtered surface showing edges
    """

Color and Statistical Operations

Functions for analyzing and manipulating surface colors.

def average_surfaces(surfaces: list[pygame.Surface], palette = None, search: int = 1) -> pygame.Surface:
    """
    Average multiple surfaces together.

    Args:
        surfaces (list[pygame.Surface]): List of surfaces to average
        palette: Color palette for result
        search (int): Palette search method

    Returns:
        pygame.Surface: Averaged surface
    """

def average_color(surface: pygame.Surface, rect: pygame.Rect = None, consider_alpha: bool = False) -> pygame.Color:
    """
    Calculate average color of surface or region.

    Args:
        surface (pygame.Surface): Surface to analyze
        rect (pygame.Rect, optional): Region to analyze (None for entire surface)
        consider_alpha (bool): Include alpha channel in calculation

    Returns:
        pygame.Color: Average color
    """

def grayscale(surface: pygame.Surface) -> pygame.Surface:
    """
    Convert surface to grayscale.

    Args:
        surface (pygame.Surface): Surface to convert

    Returns:
        pygame.Surface: Grayscale version of surface
    """

def threshold(dest_surface: pygame.Surface, surface: pygame.Surface, search_color, threshold: tuple = (0,0,0,0), set_color: tuple = (0,0,0,0), set_behavior: int = 1, search_surf: pygame.Surface = None, inverse_set: bool = False) -> int:
    """
    Apply color threshold filter to surface.

    Args:
        dest_surface (pygame.Surface): Destination for result
        surface (pygame.Surface): Source surface
        search_color: Color to threshold against
        threshold (tuple): RGBA threshold values
        set_color (tuple): Color to set matching pixels to
        set_behavior (int): How to apply threshold
        search_surf (pygame.Surface, optional): Surface to search in
        inverse_set (bool): Invert the threshold operation

    Returns:
        int: Number of pixels that matched threshold
    """

Optimized Scaling

Specialized scaling functions for different use cases.

def smoothscale_by(surface: pygame.Surface, factor: tuple[float, float]) -> pygame.Surface:
    """
    Smooth scale by factor rather than absolute size.

    Args:
        surface (pygame.Surface): Surface to scale
        factor (tuple[float, float]): (x_factor, y_factor) scaling factors

    Returns:
        pygame.Surface: Scaled surface
    """

def get_smoothscale_backend() -> str:
    """
    Get current smoothscale algorithm backend.

    Returns:
        str: Backend name ('GENERIC', 'MMX', 'SSE', etc.)
    """

def set_smoothscale_backend(backend: str) -> None:
    """
    Set smoothscale algorithm backend.

    Args:
        backend (str): Backend to use ('GENERIC', 'MMX', 'SSE')
    """

Performance Considerations

Transform operations create new surfaces and can be expensive. Here are optimization strategies:

Pre-computation

# Instead of transforming every frame:
# BAD - transforms every frame
def update_bad(self):
    rotated = pygame.transform.rotate(self.image, self.angle)
    screen.blit(rotated, self.pos)

# GOOD - pre-compute rotations
def precompute_rotations(self):
    self.rotated_images = {}
    for angle in range(0, 360, 5):  # Every 5 degrees
        self.rotated_images[angle] = pygame.transform.rotate(self.image, angle)

def update_good(self):
    # Use nearest pre-computed rotation
    nearest_angle = round(self.angle / 5) * 5
    rotated = self.rotated_images[nearest_angle]
    screen.blit(rotated, self.pos)

Scaling Guidelines

# Use scale() for integer scaling when possible
scaled_2x = pygame.transform.scale(surface, (width * 2, height * 2))

# Use smoothscale() for non-integer scaling or when quality matters
scaled_smooth = pygame.transform.smoothscale(surface, (new_width, new_height))

# Use rotozoom() when rotating AND scaling
rotated_scaled = pygame.transform.rotozoom(surface, angle, scale_factor)
# Instead of:
# rotated = pygame.transform.rotate(surface, angle)
# scaled = pygame.transform.scale(rotated, new_size)  # Less efficient

Usage Examples

Basic Image Transformations

import pygame
import math

pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()

# Create test surface
original_surface = pygame.Surface((100, 60))
original_surface.fill((255, 0, 0))
pygame.draw.rect(original_surface, (255, 255, 255), (10, 10, 80, 40))

angle = 0
scale_factor = 1.0

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    keys = pygame.key.get_pressed()

    # Control transformations
    if keys[pygame.K_LEFT]:
        angle -= 2
    if keys[pygame.K_RIGHT]:
        angle += 2
    if keys[pygame.K_UP]:
        scale_factor = min(3.0, scale_factor + 0.02)
    if keys[pygame.K_DOWN]:
        scale_factor = max(0.1, scale_factor - 0.02)

    screen.fill((50, 50, 50))

    # Original
    screen.blit(original_surface, (50, 50))

    # Rotated only
    rotated = pygame.transform.rotate(original_surface, angle)
    screen.blit(rotated, (200, 50))

    # Scaled only
    new_size = (int(100 * scale_factor), int(60 * scale_factor))
    scaled = pygame.transform.scale(original_surface, new_size)
    screen.blit(scaled, (50, 150))

    # Rotate and scale combined
    rotozoom = pygame.transform.rotozoom(original_surface, angle, scale_factor)
    screen.blit(rotozoom, (400, 150))

    # Flipped
    flipped_h = pygame.transform.flip(original_surface, True, False)
    flipped_v = pygame.transform.flip(original_surface, False, True)
    flipped_both = pygame.transform.flip(original_surface, True, True)

    screen.blit(flipped_h, (50, 300))
    screen.blit(flipped_v, (200, 300))
    screen.blit(flipped_both, (350, 300))

    # Labels
    font = pygame.font.Font(None, 24)
    labels = ["Original", "Rotated", "Scaled", "Rotozoom", "Flip H", "Flip V", "Flip Both"]
    positions = [(50, 30), (200, 30), (50, 130), (400, 130), (50, 280), (200, 280), (350, 280)]

    for label, pos in zip(labels, positions):
        text = font.render(label, True, (255, 255, 255))
        screen.blit(text, pos)

    # Instructions
    instructions = font.render("Arrow keys: rotate and scale", True, (255, 255, 255))
    screen.blit(instructions, (50, 500))

    pygame.display.flip()
    clock.tick(60)

pygame.quit()

Smooth Animation with Pre-computed Rotations

import pygame
import math

class RotatingSprite:
    def __init__(self, image_path, x, y):
        try:
            self.original_image = pygame.image.load(image_path).convert_alpha()
        except:
            # Create fallback surface
            self.original_image = pygame.Surface((50, 50), pygame.SRCALPHA)
            pygame.draw.polygon(self.original_image, (255, 255, 255),
                              [(25, 0), (50, 50), (0, 50)])

        self.x = x
        self.y = y
        self.angle = 0
        self.rotation_speed = 60  # degrees per second

        # Pre-compute rotations for smooth animation
        self.rotated_images = {}
        self.rotation_step = 3  # degrees between cached rotations
        for angle in range(0, 360, self.rotation_step):
            rotated = pygame.transform.rotate(self.original_image, angle)
            self.rotated_images[angle] = rotated

    def update(self, dt):
        self.angle += self.rotation_speed * dt
        self.angle %= 360

    def draw(self, screen):
        # Find nearest cached rotation
        nearest_angle = round(self.angle / self.rotation_step) * self.rotation_step
        nearest_angle %= 360

        rotated_image = self.rotated_images[nearest_angle]

        # Center the rotated image
        rect = rotated_image.get_rect()
        rect.center = (self.x, self.y)

        screen.blit(rotated_image, rect)

pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()

# Create rotating sprites
sprites = [
    RotatingSprite(None, 200, 200),  # Will use fallback
    RotatingSprite(None, 400, 200),
    RotatingSprite(None, 600, 200),
]

# Give different rotation speeds
sprites[0].rotation_speed = 30
sprites[1].rotation_speed = 60
sprites[2].rotation_speed = 120

running = True
while running:
    dt = clock.tick(60) / 1000.0

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # Update sprites
    for sprite in sprites:
        sprite.update(dt)

    # Draw
    screen.fill((0, 0, 50))

    for sprite in sprites:
        sprite.draw(screen)

    # Show FPS
    font = pygame.font.Font(None, 36)
    fps_text = font.render(f"FPS: {clock.get_fps():.1f}", True, (255, 255, 255))
    screen.blit(fps_text, (10, 10))

    pygame.display.flip()

pygame.quit()

Image Processing Effects

import pygame
import random

def create_test_image():
    """Create colorful test image"""
    surface = pygame.Surface((200, 150))

    # Gradient background
    for y in range(150):
        color_value = int(255 * y / 150)
        pygame.draw.line(surface, (color_value, 100, 255 - color_value), (0, y), (200, y))

    # Add some shapes
    pygame.draw.circle(surface, (255, 255, 0), (50, 75), 30)
    pygame.draw.rect(surface, (0, 255, 0), (120, 50, 60, 50))
    pygame.draw.polygon(surface, (255, 0, 255), [(150, 20), (180, 40), (160, 60), (130, 40)])

    return surface

pygame.init()
screen = pygame.display.set_mode((1000, 700))
clock = pygame.time.Clock()

# Create original image
original = create_test_image()

# Pre-compute some effects
effects = {}

# Scaling effects
effects['scaled_2x'] = pygame.transform.scale2x(original)
effects['smooth_scaled'] = pygame.transform.smoothscale(original, (300, 225))
effects['small_scaled'] = pygame.transform.scale(original, (100, 75))

# Color effects
effects['grayscale'] = pygame.transform.grayscale(original)

# Get average color
avg_color = pygame.transform.average_color(original)
effects['avg_color_surface'] = pygame.Surface((200, 150))
effects['avg_color_surface'].fill(avg_color)

# Rotation effects
effects['rotated_45'] = pygame.transform.rotate(original, 45)
effects['rotozoom'] = pygame.transform.rotozoom(original, 30, 0.7)

# Flip effects
effects['flipped_h'] = pygame.transform.flip(original, True, False)
effects['flipped_v'] = pygame.transform.flip(original, False, True)

# Laplacian edge detection
try:
    effects['edges'] = pygame.transform.laplacian(original)
except:
    effects['edges'] = original.copy()  # Fallback if not available

running = True
effect_names = list(effects.keys())
current_effect = 0

while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT:
                current_effect = (current_effect - 1) % len(effect_names)
            elif event.key == pygame.K_RIGHT:
                current_effect = (current_effect + 1) % len(effect_names)

    screen.fill((40, 40, 40))

    # Draw original
    screen.blit(original, (50, 50))

    # Draw current effect
    effect_name = effect_names[current_effect]
    effect_surface = effects[effect_name]
    screen.blit(effect_surface, (300, 50))

    # Labels
    font = pygame.font.Font(None, 36)

    original_label = font.render("Original", True, (255, 255, 255))
    screen.blit(original_label, (50, 10))

    effect_label = font.render(f"Effect: {effect_name}", True, (255, 255, 255))
    screen.blit(effect_label, (300, 10))

    # Instructions
    instructions = font.render("Left/Right arrows to change effect", True, (255, 255, 255))
    screen.blit(instructions, (50, 400))

    # Show average color info
    color_info = font.render(f"Average color: {avg_color}", True, (255, 255, 255))
    screen.blit(color_info, (50, 450))

    pygame.display.flip()
    clock.tick(60)

pygame.quit()

Performance Comparison

import pygame
import time

def performance_test():
    pygame.init()

    # Create test surface
    test_surface = pygame.Surface((100, 100))
    test_surface.fill((255, 0, 0))

    iterations = 1000

    # Test regular scale
    start_time = time.time()
    for _ in range(iterations):
        scaled = pygame.transform.scale(test_surface, (200, 200))
    scale_time = time.time() - start_time

    # Test smooth scale
    start_time = time.time()
    for _ in range(iterations):
        scaled = pygame.transform.smoothscale(test_surface, (200, 200))
    smoothscale_time = time.time() - start_time

    # Test rotation
    start_time = time.time()
    for i in range(iterations):
        rotated = pygame.transform.rotate(test_surface, i % 360)
    rotation_time = time.time() - start_time

    # Test rotozoom
    start_time = time.time()
    for i in range(iterations):
        rotozoomed = pygame.transform.rotozoom(test_surface, i % 360, 1.0)
    rotozoom_time = time.time() - start_time

    print(f"Performance test results ({iterations} iterations):")
    print(f"Regular scale:  {scale_time:.3f} seconds")
    print(f"Smooth scale:   {smoothscale_time:.3f} seconds ({smoothscale_time/scale_time:.1f}x slower)")
    print(f"Rotation:       {rotation_time:.3f} seconds")
    print(f"Rotozoom:       {rotozoom_time:.3f} seconds ({rotozoom_time/rotation_time:.1f}x vs rotation)")

    pygame.quit()

if __name__ == "__main__":
    performance_test()

Advanced Threshold Effects

import pygame
import random

def create_color_test_surface():
    """Create surface with various colors for threshold testing"""
    surface = pygame.Surface((300, 200))

    # Create color regions
    colors = [
        (255, 0, 0),    # Red
        (0, 255, 0),    # Green
        (0, 0, 255),    # Blue
        (255, 255, 0),  # Yellow
        (255, 0, 255),  # Magenta
        (0, 255, 255),  # Cyan
        (128, 128, 128), # Gray
        (255, 255, 255), # White
    ]

    # Fill with color patches
    patch_width = surface.get_width() // 4
    patch_height = surface.get_height() // 2

    for i, color in enumerate(colors):
        x = (i % 4) * patch_width
        y = (i // 4) * patch_height
        rect = pygame.Rect(x, y, patch_width, patch_height)
        pygame.draw.rect(surface, color, rect)

    return surface

pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()

# Create test surface
original = create_color_test_surface()

# Threshold settings
threshold_color = [128, 128, 128, 255]  # RGBA
set_color = [255, 255, 255, 255]        # White
threshold_tolerance = [50, 50, 50, 0]   # RGBA tolerance

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_r:
                threshold_color[0] = random.randint(0, 255)
            elif event.key == pygame.K_g:
                threshold_color[1] = random.randint(0, 255)
            elif event.key == pygame.K_b:
                threshold_color[2] = random.randint(0, 255)

    # Create threshold result
    result_surface = pygame.Surface(original.get_size())

    try:
        num_matches = pygame.transform.threshold(
            result_surface,
            original,
            tuple(threshold_color[:3]),
            tuple(threshold_tolerance[:3]),
            tuple(set_color[:3])
        )
    except:
        # Fallback if threshold not available
        result_surface = original.copy()
        num_matches = 0

    screen.fill((50, 50, 50))

    # Draw original and result
    screen.blit(original, (50, 50))
    screen.blit(result_surface, (400, 50))

    # Labels and info
    font = pygame.font.Font(None, 36)

    original_label = font.render("Original", True, (255, 255, 255))
    screen.blit(original_label, (50, 10))

    result_label = font.render("Threshold Result", True, (255, 255, 255))
    screen.blit(result_label, (400, 10))

    # Show threshold color
    threshold_rect = pygame.Rect(50, 300, 50, 50)
    pygame.draw.rect(screen, tuple(threshold_color[:3]), threshold_rect)

    threshold_info = font.render(f"Threshold Color: {threshold_color[:3]}", True, (255, 255, 255))
    screen.blit(threshold_info, (120, 310))

    matches_info = font.render(f"Matching pixels: {num_matches}", True, (255, 255, 255))
    screen.blit(matches_info, (50, 370))

    instructions = [
        "Press R to randomize red component",
        "Press G to randomize green component",
        "Press B to randomize blue component"
    ]

    for i, instruction in enumerate(instructions):
        text = font.render(instruction, True, (255, 255, 255))
        screen.blit(text, (50, 450 + i * 30))

    pygame.display.flip()
    clock.tick(60)

pygame.quit()

Install with Tessl CLI

npx tessl i tessl/pypi-pygame

docs

advanced-drawing.md

audio-sound.md

core-system.md

drawing-shapes.md

event-input.md

game-objects.md

graphics-display.md

index.md

input-devices.md

joystick-gamepad.md

math-utils.md

surface-image.md

text-font.md

time-animation.md

transform-image.md

tile.json