6.6 组合模式绘图
组合模式(Composition Mode)用来指定如何合并源图像和另一个图像。最常见的是SourceOver(通常也叫Alpha混合),当源像素和目标像素以这种方式混合时,源图像的Alpha通道定义了像素的透明度。
组合模式绘图只支持Format_ARGB32_Premultiplied和Format_ARGB32格式,而且应该优先使用Format_ARGB32_Premultiplied格式。设置了组合模式后,它对所有的绘图操作都有效,如画笔、画刷、渐变效果和pixmap/image绘制。
QPainter::CompositeMode枚举类型中前12种组合类型是由T.Porter和T.Duff于1984年在论文《Compositing Digital Images》中阐明的12条混合规则(Porter-Duff规则)。混合的计算方法在此给出,以便理解混合的过程。
首先定义混合因子:
● AS:源像素的alpha分量
● Cs:源像素中计算好的(premultiplied)色彩分量
● Ad:目标像素的alpha分量
● Cd:目标像素中计算好的色彩分量
● Fs:源像素在输出结果中占有的比例
● Fd:目标像素在输出结果中占有的比例
● Ar:输出结果中的Alpha分量
● Cr:输出结果中的计算好的色彩分量
Porter和Duff定义了选择混合因子Fs和Fd产生不同的视觉效果的12种规则。最终结果中的Alpha值和色彩由下面的公式决定:
Fs=f(Ad)
Fd=f(As)
Ar=As×Fs+Ad×Fd
Cr=Cs×Fs+Cd×Fd
每种类型Fs和Fd取值如表6-6所示。
表6-6 Porter-Duff规则
需要注意,上面的说明并没有完全概括各种混合的含义,要准确理解它们可以看公式并进行实践。除了上面12条Porter-Duff规则外,Qt还支持12种扩展混合模式。下面给出计算公式,需要注意如果计算结果中alpha值和色彩值超过0~255的范围,数值将会被截断。
1.QPainter::CompositionMode_Plus
源和目标相加。该操作可以实现动画中两幅图像的溶解过渡效果。
Cr=Cs+Cd
Ar=As+Ad
2.QPainter::CompositionMode_Multiply
源和目标进行正片叠底(multiply)操作。结果的颜色至少是源和目标中较暗的颜色。任何颜色和黑色做该操作,会产生黑色,任何颜色和白色进行正片叠底操作将不会改变。
Cr=Cs×Cd+Cs×(1-Ad)+Cd×(1-As)
Ar=As×Ad+As×(1-Ad)+Ad×(1-As)=As+Ad-As×Ad
3.QPainter::CompositionMode_Screen
源和目标互补然后相乘。结果的颜色至少是源和目标中较亮的颜色。因为任何颜色和黑色进行滤色(screen)操作不会改变,任何颜色和白色进行滤色操作还是白色。
Cr=Cs+Cd-Cs×Cd
Ar=As+Ad-As×Ad
4.QPainter::CompositionMode_Overlay
根据目标颜色值的不同,进行相乘或滤色操作。源色彩保持亮度和阴影覆盖在目标上。目标颜色和源颜色混合以反映目标的亮度。
5.QPainter::CompositionMode_Darken
选择源和目标中较暗的颜色。
6.QPainter::CompositionMode_Lighten
选择源和目标中较亮的颜色。
7.QPainter::CompositionMode_ColorDodge
加亮目标颜色以反映源色彩。绘制黑色将没有效果。
8.QPainter::CompositionMode_ColorBurn
使目标颜色变暗以反映源色彩。绘制白色将没有效果。
9.QPainter::CompositionMode_HardLight
根据源的颜色,决定是正片叠底还是滤色操作。如果源颜色亮度值高于0.5(亮度取值范围为0~1),目标将变亮,即进行了滤色操作。如果源颜色亮度值低于0.5,目标将会变暗,相当于进行了正片叠底操作。如果源亮度值等于0.5,目标不发生改变。变亮或变暗的程度取决于源色彩和0.5的差值。绘制纯黑或纯白结果还是纯黑或纯白。
10.QPainter::CompositionMode_SoftLight
根据源的颜色,决定进行变暗(darken)操作还是变亮(lighten)操作。如果源颜色比0.5亮,目标将变亮,即进行了滤色操作。如果源颜色比0.5暗,目标将会变暗,相当于进行了颜色加深(burn)操作。如果等于0.5,目标不发生改变。变亮或变暗的程度取决于源色彩和0.5的差值。
11.QPainter::CompositionMode_Difference
源和目标中较暗的颜色减去较亮的颜色。绘制白色导致反转目标颜色,绘制黑色没有任何变化。
12.QPainter::CompositionMode_Exclusion
和上一条规则的效果类似,但对比度要低一些。绘制白色导致反转目标颜色,绘制黑色没有任何变化。
下面通过一个例子来观察不同模式的绘图效果。例子中目标图像使用的图像文件中,源图形使用一个可调颜色和Alpha值的矩形。用户可以自由选择组合模式和源图像,以便观察混合效果。
用来绘图的窗口部件定义如下:
class CompositionCanvas : public QWidget { Q_OBJECT public: CompositionCanvas(QWidget *parent = 0, Qt::WindowFlags f = 0); public slots: void paintEvent(QPaintEvent *event); void mousePressEvent(QMouseEvent *event); void compositeModeChanged(int currentRow); void alphaChanged(int value); void redChanged(int value); void greenChanged(int value); void blueChanged(int value); private: QPoint pos; QImage srcImage; QImage dstImage; QImage resultImage; QPainter::CompositionMode currentMode; QColor color; };
这里定义了三个QImage对象:srcImage, dstImage和resultImage,分别表示源图像、目标图像和结果图像。pos表示目标图像绘制的左上角位置。定义了mousePressEvent()响应鼠标事件,根据鼠标点击的位置移动目标图像位置。compositeModeChanged()槽响应组合模式改变的信号并重绘。alphaChanged(), redChanged(), greenChanged()和blueChanged()分别响应Alpha和红、绿、蓝三种颜色分量变化并重绘。
绘图窗口部件的构造函数实现如下:
CompositionCanvas::CompositionCanvas(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f) { pos.setX(400); pos.setY(550); setMinimumSize(700,700); srcImage = QImage(1000,1000, QImage::Format_ARGB32_Premultiplied); resultImage = QImage(1000,1000, QImage::Format_ARGB32_Premultiplied); srcImage.load("build.jpg"); dstImage = QImage(100,100, QImage::Format_ARGB32_Premultiplied); currentMode = QPainter::CompositionMode_SourceOver; color = QColor(255,0,0,255); }
在构造函数中对srcImage、dstImage 和resultImage 进行了初始化。同时还初始化了一些控制变量。
接下来的paintEvent()事件是运用了组合模式绘图,代码如下:
void CompositionCanvas::paintEvent(QPaintEvent *event) { QPainter painter(&resultImage); painter.fillRect(resultImage.rect(), Qt::transparent); painter.drawImage(0, 0, srcImage); painter.setCompositionMode(currentMode); dstImage.fill(color.rgb()); painter.drawImage(pos, dstImage); QPainter p(this); p.drawImage(0,0,resultImage); }
paintEvent()中使用了当前的组合模式将两个QImage对象混合后在屏幕上显示。这是因为组合模式只能在QImage绘图设备上进行绘制,而不能直接在QWidget上绘制,所以程序在QImage对象上绘制好图形后再贴到QWidget。
下面的函数对组合模式、Alpha值、红、绿、蓝分量改变进行响应。
void CompositionCanvas::compositeModeChanged(int currentRow) { currentMode = (QPainter::CompositionMode)currentRow; update(); } void CompositionCanvas::alphaChanged(int value) { color.setAlpha(value); update(); } void CompositionCanvas::redChanged(int value) { color.setRed(value); update(); } void CompositionCanvas::greenChanged(int value) { color.setGreen(value); update(); } void CompositionCanvas::blueChanged(int value) { color.setBlue(value); update(); } void CompositionCanvas::mousePressEvent(QMouseEvent *event) { pos = event->pos(); update(); }
完成绘制代码后,还需要定义一个QWidget包含各种控件和绘图控件,定义如下:
class CompositionWidget : public QWidget { Q_OBJECT public: CompositionWidget(QWidget *parent = 0, Qt::WindowFlags f = 0); private: QHBoxLayout *hLayout; QGridLayout *gridLayout; QLabel *aLabel; QLabel *rLabel; QLabel *gLabel; QLabel *bLabel; QSlider *aSlider; QSlider *rSlider; QSlider *gSlider; QSlider *bSlider; QListWidget *list; CompositionCanvas *canvas; };
为了控制Alpha、红、绿、蓝颜色分量,定义了四个QSlider对象。使用了一个QListWidget来选择各种组合模式。对于CompositionWidget类,只需要实现其构造函数:
CompositionWidget::CompositionWidget(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f) { QStringList compositionList; compositionList << "Source Over" << "Destionation Over" << "Clear" << "Source" << "Destination" << "Source In" << "Destination In" << "Source Out" << "Destination Out" << "Source Atop" << "Destionation Atop" << "Xor" << "Plus" << "Multiply" << "Screen" << "Overylay" << "Darken" << "Lighten" << "ColorDodge" << "ColorBurn" << "HardLight" << "SoftLight" << "Difference" << "Exclusion"; list = new QListWidget; list->addItems( compositionList ); canvas = new CompositionCanvas; aLabel = new QLabel(tr("<b>Alpha:</b>")); rLabel = new QLabel(tr("<b>Red:</b>")); gLabel = new QLabel(tr("<b>Green:</b>")); bLabel = new QLabel(tr("<b>Blue:</b>")); aSlider = new QSlider; aSlider->setOrientation(Qt::Horizontal); aSlider->setMinimum(0); aSlider->setMaximum(255); aSlider->setValue(255); rSlider = new QSlider; rSlider->setOrientation(Qt::Horizontal); rSlider->setMinimum(0); rSlider->setMaximum(255); rSlider->setValue(255); gSlider = new QSlider; gSlider->setOrientation(Qt::Horizontal); gSlider->setMinimum(0); gSlider->setMaximum(255); gSlider->setValue(0); bSlider = new QSlider; bSlider->setOrientation(Qt::Horizontal); bSlider->setMinimum(0); bSlider->setMaximum(255); bSlider->setValue(0); gridLayout = new QGridLayout; gridLayout->addWidget(aLabel, 0, 0); gridLayout->addWidget(aSlider, 0, 1); gridLayout->addWidget(rLabel, 1, 0); gridLayout->addWidget(rSlider, 1, 1); gridLayout->addWidget(gLabel, 2, 0); gridLayout->addWidget(gSlider, 2, 1); gridLayout->addWidget(bLabel, 3, 0); gridLayout->addWidget(bSlider, 3, 1); gridLayout->addWidget(list, 4, 0, 15, 2); hLayout = new QHBoxLayout; hLayout->addWidget(canvas); hLayout->addLayout(gridLayout); setLayout(hLayout); connect(list, SIGNAL(currentRowChanged(int)), canvas, SLOT(compositeModeChanged(int))); connect(aSlider, SIGNAL(valueChanged(int)), canvas, SLOT(alphaChanged(int))); connect(rSlider, SIGNAL(valueChanged(int)), canvas, SLOT(redChanged(int))); connect(gSlider, SIGNAL(valueChanged(int)), canvas, SLOT(greenChanged(int))); connect(bSlider, SIGNAL(valueChanged(int)), canvas, SLOT(blueChanged(int))); }
构造函数生成了各种控件,并将QSlider对象的值限定在0~255之间,最后进行了信号和槽的连接。
最后完成main()函数。
int main(int argc, char **argv) { QApplication app(argc, argv); CompositionWidget compWidget; compWidget.show(); return app.exec(); }
程序的运行界面如图6-16所示,图中的矩形处显示了叠加的效果。
图6-16 组合模式绘图
用户可以改变各种模式和ARGB值来测试混合效果,最好还能将背景图替换进行测试。