Qt绘制系统简介##

Qt 的绘图系统允许使用相同的 API 在屏幕和其它打印设备上进行绘制。整个绘图系统基于QPainter,QPainterDevice和QPaintEngine三个类。

  • QPainter用来执行绘制的操作;
  • QPaintDevice是一个二维空间的抽象,这个二维空间允许QPainter在其上面进行绘制,也就是QPainter工作的空间;
  • QPaintEngine提供了画笔(QPainter)在不同的设备上进行绘制的统一的接口。QPaintEngine类应用于QPainter和QPaintDevice之间,通常对开发人员是透明的。除非你需要自定义一个设备,否则你是不需要关心QPaintEngine这个类的。

我们可以把QPainter理解成画笔;把QPaintDevice理解成使用画笔的地方,比如纸张、屏幕等;而对于纸张、屏幕而言,肯定要使用不同的画笔绘制,为了统一使用一种画笔,我们设计了QPaintEngine类,这个类让不同的纸张、屏幕都能使用一种画笔。

程序###

main.cpp

#include <QApplication>
#include "paint.h" int main(int argc, char **argv) {
QApplication a(argc, argv);
PaintedWidget mywindow;
mywindow.show();
return a.exec();
}

paint.h

#include <QWidget>
#include <QPainter> class PaintedWidget : public QWidget
{
Q_OBJECT
public:
PaintedWidget(QWidget *parent = 0);
protected:
void paintEvent(QPaintEvent *);
};

paint.cpp

#include "paint.h"
PaintedWidget::PaintedWidget(QWidget *parent) :
QWidget(parent)
{
resize(400, 400);
setWindowTitle(tr("Paint Demo"));
} void PaintedWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QPen pen(Qt::green, 3, Qt::DashDotLine, Qt::RoundCap, Qt::RoundJoin);
painter.setPen(pen);
painter.drawLine(80, 100, 300, 300);
painter.setPen(Qt::red);
painter.drawRect(10, 10, 100, 300);
painter.setPen(QPen(Qt::green, 5));
painter.setBrush(Qt::blue);
painter.drawEllipse(50, 150, 200, 100);
}

在构造函数中,我们仅仅设置了窗口的大小和标题。

paintEvent()函数则是绘制的代码。首先,我们在栈上创建了一个QPainter对象,也就是说,每次运行paintEvent()函数的时候,都会重建这个QPainter对象。QPainter接收一个QPaintDevice指针作为参数。QPaintDevice有很多子类,比如QImage,以及QWidget。QPaintDevice可以理解成要在哪里去绘制,而现在我们希望画在这个组件,因此传入的是 this 指针。

注意,这一点可能会引发某些细节问题:由于我们每次重建QPainter,因此第一次运行时所设置的画笔颜色、状态等,第二次再进入这个函数时就会全部丢失。有时候我们希望保存画笔状态,就必须自己保存数据,否则的话则需要将QPainter作为类的成员变量。paintEvent() 作为重绘函数,会在需要重绘时由 Qt 自动调用。“需要重绘”可能发生在很多地方,比如组件刚刚创建出来的时候就需要重绘;组件最大化、最小化的时候也需要重新绘制;组件由遮挡变成完全显示的时候也需要等等。

程序运行结果###

画刷和画笔##

Qt 绘图系统定义了两个绘制时使用的关键属性:画刷和画笔。前者使用QBrush描述,大多用于填充;后者使用QPen描述,大多用于绘制轮廓线。

画刷###

  • 画刷的style()定义了填充的样式,使用Qt::BrushStyle枚举,默认值是Qt::NoBrush,也就是不进行任何填充。我们可以从下面的图示中看到各种填充样式的区别:

  • 画刷的color()定义了填充模式的颜色。这个颜色可以是 Qt 预定义的颜色常量,也就是Qt::GlobalColor,也可以是任意QColor对象。
  • 画刷的gradient()定义了渐变填充。这个属性只有在样式是Qt::LinearGradientPattern、Qt::RadialGradientPattern或者Qt::ConicalGradientPattern之一时才有效。渐变可以由QGradient对象表示。Qt 提供了三种渐变:QLinearGradient、QConicalGradient和QRadialGradient,它们都是QGradient的子类。我们可以使用如下代码片段来定义一个渐变的画刷:
        QPainter painter(this);
