精通Qt4编程
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

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定义了选择混合因子FsFd产生不同的视觉效果的12种规则。最终结果中的Alpha值和色彩由下面的公式决定:

Fs=f(Ad)

Fd=f(As)

Ar=As×Fs+Ad×Fd

Cr=Cs×Fs+Cd×Fd

每种类型FsFd取值如表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值来测试混合效果,最好还能将背景图替换进行测试。