QGraphicsView.fitInView 令人费解的行为
Puzzling behavior of QGraphicsView.fitInView
我正在尝试制作一个带有两个 QGraphicsView 的小部件,以并排比较两个图像。当用户放大其中一个视图时,另一个视图应放大到同一区域。我的 zoomIn 函数发送一个带有可见场景矩形的信号,另一个视图中的一个槽捕获这个信号并调用 QGraphicsView.fitInView(rectangle)
使相同的图像区域可见。
令我惊讶的是,fitInView
似乎将矩形 (x, y, width, height) 视为 (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)
我认为您 size
的计算方式是错误的。您应该相对于 pos
的翻译值来计算它。既然知道宽高的值,那么就可以根据pos
.
的场景值计算出view_area
的右下角
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
.
好的,我在阅读@Roney Gomes 的回答后发现了我的错误。
为了将尺寸缩放到场景坐标系,我将尺寸作为一个点传递给 mapToScene
。
但是点和大小在概念上是不同的,而大小不依赖于坐标系的原点,点是。
mapToScene
,将其参数视为一个点,正在对其进行翻译以反映新的坐标系。
因此我需要减去新坐标系的原点(实际上是视口的左上角)以取回尺寸。
更简单的解决方案是直接映射视口矩形:
rect = self.mapToScene(self.viewport().rect())
rect = rect.boundingRect() # mapToScene(rect) returns QPolygonF
self.sigViewportAreaChanged.emit(rect)
我正在尝试制作一个带有两个 QGraphicsView 的小部件,以并排比较两个图像。当用户放大其中一个视图时,另一个视图应放大到同一区域。我的 zoomIn 函数发送一个带有可见场景矩形的信号,另一个视图中的一个槽捕获这个信号并调用 QGraphicsView.fitInView(rectangle)
使相同的图像区域可见。
令我惊讶的是,fitInView
似乎将矩形 (x, y, width, height) 视为 (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)
我认为您 size
的计算方式是错误的。您应该相对于 pos
的翻译值来计算它。既然知道宽高的值,那么就可以根据pos
.
view_area
的右下角
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
.
好的,我在阅读@Roney Gomes 的回答后发现了我的错误。
为了将尺寸缩放到场景坐标系,我将尺寸作为一个点传递给 mapToScene
。
但是点和大小在概念上是不同的,而大小不依赖于坐标系的原点,点是。
mapToScene
,将其参数视为一个点,正在对其进行翻译以反映新的坐标系。
因此我需要减去新坐标系的原点(实际上是视口的左上角)以取回尺寸。
更简单的解决方案是直接映射视口矩形:
rect = self.mapToScene(self.viewport().rect())
rect = rect.boundingRect() # mapToScene(rect) returns QPolygonF
self.sigViewportAreaChanged.emit(rect)