How to Rotate an Image Around an Off Center Pivot in Pygame

How can you rotate an image around an off center pivot in Pygame

First the position of the pivot on the Surface has to be defined:

image = pygame.image.load("boomerang64.png") # 64x64 surface
pivot = (48, 21) # position of the pivot on the image

When an image is rotated, then its size increase. We have to compare the axis aligned bounding box of the image before the rotation and after the rotation.

For the following math pygame.math.Vector2 is used. Note in screen coordinates the y points down the screen, but the mathematical y axis points form the bottom to the top. This causes that the y axis has to be "flipped" during calculations. Notice that pygame.math.Vector2.rotate rotates in the opposite direction than pygame.transform.rotate. Therefore the angle has to be inverted.

Set up a list with the 4 corner points of the bounding box and rotate the vectors to the corner points by pygame.math.Vector2.rotate. Finally find the minimum of the rotated box. Since the y axis in pygame points downwards, this has to be compensated by finding the maximum of the rotated inverted height ("max(rotate(-h))"):

Compute the vector from the center of the image to the pivot:

image_rect = image.get_rect(topleft = (pos[0] - originPos[0], pos[1]-originPos[1]))
offset_center_to_pivot = pygame.math.Vector2(pos) - image_rect.center

Rotate the offset vector:

rotated_offset = offset_center_to_pivot.rotate(-angle)

Calculate the center of the rotated image:

rotated_image_center = (pos[0] - rotated_offset.x, pos[1] - rotated_offset.y)

Rotate and blit the image:

rotated_image = pygame.transform.rotate(image, angle)
rotated_image_rect = rotated_image.get_rect(center = rotated_image_center)

screen.blit(rotated_image, rotated_image_rect)

In the following example program, the function blitRotate(surf, image, pos, originPos, angle) does all the above steps and blit a rotated image to the Surface which is associated to the display:

  • surf is the target Surface
  • image is the Surface which has to be rotated and blit
  • pos is the position of the pivot on the target Surface surf (relative to the top left of surf)
  • originPos is position of the pivot on the image Surface (relative to the top left of image)
  • angle is the angle of rotation in degrees

Sample Image

import math
import pygame

def blitRotate(surf, image, pos, originPos, angle):
image_rect = image.get_rect(topleft = (pos[0] - originPos[0], pos[1]-originPos[1]))
offset_center_to_pivot = pygame.math.Vector2(pos) - image_rect.center
rotated_offset = offset_center_to_pivot.rotate(-angle)
rotated_image_center = (pos[0] - rotated_offset.x, pos[1] - rotated_offset.y)
rotated_image = pygame.transform.rotate(image, angle)
rotated_image_rect = rotated_image.get_rect(center = rotated_image_center)
surf.blit(rotated_image, rotated_image_rect)

pygame.init()
size = (400,400)
screen = pygame.display.set_mode(size)
clock = pygame.time.Clock()

image = pygame.image.load('Boomerang64.png')
pivot = (48, 21)

angle, frame = 0, 0
done = False
while not done:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
screen.fill(0)

pos = (200 + math.cos(frame * 0.05)*100, 200 + math.sin(frame * 0.05)*50)
blitRotate(screen, image, pos, pivot, angle)

pygame.draw.line(screen, (0, 255, 0), (pos[0]-20, pos[1]), (pos[0]+20, pos[1]), 3)
pygame.draw.line(screen, (0, 255, 0), (pos[0], pos[1]-20), (pos[0], pos[1]+20), 3)
pygame.draw.circle(screen, (0, 255, 0), pos, 7, 0)

pygame.display.flip()
frame += 1
angle += 10

pygame.quit()
exit()

The same algorithm can be used for a Sprite, too.

In that case the position (self.pos), pivot (self.pivot) and angle (self.angle) are instance attributes of the class.
In the update method the self.rect and self.image attributes are computed.

Minimal example: Sample Image repl.it/@Rabbid76/PyGame-RotateSpriteAroundOffCenterPivot

import math
import pygame

class SpriteRotate(pygame.sprite.Sprite):

