对于我来说,我似乎无法弄清楚如何根据所选的中心来镜像所选的 QGraphicsItems?我尝试的每一次尝试都会使物品最终从屏幕上射出。我在 3d 应用程序中重新创建了相同的设置并使其正常工作,但我遗漏了一些东西并且无法弄清楚。任何帮助将非常感激。最终目标是能够单击“镜像”按钮,然后将它们来回翻转,如下图所示......
import sys
from PySide2 import QtWidgets, QtCore, QtGui
from PySide2.QtGui import QPixmap, QPainter, QColor
import os
import random
class ImageWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.resize(1500,1080)
# controls
self.scene = QtWidgets.QGraphicsScene(self)
self.scene.setBackgroundBrush(QColor(40,40,40))
self.graphicsView = QtWidgets.QGraphicsView(self)
self.graphicsView.setSceneRect(-4000, -4000, 8000, 8000)
self.graphicsView.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform)
self.graphicsView.setScene(self.scene)
# align actions
self.mirrorItemsHorizontalAct = QtWidgets.QAction('Mirror Horizontal', self)
self.mirrorItemsHorizontalAct.triggered.connect(self.mirror_items_horizontal)
transformToolbar = QtWidgets.QToolBar('Transform', self)
transformToolbar.addAction(self.mirrorItemsHorizontalAct)
self.addToolBar(transformToolbar)
self.setCentralWidget(self.graphicsView)
# Load images from subfolder
self.create_shapes()
def create_shapes(self):
gradient = QtGui.QLinearGradient(0, 0, 100, 0)
gradient.setColorAt(0, QtGui.QColor(0, 0, 255))
gradient.setColorAt(1, QtGui.QColor(255, 0, 0))
rectA = QtWidgets.QGraphicsRectItem(0,0,150,100)
rectA.setBrush(QtGui.QBrush(gradient))
rectA.setPen(QtGui.QPen(QtCore.Qt.NoPen))
rectA.setPen(QtGui.QPen(QtCore.Qt.green, 10, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
rectA.setFlags(QtWidgets.QGraphicsItem.ItemIsSelectable | QtWidgets.QGraphicsItem.ItemIsMovable)
rectA.setSelected(True)
rectA.setPos(-120,50)
self.scene.addItem(rectA)
rectB = QtWidgets.QGraphicsRectItem(0,0,70,35)
rectB.setBrush(QtGui.QBrush(gradient))
rectB.setPen(QtGui.QPen(QtCore.Qt.NoPen))
rectB.setFlags(QtWidgets.QGraphicsItem.ItemIsSelectable | QtWidgets.QGraphicsItem.ItemIsMovable)
rectB.setSelected(True)
rectB.setPos(150,-150)
self.scene.addItem(rectB)
rectC = QtWidgets.QGraphicsRectItem(0,0,120,75)
rectC.setBrush(QtGui.QBrush(gradient))
rectC.setPen(QtGui.QPen(QtCore.Qt.NoPen))
rectC.setFlags(QtWidgets.QGraphicsItem.ItemIsSelectable | QtWidgets.QGraphicsItem.ItemIsMovable)
rectC.setSelected(True)
rectC.setPos(400,70)
self.scene.addItem(rectC)
# Align
def mirror_items_horizontal(self):
items = self.scene.selectedItems()
if not items:
items = self.scene.items()
# Calculate the collective bounding box
collectivePath = QtGui.QPainterPath()
for item in items:
collectivePath.addRect(item.sceneBoundingRect())
collectiveRect = collectivePath.boundingRect()
# Calculate the horizontal center of the collective bounding box
centerX = collectiveRect.center().x()
# print('centerX:', centerX)
# print('collectiveRect:', collectiveRect)
# Debug
cItem = QtWidgets.QGraphicsRectItem(collectiveRect)
cItem.setBrush(QtGui.QBrush(QtGui.QColor(255,0,0,128)))
cItem.setZValue(-1000)
self.scene.addItem(cItem)
cItem = QtWidgets.QGraphicsEllipseItem(0, 0, 10, 10)
cItem.setPos(collectiveRect.center() - QtCore.QPointF(cItem.boundingRect().width()*0.5, cItem.boundingRect().height()*0.5) )
cItem.setBrush(QtGui.QBrush(QtGui.QColor(255,255,0,128)))
cItem.setZValue(-1000)
self.scene.addItem(cItem)
for item in items:
local_offset = item.boundingRect().center().x() * 2.0
global_offset = collectiveRect.width() + item.boundingRect().x() * 2.0
print('scenePos', item.scenePos())
print('boundingRect', item.boundingRect())
print('boundingRect center', item.boundingRect().center())
print('local_offset', local_offset)
print('global_offset', global_offset)
scaleTm = QtGui.QTransform()
scaleTm.translate(local_offset, 0)
# scaleTm.translate(global_offset, 0)
# scaleTm.translate(645, 0)
scaleTm.scale(-1, 1)
tm = item.transform() * scaleTm
item.setTransform(tm)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
mainWindow = ImageWindow()
mainWindow.show()
sys.exit(app.exec_())
您没有考虑到变换始终应用于其原点(
0, 0
),而您想要基于参考点(即“选择”的中心)来镜像项目。
应用此类转换的程序是:
然后,你的尝试中还有另一个问题,这最初也让我感到困惑。您要将新的转换与现有的转换相结合,但这必须非常小心,如有关
*
运算符: 的文档中所述
请注意,矩阵乘法不可交换,即
。a*b != b*a
与常见的乘法不同(我们知道乘法的顺序并不重要),由于变换矩阵的复杂关系,情况并非如此。由于镜像基于 current 变换(也可以镜像和平移),因此必须在 before 之前应用它,因此顺序实际上是相反的:
scaleTm * item.transform()
。最后,请注意,为了保持一致性,即使您只应用水平变换,您也应该始终考虑映射点的完整坐标(而不仅仅是 x)。另外,由于
transform()
不考虑项目 scale()
和
rotation()
,因此您必须避免它们,并且应用于该项目的任何进一步缩放或旋转都必须在当前项目
transform()
内完成。在下面的示例中,我添加了一个自定义类来将“选择”显示为单独的项目,这样就不会出现累积绘图。
class CollectiveRect(QGraphicsObject):
def __init__(self):
super().__init__()
self.setZValue(-1000)
self.rect = QRectF()
self.brush = QBrush(QColor(255, 0, 0, 128))
self.center = QGraphicsEllipseItem(-5, -5, 10, 10, self)
self.center.setBrush(QBrush(QColor(255, 255, 0, 128)))
self.hideAni = QPropertyAnimation(self, b'opacity')
self.hideAni.setDuration(1000)
self.hideAni.setStartValue(1.)
self.hideAni.setEndValue(0.)
self.hideTimer = QTimer(self, singleShot=True,
interval=2000, timeout=self.hideAni.start)
self.hide()
def boundingRect(self):
return self.rect | self.childrenBoundingRect()
def setRect(self, *args):
self.rect.setRect(*QRectF(*args).getRect())
self.center.setPos(self.rect.center())
self.prepareGeometryChange()
if self.hideAni.state():
self.hideAni.stop()
self.setOpacity(1)
self.show()
self.hideTimer.start()
def paint(self, qp, opt, widget=None):
qp.setBrush(self.brush)
qp.drawRect(self.rect)
class ImageWindow(QMainWindow):
...
def create_shapes(self):
...
self.collectiveRect = CollectiveRect()
self.scene.addItem(self.collectiveRect)
def mirror_items_horizontal(self):
items = self.scene.selectedItems()
if not items:
for item in self.scene.items():
if item.flags() & QGraphicsItem.ItemIsSelectable:
item.setSelected(True)
items.append(item)
if not items:
return
# Calculate the collective bounding box
collectiveRect = QRectF()
for item in items:
collectiveRect |= item.sceneBoundingRect()
self.collectiveRect.setRect(collectiveRect)
center = collectiveRect.center()
for item in items:
reference = item.mapFromScene(center)
scaleTm = QTransform()
scaleTm.translate(reference.x(), reference.y())
scaleTm.scale(-1, 1)
scaleTm.translate(-reference.x(), -reference.y())
item.setTransform(scaleTm * item.transform())