Вопрос-Ответ

How do I rotate an image around its center using Pygame?

Как мне повернуть изображение вокруг его центра с помощью Pygame?

Я пытался повернуть изображение вокруг его центра с помощью 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

Краткий ответ:

При использовании pygame.transform.rotate размер нового повернутого изображения увеличивается по сравнению с размером исходного изображения. Вы должны убедиться, что повернутое изображение размещено так, чтобы его центр оставался в центре неповращенного изображения. Для этого возьмите прямоугольник исходного изображения и установите положение. Получаем прямоугольник повернутого изображения и устанавливаем положение центра через центр исходного прямоугольника.
Возвращает кортеж из функции red_center с повернутым изображением и ограничивающим прямоугольником повернутого изображения:

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 градусам).

Изображение искажается из-за многократного копирования. Каждый поворот генерирует небольшую ошибку (неточность). Сумма ошибок растет, а изображения уменьшаются.

Это можно исправить, сохранив исходное изображение и "размыв" изображение, сгенерированное одной операцией поворота, с исходным изображением.

angle = 0
while not done:

# [...]

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

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

Теперь изображение, кажется, произвольно меняет свое положение, потому что размер изображения меняется при повороте, а начало координат всегда находится в верхнем левом углу ограничивающего прямоугольника изображения.

Это можно компенсировать, сравнив выровненную по оси ограничивающую рамку изображения до поворота и после поворота.
Для следующей математики 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)

Повернуть и размыть изображение:

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) выполняет все вышеуказанные шаги и "прикрепляет" повернутое изображение к поверхности.


  • surf это целевая поверхность



  • image это поверхность, которую нужно повернуть и blit



  • pos это положение оси поворота на целевой поверхности surf (относительно верхнего левого угла surf)



  • originPos это положение оси поворота на image поверхности (относительно верхнего левого угла image)



  • angle это угол поворота в градусах



Это означает, что 2-й аргумент (pos) из blitRotate - это положение точки поворота в окне, а 3-й аргумент (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()

Смотрите также Поворот поверхности и ответы на вопросы:

Ответ 2

Есть некоторые проблемы с верхним ответом: позиция предыдущей прямой линии должна быть доступна в функции, чтобы мы могли назначить ее новой прямой линии, например:

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

В другом ответе местоположение получено путем создания нового прямоугольника из исходного изображения, но это означает, что оно будет расположено в координатах по умолчанию (0, 0).

Приведенный ниже пример должен работать корректно. Новому rect требуется center положение старого rect, поэтому мы передаем его также в функцию. Затем поверните изображение, вызовите 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()
Ответ 3

Вы удаляете прямоугольник, который создает rotate . Вам нужно сохранить прямоугольник, поскольку он меняет размер при повороте.

Если вы хотите сохранить расположение объектов, выполните:

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()
Ответ 4

Все, что нужно для рисования изображения в Python 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)
python pygame