大数跨境

博客|Canvas2D:基于Qt Canvas Painter的全新QML画布元素

博客|Canvas2D:基于Qt Canvas Painter的全新QML画布元素 Qt软件
2026-10-28
3
导读:本文介绍 Qt 6.12 中全新的 Qt Quick 元素 Canvas2D。


此前关于Qt Canvas Painter的博客文章,介绍了它是什么、带来的新渲染功能、与QPainter相比的性能表现,以及路径缓存如何让百万级线段也能流畅渲染。这些文章使用的都是QCanvasPainter C++ API。在路径缓存那篇文章的结尾,我曾预告QML元素即将推出,现在它来了:Canvas2D,已在Qt 6.12中提供。


注意:Qt Canvas Painter C++ API在Qt 6.12中已不再是技术预览版,但Canvas2D仍是处于技术预览阶段。它可以正常工作,我们也鼓励您使用,但该API仍可能变更,目前也不提供源代码或二进制兼容性保证。


什么是Canvas2D?


Canvas2D是一个QML项,可让您使用JavaScript进行绘制,采用HTML Canvas和Qt Quick Canvas所普及的命令式绘图模型。其底层使用Qt Canvas Painter,这意味着绘制通过QRhi在GPU上完成,而不是通过CPU绘制到QImage中。


上手非常简单,以下是一个简单的QML示例:

