0
# Transform and Image Processing
1
2
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.
3
4
## Capabilities
5
6
### Basic Transformations
7
8
Core image transformation operations for resizing and orientation changes.
9
10
```python { .api }
11
def scale(surface: pygame.Surface, size: tuple[int, int]) -> pygame.Surface:
12
"""
13
Resize surface to new dimensions.
14
15
Args:
16
surface (pygame.Surface): Surface to scale
17
size (tuple[int, int]): New (width, height)
18
19
Returns:
20
pygame.Surface: Scaled surface
21
"""
22
23
def smoothscale(surface: pygame.Surface, size: tuple[int, int]) -> pygame.Surface:
24
"""
25
Scale surface using smooth interpolation (slower but higher quality).
26
27
Args:
28
surface (pygame.Surface): Surface to scale
29
size (tuple[int, int]): New (width, height)
30
31
Returns:
32
pygame.Surface: Smoothly scaled surface
33
"""
34
35
def scale2x(surface: pygame.Surface) -> pygame.Surface:
36
"""
37
Double surface size using 2xSaI algorithm (good for pixel art).
38
39
Args:
40
surface (pygame.Surface): Surface to scale
41
42
Returns:
43
pygame.Surface: Surface scaled to 2x size with smoothing
44
"""
45
46
def rotate(surface: pygame.Surface, angle: float) -> pygame.Surface:
47
"""
48
Rotate surface by angle in degrees.
49
50
Args:
51
surface (pygame.Surface): Surface to rotate
52
angle (float): Rotation angle in degrees (positive = counterclockwise)
53
54
Returns:
55
pygame.Surface: Rotated surface (may be larger to fit rotation)
56
"""
57
58
def rotozoom(surface: pygame.Surface, angle: float, scale: float) -> pygame.Surface:
59
"""
60
Rotate and scale surface simultaneously (more efficient than separate operations).
61
62
Args:
63
surface (pygame.Surface): Surface to transform
64
angle (float): Rotation angle in degrees
65
scale (float): Scale factor (1.0 = original size)
66
67
Returns:
68
pygame.Surface: Rotated and scaled surface
69
"""
70
71
def flip(surface: pygame.Surface, xbool: bool, ybool: bool) -> pygame.Surface:
72
"""
73
Flip surface horizontally and/or vertically.
74
75
Args:
76
surface (pygame.Surface): Surface to flip
77
xbool (bool): Flip horizontally
78
ybool (bool): Flip vertically
79
80
Returns:
81
pygame.Surface: Flipped surface
82
"""
83
```
84
85
### Advanced Transformations
86
87
More complex transformation operations for special effects.
88
89
```python { .api }
90
def chop(surface: pygame.Surface, rect: pygame.Rect) -> pygame.Surface:
91
"""
92
Remove a rectangular area from surface.
93
94
Args:
95
surface (pygame.Surface): Source surface
96
rect (pygame.Rect): Area to remove
97
98
Returns:
99
pygame.Surface: Surface with area removed
100
"""
101
102
def laplacian(surface: pygame.Surface) -> pygame.Surface:
103
"""
104
Apply Laplacian edge detection filter.
105
106
Args:
107
surface (pygame.Surface): Surface to filter
108
109
Returns:
110
pygame.Surface: Filtered surface showing edges
111
"""
112
```
113
114
### Color and Statistical Operations
115
116
Functions for analyzing and manipulating surface colors.
117
118
```python { .api }
119
def average_surfaces(surfaces: list[pygame.Surface], palette = None, search: int = 1) -> pygame.Surface:
120
"""
121
Average multiple surfaces together.
122
123
Args:
124
surfaces (list[pygame.Surface]): List of surfaces to average
125
palette: Color palette for result
126
search (int): Palette search method
127
128
Returns:
129
pygame.Surface: Averaged surface
130
"""
131
132
def average_color(surface: pygame.Surface, rect: pygame.Rect = None, consider_alpha: bool = False) -> pygame.Color:
133
"""
134
Calculate average color of surface or region.
135
136
Args:
137
surface (pygame.Surface): Surface to analyze
138
rect (pygame.Rect, optional): Region to analyze (None for entire surface)
139
consider_alpha (bool): Include alpha channel in calculation
140
141
Returns:
142
pygame.Color: Average color
143
"""
144
145
def grayscale(surface: pygame.Surface) -> pygame.Surface:
146
"""
147
Convert surface to grayscale.
148
149
Args:
150
surface (pygame.Surface): Surface to convert
151
152
Returns:
153
pygame.Surface: Grayscale version of surface
154
"""
155
156
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:
157
"""
158
Apply color threshold filter to surface.
159
160
Args:
161
dest_surface (pygame.Surface): Destination for result
162
surface (pygame.Surface): Source surface
163
search_color: Color to threshold against
164
threshold (tuple): RGBA threshold values
165
set_color (tuple): Color to set matching pixels to
166
set_behavior (int): How to apply threshold
167
search_surf (pygame.Surface, optional): Surface to search in
168
inverse_set (bool): Invert the threshold operation
169
170
Returns:
171
int: Number of pixels that matched threshold
172
"""
173
```
174
175
### Optimized Scaling
176
177
Specialized scaling functions for different use cases.
178
179
```python { .api }
180
def smoothscale_by(surface: pygame.Surface, factor: tuple[float, float]) -> pygame.Surface:
181
"""
182
Smooth scale by factor rather than absolute size.
183
184
Args:
185
surface (pygame.Surface): Surface to scale
186
factor (tuple[float, float]): (x_factor, y_factor) scaling factors
187
188
Returns:
189
pygame.Surface: Scaled surface
190
"""
191
192
def get_smoothscale_backend() -> str:
193
"""
194
Get current smoothscale algorithm backend.
195
196
Returns:
197
str: Backend name ('GENERIC', 'MMX', 'SSE', etc.)
198
"""
199
200
def set_smoothscale_backend(backend: str) -> None:
201
"""
202
Set smoothscale algorithm backend.
203
204
Args:
205
backend (str): Backend to use ('GENERIC', 'MMX', 'SSE')
206
"""
207
```
208
209
## Performance Considerations
210
211
Transform operations create new surfaces and can be expensive. Here are optimization strategies:
212
213
### Pre-computation
214
215
```python { .api }
216
# Instead of transforming every frame:
217
# BAD - transforms every frame
218
def update_bad(self):
219
rotated = pygame.transform.rotate(self.image, self.angle)
220
screen.blit(rotated, self.pos)
221
222
# GOOD - pre-compute rotations
223
def precompute_rotations(self):
224
self.rotated_images = {}
225
for angle in range(0, 360, 5): # Every 5 degrees
226
self.rotated_images[angle] = pygame.transform.rotate(self.image, angle)
227
228
def update_good(self):
229
# Use nearest pre-computed rotation
230
nearest_angle = round(self.angle / 5) * 5
231
rotated = self.rotated_images[nearest_angle]
232
screen.blit(rotated, self.pos)
233
```
234
235
### Scaling Guidelines
236
237
```python { .api }
238
# Use scale() for integer scaling when possible
239
scaled_2x = pygame.transform.scale(surface, (width * 2, height * 2))
240
241
# Use smoothscale() for non-integer scaling or when quality matters
242
scaled_smooth = pygame.transform.smoothscale(surface, (new_width, new_height))
243
244
# Use rotozoom() when rotating AND scaling
245
rotated_scaled = pygame.transform.rotozoom(surface, angle, scale_factor)
246
# Instead of:
247
# rotated = pygame.transform.rotate(surface, angle)
248
# scaled = pygame.transform.scale(rotated, new_size) # Less efficient
249
```
250
251
## Usage Examples
252
253
### Basic Image Transformations
254
255
```python
256
import pygame
257
import math
258
259
pygame.init()
260
screen = pygame.display.set_mode((800, 600))
261
clock = pygame.time.Clock()
262
263
# Create test surface
264
original_surface = pygame.Surface((100, 60))
265
original_surface.fill((255, 0, 0))
266
pygame.draw.rect(original_surface, (255, 255, 255), (10, 10, 80, 40))
267
268
angle = 0
269
scale_factor = 1.0
270
271
running = True
272
while running:
273
for event in pygame.event.get():
274
if event.type == pygame.QUIT:
275
running = False
276
277
keys = pygame.key.get_pressed()
278
279
# Control transformations
280
if keys[pygame.K_LEFT]:
281
angle -= 2
282
if keys[pygame.K_RIGHT]:
283
angle += 2
284
if keys[pygame.K_UP]:
285
scale_factor = min(3.0, scale_factor + 0.02)
286
if keys[pygame.K_DOWN]:
287
scale_factor = max(0.1, scale_factor - 0.02)
288
289
screen.fill((50, 50, 50))
290
291
# Original
292
screen.blit(original_surface, (50, 50))
293
294
# Rotated only
295
rotated = pygame.transform.rotate(original_surface, angle)
296
screen.blit(rotated, (200, 50))
297
298
# Scaled only
299
new_size = (int(100 * scale_factor), int(60 * scale_factor))
300
scaled = pygame.transform.scale(original_surface, new_size)
301
screen.blit(scaled, (50, 150))
302
303
# Rotate and scale combined
304
rotozoom = pygame.transform.rotozoom(original_surface, angle, scale_factor)
305
screen.blit(rotozoom, (400, 150))
306
307
# Flipped
308
flipped_h = pygame.transform.flip(original_surface, True, False)
309
flipped_v = pygame.transform.flip(original_surface, False, True)
310
flipped_both = pygame.transform.flip(original_surface, True, True)
311
312
screen.blit(flipped_h, (50, 300))
313
screen.blit(flipped_v, (200, 300))
314
screen.blit(flipped_both, (350, 300))
315
316
# Labels
317
font = pygame.font.Font(None, 24)
318
labels = ["Original", "Rotated", "Scaled", "Rotozoom", "Flip H", "Flip V", "Flip Both"]
319
positions = [(50, 30), (200, 30), (50, 130), (400, 130), (50, 280), (200, 280), (350, 280)]
320
321
for label, pos in zip(labels, positions):
322
text = font.render(label, True, (255, 255, 255))
323
screen.blit(text, pos)
324
325
# Instructions
326
instructions = font.render("Arrow keys: rotate and scale", True, (255, 255, 255))
327
screen.blit(instructions, (50, 500))
328
329
pygame.display.flip()
330
clock.tick(60)
331
332
pygame.quit()
333
```
334
335
### Smooth Animation with Pre-computed Rotations
336
337
```python
338
import pygame
339
import math
340
341
class RotatingSprite:
342
def __init__(self, image_path, x, y):
343
try:
344
self.original_image = pygame.image.load(image_path).convert_alpha()
345
except:
346
# Create fallback surface
347
self.original_image = pygame.Surface((50, 50), pygame.SRCALPHA)
348
pygame.draw.polygon(self.original_image, (255, 255, 255),
349
[(25, 0), (50, 50), (0, 50)])
350
351
self.x = x
352
self.y = y
353
self.angle = 0
354
self.rotation_speed = 60 # degrees per second
355
356
# Pre-compute rotations for smooth animation
357
self.rotated_images = {}
358
self.rotation_step = 3 # degrees between cached rotations
359
for angle in range(0, 360, self.rotation_step):
360
rotated = pygame.transform.rotate(self.original_image, angle)
361
self.rotated_images[angle] = rotated
362
363
def update(self, dt):
364
self.angle += self.rotation_speed * dt
365
self.angle %= 360
366
367
def draw(self, screen):
368
# Find nearest cached rotation
369
nearest_angle = round(self.angle / self.rotation_step) * self.rotation_step
370
nearest_angle %= 360
371
372
rotated_image = self.rotated_images[nearest_angle]
373
374
# Center the rotated image
375
rect = rotated_image.get_rect()
376
rect.center = (self.x, self.y)
377
378
screen.blit(rotated_image, rect)
379
380
pygame.init()
381
screen = pygame.display.set_mode((800, 600))
382
clock = pygame.time.Clock()
383
384
# Create rotating sprites
385
sprites = [
386
RotatingSprite(None, 200, 200), # Will use fallback
387
RotatingSprite(None, 400, 200),
388
RotatingSprite(None, 600, 200),
389
]
390
391
# Give different rotation speeds
392
sprites[0].rotation_speed = 30
393
sprites[1].rotation_speed = 60
394
sprites[2].rotation_speed = 120
395
396
running = True
397
while running:
398
dt = clock.tick(60) / 1000.0
399
400
for event in pygame.event.get():
401
if event.type == pygame.QUIT:
402
running = False
403
404
# Update sprites
405
for sprite in sprites:
406
sprite.update(dt)
407
408
# Draw
409
screen.fill((0, 0, 50))
410
411
for sprite in sprites:
412
sprite.draw(screen)
413
414
# Show FPS
415
font = pygame.font.Font(None, 36)
416
fps_text = font.render(f"FPS: {clock.get_fps():.1f}", True, (255, 255, 255))
417
screen.blit(fps_text, (10, 10))
418
419
pygame.display.flip()
420
421
pygame.quit()
422
```
423
424
### Image Processing Effects
425
426
```python
427
import pygame
428
import random
429
430
def create_test_image():
431
"""Create colorful test image"""
432
surface = pygame.Surface((200, 150))
433
434
# Gradient background
435
for y in range(150):
436
color_value = int(255 * y / 150)
437
pygame.draw.line(surface, (color_value, 100, 255 - color_value), (0, y), (200, y))
438
439
# Add some shapes
440
pygame.draw.circle(surface, (255, 255, 0), (50, 75), 30)
441
pygame.draw.rect(surface, (0, 255, 0), (120, 50, 60, 50))
442
pygame.draw.polygon(surface, (255, 0, 255), [(150, 20), (180, 40), (160, 60), (130, 40)])
443
444
return surface
445
446
pygame.init()
447
screen = pygame.display.set_mode((1000, 700))
448
clock = pygame.time.Clock()
449
450
# Create original image
451
original = create_test_image()
452
453
# Pre-compute some effects
454
effects = {}
455
456
# Scaling effects
457
effects['scaled_2x'] = pygame.transform.scale2x(original)
458
effects['smooth_scaled'] = pygame.transform.smoothscale(original, (300, 225))
459
effects['small_scaled'] = pygame.transform.scale(original, (100, 75))
460
461
# Color effects
462
effects['grayscale'] = pygame.transform.grayscale(original)
463
464
# Get average color
465
avg_color = pygame.transform.average_color(original)
466
effects['avg_color_surface'] = pygame.Surface((200, 150))
467
effects['avg_color_surface'].fill(avg_color)
468
469
# Rotation effects
470
effects['rotated_45'] = pygame.transform.rotate(original, 45)
471
effects['rotozoom'] = pygame.transform.rotozoom(original, 30, 0.7)
472
473
# Flip effects
474
effects['flipped_h'] = pygame.transform.flip(original, True, False)
475
effects['flipped_v'] = pygame.transform.flip(original, False, True)
476
477
# Laplacian edge detection
478
try:
479
effects['edges'] = pygame.transform.laplacian(original)
480
except:
481
effects['edges'] = original.copy() # Fallback if not available
482
483
running = True
484
effect_names = list(effects.keys())
485
current_effect = 0
486
487
while running:
488
for event in pygame.event.get():
489
if event.type == pygame.QUIT:
490
running = False
491
elif event.type == pygame.KEYDOWN:
492
if event.key == pygame.K_LEFT:
493
current_effect = (current_effect - 1) % len(effect_names)
494
elif event.key == pygame.K_RIGHT:
495
current_effect = (current_effect + 1) % len(effect_names)
496
497
screen.fill((40, 40, 40))
498
499
# Draw original
500
screen.blit(original, (50, 50))
501
502
# Draw current effect
503
effect_name = effect_names[current_effect]
504
effect_surface = effects[effect_name]
505
screen.blit(effect_surface, (300, 50))
506
507
# Labels
508
font = pygame.font.Font(None, 36)
509
510
original_label = font.render("Original", True, (255, 255, 255))
511
screen.blit(original_label, (50, 10))
512
513
effect_label = font.render(f"Effect: {effect_name}", True, (255, 255, 255))
514
screen.blit(effect_label, (300, 10))
515
516
# Instructions
517
instructions = font.render("Left/Right arrows to change effect", True, (255, 255, 255))
518
screen.blit(instructions, (50, 400))
519
520
# Show average color info
521
color_info = font.render(f"Average color: {avg_color}", True, (255, 255, 255))
522
screen.blit(color_info, (50, 450))
523
524
pygame.display.flip()
525
clock.tick(60)
526
527
pygame.quit()
528
```
529
530
### Performance Comparison
531
532
```python
533
import pygame
534
import time
535
536
def performance_test():
537
pygame.init()
538
539
# Create test surface
540
test_surface = pygame.Surface((100, 100))
541
test_surface.fill((255, 0, 0))
542
543
iterations = 1000
544
545
# Test regular scale
546
start_time = time.time()
547
for _ in range(iterations):
548
scaled = pygame.transform.scale(test_surface, (200, 200))
549
scale_time = time.time() - start_time
550
551
# Test smooth scale
552
start_time = time.time()
553
for _ in range(iterations):
554
scaled = pygame.transform.smoothscale(test_surface, (200, 200))
555
smoothscale_time = time.time() - start_time
556
557
# Test rotation
558
start_time = time.time()
559
for i in range(iterations):
560
rotated = pygame.transform.rotate(test_surface, i % 360)
561
rotation_time = time.time() - start_time
562
563
# Test rotozoom
564
start_time = time.time()
565
for i in range(iterations):
566
rotozoomed = pygame.transform.rotozoom(test_surface, i % 360, 1.0)
567
rotozoom_time = time.time() - start_time
568
569
print(f"Performance test results ({iterations} iterations):")
570
print(f"Regular scale: {scale_time:.3f} seconds")
571
print(f"Smooth scale: {smoothscale_time:.3f} seconds ({smoothscale_time/scale_time:.1f}x slower)")
572
print(f"Rotation: {rotation_time:.3f} seconds")
573
print(f"Rotozoom: {rotozoom_time:.3f} seconds ({rotozoom_time/rotation_time:.1f}x vs rotation)")
574
575
pygame.quit()
576
577
if __name__ == "__main__":
578
performance_test()
579
```
580
581
### Advanced Threshold Effects
582
583
```python
584
import pygame
585
import random
586
587
def create_color_test_surface():
588
"""Create surface with various colors for threshold testing"""
589
surface = pygame.Surface((300, 200))
590
591
# Create color regions
592
colors = [
593
(255, 0, 0), # Red
594
(0, 255, 0), # Green
595
(0, 0, 255), # Blue
596
(255, 255, 0), # Yellow
597
(255, 0, 255), # Magenta
598
(0, 255, 255), # Cyan
599
(128, 128, 128), # Gray
600
(255, 255, 255), # White
601
]
602
603
# Fill with color patches
604
patch_width = surface.get_width() // 4
605
patch_height = surface.get_height() // 2
606
607
for i, color in enumerate(colors):
608
x = (i % 4) * patch_width
609
y = (i // 4) * patch_height
610
rect = pygame.Rect(x, y, patch_width, patch_height)
611
pygame.draw.rect(surface, color, rect)
612
613
return surface
614
615
pygame.init()
616
screen = pygame.display.set_mode((800, 600))
617
clock = pygame.time.Clock()
618
619
# Create test surface
620
original = create_color_test_surface()
621
622
# Threshold settings
623
threshold_color = [128, 128, 128, 255] # RGBA
624
set_color = [255, 255, 255, 255] # White
625
threshold_tolerance = [50, 50, 50, 0] # RGBA tolerance
626
627
running = True
628
while running:
629
for event in pygame.event.get():
630
if event.type == pygame.QUIT:
631
running = False
632
elif event.type == pygame.KEYDOWN:
633
if event.key == pygame.K_r:
634
threshold_color[0] = random.randint(0, 255)
635
elif event.key == pygame.K_g:
636
threshold_color[1] = random.randint(0, 255)
637
elif event.key == pygame.K_b:
638
threshold_color[2] = random.randint(0, 255)
639
640
# Create threshold result
641
result_surface = pygame.Surface(original.get_size())
642
643
try:
644
num_matches = pygame.transform.threshold(
645
result_surface,
646
original,
647
tuple(threshold_color[:3]),
648
tuple(threshold_tolerance[:3]),
649
tuple(set_color[:3])
650
)
651
except:
652
# Fallback if threshold not available
653
result_surface = original.copy()
654
num_matches = 0
655
656
screen.fill((50, 50, 50))
657
658
# Draw original and result
659
screen.blit(original, (50, 50))
660
screen.blit(result_surface, (400, 50))
661
662
# Labels and info
663
font = pygame.font.Font(None, 36)
664
665
original_label = font.render("Original", True, (255, 255, 255))
666
screen.blit(original_label, (50, 10))
667
668
result_label = font.render("Threshold Result", True, (255, 255, 255))
669
screen.blit(result_label, (400, 10))
670
671
# Show threshold color
672
threshold_rect = pygame.Rect(50, 300, 50, 50)
673
pygame.draw.rect(screen, tuple(threshold_color[:3]), threshold_rect)
674
675
threshold_info = font.render(f"Threshold Color: {threshold_color[:3]}", True, (255, 255, 255))
676
screen.blit(threshold_info, (120, 310))
677
678
matches_info = font.render(f"Matching pixels: {num_matches}", True, (255, 255, 255))
679
screen.blit(matches_info, (50, 370))
680
681
instructions = [
682
"Press R to randomize red component",
683
"Press G to randomize green component",
684
"Press B to randomize blue component"
685
]
686
687
for i, instruction in enumerate(instructions):
688
text = font.render(instruction, True, (255, 255, 255))
689
screen.blit(text, (50, 450 + i * 30))
690
691
pygame.display.flip()
692
clock.tick(60)
693
694
pygame.quit()
695
```