def __init__(self, imageName, origin, pivot):
super().__init__()
self.image = pygame.image.load(imageName)
self.original_image = self.image
self.rect = self.image.get_rect(topleft = (origin[0]-pivot[0], origin[1]-pivot[1]))
self.origin = origin
self.pivot = pivot
self.angle = 0

def update(self):

# offset from pivot to center
image_rect = self.original_image.get_rect(topleft = (self.origin[0] - self.pivot[0], self.origin[1]-self.pivot[1]))
offset_center_to_pivot = pygame.math.Vector2(self.origin) - image_rect.center

# roatated offset from pivot to center
rotated_offset = offset_center_to_pivot.rotate(-self.angle)

# roatetd image center
rotated_image_center = (self.pos[0] - rotated_offset.x, self.pos[1] - rotated_offset.y)

# get a rotated image
self.image = pygame.transform.rotate(self.original_image, self.angle)
self.rect = self.image.get_rect(center = rotated_image_center)
self.angle += 10

pygame.init()
size = (400,400)
screen = pygame.display.set_mode(size)
clock = pygame.time.Clock()

boomerang = SpriteRotate('Boomerang64.png', (200, 200), (48, 21))
all_sprites = pygame.sprite.Group(boomerang)

frame = 0
done = False
while not done:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True

pos = (200 + math.cos(frame * 0.05)*100, 200 + math.sin(frame * 0.05)*50)
boomerang.pos = pos
all_sprites.update()

screen.fill(0)

all_sprites.draw(screen)
#pygame.draw.line(screen, (0, 255, 0), (pos[0]-20, pos[1]), (pos[0]+20, pos[1]), 3)
#pygame.draw.line(screen, (0, 255, 0), (pos[0], pos[1]-20), (pos[0], pos[1]+20), 3)
#pygame.draw.circle(screen, (0, 255, 0), pos, 7, 0)

pygame.display.flip()
frame += 1

pygame.quit()
exit()

How do I rotate an image around its center using Pygame?

How to rotate an image around its center while its scale is getting larger(in Pygame)

How can i rotate an image from the center rather than the corner in pygame?

Rotate the sprite then set the center of the new rect to the center of the old rect. This way the new one has the same center when you're done, making it look like it rotated around the center.

Here's an example function from the pygame wiki:

def rot_center(image, rect, angle):
"""rotate an image while keeping its center"""
rot_image = pygame.transform.rotate(image, angle)
rot_rect = rot_image.get_rect(center=rect.center)
return rot_image,rot_rect

And here's how you would use it in your example:

# Draw ship image centered around 100, 100
oldRect = shipImg.get_rect(center=(100,100))
screen.blit(shipImg, oldRect)

# Now rotate the ship and draw it with the new rect,
# which will keep it centered around 100,100
shipImg, newRect = rot_center(shipImg,oldRect,-90)
screen.blit(shipImg, newRect)

Rotating and scaling an image around a pivot, while scaling width and height separately in Pygame

I would suggest adopting a slightly different approach presented here: How to set the pivot point (center of rotation) for pygame.transform.rotate()?

All you have to do to adjust this algorithm is scale the vector from the image center to the pivot point on the image by the zoom factor:

offset_center_to_pivot = pygame.math.Vector2(origin) - image_rect.center

offset_center_to_pivot = (pygame.math.Vector2(origin) - image_rect.center) * scale

The final function that rotates an image around a pivot point, zooms and blits the image might look like this:

def blitRotate(surf, original_image, origin, pivot, angle, scale):

image_rect = original_image.get_rect(topleft = (origin[0] - pivot[0], origin[1]-pivot[1]))
offset_center_to_pivot = (pygame.math.Vector2(origin) - image_rect.center) * scale
rotated_offset = offset_center_to_pivot.rotate(-angle)
rotated_image_center = (origin[0] - rotated_offset.x, origin[1] - rotated_offset.y)
rotozoom_image = pygame.transform.rotozoom(original_image, angle, scale)
rect = rotozoom_image.get_rect(center = rotated_image_center)

surf.blit(rotozoom_image, rect)

The scaling factor can also be specified separately for the x and y axis:

def blitRotate(surf, original_image, origin, pivot, angle, scale_x, scale_y):

