如何在图像比例变大时围绕其中心旋转图像(in Pygame)

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

我加载了一张图片,我想让它围绕它的中心旋转,同时它的比例越来越大。我最初知道如何围绕其中心旋转图像,但是如果比例变大,我很难计算位置。我试过了,但是图像只是'dances',没有停留在中心。

您需要在缩放(和旋转)后重新设置图像的中心,因为这两者都会改变图像大小。

# rotate and zoom the sprite
self.image = pygame.transform.rotozoom(self.original_image, self.angle, self.scale)
# reset it back to original centre
self.rect = self.image.get_rect(center=self.rect.center)

另一件需要考虑的事情是确保您的图像内容在其内部居中。 (想象一个只有内容绘制到一侧的矩形)旋转将在几何上居中,但在旋转时在视觉上仍然看起来很奇怪。

简答:

存储源图像矩形的中心,并在旋转和缩放操作后通过存储的中心位置更新旋转和缩放后的图像矩形的中心。将图片旋转缩放 pygame.transform.rotozoom():

def blitRotateCenter(surf, image, topleft, angle, scale):
    
    center = image.get_rect(topleft = topleft).center
    rotated_image = pygame.transform.rotozoom(image, angle, scale)
    new_rect = rotated_image.get_rect(center = center)

    surf.blit(rotated_image, new_rect.topleft)

长答案:

一张图片(pygame.Surface) can be rotated by pygame.transform.rotate

如果在循环中逐步完成,则图像会失真并迅速增大:

while not done:

    # [...]

    image = pygame.transform.rotate(image, 1)
    screen.blit(image, pos)
    pygame.display.flip()

这是原因,因为旋转图像的边界矩形总是大于原始图像的边界矩形(除了一些旋转 90 度的倍数)。
由于多份副本,图像会失真。每次旋转都会产生一个小错误(不准确)。误差总和在增加,图像在衰减。

这可以通过保留原始图像并将通过单个旋转操作生成的图像从原始图像“blit”来解决。

angle = 0
while not done:

    # [...]

    rotated_image = pygame.transform.rotate(image, angle)
    angle += 1

    screen.blit(rotated_image, pos)
    pygame.display.flip()

现在图像似乎可以任意改变它的位置,因为图像的大小随着旋转而改变,原点总是图像边界矩形的左上角。

这可以通过比较旋转前后图像的axis aligned bounding box来补偿。
对于以下数学,使用 pygame.math.Vector2。注意屏幕坐标中的 y 指向屏幕下方,但数学 y 轴点从底部到顶部。这导致在计算过程中必须“翻转”y 轴

用边界框的 4 个角点设置一个列表:

w, h = image.get_size()
box = [pygame.math.Vector2(p) for p in [(0, 0), (w, 0), (w, -h), (0, -h)]]

将向量旋转到角点 pygame.math.Vector2.rotate:

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

通过将旋转框的最小值添加到该位置来计算图像左上点的“补偿”原点。对于y坐标max_box[1]是最小值,因为沿y轴“翻转”:

origin = (pos[0] + min_box[0], pos[1] - max_box[1])

rotated_image = pygame.transform.rotate(image, angle)
screen.blit(rotated_image, origin)

要在原始图像上定义一个枢轴,必须计算枢轴相对于图像左上角的“平移”,并且图像的“blit”位置必须被平移所取代.

定义一个枢轴,例如在图像的中心:

pivot = pygame.math.Vector2(w/2, -h/2)

计算旋转轴的平移:

pivot_rotate = pivot.rotate(angle)
pivot_move   = pivot_rotate - pivot

最后计算旋转图像的原点:

origin = (pos[0] + min_box[0] - pivot_move[0], pos[1] - max_box[1] + pivot_move[1])

rotated_image = pygame.transform.rotate(image, angle)
screen.blit(rotated_image, origin)

如果图片需要额外缩放,那么在计算图片原点的时候需要考虑缩放:

move   = (-pivot[0] + min_box[0] - pivot_move[0], pivot[1] - max_box[1] + pivot_move[1])
origin = (pos[0] + zoom * move[0], pos[1] + zoom * move[1])

rotozoom_image = pygame.transform.rotozoom(image, angle, zoom)
screen.blit(rotozoom_image, origin)

在下面的示例程序中,函数 blitRotate 执行上述所有步骤并将旋转图像“blit”到表面。 pos 是图像的位置。 originPos 是图像上位于 pos 和枢轴上的点。


最小示例: repl.it/@Rabbid76/PyGame-RotateAroundPivotAndZoom

import pygame

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

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)

try:
    image = pygame.image.load('AirPlaneFront1-128.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()

start = False
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
        elif event.type == pygame.KEYDOWN or event.type == pygame.MOUSEBUTTONDOWN:
            start = True

    pos = (screen.get_width()/2, screen.get_height()/2)
    
    screen.fill(0)
    blitRotate(screen, image, pos, (w/2, h/2), angle, zoom)
    if start:
        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()