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

QML开发:画布元素

文章目录

    • 一、画布绘制(Canvas Paint)
    • 二、渐变(Gradients)
    • 三、阴影(Shadows)
    • 四、图片(Images)
    • 五、变换(Transform)
    • 六、组合模式(Composition Mode)
    • 七、像素缓冲(Pixels Buffer)

一、画布绘制(Canvas Paint)

  在 QML 中,画布绘制(Canvas Paint) 是指使用 Canvas 元素提供的 2D 绘图上下文(类似 HTML5 Canvas)对画布进行像素级绘制和渲染的过程。它允许你以程序化方式绘制图形、处理像素数据或制作动画,而不仅仅依赖 QML 内置的可视元素(如 Rectangle、Image 等)。
基于 Canvas 2D Context,可以:

  • 绘制直线、曲线、矩形、圆形
  • 渲染文字
  • 创建渐变和图案
  • 实现动态图形(配合定时器或动画)

适用场景:

  • 绘制自定义控件
  • 绘制图表、示意图
  • 动态特效

基本语法:

import QtQuick 2.12
import QtQuick.Controls 2.12ApplicationWindow {visible: truewidth: 400height: 300title: "QML Canvas 示例"Canvas {id: myCanvasanchors.fill: parent// 当画布准备好绘制时触发onPaint: {var ctx = getContext("2d"); // 获取 2D 绘图上下文// 设置绘制颜色ctx.fillStyle = "lightblue";ctx.fillRect(0, 0, width, height); // 填充背景// 绘制红色矩形ctx.fillStyle = "red";ctx.fillRect(50, 50, 100, 80);// 绘制黑色边框矩形ctx.strokeStyle = "black";ctx.lineWidth = 2;ctx.strokeRect(200, 50, 100, 80);// 绘制圆ctx.beginPath();ctx.arc(150, 200, 50, 0, Math.PI * 2);ctx.fillStyle = "green";ctx.fill();// 绘制文字ctx.fillStyle = "black";ctx.font = "20px sans-serif";ctx.fillText("Hello Canvas", 100, 280);}Component.onCompleted: requestPaint() // 触发绘制}
}

输出结果:
在这里插入图片描述

常用 API:

  • fillRect(x, y, w, h):绘制填充矩形
  • strokeRect(x, y, w, h):绘制矩形边框
  • clearRect(x, y, w, h):清除区域
  • beginPath():开始新路径
  • moveTo(x, y):移动画笔位置
  • lineTo(x, y):绘制直线
  • arc(x, y, r, start, end):绘制圆/弧
  • fill():填充路径
  • stroke():描边路径
  • fillText(text, x, y):绘制文字
  • strokeText(text, x, y):描边文字
  • drawImage(img, x, y):绘制图片

样式属性:

  • fillStyle:填充颜色或渐变
  • strokeStyle:边框颜色
  • lineWidth:线宽
  • font:字体样式

动态绘制(动画)
Canvas 本身不会自动重绘,需要手动调用:

requestPaint()

来触发 onPaint。

示例:绘制一个移动的圆

Canvas {id: canvasanchors.fill: parentproperty int posX: 50Timer {interval: 16 // 约 60 FPSrunning: truerepeat: trueonTriggered: {canvas.posX += 2if (canvas.posX > canvas.width) canvas.posX = 0canvas.requestPaint()}}onPaint: {var ctx = getContext("2d");ctx.clearRect(0, 0, width, height);ctx.beginPath();ctx.arc(posX, height / 2, 30, 0, Math.PI * 2);ctx.fillStyle = "orange";ctx.fill();}
}

效果:一个橙色的圆不断从左向右移动。

注意事项:

  • 性能:Canvas 在 QML 中性能比 OpenGL 或 ShaderEffect 弱,不适合非常高频复杂绘制。
  • 重绘机制:必须手动 requestPaint() 才会触发绘制。
  • 分辨率:在高 DPI 屏幕下,可根据 Canvas.devicePixelRatio 调整绘制尺寸。
  • 图像缓存:对于频繁绘制的背景图,可以先绘制到离屏 Canvas,再重复绘制,提高性能。

二、渐变(Gradients)

  在 QML 的 Canvas 元素中,渐变(Gradients) 是通过 CanvasRenderingContext2D 提供的 createLinearGradient() 和 createRadialGradient() 方法来创建的,它们和 HTML5 Canvas API 是一致的。

2.1 概念
渐变是一种平滑的颜色过渡效果,可以用作 填充(fillStyle) 或 描边(strokeStyle)。QML Canvas 支持两种主要渐变:

  • 线性渐变(Linear Gradient):颜色沿直线方向变化
  • 径向渐变(Radial Gradient):颜色沿圆心向外变化

2.2 线性渐变

var gradient = ctx.createLinearGradient(x0, y0, x1, y1)
  • (x0, y0) → 渐变起点
  • (x1, y1) → 渐变终点

2.3 径向渐变

var gradient = ctx.createRadialGradient(x0, y0, r0, x1, y1, r1)
  • (x0, y0, r0) → 内圆(中心和半径)
  • (x1, y1, r1) → 外圆(中心和半径)

添加颜色断点:

gradient.addColorStop(offset, color)
  • offset:0.0 ~ 1.0(表示渐变的相对位置)
  • color:CSS 样式颜色(如 “red”, “#FF0000”, “rgba(255,0,0,0.5)”)

2.4 示例
线性渐变:

import QtQuick 2.12Canvas {width: 300; height: 200onPaint: {var ctx = getContext("2d")var grad = ctx.createLinearGradient(0, 0, width, 0)grad.addColorStop(0, "red")grad.addColorStop(1, "blue")ctx.fillStyle = gradctx.fillRect(0, 0, width, height)}
}

径向渐变:

Canvas {width: 300; height: 200onPaint: {var ctx = getContext("2d")var grad = ctx.createRadialGradient(150, 100, 20, 150, 100, 100)grad.addColorStop(0, "yellow")grad.addColorStop(1, "green")ctx.fillStyle = gradctx.fillRect(0, 0, width, height)}
}

注意事项:

  • 渐变是绘图样式,要先创建渐变对象,再赋值给 fillStyle 或 strokeStyle 才有效。
  • addColorStop() 的顺序影响最终效果(会按 offset 排序)。
  • 在 QML Canvas 中,性能比 QML 原生 Rectangle { gradient: Gradient {…} } 稍低,因为它是脚本绘制。

三、阴影(Shadows)

2.1 概念
阴影可以给 Canvas 上绘制的形状或文字添加投影效果,增强立体感。
主要作用于:

  • fillRect, strokeRect 等矩形
  • arc 绘制的圆形
  • fillText / strokeText 绘制的文字

阴影不会改变形状本身,只是增加一层投影。

2.2 属性说明
Canvas 中的阴影由以下 3 个属性控制:

  • shadowColor:阴影颜色,支持 CSS 颜色,例如 “rgba(0,0,0,0.5)”
  • shadowOffsetX:阴影水平偏移量(像素),正值向右,负值向左
  • shadowOffsetY:阴影垂直偏移量(像素),正值向下,负值向上
  • shadowBlur:阴影模糊半径(像素),越大越柔和

2.3 示例
矩形阴影:

Canvas {width: 300height: 200onPaint: {var ctx = getContext("2d")// 设置阴影ctx.shadowColor = "rgba(0,0,0,0.5)"ctx.shadowOffsetX = 10ctx.shadowOffsetY = 10ctx.shadowBlur = 15// 绘制矩形ctx.fillStyle = "orange"ctx.fillRect(50, 50, 100, 80)}
}

文字阴影:

Canvas {width: 300height: 200onPaint: {var ctx = getContext("2d")ctx.font = "30px sans-serif"ctx.fillStyle = "blue"ctx.shadowColor = "rgba(0,0,0,0.7)"ctx.shadowOffsetX = 4ctx.shadowOffsetY = 4ctx.shadowBlur = 5ctx.fillText("Hello Shadows", 50, 100)}
}

QML Canvas 阴影效果彩色示意图:
在这里插入图片描述
注意事项:
阴影作用域:

  • 阴影是上下文属性,设置后会作用于后续绘制的所有图形,直到被覆盖或重置。
  • 如果想只给特定图形加阴影,可以在绘制前后分别保存/恢复上下文:
ctx.save()
ctx.shadowColor = "rgba(0,0,0,0.5)"
ctx.fillRect(50,50,100,80)
ctx.restore()

性能:阴影和模糊会增加绘制开销,尤其在动画场景下。
透明度:阴影颜色透明度越高,投影越柔和。

四、图片(Images)

  在 QML 中,Image 是最常用的显示图片的元素,它非常灵活,可以在画布(Canvas)或者普通界面中使用。下面我给你详细整理 Image 的使用方法、属性和技巧。

4.1 基本用法

Image {id: myImagesource: "images/picture.png"   // 图片路径width: 200height: 150fillMode: Image.PreserveAspectFit  // 保持宽高比缩放
}

4.2 常用属性

  • source:图片文件路径,可以是相对路径或 URL
  • width / height:图片显示的尺寸
  • fillMode:缩放方式:Stretch / PreserveAspectFit / PreserveAspectCrop
  • smooth:是否启用平滑缩放(默认 true)
  • opacity:透明度(0~1)
  • visible:是否显示
  • layer.enabled:可启用图层渲染,实现旋转、阴影等效果

4.3 缩放方式

  • Image.Stretch:拉伸填充,不保持比例
  • Image.PreserveAspectFit:保持比例,整个图片显示在区域内
  • Image.PreserveAspectCrop:保持比例,可能裁剪超出区域的部分
Image {source: "images/picture.png"width: 300height: 200fillMode: Image.PreserveAspectCrop
}

4.4 动态加载图片

Image {id: dynamicImagewidth: 200height: 200
}Button {text: "加载图片"onClicked: dynamicImage.source = "images/new_image.png"
}

4.5 使用 Image 与 Canvas 配合
如果你在 Canvas 上绘图,但需要显示图片,可以用 context.drawImage:

Canvas {id: canvaswidth: 400height: 300onPaint: {var ctx = getContext("2d")var img = new Image()img.src = "images/picture.png"img.onload = function() {ctx.drawImage(img, 0, 0, canvas.width, canvas.height)}}
}

注意:Canvas 绘制时,Image 是 JS 对象,需要等待 onload 事件

4.6 图片替换与缓存

  • QML Image 会自动缓存图片,提高性能
  • 可通过 cache: false 禁用缓存,适合动态图片频繁变化的情况
Image {source: "images/live.png"cache: false
}

五、变换(Transform)

5.1 Canvas 上的转换(2D context)
Canvas 提供类似 HTML5 Canvas 的 2D API,通过 context 可以进行转换:

  • translate(x, y):平移坐标系,将原点移动到 (x, y)
  • rotate(angle):旋转坐标系,角度为弧度
  • scale(sx, sy):缩放坐标系,sx / sy 为水平/垂直缩放比例
  • transform(a, b, c, d, e, f):复合变换,直接设置变换矩阵
  • setTransform(a, b, c, d, e, f):重置变换矩阵为指定矩阵
  • save() / restore():保存/恢复当前坐标系状态

5.2 示例:平移、旋转、缩放

Canvas {id: canvaswidth: 400height: 300onPaint: {var ctx = getContext("2d")ctx.clearRect(0, 0, width, height)// 保存状态ctx.save()// 平移到画布中心ctx.translate(width/2, height/2)// 旋转 45 度ctx.rotate(Math.PI / 4)// 缩放ctx.scale(1.5, 1.5)// 绘制矩形ctx.fillStyle = "skyblue"ctx.fillRect(-50, -25, 100, 50)// 恢复状态ctx.restore()}
}

注意:Canvas 的变换是累积的,每次操作都会影响后续绘制,所以常用 save() 和 restore() 控制局部变换。

5.3 对 Image 的变换

Canvas {id: canvaswidth: 400height: 300onPaint: {var ctx = getContext("2d")var img = new Image()img.src = "images/picture.png"img.onload = function() {ctx.save()ctx.translate(200, 150)ctx.rotate(Math.PI / 6)ctx.scale(0.8, 0.8)ctx.drawImage(img, -img.width/2, -img.height/2)ctx.restore()}}
}

这里先平移到中心,再旋转、缩放,然后绘制图片,最后 restore() 复原坐标系。

5.4 QML 元素层级的变换
如果不使用 Canvas,而直接操作 Image、Rectangle 等 QML 元素,也可以用 Transform:

Image {source: "images/picture.png"anchors.centerIn: parenttransform: Rotation {origin.x: width/2origin.y: height/2angle: 45}
}

使用技巧::

  • 中心旋转:旋转前先 translate 到中心,绘制后 translate 回去
  • 组合变换顺序:变换顺序影响结果(先平移再旋转 vs 先旋转再平移)
  • 局部变换:save() / restore()(Canvas)或单独元素 transform(QML)
  • 动画变换:可用 NumberAnimation 或 RotationAnimation 对属性如 angle、scale 动态变化

六、组合模式(Composition Mode)

6.1 什么是组合模式
  在 QML Canvas(即 HTML5 Canvas 的 API 接口)中,globalCompositeOperation 决定了新绘制的形状(source)如何与已有画布内容(destination)进行像素级混合。它的本质是一个 像素合成公式,类似 Photoshop、Illustrator 里的“混合模式”。

6.2 常用组合模式分类
常规叠加模式:

  • source-over:默认值,新图形画在旧图形上面(常规绘制)
  • destination-over:新图形画在旧图形下面

覆盖与替换:

  • copy:只保留新图形,清空旧内容
  • destination:保留旧图形,忽略新图形

相交/相减模式:

  • source-in:只保留新图形与旧图形重叠部分
  • destination-in:只保留旧图形与新图形重叠部分
  • source-out:只保留新图形中与旧图形不重叠部分
  • destination-out:只保留旧图形中与新图形不重叠部分
  • xor:只保留新旧图形不重叠部分

混色模式:

  • lighter:颜色相加,变亮
  • multiply:颜色相乘,变暗
  • screen:反相相乘,变亮
  • overlay:叠加:暗的更暗,亮的更亮
  • darken:取新旧颜色的较暗值
  • lighten:取新旧颜色的较亮值

6.3 使用示例

import QtQuick 2.12
import QtQuick.Window 2.12Window {width: 400height: 300visible: truetitle: "QML Canvas 组合模式"Canvas {id: canvasanchors.fill: parentonPaint: {var ctx = getContext("2d");// 清空画布ctx.clearRect(0, 0, width, height);// 画第一个矩形ctx.fillStyle = "blue";ctx.fillRect(50, 50, 150, 150);// 设置组合模式ctx.globalCompositeOperation = "lighter"; // 可替换为 multiply, source-over 等// 画第二个矩形ctx.fillStyle = "red";ctx.fillRect(100, 100, 150, 150);}}
}

输出结果:
在这里插入图片描述
使用技巧与场景:

  • 遮罩效果:source-in、destination-in
  • 擦除(橡皮擦):destination-out
  • 高光/发光效果:lighter
  • 变暗阴影:multiply
  • 渐变融合:screen、overlay
  • 只显示交集:source-in

注意事项:

  • 默认模式是 source-over,其他模式需要显式设置。
  • 组合模式会影响后续绘制,建议在需要的地方设置,用完恢复:
ctx.globalCompositeOperation = "source-over"; // 恢复默认

QML Canvas 组合模式效果对照图:
在这里插入图片描述

七、像素缓冲(Pixels Buffer)

7.1 什么是像素缓冲
定义:像素缓冲就是一块连续的内存,用来存储图像中每个像素的颜色信息。每个像素通常包含 红、绿、蓝(RGB),有时还包含 透明度(Alpha)。

访问方式:通过 Canvas.getContext(“2d”).getImageData() 获取像素数据。

存储方式:

  • 按行排列:像素数据通常按行从左到右,从上到下排列。
  • 按通道排列:RGB:每个像素 3 字节(Red, Green, Blue);RGBA:每个像素 4 字节(Red, Green, Blue, Alpha)
  • 灰度图像:每个像素 1 字节

常见类型:

  • uint8_t* 或 uchar*(每通道 8 位)
  • uint16_t*(高精度图像,如医学图像或深度图)
  • float*(HDR 或科学计算图像)

7.2 像素缓冲的用途

  • 自定义绘制:可以按像素修改画布,例如绘制渐变、生成噪声、绘制动态效果。
  • 图像处理:滤镜、卷积、灰度化、反转颜色等都可以直接修改像素缓冲实现。
  • 动画渲染:在 onFrameSwapped 或定时器中不断修改像素缓冲实现像素级动画。
  • 数据可视化:将外部数组(如热力图、深度图)映射到画布像素缓冲显示。

7.3 基本使用示例
以 C++/Qt 为例,使用 QImage 直接操作像素缓冲:

int width = 100;
int height = 100;// 创建一个RGBA格式的图像
QImage image(width, height, QImage::Format_RGBA8888);// 获取像素缓冲
uchar* buffer = image.bits();
int bytesPerLine = image.bytesPerLine(); // 每行的字节数// 填充像素缓冲
for (int y = 0; y < height; ++y) {uchar* line = buffer + y * bytesPerLine;for (int x = 0; x < width; ++x) {int index = x * 4; // RGBAline[index + 0] = 255; // Rline[index + 1] = 0;   // Gline[index + 2] = 0;   // Bline[index + 3] = 255; // A}
}// 绘制到QWidget
QPainter painter(this);
painter.drawImage(0, 0, image);

示例:QML Canvas 修改像素颜色:

import QtQuick 2.12
import QtQuick.Window 2.12Window {visible: truewidth: 200height: 200title: "Canvas Pixel Buffer 示例"Canvas {id: canvasanchors.fill: parentwidth: 100height: 100onPaint: {var ctx = getContext("2d");// 填充黑色背景ctx.fillStyle = "black";ctx.fillRect(0, 0, width, height);// 获取像素缓冲var imageData = ctx.getImageData(0, 0, width, height);var pixels = imageData.data;// 左上角 50x50 红色for (var y = 0; y < 50; y++) {for (var x = 0; x < 50; x++) {var index = (y * width + x) * 4;pixels[index] = 255;     // Rpixels[index + 1] = 0;   // Gpixels[index + 2] = 0;   // Bpixels[index + 3] = 255; // A}}// 写回画布ctx.putImageData(imageData, 0, 0);}// 确保 Canvas 完成初始化后触发绘制Component.onCompleted: requestPaint()}
}

说明:

  • getImageData(x, y, width, height) 获取指定区域的像素缓冲。
  • Uint8ClampedArray 确保像素值在 0~255 范围内。
  • 修改后需要 putImageData 才能刷新画布。

注意事项:

  • 性能问题:getImageData 和 putImageData 在大画布上频繁调用会比较慢。优化方法:尽量只修改需要的区域。另外也可以使用 WebGL Canvas 或 Shader 进行 GPU 加速。
  • 透明度处理:Alpha 值 0 表示完全透明,255 表示完全不透明。
  • 数组索引:index = (y * width + x) * 4,千万不要漏乘 4,否则颜色会错乱。
  • 浮点计算:像素值必须是整数,使用 Uint8ClampedArray 自动截断。

7.4 像素缓冲的内存布局
假设画布宽度是 width,像素缓冲是 Uint8ClampedArray,每个像素有 4 个通道:

pixels = [R0, G0, B0, A0,   R1, G1, B1, A1,   R2, G2, B2, A2, ...R(width), G(width), B(width), A(width), ......
]
  • 每行有 width 个像素,每个像素 4 个字节
  • 所以第 y 行第 x 列的像素在数组中的起始索引为:index=y×width×4+x×4

也可以写成:

index = (y * width + x) * 4
  • y * width → 第 y 行有多少个像素
    • x → 第 x 列
    • 4 → 每个像素有 4 个字节(R,G,B,A)

示例:
假设画布 3x2(宽3,高2):

0: pix0, pix1, pix2
行1: pix3, pix4, pix5

每个像素 4 字节,那么缓冲数组就是:

[ R0,G0,B0,A0,  R1,G1,B1,A1,  R2,G2,B2,A2,  R3,G3,B3,A3,  R4,G4,B4,A4,  R5,G5,B5,A5 ]
  • 第 1 行第 2 列(y=0,x=1)的索引:index=(0∗3+1)∗4=4;对应 R1 的位置。
  • 第 2 行第 3 列(y=1,x=2)的索引:index=(1∗3+2)∗4=20;对应 R5 的位置。

像素缓冲数组示意图:
在这里插入图片描述

http://www.dtcms.com/a/330643.html

相关文章:

  • 数学建模-非线性规划模型
  • Spring AI PagePdfDocumentReader 全解析:基于 Apache PdfBox 的按页 PDF 读取实战
  • PDF压缩原理详解:如何在不失真的前提下减小文件体积?
  • 高分辨率PDF压缩技巧:保留可读性的最小体积方案
  • PDF 段落提取利器:Spring AI 的 ParagraphPdfDocumentReader 实战
  • 【办公自动化】如何使用Python操作PPT和自动化生成PPT?
  • pdf文件流或者本地文件读取
  • OSI七层模型和TCP/IP协议簇
  • elasticsearch冷热数据读写分离!
  • Qt TCP 客户端对象生命周期与连接断开问题解析
  • ESXI 6.7服务器时间错乱问题
  • 面试题之项目中git如何进行管理
  • 计算机如何进行“卷积”操作:从图像到矩阵的奥秘
  • 星空开源MES(万界星空科技)——专业、通用、开源、免费的MES系统
  • AM32电调学习-使用Keil编译uboot
  • 医疗AI问答系统实战:知识图谱+大模型的融合应用开发
  • 带环链表详解:环形链表检测与入环节点查找
  • 推荐三个国内开源数据治理工具
  • Python3 详解:从基础到进阶的完整指南
  • 第四天~什么是ARXML?
  • CV 医学影像分类、分割、目标检测,之【肺结节目标检测】项目拆解
  • pytorch学习笔记-加载现有的网络模型(VGG16)、增加/修改其中的网络层(修改为10分类)
  • AI测试自动化:智能软件质量守护者
  • 观察者模式(C++)
  • CV 医学影像分类、分割、目标检测,之【3D肝脏分割】项目拆解
  • Flutter 顶部导航标签组件Tab + TabBar + TabController
  • 汽车生产线白皮书:稳联技术Profinet转Ethernet IP网关通信高效性
  • 中介者模式和观察者模式的区别是什么
  • 三同步舆情处置原则对政务管理有什么影响作用
  • 从实验室到落地:飞算JavaAI水位监测系统的工程化实践