十一、QT Quick Canvas实战:2D绘图API与自定义图形指南
本文详解QT Quick中Canvas元素核心用法,涵盖2D绘图API、路径绘制、渐变及图像处理,助你快速掌握自定义动态图形技能。
十一、QT Quick Canvas实战:2D绘图API与自定义图形指南-MakerLi

第11章:画布与图形——Canvas元素、2D绘图API、路径(Path)、渐变(Gradient)、图像处理

本章导览

在QT Quick中,Canvas就像一块可自由创作的数字画布,提供类似HTML5 Canvas的2D绘图能力,能轻松实现动态图形、自定义界面组件。本章从基础到进阶,带你掌握Canvas核心用法,为打造高性能个性化界面铺路。


学习目标:熟练运用Canvas核心API,独立绘制复杂图形并实现简单图像效果。






11.1 Canvas元素基础

Canvas是QML中专门用于2D绘图的元素,核心是通过「绘图上下文(context)」执行绘图指令——相当于你拿画笔在画布上创作,上下文就是你的画笔与调色盘。


Canvas核心属性与方法:

  • width/height:定义画布尺寸,决定绘图范围;
  • contextType:指定上下文类型,常用值为"2d",对应2D绘图能力;
  • getContext(contextType):获取绘图上下文对象,所有绘图操作均通过它完成;
  • requestPaint():请求重绘画布,内容更新时调用;
  • onPaint信号:绘图触发点,所有绘图代码必须写在该信号处理器内,确保上下文状态正确。

提示:切勿在onPaint外执行绘图操作,否则会出现绘制异常。


基本示例:绘制带边框的矩形

import QtQuick 2.15

Canvas {
    id: myCanvas
    width: 400
    height: 300

    onPaint: {
        // 获取2D绘图上下文(拿起画笔)
        var ctx = getContext("2d");
        // 设置填充颜色为蓝色
        ctx.fillStyle = "#4A90E2";
        // 在(50,50)位置绘制200×150的填充矩形
        ctx.fillRect(50, 50, 200, 150);
        // 设置描边颜色为深灰,线条宽度3像素
        ctx.strokeStyle = "#333";
        ctx.lineWidth = 3;
        // 给矩形添加边框
        ctx.strokeRect(50, 50, 200, 150);
    }

    Component.onCompleted: {
        // 组件加载完成后,请求首次绘制
        requestPaint();
    }
}

该示例会在画布左上角(50,50)位置,画出一个蓝色填充、深灰边框的200×150像素矩形。






11.2 2D绘图API详解

Canvas的2D上下文提供了一套完整的绘图工具,可绘制形状、文本、图像,还能管理绘图状态。核心方法分为五大类:

  • 矩形类fillRect()(填充矩形)、strokeRect()(描边矩形)、clearRect()(清空指定区域);
  • 路径类beginPath()(开启新路径)、moveTo()(移动画笔位置)、lineTo()(画直线)、arc()(画圆弧)、closePath()(闭合路径)、fill()(填充路径)、stroke()(描边路径);
  • 文本类fillText()(填充文本)、strokeText()(描边文本);
  • 图像类drawImage()(绘制图像);
  • 状态管理save()(保存当前绘图状态,如颜色、变换)、restore()(恢复之前保存的状态),避免不同绘图操作互相干扰。

路径绘制示例:绘制笑脸

onPaint: {
    var ctx = getContext("2d");
    ctx.clearRect(0, 0, width, height); // 清空整个画布

    // 绘制脸部:开启新路径,画圆形
    ctx.beginPath();
    // 在(200,150)位置画半径80的整圆(0到2π)
    ctx.arc(200, 150, 80, 0, Math.PI * 2, false);
    ctx.fillStyle = "#F8E71C"; // 填充黄色
    ctx.fill();
    ctx.strokeStyle = "#333"; // 描边深灰
    ctx.lineWidth = 2;
    ctx.stroke();

    // 绘制左眼:开启新路径,画小圆形
    ctx.beginPath();
    ctx.arc(170, 120, 10, 0, Math.PI * 2, false);
    ctx.fillStyle = "#333";
    ctx.fill();

    // 绘制右眼
    ctx.beginPath();
    ctx.arc(230, 120, 10, 0, Math.PI * 2, false);
    ctx.fill();

    // 绘制微笑嘴巴:用半圆描边
    ctx.beginPath();
    // 画从0到π的半圆,开口向上形成微笑
    ctx.arc(200, 160, 40, 0, Math.PI, false);
    ctx.strokeStyle = "#333";
    ctx.lineWidth = 4;
    ctx.stroke();
}

每绘制一个独立图形都需用beginPath()开启新路径,避免线条互相干扰,这是路径绘制的关键技巧。






11.3 路径与复杂图形

路径是绘制复杂自定义图形的核心,它像画笔在画布上勾勒的轨迹,通过组合直线、曲线(如贝塞尔曲线)和弧线,可画出任意形状——从心形到复杂图标都能实现。


路径绘制基本步骤:

  1. 调用beginPath()开启新路径;
  2. moveTo()移动画笔到起始位置,再用lineTo()bezierCurveTo()(三次贝塞尔曲线)等方法勾勒轨迹;
  3. 可选调用closePath(),自动连接起点与终点形成闭合图形;
  4. 调用fill()填充图形,或stroke()给图形描边。

贝塞尔曲线绘制心形示例

