Rotating and scaling an image around a pivot, while scaling width and height separately in Pygame
I have a set of keyframes in a list that look like this:
[{
"duration" : 20,
"position" : [0,0],
"scale" : [1, 1],
"angle" : 0,
"rgba" : [255,255,255,255]
},
{
"duration" : 5,
"position" : [0,0],
"scale" : [1, 1.5],
"angle" : 50,
"rgba" : [255,255,255,255]
}]
The idea is being able to do the corresponding transformations every frame. Notice that scale is separated between width and height.
The problem comes form trying to scale width and height independently, while still rotating around a pivot.
I tried modifying some code from: (How to rotate an image around its center while its scale is getting larger(in Pygame))
def blitRotate(surf, image, pos, originPos, angle, zoom):
# calcaulate the axis aligned bounding box of the rotated image
w, h = image.get_size()
box = [pygame.math.Vector2(p) for p in [(0, 0), (w, 0), (w, -h), (0, -h)]]
box_rotate = [p.rotate(angle) for p in box]
min_box = (min(box_rotate, key=lambda p: p[0])[0], min(box_rotate, key=lambda p: p[1])[1])
max_box = (max(box_rotate, key=lambda p: p[0])[0], max(box_rotate, key=lambda p: p[1])[1])
# calculate the translation of the pivot
pivot = pygame.math.Vector2(originPos[0], -originPos[1])
pivot_rotate = pivot.rotate(angle)
pivot_move = pivot_rotate - pivot
# calculate the upper left origin of the rotated image
move = (-originPos[0] + min_box[0] - pivot_move[0], -originPos[1] - max_box[1] + pivot_move[1])
origin = (pos[0] + zoom * move[0], pos[1] + zoom * move[1])
# get a rotated image
rotozoom_image = pygame.transform.rotozoom(image, angle, zoom)
# rotate and blit the image
surf.blit(rotozoom_image, origin)
# draw rectangle around the image
pygame.draw.rect (surf, (255, 0, 0), (*origin, *rotozoom_image.get_size()),2)
but i'm struggling trying to think of the math necessary to make it work, i've tried separating zoom
into a dupe, and then instead of doing rotozoom , scaling first with transform.scale
and then transform.rotate
afterwards but that didn't work either.
To better illustrate what i mean, it would be something like this:
It changes it's width and height but the pivot stays the same
Solution 1:
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()