Qt 学习之路:Canvas

在 QML 刚刚被引入到 Qt 4 的那段时间,人们往往在讨论 Qt Quick 是不是需要一个椭圆组件。由此,人们又联想到,是不是还需要其它的形状?这种没玩没了的联想导致了一个最直接的结果:除了圆角矩形,Qt Quick 什么都没有提供,包括椭圆。如果你需要一个椭圆,那就找个图片,或者干脆自己用 C++ 写一个吧(反正 Qt Quick 是可以扩展的,不是么)!

为了使用脚本化的绘图机制,Qt 5 引入的Canvas元素。Canvas元素提供了一种与分辨率无关的位图绘制机制。通过Canvas,你可以使用 JavaScript 代码进行绘制。如果熟悉 HTML5 的话,Qt Quick 的Canvas元素与 HTML5 中的Canvas元素如出一辙。

Canvas元素的基本思想是,使用一个 2D 上下文对象渲染路径。这个 2D 上下文对象包含所必须的绘制函数,从而使Canvas元素看起来就像一个画板。这个对象支持画笔、填充、渐变、文本以及其它一系列路径创建函数。

下面我们看一个简单的路径绘制的例子:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

import QtQuick 2.0

Canvas {

id: root

// 画板大小

width: 200; height: 200

// 重写绘制函数

onPaint: {

// 获得 2D 上下文对象

var ctx = getContext("2d")

// 设置画笔

ctx.lineWidth = 4

ctx.strokeStyle = "blue"

// 设置填充

ctx.fillStyle = "steelblue"

// 开始绘制路径

ctx.beginPath()

// 移动到左上点作为起始点

ctx.moveTo(50,50)

// 上边线

ctx.lineTo(150,50)

// 右边线

ctx.lineTo(150,150)

// 底边线

ctx.lineTo(50,150)

// 左边线,并结束路径

ctx.closePath()

// 使用填充填充路径

ctx.fill()

// 使用画笔绘制边线

ctx.stroke()

}

}

上面的代码将在左上角为 (50, 50) 处,绘制一个长和宽均为 100 像素的矩形。这个矩形使用钢铁蓝填充,并且具有蓝色边框。程序运行结果如下所示:

让我们来仔细分析下这段代码。首先,画笔的宽度设置为 4 像素;使用strokeStyle属性,将画笔的颜色设置为蓝色。fillStyle属性则是设置填充色为 steelblue。只有当调用了stroke()fill()函数时,真实的绘制才会执行。当然,我们也完全可以独立使用这两个函数,而不是一起。调用stroke()fill()函数意味着将当前路径绘制出来。需要注意的是,路径是不能够被复用的,只有当前绘制状态才能够被复用。所谓“当前绘制状态”,指的是当前的画笔颜色、宽度、填充色等属性。

在 QML 中,Canvas元素就是一种绘制的容器。2D 上下文对象作为实际绘制的执行者。绘制过程必须在onPaint事件处理函数中完成。下面即一个代码框架:

1

2

3

4

5

6

7

8

Canvas {

width: 200; height: 200

onPaint: {

var ctx = getContext("2d")

// 设置绘制属性

// 开始绘制

}

}

Canvas本身提供一个典型的二维坐标系,原点在左上角,X 轴正方向向右,Y 轴正方向向下。使用Canvas进行绘制的典型过程是:

  1. 设置画笔和填充样式
  2. 创建路径
  3. 应用画笔和填充

例如:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

onPaint: {

var ctx = getContext("2d")

// 设置画笔

ctx.strokeStyle = "red"

// 创建路径

ctx.beginPath()

ctx.moveTo(50,50)

ctx.lineTo(150,50)

// 绘制

ctx.stroke()

}

上面这段代码运行结果应该是一个从 (50, 50) 开始,到 (150, 50) 结束的一条红色线段。

由于我们在创建路径之前会将画笔放在起始点的位置,因此,在调用beginPath()函数之后的第一个函数往往是moveTo()

形状 API

除了自己进行路径的创建之外,Canvas还提供了一系列方便使用的函数,用于一次添加一个矩形等,例如:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

import QtQuick 2.0

Canvas {

id: root

width: 120; height: 120

onPaint: {

var ctx = getContext("2d")

ctx.fillStyle = ‘green‘

ctx.strokeStyle = "blue"

ctx.lineWidth = 4

// 填充矩形

ctx.fillRect(20, 20, 80, 80)

// 裁减掉内部矩形

ctx.clearRect(30,30, 60, 60)

// 从左上角起,到外层矩形中心绘制一个边框

ctx.strokeRect(20,20, 40, 40)

}

}

代码运行结果如下:

注意蓝色边框的位置。在绘制边框时,画笔会沿着路径进行绘制。上面给出的 4 像素边框,其中心点为路径,因此会有 2 像素在路径外侧,2 像素在路径内侧。

渐变

