Cross-platform library for developing multimedia applications and video games in Python built on top of SDL
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.
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
"""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
"""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
"""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')
"""Transform operations create new surfaces and can be expensive. Here are optimization strategies:
# 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)# 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 efficientimport 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()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()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()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()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()