QT的绘图系统为UI界面在各种设备(屏幕,打印机等等)上的输出,提供了统一的API接口。
层次结构与Paint绘图输出直接相关的类大致为以下三个类及其子类QPainter QPaintDevice QPaintEngine
一个绘图操作的流程,基本上来说,就是对一个QPaintDevice直接或间接调用QPainter类,QPainter类内部调用QPaintEngine进行绘图,而QPaintEngine类通常是由QPaintDevice类负责创建和管理。
1.QPainter类
Qt绘图要用到QPainter类,绘图的设备通常是主部件,也可以是QLabel部件或QTextEdit部件,通过一定的算法实现可以绘制出很漂亮的图形或我们需要的图形。
绘制时需要先定义一个QPainter类对象,绘制可以选择的道具可以使Qpen(画笔)、QBrush(画刷)。使用QPen写文本时还可以指定字体(QFont类)
如下面一段代码:
QPainter painter;
QPen pen;
pen.setColor(QColor(255,0,0)); //设置画笔为红色
painter.setPen(pen); //选择画笔
painter.drawLine(0,0,100,100); //用该红色画笔画一条线,起点(0,0),终点(100,100)
painter.end(); //结束绘制。绘制时使用的任何资源都被释放。虽然有时不需要调用end(),析构函数将会执行它
其它的功能大同小异,参考Help文档,里面有各个方法的具体含义及参数意义。
这里要说的是,绘图在什么时候发生,怎样更新绘制的视图。
绘图时,需要重载QWidget类的paintEvent ( QPaintEvent * )方法,函数原型为
void QWidget::paintEvent ( QPaintEvent * ) [虚 保护] 该函数是受保护的虚函数,是绘制事件的函数,可以在派生类中被重新实现来接受绘制事件。
所以使用时要现在类中声明paintEvent函数,然后在函数定义中实现图像的绘制。注意参数要写上QPaintEvent *event
搞清楚这些后,我们就知道了,图形的绘制是在paintEvent这个函数中完成的,也就是说,不一定要把所有绘制的代码全写在paintEvent这个函数中(当然,很多教程都是这样写的,代码较长而且有一定框架时不建议这样),可以在外面的函数中实现,在paintEvent中调用相应的函数。
然后我们需要弄清楚,什么时候绘制的问题,其实在使用类的对象的时候,如果类中重写了paintEvent事件,对象就会调用一次paintEvent函数,即定义对象时会调用该绘制事件。如果要完成图像重绘或刷新图像时怎么办呢?这就要使用repaint()或update()函数完成,这两个都是QWidget类的成员函数,派生类可以直接调用这两个函数进行窗口的擦除和绘制(注意是先擦除然后在绘制),即通过repaint()或updata()调用paintEvent事件。如果需要立即重新绘制,我们建议使用repaint(),但repiant()有个缺陷,因为倘若repaint()后paintEvent函数中要调用的函数中又有repaint(),就会陷入无限循环中,而updata()则不会出现此情况,因为updata()通过某一机制会让paintEvent事件只调用一次。在绝大多数情况下,update()更好,因为它允许Qt来优化速度并且防止闪烁。
特别要注意一点,在绘制时,Qt在paintEvent事件中已经帮我们自动实现了双缓冲(X11系统需要手动去开启双缓冲),即绘制使用的就是双缓冲的方法,这与MFC有区别,MFC中需要我们自己使用双缓冲,否则在某些应用中就会出现闪烁。
http://www.cnblogs.com/Romi/archive/2012/04/06/2435412.html
例子来自help:
void SimpleExampleWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setPen(Qt::blue);
painter.setFont(QFont("Arial", 30));
painter.drawText(rect(), Qt::AlignCenter, "Qt");
}
2. QPixmap和QImage的区别
QPixmap
是专门为绘图而生,当需要绘制图片时你需要使用QPixmap。
QImage
则是为I/O,为图片像素访问以及修改而设计的。如果你想访问图片的像素或是修改图片像素,则需要使用QImage,或者借助于
QPainter
来操作像素。另外跟QImage不同是,QPixmap跟硬件是相关的,如X11, Mac 以及
Symbian
平台上,QPixmap 是存储在服务器端,而QImage则是存储在客户端,在Windows平台上,QPixmap和QImage都是存储在客户端,并不使用任何的GDI资源。
GDI:图形设备接口(Graphics Device Interface或Graphical Device Interface,缩写GDI),是微软公司视窗操作系统(Microsoft Windows)的三大核心部件(也称“子系统”)之一。GDI是微软视窗系统表征图形对象及将其传送给诸如显示器、打印机之类输出设备的标准。GDI类似Macintosh传统的QuickDraw。
相信大家更关心的是谁比较快,哈哈,现在来总结一下:
在X11, Mac 以及 Symbian平台上,QImage: 因为它是存储在客户端,往QImage上绘图比较快,但显示它则比较慢。QPixmap: 因为它是存储在服务器端,往QPixmap上绘图比较慢,但显示它则比较快。但在Windows平台上则是是一样的,因为它们都存储在客户端。
Qt上图片处理使用QPixmap和QImage时最多了,不过既然谈到图片了,我们把其他几个图片处理类也说一下:
QBitmap只是一个继承于QPixmap的简单类,它可以确保图片深度为1。
QBitmap是QPixmap的子类,提供单色图像,可以用来制作游标(QCursor)或者笔刷(QBrush)。我们可以通过以下代码看出两者的区别:
- class PainterWidget : public QWidget {
- protected:
- void paintEvent(QPaintEvent*);
- };
- void PainterWidget::paintEvent(QPaintEvent *event) {
- QPixmap pixImg("caterpillar.jpg");
- QBitmap bitImg("caterpillar.jpg");
- QPainter painter(this);
- painter.drawPixmap(0, 0, pixImg);
- painter.drawPixmap(200, 0, bitImg);
- }
- int main(int argc, char *argv[]) {
- QApplication app(argc, argv);
-
- PainterWidget pWidget;
- pWidget.setWindowTitle("QPixmap & QBitmap");
- pWidget.resize(400, 150);
- pWidget.show();
-
- return app.exec();
- }
复制代码
执行效果如下图所示:
QPicture是一个绘画设备类,它记录了并可以重演QPainter的命令。你可以使用QPainter的begin()方法,指定在QPicture上绘图,使用
end()方法结束绘图,使用QPicture的save()方法將QPainter所使用过的绘图指令存至档案,例如:
- QPicture
- picture;
- QPainter painter;
- painter.begin(&picture);
- painter.drawRect(10, 20, 100,
- 50);
- painter.end();
- picture.save("draw_record.pic");
复制代码
要重播绘图指令的话,建立一個QPicture,使用load()方法载入绘图指令的档案,然后在指定的绘图裝置上绘制QPicture:
- QPicture
- picture;
- picture.load("draw_record.pic");
- QPainter
- painter;
- painter.begin(this);
- painter.drawPicture(0, 0, picture);
- painter.end();
复制代码
来自:
http://www.thisisqt.com/forum/viewthread.php?tid=267
QPixmap显示图片
http://hi.baidu.com/eygaqurchnbhsyq/item/2b9624006120f2edff240d42
3.QImage与QPixmap完全解析
http://www.civilnet.cn/bbs/browse.php?topicno=4691
用Qt程序在手机上显示一幅图片对编程人员来说是再基础不过的一件事情了。那么先让大家看两段代码:
//dangerous should not be used, cannot display earth.png,
//but if we change earth.png to a smaller image e.g. apple.png, apple.png can be displayed
QPixmap pixmap;
pixmap.load( ":/pics/earth.png" );
label->setPixmap( pixmap );
和
//dangerous should not be used, cannot display earth.png,
//but if we change earth.png to a smaller image e.g. apple.png, apple.png can be displayed
QPixmap pixmap;
pixmap.load( ":/pics/earth.png" );
QPainter painter(this);
painter.drawPixmap(0,0, pixmap);
大 家认为这两段代码有什么问题吗? 看起来好像没什么问题啊。是的,在Windows操作系统上是没有问题的。问题是我们做的是Qt for Symbian! 手机上的资源本来就是比较紧缺的,所以我们使用的时候就需要更加注意。 Qt 为我们提供了四个处理图像的类:QImage,QPixmap,QBitmap 和QPicture。其中前两个是最常使用的。
本文就通过一个例子,一步一步为大家讲解QImage与QPixmap的使用奥秘,在此过程中为大家揭示以上代码存在的缺陷。
QPixmap依赖于硬件
首先需要知道的是QPixmap的具体实现是依赖于系统的。在Symbian系统上QPixmap是被存放在Server端的。
目前的Qt会把QPixmap都存储在graphics memory中,这明显是依赖硬件的。因此我们对QPixmap的使用需要格外注意。这也正是以上两段代码存在问题的根源。
那么Qt为什么要这么做呢?很简单,设计之初QPixmap就是用来加速显示的,例如我们在paint的时候用QPixmap就会比用其他类的效果好许多。
现在回到我们最初的问题,以上代码到底有什么问题呢?我们可以先用本文提供的例子程序做个试验。当使用上述代码显示较小图片的时候(比如例子程序中的background.png 和apple.png)是没有问题的,图片都能在手机上正确显示。
但是当我们把图片换成一副较大图片287KB,1058 x 1058的“earth.png”的时候就出现问题了,图片无法显示,程序的界面是一片空白。
据测算,“earth.png”被完全解码后存储在graphics memory中会占用大约4.3MB的空间。如果此时还有其他加载的窗口和QPixmap,很可能就没有空间了。
使用QImage加载后转换成QPixmap 显示
那么安全和正确的方法应该是什么呢?答案是我们需要用QImage做一下预处理:
//correct and recommended way
QImage image;
image.load( ":/pics/earth.png" );
QPainter painter(this);
QPixmap pixmapToShow = QPixmap::fromImage( image.scaled(size(), Qt::KeepAspectRatio) );
painter.drawPixmap(0,0, pixmapToShow);
和QPixmap 不同,QImage是独立于硬件的,它可以同时被另一个线程访问。QImage是存储在客户端的,对QImage的使用是非常方便和安全的。 又由于 QImage 也是一种QPaintDevice,因此我们可以在另一个线程中对其进行绘制,而不需要在GUI 线程中处理,使用这一方式可以很大幅度提高UI响应速度。 因此当图片较大时,我们可以先通过QImage将图片加载进来,然后把图片缩放成需要的尺寸,最后转换成QPixmap 进行显示。 下图是显示效果(图片是按照earth.png的原始尺寸比例缩放后显示的):
其中需要注意的是Qt::KeepAspectRatio的使用,默认参数是Qt::IgnoreAspectRatio,如果我们在程序中这么写:
QPixmap pixmapToShow = QPixmap::fromImage( image.scaled(size(), Qt::IgnoreAspectRatio) );
效果就是下面这个样子,earth.png被拉伸以充满整个屏幕:
直接使用QImage 显示
我们也可以直接使用QImage做显示,而不转换成QPixmap ,这要根据我们应用的具体需求来决定,如果需要的话我们可以这么写:
//correct, some times may be needed
QImage image;
image.load( ":/pics/earth.png" );
QPainter painter(this);
painter.drawImage(0,0, image);
下面是显示效果(当然我们也可以对其进行缩放之后再显示) 从图片可以看出来它是按照原始尺寸显示earth.png的:
测试设备
本代码已通过在N97和N8上的测试。
http://blog.csdn.net/sky_freebird/article/details/6688662
4.QPalette调色板
所有的Qt窗口部件都有一个Qpalette,并使用它绘制自己。应用程序还有一个调色板,通过Qapplication::palette()来获得,修改一个窗口部件的调色板,只影响这个窗口部件以及子窗口部件(不包含子窗口),而修改应用程序的调色板,会影响到该应用程序的所有窗口部件。
1、窗体控件设置背景图片
窗体
QPalette palette;
palette.setBrush(QPalette::Window,QBrush(QPixmap("./src/*.png")));
this->setPalette(palette);
2、设置按钮字体颜色
QPalette pal=ui->pb_Film->palette();
pal.setColor(QPalette::Active, QPalette::ButtonText, Qt::white);
ui->pb_Film->setPalette(pal);
一. 背景刷成黑色,前景色设为白色。 方法一、paltette方式,经测试,该方法不会影响到其他控件,推荐使用 QPalette bgpal = palette();
bgpal.setColor (QPalette::Background, QColor (0, 0 , 0, 255));
//bgpal.setColor (QPalette::Background, Qt::transparent);
bgpal.setColor (QPalette::Foreground, QColor (255,255,255,255)); setPalette (bgpal);
方法二、stylesheet方式 影响子控件的方法是:
setStyleSheet ("background-color: rgb(0,0,0);color: rgb(255,255,255);");
不影响子控件的方法是:
setStyleSheet ("venus--TitleBar {background-color: rgb(0,0,0);color: rgb(255,255,255);}");
二. 圆角控件 用stylesheet方式
setStyleSheet ("border:2px groove gray;border-radius:10px;padding:2px 4px;");
三. 圆角窗口 RoundRectWin::RoundRectWin() { QPalette p = palette(); QPixmap img("roundrect.png");
QBitmap mask("roundrect_mask.png");
p.setBrush(QPalette::Window, QBrush(img));
setPalette(p);
setMask(mask);
resize(img.size());
//setWindowFlags(Qt::FramelessWindowHint);//这句会去掉标题栏 } 注意:mask的图多余部分设为白色
四. 半透明窗口
1.窗口整体透明,但是窗体上的控件不透明。 通过设置窗体的背景色来实现,将背景色设置为全透。 QPalette pal = palette();
pal.setColor(QPalette::Background, QColor(0x00,0xff,0x00,0x00)); setPalette(pal); 试验效果:
窗体标题栏不透明;
窗体客户区上的控件不透明,QLabel控件只是字显示,控件背景色透明; 窗体客户区完全透明。
另外从网上看到的方法: setAttribute(Qt::WA_TranslucentBackground, true); 试验的结果是类似于上面的方法,但有时候窗体会被一些杂色斑点填充,未找到原因。
2.窗口及其上面的控件都半透明:
setWindowOpacity(0.7)
试验效果:窗口及控件都半透明。注意不能够setWindowFlags(Qt::FramelessWindowHint);要不就不起作用
3.窗口整体不透明,局部透明:
在Paint事件中使用Clear模式绘图。
void TestWindow::paintEvent( QPaintEvent* )
{ QPainter p(this);
p.setCompositionMode( QPainter::CompositionMode_Clear ); p.fillRect( 10, 10, 300, 300, Qt::SolidPattern ); }
试验效果:绘制区域全透明。如果绘制区域有控件不会影响控件。
以上实验均是基于Directfb的Qte测试。在Linux下及Windows下效果会略有不同。 比如在Windows下第3个实验,绘制区域就是一个黑窟窿,如果Window本身半透,则是Window下面的内容加上半透效果,不是全透明效果。
五. 控制QPixmap的alpha
QPixmap temp(pixmapTop.size()); temp.fill(Qt::transparent);
QPainter p(&temp);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.drawPixmap(0, 0, pixmapTop);
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
p.fillRect(temp.rect(), QColor(0, 0, 0, alpha)); //--lable显示前景图片 ui->label->setScaledContents(true);
ui->label->setPixmap(temp);
六. layout 的边界 layout->setMargin (0);
来自:
http://hi.baidu.com/dillisbest/item/23d396bb395d71ea4fc7fdd6
调色板类QPallete提供了颜色角色(color roles)概念,是指当前GUI界面中颜色的职责,通过枚举变量QPalette::ColorRole来定义,比较常用的颜色角色有:
QPalete::Window,通常指窗口部件的背景色;
QPalette:WindowText,通常指窗口不见的前景色;
QPalette::Base,指文本输入窗口部件(比如QtextEdit,QLinedit等)的背景色.
QPalette::Text,与QPalette::Base一块使用,指文本输入窗口部件的前景色;
QPalette::Button,指按钮窗口部件的背景色;
QPalette::ButtonText,指按钮窗口部件的前景色.
例:
QPalette pal = pushButtonCancel->palette();
pal.setColor(QColorGroup::ButtonText,QColor(255,0,0));
pushButtonCancel->setPalette(pal);
按钮pushButton的字体颜色为红色
6.
Qt中 窗口部件的 背景图片 的设置
方法一:
首先设置 autoFillBackground 属性为真
然后定义一个QPalette对象
设置QPalette对象的背景属性(颜色或图片)
最后设置QWidget对象的Palette
实例:
QWidget *widget = new QWidget;
widget->setAutoFillBackground(true);
QPalette palette;
//palette.setColor(QPalette::Background, QColor(192,253,123));
palette.setBrush(QPalette::Background, QBrush(QPixmap(":/images/background.png")));
widget->setPalette(palette);
方法二:
QPalette的方法
#include <QApplication>
#include <QtGui>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QFrame *frame = new QFrame;
frame->resize(400, 700);
QPixmap pixmap11(":/images/frame.png");
QPixmap pixmap = pixmap11 .scaled(400,700);
QPalette palette;
palette.setBrush(frame->backgroundRole(),QBrush(pixmap));
frame->setPalette(palette);
frame->setMask(pixmap.mask()); //可以将图片中透明部分显示为透明的
frame->setAutoFillBackground(true);
frame->show();
return app.exec();
}
方法三:
setStyleSheet方法( 非常好用的方法 )
设置属性使背景图自动调整来适应控件的大小。
#include <QApplication>
#include <QtGui>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QFrame *frame = new QFrame;
frame->setObjectName("myframe");
frame->resize(400,700);
frame->setStyleSheet("QFrame#myframe{ border-image : url(:/images/frame.png)}" );
frame->show();
return app.exec();
}
注意代码中红线的部分,设置ObjectName,才能保证setStyleSheet只作用在我们的frame上,不影响其子控件的背景设置。之所以用border-image而不用background-image,还是上面的问题,用background-image不能保证图片大小和控件大小一致,图片不能完全显示。
方法四:
paintEvent事件方法
//myframe.h文件
#ifndef MYFRAME_H
#define MYFRAME_H
#include <QWidget>
#include <QtGui>
class MyFrame : public QWidget
{
public:
MyFrame();
void paintEvent(QPaintEvent *event);
};
#endif // MYFRAME_H
//myframe.cpp文件
#include "myframe.h"
MyFrame::MyFrame()
{
}
void MyFrame::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.drawPixmap(0,0,400,700,QPixmap("images/frame.png"));
}
//main.cpp文件
#include <QApplication>
#include <QtGui>
#include "myframe.h"
int main(int argc, char *argv[])
{
QApplication app(argc,argv);
MyFrame *frame = new MyFrame;
frame->resize(400,700);
frame->show();
return app.exec();
}
这个背景图片不随着窗口的大小而变化,因为它的固定大小被设置成(400,700)了。重写QWidget的paintEvent事件,当控件发生重绘事件,比如show() 时,系统就会自动调用paintEvent函数。