当前位置: 首页 > news >正文

13. Canvas画布

一、Canvas画布

  QML 中的 Canvas,俗称画布,它用来定义一个绘图区域,画布的 原点 在左上角 (0, 0)处,x 轴 水平向右为正y垂直向下为正。我们可以使用 ECMAScript 代码来绘制直线、矩形、贝塞尔曲线、弧线、图片、文字等图元,还可以为这些图元应用填充颜色和边框颜色,甚至还可以进行低阶的像素级的操作。

  CanvasItem 的派生类,通过设置 widthheight 属性,就可以定一个绘图区域,然后在 onPaint() 信号处理器内使用 Context2D 对象来绘图。当需要绘图(更新)时会触发 paint() 信号

  Context2D 是 QML 中负责 2D 绘图的对象,与 Canvas 结合使用。有两种使用 Context2D 对象的方式,一种是在 onPaint() 信号处理器中调用 getContext("2d") 获取 Context2D 对象。另外一种是,当我们设置了 Canvas 对象的 contextType 属性(2D 绘图时取值为 "2d")后,context 属性就会保存一个可用的 Context2D 对象。

  我们可以在终端中使用 pip 安装 PySide6 模块。默认是从国外的主站上下载,因此,我们可能会遇到网络不好的情况导致下载失败。我们可以在 pip 指令后通过 -i 指定国内镜像源下载

pip install pyside6 -i https://mirrors.aliyun.com/pypi/simple

  国内常用的 pip 下载源列表:

  • 阿里云 https://mirrors.aliyun.com/pypi/simple
  • 中国科技大学 https://pypi.mirrors.ustc.edu.cn/simple
  • 清华大学 https://pypi.tuna.tsinghua.edu.cn/simple
  • 中国科学技术大学 http://pypi.mirrors.ustc.edu.cn/simple

二、绘制路径

  在 Context2D 中,我们可以使用 lineWidth 属性 设置画笔的宽度;我们可以使用 strokeStyle 属性 设置画笔的颜色;我们可以使用 fillStyle 属性 保存用于填充图元的画刷,它可以是一个颜色值,也可以是 CanvasGradientCanvasPattern 对象。

  我们可以通过 Context2D 中的如下方法绘制路径:

// 将当前路径重置为新的路径
object beginPath()// 通过绘制一条线连接到子路径的起始点来闭合当前子路径,并自动开始一个新的路径。新路径的当前点即为上一个子路径的第一个点
object closePath()                                                              // 创建一个位于点(x,y)处的新子路径
object moveTo(real x, real y)// 从当前位置画一条线至坐标为(x,y)的点处
object lineTo(real x, real y)// 在当前子路径上添加一个由给定控制点和半径构成的弧线,并通过一条直线与前一点相连                            
object arcTo(real x1, real y1, real x2, real y2, real radius)// 在当前点与终点(x,y)之间添加一条二次贝塞尔曲线,其控制点由(cpx,cpy)指定
object quadraticCurveTo(real cpx, real cpy, real x, real y)// 在当前位置与给定的终点之间添加一条由指定的控制点(cplx,cply)和(cp2x,cp2y)控制的三次贝塞尔曲线。添加曲线后,当前位置将更新为该曲线的终点(x,y)
object bezierCurveTo(real cp1x, real cp1y, real cp2x, real cp2y, real x, real y)// 在当前子路径上添加一条位于以点(x,y)为圆心、半径为radius的圆的圆周上的弧。
// anticlockwise为true时顺时针绘制,为false时逆时针绘制
// 起始角度和结束角度均是以弧度为单位,从×轴测量得出的。
object arc(real x, real y, real radius, real startAngle, real endAngle, bool anticlockwise)// 在位置(x,y)处添加一个矩形,其宽度为w,高度为h,并将其作为闭合的子路径进行绘制 
object rect(real x, real y, real w, real h)// 在由其左上角坐标(x,y)、宽度w和高度h定义的边界矩形内创建一个椭圆,并将其作为闭合子路径添加到路径中
object ellipse(real x, real y, real w, real h)

  在绘制完路径之后,我们可以调用 fill() 方法 填充路径,或者调用 stroke() 方法 进行描边

