从图像构造 QPainterPath
Construct a QPainterPath from an image
以此图为例:
是否可以从图像构造一个QPainterPath(路径是黑色形状/轮廓)?
我知道您可以使用 QPainterPath 方法手动定义此形状(quadTo
、cubicTo
、lineTo
等 — 尽管很难准确地重新创建)并设置 QPen具有适当的宽度。但我想知道是否有任何方法,通过读取像素数据或其他方式,来定义基于黑色像素的 QPainterPath。
我的目标是填充形状的内部(显示在 GUI 中),并且我非常喜欢使用 QPainter 来完成此操作而不是填充图像,因为:
- 它比洪水填充快 100 倍。
- 线条经过抗锯齿处理(因此,填充填充在轮廓周围留下 white/grey 像素)。
- 洪水填充仅限于纯色,而 QPainter 可以填充纹理、图案和渐变。
如评论和(可能) 中所述,Qt 没有光栅图像矢量化方法。
除了手动重新创建路径,更好的“泛洪”结果可以为具有大边框的简单图像(如示例中的那些)实现。
显然,问题是图像内部的抗锯齿(以及外部的抗锯齿,如果您想使用不同的颜色)。
诀窍是做光栅图像处理程序通常做的事情,即根据颜色创建一个蒙版,并扩展该蒙版“羽化”它。
在 Qt 术语中,这可以通过以作为蒙版一部分的像素中间为中心的小椭圆(2 像素宽)来实现。由于无法知道蒙版的边界,我们需要循环遍历该区域的 所有 像素,并在点位于该区域内时绘制平滑像素。
如您所见,效果还不错,但还远未达到完美(简单遮罩是中间图像,而伪抗锯齿在右侧):
这是一个显示整个过程的示例,包括简单的遮罩和“平滑”像素技巧:
from PyQt5 import QtGui, QtCore, QtWidgets
class Test(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QHBoxLayout(self)
self.sourceLabel = QtWidgets.QLabel()
layout.addWidget(self.sourceLabel)
self.maskedLabel = QtWidgets.QLabel()
layout.addWidget(self.maskedLabel)
self.targetLabel = QtWidgets.QLabel()
layout.addWidget(self.targetLabel)
self.border = QtGui.QColor(QtCore.Qt.red)
self.color = QtGui.QColor(QtCore.Qt.green)
self.sourceLabel.installEventFilter(self)
self.maskedLabel.installEventFilter(self)
self.targetLabel.installEventFilter(self)
self.processPixmap('cloud.png')
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.MouseButtonPress:
self.setColor(event.pos() in self.innerMask)
return super().eventFilter(source, event)
def setColor(self, inside):
dialog = QtWidgets.QColorDialog(self.color, self)
if dialog.exec_():
if inside:
self.color = dialog.currentColor()
else:
self.border = dialog.currentColor()
self.processPixmap()
def processPixmap(self, path=None):
if path is None:
path = self.path
self.path = path
self.sourceLabel.setPixmap(QtGui.QPixmap(path))
source = QtGui.QImage(path)
fullMask = source.createHeuristicMask()
fullMaskRegion = QtGui.QRegion(QtGui.QBitmap(fullMask))
outColor = source.pixel(0, 0)
borderMask = source.createMaskFromColor(outColor)
borderMaskRegion = QtGui.QRegion(QtGui.QBitmap(borderMask))
self.innerMask = fullMaskRegion - borderMaskRegion
outerMask = fullMaskRegion + borderMaskRegion
masked = QtGui.QPixmap(source.size())
masked.fill(self.border)
qp = QtGui.QPainter(masked)
qp.setClipRegion(fullMaskRegion)
qp.drawImage(0, 0, source)
qp.setClipRegion(self.innerMask)
qp.fillRect(source.rect(), self.color)
qp.end()
self.maskedLabel.setPixmap(masked)
t = QtCore.QElapsedTimer()
t.start()
output = QtGui.QPixmap(source.size())
output.fill(QtCore.Qt.transparent)
qp = QtGui.QPainter(output)
qp.setRenderHints(qp.Antialiasing)
qp.save()
qp.setClipRegion(fullMaskRegion)
qp.fillRect(source.rect(), self.border)
qp.drawImage(0, 0, source)
qp.restore()
qp.setPen(QtCore.Qt.NoPen)
pixel = QtCore.QRectF(-.5, -.5, 2, 2)
for rowPixel in range(output.height()):
for colPixel in range(output.width()):
p = QtCore.QPoint(colPixel, rowPixel)
if p in self.innerMask:
qp.setBrush(self.color)
qp.drawEllipse(pixel.translated(p))
if not p in outerMask:
qp.setBrush(self.border)
qp.drawEllipse(pixel.translated(p))
qp.end()
self.targetLabel.setPixmap(output)
print('Antialiasing finished in {}ms'.format(t.elapsed()))
import sys
app = QtWidgets.QApplication(sys.argv)
w = Test()
w.show()
sys.exit(app.exec_())
您可以点击任何图像的外部 select 外部颜色,点击内部 select 背景颜色。
以此图为例:
是否可以从图像构造一个QPainterPath(路径是黑色形状/轮廓)?
我知道您可以使用 QPainterPath 方法手动定义此形状(quadTo
、cubicTo
、lineTo
等 — 尽管很难准确地重新创建)并设置 QPen具有适当的宽度。但我想知道是否有任何方法,通过读取像素数据或其他方式,来定义基于黑色像素的 QPainterPath。
我的目标是填充形状的内部(显示在 GUI 中),并且我非常喜欢使用 QPainter 来完成此操作而不是填充图像,因为:
- 它比洪水填充快 100 倍。
- 线条经过抗锯齿处理(因此,填充填充在轮廓周围留下 white/grey 像素)。
- 洪水填充仅限于纯色,而 QPainter 可以填充纹理、图案和渐变。
如评论和(可能)
除了手动重新创建路径,更好的“泛洪”结果可以为具有大边框的简单图像(如示例中的那些)实现。
显然,问题是图像内部的抗锯齿(以及外部的抗锯齿,如果您想使用不同的颜色)。
诀窍是做光栅图像处理程序通常做的事情,即根据颜色创建一个蒙版,并扩展该蒙版“羽化”它。
在 Qt 术语中,这可以通过以作为蒙版一部分的像素中间为中心的小椭圆(2 像素宽)来实现。由于无法知道蒙版的边界,我们需要循环遍历该区域的 所有 像素,并在点位于该区域内时绘制平滑像素。
如您所见,效果还不错,但还远未达到完美(简单遮罩是中间图像,而伪抗锯齿在右侧):
这是一个显示整个过程的示例,包括简单的遮罩和“平滑”像素技巧:
from PyQt5 import QtGui, QtCore, QtWidgets
class Test(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QHBoxLayout(self)
self.sourceLabel = QtWidgets.QLabel()
layout.addWidget(self.sourceLabel)
self.maskedLabel = QtWidgets.QLabel()
layout.addWidget(self.maskedLabel)
self.targetLabel = QtWidgets.QLabel()
layout.addWidget(self.targetLabel)
self.border = QtGui.QColor(QtCore.Qt.red)
self.color = QtGui.QColor(QtCore.Qt.green)
self.sourceLabel.installEventFilter(self)
self.maskedLabel.installEventFilter(self)
self.targetLabel.installEventFilter(self)
self.processPixmap('cloud.png')
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.MouseButtonPress:
self.setColor(event.pos() in self.innerMask)
return super().eventFilter(source, event)
def setColor(self, inside):
dialog = QtWidgets.QColorDialog(self.color, self)
if dialog.exec_():
if inside:
self.color = dialog.currentColor()
else:
self.border = dialog.currentColor()
self.processPixmap()
def processPixmap(self, path=None):
if path is None:
path = self.path
self.path = path
self.sourceLabel.setPixmap(QtGui.QPixmap(path))
source = QtGui.QImage(path)
fullMask = source.createHeuristicMask()
fullMaskRegion = QtGui.QRegion(QtGui.QBitmap(fullMask))
outColor = source.pixel(0, 0)
borderMask = source.createMaskFromColor(outColor)
borderMaskRegion = QtGui.QRegion(QtGui.QBitmap(borderMask))
self.innerMask = fullMaskRegion - borderMaskRegion
outerMask = fullMaskRegion + borderMaskRegion
masked = QtGui.QPixmap(source.size())
masked.fill(self.border)
qp = QtGui.QPainter(masked)
qp.setClipRegion(fullMaskRegion)
qp.drawImage(0, 0, source)
qp.setClipRegion(self.innerMask)
qp.fillRect(source.rect(), self.color)
qp.end()
self.maskedLabel.setPixmap(masked)
t = QtCore.QElapsedTimer()
t.start()
output = QtGui.QPixmap(source.size())
output.fill(QtCore.Qt.transparent)
qp = QtGui.QPainter(output)
qp.setRenderHints(qp.Antialiasing)
qp.save()
qp.setClipRegion(fullMaskRegion)
qp.fillRect(source.rect(), self.border)
qp.drawImage(0, 0, source)
qp.restore()
qp.setPen(QtCore.Qt.NoPen)
pixel = QtCore.QRectF(-.5, -.5, 2, 2)
for rowPixel in range(output.height()):
for colPixel in range(output.width()):
p = QtCore.QPoint(colPixel, rowPixel)
if p in self.innerMask:
qp.setBrush(self.color)
qp.drawEllipse(pixel.translated(p))
if not p in outerMask:
qp.setBrush(self.border)
qp.drawEllipse(pixel.translated(p))
qp.end()
self.targetLabel.setPixmap(output)
print('Antialiasing finished in {}ms'.format(t.elapsed()))
import sys
app = QtWidgets.QApplication(sys.argv)
w = Test()
w.show()
sys.exit(app.exec_())
您可以点击任何图像的外部 select 外部颜色,点击内部 select 背景颜色。