同步两个QGraphicsView与不同的图像

发布于 2025-01-19 10:19:14 字数 5654 浏览 1 评论 0原文

我想并排显示两张图像,这样当我缩放或平移一张图像时,另一张图像也会随之跟随。我当前的方法是在解析鼠标事件后发出 viewUpdated 事件。该事件包含 viewportTransformation 并用于更新其他视图中的transform。这种方法适用于缩放部分,但平移不起作用。

下面的代码基于此答案中提供的qt5版本:https://stackoverflow.com/a/35514531/185475

from PyQt5 import QtCore, QtGui, QtWidgets

# Code from https://stackoverflow.com/a/35514531
class PhotoViewer(QtWidgets.QGraphicsView):
    photoClicked = QtCore.pyqtSignal(QtCore.QPoint)
    viewUpdated = QtCore.pyqtSignal(QtGui.QTransform)

    def __init__(self, parent):
        super(PhotoViewer, self).__init__(parent)
        self._zoom = 0
        self._empty = True
        self._scene = QtWidgets.QGraphicsScene(self)
        self._photo = QtWidgets.QGraphicsPixmapItem()
        self._scene.addItem(self._photo)
        self.setScene(self._scene)
        self.setTransformationAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
        self.setResizeAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(30, 30, 30)))
        self.setFrameShape(QtWidgets.QFrame.NoFrame)

    def hasPhoto(self):
        return not self._empty

    def fitInView(self, scale=True):
        rect = QtCore.QRectF(self._photo.pixmap().rect())
        if not rect.isNull():
            self.setSceneRect(rect)
            if self.hasPhoto():
                unity = self.transform().mapRect(QtCore.QRectF(0, 0, 1, 1))
                self.scale(1 / unity.width(), 1 / unity.height())
                viewrect = self.viewport().rect()
                scenerect = self.transform().mapRect(rect)
                factor = min(viewrect.width() / scenerect.width(),
                             viewrect.height() / scenerect.height())
                self.scale(factor, factor)
            self._zoom = 0

    def setPhoto(self, pixmap=None):
        self._zoom = 0
        if pixmap and not pixmap.isNull():
            self._empty = False
            self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)
            self._photo.setPixmap(pixmap)
        else:
            self._empty = True
            self.setDragMode(QtWidgets.QGraphicsView.NoDrag)
            self._photo.setPixmap(QtGui.QPixmap())
        self.fitInView()

    def wheelEvent(self, event):
        if self.hasPhoto():
            if event.angleDelta().y() > 0:
                factor = 1.25
                self._zoom += 1
            else:
                factor = 0.8
                self._zoom -= 1
            if self._zoom > 0:
                self.scale(factor, factor)
            elif self._zoom == 0:
                self.fitInView()
            else:
                self._zoom = 0
        self.viewUpdated.emit(self.viewportTransform())

    def toggleDragMode(self):
        if self.dragMode() == QtWidgets.QGraphicsView.ScrollHandDrag:
            self.setDragMode(QtWidgets.QGraphicsView.NoDrag)
        elif not self._photo.pixmap().isNull():
            self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)

    def mousePressEvent(self, event):
        if self._photo.isUnderMouse():
            self.photoClicked.emit(self.mapToScene(event.pos()).toPoint())
        super(PhotoViewer, self).mousePressEvent(event)
        self.viewUpdated.emit(self.viewportTransform())

    def set_transform(self, transform):
        self.setTransform(transform)


class Window(QtWidgets.QWidget):
    def __init__(self):
        super(Window, self).__init__()
        self.viewer = PhotoViewer(self)
        self.viewerSecondImage = PhotoViewer(self)
        self.viewer.viewUpdated.connect(self.viewerSecondImage.set_transform)
        self.viewerSecondImage.viewUpdated.connect(self.viewer.set_transform)
        # 'Load image' button
        self.btnLoad = QtWidgets.QToolButton(self)
        self.btnLoad.setText('Load image')
        self.btnLoad.clicked.connect(self.loadImage)
        # Button to change from drag/pan to getting pixel info
        self.btnPixInfo = QtWidgets.QToolButton(self)
        self.btnPixInfo.setText('Enter pixel info mode')
        self.btnPixInfo.clicked.connect(self.pixInfo)
        self.editPixInfo = QtWidgets.QLineEdit(self)
        self.editPixInfo.setReadOnly(True)
        self.viewer.photoClicked.connect(self.photoClicked)
        # Arrange layout
        VBlayout = QtWidgets.QVBoxLayout(self)
        HBlayoutImageViewers = QtWidgets.QHBoxLayout()
        HBlayoutImageViewers.addWidget(self.viewer)
        HBlayoutImageViewers.addWidget(self.viewerSecondImage)
        VBlayout.addLayout(HBlayoutImageViewers)
        HBlayout = QtWidgets.QHBoxLayout()
        HBlayout.setAlignment(QtCore.Qt.AlignLeft)
        HBlayout.addWidget(self.btnLoad)
        HBlayout.addWidget(self.btnPixInfo)
        HBlayout.addWidget(self.editPixInfo)
        VBlayout.addLayout(HBlayout)

    def loadImage(self):
        self.viewer.setPhoto(QtGui.QPixmap('input/490px-Dostojka_adype.jpg'))
        self.viewerSecondImage.setPhoto(QtGui.QPixmap('input/490px-Dostojka_adype.jpg'))

    def pixInfo(self):
        self.viewer.toggleDragMode()

    def photoClicked(self, pos):
        if self.viewer.dragMode()  == QtWidgets.QGraphicsView.NoDrag:
            self.editPixInfo.setText('%d, %d' % (pos.x(), pos.y()))



