QImage 如何设置图片的透明度

文章介绍了如何在Qt环境中,通过QPainter的CompositionMode来实现对窗口中多张图片叠加显示时,单独调整某一层图片的透明度,而不影响其他图层。作者尝试了多种方法,包括模拟透明度、直接修改源图像像素颜色以及使用多个QPainter对象,最终找到了一个既能达到视觉效果又不会改变源图像的方法。
摘要由CSDN通过智能技术生成

最近遇到了一些这样的需求,在窗口可以调节显示图片的透明度,但是不能影响其他图片。一个窗口显示的图片并不是一张,而是多张通过绘制的形式叠加起来的。可以理解为类似图层。

就像下面这个组合一样,想法是在拖动右侧透明度的滑条的时候,只修改上层图像的透明度,并不会修改底层背景图的透明度。

先看下预期效果:
在这里插入图片描述

1、通过 QPainter::CompositionMode_DestinationIn 模式模拟显示透明度

偶然的通过了解 QPainter::CompositionMode 的过程中发现了 QPainter::CompositionMode_DestinationIn,帮助文档中解释为:

The output is the destination, where the alpha is reduced by that of the source. This mode is the inverse of CompositionMode_SourceIn.

输出的是目标,并且其alpha的值与绘制源的相关。初次就有了下面的想法:

void ImageWidget::paintEvent(QPaintEvent *event)
{
    QSize sizeImg = size();
    QImage imgBack(sizeImg, QImage::Format_ARGB32);
    imgBack.fill(Qt::black);

    DrawTool::ImageShow img = m_img;
    DrawTool::DrawToolData op = m_operate;

    QImage imgDraw(img.size, QImage::Format_ARGB32);
    imgDraw.fill(Qt::transparent);
    m_draw = imgDraw.scaled(img.size * m_dScale, Qt::KeepAspectRatio, Qt::FastTransformation);

    QPainter painter;
    painter.begin(&imgBack);
    painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
    painter.drawImage(m_paintPt, m_show);
    painter.drawImage(m_paintPt, m_mask);
    painter.drawImage(m_paintPt, m_draw);

    painter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
    painter.fillRect(imgBack.rect(), QColor(0, 0, 0, op.diaphaneity));
    painter.end();

    painter.begin(this);
    painter.setCompositionMode(QPainter::CompositionMode_Source);
    painter.drawImage(QPoint(0, 0), imgBack);
    painter.end();
}

上面这个绘制函数的过程是,先在一张纯黑的图像上以 QPainter::CompositionMode_SourceOver 的模式绘制三张图像,然后修改 QPainter 的 CompositionMode 为 QPainter::CompositionMode_DestinationIn,绘制并填充一个矩形,填充的颜色设定了我们前面说的透明度,并且这个透明度的值是可以动态修改的。

经过测试发现,这样的操作会将整个窗口的透明度都修改。效果如下:
在这里插入图片描述
虽然只是修改了窗口的透明程度,并不会实际的修改图片的通明度。但与我们的目标想不好像多了一点。所以这并不是最佳答案。显然得重新找答案了。

2、修改源图像的像素颜色透明度

既然是要修改图片的透明度,那么我们是不是可以直接修改图片每个像素的透明度呢?

void ImageWidget::setImageAlpha(QImage& img, int val)
{
    for (int r = 0, wd = img.width(); r < wd; ++r)
    {
        for (int c = 0, ht = img.height(); c < ht; ++c)
        {
            QColor color = img.pixelColor(r, c);
            if (color == Qt::transparent)
            {
                continue;
            }
            color.setAlpha(val);
            img.setPixelColor(r, c, color);
        }
    }
}

像上面这样,每次在绘制的时候先修改目标图片的透明度,然后再将图片绘制到屏幕上。

if (oldOp.diaphaneity != newOp.diaphaneity)
{
    DrawTool::ImageShow img = m_img;
    QImage maskImg = m_maskOri;
    setImageAlpha(maskImg, newOp.diaphaneity);
    m_mask = maskImg.scaled(img.size * m_dScale, Qt::KeepAspectRatio, Qt::FastTransformation);
}

