我是 Qt 的新手,我正在尝试使用 QGraphicsScene 和 QGraphicsView 制作一个绘画应用程序。我发现的唯一绘制方法是在 mouseMoveEvent 上向 QGraphicsScene 添加圆圈和线条。它工作正常,但有没有办法像 FabricJS 那样绘制(当添加的项目与图像具有相同的分辨率时)?
PyQt绘图:
fabricJS 绘图:
我的代码:
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class QDMWorkingAreaScene(QGraphicsScene):
def __init__(self, parent=None):
super().__init__(parent)
self._color_background = QColor("#393939")
self.textItems = []
self.drawingItems = []
self.empty = True
self.mainImage = QGraphicsPixmapItem()
self.mainImage.setTransformationMode(Qt.SmoothTransformation)
self.dirtySpeechBubbles = []
self.setBackgroundBrush(self._color_background)
def setImage(self, pixmap=None):
if pixmap and not pixmap.isNull():
self.empty = False
self.mainImage.setPixmap(pixmap)
else:
self.empty = True
self.mainImage.setPixmap(QPixmap())
self.addItem(self.mainImage)
#self.fitInView()
def hasPhoto(self):
return not self.empty
def drawCircle(self, x, y, brushSize, pen, brush):
self.drawingItems.append(self.addEllipse(x, y, brushSize, brushSize, pen, brush))
print(len(self.drawingItems))
def drawLine(self, start_x, start_y, x, y, pen):
self.drawingItems.append(self.addLine(start_x, start_y, x, y, pen))
print(len(self.drawingItems))
class QDMGraphicsView(QGraphicsView):
def __init__(self, grScene, parent = None):
super().__init__(parent)
self.empty = True
#brush drawing settings
self.drawingMode = True
self.brushSize = 10
self.brushColor = Qt.black
self.lastPoint = QPoint()
self.brush_line_pen = QPen(self.brushColor, self.brushSize, Qt.SolidLine, Qt.RoundCap)
#scene settings
self.grScene = grScene
self.initUI()
self.setScene(self.grScene)
#pan settings
self.setDragMode(QGraphicsView.RubberBandDrag)
self._isPanning = False
self._mousePressed = False
#zoom settings
self.zoomInFactor = 1.25
self.zoomOutFactor = 0.8
self.zoomClamp = False
self.zoom = 10
self.zoomStep = 1
self.zoomRange = [0, 20]
if self.drawingMode:
self.brush = self.grScene.addEllipse(0, 0, self.brushSize, self.brushSize, QPen(Qt.NoPen), self.brushColor)
self.brush.setFlag(QGraphicsItem.ItemIsMovable)
self.brush.setZValue(100)
def initUI(self):
self.setRenderHints(QPainter.Antialiasing | QPainter.HighQualityAntialiasing | QPainter.TextAntialiasing | QPainter.SmoothPixmapTransform)
self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate)
def setMainImage(self, pixmapItem):
self.grScene.setImage(pixmapItem)
def mousePressEvent(self, event):
if self.drawingMode and (event.button() == Qt.LeftButton):
x = self.mapToScene(event.pos()).x()
y = self.mapToScene(event.pos()).y()
self.grScene.drawCircle(x - self.brushSize / 2, y - self.brushSize / 2, self.brushSize, QPen(Qt.NoPen), self.brushColor)
self.lastPoint = self.mapToScene(event.pos())
elif event.button() == Qt.LeftButton:
self._mousePressed = True
if self._isPanning:
self.setCursor(Qt.ClosedHandCursor)
self._dragPos = event.pos()
event.accept()
else:
super(QDMGraphicsView, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
if self.drawingMode:
x = self.mapToScene(event.pos()).x()
y = self.mapToScene(event.pos()).y()
self.brush.setPos(x - self.brushSize / 2, y - self.brushSize / 2)
if(event.buttons() & Qt.LeftButton) & self.drawingMode:
x = self.mapToScene(event.pos()).x()
y = self.mapToScene(event.pos()).y()
self.grScene.drawLine(self.lastPoint.x(), self.lastPoint.y(), x, y, self.brush_line_pen)
self.lastPoint = self.mapToScene(event.pos())
elif self._mousePressed and self._isPanning:
newPos = event.pos()
diff = newPos - self._dragPos
self._dragPos = newPos
self.horizontalScrollBar().setValue(self.horizontalScrollBar().value() - diff.x())
self.verticalScrollBar().setValue(self.verticalScrollBar().value() - diff.y())
event.accept()
else:
super(QDMGraphicsView, self).mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
if event.button() == Qt.LeftButton:
if event.modifiers() & Qt.ControlModifier:
self.setCursor(Qt.OpenHandCursor)
else:
self._isPanning = False
self.setCursor(Qt.ArrowCursor)
self._mousePressed = False
super(QDMGraphicsView, self).mouseReleaseEvent(event)
def keyPressEvent(self, event):
if event.key() == Qt.Key_Control and not self._mousePressed:
self._isPanning = True
self.setCursor(Qt.OpenHandCursor)
else:
super(QDMGraphicsView, self).keyPressEvent(event)
def keyReleaseEvent(self, event):
if event.key() == Qt.Key_Control:
if not self._mousePressed:
self._isPanning = False
self.setCursor(Qt.ArrowCursor)
elif event.key() == Qt.Key_Delete:
self.deleteSelected()
else:
super(QDMGraphicsView, self).keyPressEvent(event)
def deleteSelected(self):
for item in self.grScene.selectedItems():
self.grScene.removeItem(item)
def getZoomStep(self, mode):
if mode == "+":
if self.zoom + self.zoomStep not in range(self.zoomRange[0], self.zoomRange[1]):
return self.zoom, 1
else:
return self.zoom + self.zoomStep, self.zoomInFactor
elif mode == "-":
if self.zoom - self.zoomStep not in range(self.zoomRange[0], self.zoomRange[1]):
return self.zoom, 1
else:
return self.zoom - self.zoomStep, self.zoomOutFactor
return 10, 1
def wheelEvent(self, event):
if event.angleDelta().y() > 0:
self.zoom, zoomFactor = self.getZoomStep("+")
else:
self.zoom, zoomFactor = self.getZoomStep("-")
self.scale(zoomFactor, zoomFactor)
def fitInView(self, scale=True):
rect = QRectF(self.grScene.mainImage.pixmap().rect())
if not rect.isNull():
self.setSceneRect(rect)
if self.grScene.hasPhoto():
unity = self.transform().mapRect(QRectF(0, 0, 1, 1))
self.scale(1 / unity.width(), 1 / unity.height())
self.zoom = 5
class WorkingArea(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.initUI()
def loadImage(self):
self.view.setMainImage(QPixmap('roi.jpg'))
def initUI(self):
self.layout = QVBoxLayout()
self.layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(self.layout)
self.grScene = QDMWorkingAreaScene()
self.view = QDMGraphicsView(self.grScene, self)
self.layout.addWidget(self.view)
gl = QOpenGLWidget()
gl.setMouseTracking(True)
format = QSurfaceFormat()
format.setSamples(4)
gl.setFormat(format)
self.view.setViewport(gl)
self.setWindowTitle("AutoMangaCleaner")
self.loadImage()
self.show()
#self.showMaximized()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = WorkingArea()
sys.exit(app.exec_())
编辑: 最近发现了另一种画法:
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
class Canvas(QGraphicsPixmapItem):
def __init__(self, image=None):
super().__init__()
self.last_pos = QPoint()
def setImage(self, image):
self.pixmap = image
self.pixmap_clone = self.pixmap.copy()
self.last_pos = QPoint()
self.setPixmap(self.pixmap)
def mousePressEvent(self, event):
pos = self.mapToParent(event.pos())
p = QPainter(self.pixmap_clone)
p.setBrush(Qt.black)
p.drawEllipse(pos, 5, 5)
self.last_pos = pos
self.setPixmap(self.pixmap_clone)
def mouseMoveEvent(self, event):
pos = self.mapToScene(event.pos())
if(event.buttons() & Qt.LeftButton):
p = QPainter(self.pixmap_clone)
p.setPen(QPen(Qt.black, 10, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
p.drawLine(self.last_pos, event.pos())
self.last_pos = pos
self.setPixmap(self.pixmap_clone)
class QDMWorkingAreaScene(QGraphicsScene):
def __init__(self, parent=None):
super().__init__(parent)
self.empty = True
self._color_background = QColor("#393939")
self.mainImage = Canvas()
self.mainImage.setTransformationMode(Qt.SmoothTransformation)
self.dirtySpeechBubbles = []
self.setBackgroundBrush(self._color_background)
def mouseMoveEvent(self, event):
super().mouseMoveEvent(event)
def setImage(self, pixmap=None):
if pixmap and not pixmap.isNull():
self.empty = False
self.mainImage.setImage(pixmap)
else:
self.empty = True
self.mainImage.setPixmap(QPixmap())
self.addItem(self.mainImage)
#self.fitInView()
def hasPhoto(self):
return not self.empty
class QDMGraphicsView(QGraphicsView):
def __init__(self, grScene, parent = None):
super().__init__(parent)
self.empty = True
self.photo = QGraphicsPixmapItem()
#text settings
#fonts, color, outline etc.
#brush drawing settings
#self.brush = QGraphicsEllipseItem
self.drawingMode = True
self.is_drawing = True
self.brushSize = 10
self.brushColor = Qt.black
self.lastPoint = QPoint()
self.brush_line_pen = QPen(self.brushColor, self.brushSize, Qt.SolidLine, Qt.RoundCap)
# self.brush = self.grScene.addEllipse(0, 0, self.brushSize, self.brushSize, QPen(Qt.NoPen), self.brushColor)
#scene settings
self.grScene = grScene
self.initUI()
self.setScene(self.grScene)
#pan settings
self.setDragMode(QGraphicsView.RubberBandDrag)
self.setDragMode(QGraphicsView.NoDrag)
self._isPanning = False
self._mousePressed = False
#zoom settings
self.zoomInFactor = 1.25
self.zoomOutFactor = 0.8
self.zoomClamp = False
self.zoom = 10
self.zoomStep = 1
self.zoomRange = [0, 20]
if self.drawingMode:
self.setDragMode(QGraphicsView.NoDrag)
self.brush = self.grScene.addEllipse(0, 0, self.brushSize, self.brushSize, QPen(Qt.NoPen), self.brushColor)
self.brush.setAcceptedMouseButtons(Qt.NoButton)
self.brush.setFlag(QGraphicsItem.ItemIsMovable)
self.brush.setZValue(100)
def initUI(self):
self.setRenderHints(QPainter.Antialiasing | QPainter.HighQualityAntialiasing | QPainter.TextAntialiasing | QPainter.SmoothPixmapTransform)
self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate)
#self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
#self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
def setMainImage(self, pixmapItem):
self.grScene.setImage(pixmapItem)
self.fitInView()
def mousePressEvent(self, event):
#print("view pos:", self.mapToScene(event.pos()))
if self.drawingMode and (event.button() == Qt.LeftButton):
super(QDMGraphicsView, self).mousePressEvent(event)
#self.grScene.mainImage.mousePressEvent(event)
if event.button() == Qt.LeftButton:
self._mousePressed = True
if self._isPanning:
self.setCursor(Qt.ClosedHandCursor)
self._dragPos = event.pos()
event.accept()
else:
super(QDMGraphicsView, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
if self.drawingMode:
x = self.mapToScene(event.pos()).x()
y = self.mapToScene(event.pos()).y()
self.brush.setPos(x - self.brushSize / 2, y - self.brushSize / 2)
if(event.buttons() == Qt.LeftButton) & self.drawingMode:
super(QDMGraphicsView, self).mouseMoveEvent(event)
elif self._mousePressed and self._isPanning:
newPos = event.pos()
diff = newPos - self._dragPos
self._dragPos = newPos
self.horizontalScrollBar().setValue(self.horizontalScrollBar().value() - diff.x())
self.verticalScrollBar().setValue(self.verticalScrollBar().value() - diff.y())
event.accept()
else:
super(QDMGraphicsView, self).mouseMoveEvent(event)
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
if event.button() == Qt.LeftButton:
if event.modifiers() == Qt.ControlModifier:
self.setCursor(Qt.OpenHandCursor)
else:
self._isPanning = False
self.setCursor(Qt.ArrowCursor)
self._mousePressed = False
super(QDMGraphicsView, self).mouseReleaseEvent(event)
def keyPressEvent(self, event):
if event.key() == Qt.Key_Control and not self._mousePressed:
self.drawingMode = False
self._isPanning = True
self.setCursor(Qt.OpenHandCursor)
else:
super(QDMGraphicsView, self).keyPressEvent(event)
def keyReleaseEvent(self, event):
if event.key() == Qt.Key_Control:
if self.is_drawing:
self.drawingMode = True
if not self._mousePressed:
self._isPanning = False
self.setCursor(Qt.ArrowCursor)
elif event.key() == Qt.Key_Delete:
self.deleteSelected()
else:
super(QDMGraphicsView, self).keyPressEvent(event)
def deleteSelected(self):
for item in self.grScene.selectedItems():
self.grScene.removeItem(item)
def wheelEvent(self, event):
if event.angleDelta().y() > 0:
step = self.zoomStep
fact = self.zoomInFactor
else:
step = -self.zoomStep
fact = self.zoomOutFactor
zoom = max(self.zoomRange[0], min(self.zoom + step, self.zoomRange[1]))
if zoom != self.zoom:
self.zoom = zoom
self.scale(fact, fact)
def fitInView(self, scale=True):
rect = QRectF(self.grScene.mainImage.pixmap.rect())
if not rect.isNull():
self.setSceneRect(rect)
if self.grScene.hasPhoto():
unity = self.transform().mapRect(QRectF(0, 0, 1, 1))
self.scale(1 / unity.width(), 1 / unity.height())
self.zoom = 5
class WorkingArea(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.initUI()
def loadImage(self):
self.view.setMainImage(QPixmap('roi.jpg'))
def initUI(self):
self.setGeometry(0, 0, 800, 800)
self.layout = QVBoxLayout()
self.layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(self.layout)
self.grScene = QDMWorkingAreaScene()
self.view = QDMGraphicsView(self.grScene, self)
self.layout.addWidget(self.view)
gl = QOpenGLWidget()
gl.setMouseTracking(True)
format = QSurfaceFormat()
format.setSamples(4)
gl.setFormat(format)
self.view.setViewport(gl)
self.setWindowTitle("AutoMangaCleaner")
self.loadImage()
self.show()
#self.view.setFocus()
def mouseMoveEvent(self, event):
self.view.mouseMoveEvent()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = WorkingArea()
sys.exit(app.exec_())
最佳答案
这是因为形状项总是矢量,所以没有“分辨率”的概念:无论比例如何,圆总是圆,而不是光栅 使用像素概念的图像。
由于用于缩放的平滑变换类似于模糊效果,可能是使用 QGraphicsBlurEffect对于元素,带有 blurRadius
1
的值(如“1 像素”)。
虽然您可以为每个项目设置效果,但出于性能原因,这不是一个好的选择:您应该将所有这些项目分组到一个父项目中。 Qt 提供了 QGraphicsGroupItem可以使用 scene.createItemGroup()
轻松创建的类.
class QDMWorkingAreaScene(QGraphicsScene):
def __init__(self, parent=None):
# ...
self.drawingGroup = self.createItemGroup([])
blur = QGraphicsBlurEffect(blurRadius=1)
self.drawingGroup.setGraphicsEffect(blur)
self.drawingGroup.setZValue(100)
# ...
def drawCircle(self, x, y, brushSize, pen, brush):
item = QGraphicsEllipseItem(
round(x), round(y),
brushSize, brushSize,
self.drawingGroup
)
item.setPen(pen)
item.setBrush(brush)
def drawLine(self, start_x, start_y, x, y, pen):
item = QGraphicsLineItem(
round(start_x), round(start_y),
round(x), round(y),
self.drawingGroup
)
item.setPen(pen)
考虑到您可能希望在导出图像时暂时禁用图形效果。
补充说明:
- 同样,出于优化原因,绘制连续线条时应使用 QPainterPathItem,并向其路径添加线条,直到松开鼠标,然后在再次按下鼠标时创建新项目;
- 在创建椭圆或开始新线/路径时,您应该区分,否则您将始终两者;
- 因为您可能希望始终使用非十进制值(这是使用基本构造函数
addEllipse()
时发生的情况,因为它们始终使用整数值),您应该始终将场景位置四舍五入,和上面一样;或者,再次将场景点转换为 QPoint 和 QPointF:
scenePos = QPointF(self.mapToScene(event.pos()).toPoint())
self.grScene.drawLine(self.lastPoint, scenePos, self.brush_line_pen)
self.lastPoint = scenePos
- 为“画笔光标”使用图形项有重要的副作用:首先,每当它移动靠近场景的边缘时,场景将增加其边界矩形,因此您应该可能在
setImage()
中执行self.setSceneRect(self.mainImage.sceneBoundingRect())
(而不是在fitInView()
中);然后,如果鼠标非常快地移出 View ,该项目仍将在场景中的某处可见:考虑在 View 的enterEvent
和leaveEvent
中切换其可见性; - 如果不够小心,为鼠标按钮和按键使用标志可能会很危险;使用正确的 event properties相反,哪个更简单明了:
if event.buttons() == Qt.LeftButton:
# left mouse button pressed *during* a mouse move event
if event.modifiers() == Qt.ControlModifier:
# Ctrl key pressed
- 简化缩放功能:
def wheelEvent(self, event):
if event.angleDelta().y() > 0:
step = self.zoomStep
fact = self.zoomInFactor
else:
step = -self.zoomStep
fact = self.zoomOutFactor
zoom = max(self.zoomRange[0], min(self.zoom + step, self.zoomRange[1]))
if zoom != self.zoom:
self.zoom = zoom
self.scale(fact, fact)
关于python - PyQt5 QGraphicsView绘图分辨率,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73676005/