if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    window = Window()
    window.setGeometry(500, 300, 800, 600)
    window.show()
    sys.exit(app.exec_())

I would like to show two images next to each other, such that when I zoom or pan on one image the other image follows along. My current approach is to emit a viewUpdated event after resolving mouse events. The event contains the viewportTransformation and is used to update the transform in the other view. This sort of works for the zoom part, but panning does not work.

The code below is based on the qt5 version provided in this answer: https://stackoverflow.com/a/35514531/185475

from PyQt5 import QtCore, QtGui, QtWidgets

# Code from https://stackoverflow.com/a/35514531
class PhotoViewer(QtWidgets.QGraphicsView):
    photoClicked = QtCore.pyqtSignal(QtCore.QPoint)
    viewUpdated = QtCore.pyqtSignal(QtGui.QTransform)

    def __init__(self, parent):
        super(PhotoViewer, self).__init__(parent)
        self._zoom = 0
        self._empty = True
        self._scene = QtWidgets.QGraphicsScene(self)
        self._photo = QtWidgets.QGraphicsPixmapItem()
        self._scene.addItem(self._photo)
        self.setScene(self._scene)
        self.setTransformationAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
        self.setResizeAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(30, 30, 30)))
        self.setFrameShape(QtWidgets.QFrame.NoFrame)

    def hasPhoto(self):
        return not self._empty

    def fitInView(self, scale=True):
        rect = QtCore.QRectF(self._photo.pixmap().rect())
        if not rect.isNull():
            self.setSceneRect(rect)
            if self.hasPhoto():
                unity = self.transform().mapRect(QtCore.QRectF(0, 0, 1, 1))
                self.scale(1 / unity.width(), 1 / unity.height())
                viewrect = self.viewport().rect()
                scenerect = self.transform().mapRect(rect)
                factor = min(viewrect.width() / scenerect.width(),
                             viewrect.height() / scenerect.height())
                self.scale(factor, factor)
            self._zoom = 0

    def setPhoto(self, pixmap=None):
        self._zoom = 0
        if pixmap and not pixmap.isNull():
            self._empty = False
            self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)
            self._photo.setPixmap(pixmap)
        else:
            self._empty = True
            self.setDragMode(QtWidgets.QGraphicsView.NoDrag)
            self._photo.setPixmap(QtGui.QPixmap())
        self.fitInView()

    def wheelEvent(self, event):
        if self.hasPhoto():
            if event.angleDelta().y() > 0:
                factor = 1.25
                self._zoom += 1
            else:
                factor = 0.8
                self._zoom -= 1
            if self._zoom > 0:
                self.scale(factor, factor)
            elif self._zoom == 0:
                self.fitInView()
            else:
                self._zoom = 0
        self.viewUpdated.emit(self.viewportTransform())

    def toggleDragMode(self):
        if self.dragMode() == QtWidgets.QGraphicsView.ScrollHandDrag:
            self.setDragMode(QtWidgets.QGraphicsView.NoDrag)
        elif not self._photo.pixmap().isNull():
            self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)

    def mousePressEvent(self, event):
        if self._photo.isUnderMouse():
            self.photoClicked.emit(self.mapToScene(event.pos()).toPoint())
        super(PhotoViewer, self).mousePressEvent(event)
        self.viewUpdated.emit(self.viewportTransform())

    def set_transform(self, transform):
        self.setTransform(transform)


