文章目錄
- 一、繪圖系統四要素
- 1.1、基本繪圖模式
- 二、繪制路徑
- 三、繪制文本
- 四、繪制圖檔
- 五、變換
- 六、裁切
- 七、圖像合成
一、繪圖系統四要素
- 畫布(Canvas,類似于Widget的QPaintDevice)
- 畫師(Context2d,類似于Widget的QPainter)
- 畫筆(strokeStyle屬性,類似于Widget的QPen)
- 畫刷(fillStyle屬性,類似于Widget的QBrush)
1.1、基本繪圖模式
Canvas是Item的派生類,通過設定width和height屬性,就可以定一個繪圖區域,然後在onPaint()信号處理器内使用Context2D對象來繪圖。
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
width: 640
height: 480
visible: true
//=========================================
Canvas {
width: 400;
height: 200;
onPaint: {
var ctx = getContext("2d"); //擷取畫師,每個畫布都有一個獨一無二的畫師
ctx.lineWidth = 2; //設定畫布寬度
ctx.strokeStyle = "red"; //設定畫布顔色
ctx.fillStyle = "blue"; //設定畫刷顔色
ctx.beginPath(); //開始繪圖信号
ctx.rect(100,80,120,80); //繪制一個矩形
ctx.fill(); //使用畫刷顔色填充
ctx.stroke(); //使用畫筆顔色勾勒邊框
}
}
//=========================================
}