image_rect = original_image.get_rect(topleft = (origin[0] - pivot[0], origin[1]-pivot[1]))
offset_center_to_pivot = pygame.math.Vector2(origin) - image_rect.center
offset_center_to_pivot.x *= scale_x
offset_center_to_pivot.y *= scale_y
rotated_offset = offset_center_to_pivot.rotate(-angle)
rotated_image_center = (origin[0] - rotated_offset.x, origin[1] - rotated_offset.y)
scaled_image = pygame.transform.smoothscale(original_image, (image_rect.width * scale_x, image_rect.height * scale_y))
rotozoom_image = pygame.transform.rotate(scaled_image, angle)
rect = rotozoom_image.get_rect(center = rotated_image_center)

surf.blit(rotozoom_image, rect)

See also Rotate surface


Minimal example: Sample Image repl.it/@Rabbid76/PyGame-RotateZoomPivot

Sample Image

import pygame

pygame.init()
screen = pygame.display.set_mode((500, 500))
clock = pygame.time.Clock()

def blitRotate(surf, original_image, origin, pivot, angle, scale):

image_rect = original_image.get_rect(topleft = (origin[0] - pivot[0], origin[1]-pivot[1]))
offset_center_to_pivot = (pygame.math.Vector2(origin) - image_rect.center) * scale
rotated_offset = offset_center_to_pivot.rotate(-angle)
rotated_image_center = (origin[0] - rotated_offset.x, origin[1] - rotated_offset.y)
rotozoom_image = pygame.transform.rotozoom(original_image, angle, scale)
rect = rotozoom_image.get_rect(center = rotated_image_center)

surf.blit(rotozoom_image, rect)
pygame.draw.rect (surf, (255, 0, 0), rect, 2)

try:
image = pygame.image.load('AirPlane.png')
except:
text = pygame.font.SysFont('Times New Roman', 50).render('image', False, (255, 255, 0))
image = pygame.Surface((text.get_width()+1, text.get_height()+1))
pygame.draw.rect(image, (0, 0, 255), (1, 1, *text.get_size()))
image.blit(text, (1, 1))
w, h = image.get_size()

angle, zoom = 0, 1
done = False
while not done:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True

pos = (screen.get_width()/2, screen.get_height()/2)

screen.fill(0)
blitRotate(screen, image, pos, (w/4, h/2), angle, zoom)
angle += 1
zoom += 0.01
if zoom > 5:
zoom = 1

pygame.draw.line(screen, (0, 255, 0), (pos[0]-20, pos[1]), (pos[0]+20, pos[1]), 3)
pygame.draw.line(screen, (0, 255, 0), (pos[0], pos[1]-20), (pos[0], pos[1]+20), 3)
pygame.draw.circle(screen, (0, 255, 0), pos, 7, 0)

pygame.display.flip()

pygame.quit()
exit()

Example 2: Sample Image repl.it/@Rabbid76/PyGame-RotateZoomPivot

Sample Image

import pygame

pygame.init()
screen = pygame.display.set_mode((400, 300))
clock = pygame.time.Clock()

def blitRotateZoomXY(surf, original_image, origin, pivot, angle, scale_x, scale_y):

image_rect = original_image.get_rect(topleft = (origin[0] - pivot[0], origin[1]-pivot[1]))
offset_center_to_pivot = pygame.math.Vector2(origin) - image_rect.center
offset_center_to_pivot.x *= scale_x
offset_center_to_pivot.y *= scale_y
rotated_offset = offset_center_to_pivot.rotate(-angle)
rotated_image_center = (origin[0] - rotated_offset.x, origin[1] - rotated_offset.y)
scaled_image = pygame.transform.smoothscale(original_image, (image_rect.width * scale_x, image_rect.height * scale_y))
rotozoom_image = pygame.transform.rotate(scaled_image, angle)
rect = rotozoom_image.get_rect(center = rotated_image_center)

surf.blit(rotozoom_image, rect)

cannon = pygame.image.load('icon/cannon.png')
cannon_mount = pygame.image.load('icon/cannon_mount.png')

angle, zoom_x, zoom_y = -90, 1, 1
stage = 0
done = False
while not done:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True

pos = (screen.get_width()/3, screen.get_height()*3/4)

