1. Qt 图像合成模式(一) Composition_Modes_Demo
Qt的图像的合成模式部分讲得太简单了,给出了一个简单的案例,还没有代码,demo里有个很全面的案例,但是又太复杂了,让人看得心生厌烦。尝试总结一些过度性的知识,并为自己梳理一下相关的内容。
1.1 官方档案内容摘要
-
QPainter提供了CompositionMode枚举,该枚举定义了用于数字图像合成的 Porter-Duff 规则; 它描述了一种模型,用于将一个图像( 源)中的像素与另一图像( 目标)中的像素进行组合。
-
几种合成模式需要在源图像或目标图像中使用alpha通道才能生效。
-
为了获得最佳性能,首选格式Format_ARGB32_Premultiplied。任何其他格式,包括QImage :: Format_ARGB32,都具有明显较差的性能。 默认情况下,此引擎用于 QWidget 和 QPixmap。
1.2 enum QPainter::CompositionMode
Constant | Value | Description |
---|---|---|
QPainter::CompositionMode_SourceOver | 0 | 这是默认模式。源的Alpha用于在目标顶部混合像素。 |
QPainter::CompositionMode_DestinationOver | 1 | 目标的Alpha用于将其混合在源像素的顶部。此模式与CompositionMode_SourceOver相反。 |
QPainter::CompositionMode_Clear | 2 | 独立于源,清除目标中的像素( 将其设置为完全透明)。 |
QPainter::CompositionMode_Source | 3 | 输出是源像素。 ( 这意味着基本的复制操作,并且在源像素不透明时与SourceOver相同)。 |
QPainter::CompositionMode_Destination | 4 | 输出是目标像素。这意味着混合无效。此模式与CompositionMode_Source相反。 |
QPainter::CompositionMode_SourceIn | 5 | 输出是源,其中的alpha值将减去目标的alpha值。 |
QPainter::CompositionMode_DestinationIn | 6 | 输出是目标,其中的alpha值被源的alpha值减小。此模式与CompositionMode_SourceIn相反。 |
QPainter::CompositionMode_SourceOut | 7 | 输出是源,其中alpha减小了目标的倒数。 |
QPainter::CompositionMode_DestinationOut | 8 | 输出是目标,其中的alpha值由源的倒数减少。此模式与CompositionMode_SourceOut相反。 |
QPainter::CompositionMode_SourceAtop | 9 | 源像素在目标顶部混合,源像素的alpha减少了目标像素的alpha。 |
QPainter::CompositionMode_DestinationAtop | 10 | 目标像素混合在源的顶部,目标像素的alpha减小目标像素的alpha。此模式与CompositionMode_SourceAtop相反。 |
QPainter::CompositionMode_Xor | 11 | 源( 其alpha值与目标alpha值的倒数成反比)与目标合并,该目标的alpha值与源alpha的倒数成反比。 CompositionMode_Xor与按位Xor不同。 |
QPainter::CompositionMode_Plus | 12 | 源像素和目标像素的alpha和颜色都被加在一起。 |
QPainter::CompositionMode_Multiply | 13 | 输出是源颜色乘以目标颜色。将颜色与白色相乘会使颜色保持不变,而颜色与黑色相乘则会产生黑色。 |
QPainter::CompositionMode_Screen | 14 | 将源颜色和目标颜色反转,然后相乘。用白色屏蔽颜色会产生白色,而用黑色屏蔽颜色会使颜色保持不变。 |
QPainter::CompositionMode_Overlay | 15 | 根据目标颜色乘以或筛选颜色。将目标颜色与源颜色混合以反映目标的明暗。 |
QPainter::CompositionMode_Darken | 16 | 选择了较深的源颜色和目标颜色。 |
QPainter::CompositionMode_Lighten | 17 | 选择了源色和目标色的浅色。 |
QPainter::CompositionMode_ColorDodge | 18 | 目标颜色变亮以反映源颜色。黑色源颜色使目标颜色保持不变。 |
QPainter::CompositionMode_ColorBurn | 19 | 目标颜色变暗以反映源颜色。白色源颜色使目标颜色保持不变。 |
QPainter::CompositionMode_HardLight | 20 | 根据源颜色乘以或筛选颜色。光源颜色将使目标颜色变亮,而光源源颜色将使目标颜色变暗。 |
QPainter::CompositionMode_SoftLight | 21 | 根据源颜色使颜色变暗或变亮。类似于CompositionMode_HardLight。 |
QPainter::CompositionMode_Difference | 22 | 从较浅的颜色中减去较深的颜色。用白色绘画会反转目标颜色,而使用黑色绘画会使目标颜色保持不变。 |
QPainter::CompositionMode_Exclusion | 23 | 与CompositionMode_Difference相似,但对比度较低。用白色绘画会反转目标颜色,而使用黑色绘画会使目标颜色保持不变。 |
QPainter::RasterOp_SourceOrDestination | 24 | 对源像素和目标像素( src OR dst)进行按位或运算。 |
QPainter::RasterOp_SourceAndDestination | 25 | 对源像素和目标像素( src AND dst)进行按位与运算。 |
QPainter::RasterOp_SourceXorDestination | 26 | 对源像素和目标像素进行按位XOR操作( src XOR dst)。 |
QPainter::RasterOp_NotSourceAndNotDestination | 27 | 对源像素和目标像素进行按位“或非”运算( ( NOT src)AND( NOT dst))。 |
QPainter::RasterOp_NotSourceOrNotDestination | 28 | 在源像素和目标像素上执行按位与非运算( ( NOT src)OR( NOT dst))。 |
QPainter::RasterOp_NotSourceXorDestination | 29 | 进行按位运算,其中将源像素反转,然后与目标( ( NOT src)XOR dst)进行异或。 |
QPainter::RasterOp_NotSource | 30 | 在源像素反转的情况下执行按位运算( NOT src)。 |
QPainter::RasterOp_NotSourceAndDestination | 31 | 进行按位运算,其中源反转,然后与目标( ( NOT src)AND dst)进行“与”运算。 |
QPainter::RasterOp_SourceAndNotDestination | 32 | 执行按位运算,其中源与反转的目标像素( src AND( NOT dst))进行“与”运算。 |
QPainter::RasterOp_NotSourceOrDestination | 33 | 进行按位运算,其中源反转,然后与目标( ( NOT src)OR dst)进行“或”运算。 |
QPainter::RasterOp_ClearDestination | 35 | 清除目标中的像素( 设置为0),与源无关。 |
QPainter::RasterOp_SetDestination | 36 | 目标中的像素独立于源进行设置( 设置为1)。 |
QPainter::RasterOp_NotDestination | 37 | 在目标像素反转的情况下进行按位运算( 不是dst)。 |
QPainter::RasterOp_SourceOrNotDestination | 34 | 执行按位运算,其中源与反转的目标像素进行“或”运算( src OR( NOT dst))。 |
1.3 山寨Qt合成模式图片的代码
在Qt的示例中,死活没有找到与如下图片相关的代码,然后自己写了一个,以假乱真,可以供参考。
代码很简单,就一个QWidget做界面,在上面 绘制 目标图像 + 源图像。
class CompositionWidget : public QWidget
#include "CompositionWidget.h"
static const int side = 15;
static const int IMG_Width = 9 * side;
static const int IMG_Height = 12 * side;
static int cm[12] = {
3,4,0,1,5,6,
7,8,9,10,2,11
};
QString topic[12]={
"Source","Destination","SourceOver","DestinationOver","SourceIn","DestinationIn",
"SourceOut","DestinationOut","SourceAtop","DestinationAtop","Clear","Xor"
};
CompositionWidget::CompositionWidget(QWidget *parent)
: QWidget(parent)
{
resize((IMG_Width+10)*6+10,(IMG_Height+10) *2+10);
}
void CompositionWidget::paintEvent(QPaintEvent */*event*/)
{
QImage SourceIMG(IMG_Width,IMG_Height,QImage::Format_ARGB32);
QImage DestIMG(IMG_Width,IMG_Height,QImage::Format_ARGB32);
QPolygon destPolygon,sourcePolygon;
destPolygon << QPoint(0,0) << QPoint(IMG_Width,0) << QPoint(0,IMG_Width);
sourcePolygon << QPoint(0,0) << QPoint(IMG_Width,0) << QPoint(IMG_Width,IMG_Width);
QImage BackIMG(IMG_Width,IMG_Height,QImage::Format_ARGB32);
BackIMG.fill(Qt::white);
QPainter painter;
QFont font("times new roman",11,75);
painter.begin(&BackIMG);
painter.setBrush(QColor(239,239,239));
painter.setPen(Qt::NoPen);
for (int x= 0; x< IMG_Width; x+=2*side) {
for (int y = 0,row = 0; y < IMG_Height; y+=side,row++) {
painter.drawRect(x + (row % 2) * side,y,side,side);
}
}
painter.end();
for (int i = 0; i < 12; ++i) {
SourceIMG.fill(0);
DestIMG.fill(0);
painter.begin(&SourceIMG);
painter.setPen(Qt::NoPen);
painter.setBrush(QColor(98,136,61));
painter.drawPolygon(sourcePolygon);
painter.end();
painter.begin(&DestIMG);
painter.save();
painter.setPen(Qt::NoPen);
painter.setBrush(QColor(220,157,57));
painter.drawPolygon(destPolygon);
painter.setCompositionMode(QPainter::CompositionMode(cm[i]));
painter.drawImage(0,0,SourceIMG);
painter.restore();
painter.setPen(Qt::black);
painter.setFont(font);
painter.setRenderHint(QPainter::TextAntialiasing);
painter.drawText(QRect(0,0,IMG_Width,IMG_Height-10),Qt::AlignBottom | Qt::AlignHCenter,topic[i]);
painter.end();
painter.begin(this);
painter.drawImage( (IMG_Width+10)* (i % 6)+10,(IMG_Height+10)*(i/6)+10,BackIMG);
painter.drawRect((IMG_Width+10)* (i % 6)+10,(IMG_Height+10)*(i/6)+10,IMG_Width,IMG_Height);
painter.drawImage( (IMG_Width+10)* (i % 6)+10,(IMG_Height+10)*(i/6)+10,DestIMG);
painter.end();
}
}
1.4 Porter-Duff 规则
1984 年 7 月《计算机图形》杂志 18 期 253 - 259 页,刊登了Porter Thomas 和 Duff Tom 的论文《Compositing Digital Ima-ges》,文中讲述了12个合成规则,这些规则都是基于一些简单的数学等式。
文中提出一个重要的alpha channel的概念,并定义了源图像(正在绘制的基元)和目标图像(图形区域)的像素的alpha分量和颜色的值。
注意:分量和通道(Components and Channels)。颜色由三个值编码,这些值也称为 分量 或 通道。软件中最普通的编码称为RGB,它使用红色、绿色和蓝色分量。Yuv是另外一种编码 方式,它使用一个亮度通道(Y)和两个色度通道(u和v)。alpha通道(或alpha分量)是第四种分量,独立于颜色编码之外,它定义颜色的半透明或 不透明的级别。
下面的内容,是借用了 《Filthy Rich Clients: Developing Animated and Graphical Effects for Desktop JAVA Applications》中文名《java极富客户端》书中的内容来写的。书中总结归纳的很简单一看就懂。
下面的Porter和Duff的12个规则,每个规则都有一个简短的公式,每个案例都描述了在一个蓝色矩形(目标图像)上绘制一个红色椭圆的图像(源图像)。
1.4.1 Source Over (源覆盖)
这是默认模式。源的Alpha用于在目标顶部混合像素。
要决定在什么时候使用什么规则,理解文档中提到的Porter-Duff等式很重要。为了避免使您对数据描述讨厌得要死(还因为我对考虑这12个等式也很 头痛),让我们集中在最有用的规则之一 uSource Over (源覆盖)”上,它在目标图像之上绘制源图像,源图像好像是覆盖在目标图像上的一片玻璃上的一个半透明图画。
描述这个规则的等式 如下所示:
Ar = As + Ad * ( 1 - As )
Cr = Cs + Cd * ( 1 - As )
因子 A 代表这个像素的alpha通道,C代表这个像素的每个颜色分量。
下标 r、s 和 d分别 代表这个像素的 结果(result)、源 (source) 和 目标 (destination)。
As 代表(绘制到图形区域的基元) 源 的alpha通道,
Ad 代表已经在图形区域上的像素的alpha 通道。
Ar 最终计算结果的alpha通道。这个等式中的所有值都是在0.0和1.0之间的 浮点数,计算结果也位于这个范围。
**注意1:**在实际代码中,每个分量都是一个在0到255之间的整数值,而不是0. 0和1. 0之间的浮点数。
**注意2:**预乘的分量。注意到Porter-Duff等式都定义为操作与相应的alpha分量预乘的颜色 分量,这很重要。
例、如果我们在一个蓝色矩形上绘制一个半透明的红色矩形,那将会发生什么?让我们着手把这些等式写成完全由每个颜色分量代表的代码:
int main(int argc,char *argv[])
{
int srcA = 127 ; //semi-opaque source 半透明源
int srcR =255 ; //full red 全部红色
int srcG = 0 ; //no green 没有绿色
int srcB = 0 ; //no blue 没有蓝色
int dstA = 255 ; //fully opaque destination 完全不透明的目白勺地
int dstR = 0 ; //no red 没有红色
int dstG = 0 ; //no green 没有绿色
int dstB =255 ; //full blue 全部蓝色
srcR = (srcR * srcA) /255 ; //premultiply srcR 预乘 srcA
srcG = (srcG * srcA) /255 ; //premultiply srcR 预乘 srcA
srcB = (srcB * srcA) /255 ; //premultiply srcR 预乘 srcA
dstR = (dstR * dstA) /255 ; //premultiply dstR 预乘 dstR
dstG = (dstG * dstA) /255 ; //premultiply dstG 预乘 dstG
dstB = (dstB * dstA) /255 ; //premultiply dstB 预乘 dstB
int resultA = srcA + (dstA * (255 - srcA) ) /255;
int resultR = srcR + (dstR * (255 - srcA) ) /255;
int resultG = srcG + (dstG * (255 - srcA) ) /255;
int resultB = srcB + (dstB * (255 - srcA) ) /255;
qDebug() << resultA << resultR << resultG << resultB;
return 0;
}
生成的结果 : 255 127 0 128
生成的颜色为完全不透明的洋红色,就像把一张半透明的红色纸片放在蓝色背景上一样。
1.4.2 Clear
独立于源,清除目标中的像素( 将其设置为完全透明)。
描述这个规则的等式 如下所示:
Ar =0
Cr =0
目标中的颜色和alpha都被清除掉。无论用什么颜色或形状来绘制,被源覆盖的每个目的地 的像素都会消失,如图所示。
1.4.3 Destination
输出是目标像素。这意味着混合无效。此模式与CompositionMode_Source相反。
Ar = Ad
Cr = Cd
1.4.4 DestinationAtop
目标像素混合在源的顶部,目标像素的alpha减小目标像素的alpha。此模式与CompositionMode_SourceAtop相反。
Ar = As * ( 1 - Ad ) + Ad * As ) = As
Cr = Cs * ( 1 - Ad ) + Cd * As
1.4.5 DestinationIn
输出是目标,其中的alpha值被源的alpha值减小。此模式与CompositionMode_SourceIn相反。
Ar = Ad * As
Cr = Cd * As
位于源内部的那部分目的地取代目标。它与DstOut相反,但是,如果使用50%的alpha 值,两种操作看起来一样。
1.4.6 DestinationOut
输出是目标图像,其中的alpha值由源的A值而减少。此模式与SourceOut相反。
Ar = Ad * ( 1 - As )
Cr = Cd * ( 1- As )
位于源外部的那部分目的地取代目标。它与DstIn相反,但是,如果使用50%的alpha 值,两种操作看起来一样。
1.4.7 DestinationOver
目标的Alpha用于将其混合在源像素的顶部。此模式与SourceOver相反。
Ar = As * ( 1 - Ad ) + Ad )
Cr = Cs * ( 1 - Ad ) + Cd
目的地与源组合,然后用这个结果取代目标。位于目标之外的那部分源通常用这个合成的累加的不透明度绘制。
1.4.8 Source
输出是源像素。 ( 这意味着基本的复制操作,并且在源像素不透明时与SourceOver相同)。
Ar = As
Cr = Cs
源复制到目的地。目的地由源来代替。在图中,蓝色矩形(目的地)没有显示在红色 椭圆下面,因为红色椭圆(源)代替了它。
1.4.9 SourceAtop
源像素在目标顶部混合,源像素的alpha减少了目标像素的alpha。
Ar = As * Ad + Ad * ( 1 - As ) = Ad
Cr = Cs * Ad + Cd * ( 1 - As )
位于目标内部的那部分源与目标组合。位于目的地之外的那部分源丢弃掉。
1.4.10 SourceIn
输出是源,其中的alpha值将减去目标的alpha值。
Ar = As * Ad
Cr = Cs * Ad
位于目的地内部的那部分源代替目的地。位于目的地之外的那部分源丢弃掉。
1.4.11 SourceOut
输出是源,其中目标的alpha值减小了最终的A值。
Ar = As * ( 1 - Ad )
Cr = Cs * ( 1 - Ad )
位于目的地之外的那部分源代替目的地。位于目的地内部的那部分源丢弃掉。
1.4.12 Xor
源( 其alpha值与目标alpha值的负数)与目标合并,该目标的alpha值与源alpha的倒数成反比。 CompositionMode_Xor与按位Xor不同。
Ar = As * ( 1 - Ad ) + Ad * (1 - As)
Cr = Cs * ( 1 - Ad ) + Cd * (1 - As)
位于目的地之外的那部分源与位于源之外的那部分目的地结合。
书中的12种模式已经讲完,但Qt种还有12种混合模式,以及14种二元光栅操作模式。