ItemClipsToShape QGraphicsSVGItem
ItemClipsToShape QGraphicsSVGItem
下面的标志没有按照它的意思剪裁形状。相反,它会创建一个矩形选区。
self.setFlag(self.ItemClipsToShape)
如何让选区剪辑到形状?
test.svg
<svg viewBox='0 0 108 95'
xmlns='http://www.w3.org/2000/svg'>
<g transform='scale(0.1)'>
<path id="p2" fill='blue' stroke='red' d='M249,699v43h211v-43h-64l-2,3l-2,4l-4,3c0,0-1,2-2,2h-4c-2,0-3,0-4,1c-1,1-3,1-3,
2l-3,4c0,1-1,2-2,2h-4c0,0-2,1-3,0l-3-1c-1,0-3-1-3-2c-1-1,0-2-1-3l-1-3c-1-1-2-1-3-1c-1,0-4,
0-4-1c0-2,0-3-1-4v-3v-3z'/>
<path id="p3" fill='blue' d='M385,593c0,9-6,15-13,15c-7,0-13-6-13-15c0-8,12-39,14-39c1,0,12,31,12,39'/>
</g>
</svg>
svg_mouse.py
from PyQt5 import QtWidgets
from PyQt5.QtSvg import QGraphicsSvgItem, QSvgRenderer
class SvgItem(QGraphicsSvgItem):
def __init__(self, id, renderer, parent=None):
super().__init__(parent)
self.id = id
self.setSharedRenderer(renderer)
self.setElementId(id)
bounds = renderer.boundsOnElement(id)
self.setPos(bounds.topLeft())
#self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True) #horrible selection-box
def mousePressEvent(self, event: 'QtWidgets.QGraphicsSceneMouseEvent'):
print('svg item: ' + self.id + ' - mousePressEvent()')
super().mousePressEvent(event)
def mouseReleaseEvent(self, event: 'QtWidgets.QGraphicsSceneMouseEvent'):
print('svg item: ' + self.id + ' - mouseReleaseEvent()')
super().mouseReleaseEvent(event)
class SvgViewer(QtWidgets.QGraphicsView):
def __init__(self, parent):
super().__init__(parent)
self._scene = QtWidgets.QGraphicsScene(self)
self._renderer = QSvgRenderer()
self.setScene(self._scene)
def set_svg(self, data):
self.resetTransform()
self._scene.clear()
self._renderer.load(data)
item1 = SvgItem('p2', self._renderer)
self._scene.addItem(item1)
item2 = SvgItem('p3', self._renderer)
self._scene.addItem(item2)
def mousePressEvent(self, event: 'QtWidgets.QGraphicsSceneMouseEvent'):
print('SvgViewer - mousePressEvent()')
super().mousePressEvent(event)
def mouseReleaseEvent(self, event: 'QtWidgets.QGraphicsSceneMouseEvent'):
print('SvgViewer - mouseReleaseEvent()')
super().mouseReleaseEvent(event)
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.viewer = SvgViewer(self)
vb_layout = QtWidgets.QVBoxLayout(self)
vb_layout.addWidget(self.viewer)
img = b'''
<svg viewBox='0 0 108 95' xmlns='http://www.w3.org/2000/svg'>
<g transform='scale(0.1)'>
<a href=""><path id="p2" d='M249,699v43h211v-43h-64l-2,3l-2,4l-4,3c0,0-1,2-2,2h-4c-2,0-3,0-4,1c-1,1-3,1-3,
2l-3,4c0,1-1,2-2,2h-4c0,0-2,1-3,0l-3-1c-1,0-3-1-3-2c-1-1,0-2-1-3l-1-3c-1-1-2-1-3-1c-1,0-4,
0-4-1c0-2,0-3-1-4v-3v-3z'/></a>
<path id="p3" d='M385,593c0,9-6,15-13,15c-7,0-13-6-13-15c0-8,12-39,14-39c1,0,12,31,12,39'/>
</g>
</svg>'''
self.viewer.set_svg(img)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 600, 400)
window.show()
sys.exit(app.exec_())
我认为 QGraphicsSvgItem 使用边界矩形作为它的形状。
一个可能的 hacky 但有效的解决方案是做 QGraphicsPixmapItem 所做的,即获取掩码并将 alpha 区域用于 return shape()
的元素。
请注意,我没有设置 ItemClipsToShape 标志,因为不需要它。
我还根据 Qt implementation.
实施了正确的选择大纲
class SvgItem(QGraphicsSvgItem):
def __init__(self, id, renderer, parent=None):
super().__init__(parent)
self.id = id
self.setSharedRenderer(renderer)
self.setElementId(id)
bounds = renderer.boundsOnElement(id)
self.setPos(bounds.topLeft())
self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable)
# create an image based on the item size
img = QtGui.QImage(bounds.size().toSize(), QtGui.QImage.Format_ARGB32)
# clear its buffer (this is important!)
img.fill(QtCore.Qt.transparent)
# create a qpainter and ask the renderer to render it
qp = QtGui.QPainter(img)
renderer.render(qp, id)
qp.end()
# create the mask by adding a QRegion based on it
mask = img.createAlphaMask()
shape = QtGui.QPainterPath()
shape.addRegion(QtGui.QRegion(QtGui.QBitmap.fromImage(mask)))
# a QBitmap based region can be unnecessarily complex, let's
# simplify it
self._shape = shape.simplified()
def shape(self):
return self._shape
def paint(self, qp, option, widget):
# keep track of the selected state and call the base painting
# implementation without it
selected = option.state & QtWidgets.QStyle.State_Selected
option.state &= ~QtWidgets.QStyle.State_Selected
super().paint(qp, option, widget)
if selected:
# draw the selection based on the shape, using the right
# amount of contrast with the background
fgcolor = option.palette.windowText().color()
bgcolor = QtGui.QColor(
0 if fgcolor.red() > 127 else 255,
0 if fgcolor.green() > 127 else 255,
0 if fgcolor.blue() > 127 else 255,
)
qp.setPen(QtGui.QPen(bgcolor, 0, QtCore.Qt.SolidLine))
qp.setBrush(QtCore.Qt.NoBrush)
qp.drawPath(self._shape)
qp.setPen(QtGui.QPen(option.palette.windowText(), 0, QtCore.Qt.DashLine))
qp.setBrush(QtCore.Qt.NoBrush)
qp.drawPath(self._shape)
下面的标志没有按照它的意思剪裁形状。相反,它会创建一个矩形选区。
self.setFlag(self.ItemClipsToShape)
如何让选区剪辑到形状?
test.svg
<svg viewBox='0 0 108 95'
xmlns='http://www.w3.org/2000/svg'>
<g transform='scale(0.1)'>
<path id="p2" fill='blue' stroke='red' d='M249,699v43h211v-43h-64l-2,3l-2,4l-4,3c0,0-1,2-2,2h-4c-2,0-3,0-4,1c-1,1-3,1-3,
2l-3,4c0,1-1,2-2,2h-4c0,0-2,1-3,0l-3-1c-1,0-3-1-3-2c-1-1,0-2-1-3l-1-3c-1-1-2-1-3-1c-1,0-4,
0-4-1c0-2,0-3-1-4v-3v-3z'/>
<path id="p3" fill='blue' d='M385,593c0,9-6,15-13,15c-7,0-13-6-13-15c0-8,12-39,14-39c1,0,12,31,12,39'/>
</g>
</svg>
svg_mouse.py
from PyQt5 import QtWidgets
from PyQt5.QtSvg import QGraphicsSvgItem, QSvgRenderer
class SvgItem(QGraphicsSvgItem):
def __init__(self, id, renderer, parent=None):
super().__init__(parent)
self.id = id
self.setSharedRenderer(renderer)
self.setElementId(id)
bounds = renderer.boundsOnElement(id)
self.setPos(bounds.topLeft())
#self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True) #horrible selection-box
def mousePressEvent(self, event: 'QtWidgets.QGraphicsSceneMouseEvent'):
print('svg item: ' + self.id + ' - mousePressEvent()')
super().mousePressEvent(event)
def mouseReleaseEvent(self, event: 'QtWidgets.QGraphicsSceneMouseEvent'):
print('svg item: ' + self.id + ' - mouseReleaseEvent()')
super().mouseReleaseEvent(event)
class SvgViewer(QtWidgets.QGraphicsView):
def __init__(self, parent):
super().__init__(parent)
self._scene = QtWidgets.QGraphicsScene(self)
self._renderer = QSvgRenderer()
self.setScene(self._scene)
def set_svg(self, data):
self.resetTransform()
self._scene.clear()
self._renderer.load(data)
item1 = SvgItem('p2', self._renderer)
self._scene.addItem(item1)
item2 = SvgItem('p3', self._renderer)
self._scene.addItem(item2)
def mousePressEvent(self, event: 'QtWidgets.QGraphicsSceneMouseEvent'):
print('SvgViewer - mousePressEvent()')
super().mousePressEvent(event)
def mouseReleaseEvent(self, event: 'QtWidgets.QGraphicsSceneMouseEvent'):
print('SvgViewer - mouseReleaseEvent()')
super().mouseReleaseEvent(event)
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.viewer = SvgViewer(self)
vb_layout = QtWidgets.QVBoxLayout(self)
vb_layout.addWidget(self.viewer)
img = b'''
<svg viewBox='0 0 108 95' xmlns='http://www.w3.org/2000/svg'>
<g transform='scale(0.1)'>
<a href=""><path id="p2" d='M249,699v43h211v-43h-64l-2,3l-2,4l-4,3c0,0-1,2-2,2h-4c-2,0-3,0-4,1c-1,1-3,1-3,
2l-3,4c0,1-1,2-2,2h-4c0,0-2,1-3,0l-3-1c-1,0-3-1-3-2c-1-1,0-2-1-3l-1-3c-1-1-2-1-3-1c-1,0-4,
0-4-1c0-2,0-3-1-4v-3v-3z'/></a>
<path id="p3" d='M385,593c0,9-6,15-13,15c-7,0-13-6-13-15c0-8,12-39,14-39c1,0,12,31,12,39'/>
</g>
</svg>'''
self.viewer.set_svg(img)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 600, 400)
window.show()
sys.exit(app.exec_())
我认为 QGraphicsSvgItem 使用边界矩形作为它的形状。
一个可能的 hacky 但有效的解决方案是做 QGraphicsPixmapItem 所做的,即获取掩码并将 alpha 区域用于 return shape()
的元素。
请注意,我没有设置 ItemClipsToShape 标志,因为不需要它。
我还根据 Qt implementation.
class SvgItem(QGraphicsSvgItem):
def __init__(self, id, renderer, parent=None):
super().__init__(parent)
self.id = id
self.setSharedRenderer(renderer)
self.setElementId(id)
bounds = renderer.boundsOnElement(id)
self.setPos(bounds.topLeft())
self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable)
# create an image based on the item size
img = QtGui.QImage(bounds.size().toSize(), QtGui.QImage.Format_ARGB32)
# clear its buffer (this is important!)
img.fill(QtCore.Qt.transparent)
# create a qpainter and ask the renderer to render it
qp = QtGui.QPainter(img)
renderer.render(qp, id)
qp.end()
# create the mask by adding a QRegion based on it
mask = img.createAlphaMask()
shape = QtGui.QPainterPath()
shape.addRegion(QtGui.QRegion(QtGui.QBitmap.fromImage(mask)))
# a QBitmap based region can be unnecessarily complex, let's
# simplify it
self._shape = shape.simplified()
def shape(self):
return self._shape
def paint(self, qp, option, widget):
# keep track of the selected state and call the base painting
# implementation without it
selected = option.state & QtWidgets.QStyle.State_Selected
option.state &= ~QtWidgets.QStyle.State_Selected
super().paint(qp, option, widget)
if selected:
# draw the selection based on the shape, using the right
# amount of contrast with the background
fgcolor = option.palette.windowText().color()
bgcolor = QtGui.QColor(
0 if fgcolor.red() > 127 else 255,
0 if fgcolor.green() > 127 else 255,
0 if fgcolor.blue() > 127 else 255,
)
qp.setPen(QtGui.QPen(bgcolor, 0, QtCore.Qt.SolidLine))
qp.setBrush(QtCore.Qt.NoBrush)
qp.drawPath(self._shape)
qp.setPen(QtGui.QPen(option.palette.windowText(), 0, QtCore.Qt.DashLine))
qp.setBrush(QtCore.Qt.NoBrush)
qp.drawPath(self._shape)