Canvas元素可以使用颜色进行填充,同样也可以使用渐变。例如下面的代码:

1

2

3

4

5

6

7

8

9

onPaint: {

var ctx = getContext("2d")

var gradient = ctx.createLinearGradient(100,0,100,200)

gradient.addColorStop(0, "blue")

gradient.addColorStop(0.5, "lightsteelblue")

ctx.fillStyle = gradient

ctx.fillRect(50,50,100,100)

}

运行结果如下所示:

在这个例子中,渐变的起始点位于 (100, 0),终止点位于 (100, 200)。注意这两个点的位置,这两个点实际创建了一条位于画布中央位置的竖直线。渐变类似于插值,可以在 [0.0, 1.0] 区间内插入一个定义好的确定的颜色;其中,0.0 意味着渐变的起始点,1.0 意味着渐变的终止点。上面的例子中,我们在 0.0 的位置(也就是渐变起始点 (100, 0) 的位置)设置颜色为“blue”;在 1.0 的位置(也就是渐变终止点 (100, 200) 的位置)设置颜色为“lightsteelblue”。注意,渐变的范围可以大于实际绘制的矩形,此时,绘制出来的矩形实际上裁减了渐变的一部分。因此,渐变的定义其实是依据画布的坐标,也不是定义的绘制路径的坐标。

阴影

路径可以使用阴影增强视觉表现力。我们可以把阴影定义为一个围绕在路径周围的区域,这个区域会有一定的偏移、有一定的颜色和特殊的模糊效果。我们可以使用shadowColor属性定义阴影的颜色;使用shadowOffsetX属性定义阴影在 X 轴方向的偏移量;使用shadowOffsetY属性定义阴影在 Y 轴方向的偏移量;使用shadowBlur属性定义阴影模糊的程度。不仅是阴影,利用这种效果,我们也可以实现一种围绕在路径周边的发光特效。下面的例子中,我们将创建一个带有发光效果的文本。为了更明显的显示发光效果,其背景界面将会是深色的。下面是相应的代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

import QtQuick 2.0

Canvas {

id: root

width: 280; height: 120

onPaint: {

var ctx = getContext("2d")

// 背景矩形

ctx.strokeStyle = "#333"

ctx.fillRect(0, 0, root.width, root.height);

// 设置阴影属性

ctx.shadowColor = "blue";

ctx.shadowOffsetX = 2;

ctx.shadowOffsetY = 2;

ctx.shadowBlur = 10;

// 设置字体并绘制

ctx.font = ‘bold 80px sans-serif‘;

ctx.fillStyle = "#33a9ff";

ctx.fillText("Earth", 30, 80);

}

}

首先,我们利用 #333 填充了一个背景矩形。矩形的起始点位于原点,长度和宽度分别绑定到画布的长度和宽度。接下来定义阴影的属性。最后,我们设置文本字体为 80 像素加粗的 sans-serif,会绘制了“Earth”单词。代码运行结果如下所示:

注意观察字母旁边的发光效果,这其实是使用阴影制作的。

图像

Canvas元素支持从多种源绘制图像。为了绘制图像,需要首先加载图像;使用Component.onCompleted事件处理函数可以达到这一目的:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

onPaint: {

var ctx = getContext("2d")

// 绘制图像

ctx.drawImage(‘assets/earth.png‘, 10, 10)

// 保存当前状态

ctx.save()

// 平移坐标系

ctx.translate(100,0)

ctx.strokeStyle = ‘red‘

// 创建裁剪范围

ctx.beginPath()

ctx.moveTo(10,10)

ctx.lineTo(55,10)

ctx.lineTo(35,55)

ctx.closePath()

ctx.clip()  // 根据路径裁剪

// 绘制图像并应用裁剪

ctx.drawImage(‘assets/earth.png‘, 10, 10)

// 绘制路径

ctx.stroke()

// 恢复状态

ctx.restore()

}

Component.onCompleted: {

loadImage("assets/earth.png")

}

代码运行结果如下:

左侧的地球图像绘制在左上角坐标为 (10, 10) 的位置;右侧的图像应用了路径裁剪。图像和路径都可以被另外的路径裁剪,只需使用clip()函数即可。调用该函数之后,所有的绘制都将限制在这个路径中,也就是所谓“裁剪”。裁剪会在恢复上次状态时被取消。

时间: 2024-11-24 12:07:59

Qt 学习之路:Canvas的相关文章

Qt学习之路

  Qt学习之路_14(简易音乐播放器) Qt学习之路_13(简易俄罗斯方块) Qt学习之路_12(简易数据管理系统) Qt学习之路_11(简易多文档编辑器) Qt学习之路_10(Qt中statusBar,MessageBox和Timer的简单处理) Qt学习之路_9(Qt中Item Widget初步探索) Qt学习之路_8(Qt中与文件目录相关操作) Qt学习之路_7(线性布局和网格布局初步探索) Qt学习之路_6(Qt局域网聊天软件) Qt学习之路_5(Qt TCP的初步使用) Qt学习之路

