Reputation: 21
This minimal code example works well when loading the image using the button:
import sys
import numpy as np
from PyQt5 import QtWidgets, QtCore, QtGui
img_path = r'C:\somepath\test.jpg'
ICON_SIZE = 100
ELLIPSE_RADIUS = 35
def pt_distance(pt1, pt2):
return np.sqrt(np.square(pt2[0]-pt1[0]) + np.square(pt2[1]-pt1[1]))
class PhotoViewer(QtWidgets.QGraphicsView):
photoClicked = QtCore.pyqtSignal(QtCore.QPoint)
def __init__(self, parent):
super().__init__(parent)
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(50, 50, 50)))
self.setFrameShape(QtWidgets.QFrame.NoFrame)
def hasPhoto(self):
return not self._empty
def fitInView(self, scale=True):
rect = QtCore.QRectF(self._photo.pixmap().rect()) # return pixmap enclosing rectangle
if not rect.isNull():
self.setSceneRect(rect) # set the visualized area rectangle to the pixmap's enclosing rectangle
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)
def setPhoto(self, pixmap):
self._scene.removeItem(self._photo)
self._zoom = 0
if pixmap and not pixmap.isNull():
self._empty = False
self._photo.setPixmap(pixmap)
self._scene.addItem(self._photo)
else:
self._empty = True
self._photo.setPixmap(QtGui.QPixmap())
self.fitInView()
def mousePressEvent(self, event):
if self._photo.isUnderMouse():
self.photoClicked.emit(self.mapToScene(event.pos()).toPoint())
super().mousePressEvent(event)
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.viewer = PhotoViewer(self)
# Styling
self.setWindowIcon(QtGui.QIcon("pyHelios.png"))
self.setWindowTitle("pyHelios")
# 'Load image' button
self.btnLoad = QtWidgets.QToolButton(self)
self.btnLoad.setText('Load image')
self.btnLoad.clicked.connect(self.loadImage)
self.viewer.photoClicked.connect(self.photoClicked)
# Arrange layout
VBlayout = QtWidgets.QVBoxLayout(self)
VBlayout.addWidget(self.viewer)
HBlayout = QtWidgets.QHBoxLayout()
HBlayout.setAlignment(QtCore.Qt.AlignLeft)
HBlayout.addWidget(self.btnLoad)
VBlayout.addLayout(HBlayout)
def loadImage(self):
pixmap = QtGui.QPixmap(img_path)
self.viewer.setPhoto(pixmap)
self.viewer._pixmap = pixmap
def photoClicked(self, pos):
pt = (pos.x(), pos.y())
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 1600, 900)
window.show()
app.exec()
However, if I try to load the image without using the button by adding the loadImage()
code into the __init__
of the class Window
, the fitInView
function no longer work as intended (the displayed image is really small:
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.viewer = PhotoViewer(self)
# Styling
self.setWindowIcon(QtGui.QIcon("pyHelios.png"))
self.setWindowTitle("pyHelios")
# 'Load image' button
self.btnLoad = QtWidgets.QToolButton(self)
self.btnLoad.setText('Load image')
self.btnLoad.clicked.connect(self.loadImage)
self.viewer.photoClicked.connect(self.photoClicked)
# Arrange layout
VBlayout = QtWidgets.QVBoxLayout(self)
VBlayout.addWidget(self.viewer)
HBlayout = QtWidgets.QHBoxLayout()
HBlayout.setAlignment(QtCore.Qt.AlignLeft)
HBlayout.addWidget(self.btnLoad)
VBlayout.addLayout(HBlayout)
pixmap = QtGui.QPixmap(img_path)
-> self.viewer.setPhoto(pixmap)
-> self.viewer._pixmap = pixmap
I dug a bit on what's going on and I basically have viewrect.width()
& viewrect.height()
super small (98 and 28) instead of (1578, 853). Pixmap rect is 1936x1936.
I am really clueless what I am missing here. I'm guessing that some "hidden" function is getting called when loading the image using the button instead of inside of the class initialization.
Upvotes: 2
Views: 532
Reputation: 48231
When a widget is being constructed it always has a default size:
Since you're trying to set the view based on the current size, and at that point the widget has not been fully constructed/mapped, nor resized (you're setting the geometry after creating it), it's considering the default size above: you created PhotoViewer
with a parent, so it will be 100x30.
A possible solution would be to call your fitInView
implementation when it's being shown the first time:
class PhotoViewer(QtWidgets.QGraphicsView):
shown = False
# ...
def showEvent(self, event):
super().showEvent(event)
if not self.shown:
self.shown = True
self.fitInView()
Upvotes: 2