画布
画布元素(canvas element)的基本思想是 使用一个2D 对象来渲染路径。2D对象支持画笔,填充、渐变,文本和绘制路径创建命令。
- strokeStyle: 画笔样式
- fillStyle: 填充样式
注意: 只有调用 stroke 或 fill 函数, 创建的路径才会绘制
注意:画布元素 充当绘制的容器。 2D 绘制对象提供了实际绘制的方法,绘制需要在 onPaint 事件中完成。在重置路径后,需要设置一个开始点,所以在 beginPath() 这个操作后,需要使用 moveTo 来设置开始点。


便捷接口
我们获取的 2d 对象 提供一些常用 图形接口(矩形,圆形、弧形等)不需要 调用stroke 或者 fill 完成。
注意:画笔的绘制区域 由中间向 两边延展。 一个宽度为 4 像素的画笔 将会在绘制路径的 里面 绘制2个 像素,外面绘制 2个像素。
onPaint: {
var ctx = getContext("2d")
ctx.fillStyle = 'green'
ctx.strokeStyle = "blue"
ctx.lineWidth = 4
// draw a filles rectangle
ctx.fillRect(20, 20, 80, 80)
// 绘制圆形
ctx.beginPath();
ctx.arc(200, 75, 50, 0, 2 * Math.PI, false);
ctx.fill();
// 绘制弧线
ctx.beginPath();
ctx.arc(200, 225, 50, 0, Math.PI, false);
ctx.stroke();
// cut our an inner rectangle
ctx.clearRect(30,30, 60, 60)
// stroke a border from top-left to
// inner center of the larger rectangle
ctx.strokeRect(20,20, 40, 40)
// 绘制路径 三角形
ctx.beginPath();
ctx.moveTo(250, 25);
ctx.lineTo(350, 25);
ctx.lineTo(350, 125);
ctx.closePath(); // 关闭路径
ctx.stroke();
}
渐变
渐变色是在 画布坐标下定义的, 而不是在 绘制路径 相对坐标下定义的。 画布中没有相对坐标的概念。

阴影
2d 对象 的路径可以使用阴影增强来显示效果。阴影是 一个区域的轮廓线 使用偏移量、颜色和模糊来实现的。黑色背景可能有更好效果
-
shadowColor: 阴影色
-
shadowOffsetX:阴影 X 轴偏移值
-
shadowOffsetY:阴影Y轴偏移值
-
shadowBlur: 阴影模糊
带模糊效果(shadowBlur):
图片
QML 画布支持多种资源的图片绘制 ,drawImage
常用的版本 接受三个参数:要绘制的图像、目标位置的 x 坐标和 y 坐标。
注意:画布中使用 一张图片 需要 先加载图片资源(可以使用Componet中的loadImage 或者直接使用 Image 而不显示)

绘制一个足球并进行裁剪
- translate: 平移坐标系
- clip: 裁剪
绘制特定部分
ctx.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh);
- sx, sy:源图像中要复制区域的左上角坐标。
- sw, sh:源图像中要复制区域的宽度和高度。
- dx, dy:目标画布上放置图像的左上角坐标。
- dw, dh:目标画布上绘制图像的宽度和高度。
放大后的效果:
转换
转换坐标系(缩放(scale)、旋转( rotate)、平移(translate))
先绘制一个一个矩形,移动坐标系,然后再绘制一个矩形
ctx.translate(120,60) x移动到120,y移动到60。注意 如果重复移动 是相对新的坐标原点
组合模式
组合模式允许 绘制一个形状然后与已有的像素点集合 混合
-
source-over:新的绘制内容(即源 source) 会被绘制到现有内容(即目标, destination)之上。 如果新绘制的内容和已有内容有重叠,重叠部分会显示新的内容, 而新内容之外的部分保持不变。
-
source-in : 同上,在里面
-
source-out: 同上,在外面
-
source-atop:同上, 在下面




