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(); //使用画笔颜色勾勒边框
}
}
//=========================================
}

Canvas {
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(); //使用画笔颜色勾勒边框
}
}
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绘制路径的一般步骤:
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()
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();
}
}
}

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文件在同一目录下,因为是通过相对路径引用的;
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();
}
}
}

context.translate(width/2,height/2);将坐标系平移到了画布中心,那么画布中心就是(0,0)点了;restore()来恢复之前的画布状态,否则发生重绘时(比如用户拖动窗口改变大小),你就看不见绘图的图元了,而要restore()必先save();translate(real x, real y)方法,平移画布原点,x、y两个参数是相对于当前原点的偏移量;当然,也可以不多次平移,后面的针对平移后的画布原点计算坐标即可,例如上例:
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()方法,让我们能够根据当前路径包围的区域来裁切后续的绘图操作,在此区域之外的图像会被毫不留情的丢弃掉。
步骤如下:
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 | 只有新图形会被保留,其它都被清除掉 |
| xor | 重叠部分会变成透明的 |