上一篇博客给了一个QImageView类,其中实现了图片浏览器的基础鼠标操作,在此基础之上,进一步实现鼠标框选的操作。具体实现时,我们先构建一个场景类QImageScene,用于管理场景中的Item,场景中的Item主要有两类,一类是图像,另一类是选择框。对于选择框的操作,我们把鼠标事件定义在QImage Scene类中。
为了方便我们理清逻辑,这里强调下消息流的方向。视图框架事件的传递顺序是view->scene->item,如果需要将事件继续向后传递,使用event->ignore()是没用的,猜测因为view看做是一个控件,scene和item都是控件内的组件,ignore只能处理控件到控件的事件,但是控件内的事件无能为力。这里可以使用QGraphicsView::mousexxxEvent(event)这样的函数,将event事件再次传入视图。上面的例子也是用到了这样的方法。
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
QMetaObject, QObject, QPoint, QPointF, QRect, QRectF,
QSize, QSizeF, QTime, QUrl, Qt, Signal)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QAbstractScrollArea, QApplication, QGraphicsView, QMainWindow,
QMenuBar, QSizePolicy, QStatusBar, QVBoxLayout,
QWidget)
from PySide6 import QtGui, QtWidgets
class QImageScene(QtWidgets.QGraphicsScene):
def __init__(self, parent=None):
super(QImageScene, self).__init__(QRectF(-500, -500, 1000, 1000), parent)
self._start = QPointF()
self.it_img = None
self.it_rect = None
def remove_img(self):
if self.it_img is not None:
self.removeItem(self.it_img)
self.it_img = None
def remove_rect(self):
if self.it_rect is not None:
self.removeItem(self.it_rect)
self.it_rect = None
def has_rect_roi(self):
if self.it_rect is None or self.it_img is None:
return False
return True
def get_rect_roi(self):
if self.it_img is None:
return None
img_rect = self.it_img.boundingRect()
if self.it_rect is None:
return img_rect
lab_rect = self.it_rect.rect()
if not lab_rect.intersects(img_rect):
return img_rect
return lab_rect.intersected(img_rect)
def set_image(self, img):
if self.it_img is not None:
self.removeItem(self.it_img)
self.it_img = QtWidgets.QGraphicsPixmapItem(img)
self.addItem(self.it_img)
def mousePressEvent(self, event):
#if self.itemAt(event.scenePos(), QtGui.QTransform()) is None:
if event.button() == Qt.RightButton \
and self.it_img is not None:
if self.it_rect is not None:
self.removeItem(self.it_rect)
self.it_rect = QtWidgets.QGraphicsRectItem()
self.it_rect.setPen(QtGui.QPen(Qt.red, 0, Qt.SolidLine))
self.it_rect.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, False)
self.addItem(self.it_rect)
self._start = event.scenePos()
r = QRectF(self._start, self._start)
self.it_rect.setRect(r)
super(QImageScene, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
if self.it_rect is not None and event.buttons() == Qt.RightButton:
r = QRectF(self._start, event.scenePos()).normalized()
self.it_rect.setRect(r)
super(QImageScene, self).mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
if self.it_img is None:
self.remove_img()
if self.it_rect is not None:
rect = self.it_rect.rect()
rect_img = self.it_img.boundingRect()
if rect.width() < 4 or rect.height() < 4 or not rect.intersects(rect_img):
self.remove_rect()
else:
r = rect.intersected(rect_img).normalized()
self.it_rect.setRect(r)
super(QImageScene, self).mouseReleaseEvent(event)
下面是QImageView类,调用QImageScene实现完整功能(总体和前节博客给出的代码类似):
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
QMetaObject, QObject, QPoint, QPointF, QRect, QRectF,
QSize, QSizeF, QTime, QUrl, Qt, Signal)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QAbstractScrollArea, QApplication, QGraphicsView, QMainWindow,
QMenuBar, QSizePolicy, QStatusBar, QVBoxLayout,
QWidget)
from PySide6 import QtGui, QtWidgets
from window.viewer.qimagescene import QImageScene
class QImageView(QGraphicsView):
#signal_lclick = Signal(str)
def __init__(self, parent):
super(QImageView, self).__init__(parent)
self.setMouseTracking(True)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setAcceptDrops(False)
self.setStyleSheet(u"broder: none")
#self.setCacheMode(QGraphicsView.CacheBackground)
#self.setDragMode(QGraphicsView.ScrollHandDrag)
#self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
#self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
self.it_img = QtWidgets.QGraphicsPixmapItem()
self.scene = QImageScene()
self.img = QtGui.QPixmap()
self.img_sz = []
self.scale_current = 1
self.scale_fill = 1
self.scale_index = 0
self.scale_lut_base = [0.01, 0.02, 0.05, 0.1, 0.15, 0.2, 0.3, 0.4, 0.5, 0.6, 0.8, 1.0, 1.25, 1.5, 1.75,
2.0, 3.0, 5.0, 7.0, 10.0, 20.0, 30.0, 50.0]
self.scale_lut = self.scale_lut_base.copy()
self.left_click = QPoint(0, 0)
self.pointer_pos = QPoint(0, 0)
def update_shown_image(self, img):
self.img = img
self.img_sz = self.img.size()
self.scene.setSceneRect(0, 0, self.img_sz.width(), self.img_sz.height())
self.scene.set_image(img)
#self.scene.removeItem(self.it_img)
#self.it_img = QtWidgets.QGraphicsPixmapItem(self.img)
#self.scene.addItem(self.it_img)
def set_shown_image(self, img):
#self.img.load(filename)
self.img = img
self.img_sz = self.img.size()
self.scene.setSceneRect(0, 0, self.img_sz.width(), self.img_sz.height())
self.scene.set_image(img)
self.setScene(self.scene)
self.setTransform(QtGui.QTransform())
# init scale lut
size = self.size()
scale_fill_h = size.height() / self.img_sz.height()
scale_fill_w = size.width() / self.img_sz.width()
self.scale_fill = min(scale_fill_w, scale_fill_h)
self.scale_lut = self.scale_lut_base.copy()
if scale_fill_h not in self.scale_lut:
self.scale_lut.append(scale_fill_h)
if scale_fill_w not in self.scale_lut:
self.scale_lut.append(scale_fill_w)
self.scale_lut.sort()
self.scale_index = self.scale_lut.index(self.scale_fill)
self.scale_current = self.scale_lut[self.scale_index]
img_center = QPoint(self.img_sz.width() // 2, self.img_sz.height() // 2)
self.setTransform(QtGui.QTransform(self.scale_fill, 0, 0, 0, self.scale_fill, 0, 0, 0, 1))
self.centerOn(img_center)
def wheelEvent(self, event):
self.setTransformationAnchor(QGraphicsView.NoAnchor)
self.setResizeAnchor(QGraphicsView.NoAnchor)
if event.angleDelta().y() > 0.5 and self.scale_index < len(self.scale_lut) - 1: # zoom in
self.scale_index = self.scale_index + 1
elif event.angleDelta().y() < 0.5 and self.scale_index > 0: # zoom out
self.scale_index = self.scale_index - 1
self.scale_current = self.scale_lut[self.scale_index]
oldpos = self.mapToScene(QPoint(event.position().x(), event.position().y()))
self.setTransform(QtGui.QTransform(self.scale_current, 0, 0, 0, self.scale_current, 0, 0, 0, 1))
newpos = self.mapToScene(QPoint(event.position().x(), event.position().y()))
delta = newpos - oldpos
self.translate(delta.x(), delta.y())
event.ignore()
#super(QImageView, self).wheelEvent(event)
def mousePressEvent(self, event):
button = event.button()
# move image
if button == Qt.LeftButton:
self.setCursor(Qt.OpenHandCursor)
self.left_click = self.mapToScene(event.pos())
event.accept()
super(QImageView, self).mousePressEvent(event)
def mouseMoveEvent(self, event: QtGui.QMouseEvent) -> None:
button = event.buttons()
self.pointer_pos = self.mapToScene(event.pos())
# move image
if button == Qt.LeftButton:
mouse_pos = self.mapToScene(event.pos())
delta = mouse_pos - self.left_click
delta = delta * self.transform().m11() # 乘以缩放比
self.centerOn(self.mapToScene(QPoint(self.viewport().rect().width() / 2 - delta.x(),
self.viewport().rect().height() / 2 - delta.y())))
event.accept()
else:
event.ignore()
super(QImageView, self).mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
button = event.button()
# move image
if button == Qt.LeftButton:
self.setCursor(Qt.ArrowCursor)
mouse_pos = self.mapToScene(event.pos())
delta = mouse_pos - self.left_click
delta = delta * self.transform().m11() # 乘以缩放比
if delta.x() != 0 or delta.y() != 0:
self.centerOn(self.mapToScene(QPoint(self.viewport().rect().width() / 2-delta.x(),
self.viewport().rect().height() / 2-delta.y())))
event.accept()
elif button == Qt.RightButton:
pass
super(QImageView, self).mouseReleaseEvent(event)
我们在窗体中加入提升过后的graphview就可以使用了。