如何使用 Pygame 围绕其中心旋转图像?

Posted

技术标签:

【中文标题】如何使用 Pygame 围绕其中心旋转图像?【英文标题】:How do I rotate an image around its center using Pygame? 【发布时间】:2011-05-10 03:39:29 【问题描述】:

我一直在尝试使用pygame.transform.rotate() 围绕其中心旋转图像,但它不起作用。具体来说,挂起的部分是rot_image = rot_image.subsurface(rot_rect).copy()。我得到了例外:

ValueError: subsurface rectangle outside surface area

这是用于旋转图像的代码:

def rot_center(image, angle):
    """rotate an image while keeping its center and size"""
    orig_rect = image.get_rect()
    rot_image = pygame.transform.rotate(image, angle)
    rot_rect = orig_rect.copy()
    rot_rect.center = rot_image.get_rect().center
    rot_image = rot_image.subsurface(rot_rect).copy()
    return rot_image

【问题讨论】:

【参考方案1】:

简答:

获取原始图像的矩形并设置位置。获取旋转图像的矩形,并通过原始矩形的中心设置中心位置。返回旋转图像和矩形的元组:

def rot_center(image, angle, x, y):
    
    rotated_image = pygame.transform.rotate(image, angle)
    new_rect = rotated_image.get_rect(center = image.get_rect(center = (x, y)).center)

    return rotated_image, new_rect

或者写一个函数来旋转和.blit图片:

def blitRotateCenter(surf, image, topleft, angle):

    rotated_image = pygame.transform.rotate(image, angle)
    new_rect = rotated_image.get_rect(center = image.get_rect(topleft = topleft).center)

    surf.blit(rotated_image, new_rect)

长答案:

图像 (pygame.Surface) 可以旋转 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)

甚至可以在原始图像上定义一个轴。计算从图像中心到枢轴的偏移向量并旋转向量。向量可以用pygame.math.Vector2 表示,也可以用pygame.math.Vector2.rotate 旋转。请注意,pygame.math.Vector2.rotate 的旋转方向与 pygame.transform.rotate 的旋转方向相反。因此必须反转角度:

计算从图像中心到枢轴的向量:

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)

旋转和blit图像:

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)

在下面的示例程序中,函数blitRotate(surf, image, pos, originPos, angle) 完成上述所有步骤并将旋转的图像“blit”到表面。

surf 是目标表面

image 是必须旋转的表面,blit

pos 是目标 Surface surf 上枢轴的位置(相对于surf 的左上角)

originPosimage 表面上的枢轴位置(相对于image 的左上角)

angle 是以度为单位的旋转角度

这意味着,blitRotate 的第二个参数 (pos) 是窗口中枢轴点的位置,第三个参数 (originPos) 是旋转 上的枢轴点位置表面


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

import pygame

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

def blitRotate(surf, image, pos, originPos, angle):

    # offset from pivot to center
    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
    
    # roatated offset from pivot to center
    rotated_offset = offset_center_to_pivot.rotate(-angle)

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

    # get a rotated image
    rotated_image = pygame.transform.rotate(image, angle)
    rotated_image_rect = rotated_image.get_rect(center = rotated_image_center)

    # rotate and blit the image
    surf.blit(rotated_image, rotated_image_rect)
  
    # draw rectangle around the image
    pygame.draw.rect(surf, (255, 0, 0), (*rotated_image_rect.topleft, *rotated_image.get_size()),2)

def blitRotate2(surf, image, topleft, angle):

    rotated_image = pygame.transform.rotate(image, angle)
    new_rect = rotated_image.get_rect(center = image.get_rect(topleft = topleft).center)

    surf.blit(rotated_image, new_rect.topleft)
    pygame.draw.rect(surf, (255, 0, 0), new_rect, 2)

