如何在Qt QGraphicsScene中裁剪未闭合路径

3 投票
1 回答
1392 浏览
提问于 2025-04-17 09:41

我有一个QGraphicsView,里面包含了QGraphicsScene,我需要在一个由rect定义的有限区域内绘制不封闭的路径(这些路径可能包含直线或贝塞尔曲线)。

对于封闭的路径,有一个简单的方法可以使用QPainterPath.intersected(path)函数来裁剪,但如果path是不封闭的,intersected会把它封闭(也就是从路径的末尾画一条线到起点)。

下面是一个简化的代码,展示了我的问题:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import functools
import sys
from PySide.QtCore import *
from PySide.QtGui import *

def path_through_points(points):
    path = QPainterPath()
    path.moveTo(*points[0])
    for x, y in points[1:]:
        path.lineTo(x, y)
    return path

def path_through_points_workaround(points):
    return path_through_points(points + list(reversed(points)))

if __name__ == "__main__":
    app = QApplication(sys.argv)
    scene = QGraphicsScene()
    view = QGraphicsView(scene)
    rect = QRectF(0, 0, 300, 300)

    clip = QPainterPath()
    clip.addRect(rect)

    points = [(50, 50), (100, 100), (500, 300)]

    def test_draw(path):
        scene.clear()
        scene.addRect(rect)
        scene.addPath(path)

    unclosed_path = path_through_points(points)
    closed_path = path_through_points_workaround(points)

    QTimer.singleShot(0, functools.partial(test_draw, unclosed_path))
    QTimer.singleShot(2000, functools.partial(test_draw, unclosed_path.intersected(clip)))
    QTimer.singleShot(4000, functools.partial(test_draw, closed_path.intersected(clip)))

    view.resize(640, 480)
    view.show()
    sys.exit(app.exec_())

它绘制了生成的路径:

  1. 没有裁剪。
  2. 进行了裁剪(路径被封闭,而不是单纯裁剪)——这对我来说是不可接受的。
  3. 最后,我想要得到的结果(但这是通过变通方法实现的)。

变通方法:通过反向绘制来封闭path。但是我的path可能包含很多直线和贝塞尔曲线,所以这样做效率不高,而且双重线条的抗锯齿效果看起来很糟糕。

所以我的问题是,如何在不改变路径生成逻辑的情况下,在QGraphicsScene中裁剪不封闭的路径或线条?

更新

现在我使用以下函数:

def clipped_path(path, min_x, min_y, max_x, max_y):
    """ Returns clipped path, supports unclosed paths of any kind
    (lines, beziers)

    NOTE: Resulting path can loose antialiasing
    """
    path.connectPath(path.toReversed())
    clip = QPainterPath()
    clip.addRect(QRectF(min_x, min_y, max_x, max_y))
    return path.intersected(clip)

更新 2

更好的方法是@Hello W建议的:

class ClippedItemMixin(object):
    def __init__(self, min_x, min_y, max_x, max_y):
        self._clip_path = QtGui.QPainterPath()
        self._clip_path.addRect(QtCore.QRectF(min_x, min_y, max_x, max_y))
        super(ClippedItemMixin, self).__init__()

    def paint(self, painter, *args, **kwargs):
        painter.setClipPath(self._clip_path)
        super(ClippedItemMixin, self).paint(painter, *args, **kwargs)


class ClippedPathItem(ClippedItemMixin, QtGui.QGraphicsPathItem):
   pass

现在看起来更好,因为抗锯齿效果正常工作。

1 个回答

1

你可以试试从 QGraphicsPathItem 这个类继承一个新类,然后重新实现它的绘制方法。在这个方法里,你可以调用 painter.setClipRect() 来设置绘图区域。

撰写回答