如何使用 Pygame 围绕其中心旋转图像? [英] How do I rotate an image around its center using Pygame?

查看:182
本文介绍了如何使用 Pygame 围绕其中心旋转图像?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

<块引用>

ValueError: 次表面矩形外表面积

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

def rot_center(图像,角度):"""旋转图像,同时保持其中心和大小"""orig_rect = image.get_rect()rot_image = pygame.transform.rotate(图像,角度)rot_rect = orig_rect.copy()rot_rect.center = rot_image.get_rect().centerrot_image = rot_image.subsurface(rot_rect).copy()返回 rot_image

解决方案

简答:

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

def rot_center(image, angle, x, y):旋转图像 = pygame.transform.rotate(图像,角度)new_rect =rotated_image.get_rect(center = image.get_rect(center = (x, y)).center)返回rotated_image, new_rect

或者编写一个旋转和.blit图像的函数:

def blitRotateCenter(surf, image, topleft, angle):旋转图像 = pygame.transform.rotate(图像,角度)new_rect =rotated_image.get_rect(center = image.get_rect(topleft = topleft).center)surf.blit(rotated_image, new_rect)


长答案:

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

font = pygame.font.SysFont('Times New Roman', 50)text = font.render('图像', False, (255, 255, 0))图像 = 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))

一张图片(

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

这可以通过保留原始图像和blit"来解决.由单次旋转操作生成的图像形成原始图像.

angle = 0虽然没有完成:# [...]旋转图像 = pygame.transform.rotate(图像,角度)角度 += 1screen.blit(rotated_image, pos)pygame.display.flip()

现在图像似乎可以任意改变其位置,因为图像的大小随旋转而变化,原点始终是图像边界矩形的左上角.

这可以通过比较旋转前图像的

甚至可以在原始图像上定义一个枢轴.翻译"必须计算相对于图像左上角的枢轴的blit"和blit".图像的位置必须通过翻译进行移位.

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

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

计算旋转轴的平移:

pivot_rotate = pivot.rotate(angle)枢轴移动 = 枢轴旋转 - 枢轴

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

origin = (pos[0] + min_box[0] - pivot_move[0], pos[1] - max_box[1] + pivot_move[1])旋转图像 = pygame.transform.rotate(图像,角度)screen.blit(rotated_image,原点)

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

  • surf 是目标 Surface

  • image 是需要旋转和 blit

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

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

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

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


最小示例:

导入pygamepygame.init()屏幕 = pygame.display.set_mode((300, 300))时钟 = pygame.time.Clock()def blitRotate(surf, image, pos, originPos, angle):# 计算旋转图像的轴对齐边界框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])# 计算枢轴的平移枢轴 = pygame.math.Vector2(originPos[0], -originPos[1])枢轴旋转 = 枢轴.旋转(角度)枢轴移动 = 枢轴旋转 - 枢轴# 计算旋转图像的左上角原点origin = (pos[0] - originPos[0] + min_box[0] - pivot_move[0], pos[1] - originPos[1] - max_box[1] + pivot_move[1])# 获取旋转图像旋转图像 = pygame.transform.rotate(图像,角度)# 旋转和 blit 图像surf.blit(rotated_image, origin)# 在图像周围绘制矩形pygame.draw.rect(surf, (255, 0, 0), (*origin, *rotated_image.get_size()),2)def blitRotate2(冲浪,图像,左上角,角度):旋转图像 = pygame.transform.rotate(图像,角度)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)尝试:image = pygame.image.load('AirPlaneFront1-128.png')除了:text = pygame.font.SysFont('Times New Roman', 50).render('image', False, (255, 255, 0))图像 = 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()开始=假角度 = 0完成 = 错误虽然没有完成:时钟滴答(60)对于 pygame.event.get() 中的事件:如果 event.type == pygame.QUIT:完成 = 真elif event.type == pygame.KEYDOWN 或 event.type == pygame.MOUSEBUTTONDOWN:开始 = 真pos = (screen.get_width()/2, screen.get_height()/2)屏幕填充(0)blitRotate(屏幕,图像,位置,(w/2,h/2),角度)#blitRotate2(屏幕,图像,位置,角度)如果开始:角度 += 1pygame.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()出口()


另见旋转表面 和问题的答案:

I had been trying to rotate an image around its center in using pygame.transform.rotate() but it's not working. Specifically the part that hangs is rot_image = rot_image.subsurface(rot_rect).copy(). I get the exception:

ValueError: subsurface rectangle outside surface area

Here is the code used to rotate an image:

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

解决方案

Short answer:

Get the rectangle of the original image and set the position. Get the rectangle of the rotated image and set the center position through the center of the original rectangle. Return a tuple of the rotated image and the rectangle:

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

Or write a function which rotates and .blit the image:

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)


Long answer:

For the following examples and explanation I'll use a simple image generated by a rendered text:

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

An image (pygame.Surface) can be rotated by pygame.transform.rotate.

If that is done progressively in a loop, then the image gets distorted and rapidly increases:

while not done:

    # [...]

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

This is cause, because the bounding rectangle of a rotated image is always greater than the bounding rectangle of the original image (except some rotations by multiples of 90 degrees).
The image gets distort because of the multiply copies. Each rotation generates a small error (inaccuracy). The sum of the errors is growing and the images decays.

That can be fixed by keeping the original image and "blit" an image which was generated by a single rotation operation form the original image.

angle = 0
while not done:

    # [...]

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

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

Now the image seems to arbitrary change its position, because the size of the image changes by the rotation and origin is always the top left of the bounding rectangle of the image.

This can be compensated by comparing 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

Set up a list with the 4 corner points of the bounding box:

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

Rotate the vectors to the corner points by pygame.math.Vector2.rotate:

box_rotate = [p.rotate(angle) for p in box]

Get the minimum and the maximum of the rotated points:

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 "compensated" origin of the upper left point of the image by adding the minimum of the rotated box to the position. For the y coordinate max_box[1] is the minimum, because of the "flipping" along the y axis:

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

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

It is even possible to define a pivot on the original image. The "translation" of the pivot in relation to the upper left of the image has to be calculated and the "blit" position of the image has to be displaced by the translation.

Define a pivot e.g. in the center of the image:

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

Calculate the translation of the rotated pivot:

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

Finally calculate the origin of the rotated image:

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)

In the following example program, the function blitRotate(surf, image, pos, originPos, angle) does all the above steps and "blit" a rotated image to a surface.

  • surf is the target Surface

  • image is the Surface which has to be rotated and blit

  • pos is the position of the pivot on the target Surface surf (relative to the top left of surf)

  • originPos is position of the pivot on the image Surface (relative to the top left of image)

  • angle is the angle of rotation in degrees

This means, the 2nd argument (pos) of blitRotate is the position of the pivot point in the window and the 3rd argument (originPos) is the position of the pivot point on the rotating Surface:


Minimal example:

import pygame

pygame.init()
screen = pygame.display.set_mode((300, 300))
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)

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('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 = 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 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)
    #blitRotate2(screen, image, pos, angle)
    if start:
        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()


See also Rotate surface and the answers to the questions:

这篇关于如何使用 Pygame 围绕其中心旋转图像?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