object fill()                                                                   // 用当前的填充样式填充子路径
object stroke()                                                                 // 使用当前的描边样式对子路径进行描边处理

  我们新建一个 template.py 文件。

import sysfrom PySide6.QtWidgets import QApplication
from PySide6.QtQml import QQmlApplicationEngineif __name__ == "__main__":app = QApplication(sys.argv)                                                # 1.创建一个QApplication类的实例engine = QQmlApplicationEngine()                                            # 2.创建QML引擎对象engine.load("template.qml")                                                 # 3.加载QML文件sys.exit(app.exec())                                                        # 4.进入程序的主循环并通过exit()函数确保主循环安全结束

  我们新建一个 template.qml 文件。

import QtQuick.Window
import QtQuick.Controls// Window控件表示一个顶级窗口
// 在QML中,元素是通过大括号{}内的属性来配置的。
Window {id: windowIdwidth: 800                                                                  // 窗口的宽度height: 600                                                                 // 窗口的高度visible: true                                                               // 显示窗口color: "lightgray"                                                          // 窗口的背景颜色Canvas {id: canvasIdanchors.fill: parentcontextType: "2d"// 当需要绘图(更新)时会触发paint()信号onPaint: {context.lineWidth = 10                                              // 设置画笔宽度context.strokeStyle = "#FF6666"                                     // 设置画笔颜色context.fillStyle = "#99CCFF"                                       // 设置填充颜色 context.rect(10, 10, 120, 80)                                       // 绘制矩形context.fill()                                                      // 填充路径context.stroke()                                                    // 绘制路径context.ellipse(150, 100, 100, 100)                                 // 绘制椭圆context.fill()                                                      // 填充路径context.moveTo(260, 260)                                            // 创建一个位于点(x,y)处的新子路径context.lineTo(360, 360)                                            // 从当前位置画一条线至坐标为(x,y)的点处context.lineTo(400, 300)                                            // 从当前位置画一条线至坐标为(x,y)的点处context.closePath()                                                 // 通过绘制一条线连接到子路径的起始点来闭合当前子路径context.stroke()                                                    // 绘制路径}}
}

绘制路径

三、渐变填充

  我们可以使用 Context2D 的如下方法创建一个渐变:

// 返回一个CanvasGradient对象,该对象代表一条线性渐变,它沿着从起始点(x0,y0)到结束点(x1,y1)的一条线来改变颜色
object createLinearGradient(real x0, real y0, real x1, real y1)// 返回一个CanvasGradient对象,该对象代表一个径向渐变,用于沿着由起始圆(圆心为(x0,y0)且半径为r0,终点圆的圆心为(x1,y1)且半径为r1)所确定的圆锥进行绘制。
object createRadialGradient(real x0, real y0, real r0, real x1, real y1, real r1)// 通过绘制一条线连接到子路径的起始点来闭合当前子路径,并自动开始一个新的路径。新路径的当前点即为上一个子路径的第一个点
object createConicalGradient(real x, real y, real angle)

  我们可以使用 CanvasGradient 对象的 addColorStop() 方法 添加渐变路径上的关键点的颜色

// 在给定的偏移量处为渐变添加带有给定颜色的色点。0.0是渐变一端的偏移量,1.0是另一端的偏移量
CanvasGradient addColorStop(real offset, string color)

  我们新建一个 template.qml 文件。