- paint是Canvas的信号,當需要繪圖(更新)時會觸發,開發者通過實作名為onPaint的信号處理器來響應paint信号,在信号處理器内進行繪圖;
- Context2D是QML中負責2D繪圖的對象,與Canvas結合使用,有兩種使用Context2D對象的方式:
- 1、在onPaint信号處理器中調用getContext(“2d”)擷取Context2D對象;
- 2、設定Canvas對象的contextType屬性(2D繪圖時取值為“2d”)後,context屬性就會儲存一個可用的Context2D對象;故上例也可以寫為如下方式:
{
width: 400;
height: 200;
contextType: "2d";
onPaint: {
context.lineWidth = 2; //設定畫布寬度
context.strokeStyle = "red"; //設定畫布顔色
context.fillStyle = "blue"; //設定畫刷顔色
context.beginPath(); //開始繪圖信号
context.rect(100,80,120,80); //繪制一個矩形
context.fill(); //使用畫刷顔色填充
context.stroke(); //使用畫筆顔色勾勒邊框
}
}
- fillStyle和Qt C++中的QBrush類似,用于儲存填充圖元的畫刷,它可以是一個顔色值,也可以是CanvasGradient或CanvasPattern對象
- 當使用顔色時,支援下列文法:
- rgb:例如 rgb(255,100,55);
- rgba:例如(255,100,50,1.0);
- #RRGGBB:例如#00FFCC;
- Qt.rgba(red,green,blue,alpha),例如 Qt.rgba(0.3,0.7,1,1.0),類似的還有Qt.hsla()、Qt.lighter()等;
- Context2D的createLinearGradient()方法用于建立一個線性漸變對象,createRadialGradient()方法用于建立一個放射漸變對象(類型為CanvasGradient),CanvasGradient對象的addColorStop()可以添加漸變路徑上的關鍵點的顔色,例如:
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
width: 640
height: 480
visible: true
Canvas {
width: 400;
height: 200;
onPaint: {
var ctx = getContext("2d");
ctx.lineWidth = 2;
var gradient = ctx.createLinearGradient(60,50,180,130);
gradient.addColorStop(0.0,Qt.rgba(1,0,0,1.0));
gradient.addColorStop(1.0,Qt.rgba(0,0,1,1.0));
ctx.strokeStyle = "red";
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.rect(60,50,120,80);
ctx.fill();
ctx.stroke();
gradient = ctx.createRadialGradient(230,160,30,260,200,20);
gradient.addColorStop(0.0,Qt.rgba(1,0,0,1.0));
gradient.addColorStop(1.0,Qt.rgba(0,0,1,1.0));
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.rect(200,140,80,80);
ctx.fill();
ctx.stroke();
}
}
}
二、繪制路徑
使用Context2D繪制路徑的一般步驟:
- 調用beginPath();
- 調用moveTo()【移動到】、lineTo()【連線到】、arcTo()【畫弧到】、rect()【矩形】、quadraticCurveTo()【二次方貝塞爾曲線】、arc()【弧線】、bezierCurveTo()【三次方貝塞爾曲線】、ellipse()【橢圓】、text()【文字】等構造路徑元素的方法;
- 調用fill()或stroke();
- 調用closePath()方法用于結束目前的路徑,從路徑終點到起點繪制一條直線來封閉路徑(可選非必須);
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
width: 640
height: 480
visible: true
Canvas {
width: 400;
height: 300;
id: root;
onPaint: {
var ctx = getContext("2d");
ctx.lineWidth = 2;
ctx.strokeStyle = "red";
ctx.font = "42px sans-serif";
var gradient = ctx.createLinearGradient(0,0,width,height);
gradient.addColorStop(0.0,Qt.rgba(0,1,0,1));
gradient.addColorStop(1.0,Qt.rgba(0,0,1,1));
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.moveTo(4,4);
ctx.bezierCurveTo(0,height-1,width-1,height/2,width/4,height/4);
ctx.lineTo(width/2,height/4);
ctx.arc(width*5/8,height/4,width/8,Math.PI,0,false);
ctx.ellipse(width*11/16,height/4,width/8,height/4);
ctx.lineTo(width/2,height*7/8);
ctx.text("Complex Path",width/4,height*7/8);
ctx.fill();
ctx.stroke();
}
}
}
三、繪制文本
Context2D對象與文本相關的方法有三個:fillText()、strokeText()、text()
- fillText()方法使用fillStyle填充文字;
- strokeText()使用strokeStyle描畫文字邊框;
- text()方法在路徑上添加一串文本作為構成路徑的元素之一
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
width: 640
height: 480
visible: true
Canvas {
width: 400;
height: 300;
id: root;
onPaint: {
var ctx = getContext("2d");
ctx.lineWidth = 2;
ctx.strokeStyle = "red";
ctx.font = "42px sans-serif";
var gradient = ctx.createLinearGradient(0,0,width,height);
gradient.addColorStop(0.0,Qt.rgba(0,1,0,1));
gradient.addColorStop(1.0,Qt.rgba(0,0,1,1));
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.text("Fill Text On Path",10,50);
ctx.fill();
ctx.fillText("Fill Text",10,100);
ctx.beginPath();
ctx.text("Stroke Text On Path",10,150);
ctx.stroke();
ctx.strokeText("Stroke Text",10,200);
ctx.beginPath();
ctx.text("Stroke and Fill Text on Path",10,250);
ctx.stroke();
ctx.fill();
}
}
}
- 描述font屬性的文法與CSS font屬性相同,font屬性的預設值為“ 10px sans-serif ”;
- Context2D的font屬性,允許我們按順序設定字型的下列屬性:
- font-style(可選):可以取normal、italic、oblique三值之一;
- font-variant(可選):可以取normal、small-caps二值之一;
- font-weight(可選):可以取normal、bold二值之一,或0-99的數字;
- font-size:取Npx或Npt,其中N為數字,px代表像素,pt代表點,對于移動裝置,使用pt為機關更合适一些,能夠适應各種螢幕尺寸;
- font-family:常見的有serif、sans-serif、cursive、fantasy、monospace,詳情參考http://www.w3.org/TR/CSS2/fonts.html#propdef-font-family
四、繪制圖檔
1、drawImage(variant image, real dx, read dy)
它在(dx,dy)位置繪制指定的image對象代表的圖檔,image可以是一個Image元素、一個圖檔URL,或者一個CanvasImageData對象
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
width: 640
height: 480
visible: true
Canvas {
width: 400;
height: 300;
id: root;
property var dartlikeWeapon: "dartlike_weapon.png"
onPaint: {
var ctx = getContext("2d");
ctx.drawImage(dartlikeWeapon,0,0);
}
Component.onCompleted: {
loadImage(dartlikeWeapon);
console.log("圖檔加載:",isImageLoaded(dartlikeWeapon));
}
onImageLoaded: {
requestPaint();
}
}
}
注意:需要確定dartlike_weapon.png和QML檔案在同一目錄下,因為是通過相對路徑引用的;
- Canvas對象内定義了一個dartlikeWeapon屬性來儲存圖檔URL;
- 然後在Component.onCompleted附加信号處理器内調用Cancas的loadImage()方法來加載圖檔;該方法會異步加載圖檔,當圖檔加載完成時,發射imageLoaded信号;
- 然後在對應的信号處理器onImageLoaded内調用了requestPaint()方法來重繪Canvas;
- 可以通過Canvas的isImageError()、isImageLoaded()兩個方法來判斷圖檔是否加載成功,它們接受和loadImage()同樣的參數,傳回布爾值;
- 隻有成功加載的圖檔,才可以使用Context2D來繪制:
- 在Canvas内定義的darklikeWeapon,可以了解為圖檔索引,加載圖檔、判斷圖檔是否成功加載、繪制圖檔,都适用這個索引;
2、一個Canvas可以加載多張圖檔,既可以加載本地圖檔,也可以加載網絡圖檔
import QtQuick 2.2
Canvas {
width: 400;
height: 300;
id: root;
property var dartlikeWeapon: "dartlike_weapon.png";
property var poster: "https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png"
onPaint: {
var ctx = getContext("2d");
ctx.drawImage(poster,120,0);
ctx.drawImage(dartlikeWeapon,0,0);
}
Component.onCompleted: {
loadImage(dartlikeWeapon);
loadImage(poster)
}
onImageLoaded: requestPaint();
}
3、繪制一個Image元素
import QtQuick 2.2
Canvas {
width: 400;
height: 300;
id: root;
Image {
id: poter;
source: "https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png";
visible: false;
onStatusChanged: {
if(status == Image.Ready) {
root.requestPaint();
}
}
}
onPaint: {
var ctx = getContext("2d");
ctx.drawImage(poster,50,0);
}
Component.onCompleted: {
loadImage(poster)
}
onImageLoaded: requestPaint();
}
五、變換
就像QPainter一樣,Context2D也支援平移(translate())、旋轉(rotate())、縮放(scale())、錯切(shear())等簡單的圖像變換,它還支援簡單的矩陣變換(setTransform()):
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
width: 400
height: 400
visible: true
Canvas {
width: 300;
height: 300;
id: root;
contextType: "2d"
onPaint: {
context.lineWidth = 2;
context.strokeStyle = "blue";
context.fillStyle = "red";
context.save();
context.translate(width/2,height/2);
context.beginPath();
context.arc(0,0,30,0,Math.PI*2);
context.arc(0,0,50,0,Math.PI*2);
context.arc(0,0,70,0,Math.PI*2);
context.arc(0,0,90,0,Math.PI*2);
context.stroke();
context.restore();
context.save();
context.translate(width/2,30);
context.font = "26px serif";
context.textAlign = "center";
context.fillText("concentric circles",0,0);
context.restore();
}
}
}
- 繪制同心圓時,我們以(0,0)做圓心,按道理應該是在畫布左上角繪制,但是結果确實在畫布中心繪制,是因為使用
将坐标系平移到了畫布中心,那麼畫布中心就是(0,0)點了;context.translate(width/2,height/2);
- 平移變換、繪圖操作完成後,應當調用
來恢複之前的畫布狀态,否則發生重繪時(比如使用者拖動視窗改變大小),你就看不見繪圖的圖元了,而要restore()
必先restore()
;save()
-
方法,平移畫布原點,x、y兩個參數是相對于目前原點的偏移量;translate(real x, real y)
- 示例中為了使“concentric circles”在畫布上方居中顯示,先恢複畫布原點到左上角,然後sava(),接着再變換到(width/2,30),接着設定了textAlign屬性為“center”,最後調用fillText()在原點處繪制文本,畫完後再次調用restore();
當然,也可以不多次平移,後面的針對平移後的畫布原點計算坐标即可,例如上例:
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
width: 400
height: 400
visible: true
Canvas {
width: 300;
height: 300;
id: root;
contextType: "2d"
onPaint: {
context.lineWidth = 2;
context.strokeStyle = "blue";
context.fillStyle = "red";
//context.save();
context.translate(width/2,height/2);
context.beginPath();
context.arc(0,0,30,0,Math.PI*2);
context.arc(0,0,50,0,Math.PI*2);
context.arc(0,0,70,0,Math.PI*2);
context.arc(0,0,90,0,Math.PI*2);
context.stroke();
//context.restore();
//context.save();
//context.translate(width/2,30);
context.font = "26px serif";
context.textAlign = "center";
context.fillText("concentric circles",0,-height/2+30);
//context.restore();
}
}
}
六、裁切
Context2D的
clip()
方法,讓我們能夠根據目前路徑包圍的區域來裁切後續的繪圖操作,在此區域之外的圖像會被毫不留情的丢棄掉。
步驟如下:
- 調用beginPath();
- 使用lineTo()、arc()、bezierCurveTo()、moveTo()、closePath()等建立路徑;
- 調用clip()确定裁切區域;
- 繪圖,如使用drawImage();
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
id: root;
width: 400
height: 400
visible: true
Canvas {
width: 400;
height: 400;
contextType: "2d";
property var comicRole: "https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png";
onPaint: {
context.lineWidth = 2;
context.strokeStyle = "blue";
context.fillStyle = Qt.rgba(0.3,0.5,0.7,0.3);
context.save();
context.beginPath();
context.arc(180,150,80,0,Math.PI*2,true);
context.moveTo(180,230);
context.lineTo(420,280);
context.lineTo(160,320);
context.closePath();
context.clip();
context.drawImage(comicRole,0,0,600,600,0,0,400,400);
context.stroke();
context.fill();
context.rotate(Math.PI / 5);
context.font = " italic bold 32px serif";
context.fillStyle = "red";
context.fillText("the text will be clipped",110,70);
context.restore();
}
Component.onCompleted: loadImage(comicRole);
onImageLoaded: requestPaint();
}
}
七、圖像合成
Context2D允許我們繪制一個圖元,将其與已有的圖像按照
globalCompositeOperation
屬性指定的模式合成,
globalCompositeOperation
支援下列模式:
模式 | 詳細說明 |
source-over | 預設模式,新圖形覆寫在原有内容之上 |
source-in | 新圖形中僅僅出現與原有内容重疊的部分,其它區域變成透明的 |
source-out | 隻有新圖形中與原有内容不重疊的部分會被繪制出來 |
source-atop | 新圖形中與原有内容重疊的部分會被繪制,并覆寫于原有内容之上 |
destination-over | 在原有内容至下繪制新圖形 |
destination-in | 原有内容中與新圖形重疊的部分會被保留,其它區域變成透明的 |
destination-out | 原有内容中與新圖形不重疊的部分會被保留 |
destination-atop | 原有内容中與新内容重疊的部分會被保留,并且在原有内容至下繪制新圖形 |
lighter | 兩圖形中的重疊部分做加色處理 |
copy | 隻有新圖形會被保留,其它都被清除掉 |