Pygame - Smooth Transform of Image/Surface - python

Is there a way to create a smooth transition between one image and another?
Say I have Image X and Image Y
ImageX = pygame.image.load('foo.png')
ImageY = pygame.image.load('oof.png')
Image X transforms into image Y when W = 5
Normally, I would just do this in the draw code:
if w == 5:
screen.blit(ImageY, (100, 100))
else:
screen.blit(ImageX, (100, 100))
But that just makes image X become replaced with ImageY, one frame its imageX, another frame its imageY
How would I make ImageX transition into ImageY smoothly? Like the transition attribute in css.

You can assign transparency (0-255) to surface with set_alpha
ImageX.set_alpha(128)
and when you blit ImageX after ImageY then you should see both images.
blit(ImageY, ...)
blit(ImageX, ...)
You have to only change set_alpha() (from 255 to 0) in loop to get smooth efect.
if transparency > 0:
transparency -= 1
ImageX.set_alpha(transparency)
blit(ImageY, ...)
blit(ImageX, ...)
BTW: probably to use set_alpha() image has to use convert(), not convert_alpha().
See my examples on GitHub: pygame - transparency

To set the alpha/transparency of images with per-pixel alpha, you need to use a little trick. You can create an intermediate surface, fill it with white and the desired alpha value and then blit it onto the other surface with the pygame.BLEND_RGBA_MULT flag. That will leave fully transparent pixels untouched and just change the transparency of the visible parts.
So if you want to fade one surface in and the other out, you just have to store their alpha values, change them every frame and then call the change_alpha function to get new surfaces with the right transparency. (I'm using text surfaces in this example, but it works with other surfaces/loaded images as well).
import pygame as pg
def change_alpha(orig_surf, alpha):
"""Create a copy of orig_surf with the desired alpha value.
This function creates another surface with the desired alpha
value and then blits it onto the copy of the original surface
with the `BLEND_RGBA_MULT` flag to change the transparency."""
surf = orig_surf.copy()
# This surface is used to adjust the alpha of the txt_surf.
alpha_surf = pg.Surface(surf.get_size(), pg.SRCALPHA)
alpha_surf.fill((255, 255, 255, alpha)) # Set the alpha value.
surf.blit(alpha_surf, (0, 0), special_flags=pg.BLEND_RGBA_MULT)
return surf
def main():
clock = pg.time.Clock()
screen = pg.display.set_mode((640, 480))
font = pg.font.Font(None, 64)
# The original surface which will never be modified.
orig_surf = font.render('Enter your text', True, pg.Color('dodgerblue'))
alpha = 255 # The current alpha value of the surface.
# Surface 2
orig_surf2 = font.render('Another text surface', True, pg.Color('sienna1'))
alpha2 = 0
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
if alpha > 0:
# Reduce alpha each frame.
alpha -= 4
alpha = max(0, alpha) # Make sure it doesn't go below 0.
surf = change_alpha(orig_surf, alpha)
if alpha2 < 255:
alpha2 += 4
alpha2 = min(255, alpha2)
surf2 = change_alpha(orig_surf2, alpha2)
screen.fill((30, 30, 30))
screen.blit(surf, (30, 60))
screen.blit(surf2, (30, 60))
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
pg.init()
main()
pg.quit()

Related

pygame surface rotating in a nonsensical way [duplicate]

This question already has answers here:
How do I rotate an image around its center using Pygame?
(6 answers)
Closed 3 years ago.
I'm trying to build a simple game and so far I'm just learning the basics.
I'm trying to draw a rectangle tilted 45°, but I couldn't figure how to keep it centered even after reading some previous question here on SO.
So I tried making a rectangle that keeps rotating; this is the corresponding code.
alpha=0
while True:
w, h=screen.get_size()
s=pygame.Surface((w/2, h))
pygame.draw.rect(s, col, (300,150,50,10))
s=pygame.transform.rotozoom(s, alpha, 1)
alpha+=2
s.set_colorkey((0,0,0))
background.blit(s, (0, 0))
# flip screen, etc
The surface should keep rotating forever around some center (I wanted to use this to understand clearly which it was), but it moves in an irregular way.
This is the video of what happens [...].
EDIT:marked as duplicate, I'm removing the video link
Rotating a rectangle (not image) in pygame provides you an answer:
Example code:
import pygame as py
# define constants
WIDTH = 500
HEIGHT = 500
FPS = 30
# define colors
BLACK = (0 , 0 , 0)
GREEN = (0 , 255 , 0)
# initialize pygame and create screen
py.init()
screen = py.display.set_mode((WIDTH , HEIGHT))
# for setting FPS
clock = py.time.Clock()
rot = 0
rot_speed = 3
# define a surface (RECTANGLE)
image_orig = py.Surface((100 , 100))
# for making transparent background while rotating an image
image_orig.set_colorkey(BLACK)
# fill the rectangle / surface with green color
image_orig.fill(GREEN)
# creating a copy of orignal image for smooth rotation
image = image_orig.copy()
image.set_colorkey(BLACK)
# define rect for placing the rectangle at the desired position
rect = image.get_rect()
rect.center = (WIDTH // 2 , HEIGHT // 2)
# keep rotating the rectangle until running is set to False
running = True
while running:
# set FPS
clock.tick(FPS)
# clear the screen every time before drawing new objects
screen.fill(BLACK)
# check for the exit
for event in py.event.get():
if event.type == py.QUIT:
running = False
# making a copy of the old center of the rectangle
old_center = rect.center
# defining angle of the rotation
rot = (rot + rot_speed) % 360
# rotating the orignal image
new_image = py.transform.rotate(image_orig , rot)
rect = new_image.get_rect()
# set the rotated rectangle to the old center
rect.center = old_center
# drawing the rotated rectangle to the screen
screen.blit(new_image , rect)
# flipping the display after drawing everything
py.display.flip()
py.quit()

Transparent Image in Pygame

I have a sprite in Pygame that is a blue circle. I want this image to be drawn to the screen "faded", e.g. translucent. However, I don't want a translucent rectangle to be drawn over it; instead, I want the actual image to be modified and made translucent. Any help is greatly appreciated!
Right now I have:
Class Circle(pygame.sprite.Sprite):
self.image = self.image = pygame.image.load("circle.png")
circle = Circle()
and eventually...
window.blit(pygame.transform.scale(circle.image, (zoom, zoom)), (100, 100))
How the circle.png looks:
How I want the image to look after making it transparent:
I am blitting the image onto the window, which is a white background.
First, your image/surface needs to use per-pixel alpha, therefore call the convert_alpha() method when you load it. If you want to create a new surface (as in the example), you can also pass pygame.SRCALPHA to pygame.Surface.
The second step is to create another surface (called alpha_surface here) which you fill with white and the desired alpha value (the fourth element of the color tuple).
Finally, you have to blit the alpha_surface onto your image and pass pygame.BLEND_RGBA_MULT as the special_flags argument. That will make
the opaque parts of the image translucent.
import pygame as pg
pg.init()
screen = pg.display.set_mode((800, 600))
clock = pg.time.Clock()
BLUE = pg.Color('dodgerblue2')
BLACK = pg.Color('black')
# Load your image and use the convert_alpha method to use
# per-pixel alpha.
# IMAGE = pygame.image.load('circle.png').convert_alpha()
# A surface with per-pixel alpha for demonstration purposes.
IMAGE = pg.Surface((300, 300), pg.SRCALPHA)
pg.draw.circle(IMAGE, BLACK, (150, 150), 150)
pg.draw.circle(IMAGE, BLUE, (150, 150), 130)
alpha_surface = pg.Surface(IMAGE.get_size(), pg.SRCALPHA)
# Fill the surface with white and use the desired alpha value
# here (the fourth element).
alpha_surface.fill((255, 255, 255, 90))
# Now blit the transparent surface onto your image and pass
# BLEND_RGBA_MULT as the special_flags argument.
IMAGE.blit(alpha_surface, (0, 0), special_flags=pg.BLEND_RGBA_MULT)
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
screen.fill((50, 50, 50))
pg.draw.rect(screen, (250, 120, 0), (100, 300, 200, 100))
screen.blit(IMAGE, (150, 150))
pg.display.flip()
clock.tick(60)
pg.quit()
Create a new Surface with per-pixel alpha.
surf = pygame.Surface((circle_width, circle_height), pygame.SRCALPHA)
Make the surface transparent
surf.set_alpha(128) # alpha value
Draw the circle to that surface at (x=0, y=0)
surf.blit(pygame.transform.scale(circle.image, (zoom, zoom)), (0, 0))
Draw the surface to the window
window.blit(surf, (circle_x, circle_y))
As the Surface.set_alpha() documentation says, you can have surfaces with "homogeneous alpha" or per pixel alpha, but not both, wich I believe is what you want. It might work with colorkey transparency, but I'm not sure (I didn't tested that yet). Any non RGBA (without active alpha channel) pixel format might work if you use set_colorkey() and set_alpha() before blitting.
So, the code might look like:
class Circle(pygame.sprite.Sprite):
def __init__(self)
self.image = pygame.image.load("circle.png")
# get the surface's top-left pixel color and use it as colorkey
colorkey = self.image.get_at((0, 0))
self.image.set_colorkey(colorkey)
At some point in your code (immediately before rendering) you might want to set the transparency by calling:
circle.image.set_alpha(some_int_val)
And then you can scale and blit it as intended.

How do I color in specific blocks for a chess board? [duplicate]

This question already has an answer here:
Nested for loop chess board coloring not working Python
(1 answer)
Closed 1 year ago.
I made a chess board, but I need help with coloring the different squares. I only have white squares but I need black squares. Here is my code.
import pygame
def board():
width=480 # measurements for the window
height=480
block_size=59
window = pygame.display.set_mode((width,height))
background_color = (0,0,0) # This is how I make the lines
window.fill(background_color)
for y in range(height):
for x in range(width):
rect = pygame.Rect(x*(block_size+1), y*(block_size+1), block_size, block_size)
pygame.draw.rect(window, (255,255,255), rect) # Leaves space for lines to be visible.
pygame.display.flip()
board()
# It's just the board.
I already know I violated PEP 8.
You can do it like: I changed your coord-manipulation to benefit directly from the given range values not using a mult inside it. Color is flipped on each rect drawn and also on line-change to get alternating row colorings:
import pygame
def board():
def flipColor(color):
white = (240,240,240)
black = (30,30,30)
if not color or color == white:
color = black
else:
color = white
return color
width=480 # measurements for the window
height=480
block_size= 60
window = pygame.display.set_mode((width,height))
background_color = (0,0,0) # This is how I make the lines
window.fill(background_color)
c = None
pygame.draw.rect(window,(255,0,0),pygame.Rect(0,0,width,height)) # red background
for y in range(0,height,block_size):
c = flipColor(c)
for x in range(0,width,block_size):
c = flipColor(c)
rect = pygame.Rect(x , y , x+block_size , y+block_size )
pygame.draw.rect(window, c , rect, 0) # Leaves space for lines to be visible.
for i in range(0,height+1,block_size):
pygame.draw.line(window,(233,33,187),(i,0),(i,width),2)
pygame.draw.line(window,(233,33,187),(0,i),(height,i),2)
pygame.draw.line(window,(233,33,187),(height-2,0),(height-2,width),2) # fix for out of window line
pygame.draw.line(window,(233,33,187),(0,width-2),(height,width-2),2) # fix for out of wondow line
pygame.display.flip()
board()
You can also use itertools.cycle, pass an iterable with the colors and then just call next to cycle through them. I'd create the background surface when the program starts and then just blit it in the while loop. If you need a background with extra lines, just draw them on the same background surface as well or create a copy.
import itertools
import pygame as pg
pg.init()
screen = pg.display.set_mode((480, 480))
clock = pg.time.Clock()
width, height = screen.get_size()
block_size = 60
# Create a surface onto which we'll blit the rectangles.
background = pg.Surface((width, height))
colors = itertools.cycle((pg.Color('white'), pg.Color('black')))
for y in range(0, height, block_size):
for x in range(0, width, block_size):
rect = (x, y, block_size, block_size)
pg.draw.rect(background, next(colors), rect)
next(colors) # Skip the next color.
# Then you can just blit the background in the while loop.
screen.blit(background, (0, 0))
pg.display.flip()
You can also calculate the color.
If the index of the column and row are both equal or not equal the color is white, else black.
For example field A8:
Index of A is 0 --> equal
8 --> equal
==> Field is white
Example field A1:
Index of A is 0 --> equal
1 --> unequal
==> Field is black

Rotating An Image In Pygame Without Distortion

I have been trying to make an image rotate in pygame, using python 3.6, however when I do it either distorts the image into an unrecognizable image, or when it rotates it bumps all over the place
Just using pygame.transform.rotate(image, angle) makes the distorted mess.
And using something like:
pygame.draw.rect(gameDisplay, self.color, [self.x, self.y, self.width, self.height]) makes the image bump all over the place.
I have looked at many questions on this site and others and so far none of them have worked perfectly.
To anyone who is interested here is the link to my code so far.
https://pastebin.com/UQJJFNTy
My image is 64x64.
Thanks in advance!
Per the docs (http://www.pygame.org/docs/ref/transform.html):
Some of the transforms are considered destructive. These means every time they are performed they lose pixel data. Common examples of this are resizing and rotating. For this reason, it is better to re-transform the original surface than to keep transforming an image multiple times.
Each time you call transform.rotate you need to do it on the original image, not on the previously rotated one. For example, if I want the image rotated 10 degrees each frame:
image = pygame.image.load("myimage.png").convert()
image_clean = image.copy()
rot = 0
Then in your game loop (or object's update):
rot += 10
image = pygame.transform.rotate(image_clean, rot)
Here's a complete example. Don't modify the original image and in the while loop use pygame.transform.rotate or rotozoom to get a new rotated surface and assign it to another name. Use a rect to keep the center.
import sys
import pygame as pg
pg.init()
screen = pg.display.set_mode((640, 480))
BG_COLOR = pg.Color('darkslategray')
# Here I just create an image with per-pixel alpha and draw
# some shapes on it so that we can better see the rotation effects.
ORIG_IMAGE = pg.Surface((240, 180), pg.SRCALPHA)
pg.draw.rect(ORIG_IMAGE, pg.Color('aquamarine3'), (80, 0, 80, 180))
pg.draw.rect(ORIG_IMAGE, pg.Color('gray16'), (60, 0, 120, 40))
pg.draw.circle(ORIG_IMAGE, pg.Color('gray16'), (120, 180), 50)
def main():
clock = pg.time.Clock()
# The rect where we'll blit the image.
rect = ORIG_IMAGE.get_rect(center=(300, 220))
angle = 0
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
# Increment the angle, then rotate the image.
angle += 2
# image = pg.transform.rotate(ORIG_IMAGE, angle) # rotate often looks ugly.
image = pg.transform.rotozoom(ORIG_IMAGE, angle, 1) # rotozoom is smoother.
# The center of the new rect is the center of the old rect.
rect = image.get_rect(center=rect.center)
screen.fill(BG_COLOR)
screen.blit(image, rect)
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
main()
pg.quit()
sys.exit()

How to redraw text on gradient background in pygame?

I am using python 2.7.1 with pygame 1.9.1 on 64-bit win7. I am using the gradient code from http://www.pygame.org/wiki/GradientCode to draw my background. I then display text like so:
countText = font.render( "%d" % secs_left, 1, (255, 0, 0))
countRect = countText.get_rect()
countRect.y, countRect.centerx = yPos, screen.get_rect().width/2
screen.blit(countText, countRect)
pygame.display.flip()
I use this to display a countdown timer, but the problem is the numbers draw on top of one another. I can specify a background color in the font.render() call that will solve this, but then I get another problem where the solid background doesn't match the gradient background.
I think this can be solved by saving a copy of the gradient background in another surface, and then drawing the relevant portion of the saved surface onto the background before drawing the next number, but I am not sure how to do this.
I can save a copy of the gradient background like this:
# save a surface with same size and gradient as background
bg_image = pygame.Surface(screen.get_size())
fill_gradient(bg_image, BG_COLOR, GRADIENT_COLOR)
But how do I select the relevant portion of bg_image and draw it to my main screen background? I tried something like this, doing a screen.blit() to try and erase the current countdown number before blitting the new number, but it doesn't work:
countText = usefont.render( "%d" % secs_left, 1, (255, 0, 0))
countRect = countText.get_rect()
countRect.y, countRect.centerx = yPos, screen.get_rect().width/2
screen.blit(bg_image, (0,0), countRect)
screen.blit(countText, countRect)
pygame.display.flip()
Would this be the best approach (with code that works)? Or is there a better way to do this?
Thanks for your help.
use pygame.display.set_mode(size, 0, 32) to get your screen surface, it supports transparent.
Use a surface to save your background, we name it bg_surface
You get a new text surface every time you do font.render, name it txt_surface
When each time before pygame.display.flip(), blit the bg_surface and then txt_surface to screen.
It's that what you need? If you are rendering a counter, it's enougth to blit the whole background every frame.
I can use Surface.set_clip() to restrict the area being updated to just the rectangle containing the countdown text:
countText = usefont.render( "%d" % secs_left, 1, (255, 0, 0))
countRect = countText.get_rect()
countRect.y, countRect.centerx = yPos, screen.get_rect().width/2
screen.set_clip(countRect) # Allow updates only to area in countRect
screen.blit(bg_image, (0,0)) # bg_img will only be drawn within countRect
screen.blit(countText, countRect)
pygame.display.flip()
Found this post while researching a minor variation of the same question. Here is a running generic solution created after combining information from various places. In this case, the bg_image can be used directly, since the title that is (also) placed on top does not overlap with the count. I used a different method of generating the gradient, but that is not important. Especially when using screen.copy() to create a reference surface to use during the restore.
#!python3
import os
import numpy as np
import pygame
from pygame.locals import *
size = 640, 480
pygame.init()
os.environ['SDL_VIDEO_CENTERED'] = '1'
screen = pygame.display.set_mode(size, NOFRAME, 0)
width, height = screen.get_size()
pygame.event.set_blocked(MOUSEMOTION) # keep our queue cleaner
# build gradient background
colors = np.random.randint(0, 255, (2, 3))
topcolor = np.array(colors[0], copy=0)
bottomcolor = np.array(colors[1], copy=0)
diff = bottomcolor - topcolor
column = np.arange(height, dtype=np.float32) / height # create array from 0.0 to 1.0 triplets
column = np.repeat(column[:, np.newaxis], [3], 1)
column = topcolor + (diff * column).astype(np.int) # create a single column of gradient
column = column.astype(np.uint8)[np.newaxis, :, :] # make column a 3d image column by adding X
column = pygame.surfarray.map_array(screen, column) # 3d array into 2d array
gradient = np.resize(column, (width, height)) # stretch the column into a full image
bg_image = pygame.surfarray.make_surface(gradient)
screen.blit(bg_image, (0, 0))
usefont = pygame.font.Font(None, 144)
# add content that does not get erased with the count down value
title_surf = usefont.render('Counting…', True, (200, 100, 50))
title_rect = title_surf.get_rect()
title_rect.topleft = (20, 5)
screen.blit(title_surf, title_rect)
pygame.display.flip()
savedSurface = screen.copy() # when no convenient surface to restore from
pygame.time.set_timer(USEREVENT, 1000)
screen_center = int(width / 2), int(height / 2)
savedRect = screen_center, (0, 0) # First time, nothing to restore
secs_left = 11
while secs_left > 0:
event = pygame.event.wait()
if event.type in (QUIT, KEYDOWN, MOUSEBUTTONDOWN):
break
if event.type == USEREVENT:
# screen.blit(bg_image, savedRect, savedRect) # restore background
screen.blit(savedSurface, savedRect, savedRect) # restore background
secs_left -= 1
countText = usefont.render('%d' % secs_left, 1, (255, 0, 0))
countRect = countText.get_rect()
countRect.center = screen_center
savedRect = screen.blit(countText, countRect)
pygame.display.flip()

Categories