import QtQuick.Window
import QtQuick.Controls// Window控件表示一个顶级窗口
// 在QML中,元素是通过大括号{}内的属性来配置的。
Window {id: windowIdwidth: 800                                                                  // 窗口的宽度height: 600                                                                 // 窗口的高度visible: true                                                               // 显示窗口color: "lightgray"                                                          // 窗口的背景颜色Canvas {id: canvasIdanchors.fill: parent// 当需要绘图(更新)时会触发paint()信号onPaint: {var ctx = getContext("2d")ctx.beginPath()                                                     // 将当前路径重置为新的路径ctx.rect(10, 10, 385, 285)                                          // 绘制矩形var gradient = ctx.createLinearGradient(10, 10, 385, 285)           // 创建线性渐变gradient.addColorStop(0, "#99CCFF")                                 // 添加渐变路径上的关键点的颜色gradient.addColorStop(0.3, "#9933CC")                               // 添加渐变路径上的关键点的颜色gradient.addColorStop(0.7, "#FF33CC")                               // 添加渐变路径上的关键点的颜色gradient.addColorStop(1, "#FF6666")                                 // 添加渐变路径上的关键点的颜色ctx.fillStyle = gradient                                            // 设置填充样式ctx.fill()                                                          // 填充路径ctx.beginPath()ctx.rect(405, 10, 385, 285)gradient = ctx.createRadialGradient(600, 150, 10, 600, 150, 200)    // 创建径向渐变gradient.addColorStop(0, "#99CCFF")gradient.addColorStop(0.3, "#9933CC")gradient.addColorStop(0.7, "#FF33CC")gradient.addColorStop(1, "#FF6666")ctx.fillStyle = gradientctx.fill()ctx.beginPath()ctx.rect(10, 305, 385, 285)gradient = ctx.createConicalGradient(200, 450, 30)                  // 创建圆锥渐变gradient.addColorStop(0, "#99CCFF")gradient.addColorStop(0.3, "#9933CC")gradient.addColorStop(0.7, "#FF33CC")gradient.addColorStop(1, "#FF6666")ctx.fillStyle = gradientctx.fill()ctx.beginPath()ctx.rect(405, 305, 385, 285)gradient = ctx.createLinearGradient(405, 305, 790, 305)gradient.addColorStop(0, "#99CCFF")gradient.addColorStop(0.3, "#9933CC")gradient.addColorStop(0.7, "#FF33CC")gradient.addColorStop(1, "#FF6666")ctx.fillStyle = gradientctx.fill()}}
}

渐变填充

四、绘制文本

  我们可以使用 Context2D 的如下方法绘制文本:

object fillText(text, x, y)                                                     // 在指定位置(x,y)处填充指定的文本
object strokeText(text, x, y)                                                   // 在由(x,y)指定的位置对给定文本进行描边处理
object text(string text, real x, real y)                                        // 将给定的文本添加到路径中,作为由当前上下文字体生成的一组封闭子路径组成 

  我们可以通过 Context2Dfont 属性 设置当前画布上文本内容的当前字体,我们可以按下方式设置字体:

  • font-style字体样式,可选),可以取 normal正常)、italic斜体)、oblique斜体)三值之一。
  • font-variant字体变体,可选),可以取 normal正常)、small-caps小型大写字母)二值之一。
  • font-weight字重,可选),可以取 normal正常)、bold粗体)二值之一,或 0 ~ 99 的数字。
  • font-size字体大小),取 NpxNpt,其中 N 为数字,px 代表像素,pt 代表点,对于移动设备,使用 pt 为单位更合适一些。
  • font-family字体族),常见的有 serif衬线字体族)、sans-serif无衬线字体族)、cursive手写字体族)、fantasy梦幻字体族)、monospace等宽字体族)。

字体大小和字体系列属性是必填项,且需要按照上述所示的顺序进行设置(前面三个可选项的顺序不固定,但不能放在后面,否则样式不生效,并且字体大小必须在字体族前面)。

如果字体系列名称中包含空格,则必须使用引号将其括起来,属性值之间只能用一个空格分隔,否则会报 Invalid or misplaced token "" found in font string

默认的字体值为 "10px 等线字体"

  我们可以通过 Context2DtextAlign 属性 设置字体的对齐方式,它是一个字符串,我们可以取值如下:

