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 Surfaceimage
is the Surface which has to be rotated andblit
pos
is the position of the pivot on the target Surfacesurf
(relative to the top left ofsurf
)originPos
is position of the pivot on theimage
Surface (relative to the top left ofimage
)angle
is the angle of rotation in degrees
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: 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 blit
s 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: repl.it/@Rabbid76/PyGame-RotateZoomPivot
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: repl.it/@Rabbid76/PyGame-RotateZoomPivot
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))
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
Problem Running Python from Crontab - "Invalid Python Installation"
Problems Adding Path and Calling External Program from Python
Python Multiprocessing + Subprocess Issues
Set Bash Variable from Python Script
Unicodedecodeerror Reading Binary Input
What Is the Correct Way to Include Localisation in Python Packages
Python Script Not Working via Cron
Why Does Loading the Libc Shared Library Have "'Libraryloader' Object Is Not Callable" Error
Using Python Subprocess.Call() to Launch an Ncurses Process
How to Configure Chromedriver to Initiate Chrome Browser in Headless Mode Through Selenium
Python Dictionary: Are Keys() and Values() Always the Same Order
Python None Comparison: Should I Use "Is" or ==
How Exactly Does a Generator Comprehension Work
After Conda Update, Python Kernel Crashes When Matplotlib Is Used