这种情况下,绘制过程中设置绘制模式为 QPainter::CompositionMode_DestinationIn 并填充矩形框的操作就没有什么实际的意义了。需要注释掉。

//painter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
//painter.fillRect(imgBack.rect(), QColor(0, 0, 0, op.diaphaneity));

测试之后,发现这种想法能够满足我们的目标。并且绘制过程也很丝滑。效果如下:

在这里插入图片描述

但是在后续的测试中发现,通过上面这种粗暴的方式设置了源图的透明度之后,再重新从图中读取像素颜色,像素颜色会有一定的误差。并且和透明度的大小存在一定的关系,但不是线性的。

当透明度在 50% 左右的时候,我发现,颜色会有一种这样的关系:

newRgb = 255 -255 - rgb)*double)a / 255

这个公式对R、G、B都适用,计算出来的结果有误差(是因为有了浮点数),但不影响。但是如果当a越小或者越大的时候,这个误差会增加。因此这个公式肯定是错的。

所以通过这中直接设置源图像透明底的方法,如果设置之后不在乎他的颜色,只是为了显示效果,是完全可以的,但如果它的像素颜色对你有用,或者你要在某些时候恢复它不透明,会有一定的影响。

3、通过 QPainter::CompositionMode_DestinationIn 模式和多个QPainter对象实现

想到这个方法是由上面第一种方式反思得到的,既然可以通过第一种方法模拟得到一个图像的透明度,那么为什么不能通过两个QPainter对象来实现了,一个专门用来实现针对单张图像的透明度,另一个用来实现正常的绘制流程呢?

但是在考虑这种方法的时候,需要注意的一点是:

Qt 中 是不允许两个QPainter对象同时进行绘制的,因此要注意他们之间的绘制顺序关系。

下面这个方法用来绘制一张图像的透明度。

void ImageWidget::drawMaskAlpha(QImage& img)
{
    DrawTool::DrawToolData op = m_operate;
    QImage back(m_mask.size(), QImage::Format_ARGB32);
    back.fill(Qt::transparent);
    img = back;
    QPainter pter(&img);
    pter.setCompositionMode(QPainter::CompositionMode_Source);
    pter.drawImage(0, 0, m_mask);
    pter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
    pter.fillRect(back.rect(), QColor(0, 0, 0, op.diaphaneity));
    pter.end();
}
void ImageBaseWidget::paintEvent(QPaintEvent *event)
{
    QImage imgBack(size(), QImage::Format_ARGB32);
    imgBack.fill(Qt::black);

    DrawTool::ImageShow img = m_img;
    DrawTool::DrawToolData op = m_operate;

    QImage imgDraw(img.size, QImage::Format_ARGB32);
    imgDraw.fill(Qt::transparent);
    m_draw = imgDraw.scaled(img.size * m_dScale, Qt::KeepAspectRatio, Qt::FastTransformation);

    QImage mask;
    drawMaskAlpha(mask);

    QPainter painter;
    painter.begin(&imgBack);
    painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
    painter.drawImage(m_paintPt, m_show);
    painter.drawImage(m_paintPt, m_draw);
    painter.drawImage(m_paintPt, mask);
    
    painter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
    painter.fillRect(imgBack.rect(), QColor(0, 0, 0, op.diaphaneity));
    painter.end();
    
    painter.begin(this);
    painter.setCompositionMode(QPainter::CompositionMode_Source);
    painter.drawImage(QPoint(0, 0), imgBack);
    painter.end();
}

上面代码中出现的 DrawTool::ImageShowDrawTool::DrawToolData,是为了方便保存一些图像绘制过程中的变量。可以直接理解为成员变量。也可以参考我的上一篇博客 Qt QImage scaled方法缩放中的问题, 里面有相应的解释。

经过测试,最后的这种方法,可以达到我们的目标,并且并不会修改源图,只是在视觉上给我们一种图像透明度的错觉。在实际应用过程中可能会比较实用。

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值