Ответ 1
Короткий ответ:
Сохраните центр прямоугольника исходного изображения и обновите центр прямоугольника повернутого изображения после поворота на сохраненную центральную позицию и верните кортеж повернутого изображения и прямоугольника:
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))
Изображение ( 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)
Можно даже определить точку поворота на исходном изображении. "Перевод" оси поворота относительно верхнего левого угла изображения должен быть рассчитан, и положение "блиц" изображения должно быть смещено переводом.
Определите опору, например, в центре изображения:
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
выполняет все вышеперечисленные шаги и "перетягивает" повернутое изображение на поверхность. pos
- позиция изображения. originPos
- это точка на изображении, которая размещается на pos
и оси:
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()