Загадочное поведение QGraphicsView.fitInView - PullRequest
0 голосов
/ 10 июля 2020

Я пытаюсь создать виджет с двумя QGraphicsView для сравнения двух изображений бок о бок. Когда пользователь увеличивает одно из представлений, другое должно увеличивать ту же область. Моя функция zoomIn отправляет сигнал с видимым прямоугольником сцены, а слот в другом представлении захватывает этот сигнал и вызывает QGraphicsView.fitInView(rectangle), чтобы сделать видимой ту же область изображения. К моему удивлению, fitInView, кажется, обрабатывает прямоугольник (x, y, ширина, высота) как (x, y, x + width, y + height). Это ошибка или я что-то упустил?

Вот мой пример кода:

Подкласс QGraphicsView

import logging
from PyQt5 import (
    QtWidgets as qw,
    QtCore as qc,
    QtGui as qg
)

class Display(qw.QGraphicsView):
    sigViewportAreaChanged = qc.pyqtSignal(qc.QRectF)

    def __init__(self, *args, **kwargs):
        super(Display, self).__init__(*args, **kwargs)
        ...
        self.zoomInAction = qw.QAction('Zoom in')
        self.zoomInAction.triggered.connect(self.zoomIn)

    @qc.pyqtSlot()
    def zoomIn(self):
        self.scale(1.2, 1.2)
        pos = self.viewport().pos()
        size = self.viewport().size()
        logging.debug(f'{self.objectName()}: viewport:: pos: {pos}, size: {size}')
        pos = self.mapToScene(pos)
        size = self.mapToScene(size.width(), size.height())
        logging.debug(f'{self.objectName()}: scene:: pos: {pos}, size: {size}')
        view_area = qc.QRectF(pos.x(), pos.y(), size.x(), size.y())
        logging.debug(
            f'Emitting changed viewport area, {view_area},\n{mat}')
        self.sigViewportAreaChanged.emit(view_area)

    def setViewportRect(self, rect: qc.QRectF) -> None:
        logging.debug(f'{self.objectName()} <- {self.sender().objectName()}\nFit view to {rect}')
        pos = self.viewport().pos()
        size = self.viewport().size()
        logging.debug(f'{self.objectName()}: before fitting area: viewport:: pos: {pos}, size: {size}')
        pos = self.mapToScene(pos)
        size = self.mapToScene(size.width(), size.height())
        logging.debug(f'{self.objectName()}: before fitting area: scene:: pos: {pos}, size: {size}')
        # self.fitInView(rect)  # Incorrectly sets the visible area
        self.fitInView(rect.x(), rect.y(), rect.width() - rect.x(), rect.height() - rect.y())  # this works
        pos = self.viewport().pos()
        size = self.viewport().size()
        logging.debug(f'{self.objectName()}: after fitting area: viewport:: pos: {pos}, size: {size}')
        pos = self.mapToScene(pos)
        size = self.mapToScene(size.width(), size.height())
        logging.debug(f'{self.objectName()}: after fitting area: scene:: pos: {pos}, size: {size}')  # this suggests that the new viewport is (x, y, x+width, y+height) instead of (x, y, width, height)

Виджет контейнера:

class ReviewWidget(qw.QWidget):
    """A widget with two panes for comparing images"""

    def __init__(self, *args, **kwargs):
        super(ReviewWidget, self).__init__(*args, **kwargs)
        layout = qw.QVBoxLayout()
        self.before = Display()
        self.before.setObjectName('Left')
        self.after = Display()
        self.after.setObjectName('Right')
        self.before.setHorizontalScrollBarPolicy(qc.Qt.ScrollBarAlwaysOn)
        self.before.setVerticalScrollBarPolicy(qc.Qt.ScrollBarAlwaysOn)
        self.after.setHorizontalScrollBarPolicy(qc.Qt.ScrollBarAlwaysOn)
        self.after.setVerticalScrollBarPolicy(qc.Qt.ScrollBarAlwaysOn)

        panes_layout = qw.QHBoxLayout()
        panes_layout.addWidget(self.before)
        panes_layout.addWidget(self.after)
        layout.addLayout(panes_layout)
        self.setLayout(panes_layout)
        self.make_actions()

    def tieViews(self, tie):
        if tie:
            self.before.sigViewportAreaChanged.connect(self.after.setViewportRect)
            self.after.sigViewportAreaChanged.connect(self.before.setViewportRect)
       else:
            self.before.disconnect(self.before.sigViewportAreaChanged)
            self.after.disconnect(self.after.sigViewportAreaChanged)

    def make_actions(self):
        self.tieViewsAction = qw.QAction('Zoom views together')
        self.tieViewsAction.setCheckable(True)
        self.tieViewsAction.triggered.connect(self.tieViews)

Ответы [ 2 ]

1 голос
/ 10 июля 2020

Я считаю, что ваш способ вычисления size неверен. Вы должны рассчитать его относительно переведенного значения pos. Поскольку вам известны значения ширины и высоты, вы можете вычислить правый нижний угол view_area на основе значения сцены pos.

viewport_pos = self.viewport().pos()
scene_pos = self.mapToScene(viewport_pos)

width = self.viewport().size().width()
height = self.viewport().size().height()

lower_right_corner = QPointF(width - scene_pos.x(), height() - scene_pos.y())

Без вычитания вы предполагаете, что начало координат из view_area находится на (0, 0), тогда как на самом деле это pos.

0 голосов
/ 10 июля 2020

Хорошо, я понял свою ошибку после прочтения ответа @Roney Gomes.

Чтобы масштабировать размер в системе координат сцены, я передавал размер как точку в mapToScene.

Но точка и размер концептуально отличаются, в то время как размер не зависит от начала системы координат, в отличие от точки. mapToScene, рассматривая свой аргумент как точку, переводил его, чтобы отразить новую систему координат.

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

Более простое решение - напрямую сопоставить прямоугольник области просмотра:

rect = self.mapToScene(self.viewport().rect())  
rect = rect.boundingRect() # mapToScene(rect) returns QPolygonF
self.sigViewportAreaChanged.emit(rect)
...