// 默认设置,与文本的起始边缘对齐(对于从左向右排列的文本,在左侧对齐;对于从右向左排列的文本,在右侧对齐)
"start" 
// 与文本的末尾边缘对齐(对于从右向左书写的文字,应位于右侧;对于从左向右书写的文字,则应位于左侧)
"end""left"                                                                          // 左对齐
"right"                                                                         // 右对齐
"center"                                                                        // 水平居中对齐

  我们可以通过 Context2DtextBaseline 属性 设置字体的基线对齐,它是一个字符串,我们可以取值如下:

"top"                                                                           // 矩形的顶部
"middle"                                                                        // 矩形的中间
"bottom"                                                                        // 矩形的底部
"hanging"                                                                       // 悬垂基准线
"alphabetic"                                                                    // 默认值,字母基线
"ideographic"                                                                   // 表意字下基线

字体基线

  修改 template.qml 文件的内容。

import QtQuick.Window
import QtQuick.Controls// Window控件表示一个顶级窗口
// 在QML中,元素是通过大括号{}内的属性来配置的。
Window {id: windowIdwidth: 800                                                                  // 窗口的宽度height: 600                                                                 // 窗口的高度visible: true                                                               // 显示窗口color: "lightgray"                                                          // 窗口的背景颜色Canvas {id: canvasIdanchors.fill: parentcontextType: "2d"// 当需要绘图(更新)时会触发paint()信号onPaint: {context.lineWidth = 2                                               // 设置画笔宽度context.strokeStyle = "#FF6666"                                     // 设置画笔颜色context.fillStyle = "#99CCFF"                                       // 设置填充颜色 context.font = "48px fantasy"                                       // 设置字体context.beginPath()                                                 // 将当前路径重置为新的路径context.text("Hello World", 10, 50)                                 // 将给定的文本添加到路径中,作为由当前上下文字体生成的一组封闭子路径组成 context.fill()                                                      // 填充路径context.stroke()                                                    // 绘制路径context.font = "oblique bold small-caps 48px sans-serif"context.beginPath()context.fillText("Hello World", 10, 100)                            // 填充文本context.font = "italic small-caps bold 48px sans-serif"context.beginPath()context.strokeText("Hello World", 10, 150)                          // 绘制文本的轮廓}}
}

绘制文字

五、绘制图片

  我们可以使用 Context2D 的如下方法绘制图片:

// 将给定的图像绘制到画布上,位置为(dx,dy)
// 图像类型可以是Image对象、图像URL或CanvaslmageData对象
drawImage(variant image, real dx, real dy)// 将给定的项目以图像形式绘制到画布上,绘制位置为(dx,dy),宽度为dw,高度为dh
drawImage(variant image, real dx, real dy, real dw, real dh)// 将给定的项目从源点(sx,sy)以及源宽度sW、源高度sh处绘制到画布上(位置为(dx,dy)),并且绘制的宽度为dw、高度为dh
drawImage(variant image, real sx, real sy, real sw, real sh, real dx, real dy, real dw, real dh)

  修改 template.qml 文件的内容。

import QtQuick.Window
import QtQuick.Controls// Window控件表示一个顶级窗口
// 在QML中,元素是通过大括号{}内的属性来配置的。
Window {id: windowIdwidth: 800                                                                  // 窗口的宽度height: 600                                                                 // 窗口的高度visible: true                                                               // 显示窗口color: "lightgray"                                                          // 窗口的背景颜色Canvas {id: canvasIdanchors.fill: parentcontextType: "2d"// 定义一个属性保存图片的URLproperty var imageUrl: "https://upload-bbs.miyoushe.com/upload/2025/09/27/75276539/7d3a0a9126d65cca62eab88e62804166_8060074586752109227.jpg"// 当需要绘图(更新)时会触发paint()信号onPaint: {context.drawImage(imageUrl, 240, 260, width, height, 0, 0, width, height)}// 当组件完成加载时触发Component.onCompleted: {loadImage(imageUrl)                                                 // 加载图片}// 当图片加载完成时触发onImageLoaded: {requestPaint()                                                      // 请求重新绘制}}
}

  上述代码,我们首先在 Canvas 对象内定义了一个属性来保存图片 URL,然后在 Component.onCompleted 附加信号处理器内调用 CanvasloadImage() 方法来 加载图片,该方法会异步加载图片,当图片加载完成时,会发射 imageLoaded() 信号,然后我们在对应的信号处理器 onImageLoaded()内调用了 requestPaint() 方法来重绘 Canvas。只有成功加载的图片,我们才可以使用 Context2D 来绘制图像。一个 Canvas 可以加载多张图片,既可以加载本地图片,也可以加载网络图片。