QT学习之路(1):彩票绝对不中模拟器

//============================================//绝对不中,彩票开奖模拟器#include "mainwindow.h"#include "ui_mainwindow.h"#include <QHash>#include <QDebug>MainWindow::MainWindow(QWidget *parent) :    QMainWindow(parent),    ui(new Ui::M

QT学习之路--创建一个对话框

Q_OBJECT:这是一个宏,凡是定义信号槽的类都必须声明这个宏. 函数tr()全名是QObject::tr(),被他处理过的字符串可以使用工具提取出来翻译成其他语言,也就是做国际化使用. 对于QT学习之路:Qt学习之路(7):创建一个对话框(上)这个程序.编译出现 invalid use of incomplete type ‘class QPushButton’ findButton->setEnabled(!text.isEmpty()); ^ In file included from

Qt 学习之路 2 --- 读书笔记

一.文章来由 来自豆子老师非常好的一本Qt教程,但是只有网络版,所以用这个做笔记了,不动笔墨不读书嘛~~ 二.读书笔记 1.Qt 学习之路 2(2):Qt 简介 1.1 关于 Qt 的一站式解决 Qt 是一个著名的 C++ 应用程序框架.但并不只是一个 GUI 库,因为 Qt 十分庞大,并不仅仅是 GUI 组件.使用 Qt,在一定程度上你获得的是一个"一站式"的解决方案:不再需要研究 STL,不再需要 C++ 的,不再需要到处去找解析 XML.连接数据库.访问网络的各种第三方库,因为

Qt学习之路1---软件下载安装及工程简介

1.下载安装目前最新版的qt,官网链接:https://www.qt.io/qt5-8/: 和qt4不同,qt5在线安装,轻巧快速,而且不用配置一些繁琐的东西,安装之后会出现Qt creator这就是我们之后使用的IDE. 2.Qt creator工程包含不同类型的文件 _ .pro项目描述文件 _ .pro.user 用户配置描述文件  _ .ui 界面描述文件  _ 资源文件(图片,音频等) 2.1 .pro项目描述文件的基本组成 _ .#  注释符 _ QT 模块声明 _ TARGET  

Qt学习之路(24): QPainter(改写paintEvent)

多些大家对我的支持啊!有朋友也提出,前面的几节有关event的教程缺少例子.因为event比较难做例子,也就没有去写,只是把大概写了一下.今天带来的是新的部分,有关Qt的2D绘图.这部分不像前面的内容,还是比较好理解的啦!所以,例子也会增加出来. 有人问豆子拿Qt做什么,其实,豆子就是在做一个Qt的画图程序,努力朝着Photoshop和GIMP的方向发展.但这终究要经过很长的时间.很困难的路程的,所以也放在网上开源,有兴趣的朋友可以来试试的呀… 好了,闲话少说,来继续我们的学习吧! Qt的绘图系

Qt学习之路(49): 通用算法

今天开始的部分是关于Qt提供的一些通用算法.这部分内容来自C++ GUI Programming with Qt 4, 2nd Edition. <QtAlgorithms>提供了一系列通用的模板函数,用于实现容器上面的基本算法.这部分算法很多依赖于STL风格的遍历器(还记得前面曾经说过的Java风格的遍历器和STL风格的遍历器吗?).实际上,C++ STL也提供了很多通用算法,包含在<algorithm>头文件内.这部分算法对于Qt容器同样也是适用的.因此,如果你想使用的算法在Q

Qt 学习之路 2(75):线程总结

前面我们已经详细介绍过有关线程的一些值得注意的事项.现在我们开始对线程做一些总结. 有关线程,你可以做的是: 在QThread子类添加信号.这是绝对安全的,并且也是正确的(前面我们已经详细介绍过,发送者的线程依附性没有关系) 不应该做的是: 调用moveToThread(this)函数 指定连接类型:这通常意味着你正在做错误的事情,比如将QThread控制接口与业务逻辑混杂在了一起(而这应该放在该线程的一个独立对象中) 在QThread子类添加槽函数:这意味着它们将在错误的线程被调用,也就是QT

Qt学习之路(58): 进程间交互(QProcess.readAllStandardOutput可以读取控制台的输出)

所谓 IO 其实不过是与其他设备之间的数据交互.在 Linux 上这个概念或许会更加清楚一些.Linux 把所有设备都看作是一种文件,因此所有的 IO 都归结到对文件的数据交互.同样,与其他进程之间也存在着数据交互,这就是进程间交互. 为什么需要进程间交互呢?Qt 虽然是一个很庞大的库,但是也不能面面俱到.每个需求都提供一种解决方案是不现实的.比如操作系统提供了查看当前文件夹下所有文件的命令(Windows 下是 dir, Linux 下是 ls),那么 Qt 就可以通过调用这个命令获取其中的信