class Window(QtWidgets.QWidget):
    def __init__(self):
        super(Window, self).__init__()
        self.viewer = PhotoViewer(self)
        self.viewerSecondImage = PhotoViewer(self)
        self.viewer.viewUpdated.connect(self.viewerSecondImage.set_transform)
        self.viewerSecondImage.viewUpdated.connect(self.viewer.set_transform)
        # 'Load image' button
        self.btnLoad = QtWidgets.QToolButton(self)
        self.btnLoad.setText('Load image')
        self.btnLoad.clicked.connect(self.loadImage)
        # Button to change from drag/pan to getting pixel info
        self.btnPixInfo = QtWidgets.QToolButton(self)
        self.btnPixInfo.setText('Enter pixel info mode')
        self.btnPixInfo.clicked.connect(self.pixInfo)
        self.editPixInfo = QtWidgets.QLineEdit(self)
        self.editPixInfo.setReadOnly(True)
        self.viewer.photoClicked.connect(self.photoClicked)
        # Arrange layout
        VBlayout = QtWidgets.QVBoxLayout(self)
        HBlayoutImageViewers = QtWidgets.QHBoxLayout()
        HBlayoutImageViewers.addWidget(self.viewer)
        HBlayoutImageViewers.addWidget(self.viewerSecondImage)
        VBlayout.addLayout(HBlayoutImageViewers)
        HBlayout = QtWidgets.QHBoxLayout()
        HBlayout.setAlignment(QtCore.Qt.AlignLeft)
        HBlayout.addWidget(self.btnLoad)
        HBlayout.addWidget(self.btnPixInfo)
        HBlayout.addWidget(self.editPixInfo)
        VBlayout.addLayout(HBlayout)

    def loadImage(self):
        self.viewer.setPhoto(QtGui.QPixmap('input/490px-Dostojka_adype.jpg'))
        self.viewerSecondImage.setPhoto(QtGui.QPixmap('input/490px-Dostojka_adype.jpg'))

    def pixInfo(self):
        self.viewer.toggleDragMode()

    def photoClicked(self, pos):
        if self.viewer.dragMode()  == QtWidgets.QGraphicsView.NoDrag:
            self.editPixInfo.setText('%d, %d' % (pos.x(), pos.y()))



if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    window = Window()
    window.setGeometry(500, 300, 800, 600)
    window.show()
    sys.exit(app.exec_())

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

深者入戏 2025-01-26 10:19:14

只需要同步两个QGraphicsViews的滚动条值即可,

def bindScrollBars(scrollBar1, scrollBar2):

    # syncronizing scrollbars syncrnonously somehow breaks zooming and doesn't work
    # scrollBar1.valueChanged.connect(lambda value: scrollBar2.setValue(value))
    # scrollBar2.valueChanged.connect(lambda value: scrollBar1.setValue(value))

    # syncronizing scrollbars asyncronously works ok
    scrollBar1.valueChanged.connect(lambda _: QtCore.QTimer.singleShot(0, lambda: scrollBar2.setValue(scrollBar1.value())))
    scrollBar2.valueChanged.connect(lambda _: QtCore.QTimer.singleShot(0, lambda: scrollBar1.setValue(scrollBar2.value())))
    
class Window(QtWidgets.QWidget):
    def __init__(self):
        ...
        bindScrollBars(self.viewer.horizontalScrollBar(), self.viewerSecondImage.horizontalScrollBar())
        bindScrollBars(self.viewer.verticalScrollBar(), self.viewerSecondImage.verticalScrollBar())

另外wheelEvent也可以简化

    def wheelEvent(self, event):
        if self.hasPhoto():
            factor = 1.25
            if event.angleDelta().y() > 0:
                self.scale(factor, factor)
            else:
                self.scale(1/factor, 1/factor)
        self.viewUpdated.emit(self.transform())

You only need to syncronize scrollbar values of two QGraphicsViews

def bindScrollBars(scrollBar1, scrollBar2):

    # syncronizing scrollbars syncrnonously somehow breaks zooming and doesn't work
    # scrollBar1.valueChanged.connect(lambda value: scrollBar2.setValue(value))
    # scrollBar2.valueChanged.connect(lambda value: scrollBar1.setValue(value))

    # syncronizing scrollbars asyncronously works ok
    scrollBar1.valueChanged.connect(lambda _: QtCore.QTimer.singleShot(0, lambda: scrollBar2.setValue(scrollBar1.value())))
    scrollBar2.valueChanged.connect(lambda _: QtCore.QTimer.singleShot(0, lambda: scrollBar1.setValue(scrollBar2.value())))
    
class Window(QtWidgets.QWidget):
    def __init__(self):
        ...
        bindScrollBars(self.viewer.horizontalScrollBar(), self.viewerSecondImage.horizontalScrollBar())
        bindScrollBars(self.viewer.verticalScrollBar(), self.viewerSecondImage.verticalScrollBar())

Also wheelEvent can be simplified

    def wheelEvent(self, event):
        if self.hasPhoto():
            factor = 1.25
            if event.angleDelta().y() > 0:
                self.scale(factor, factor)
            else:
                self.scale(1/factor, 1/factor)
        self.viewUpdated.emit(self.transform())
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文