Python Forum
[PyGame] How to rotate images
Thread Rating:
  • 4 Vote(s) - 4 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[PyGame] How to rotate images
#1
Hey I did some looking online about this but I wasn't able to figure out how to get it to work. How do you rotate an image? For example I want to create a program the displays an image that just rotates on the screen. How would I do that? And is it possible to rotate text? P.s. I am using pygame.
Reply
#2
When you rotate an image you need to make sure that you make an original of the image, and use that to rotate, otherwise it gets distorted if you keep rotating the image from a rotated image, etc. 

Whatever you rotate, whether it be an image or text, both are going to be a surface and rotated the same way. 

You are going to use pygame.transform.rotate

Here is an example of a surface rotated based on mouse position
import pygame as pg
import math

class Rotator:
    def __init__(self, screen_rect):
        self.orig_image = pg.Surface([10,100]).convert_alpha() #
        self.image = self.orig_image
        self.image.fill((255,255,255))
        self.rect = self.image.get_rect(center=screen_rect.center)
        self.angle = 0
        self.distance = 0
        self.angle_offset = 0

    def render(self, screen):
        screen.blit(self.image, self.rect)
        
    def get_angle(self):
        mouse = pg.mouse.get_pos()
        offset = (self.rect.centerx-mouse[0],self.rect.centery-mouse[1])
        self.angle = math.degrees(math.atan2(*offset)) - self.angle_offset
        old_center = self.rect.center
        self.image = pg.transform.rotate(self.orig_image, self.angle)
        self.rect = self.image.get_rect(center=old_center)
        self.distance = math.sqrt((offset[0] * offset[0]) + (offset[1] * offset[1]))
        
    def update(self):
        self.get_angle()
        self.display = 'angle:{:.2f} disatance:{:.2f}'.format(self.angle, self.distance)

if __name__ == '__main__':
    running = True
    pg.init()
    screen = pg.display.set_mode((600,400))
    screen_rect = screen.get_rect()
    rotator = Rotator(screen_rect)
    clock = pg.time.Clock()

    while running:
        screen.fill((0,0,0))
        for event in pg.event.get():
            if event.type == pg.QUIT:
                running = False
        rotator.update()
        rotator.render(screen)
        pg.display.set_caption(rotator.display)
        pg.display.update()
        clock.tick(60)
here the surface is just a generic rectangle, but you can plug in an image or text surface. The lines you would need to change to do that is
        #self.orig_image = pg.Surface([10,100]).convert_alpha() #
        self.orig_image = pg.image.load("your_image.png").convert()
        self.image = self.orig_image
        #self.image.fill((255,255,255))
where you load your image as the original instead and comment out the image fill

another example, a non-interactive example a constant turning object. Again you can plugin your image/text surface instead of a generic surface

import pygame as pg

screen = pg.display.set_mode((800,600))
screen_rect = screen.get_rect()
clock = pg.time.Clock()
done = False

class Rotator:
    def __init__(self, screen_rect):
        self.screen_rect = screen_rect
        self.master_image = pg.Surface([100,100]).convert_alpha()
        self.master_image.fill((255,0,0))
        self.image = self.master_image.copy()
        self.rect = self.image.get_rect(center=self.screen_rect.center)
        self.delay = 10
        self.timer = 0.0
        self.angle = 0

    def new_angle(self):
        self.angle += 1
        self.angle %= 360

    def rotate(self):
        self.new_angle()
        self.image = pg.transform.rotate(self.master_image, self.angle)
        self.rect = self.image.get_rect(center=self.screen_rect.center)

    def update(self):
        if pg.time.get_ticks()- self.timer > self.delay:
            self.timer = pg.time.get_ticks()
            self.rotate()

    def draw(self, surf):
        surf.blit(self.image, self.rect)

rotator = Rotator(screen_rect)

while not done:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            done = True
    screen.fill((0,0,0))
    rotator.update()
    rotator.draw(screen)
    pg.display.update()
Recommended Tutorials:
Reply
#3
Thank you for your help.
Reply
#4
I have discovered a strange problem. So by using this code I am able to rotate images but I have found a problem with rotating images that are smaller than 40x40 or have a rectangle shape. So what happens is if I have an image that is 40x40 or larger it rotates with out a problem, but if the size of the image is smaller than 40x40 or is in a rectangle shape what happens is as the image rotates a non rotating square grows and shrinks around the image. If you scale the image in pygame it has no effect. The problem only happens with images who's default dimensions are below 40x40 or in a rectangle shape. This is the code I use.
import pygame as pg
import sys
pg.init()
 
screen = pg.display.set_mode((800,600))
screen_rect = screen.get_rect()
clock = pg.time.Clock()
done = False
 
class Rotator:
    def __init__(self, screen_rect):
        self.screen_rect = screen_rect
        self.master_image = pg.image.load('block.png') 
        #self.master_image = pg.transform.scale(self.master_image,(20,20))
        self.image = self.master_image.copy()
        self.rect = self.image.get_rect(center=[400,300])
        self.delay = 10
        self.timer = 0.0
        self.angle = 0
 
    def new_angle(self,n):
        self.angle -= n
        self.angle %= 360
 
    def rotate(self):
        #n = input('give number')
        #n = int(n)
        self.new_angle(1)
        self.image = pg.transform.rotate(self.master_image, self.angle)
        self.rect = self.image.get_rect(center=[400,300])
 
    def update(self):
        if pg.time.get_ticks()- self.timer > self.delay:
            self.timer = pg.time.get_ticks()
            self.rotate()
 
    def draw(self, surf):
        surf.blit(self.image, self.rect)
 