QLinearGradient linearGradient(200, 50, 300, 50);
linearGradient.setColorAt(0, Qt::red);
linearGradient.setColorAt(1, Qt::green);
painter.setBrush(linearGradient);
painter.drawEllipse(QPointF(250, 50), 50, 50); QRadialGradient radialGradient(QPointF(50, 50), 50, QPointF(50, 50));
radialGradient.setColorAt(0, QColor(255, 255, 100, 150));
radialGradient.setColorAt(1, QColor(0, 0, 0, 50));
painter.setBrush(radialGradient);
painter.drawEllipse(QPointF(50, 50), 50, 50); QConicalGradient conicalGradient(QPointF(150, 50), 60);
conicalGradient.setColorAt(0.2, Qt::white);
conicalGradient.setColorAt(0.9, Qt::black);
painter.setBrush(conicalGradient);
painter.drawEllipse(QPointF(150, 50), 50, 50);

运行结果如下:

  • 画刷样式是 Qt::TexturePattern时,texture()定义了用于填充的纹理。注意,即使你没有设置样式为Qt::TexturePattern,当你调用setTexture()函数时,QBrush会自动将style()设置为Qt::TexturePattern。
    QPainter painter(this);
QBrush brush;
brush.setTexture(QPixmap("image.jpg"));
painter.setBrush(brush);
painter.drawEllipse(50, 100, 300, 200);

运行结果:

画笔###

QPen定义了用于QPainter应该怎样画线或者轮廓线。

画笔具有样式、宽度、画刷、笔帽样式和连接样式等属性。

  • 1.画笔的样式style()定义了线的样式。画刷brush()用于填充画笔所绘制的线条。
  • 2.笔帽样式capStyle()定义了使用QPainter绘制的线的末端;
  • 3.连接样式joinStyle()则定义了两条线如何连接起来。
  • 4.画笔宽度width()或widthF()定义了画笔的宽。注意,不存在宽度为 0 的线。

    注:假设你设置 width 为 0,QPainter依然会绘制出一条线,而这个线的宽度为 1 像素。也就是说,画笔宽度通常至少是 1 像素。

这么多参数既可以在构造时指定,也可以使用 set 函数指定,完全取决于你的习惯,例如:

QPainter painter(this);
QPen pen(Qt::green, 3, Qt::DashDotLine, Qt::RoundCap, Qt::RoundJoin);
painter.setPen(pen);

等价于

QPainter painter(this);
QPen pen; // creates a default pen pen.setStyle(Qt::DashDotLine);
pen.setWidth(3);
pen.setBrush(Qt::green);
pen.setCapStyle(Qt::RoundCap);
pen.setJoinStyle(Qt::RoundJoin); painter.setPen(pen);

使用构造函数的优点是代码较短,但是参数含义不明确;使用 set 函数则正好反过来。

默认的画笔属性是纯黑色,0 像素,方形笔帽(Qt::SquareCap),斜面型连接(Qt::BevelJoin)。

1.下面是画笔样式的示例:####



你也可以使用setDashPattern()函数自定义样式,例如如下代码片段:

 QPen pen;
QVector<qreal> dashes;
qreal space = 4;
dashes << 1 << space << 3 << space << 9 << space
<< 27 << space << 9 << space;
pen.setDashPattern(dashes);

2.笔帽定义了画笔末端的样式,例如:####



他们之间的区别是,Qt::SquareCap是一种包含了最后一个点的方形端点,使用半个线宽覆盖;Qt::FlatCap不包含最后一个点;Qt::RoundCap是包含最后一个点的圆形端点。具体可以参考下面的示例(出自《C++ GUI Programming with Qt 4, 2nd Edition》):

3.连接样式定义了两条线连接时的样式,例如:####



同样,可以参考下面图示来理解这几种连接样式的细节(出自《C++ GUI Programming with Qt 4, 2nd Edition》):

反走样##

我们在光栅图形显示器上绘制非水平、非垂直的直线或多边形边界时,或多或少会呈现锯齿状外观。这是因为直线和多边形的边界是连续的,而光栅则是由离散的点组成。在光栅显示设备上表现直线、多边形等,必须在离散位置采样。由于采样不充分重建后造成的信息失真,就叫走样;用于减少或消除这种效果的技术,就称为反走样。

反走样是图形学中的重要概念,用以防止通常所说的“锯齿”现象的出现。很多系统的绘图 API 里面都内置了有关反走样的算法,不过由于性能问题,默认一般是关闭的,Qt 也不例外。下面我们来看看代码:

void paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setPen(QPen(Qt::black, 5, Qt::DashDotLine, Qt::RoundCap));
painter.setBrush(Qt::yellow);
painter.drawEllipse(100, 50, 200, 150); painter.setRenderHint(QPainter::Antialiasing, true);
painter.setPen(QPen(Qt::black, 5, Qt::DashDotLine, Qt::RoundCap));
painter.setBrush(Qt::yellow);
painter.drawEllipse(100, 220, 200, 150);
}



显然,我们通过这条语句,将Antialiasing属性(也就是反走样)设置为 true。经过这句设置,我们就打开了QPainter的反走样功能。还记得我们曾经说过,QPainter是一个状态机,因此,只要这里我们打开了它,之后所有的代码都会是反走样绘制的了。

坐标系统##

坐标变换###

前面说过,QPainter是一个状态机。那么,有时我想保存下当前的状态:当我临时绘制某些图像时,就可能想这么做。当然,我们有最原始的办法:将可能改变的状态,比如画笔颜色、粗细等,在临时绘制结束之后再全部恢复。对此,QPainter提供了内置的函数:save()和restore()。save()就是保存下当前状态;restore()则恢复上一次保存的结果。这两个函数必须成对出现:QPainter使用栈来保存数据,每一次save(),将当前状态压入栈顶,restore()则弹出栈顶进行恢复。

在了解了这两个函数之后,我们就可以进行示例代码了:

void PaintDemo::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.fillRect(10, 10, 50, 100, Qt::red);
painter.save();
painter.translate(100, 0); // 向右平移 100px
painter.fillRect(10, 10, 50, 100, Qt::yellow);
painter.restore();
painter.save();
painter.translate(300, 0); // 向右平移 300px
painter.rotate(30); // 顺时针旋转 30 度
painter.fillRect(10, 10, 50, 100, Qt::green);
painter.restore();
painter.save();
painter.translate(0, 100); // 向下平移 100px
painter.scale(2, 2); // 横坐标单位放大 2 倍,纵坐标放大 2 倍
painter.fillRect(10, 10, 50, 100, Qt::blue);
painter.restore();
painter.save();
painter.translate(200, 100); // 向右平移 200px 向下平移 100px
painter.shear(0, 1); // 横向不变,纵向扭曲 1 倍
painter.fillRect(10, 10, 50, 100, Qt::cyan);
painter.restore();
}

Qt 提供了四种坐标变换:平移 translate,旋转 rotate,缩放 scale 和扭曲 shear。在这段代码中,我们首先在 (10, 10) 点绘制一个红色的 50×100 矩形。保存当前状态,将坐标系平移到 (100, 0),绘制一个黄色的矩形。注意,translate()操作平移的是坐标系,不是矩形。因此,我们还是在 (10, 10) 点绘制一个 50×100 矩形,现在,它跑到了右侧的位置。然后恢复先前状态,也就是把坐标系重新设为默认坐标系(相当于进行translate(-100, 0)),再进行下面的操作。之后也是类似的。由于我们只是保存了默认坐标系的状态,因此我们之后的translate()横坐标值必须增加,否则就会覆盖掉前面的图形。所有这些操作都是针对坐标系的,因此在绘制时,我们提供的矩形的坐标参数都是不变的。

运行结果如下:

窗口坐标###

void PaintDemo::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setWindow(0, 0, 200, 200);
painter.fillRect(0, 0, 200, 200, Qt::red);
}

运行结果:



原本窗口大小为400400,但这里利用setWindow重新分配了窗口坐标为200200,即便拖动窗口大小红色矩形也会覆盖整个窗口,由于setWindow将窗口的左上角固定位(0,0)窗口的右下角固定为(200,200)。此时再画矩形就是在这个窗口坐标下画。

视口坐标###

    QPainter painter(this);
painter.setWindow(0, 0, 200, 200);
painter.setViewport(100, 100, 100, 100);
painter.fillRect(0, 0, 200, 200, Qt::red);



利用setVieweport将窗口映射到视口。

参考##

豆子空间