所有代码:
import QtQuick 2.15
Canvas{
id: root
width: 1200
height: 700
// onPaint: {
// var ctx = getContext("2d")
// ctx.lineWidth = 4
// ctx.strokeStyle = "blue"
// ctx.fillStyle = "steelblue"
// // begin a new path to draw
// ctx.beginPath()
// // top-left start point 左上角的开始点
// ctx.moveTo(50,50)
// // upper line
// ctx.lineTo(150,50)
// // right line
// ctx.lineTo(150,150)
// // bottom line
// ctx.lineTo(50,150)
// // left line through path closing
// ctx.closePath()
// // fill using fill style
// ctx.fill()
// // stroke using line width and stroke style
// ctx.stroke()
// }
// onPaint: {
// var ctx = getContext("2d")
// ctx.fillStyle = 'green'
// ctx.strokeStyle = "blue"
// ctx.lineWidth = 4
// // draw a filles rectangle
// ctx.fillRect(20, 20, 80, 80)
// // 绘制圆形
// ctx.beginPath();
// ctx.arc(200, 75, 50, 0, 2 * Math.PI, false);
// ctx.fill();
// // 绘制弧线
// ctx.beginPath();
// ctx.arc(200, 225, 50, 0, Math.PI, false);
// ctx.stroke();
// // cut our an inner rectangle
// ctx.clearRect(30,30, 60, 60)
// // stroke a border from top-left to
// // inner center of the larger rectangle
// ctx.strokeRect(20,20, 40, 40)
// // 绘制路径 三角形
// ctx.beginPath();
// ctx.moveTo(250, 25);
// ctx.lineTo(350, 25);
// ctx.lineTo(350, 125);
// ctx.closePath(); // 关闭路径
// ctx.stroke();
// }
// onPaint:{
// var ctx = getContext("2d")
// // var gradient = ctx.createLinearGradient(100,0,100,200)
// // gradient.addColorStop(0, "blue")
// // gradient.addColorStop(0.5, "lightsteelblue")
// // 创建线性渐变
// var gradient = ctx.createLinearGradient(0, 0, 300, 100); // 从左上角到右下角
// //addColorStop 方法用来设置渐变的颜色过渡点,其中第一个参数是位置,第二个参数是颜色。
// gradient.addColorStop(0, "red"); // 起始颜色
// gradient.addColorStop(0.5, "yellow"); // 中间颜色
// gradient.addColorStop(1, "blue"); // 结束颜色
// // 设置填充样式为渐变
// ctx.fillStyle = gradient
// //渐变⾊的定义⽐我们想要绘制的矩形更⼤,所以矩形在它定义的范围内对渐变进⾏了裁剪。
// //ctx.fillRect(100,0,100,100)
// ctx.fillRect(0,0,300,100)
// }
// onPaint:{
// var ctx = getContext("2d")
// ctx.strokeStyle = "#333"
// ctx.shadowColor = "blue";
// ctx.shadowOffsetX = 2;
// ctx.shadowOffsetY = 2;
// // next line crashes
// // ctx.shadowBlur = 10;
// ctx.font = 'Bold 80px ';
// ctx.fillStyle = "#33a9ff";
// ctx.fillText("Earth",30,180);
// }
// onPaint:{
// var ctx = getContext("2d")
// // //要绘制的图像、目标位置的 x 坐标和 y 坐标。
// // ctx.drawImage("football.jpg", -150, -150)
// // // 确保图像已经加载完成
// // // if (myImage.status === Image.Ready) {
// // // // 绘制图像
// // // ctx.drawImage(myImage,-150, -150); // 在 (-150, -150) 位置开始绘制图像
// // // }
// // ctx.save()
// // ctx.strokeStyle = 'red'//
// // // create a triangle as clip region
// // ctx.beginPath()
// // ctx.moveTo(10,10)
// // ctx.lineTo(610,10)
// // ctx.lineTo(300,300)
// // ctx.closePath()
// // // translate(平移) coordinate system
// // //translate 方法接受两个参数:要移动的 x 和 y 坐标值 调用 translate 后,所有后续的绘图操作都会相对于新的坐标系进行。
// // ctx.translate(150,0)
// // //clip 之前所有的绘制操作都会⽤来进⾏裁剪
// // ctx.clip() // create clip from triangle path
// // // draw image with clip applied
// // ctx.drawImage("football.jpg", -150, -150)//在裁剪区域重新绘制
// // // draw stroke around path
// // ctx.stroke()
// // // restore previous setup
// // ctx.restore()
// // // 清除画布
// // ctx.clearRect(0, 0, width, height);
// // // 检查图像是否已准备好
// // if (myImage.status === Image.Ready) {
// // // 绘制图像的某一部分
// // ctx.drawImage(myImage, 50, 50, 300, 300, 50, 50, 600, 600);
// // // 从原图(50, 50)处开始,取100x100大小的区域,然后绘制到画布(50, 50)处,并放大至200x200
// // }
// }
// onPaint:{
// var ctx = getContext("2d")
// ctx.strokeStyle = "blue"
// ctx.lineWidth = 4
// ctx.beginPath()
// // ctx.rect(-20, -20, 40, 40)
// ctx.rect(50, 50, 40, 40)
// ctx.translate(200, 200)
// ctx.stroke()
// ctx.strokeStyle = "green"
// ctx.rotat(Math.PI/4)
// ctx.stroke()
// }
// Component.onCompleted: {
// loadImage("football.jpg")
// }
// // 加载图像
// Image {
// id: myImage
// source: "football.jpg" // 替换为你的图片路径
// visible: false // 不需要显示这个Image元素
// }
// onPaint: {
// var ctx = getContext("2d")
// ctx.lineWidth = 4
// ctx.strokeStyle = "blue"
// ctx.fillStyle = "steelblue"
// // begin a new path to draw
// ctx.beginPath()
// // top-left start point
// ctx.moveTo(50,50)
// // upper line
// ctx.lineTo(150,50)
// // right line
// ctx.lineTo(150,150)
// // bottom line
// ctx.lineTo(50,150)
// // left line through path closing
// ctx.closePath()
// // fill using fill style
// ctx.fill()
// // stroke using line width and stroke style
// ctx.stroke()
// }
// onPaint: {
// var ctx = getContext("2d")
// ctx.fillStyle = 'green'
// ctx.strokeStyle = "blue"
// ctx.lineWidth = 4
// // draw a filles rectangle
// ctx.fillRect(20, 20, 80, 80)
// // cut our an inner rectangle
// ctx.clearRect(30,30, 60, 60)
// // stroke a border from top-left to
// // inner center of the larger rectangle
// ctx.strokeRect(20,20, 40, 40)
// }
// 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)
// }
// transform: [
// Rotation { origin.x: 120; origin.y: 60; angle: 45 } // 绕 (120, 60) 点旋转 45 度
// ]
// onPaint: {
// var ctx = getContext("2d")
// ctx.strokeStyle = "blue"
// ctx.lineWidth = 4
// ctx.beginPath()
// ctx.rect(100, 40, 40, 40)
// //ctx.rect(4, 4, 40, 40)
// // 设置旋转中心点
// ctx.translate(120,60)
// ctx.stroke()
// // 设置旋转中心点
// //ctx.translate(120,60)
// ctx.rotate(Math.PI/4)
// ctx.beginPath()
// // draw path now rotated
// ctx.strokeStyle = "green"
// ctx.rect(-20, -20, 40, 40)
// ctx.stroke()
// // var ctx = getContext("2d");
// // ctx.clearRect(0, 0, width, height);
// // // 保存当前上下文状态
// // ctx.save();
// // // ctx.strokeStyle = "blue"
// // // ctx.rect(-20, -20, 40, 40)
// // // 设置旋转中心点
// // //ctx.translate(width / 2, height / 2);
// // // 旋转上下文
// // //ctx.rotate(Math.PI / 4); // 旋转 45 度
// // // 绘制矩形
// // ctx.fillStyle = "red";
// // ctx.fillRect(-50, -50, 100, 100);
// // // 恢复上下文状态
// // ctx.restore();
// }
// //组合模式--xor 模式
// onPaint: {
// var ctx = getContext("2d")
// ctx.globalCompositeOperation = "xor"
// ctx.fillStyle = "#33a9ff"
// for(var i=0; i<40; i++) {
// ctx.beginPath()
// ctx.arc(Math.random()*400, Math.random()*200, 20, 0, 2*Math.PI)
// ctx.closePath()
// ctx.fill()
// }
// }
//组合模式--多模式
property var operation : [
'source-over', 'source-in', 'source-out',
'source-atop', 'destination-over', 'destination-in',
'destination-out', 'destination-atop', 'lighter',
'copy', 'xor', 'qt-clear', 'qt-destination','qt-multiply', 'qt-screen', 'qt-overlay', 'qt-darken',
'qt-lighten', 'qt-color-dodge', 'qt-color-burn',
'qt-hard-light', 'qt-soft-light', 'qt-difference',
'qt-exclusion'
]
onPaint: {
var ctx = getContext('2d')
for(var i=0; i<operation.length; i++) {
var dx = Math.floor(i%6)*100
var dy = Math.floor(i/6)*100
ctx.save()
ctx.fillStyle = '#33a9ff'
ctx.fillRect(10+dx,10+dy,60,60)
// TODO: does not work yet
ctx.globalCompositeOperation = root.operation[i]
ctx.fillStyle = '#ff33a9'
ctx.globalAlpha = 0.75
ctx.beginPath()
ctx.arc(60+dx, 60+dy, 30, 0, 2*Math.PI)
ctx.closePath()
ctx.fill()
ctx.restore()
}
}
}