在 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()函数即可。调用该函数之后,所有的绘制都将限制在这个路径中,也就是所谓“裁剪”。裁剪会在恢复上次状态时被取消。

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

  1. Qt 学习之路 2(30):Graphics View Framework

    Qt 学习之路 2(30):Graphics View Framework 豆子 2012年12月11日 Qt 学习之路 2 27条评论 Graphics View 提供了一种接口,用于管理大量自定义 ...

  2. 《Qt 学习之路 2》目录

    <Qt 学习之路 2>目录 <Qt 学习之路 2>目录  豆子  2012年8月23日  Qt 学习之路 2  177条评论 <Qt 学习之路 2>目录 序 Qt ...

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

    Q_OBJECT:这是一个宏,凡是定义信号槽的类都必须声明这个宏. 函数tr()全名是QObject::tr(),被他处理过的字符串可以使用工具提取出来翻译成其他语言,也就是做国际化使用. 对于QT学 ...

  4. 转载: Qt 学习之路 2归档

    Qt 学习之路 2归档 http://www.devbean.net/2012/08/qt-study-road-2-catelog/

  5. Qt学习之路

      Qt学习之路_14(简易音乐播放器)   Qt学习之路_13(简易俄罗斯方块)   Qt学习之路_12(简易数据管理系统)   Qt学习之路_11(简易多文档编辑器)   Qt学习之路_10(Qt ...

  6. Qt 学习之路 2

    Qt 学习之路 2 | DevBean Tech World Qt 学习之路 2 Qt 学习之路 2 目录

  7. Qt 学习之路 2(76):QML 和 QtQuick 2

    Home / Qt 学习之路 2 / Qt 学习之路 2(76):QML 和 QtQuick 2 Qt 学习之路 2(76):QML 和 QtQuick 2  豆子  2013年12月18日  Qt ...

  8. Qt 学习之路 2(74):线程和 QObject

    Home / Qt 学习之路 2 / Qt 学习之路 2(74):线程和 QObject Qt 学习之路 2(74):线程和 QObject  豆子  2013年12月3日  Qt 学习之路 2  2 ...

  9. Qt 学习之路 2(73):Qt 线程相关类

    Home / Qt 学习之路 2 / Qt 学习之路 2(73):Qt 线程相关类 Qt 学习之路 2(73):Qt 线程相关类  豆子  2013年11月26日  Qt 学习之路 2  7条评论 希 ...

  10. Qt 学习之路 2(72):线程和事件循环

    Qt 学习之路 2(72):线程和事件循环 <理解不清晰,不透彻>  --  有需求的话还需要进行专题学习  豆子  2013年11月24日  Qt 学习之路 2  34条评论 前面一章我 ...

随机推荐

  1. ASP.NET 后台不识别ASPX中的控件

    请问后台不识别ASPX中的控件,怎么解决 这个程序是在网上下载的 C# code <asp:DataGrid runat="server" ID="dgList1& ...

  2. 局部视图(partial)

    局部视图(partial) 原文:Partial Views作者:Steve Smith翻译:张海龙(jiechen).刘怡(AlexLEWIS)校对:许登洋(Seay).何镇汐.魏美娟(初见) AS ...

  3. Android Learning:数据存储方案归纳与总结

    前言 最近在学习<第一行android代码>和<疯狂android讲义>,我的感触是Android应用的本质其实就是数据的处理,包括数据的接收,存储,处理以及显示,我想针对这几 ...

  4. bzoj 1079: [SCOI2008]着色方案 DP

    1079: [SCOI2008]着色方案 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 803  Solved: 512[Submit][Status ...

  5. [BZOJ 1029] [JSOI2007] 建筑抢修 【贪心】

    题目链接:BZOJ - 1029 题目分析 使用一种贪心策略. 现将任务按照deadline从小到大排序. 然后枚举每一个任务,如果当前消耗的时间加上完成这个任务的时间不会超过这个任务的deadlin ...

  6. API通常的url语法

    ?后面带的是get方式传递的值,如果有多个值,用 & 号分割.另外正式项目一般不用get方式传递,容易被人sql注入,即所谓的入侵. 详细看这篇http://www.cnblogs.com/k ...

  7. Java语言基础(四) String和StringBuffer的区别

    Java提供了两个字符串类:String和StringBuffer. String提供了数值不可变的字符串,而StringBuffer提供的字符串对象可以进行修改. 当知道字符数据要改变的时候就可以使 ...

  8. SpringMVC+Json构建基于Restful风格的应用(转)

    一.spring 版本:spring-framework-3.2.7.RELEASE 二.所需其它Jar包: 三.主要代码: web.xml <?xml version="1.0&qu ...

  9. wpf 资源的重用

    资源的利用 1) Window.Resource <Window.Resource> <ImageBrush x:Key="TitleBrush" TileMod ...

  10. 【POJ】2117 Electricity

    无向图求割点和连通块. /* POJ2117 */ #include <iostream> #include <vector> #include <algorithm&g ...