如何在 Qt 图形项中实现半透明重叠区域?

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

我目前正在使用 Qt 开发图像处理软件,并且遇到了客户的特定要求。在我的应用程序中,我有一个自定义的 QGraphicsScene,其中包含多个图像,每个图像都表示为自定义的 QGraphicsPixmapItem。客户的要求是,当其中两个图像重叠时,重叠区域应该是半透明的,透明度为 50%。

我探索了几种方法,但遇到了一些障碍。最初,我尝试使用 Qt 的合成模式,但它们都没有提供我想要的确切效果 - 即在重叠区域实现 50% 的透明度。

这是我第一次尝试的代码:


 void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override
    {
        QGraphicsPixmapItem::paint(painter, option, widget);

        painter->setRenderHint(QPainter::Antialiasing);

        QList<QGraphicsItem *> collidingItems = this->collidingItems();

        for (auto item : qAsConst(collidingItems)) {

            if (item == this)
                continue;

            if (QGraphicsPixmapItem *otherItem = qgraphicsitem_cast<QGraphicsPixmapItem *>(item)) {

                // Evaluate intersection between two items in local coordinates
                QPainterPath path = this->shape().intersected(this->mapFromItem(otherItem, otherItem->shape()));
                QPainterPath otherPath = otherItem->shape().intersected(otherItem->mapFromItem(this, this->shape()));

                if (!path.isEmpty() && !otherPath.isEmpty()) {

                    QRectF thisBoundingRect = path.boundingRect();
                    QRectF otherBoundingRect = otherPath.boundingRect();

                    // Create two pixmap of the overlapping section
                    QPixmap thisPixmap = this->pixmap().copy(thisBoundingRect.toRect());
                    QPixmap otherPixmap = otherItem->pixmap().copy(otherBoundingRect.toRect());

                    // Clear overlapping section
                    painter->save();
                    painter->fillPath(path, Qt::black);

                    painter->setClipPath(path);

                    // Redraw both the pixmaps with opacity at 0.5
                    painter->setOpacity(0.65);
                    painter->drawPixmap(path.boundingRect().topLeft(), thisPixmap);
                    painter->drawPixmap(path.boundingRect().topLeft(), otherPixmap);
                    painter->restore();
                }
            }
        }
    }

不旋转时的结果(这正是我想要的):

涉及旋转时的结果:

当图像不旋转时,上面的代码可以按预期工作,但是当旋转开始发挥作用时,事情就会变得棘手。

为了避免与转换相关的问题,特别是在旋转图像时,我决定修改我的方法。这是新代码:

void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override
    {
        QGraphicsPixmapItem::paint(painter, option, widget);

        painter->setRenderHint(QPainter::Antialiasing);

        QList<QGraphicsItem *> collidingItems = this->collidingItems();

        for (auto item : qAsConst(collidingItems)) {
            if (item == this)
                continue;

            if (CustomGraphicsPixmapItem *otherItem = qgraphicsitem_cast<CustomGraphicsPixmapItem *>(item)) {
                // Evaluate intersection between two items in local coordinates
                QPainterPath path = this->shape().intersected(this->mapFromItem(otherItem, otherItem->shape()));

                if (!path.isEmpty()) {
                    QRectF thisBoundingRect = path.boundingRect();

                    // Create two pixmap of the overlapping section
                    QPixmap thisPixmap = this->pixmap().copy(thisBoundingRect.toRect());

                    // Clear overlapping section
                    painter->save();

                    // Set the composition mode to clear and then draw with SourceOver
                    painter->setCompositionMode(QPainter::CompositionMode_Clear);
                    painter->fillPath(path, Qt::transparent);
                    painter->setCompositionMode(QPainter::CompositionMode_SourceOver);

                    painter->setOpacity(0.5);
                    painter->drawPixmap(thisBoundingRect.topLeft(), thisPixmap);

                    painter->restore();
                }
            }
        }
    } 

但是,使用这种修改后的方法,我面临的问题是,当两个图像重叠时,第二个图像的 CompositionMode_Clear 不仅会清除第二个图像的区域,还会清除底层图像的区域,从而导致第二张图片的黑色背景,这样:

我正在向社区寻求关于如何有效地实现所需效果的建议或想法,尤其是在图像旋转时。任何指导将不胜感激,因为我已经坚持了很长一段时间了。

提前致谢!

c++ qt image-processing qpixmap qgraphicspixmapitem
1个回答
0
投票

仅使用

mapFromItem()
可能会在累积变换时产生一些问题,我的建议是始终将所有内容映射到场景,然后将结果重新映射回项目。

这个概念是创建一个 QPainterPath,它是所有碰撞项的所有 scene 多边形的结果,使用当前项的边界矩形的完整路径(也映射到场景),然后分两遍绘制像素图:

  1. 用完整路径减去碰撞多边形设置剪辑路径,并以完全不透明度绘制;
  2. 设置剪辑路径,使碰撞多边形完全相交,然后以一半不透明度绘制;

这是 PyQt 中的一个基本示例,但我相信您可以轻松地将其转换为 C++。

class OverlapPixmapItem(QGraphicsPixmapItem):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setFlag(self.ItemIsMovable)
        self.setTransformationMode(Qt.SmoothTransformation)
        self.setTransformOriginPoint(self.boundingRect().center())

    def wheelEvent(self, event):
        if event.orientation() == Qt.Vertical:
            rot = self.rotation()
            if event.delta() > 0:
                rot += 5
            else:
                rot -= 5
            self.setRotation(rot)
        else:
            super().wheelEvent(event)

    def paint(self, qp, opt, widget=None):
        colliding = []
        for other in self.collidingItems():
            if isinstance(other, OverlapPixmapItem):
                colliding.append(other)

        if not colliding:
            super().paint(qp, opt, widget)
            return

        qp.save()
        collisions = QPolygonF()
        for other in colliding:
            collisions = collisions.united(
                other.mapToScene(other.boundingRect()))
        collisionPath = QPainterPath()
        collisionPath.addPolygon(collisions)

        fullPath = QPainterPath()
        fullPath.addPolygon(self.mapToScene(self.boundingRect()))

        qp.setClipPath(self.mapFromScene(fullPath.subtracted(collisionPath)))
        super().paint(qp, opt, widget)
        qp.setClipPath(self.mapFromScene(fullPath.intersected(collisionPath)))
        qp.setOpacity(.5)
        super().paint(qp, opt, widget)
        qp.restore()
© www.soinside.com 2019 - 2024. All rights reserved.