在 QLabel 内旋转 QPixmap 会导致像素图沿 x 轴移动,而不是停留在 QLabel 内
Rotate a QPixmap inside a QLabel causes the pixmap to move along the x-axis rather than staying inside the QLabel
我试图让一个球(QLabel 内的 QPixmap)在从屏幕边缘弹起的同时旋转。但是球,即使它确实旋转,似乎也沿着 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_()
解释:
要理解问题,必须使用以下代码分析 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的大小是变化的,为什么会变化?因为当旋转一个矩形时,ex内接的矩形必须更大,而导致矩形变大的原因是什么?好吧,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 图形框架的元素,例如实现旋转和平移的 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_()
我试图让一个球(QLabel 内的 QPixmap)在从屏幕边缘弹起的同时旋转。但是球,即使它确实旋转,似乎也沿着 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_()
解释:
要理解问题,必须使用以下代码分析 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的大小是变化的,为什么会变化?因为当旋转一个矩形时,ex内接的矩形必须更大,而导致矩形变大的原因是什么?好吧,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 图形框架的元素,例如实现旋转和平移的 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_()