rotator = Rotator(screen_rect)
 
while not done:
    for event in pg.event.get():
        if event.type == pg.QUIT:
           sys.exit()
           pygame.quit()
    screen.fill((0,0,0))
    rotator.update()
    rotator.draw(screen)
    pg.display.update()
As you can see there is a part of code for scaling the image. I used that to scale the image to see if that would fix or reproduce the problem and it has no effect. Only the original size of the image matters in reproducing the problem. Just create a square image of 39x39 or smaller to see the problem or create a rectangle of any size.
Reply
#5
If I add ".convert_alpha()" to the end of the line that loads the image, the issue goes away. Per the docs, it looks like you can avoid this if you use images that don't have an alpha channel (or just call convert first): http://www.pygame.org/docs/ref/transform...orm.rotate

Also, sys.exit() is the end of the program, nothing runs after that. If you want to quit pygame, you should do so before calling sys.exit(). And also "pygame" doesn't exist since you named it pg, so that'd throw an error anyway. It's a personal preference, but I sort of hate seeing sys.exit(), and would rewrite that loop to get it out of there, but if you're fine with it don't worry about it.
Reply
#6
Oh, so that is what that part of the code was for. I guess I should have asked about it but I just thought that it was something that was not realy needed for what I was doing. So I will try that and reply if I need more help. PS. I see what you mean about the pygame.quit() I don't usually name pygame pg but because I copied the code that was given to me above that is why it is called pg and I just did not think when I added the pygame.quit()
Reply
#7
Well, the docs say convert() simply makes blitting the same thing over and over slightly faster, as you save it a step that it'd need to do each time. My guess is that there's just something weird about the way it handles the alpha channel.

For those of you joining in late, here's some graphics showing what the situation is... The background for the window is black, the image is a black square with an arrow in it (I just took a screen cap of Chrome's back button to quickly have something squarish).  When rotating, the image background is noticably not the same shade of black as the rest of the window.  The first image shows this, while the second is after adding .convert_alpha().
       

...apparently file uploads might not be working for us right now.  Here's some github links:
https://github.com/nilamo/assorted_pub/b...before.PNG

https://github.com/nilamo/assorted_pub/b.../after.png
Reply
#8
if you are going to use pygame quit put it like this. Dont use sys exit. You should always flow your program to the end of the script, not kill it in an if condition. 

from this
Quote:
while not done:
    for event in pg.event.get():
        if event.type == pg.QUIT:
           sys.exit()
           pg.quit()

to this 
while not done:
    for event in pg.event.get():
        if event.type == pg.QUIT:
           done = True
...
#at the bottom of script last
pg.quit()
pg is just what a few of us do to not have to type out pygame every time. Its explicit anyways in the code examples as we change the name via the import line. 

Quote:Oh, so that is what that part of the code was for. I guess I should have asked about it but I just thought that it was something that was not realy needed for what I was doing
yup

Quote:apparently file uploads might not be working for us right now.
for some reason some forum plugins mess with attachments, if you see that again, that most likely means the last plugin we added, broke the attachments and needs to be removed.
Recommended Tutorials:
Reply
#9
Thank you, you are all a big help.
Reply
#10
Or you can remove the while loop altogether :p

In a recent project, I took all the constructor/destructor pygame nonsense and threw them into a context manager that offers the pygame events as a generator.  Could be useful if you're into that sort of thing.  Personally, I think it looks a lot cleaner.
import pygame

class Renderer(object):
    def __init__(self, resolution, clock_speed=60):
        self.resolution = resolution
        self.clock_speed = clock_speed
        self.screen = pygame.display.set_mode(self.resolution)
        self.clock = pygame.time.Clock()
        self.objects = []
        
    def add(self, renderable):
        self.objects.append(renderable)
    
    def clear(self):
        self.screen.fill(WHITE)
        
    def update(self):
        for obj in self.objects:
            self.screen.blit(*obj.render())
            
        pygame.display.flip()
        self.clock.tick()

class GUI(object):
    def __init__(self, renderer):
        self.renderer = renderer
    
    def event_loop(self):
        running = True
        while running:
            self.renderer.clear()
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False
                yield event
            self.renderer.update()
    
    def __enter__(self):
        pygame.init()
        return self
        
    def __exit__(self, *args):
        pygame.quit()

if __name__ == '__main__':
    win_size = (500, 500)
    renderer = Renderer(win_size, 60)

    # use renderer.add() to add objects with a "render" method
    # each clock tick, the render() method will be called on those objects
    # that render() method returns a tuple of (surface_to_blit, (top_left_xpos, top_left_ypos))

    with GUI(renderer) as g:
        for event in g.event_loop():
        # event is any pygame event, INCLUDING QUIT, so you can clean up
        # quit is auto-handled by the generator/context manager

            mouse_down = pygame.mouse.get_pressed()[0]
            mouse_pos = pygame.mouse.get_pos()
            # ...then you do things
            [button.update(mouse_down, mouse_pos[0], mouse_pos[1]) for button in buttons]
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  How do I use pygame.transform.rotate()? noodlespinbot 1 2,878 Mar-05-2020, 08:08 AM
Last Post: michael1789

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020