onPaint: {
    var ctx = getContext("2d");
    ctx.clearRect(0, 0, width, height);

    ctx.beginPath();
    // 从中心出发,用三次贝塞尔曲线勾勒心形左右两半
    ctx.moveTo(200, 150);
    ctx.bezierCurveTo(200, 130, 150, 100, 150, 130);
    ctx.bezierCurveTo(150, 170, 200, 190, 200, 210);
    ctx.bezierCurveTo(200, 190, 250, 170, 250, 130);
    ctx.bezierCurveTo(250, 100, 200, 130, 200, 150);
    ctx.closePath();

    // 创建径向渐变填充:从浅粉到深粉
    var gradient = ctx.createRadialGradient(200, 150, 10, 200, 150, 70);
    gradient.addColorStop(0, "#FF9E9E"); // 中心浅粉
    gradient.addColorStop(1, "#D81B60"); // 边缘深粉
    ctx.fillStyle = gradient;
    ctx.fill();
    ctx.strokeStyle = "#880E4F"; // 描边深紫红色
    ctx.lineWidth = 3;
    ctx.stroke();
}

三次贝塞尔曲线通过三个控制点调整形状,灵活度极高,是绘制复杂曲线的首选工具。






11.4 渐变效果

渐变能让颜色过渡更平滑,Canvas支持两种渐变类型:

  • 线性渐变:沿直线从一种颜色过渡到另一种(或多种)颜色,用createLinearGradient(x0, y0, x1, y1)创建,参数为渐变起点(x0,y0)与终点(x1,y1);
  • 径向渐变:以圆形区域为范围,放射状过渡颜色,用createRadialGradient(x0, y0, r0, x1, y1, r1)创建,参数为起始圆的中心、半径,以及结束圆的中心、半径。

创建渐变后,需用addColorStop(位置, 颜色)添加颜色节点,位置为0-1之间的数值,0代表起点,1代表终点。


线性渐变示例:绘制渐变背景与文字

onPaint: {
    var ctx = getContext("2d");
    // 创建从左上角到右下角的线性渐变
    var linGrad = ctx.createLinearGradient(0, 0, width, height);
    linGrad.addColorStop(0, "#4A90E2");   // 起始色:蓝色
    linGrad.addColorStop(0.5, "#7ED321"); // 中间色:绿色
    linGrad.addColorStop(1, "#F8E71C");   // 结束色:黄色

    // 用渐变填充整个画布作为背景
    ctx.fillStyle = linGrad;
    ctx.fillRect(0, 0, width, height);

    // 在渐变背景上绘制带描边的白色文字
    ctx.font = "bold 36px Arial"; // 设置字体样式
    ctx.fillStyle = "white"; // 填充色白色
    ctx.strokeStyle = "#333"; // 描边色深灰
    ctx.lineWidth = 2;
    ctx.fillText("QT Quick Canvas", 60, 180); // 填充文字
    ctx.strokeText("QT Quick Canvas", 60, 180); // 描边文字,提升辨识度
}

该示例会生成蓝-绿-黄的渐变背景,叠加带深灰描边的白色文字,视觉效果醒目。






11.5 图像处理

Canvas不仅能绘制图形,还可加载、绘制图像,甚至进行像素级操作实现滤镜效果。


图像绘制与裁剪示例

Canvas {
    id: canvasWithImage
    width: 400; height: 300

    // 用隐藏的Image元素预加载图片,避免绘制卡顿
    Image {
        id: sourceImage
        source: "qrc:/images/landscape.jpg"
        visible: false
        // 图片加载完成后请求绘制
        onStatusChanged: if (status == Image.Ready) canvasWithImage.requestPaint();
    }

    onPaint: {
        var ctx = getContext("2d");
        ctx.clearRect(0, 0, width, height);

        if (sourceImage.status == Image.Ready) {
            // 1. 绘制完整图像,放在(10,10)位置,大小180×120
            ctx.drawImage(sourceImage, 10, 10, 180, 120);

            // 2. 裁剪图像部分区域绘制:从源图像(50,50)裁剪100×100区域,
            // 放到画布(200,10)位置,放大为180×180
            ctx.drawImage(sourceImage,
                          50, 50, 100, 100, // 源图像裁剪区域
                          200, 10, 180, 180); // 画布目标区域

            // 3. 添加灰色滤镜:修改像素值实现
            var imageData = ctx.getImageData(10, 10, 180, 120);
            var data = imageData.data; // 像素数组,每个像素含R、G、B、Alpha四位
            for (var i = 0; i < data.length; i += 4) {
                // 计算RGB平均值,赋值给R、G、B,实现灰色效果
                var avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
                data[i]     = avg; // 红色通道
                data[i + 1] = avg; // 绿色通道
                data[i + 2] = avg; // 蓝色通道
                // Alpha通道保持不变,不影响透明度
            }
            // 将修改后的像素放回画布(10,150)位置
            ctx.putImageData(imageData, 10, 150);
        }
    }
}

性能提示:频繁调用getImageData()putImageData()会消耗较多性能,复杂图像处理建议放到后台线程(如WorkerScript)执行,避免界面卡顿。






本章总结

Canvas是QT Quick实现动态2D绘图的核心工具,用法与HTML5 Canvas高度相似,易上手、功能强:

  • 通过2D上下文调用API,可轻松绘制矩形、路径、文本与图像;
  • 路径是自定义复杂图形的基础,组合直线、曲线即可实现任意形状;
  • 线性与径向渐变能为图形添加丰富色彩过渡;
  • 图像处理支持绘制、裁剪与像素级操作,丰富界面视觉内容。

掌握Canvas后,你将摆脱标准控件限制,打造独一无二的动态视觉效果与自定义图表组件,让QT Quick应用更具特色!