绘制图片

六、图像变换

  就像 QPainter 一样,Context2D 也支持 平移旋转缩放错切 等简单的图像变换,它还支持简单的 矩阵变换

// 在坐标空间单位中,将画布的原点沿水平方向移动×个单位,沿垂直方向移动y个单位
object translate(real x, real y)// 将画布围绕当前的原点按弧度值和顺时针方向旋转
object rotate(real angle)// 通过将缩放因子乘以当前的变换矩阵,来增大或缩小画布网格中每个单元的尺寸。其中×是水平方向的缩放因子,y是垂直方向的缩放因子。
object scale(real x, real y)// 通过在水平方向上乘以sh并在垂直方向上乘以sv来对变换矩阵进行处理
object shear(real sh, real sv)// 通过相乘的方式将给定的变换矩阵应用到当前矩阵上
object transform(real a, real b, real c, real d, real e, real f)

  在绘图操作完成后,应当调用 restore()恢复之前保存的画布状态,否则后面绘画的图形也会应用此变换。而使用 restore() 方法之前,一定要先用 save() 方法 保存画布状态

  修改 template.qml 文件的内容。

import QtQuick.Window
import QtQuick.Controls// Window控件表示一个顶级窗口
// 在QML中,元素是通过大括号{}内的属性来配置的。
Window {id: windowIdwidth: 800                                                                  // 窗口的宽度height: 600                                                                 // 窗口的高度visible: true                                                               // 显示窗口color: "lightgray"                                                          // 窗口的背景颜色Canvas {id: canvasIdanchors.fill: parentcontextType: "2d"// 当需要绘图(更新)时会触发paint()信号onPaint: {context.lineWidth = 2                                               // 设置画笔宽度context.strokeStyle = "#FF6666"                                     // 设置画笔颜色context.fillStyle = "#99CCFF"                                       // 设置填充颜色 context.font = "48px fantasy"                                       // 设置字体context.save()                                                      // 保存当前绘图状态context.beginPath()                                                 // 将当前路径重置为新的路径context.translate(width / 2, height / 2)                            // 移动坐标原点context.rotate(Math.PI / 4)                                         // 旋转坐标系统context.scale(1.2, 1.2)                                             // 缩放坐标系统context.text("Hello, Sakura!", 10, 50)                              // 将给定的文本添加到路径中,作为由当前上下文字体生成的一组封闭子路径组成 context.fill()                                                      // 填充路径context.stroke()                                                    // 绘制路径context.restore()                                                   // 恢复之前保存的绘图状态context.save()                                                      // 保存当前绘图状态context.beginPath()                                                 // 将当前路径重置为新的路径context.shear(0.2, 0.2)                                             // 倾斜坐标系统context.text("Hello, Sakura!", 10, 100)                              // 将给定的文本添加到路径中,作为由当前上下文字体生成的一组封闭子路径组成 context.fill()                                                      // 填充路径context.stroke()                                                    // 绘制路径context.restore()                                                   // 恢复之前保存的绘图状态context.save()                                                      // 保存当前绘图状态context.beginPath()                                                 // 将当前路径重置为新的路径context.text("Hello, Sakura!", 10, 300)                              // 将给定的文本添加到路径中,作为由当前上下文字体生成的一组封闭子路径组成 context.fill()                                                      // 填充路径context.stroke()                                                    // 绘制路径}}
}

图像变换