Qt__绘制系统的更多相关文章

  1. Qt 学习之路 2(24):Qt 绘制系统简介

    Qt 学习之路 2(24):Qt 绘制系统简介 豆子 2012年10月30日 Qt 学习之路 2 77条评论 Qt 的绘图系统允许使用相同的 API 在屏幕和其它打印设备上进行绘制.整个绘图系统基于Q ...

  2. Qt 学习之路 :Qt 绘制系统简介

    Qt 的绘图系统允许使用相同的 API 在屏幕和其它打印设备上进行绘制.整个绘图系统基于QPainter,QPainterDevice和QPaintEngine三个类. QPainter用来执行绘制的 ...

  3. QT绘制系统简介

    #3个类:QPainter,QPainterDevice 和 QPaintEngine 三个类 #qpainter用于执行绘制操作 #QPainterDevice是一个二维空间抽象,允许qpainte ...

  4. 关于系统首页绘制问题(ext布局+c#后台加入数据)经html输出流输出响应client

    关于系统首页绘制问题,业务需求 TODO 绘制系统首页(Main.aspx) 採用的技术:functioncharts+jquery+ext布局+c#+html 解说篇:1,服务端aspx,2,服务端 ...

  5. Android图形显示系统——上层显示1:界面绘制大纲

    Android显示之应用界面绘制 越到上层,跟业务关联越直接.代码就越繁杂.Android上层显示的代码正是如此.此外,java语言本身繁复的特点(比C语言多了满屏的try-catch,比C++少了析 ...

  6. 简单研究Android View绘制三 布局过程

    2015-07-28 17:29:19 这一篇主要看看布局过程 一.布局过程肯定要不可避免的涉及到layout()和onLayout()方法,这两个方法都是定义在View.java中,源码如下: /* ...

  7. IOS UIView 01-View开始深入 绘制像素到屏幕上

    注:本人是翻译过来,并且加上本人的一点见解. 前言 一个像素是如何绘制到屏幕上去的?有很多种方式将一些东西映射到显示屏上,他们需要调用不同的框架.许多功能和方法的结合体.这里我们大概的看一下屏幕之后发 ...

  8. 简单研究Android View绘制一 测量过程

    2015-07-27 16:52:58 一.如何通过继承ViewGroup来实现自定义View?首先得搞清楚Android时如何绘制View的,参考Android官方文档:How Android Dr ...

  9. 一种工业级系统交互建模工具的应用--EventStudio System Designer

    一种工业级系统交互建模工具的应用 [摘要] 本文以探索如何维护大规模复杂系统交互设计模型为目的,以EventHelix公司的商业付费软件EventStudio System Designer为建模工具 ...

随机推荐

  1. php 对象数组互转

    数组转对象 function array2object($array) {   if (is_array($array)) {     $obj = new StdClass();     forea ...

  2. <数据结构与算法分析>读书笔记--运行时间中的对数及其分析结果的准确性

    分析算法最混乱的方面大概集中在对数上面.我们已经看到,某些分治算法将以O(N log N)时间运行.此外,对数最常出现的规律可概括为下列一般法则: 如果一个算法用常数时间(O(1))将问题的大小削减为 ...

  3. Skyline从5.1升级版本到6.5的常见接口变化问题

    1.原来Route对象升级成Presentation对象后,激活方法的变化: 原来5.1版本示例代码: function flyto(thisa) { var thisid = thisa.id; v ...

  4. 初始化应用程序数据ng-init指令

    ng-init指令初始化应用程序数据. 如果我们想给文本框一个初化的值: <div ng-app="" ng-init="Name='Leo'"> ...

  5. Ubuntu16.04下完美切换Python版本

    http://blog.csdn.net/beijiu5854/article/details/77897767

  6. C#基础巩固(1)-多态+简单工厂

    多态 如果要简要的描述多态的话,我个人是这样理解的:通过继承,父类定义方法,具休的实现由子类进行. 01代码 //父类 class Person { public virtual void skill ...

  7. docker for windows 10 添加阿里云镜像仓库无效问题

    原来一直是用cmd来执行docker 命令的,结果今天发现不行了,改了镜像仓库也pull不下来. 后来换用powerShell执行docker pull 才成功.大家可以试试 win+R  运行 po ...

  8. eclipse取消自动输入提示

    在设置Eclipse自动提示后,按a-z都会显示提示,但是我们需要键入Enter才会输入,而默认的所有都键入,非常弱智,可采用下面方法设置. 1,先找到相关的插件: window -> show ...

  9. Spring+SpringMVC+MyBatis+easyUI整合进阶篇(六)一定要RESTful吗?

    作者:13 GitHub:https://github.com/ZHENFENG13 版权声明:本文为原创文章,未经允许不得转载. 写在前面的话 这个问题看起来就显得有些萌,或者说类似的问题都有些不靠 ...

  10. 没有 iOS 开发者账号的情况下部署到真机的方法

    原文发表于我的技术博客 本文分享了官方推荐的没有 iOS 开发者账号的情况下部署到真机的方法,供参考. 原文发表于我的技术博客 1. 官方推荐的方法 原文在此,也就是 Ionic 官方团队在博客中分享 ...