Как повернуть изображение вокруг его центра с помощью Pygame? - PullRequest
12 голосов
/ 15 ноября 2010

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

Ответы [ 4 ]

7 голосов
/ 15 февраля 2019

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

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()
4 голосов
/ 22 сентября 2011

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

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

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()
2 голосов
/ 07 декабря 2017

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

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

Вот еще один пример с вращающимся пигамным спрайтом.

1 голос
/ 15 ноября 2010

Найдена проблема: пример работает хорошо, но нуждается в равных размерах для ширины и высоты.Исправлены картинки и все работает.

...