如何在 QGraphicsItems 中实现半透明重叠区域?

问题描述 投票: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个回答
3
投票

仅使用

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())) # draw the pixmap only where it has no colliding items qp.setClipPath(self.mapFromScene(fullPath.subtracted(collisionPath))) super().paint(qp, opt, widget) # draw the collision parts with half opacity 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.