Поворот QPixmap внутри QLabel заставляет растровое изображение перемещаться вдоль оси x, а не оставаться внутри QLabel. - PullRequest
1 голос
/ 21 марта 2020

Я пытаюсь заставить шар (QPixmap внутри QLabel) вращаться, отскакивая от краев экрана. Но мяч, даже если он вращается, кажется, движется вдоль оси внутри QLabel, поэтому после нескольких движений таймера он выходит за границы QLabel и поэтому больше не появляется.

Пожалуйста см. мой код ниже.

from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import random
x = 0
y = 00
velX = 2
velY = 1
randX = random.choice([1, 2, 3])
randY = random.choice([1, 2, 3])


class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        self.setWindowTitle('ball move')
        self.setMinimumWidth(800)
        self.setMaximumWidth(800)
        self.setMinimumHeight(500)
        self.setMaximumHeight(500)
        self.setStyleSheet('background-color: black;border:none;')

        self.ballLabel = QLabel(self)
        self.ballPixmap = QPixmap('ball.png')
        self.resizedBallPixmap = self.ballPixmap.scaled(50, 50, Qt.KeepAspectRatio, Qt.FastTransformation)
        self.ballLabel.setFixedSize(50, 50)

        self.ballRotation = 10
        self.ballLabel.setPixmap(self.resizedBallPixmap)
        self.ballLabel.setStyleSheet('border:1px solid red;')
        self.ballLabel.show()

        self.ballLabel.move(0, 0)

    def rotateBall(self):
        self.resizedBallPixmap = self.resizedBallPixmap.transformed(
            QTransform().rotate(self.ballRotation), Qt.SmoothTransformation)

        # self.resizedBallPixmap = self.resizedBallPixmap.transformed(QTransform().translate(self.resizedBallPixmap.size().width()/2,                                    self.resizedBallPixmap.size().height()/2))

        self.ballLabel.setPixmap(self.resizedBallPixmap)


def ballMove():
    global x, y, velX, velY, randX, randY
    if (main_window.ballLabel.pos().x() + 50) > 800:
        velX = -1
        randX = random.choice([1, 2, 3])
        randY = random.choice([1, 2, 3])
    elif main_window.ballLabel.pos().x() < 0:
        velX = 1
        randX = random.choice([1, 2, 3])
        randY = random.choice([1, 2, 3])
    elif (main_window.ballLabel.pos().y() + 50) > 500:
        velY = -1
        randX = random.choice([1, 2, 3])
        randY = random.choice([1, 2, 3])
    elif main_window.ballLabel.pos().y() < 0:
        velY = 1
        randX = random.choice([1, 2, 3])
        randY = random.choice([1, 2, 3])

    x += velX*randX
    y += velY*randY
    main_window.rotateBall()
    main_window.ballLabel.move(x, y)


if __name__ == "__main__":
    app = QApplication([])
    main_window = MainWindow()
    main_window.show()
    timer = QTimer()
    timer.timeout.connect(ballMove)
    timer.start(1000)
    app.exec_()

1 Ответ

3 голосов
/ 22 марта 2020

Объяснение:

Чтобы понять проблему, необходимо проанализировать размер self.resizedBallPixmap, используя следующий код:

def rotateBall(self):
    print(self.resizedBallPixmap.size())
    # ...

Вывод:

PyQt5.QtCore.QSize(50, 50)
PyQt5.QtCore.QSize(59, 58)
PyQt5.QtCore.QSize(70, 68)
PyQt5.QtCore.QSize(81, 80)
PyQt5.QtCore.QSize(94, 93)
PyQt5.QtCore.QSize(110, 108)
PyQt5.QtCore.QSize(128, 126)
PyQt5.QtCore.QSize(149, 147)
PyQt5.QtCore.QSize(173, 171)
PyQt5.QtCore.QSize(201, 199)
PyQt5.QtCore.QSize(233, 231)
PyQt5.QtCore.QSize(271, 268)
PyQt5.QtCore.QSize(314, 311)
...

Как видите, размер QPixmap меняется, и почему он меняется? потому что при вращении прямоугольника бывший вписанный прямоугольник должен быть больше, и что заставляет прямоугольник быть больше? Ну, размер QLabel недостаточен для рисования QPixmap, и он рисует только левую часть, заставляя пользователя наблюдать, как мяч продвигается.

Решение:

Решение состоит в том, что когда QPixmap вращается, обрезается и сохраняется только необходимая часть. Кроме того, каждое преобразование вращения генерирует искажение, поэтому не рекомендуется выполнять итерацию по тому же QPixmap, но поддерживать исходную QPixmap и увеличивать угол поворота.

class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        # ...

        self.ballLabel.move(0, 0)
        <b>self.angle = 0</b>

    def rotateBall(self):
        <b>self.angle += self.ballRotation
        pixmap = self.resizedBallPixmap.transformed(
            QTransform().rotate(self.angle), Qt.FastTransformation
        )
        r = QtCore.QRect(self.resizedBallPixmap.rect())
        r.moveCenter(pixmap.rect().center())
        pixmap = pixmap.copy(r)
        self.ballLabel.setPixmap(pixmap)</b>

Лучшее решение состоит в том, чтобы используйте элементы Qt Graphics Framework, такие как QGraphicsItems, который реализует вращение и перевод.

class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        self.setWindowTitle("ball move")
        self.setFixedSize(800, 500)

        scene = QGraphicsScene()
        scene.setSceneRect(QRectF(QPointF(), QSizeF(self.size())))
        view = QGraphicsView(scene)
        self.setCentralWidget(view)

        self.setStyleSheet("background-color: black;border:none;")

        pixmap = QPixmap("ball.png").scaled(
            50, 50, Qt.KeepAspectRatio, Qt.FastTransformation
        )
        self.ballLabel = scene.addPixmap(pixmap)
        self.ballLabel.setTransformOriginPoint(self.ballLabel.boundingRect().center())


class BallManager(QObject):
    positionChanged = pyqtSignal(QPointF)
    angleChanged = pyqtSignal(float)

    def __init__(self, parent=None):
        super(BallManager, self).__init__(parent)

        self.pos = QPointF(0, 0)
        self.angle = 0

        self.vel = QPointF(2, 1)
        self.rand = QPointF(*random.sample([1, 2, 3], 2))

        self.step_angle = 10

        self.timer = QTimer(interval=1000, timeout=self.ballMove)
        self.timer.start()

    def ballMove(self):
        if (self.pos.x() + 50) > 800:
            self.vel.setX(-1)
            self.randX = QPointF(*random.sample([1, 2, 3], 2))
        elif self.pos.x() < 0:
            self.vel.setX(1)
            self.rand = QPointF(*random.sample([1, 2, 3], 2))
        elif (self.pos.y() + 50) > 500:
            self.vel.setY(-1)
            self.rand = QPointF(*random.sample([1, 2, 3], 2))
        elif self.pos.y() < 0:
            self.vel.setY(1)
            self.rand = QPointF(*random.sample([1, 2, 3], 2))
        self.pos += QPointF(self.vel.x() * self.rand.x(), self.vel.y() * self.rand.y())
        self.angle += self.step_angle

        self.positionChanged.emit(self.pos)
        self.angleChanged.emit(self.angle)


if __name__ == "__main__":
    app = QApplication([])
    main_window = MainWindow()
    main_window.show()
    manager = BallManager()
    manager.positionChanged.connect(main_window.ballLabel.setPos)
    manager.angleChanged.connect(main_window.ballLabel.setRotation)
    app.exec_()
...