try:
    image = pygame.image.load('AirPlaneFront.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 = 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()/2, screen.get_height()/2)
    
    screen.fill(0)
    blitRotate(screen, image, pos, (w/2, h/2), angle)
    #blitRotate2(screen, image, pos, angle)
    angle += 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()

另请参阅Rotate surface 以及问题的答案:

How can you rotate an image around an off center pivot in Pygame How to rotate an image around its center while its scale is getting larger(in Pygame) How do you point the barrel towards mouse in pygame?

【讨论】:

超出了原始答案的范围,但绝对是我想要的。我喜欢它超出中心并且可以围绕提供点旋转。在做得比要求的更多方面反应很好。【参考方案2】:

您正在删除旋转创建的矩形。您需要保留矩形,因为它在旋转时会改变大小。

如果您想保留对象位置,请执行以下操作:

def rot_center(image, angle):
    """rotate a Surface, maintaining position."""
    
    loc = image.get_rect().center  #rot_image is not defined 
    rot_sprite = pygame.transform.rotate(image, angle)
    rot_sprite.get_rect().center = loc
    return rot_sprite
    
    # or return tuple: (Surface, Rect)
    # return rot_sprite, rot_sprite.get_rect()

【讨论】:

【参考方案3】:

上面的回答有些问题:函数中需要有前一个rect的位置,这样我们才能赋值给新的rect,例如:

rect = new_image.get_rect(center=rect.center) 

在另一个答案中,位置是通过从原始图像创建一个新的矩形来获得的,但这意味着它将定位在默认的 (0, 0) 坐标处。

下面的示例应该可以正常工作。新的矩形需要旧矩形的center 位置,所以我们也将它传递给函数。然后旋转图像,调用get_rect 以获取具有正确大小的新矩形并将旧矩形的center 属性作为center 参数传递。最后,将旋转后的图像和新的矩形作为元组返回,并在主循环中解包。

import pygame as pg


def rotate(image, rect, angle):
    """Rotate the image while keeping its center."""
    # Rotate the original image without modifying it.
    new_image = pg.transform.rotate(image, angle)
    # Get a new rect with the center of the old rect.
    rect = new_image.get_rect(center=rect.center)
    return new_image, rect


def main():
    clock = pg.time.Clock()
    screen = pg.display.set_mode((640, 480))
    gray = pg.Color('gray15')
    blue = pg.Color('dodgerblue2')

    image = pg.Surface((320, 200), pg.SRCALPHA)
    pg.draw.polygon(image, blue, ((0, 0), (320, 100), (0, 200)))
    # Keep a reference to the original to preserve the image quality.
    orig_image = image
    rect = image.get_rect(center=(320, 240))
    angle = 0

    done = False
    while not done:
        for event in pg.event.get():
            if event.type == pg.QUIT:
                done = True

        angle += 2
        image, rect = rotate(orig_image, rect, angle)

        screen.fill(gray)
        screen.blit(image, rect)
        pg.display.flip()
        clock.tick(30)


if __name__ == '__main__':
    pg.init()
    main()
    pg.quit()

这是另一个旋转 pygame 精灵的示例。

import pygame as pg


class Entity(pg.sprite.Sprite):

    def __init__(self, pos):
        super().__init__()
        self.image = pg.Surface((122, 70), pg.SRCALPHA)
        pg.draw.polygon(self.image, pg.Color('dodgerblue1'),
                        ((1, 0), (120, 35), (1, 70)))
        # A reference to the original image to preserve the quality.
        self.orig_image = self.image
        self.rect = self.image.get_rect(center=pos)
        self.angle = 0

    def update(self):
        self.angle += 2
        self.rotate()

    def rotate(self):
        """Rotate the image of the sprite around its center."""
        # `rotozoom` usually looks nicer than `rotate`. Pygame's rotation
        # functions return new images and don't modify the originals.
        self.image = pg.transform.rotozoom(self.orig_image, self.angle, 1)
        # Create a new rect with the center of the old rect.
        self.rect = self.image.get_rect(center=self.rect.center)


def main():
    screen = pg.display.set_mode((640, 480))
    clock = pg.time.Clock()
    all_sprites = pg.sprite.Group(Entity((320, 240)))

    while True:
        for event in pg.event.get():
            if event.type == pg.QUIT:
                return

        all_sprites.update()
        screen.fill((30, 30, 30))
        all_sprites.draw(screen)
        pg.display.flip()
        clock.tick(30)


if __name__ == '__main__':
    pg.init()
    main()
    pg.quit()

【讨论】:

我正在使用你的方法,它似乎也有一些问题。它在屏幕的左上角显示我的图像(它仍然在中心正确旋转,万岁),但我不希望它在那里,并且发生了一些奇怪的事情(位置相同,但目的地不同)。我无法在这里全部解释,我可能会就此提出问题并附上此问题的链接和您的答案。 @Ethanol 如果没有看到您的代码,我会情不自禁。上面的例子对我来说是正确的。 实际上,只是一个可能对我有帮助的快速问题:为什么您将(320, 240) 在这个rect = image.get_rect(center=(320, 240)) 中分配给中心?它有什么作用,如果删除它会对代码产生什么影响? 我正在那里设置rect 的初始位置。矩形将具有imagecenter 坐标(320, 240) 的大小。其他坐标如topleft(即实际 blit 位置)将相应调整。当图像被旋转并且你再次调用get_rect时,矩形的大小会有所不同,但是通过将中心坐标设置为之前的中心坐标,我们也会自动更新左上角位置,这样图像就会被blitted在正确的位置。 @Ethanol 你明白了吗?如果是,我们可以在这里删除 cmets,我会在答案中添加一些额外的信息。【参考方案4】:

发现问题:示例效果很好,但宽度和高度需要相同的尺寸。修复了图片,它的工作原理。

【讨论】:

【参考方案5】:

pygame 中绘制图像所需的一切

game_display = pygame.display.set_mode((800, 600))

x = 0
y = 0
angle = 0

img = pygame.image.load("resources/image.png")
img = pygame.transform.scale(img, (50, 50)) # image size

def draw_img(self, image, x, y, angle):
    rotated_image = pygame.transform.rotate(image, angle) 
    game_display.blit(rotated_image, rotated_image.get_rect(center=image.get_rect(topleft=(x, y)).center).topleft)

# run this method with your loop
def tick():
    draw_img(img, x, y, angle)

【讨论】:

【参考方案6】:

我必须如下修改 skrx 解决方案,这种方式对我有用。

angle=0
roll = true
while roll:
    # clean surface with your background color
    gameDisplay.fill(color)
    self.image = yourImage
    rotate_image = pygame.transform.rotate(self.image, angle)
    rect = rotate_image.get_rect()
    pos = (((your_surface_width - rect.width)/2),((your_surface_height - rect.height)/2))
    gameDisplay.blit(rotate_image,pos)
    pygame.display.flip()
    angle+=2
    if angle == 360:
        roll=False 

【讨论】:

以上是关于如何使用 Pygame 围绕其中心旋转图像?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Pygame 中围绕偏离中心的枢轴旋转图像

当围绕其中心旋转图像时,为啥需要添加/减去质心?

颤动 - 如何使用画布围绕中心旋转图像

围绕其中心旋转 UIImageView

围绕其中心旋转图像

如何计算围绕其中心旋转的矩形的边界框?