我目前正在使用 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 不仅会清除第二个图像的区域,还会清除底层图像的区域,从而导致第二张图片的黑色背景,这样:
我正在向社区寻求关于如何有效地实现所需效果的建议或想法,尤其是在图像旋转时。任何指导将不胜感激,因为我已经坚持了很长一段时间了。
提前致谢!
仅使用
mapFromItem()
可能会在累积变换时产生一些问题,我的建议是始终将所有内容映射到场景,然后将结果重新映射回项目。
这个概念是创建一个 QPainterPath,它是所有碰撞项的所有 scene 多边形的结果,使用当前项的边界矩形的完整路径(也映射到场景),然后分两遍绘制像素图:
这是 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()