Hello I am tring to implement simple image widget with zoom to mouse position. I combined the example at Zooming in/out on a mouser point ? and https://doc.qt.io/qt-5/qtwidgets-widgets-imageviewer-example.html. However the image do not scale as expected and the scale bars do not update appropriately either. Here is my code:
import sys
from PySide6 import QtWidgets
from PySide6.QtCore import Qt
from PIL.ImageQt import ImageQt
from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import QDialog, QVBoxLayout, QLabel, QScrollArea
class MyScrollArea(QScrollArea):
def __init__(self, imageWidget):
# initialize widget
super().__init__()
self.setWidget(imageWidget)
self.myImageWidget = imageWidget
self.oldScale = 1
self.newScale = 1
def wheelEvent(self, event) -> None:
if event.angleDelta().y() < 0:
# zoom out
self.newScale = 0.8
else:
# zoom in
self.newScale = 1.25
# compute scrollbar positions
scrollBarPosHorizontal = self.horizontalScrollBar().value()
scrollBarPosVertical = self.verticalScrollBar().value()
deltaToPos = (event.position() / self.oldScale) - (self.myImageWidget.pos() / self.oldScale)
delta = deltaToPos * self.newScale - deltaToPos * self.oldScale
# resize image
self.myImageWidget.resize(self.myImageWidget.size() * self.newScale)
# set scrollbars
self.horizontalScrollBar().setValue(scrollBarPosHorizontal+delta.x())
self.verticalScrollBar().setValue(scrollBarPosVertical+delta.y())
# save old scale
self.oldScale = self.newScale
class ImageViewer(QDialog):
def __init__(self, img):
# initialize widget
super().__init__()
self.setWindowTitle('Zoom example')
self.imageWidget = QLabel()
self.imageWidget.installEventFilter(self)
self.imageWidget.setAlignment(Qt.AlignCenter)
self.pixmap = QPixmap.fromImage(img)
self.imageWidget.setPixmap(self.pixmap)
# create scroll area
self.scrollArea = MyScrollArea(self.imageWidget)
# insert to layout
self.layout = QVBoxLayout()
self.layout.addWidget(self.scrollArea)
self.setLayout(self.layout)
if __name__ == '__main__':
# prepare app
app = QtWidgets.QApplication(sys.argv)
# prepare image
image = ImageQt("test.png")
# create viewer widget
MyWidget = ImageViewer(image)
MyWidget.show()
# close app
sys.exit(app.exec())
The image do not scale to mouse point at all. What am I doing wrong?
The main problem is that by default the pixmap is not resized when QLabel is resized, so setScaledContents(True)
must be used.
Note that the algorithm used for the zoom and translation doesn't work very well, as it doesn't consider the change in the range of the scroll bars correctly.
I propose an alternate version that actually zooms on the mouse similarly to what happens in common image viewers/editors and map viewers. The trick is to map the mouse position to the label, and get the delta based on the scaled position:
class MyScrollArea(QScrollArea):
def __init__(self, imageWidget):
# ...
imageWidget.setScaledContents(True)
# ...
def wheelEvent(self, event) -> None:
if event.angleDelta().y() < 0:
# zoom out
self.newScale = 0.8
else:
# zoom in
self.newScale = 1.25
widgetPos = self.myImageWidget.mapFrom(self, event.position())
# resize image
self.myImageWidget.resize(self.myImageWidget.size() * self.newScale)
delta = widgetPos * self.newScale - widgetPos
self.horizontalScrollBar().setValue(
self.horizontalScrollBar().value() + delta.x())
self.verticalScrollBar().setValue(
self.verticalScrollBar().value() + delta.y())
self.oldScale = self.newScale
Note that QLabel is not well suited for such purposes (especially for big images and high zoom values). I strongly suggest you to consider switching to the Graphics View Framework.