screen.fill((192, 192, 192))
blitRotateZoomXY(screen, cannon, pos, (33.5, 120), angle, zoom_x, zoom_y)
screen.blit(cannon_mount, (pos[0]-43, pos[1]-16))
pygame.display.flip()

if stage == 0:
angle += 1
if angle >= -30:
stage += 1
elif stage == 1:
zoom_y -= 0.05
if zoom_y <= 0.7:
stage += 1
elif stage == 2:
zoom_y += 0.05
if zoom_y >= 1:
stage += 1
elif stage == 3:
angle -= 1
if angle <= -90:
stage = 0

pygame.quit()
exit()

How to rotate Element around pivot point in pygame?

Rencered text in only image/surface so you can use pygame's functions to rotate image in place (around of center of image)

image = pygame.transform.rotate(image, -angle)

and later move it to border of circle using radius and angle starting at center of circle

angle = 0 # 90, 180, 270
x = dial_center_x
y = dial_center_y
x += radius * sin(radians(angle))
y += radius * cos(radians(angle))

It is good to use pygame.Rect() to keep position and size - it has attributes like .x, .y but also .center, .centerx, .centery so it is easy to center number on dial circle

image_rect = image.get_rect()

image_rect.center = dial_rect.center

and then you can use radius and angle to move it to position of 12 o'clock, 3 o'clock, 6 o'clock and 9 o'clock or even 1 o'clock, 2 o'clock, etc

angle = 0 # 90, 180, 270
image_rect.centerx += radius * sin(radians(angle))
image_rect.centery += radius * cos(radians(angle))

Sample Image


import pygame
import time
import math

# --- constants --- (UPPER_CASE_NAMES)

BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)

DISPLAY_WIDTH = 800
DISPLAY_HEIGHT = 800

# --- classes ---

class Dial:

def __init__(self, x, y, radius, inputs, angle_step=45):
self.rect = pygame.Rect(x-radius, y-radius, 2*radius, 2*radius)
self.radius = radius
self.radius2 = radius - 30 # position for digits
self.inputs = inputs
self.angle_step = angle_step

def draw(self): # no need to add prefix/postfix 'Dial' to name `draw`
#for i in range(0, self.columns):
# `Dial` means single object so it should draw only one circle
pygame.draw.circle(window, RED, self.rect.center, self.radius)
pygame.draw.circle(window, BLACK, self.rect.center, self.radius, 1)

angle = 0
for number in self.inputs:
text = font.render(str(number), 1, BLACK)

# rotate image
text = pygame.transform.rotate(text, -angle)

# center image on circle
text_rect = text.get_rect(center=self.rect.center)

# move to border using `angle` and `radius`
angle_rad = math.radians(180-angle)
text_rect.centerx += self.radius2 * math.sin(angle_rad)
text_rect.centery += self.radius2 * math.cos(angle_rad)

window.blit(text, text_rect)

angle += self.angle_step

# --- functions ---

def level_1():
window.fill(WHITE)
dial1.draw()
dial2.draw()
dial3.draw()

# --- main ---

pygame.init()

window = pygame.display.set_mode((DISPLAY_WIDTH, DISPLAY_HEIGHT))
window_rect = window.get_rect()

pygame.display.set_caption("Dials")

clock = pygame.time.Clock()

font = pygame.font.SysFont('Arial', 50, True)

#tutorial = [pygame.image.load('Dial_Images/tutorial_base.png')]

tutorial_inputs = [[1,4,3,4], [1,4,3,2], [1,4,3,2], [1,4,3,4]]

dial1 = Dial(window_rect.centerx, window_rect.centery, window_rect.centerx-250, tutorial_inputs[0], 45)
dial2 = Dial(window_rect.centerx-250, window_rect.centery-250, window_rect.centerx-250, tutorial_inputs[1], 90)
dial3 = Dial(window_rect.centerx+250, window_rect.centery-250, window_rect.centerx-250, tutorial_inputs[2], 30)

score = 0

run = True
level1 = True

while run:
#keys = pygame.key.get_pressed()

for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False

pygame.display.update()
clock.tick(100)

if level1:
level_1()

pygame.quit()


Related Topics



Leave a reply



Submit