从图像构造QPaint路径

2024-04-25 18:25:32 发布

您现在位置:Python中文网/ 问答频道 /正文

以这张图片为例:

enter image description here

是否可以从图像构建QPainterPath(路径为黑色形状/轮廓)

我知道您可以使用QPainterPath方法(quadTocubicTolineTo等-尽管很难准确地重新创建)手动定义此形状,并设置具有适当宽度的QPen。但是我想知道是否有任何方法,通过读取像素数据或其他方式,基于黑色像素定义QPainterPath

我的目标是填充形状的内部(显示在GUI中),我非常喜欢使用QPaint而不是整体填充图像,因为:

  • 它比洪水快100倍
  • 线是抗锯齿的(因此泛光填充会在轮廓周围留下白色/灰色像素)
  • 泛光填充仅限于纯色,而QPaint可以使用纹理、图案和渐变进行填充

Tags: 方法图像路径定义图片像素手动轮廓
1条回答
网友
1楼 · 发布于 2024-04-25 18:25:32

正如在评论和(可能的)duplicate question中所解释的,Qt没有用于光栅图像矢量化的方法

除了手动重新创建路径外,对于具有较大边界的简单图像(如示例中的图像),还可以获得更好的“泛洪”结果

显然,问题在于图像内部(以及外部,如果您想使用不同的颜色)的抗锯齿。
诀窍是执行光栅图像处理程序通常执行的操作,即基于颜色创建遮罩,并扩展遮罩“羽化”它

在Qt术语中,这可以通过小椭圆(2像素宽)实现,该椭圆位于作为遮罩一部分的像素中间。由于无法知道遮罩的边界,我们需要循环通过该区域的所有像素,并在点位于该区域内时绘制平滑像素

如您所见,结果相当不错,但仍远远不够完美(简单遮罩是中间图像,而伪抗锯齿在右侧):

masking and pseudo-antialiasing

这是一个显示整个过程的示例,包括简单遮罩和“平滑”像素技巧:

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_())

您可以单击任何图像的外部以选择外部颜色,单击内部以选择背景颜色

相关问题 更多 >