import QtQuickimport QtCanvas2DCanvas2D {    width: 100    height: 200    onPaint: {        var ctx = getContext("2d");        // Paint a red rectangle        ctx.fillStyle = Qt.rgba(1, 0, 0, 1);        ctx.fillRect(0, 0, width, height);    }}
如果您曾在 QML 中编写过 Canvas { onPaint: ... },或在浏览器中使用过 <canvas> 元素,那么这里没有什么特别之处。您只需导入 QtCanvas2D 模块,即可创建 Canvas2D 元素。


与HTML Canvas的兼容性


Canvas2DContext API实现了Web开发者已经熟悉的同一套W3C Canvas 2D Context API:beginPath()、moveTo()、lineTo()、bezierCurveTo()、fill()、stroke() 等,以及熟悉的状态属性fillStyle、strokeStyle、lineWidth、lineCap、lineJoin等。


这意味着,来自网络、教程、Stack Overflow或大语言模型(LLM)的画布代码通常可以直接运行。对于命令式2D API而言,这是巨大的效率优势:相关知识和代码片段已经存在。


不过,Canvas2D的目标并非100% 兼容HTML Canvas。为使API更简洁并保持GPU上的高性能,部分功能未被纳入。相较HTML Canvas,目前缺少以下功能:


  • 路径裁剪 – 裁剪区域只能是(可经变换的)矩形,通过setClipRect() 和resetClipping() 实现。

  • 虚线 – 描边始终为实线。

  • 路径检测 – 不支持isPointInPath() 和isPointInStroke()。

  • 文字描边 – 不支持文字轮廓描边。

  • 滤镜 – 不支持画布SVG滤镜效果。

  • 合成模式 – 仅限于无需渲染到额外缓冲区即可支持的三种模式:source-over、source-atop和destination-out。

  • 内置阴影属性 – shadowBlur及相关属性由可调抗锯齿和盒状阴影取代,详见下文。


另一方面,Canvas2D还新增了HTML Canvas不具备的功能:


  • 带路径组的Path2D – createPath2D() 可生成path2d容器,并可通过可选的路径组进行填充和描边。同一组中的路径共享一个在GPU端缓存的顶点缓冲区,这正是路径缓存那篇文章中大幅性能提升的来源。

  • 可调抗锯齿 – ctx.antialias和ctx.textAntialias以像素为单位设置抗锯齿宽度,只需一个数值即可实现从锐利到柔和光晕的效果。

  • 盒状渐变 – createBoxGradient() 可沿圆角矩形的形状创建渐变。

  • 盒状阴影 – createBoxShadow() / drawBoxShadow() 采用SDF(有向距离场) 方法渲染CSS风格的盒状阴影,与Qt Quick的RectangularShadow类似。相比高斯模糊阴影,开销极低。

  • 网格图案 – createGridPattern() 可用于动态网格和条形图图案,且开销恒定。

  • 色彩效果 – 在标准globalAlpha之外,还提供globalBrightness、globalContrast和globalSaturation色彩效果。

  • 孔洞子路径 – beginHoleSubPath() / beginSolidSubPath() / setPathWinding() / windingEnforce让环形和镂空图形的绘制变得简单,无需手动管理路径的环绕方向。

  • 文本换行 – fillText(text, x, y, width, height) 可将文本换行到矩形区域内,由textWrapMode和textLineHeight控制。

  • 额外图形 – circle()、ellipse() 和ellipseRect() 作为一等路径图元提供。

  • Transform2d – createTransform2D() 提供真正的3x3矩阵对象,可逐步构建并传递给setTransform() 或transform(),无需手动处理六个浮点数,也无需使用完整的DOMMatrix。


与Qt Quick Canvas的兼容性


如果您已在使用Qt Quick Canvas,Canvas2D API与其几乎完全兼容。您只需导入QtCanvas2D并将Canvas改名为Canvas2D,基本即可正常使用。


以下代码示例展示了这两行改动:

// Beforeimport QtQuickCanvas {    onPaint: { var ctx = getContext("2d"); paintEverything(ctx); }}// Afterimport QtQuickimport QtCanvas2DCanvas2D {    onPaint: { var ctx = getContext("2d"); paintEverything(ctx); }}

下文介绍的Canvas 2D Tester示例正是依赖于此:同一套JavaScript绘制函数同时被用于Canvas和Canvas2D元素。


下表总结了Qt Quick Canvas与Canvas2D的差异。

简而言之,取舍如下:您需放弃少数较少使用的功能,换来GPU级别的渲染速度以及大量新画刷和效果。对于典型场景(如动画自定义可视化、仪表盘、图表、波形图、手绘控件等),这是值得的取舍。如果您的代码依赖Canvas2D不具备的Quick Canvas功能,且运行良好,可继续使用,该功能仍然可用。我们也很乐意了解您的使用场景,以便今后优先安排这些功能。


有一个行为差异值得再次强调:Canvas2D默认每帧从头重绘,因此无需像HTML Canvas和Quick Canvas那样在onPaint开头调用clearRect()。


与QCanvasPainter C++ API的兼容性


QCanvasPainter与Canvas2DContext是同一个绘制器的两种形式。C++ API使用setter方法和Qt值类型,QML API使用属性和对JavaScript友好的参数,但方法名、参数顺序和语义保持一致。两个方向之间的移植几乎是机械性的,可以轻松借助脚本或大语言模型完成。


以下是QCanvasPainter文档中的圆角按钮示例,并与Canvas2DContext文档中的同一示例并列对照。


C++ 使用QCanvasPainter:

QRectF rect(40, 70, 120, 60);QRectF shadowRect = rect.translated(2, 4);// Paint shadowQCanvasBoxShadow shadow(shadowRect);shadow.setRadius(30);shadow.setBlur(15);shadow.setColor("#60373F26");p->drawBoxShadow(shadow);// Paint rounded rectp->beginPath();p->roundRect(rect, 30);p->setFillStyle("#DBEB00");p->fill();// Paint textp->setTextAlign(QCanvasPainter::TextAlign::Center);p->setTextBaseline(QCanvasPainter::TextBaseline::Middle);QFont font("Titillium Web", 18);p->setFont(font);p->setFillStyle("#373F26");p->fillText("CLICK!", rect);

JavaScript使用Canvas2DContext:

let offsetX = 2;let offsetY = 4;// Paint shadowlet shadow = ctx.createBoxShadow(40 + offsetX, 70 + offsetY,                                 120, 60);shadow.setRadius(30);shadow.setBlur(15);shadow.setColor("#60373F26");ctx.drawBoxShadow(shadow);// Paint rounded rectctx.beginPath();ctx.roundRect(40, 70, 120, 60, 30);ctx.fillStyle = "#DBEB00";ctx.fill();// Paint textctx.textAlign = "center";ctx.textBaseline = "middle";ctx.font = "24px 'Titillium Web'";ctx.fillStyle = "#373F26";ctx.fillText("CLICK!", 100, 100);

移植规则相当简单:

API的统一意义重大,其价值可能超出想象。您可以先在QML中原型化可视化效果,编辑与运行的循环只需几秒。之后若性能分析表明JavaScript成为瓶颈,或需要使用位于C++ 端的实际数据,只需通过大部分查找替换,即可将相同的绘制代码迁移到C++ 中的QCanvasPainterItem。反过来也可以:将现有的C++ Canvas Painter项进行封装,提供可供QML脚本调用的版本。这一过程中并不会更换渲染引擎,只是切换编程语言,因此渲染输出保持完全一致。


Canvas 2D Tester示例


要切身体会这一切,最好的方式是运行随模块提供的Canvas 2D Tester示例。它正是为回答大家最关心的两个问题而构建的:我的画布代码还能正常运行吗?它的速度真的更快吗?


该示例的巧妙之处在于:Canvas和Canvas2D元素使用完全相同的QML JavaScript绘制代码。只有一套绘制函数,工具栏中的开关决定由哪个元素执行它们。界面角落显示FPS计数器,"Animate" 开关用于开启或关闭持续重绘,"Complexity" 滑块(范围1至10)用于调整每个测试绘制的内容量。因此,您可以选择一个测试、观察帧率、切换开关,即可在自己的硬件和GPU驱动上直观看到差异。


测试分为三列:


  • 基准测试 — 矩形、线条、圆形、裁剪、线型、文本、状态处理、图像和变换。这些都是最常用的基础操作,每次绘制数量可达数百乃至数千次。矩形测试每帧最多交替执行5000次fillRect() 和strokeRect();线条测试最多绘制50条折线,每条1000段;圆形测试最多用2000段圆弧构建单一路径。将复杂度调高,即可观察每个元素在何处开始力不从心。

  • 示例 — 文本对齐、合成模式和渐变。这些是测试用于验证渲染正确性,而非比较速度:两个元素运行相同代码,便于您验证关于基线位置、合成及渐变色标的假设是否成立。

  • 新功能 — 盒状渐变、盒状阴影、色彩效果、孔洞子路径、调整抗锯齿、网格图案和Path2D。这些仅可在Canvas2D上运行。该列是对Canvas2D新功能的快速导览。


该示例也是获取代码灵感的好地方。其CanvasView.qml包含数百行纯JavaScript绘制函数,其中的Path2D测试会构建一个基于SVG路径的图标,并渲染该图标的多个经变换、带缓存的副本。


您可以在code.qt.io上找到该示例项目,也可以在Qt Creator示例中搜索 "canvas" 找到。


使用QCPainterBench进行基准测试


如果上文的Canvas 2D Tester示例尚未让您信服Canvas2D的性能,QCPainterBench或许能让您改变看法。我们为该基准测试应用新增了两个后端:(旧)Quick Canvas和(新)Canvas2D。请观看以下视频,了解两者之间的性能差异:


在所有测试各渲染16次的条件下,作者笔记本电脑上的性能数据为4 FPS对比165 FPS,因此Canvas2D的速度比Canvas快40倍以上。


关键在于两者的JavaScript代码完全相同,因此性能差异完全源于Canvas2D对动画内容进行了更好的优化。这种从QML JavaScript实现的高性能绘制,将带来众多新的可能性。


当前状态与后续计划


Canvas2D是Qt 6.12中的新功能,目前为技术预览版。API可能会根据我们的经验而调整,性能也很可能进一步提升——迄今为止每个版本都是如此。


因此,请试用Canvas 2D Tester,在您的目标硬件上运行,并尝试将一个现有的Canvas项移植到Canvas2D。然后告诉我们体验如何,例如通过Qt缺陷跟踪系统 或论坛反馈。在API仍处于技术预览阶段时提供的反馈,将决定最终API的形态。


在下一篇博客中,我将介绍一个基于Canvas2D的具体应用(并结合特效与Qt Quick 3D),敬请期待。

END




往期推荐

博客|Qt Canvas Painter:路径加速性能

博客 | 加速2D Canvas基准测试

博客 | 2D渲染——Qt Canvas Painter简介


【声明】内容源于网络
0
0
Qt软件
Qt Group是一家全球软件公司,致力于开发用户喜爱的应用程序和智能设备,深受行业领导者和全球超过 150 万开发人员的信赖。我们帮助客户在整个产品开发生命周期中提高生产力——从 UI 设计和软件开发到质量管理和部署。网址:qt.io
内容 577
粉丝 0
Qt软件 Qt Group是一家全球软件公司,致力于开发用户喜爱的应用程序和智能设备,深受行业领导者和全球超过 150 万开发人员的信赖。我们帮助客户在整个产品开发生命周期中提高生产力——从 UI 设计和软件开发到质量管理和部署。网址:qt.io
总阅读9.7k
粉丝0
内容577