How to rotate a surface in pygame, without changing its shape - python

I'm writing a class in pygame to create a sprite object, and I'd like to be able to rotate it. It works fine with an image, and rotates without issue. But when rotating a surface with a plain colour, the box appears to grow and shrink. I know that this is a result of the surface changing size to fit the vertices of the rectangle inside, but how do I stop it? I'd like to see a visual rotation.
I've created some sample code to show the problem that I'm facing, running it causes the box to simply change in size.
import sys, pygame
from pygame.locals import *
pygame.init()
SCREEN = pygame.display.set_mode((200, 200))
CLOCK = pygame.time.Clock()
surface = pygame.Surface((50 , 50))
surface.fill((0, 0, 0))
rotated_surface = surface
rect = surface.get_rect()
angle = 0
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
SCREEN.fill((255, 255, 255))
angle += 5
rotated_surface = pygame.transform.rotate(surface, angle)
rect = rotated_surface.get_rect(center = (100, 100))
SCREEN.blit(rotated_surface, (rect.x, rect.y))
pygame.display.update()
CLOCK.tick(30)
How do I fix this issue, to make the surface rotate how I want?
Any help would be appreciated!

You have to create the Surface objects you create to stamp on the display surface in a way they use transparency information (That is - they have to have an alpha channel).
To do that, is just a question of passing the appropriate flag when creating your surface objects - simply replace this:
surface = pygame.Surface((50 , 50))
with:
surface = pygame.Surface((50 , 50), pygame.SRCALPHA)
and it should work.

Related

How do I make a circle semi-transparent in pygame?

I want to make a partially transparent circle as an area of effect indicator, but I haven't found any way to do this in pygame.
I tried passing in an rgba 4-tuple as an argument (as suggested by this post), but it didn't change the transparency at all. On top of that, according to this other post, the method shouldn't work at all, since draw doesn't accept alpha values. That's conflicting information.
pygame.draw.circle(screen, (255, 0, 0, 100), (x, y), r)
I've also tried creating a new surface, drawing the circle onto that surface, and then blitting the new surface onto my game screen (as suggested by this post). It gives me a semi-transparent circle as requested, but since surfaces in pygame are rectangular, it also gives me a gray rectangle around the circle, which I don't want.
s = pygame.Surface((1000, 1000))
s.set_alpha(100)
pygame.draw.circle(s, (255, 0, 0), (x, y), r)
screen.blit(s, (0, 0))
Is there any way to make this circle semi-transparent without adding any other visible effects to my screen?
Here's a runnable version of the answer to the last question you linked to titled How to draw a semi-transparent circle in Pygame?. It seems to do what you want, as far as I can tell.
import pygame
width, height = 640, 480
pygame.init()
clock = pygame.time.Clock()
screen = pygame.display.set_mode((width,height))
surface = pygame.display.set_mode((width,height), pygame.SRCALPHA)
while True:
msElapsed = clock.tick(100)
screen.fill((255,255,255))
pygame.draw.circle(surface,(30,224,33,100),(250,100),10)
screen.blit(surface, (0,0))
pygame.display.update()
for event in pygame.event.get():
if event.type==pygame.QUIT:
exit()

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()

Additive blending in pygame doesn't work with alpha

I recently discovered the different blending modes you can apply to blitted surfaces in pygame and I wanted to see how flexible the system was. Unless I'm doing something wrong, it's apparently pretty limited (just like the rest of pygame OOOOOOOH shots fired). I wrote a simple program that draws a bunch of gradient circles using alpha and blits them all around the screen. This is the code:
import pygame
import pygame.gfxdraw
pygame.init()
import random
SCREEN = pygame.display.set_mode((800, 600))
SCREEN.fill((0, 0, 0))
def draw_square(surface, colour, x, y):
"""
Yeah it's called draw square but it actually draws a circle thing I was just too lazy
to change the name so you gotta deal with it.
"""
square = pygame.Surface((100, 100))
square.fill((0, 0, 0))
colour += (int(15/255*100), )
for i in range(25):
pygame.gfxdraw.filled_circle(square, 50, 50, i*2, colour)
# Comment the previous two lines out and uncomment the next line to see different results.
# pygame.draw.circle(square, colour[:3], (50, 50), 50)
surface.blit(square, (x - 50, y - 50), special_flags=pygame.BLEND_RGB_ADD)
running = True
while running:
for evt in pygame.event.get():
if evt.type == pygame.QUIT:
running = False
draw_square(SCREEN, (25, 255, 25), random.randint(0, 800), random.randint(0, 600))
pygame.display.update()
pygame.quit()
It seems to work when drawing a normal circle, but when drawing the circles with pygame.gfxdraw.filled_circle additive blending doesn't work. Any ideas?
EDIT: I'm using Python 3, so 15/255 evaluates properly to a float.
The issue is still with this line:
colour += (int(15/255*100), )
It should go to white initially, but the alpha is so low it will take a long time (well, it should in theory...).
Doing:
colour += (int(125/255*100), )
Makes the effect more obvious.
Result:

How to draw to an off-screen display in PyGame

