Qt 图片合成例子
源码下载
本文取材自 Qt4.5 的官网 Qt Examples -> Painting -> Image Composition。本 demo 主要演示了在 Qt 中是如何选用合成模型将不同的图片合成在一起的。通过本 demo ,我们可以掌握 Qt 中拼接图片、合成图片、组合图片、制造图片以及给图片换背景等实际开发中需要的图片处理技术。
在学习本 demo 之前,我们先来回顾一下 signal 和 slot 的相关内容。
signal 和 slot 的基本知识
信号和槽是用来在对象间通讯的方法,当一个特定事件发生的时候,signal 会被发出,slot 调用是用来响应相应的 signal 的。
Qt 对象已经含了许多预定义的 signal,但我们总是可以在派生类中添加新的 signal。Qt 对象中也已经包含了许多预定义的 slot,但我们可以在派生类中添加新的 slot 来处理我们感兴趣的 signal。
signal 和 slot 机制是类型安全的,signal 和 slot 必须互相匹配(实际上,一个slot 的参数可以比对应的 signal 的参数少,因为它可以忽略多余的参数)。signal 和 slot 是松散的配对关系,发出 signal 的对象不关心是那个对象链接了这个 signa包l,也不关心是那个或者有多少 slot 链接到了这个 signal。Qt 的 signal 和 slot 机制保证了:如果一个 signal 和 slot 相链接,slot 会在正确的时机被调用,并且是使用正确的参数。signal 和 slot 都可以携带任何数量和类型的参数,他们都是类型安全的。
所有从 QObject 直接或者间接继承出来的类都能包含信号和槽,当一个对象的状态发生变化的时候,信号就可以被 emit 出来,这可能是某个其它的对象所关心的。这个对象并不关心有哪个对象或者多少个对象链接到这个信号了,这是真实的信息封装,它保证了这个对象可以作为一个软件组件来被使用。
槽( slot )是用来接收信号的,但同时他们也是一个普通的类成员函数,就象一个对象不关心有多少个槽链接到了它的某个信号,一个对象也不关心一个槽链接了多少个信号。这保证了用 Qt 创建的对象是一个真实的独立的软件组件。
一个信号可以链接到多个槽,一个槽也可以链接多个信号。同时,一个信号也可以链接到另外一个信号。
所有使用了信号和槽的类都必须包含 Q_OBJECT 宏,而且这个类必须从 QObject 类派生(直接或者间接派生)出来。
当一个 signal 被 emit 出来的时候,链接到这个 signal 的 slot 会立刻被调用,就好像是一个函数调用一样。当这件事情发生的时候,signal 和 slot 机制与 GUI 的事件循环完全没有关系,当所有链接到这个 signal 的 slot 执行完成之后,在 emit 代码行之后的代码会立刻被执行。当有多个 slot 链接到一个 signal 的时候,这些 slot 会一个接着一个的、以随机的顺序被执行。
signal 代码会由 moc(Meta-Object Compiler,元对象编译器,是处理 Qt 的 C++ 扩展的程序)自动生成,开发人员一定不能在自己的 C++ 代码中实现它,并且,它永远都不能有返回值。
slot 其实就是一个普通的类函数,并且可以被直接调用,唯一特殊的地方是它可以与signal相链接。
C++ 的预处理器更改或者删除 signal,slot,emit 关键字,所以,对于 C++ 编译器来说,它处理的是标准的 C++ 源文件。
复习完 signal 和 slot 的基本知识之后,我们开始按照官网介绍一步步创建 demo。
本 demo 里用到了两张图片:butterfly.png 和 checker.png。在 imagecomposition.qrc 文件中设置如下:
<!DOCTYPE RCC><RCC version="1.0"> <qresource> <file>images/butterfly.png</file> <file>images/checker.png</file> </qresource> </RCC>
更多资源文件的配置信息,请参阅 Qt 资源体系 。
ImageComposer 类定义
ImageComposer 类是 QWidget 的一个子类,它自定义了三个私有 slot 函数,chooseSource(), chooseDestination() 和 recalculateResult()。
class ImageComposer : public QWidget { Q_OBJECT public: ImageComposer(); private slots: void chooseSource(); void chooseDestination(); void recalculateResult();
另外,ImageComposer 还包括 5 个私有函数,addOp(),chooseImage(),loadImage(),currentMode() 和 imagePos(),以及几个 QToolButton,QComboBox,QLabel 和 QImage 的实例变量作为它的私有的成员变量。
private: void addOp(QPainter::CompositionMode mode, const QString &name); void chooseImage(const QString &title, QImage *image, QToolButton *button); void loadImage(const QString &fileName, QImage *image, QToolButton *button); QPainter::CompositionMode currentMode() const; QPoint imagePos(const QImage ℑ) const; QToolButton *sourceButton; QToolButton *destinationButton; QComboBox *operatorComboBox; QLabel *equalLabel; QLabel *resultLabel; QImage sourceImage; QImage destinationImage; QImage resultImage; };
ImageComposer 类的实现
我们先声明一个 QSize 的新实例 resultSize 作为一个静态变量,它的高度和宽度都是 200。
static const QSize resultSize(200, 200);
在构造方法中,我们实例化一个 QToolButton 的对象 sourceButton,并将其 iconSize 属性设置为 resultSize 。将 operatorComboBox 实例化,然后使用 addOp 方法(对 ImageComposer 进行填充)。此函数接受一个 QPainter::CompositionMode 的模型,并使用一个 QString 型的名字来标识不同的组成模式。
ImageComposer::ImageComposer() { sourceButton = new QToolButton; sourceButton->setIconSize(resultSize); operatorComboBox = new QComboBox; addOp(QPainter::CompositionMode_SourceOver, tr("SourceOver")); addOp(QPainter::CompositionMode_DestinationOver, tr("DestinationOver")); addOp(QPainter::CompositionMode_Clear, tr("Clear")); addOp(QPainter::CompositionMode_Source, tr("Source")); addOp(QPainter::CompositionMode_Destination, tr("Destination")); addOp(QPainter::CompositionMode_SourceIn, tr("SourceIn")); addOp(QPainter::CompositionMode_DestinationIn, tr("DestinationIn")); addOp(QPainter::CompositionMode_SourceOut, tr("SourceOut")); addOp(QPainter::CompositionMode_DestinationOut, tr("DestinationOut")); addOp(QPainter::CompositionMode_SourceAtop, tr("SourceAtop")); addOp(QPainter::CompositionMode_DestinationAtop, tr("DestinationAtop")); addOp(QPainter::CompositionMode_Xor, tr("Xor")); addOp(QPainter::CompositionMode_Plus, tr("Plus")); addOp(QPainter::CompositionMode_Multiply, tr("Multiply")); addOp(QPainter::CompositionMode_Screen, tr("Screen")); addOp(QPainter::CompositionMode_Overlay, tr("Overlay")); addOp(QPainter::CompositionMode_Darken, tr("Darken")); addOp(QPainter::CompositionMode_Lighten, tr("Lighten")); addOp(QPainter::CompositionMode_ColorDodge, tr("ColorDodge")); addOp(QPainter::CompositionMode_ColorBurn, tr("ColorBurn")); addOp(QPainter::CompositionMode_HardLight, tr("HardLight")); addOp(QPainter::CompositionMode_SoftLight, tr("SoftLight")); addOp(QPainter::CompositionMode_Difference, tr("Difference")); addOp(QPainter::CompositionMode_Exclusion, tr("Exclusion"));
将 destinationButton 实例化,其 iconSize 属性也被设置为 resultSize。将 QLabel 的实例变量 equalLabel 和 resultLabel 实例化,并将 resultLabel 的 minimumWidth 设置为 resultSize 的宽度,即 200。
destinationButton = new QToolButton; destinationButton->setIconSize(resultSize); equalLabel = new QLabel(tr("=")); resultLabel = new QLabel; resultLabel->setMinimumWidth(resultSize.width());
将下面的 signal 与对应的 slot 进行链接:
sourceButton 的 clicked() 信号被链接到 ImageComposer 的 chooseSource() 方法;
operatorComboBox 的 activated() 信号被链接到了 ImageComposer 的 recalculateResult() 方法;
destinationButton 的 clicked() 信号被链接到了 ImageComposer 的 chooseDestination() 方法。
connect(sourceButton, SIGNAL(clicked()), this, SLOT(chooseSource())); connect(operatorComboBox, SIGNAL(activated(int)), this, SLOT(recalculateResult())); connect(destinationButton, SIGNAL(clicked()), this, SLOT(chooseDestination()));
创建一个 QGridLayout 实例 mainLayout 用来装在所有的 Widget 对象。注意:mainLayout 的属性 sizeConstraint 被设置为 QLayout::SetFixedSize,意味着 ImageComposer 的大小不可变。
QGridLayout *mainLayout = new QGridLayout; mainLayout->addWidget(sourceButton, 0, 0, 3, 1); mainLayout->addWidget(operatorComboBox, 1, 1); mainLayout->addWidget(destinationButton, 0, 2, 3, 1); mainLayout->addWidget(equalLabel, 1, 3); mainLayout->addWidget(resultLabel, 0, 4, 3, 1); mainLayout->setSizeConstraint(QLayout::SetFixedSize); setLayout(mainLayout);
将 resultImage 实例化,两次调用 loadImage() 函数来加载我们定义在 imagecomposition.qrc 文件中的两个图片。 然后,我们将调用 QWidget 的 setWindowTitle 函数将 ImageComposer 的 windowTitle 属性设置为 “Image Composition”。
resultImage = QImage(resultSize, QImage::Format_ARGB32_Premultiplied); loadImage(":/images/butterfly.png", &sourceImage, sourceButton); loadImage(":/images/checker.png", &destinationImage, destinationButton); setWindowTitle(tr("Image Composition")); }
定义 chooseSource() 和 chooseDestination() 方法,它们使用了特别的参量用来调用接下来将被定义的 chooseImage() 方法。
void ImageComposer::chooseSource() { chooseImage(tr("Choose Source Image"), &sourceImage, sourceButton); } void ImageComposer::chooseDestination() { chooseImage(tr("Choose Destination Image"), &destinationImage, destinationButton); }
定义 chooseImage() 方法用来根据用户选择的名称,图片和按钮加载具体图片。
void ImageComposer::chooseImage(const QString &title, QImage *image, QToolButton *button) { QString fileName = QFileDialog::getOpenFileName(this, title); if (!fileName.isEmpty()) loadImage(fileName, image, button); }
定义 recalculateResult() 方法用来根据用户选择的合成模型来计算并显示两张图片合成的结果。
void ImageComposer::recalculateResult() { QPainter::CompositionMode mode = currentMode(); QPainter painter(&resultImage); painter.setCompositionMode(QPainter::CompositionMode_Source); painter.fillRect(resultImage.rect(), Qt::transparent); painter.setCompositionMode(QPainter::CompositionMode_SourceOver); painter.drawImage(0, 0, destinationImage); painter.setCompositionMode(mode); painter.drawImage(0, 0, sourceImage); painter.setCompositionMode(QPainter::CompositionMode_DestinationOver); painter.fillRect(resultImage.rect(), Qt::white); painter.end(); resultLabel->setPixmap(QPixmap::fromImage(resultImage)); }
addOp() 方法调用了 QComboBox 的 addItem 方法用来给 operatorComboBox 加入具体模型。这个方法接受一个 QPainter::CompositionMode 类型的模型和一个 QString 类型的名字作为参数。在此结果显示之前,矩形区域被 Qt::Transparent 填充,并且原图片和结果图片都被喷绘。
void ImageComposer::addOp(QPainter::CompositionMode mode, const QString &name) { operatorComboBox->addItem(name, mode); }
loadImage() 方法使用 QPainter 的 fillRect() 绘制了一个透明背景,使用 OPainter 的 drawImage() 方法在一个集中的位置绘制图片。此图片随即被设置为按钮的图标。
void ImageComposer::loadImage(const QString &fileName, QImage *image, QToolButton *button) { image->load(fileName); QImage fixedImage(resultSize, QImage::Format_ARGB32_Premultiplied); QPainter painter(&fixedImage); painter.setCompositionMode(QPainter::CompositionMode_Source); painter.fillRect(fixedImage.rect(), Qt::transparent); painter.setCompositionMode(QPainter::CompositionMode_SourceOver); painter.drawImage(imagePos(*image), *image); painter.end(); button->setIcon(QPixmap::fromImage(fixedImage)); *image = fixedImage; recalculateResult(); }
currentMode() 方法返回在 operatorComboBox 中当前选择的组合模型。
QPainter::CompositionMode ImageComposer::currentMode() const { return (QPainter::CompositionMode) operatorComboBox->itemData(operatorComboBox->currentIndex()).toInt(); }
我们使用 imagePos() 函数来保证图片载入到 QToolButton 对象 sourceButton 和 destinationButton。
QPoint ImageComposer::imagePos(const QImage ℑ) const { return QPoint((resultSize.width() - image.width()) / 2, (resultSize.height() - image.height()) / 2); }
main() 函数
我们在 main() 函数中创建 QApplication 和 ImageComposer 实例,并调用其 show() 函数。
int main(int argc, char *argv[]) { Q_INIT_RESOURCE(imagecomposition); QApplication app(argc, argv); ImageComposer composer; composer.show(); return app.exec(); }
原文链接:http://qt.nokia.com/doc/4.5/painting-imagecomposition.html