七、图像裁切

  Context2Dclip() 方法,让我们能够根据当前路径包围的区域来裁切后续的绘图操作,在此区域之外的图像都会被毫不留情地丢弃掉。

  修改 template.qml 文件的内容。

import QtQuick.Window
import QtQuick.Controls// Window控件表示一个顶级窗口
// 在QML中,元素是通过大括号{}内的属性来配置的。
Window {id: windowIdwidth: 800                                                                  // 窗口的宽度height: 600                                                                 // 窗口的高度visible: true                                                               // 显示窗口color: "lightgray"                                                          // 窗口的背景颜色Canvas {id: canvasIdanchors.fill: parentcontextType: "2d"// 定义一个属性保存图片的URLproperty var imageUrl: "https://upload-bbs.miyoushe.com/upload/2025/09/27/75276539/7d3a0a9126d65cca62eab88e62804166_8060074586752109227.jpg"// 当需要绘图(更新)时会触发paint()信号onPaint: {context.lineWidth = 2                                              // 设置画笔宽度context.strokeStyle = "#FF6666"                                     // 设置画笔颜色context.save()                                                      // 保存当前绘图状态context.beginPath()                                                 // 将当前路径重置为新的路径context.rect(10, 10, 300, 200)                                      // 绘制矩形context.stroke()                                                    // 绘制路径context.ellipse(500, 200, 200, 200)                                 // 绘制椭圆context.stroke()                                                    // 绘制路径context.moveTo(300, 300)                                            // 创建一个位于点(x,y)处的新子路径context.lineTo(500, 460)                                            // 从当前位置画一条线至坐标为(x,y)的点处context.lineTo(420, 300)                                            // 从当前位置画一条线至坐标为(x,y)的点处context.closePath()                                                 // 通过绘制一条线连接到子路径的起始点来闭合当前子路径context.stroke()                                                    // 绘制路径context.clip()context.drawImage(imageUrl, 0, 0)                                   // 绘制图片}// 当组件完成加载时触发Component.onCompleted: {loadImage(imageUrl)                                                 // 加载图片}// 当图片加载完成时触发onImageLoaded: {requestPaint()                                                      // 请求重新绘制}}
}

裁切图像

http://www.hskmm.com/?act=detail&tid=28987

相关文章:

  • 预训练相关的一些概念
  • 2025/10/11 模拟赛总结 - sb
  • 分布式训练的一些知识
  • Visual Studio 2013 Update 4 中文版安装步骤(带TFS拥护)附安装包​
  • 排列
  • 白纷纷副
  • 低秩适配器(LoRA)
  • ROC曲线
  • 10.12~10.18随笔
  • 面向对象的题目
  • P11229 [CSP-J 2024] 小木棍题解
  • [HZOI] CSP-S模拟29
  • 初识pytorch:数据标准化及数据增强的transforms
  • 谈程序员如何做好业务
  • 10.11 CSP-S模拟29 改题记录
  • 二三阶行列式
  • 2025 年 10 月 8 日 语文作业
  • CHAR与VARCHAR深度解析:MySQL字符类型选择指南与性能对比
  • vivo霸榜背后:以技术打赢用户保卫战
  • 国庆期间做题记录
  • 02020508 EF Core高级08-表达式树、Expression和委托的关系、查看表达式树结构、AST、手动创建表示树、工厂方法
  • UnitTask中的Forget()与 CTS
  • commons-net - 详解
  • 12 种 Pandas 测试技巧,让数据处理少踩坑
  • 02020505 EF Core高级05-实体的5种状态、EntityEntry、AsNoTracking、实体状态跟踪
  • securityCTF 2025 pwn方向题解
  • 02020507 EF Core高级07-悲观并发控制、乐观并发控制、EF Core连接MySQL、RowVersion
  • linux防火墙操作命令
  • 02020506 EF Core高级06-EF Core批量删除更新插入、全局筛选器、软删除、全局筛选的性能问题
  • 机器学习社会影响与导航系统研究