I'm doing a graphics test using PyGame to simulate the Dragon Curve being unfolded. I already made one successful version that keeps track of all the points as they rotate around each other, but obviously, this begins to slow down pretty substantially after a few iterations. In order to speed this up, I want to simply store the drawn segments into an image variable, and continually save a segment of the screen to a variable and draw those moving rather than keeping track of a lot of points. How can I do either of the following?
Draw to an off-screen image variable that then gets drawn to the screen in the correct place
Save a section of the visible display into an image variable
I tried reading through some of the PyGame documentation, but I didn't have any success.
Thanks!
Creating an additional surface object, and drawing to it is the solution. This surface object can then be drawn onto the display's surface object as shown below.
More information on the PyGame Surface object can be found here
import pygame, sys
SCREEN_SIZE = (600, 400)
BG_COLOR = (0, 0, 0)
LINE_COLOR = (0, 255, 0)
pygame.init()
clock = pygame.time.Clock() # to keep the framerate down
image1 = pygame.Surface((50, 50))
image2 = pygame.Surface((50, 50))
image1.set_colorkey((0, 0, 0)) # The default background color is black
image2.set_colorkey((0, 0, 0)) # and I want drawings with transparency
screen = pygame.display.set_mode(SCREEN_SIZE, 0, 32)
screen.fill(BG_COLOR)
# Draw to two different images off-screen
pygame.draw.line(image1, LINE_COLOR, (0, 0), (49, 49))
pygame.draw.line(image2, LINE_COLOR, (49, 0), (0, 49))
# Optimize the images after they're drawn
image1.convert()
image2.convert()
# Get the area in the middle of the visible screen where our images would fit
draw_area = image1.get_rect().move(SCREEN_SIZE[0] / 2 - 25,
SCREEN_SIZE[1] / 2 - 25)
# Draw our two off-screen images to the visible screen
screen.blit(image1, draw_area)
screen.blit(image2, draw_area)
# Display changes to the visible screen
pygame.display.flip()
# Keep the window from closing as soon as it's finished drawing
# Close the window gracefully upon hitting the close button
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit(0)
clock.tick(30)

How do I blit a PNG with some transparency onto a surface in Pygame?

I'm trying to blit a PNG image onto a surface, but the transparent part of the image turns black for some reason, here's the simple code:
screen = pygame.display.set_mode((800, 600), pygame.DOUBLEBUF, 32)
world = pygame.Surface((800, 600), pygame.SRCALPHA, 32)
treeImage = pygame.image.load("tree.png")
world.blit(treeImage, (0,0), (0,0,64,64))
screen.blit(world, pygame.rect.Rect(0,0, 800, 600))
What do I have to do to solve the problem?
The image has alpha transparency, I've opened it up in PhotoShop and the background turns transparent, not black or white or any other color.
Thank you for your support :)
http://www.pygame.org/docs/ref/image.html recommends:
For alpha transparency, like in .png images use the convert_alpha() method after loading so that the image has per pixel transparency.
You did not flip the doublebuffer.
import pygame
from pygame.locals import Color
screen = pygame.display.set_mode((800, 600))
treeImage = pygame.image.load("tree.png").convert_alpha()
white = Color('white')
while(True):
screen.fill(white)
screen.blit(treeImage, pygame.rect.Rect(0,0, 128, 128))
pygame.display.flip()
This should work for your problem.
Images are represented by "pygame.Surface" objects. A Surface can be created from an image with pygame.image.load:
my_image_surface = pygame.load.image('my_image.jpg')
However, the pygame documentation notes that:
The returned Surface will contain the same color format, colorkey and alpha transparency as the file it came from. You will often want to call convert() with no arguments, to create a copy that will draw more quickly on the screen.
For alpha transparency, like in .png images, use the convert_alpha() method after loading so that the image has per pixel transparency.
Use the convert_alpha() method for best performance:
alpha_image_surface = pygame.load.image('my_icon.png').convert_alpha()
A Surface can be drawn on or blended with another Surface using the blit method. The first argument to blit is the Surface that should be drawn. The second argument is either a tuple (x, y) representing the upper left corner or a rectangle. With a rectangle, only the upper left corner of the rectangle is taken into account. It should be mentioned that the window respectively display is also represented by a Surface. Therefore, drawing a Surface in the window is the same as drawing a Surface on a Surface:
window_surface.blit(image_surface, (x, y))
window_surface.blit(image_surface,
image_surface.get_rect(center = window_surface.get_rect().center))
Minimal example: repl.it/#Rabbid76/PyGame-LoadTransparentImage
import pygame
pygame.init()
window = pygame.display.set_mode((300, 300))
clock = pygame.time.Clock()
pygameSurface = pygame.image.load('Porthole.png').convert_alpha()
background = pygame.Surface(window.get_size())
ts, w, h, c1, c2 = 50, *window.get_size(), (160, 160, 160), (192, 192, 192)
tiles = [((x*ts, y*ts, ts, ts), c1 if (x+y) % 2 == 0 else c2) for x in range((w+ts-1)//ts) for y in range((h+ts-1)//ts)]
for rect, color in tiles:
pygame.draw.rect(background, color, rect)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.blit(background, (0, 0))
window.blit(pygameSurface, pygameSurface.get_rect(center = window.get_rect().center))
pygame.display.flip()
pygame.quit()
exit()
Your code looks like it should be correct. The SDL library does not support alpha to alpha blitting like this, but Pygame added support for it awhile ago. In Pygame 1.8 support was added for custom blending modes, and I wonder if that removed Pygame's internal alpha-to-alpha blitter?
Alas, further investigation will be required.

Categories