#(88):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元素看起來就像一個畫板。這個對象支持畫筆、填充、漸變、文本以及其它一系列路徑創建函數。
下面我們看一個簡單的路徑繪制的例子:
~~~
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 像素的矩形。這個矩形使用鋼鐵藍填充,并且具有藍色邊框。程序運行結果如下所示:
Canvas
讓我們來仔細分析下這段代碼。首先,畫筆的寬度設置為 4 像素;使用strokeStyle屬性,將畫筆的顏色設置為藍色。fillStyle屬性則是設置填充色為 steelblue。只有當調用了stroke()或fill()函數時,真實的繪制才會執行。當然,我們也完全可以獨立使用這兩個函數,而不是一起。調用stroke()或fill()函數意味著將當前路徑繪制出來。需要注意的是,路徑是不能夠被復用的,只有當前繪制狀態才能夠被復用。所謂“當前繪制狀態”,指的是當前的畫筆顏色、寬度、填充色等屬性。
在 QML 中,Canvas元素就是一種繪制的容器。2D 上下文對象作為實際繪制的執行者。繪制過程必須在onPaint事件處理函數中完成。下面即一個代碼框架:
~~~
Canvas {
width: 200; height: 200
onPaint: {
var ctx = getContext("2d")
// 設置繪制屬性
// 開始繪制
}
}
~~~
Canvas本身提供一個典型的二維坐標系,原點在左上角,X 軸正方向向右,Y 軸正方向向下。使用Canvas進行繪制的典型過程是:
設置畫筆和填充樣式
創建路徑
應用畫筆和填充
例如:
~~~
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還提供了一系列方便使用的函數,用于一次添加一個矩形等,例如:
~~~
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)
}
}
~~~
代碼運行結果如下:
canvas rectangle
注意藍色邊框的位置。在繪制邊框時,畫筆會沿著路徑進行繪制。上面給出的 4 像素邊框,其中心點為路徑,因此會有 2 像素在路徑外側,2 像素在路徑內側。
##漸變
Canvas元素可以使用顏色進行填充,同樣也可以使用漸變。例如下面的代碼:
~~~
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)
}
~~~
運行結果如下所示:
canvas 漸變
在這個例子中,漸變的起始點位于 (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屬性定義陰影模糊的程度。不僅是陰影,利用這種效果,我們也可以實現一種圍繞在路徑周邊的發光特效。下面的例子中,我們將創建一個帶有發光效果的文本。為了更明顯的顯示發光效果,其背景界面將會是深色的。下面是相應的代碼:
~~~
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 陰影
注意觀察字母旁邊的發光效果,這其實是使用陰影制作的。
##圖像
Canvas元素支持從多種源繪制圖像。為了繪制圖像,需要首先加載圖像;使用Component.onCompleted事件處理函數可以達到這一目的:
~~~
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")
}
~~~
代碼運行結果如下:
canvas image
左側的地球圖像繪制在左上角坐標為 (10, 10) 的位置;右側的圖像應用了路徑裁剪。圖像和路徑都可以被另外的路徑裁剪,只需使用clip()函數即可。調用該函數之后,所有的繪制都將限制在這個路徑中,也就是所謂“裁剪”。裁剪會在恢復上次狀態時被取消。
- (1)序
- (2)Qt 簡介
- (3)Hello, world!
- (4)信號槽
- (5)自定義信號槽
- (6)Qt 模塊簡介
- (7)MainWindow 簡介
- (8)添加動作
- (9)資源文件
- (10)對象模型
- (11)布局管理器
- (12)菜單欄、工具欄和狀態欄
- (13)對話框簡介
- (14)對話框數據傳遞
- (15)標準對話框 QMessageBox
- (16)深入 Qt5 信號槽新語法
- (17)文件對話框
- (18)事件
- (19)事件的接受與忽略
- (21)事件過濾器
- (22)事件總結
- (23)自定義事件
- (24)Qt 繪制系統簡介
- (25)畫刷和畫筆
- (26)反走樣
- (27)漸變
- (28)坐標系統
- (29)繪制設備
- (30)Graphics View Framework
- (31)貪吃蛇游戲(1)
- (32)貪吃蛇游戲(2)
- (33)貪吃蛇游戲(3)
- (34)貪吃蛇游戲(4)
- (35)文件
- (36)二進制文件讀寫
- (37)文本文件讀寫
- (38)存儲容器
- (39)遍歷容器
- (40)隱式數據共享
- (41)model/view 架構
- (42)QListWidget、QTreeWidget 和 QTableWidget
- (43)QStringListModel
- (44)QFileSystemModel
- (45)模型
- (46)視圖和委托
- (47)視圖選擇
- (48)QSortFilterProxyModel
- (49)自定義只讀模型
- (50)自定義可編輯模型
- (51)布爾表達式樹模型
- (52)使用拖放
- (53)自定義拖放數據
- (54)剪貼板
- (55)數據庫操作
- (56)使用模型操作數據庫
- (57)可視化顯示數據庫數據
- (58)編輯數據庫外鍵
- (59)使用流處理 XML
- (60)使用 DOM 處理 XML
- (61)使用 SAX 處理 XML
- (62)保存 XML
- (63)使用 QJson 處理 JSON
- (64)使用 QJsonDocument 處理 JSON
- (65)訪問網絡(1)
- (66)訪問網絡(2)
- (67)訪問網絡(3)
- (68)訪問網絡(4)
- (69)進程
- (70)進程間通信
- (71)線程簡介
- (72)線程和事件循環
- (73)Qt 線程相關類
- (74)線程和 QObject
- (75)線程總結
- (76)QML 和 QtQuick 2
- (77)QML 語法
- (78)QML 基本元素
- (79)QML 組件
- (80)定位器
- (81)元素布局
- (82)輸入元素
- (83)Qt Quick Controls
- (84)Repeater
- (85)動態視圖
- (86)視圖代理
- (87)模型-視圖高級技術
- (88)Canvas
- (89)Canvas(續)