最近遇到了一些这样的需求,在窗口可以调节显示图片的透明度,但是不能影响其他图片。一个窗口显示的图片并不是一张,而是多张通过绘制的形式叠加起来的。可以理解为类似图层。
就像下面这个组合一样,想法是在拖动右侧透明度的滑条的时候,只修改上层图像的透明度,并不会修改底层背景图的透明度。
先看下预期效果:
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::ImageShow
和 DrawTool::DrawToolData
,是为了方便保存一些图像绘制过程中的变量。可以直接理解为成员变量。也可以参考我的上一篇博客 Qt QImage scaled方法缩放中的问题, 里面有相应的解释。
经过测试,最后的这种方法,可以达到我们的目标,并且并不会修改源图,只是在视觉上给我们一种图像透明度的错觉。在实际应用过程中可能会比较实用。