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

2024-05-14 07:24:38 发布

您现在位置:Python中文网/ 问答频道 /正文

在使用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

Tags: rect图像imagegettransform中心pygamecenter
3条回答

上面的答案有一些问题:前一个rect的位置需要在函数中可用,以便我们可以将其分配给新的rect,例如:

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

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

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

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

这是另一个旋转游戏精灵的例子。

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

简短回答:

存储源图像矩形的中心,并在旋转后根据存储的中心位置更新旋转图像矩形的中心,然后返回旋转图像和矩形的元组:

def rot_center(image, angle):

    center = image.get_rect().center
    rotated_image = pygame.transform.rotate(image, angle)
    new_rect = rotated_image.get_rect(center = 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.topleft)

长答案:

对于以下示例和说明,我将使用由渲染文本生成的简单图像:

font = pygame.font.SysFont('Times New Roman', 50)
text = font.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))

图像(^{})可以通过^{}旋转。

如果循序渐进地进行循环,则图像会失真并迅速增加:

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来补偿。
对于下面的数学运算,使用^{}。注意,屏幕上的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)]]

通过^{}将矢量旋转到角点:

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)

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

  • surf是目标曲面

  • image是必须旋转的表面,并且blit

  • pos是枢轴在目标表面上的位置surf(相对于surf的左上角)

  • originPos是枢轴在image表面上的位置(相对于image的左上角)

  • angle是旋转角度,单位为度

import pygame
import pygame.font

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

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

    # 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
    origin = (pos[0] - originPos[0] + min_box[0] - pivot_move[0], pos[1] - originPos[1] - max_box[1] + pivot_move[1])

    # get a rotated image
    rotated_image = pygame.transform.rotate(image, angle)

    # rotate and blit the image
    surf.blit(rotated_image, origin)

    # draw rectangle around the image
    pygame.draw.rect (surf, (255, 0, 0), (*origin, *rotated_image.get_size()),2)

font = pygame.font.SysFont('Times New Roman', 50)
text = font.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
        elif event.type == pygame.KEYDOWN:
            if event.key==pygame.K_ESCAPE:
                done = True

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

    screen.fill(0)
    blitRotate(screen, image, pos, (w//2, h//2), 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()

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

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

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

相关问题 更多 >

    热门问题