如何仅对 QGraphicsEllipseItem 中需要的部分进行着色

问题描述 投票:0回答:1

我正在尝试在 QGraphicsScene 内将圆(QGraphicsEllipseItem)着色为 25% 蓝色、25% 透明、25% 红色、25% 透明。我正在努力这样做。我尝试做这样的事情:

class CircleItem(QGraphicsEllipseItem):
    def __init__(self, x=0, y=0, radius=0):
        super(CircleItem, self).__init__(x - radius, y - radius, radius * 2, radius * 2)
        self.setPen(QPen(Qt.red, 2))

        gradient = QRadialGradient(self.boundingRect().center(), radius)
        gradient.setColorAt(0, Qt.blue)
        gradient.setColorAt(0.25, Qt.transparent)
        gradient.setColorAt(0.5, Qt.red)
        gradient.setColorAt(0.75, Qt.transparent)
        self.setBrush(QBrush(gradient))

但它并没有真正填充任何东西,有没有办法按照我想要的方式画圆圈?就像图片一样

pyqt
1个回答
0
投票

您使用了错误的渐变类型。

QRadialGradient 使用不同的 radius 值传播渐变,如 QGradient 文档中所示:

Image of a Qt radial gradient

你需要的是一个QConicalGradient

Image of a Qt conical gradient

然后,即使使用正确的渐变类型,它也不会按照您想要的给定值显示圆的“部分”:您使用的是 gradient,因此它的颜色在其 stops 之间逐渐变化。

更改渐变类型后(并使用默认的 0

angle
值),如下所示:

        gradient = QConicalGradient(self.boundingRect().center(), 0)
        gradient.setColorAt(0, Qt.blue)
        gradient.setColorAt(0.25, Qt.transparent)
        gradient.setColorAt(0.5, Qt.red)
        gradient.setColorAt(0.75, Qt.transparent)

结果仍然不合适,这是因为渐变使用颜色插值,这是渐变的主要目的:

一个可能的解决方案是在颜色变化旁边使用窄值,例如:

        gradient = QConicalGradient(self.boundingRect().center(), 0)
        gradient.setColorAt(0, Qt.blue)
        gradient.setColorAt(0.2499, Qt.blue)
        gradient.setColorAt(0.25, Qt.transparent)
        gradient.setColorAt(0.4999, Qt.transparent)
        gradient.setColorAt(0.5, Qt.red)
        gradient.setColorAt(0.7499, Qt.red)
        gradient.setColorAt(0.75, Qt.transparent)

这要好得多,因为几乎没有插值:

仍然不够。即使停靠点之间的差异很小,在更高的分辨率下也可能会出现问题:

注意顶部的小工件。如果渐变具有不同的角度并进行以下更改,这一点会更加明显:

        gradient = QConicalGradient(self.boundingRect().center(), 15)

注意

15
值,该值会将渐变逆时针旋转 15°。

即使使用抗锯齿功能,渐变仍然存在锯齿:

请注意,设置项目的旋转不会解决问题。

只有通过自定义绘画才能获得更好的视觉效果,这涉及到 QGraphicsEllipseItem 子类,该子类在内部设置“切片”并自行覆盖绘画。

这种方法可能看起来有点复杂,但它也是正确显示所必需的,并且允许进一步的可扩展性(例如多种颜色或每种颜色的不同范围)。

class SmoothCircleItem(QGraphicsEllipseItem):
    _colors = None
    _colorAngle = 0
    _colorRanges = None
    def __init__(self, x=0, y=0, radius=1, colors=None, angle=0):
        super().__init__(x - radius, y - radius, radius * 2, radius * 2)
        self.setPen(QPen(Qt.red, 2))
        if colors is None:
            colors = (Qt.blue, Qt.transparent, Qt.red, Qt.transparent)
        self.setColors(colors, angle)

    def setColors(self, colors, angle=None):
        # acceptable values for "colors" are lists or tuples of:
        # - QColor, Qt.GlobalColor (or their int values)
        # - list/tuples of [color, position]
        if isinstance(colors[0], (QColor, Qt.GlobalColor, int)):
            # accept an arbitrary list of colors that splits the ellipse 
            # assigning identical parts to each color
            _colors = []
            ratio = 1 / len(colors)
            pos = 0
            for c in colors:
                if not isinstance(c, QColor):
                    c = QColor(c)
                _colors.append((c, pos))
                pos += ratio
            colors = _colors

        else:
            # accept iterables in the form of [color, pos], with positions
            # in real ranges between 0.0 and 1.0
            colors = sorted(
                [(c, p % 1.) for c, p in colors], 
                key=lambda cd: cd[1]
            )

        # create an internal list of colors in the following form:
        # [color, start, extent]
        first = colors[0]
        last = [first[0], first[1] % 1.]
        self._colors = [last]
        for color, pos in colors[1:]:
            last.append((pos - last[-1]))
            colorData = [color, pos]
            self._colors.append(colorData)
            last = colorData
        if len(colors) > 1:
            last.append(1. + first[1] - last[-1])
        else:
            last.append(1.)

        if self._colorAngle == angle:
            self._update()
        else:
            self.setColorAngle(angle)

    def setColorAngle(self, angle):
        angle = angle % 360
        if self._colorAngle != angle:
            self._colorAngle = angle
            self._update()

    def _update(self):
        if self._colorAngle:
            # adjust the start values based on the angle
            realAngle = self._colorAngle / 360
            values = [(c, (s + realAngle) % 1, e) for c, s, e in self._colors]
        else:
            values = self._colors

        # update angles using QPainter angles, which are in sixteenths
        self._colorRanges = [
            (c, int(s * 5760), int(e * 5760)) for c, s, e in values
        ]
        self.update()

    def paint(self, qp, opt, widget=None):
        rect = self.rect()
        qp.save()
        qp.setPen(Qt.NoPen)
        for color, start, extent in self._colorRanges:
            qp.setBrush(color)
            qp.drawPie(rect, start, extent)
        qp.restore()
        super().paint(qp, opt, widget)

从下面的比较中,视觉差异非常明显(第一个是基于 QGradient 的项目,第二个是上面具有相似值的

SmoothCircleItem
,而第三个显示了具有不同颜色数据的可能扩展:

© www.soinside.com 2019 - 2024. All rights reserved.