在Qt中我们通过paintEvent和QPainter的组合可以在控件中进行各种绘制操作, 包括绘制各种图形和文字。同样在QML中引入了画布元素(canvas element),通过使用画布元素我们也可以画出各种各样的图形,同时这个元素允许脚本绘制。画布元素提供了一个依赖于分辨率的位图画布,你可以使用JavaScript脚本来绘制图形,制作游戏或者其它的动态图像。QML中的画布元素是基于HTML5的画布元素来完成的。
画布元素的基本思想是使用一个2D对象来渲染路径。这个2D对象包括了必要的绘图函数,画布元素充当绘制的画布。
下面分几类使用场景来介绍QML的画布元素的使用。
绘制基本元素
基本元素包括矩形、圆弧、圆角矩形、贝塞尔曲线、文本、渐变色、阴影效果、图片。
在Canvas控件中我们通过getContext("2d")接口可以获得绘制的上下文来进行绘制。
import QtQuick 2.8
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("QML Canvas")
Canvas{
id: root
width: 600; height: 600
onPaint: {
var ctx = getContext("2d")
ctx.lineWidth = 3 //画笔的宽度
ctx.strokeStyle = "#0000FF" //画笔的颜色
ctx.fillStyle = "#4D9CF8" //填充的颜色
/******绘制矩形******/
ctx.beginPath()
ctx.moveTo(50,50)
ctx.lineTo(150,50)
ctx.lineTo(150,150)
ctx.lineTo(50,150)
ctx.closePath()
ctx.fill()
ctx.stroke()
//在(160,50)位置绘制一个宽高都为100的矩形
ctx.fillRect(160,50,100,100)
//采用清空的模式在(185,75)绘制出一个矩形
ctx.clearRect(185,75,50,50)
//在(270,50)绘制出一个长宽为50的矩形边框
ctx.strokeRect(270,50,50,50)
/******绘制圆弧******/
//以(330,75)为中心,半径为50,起始角为0,终止角为120,绘制逆时针圆弧
ctx.beginPath()
ctx.arc(380,75, 50, 0, -1*Math.PI*120/180,true)
ctx.stroke()
/******绘制圆角矩形*****/
ctx.beginPath()
ctx.roundedRect(440, 75, 100, 60, 5, 5)
ctx.fill()
/******绘制贝塞尔曲线******/
//以(50,200)为起始点,(80,190)和(110,280)为控制点,(150,220)为终点
ctx.beginPath()
ctx.moveTo(50, 200)
ctx.bezierCurveTo(80, 190, 110, 280, 150, 220)
ctx.stroke()
/******绘制文本******/
ctx.lineWidth = 1
ctx.font="70px Arial";
ctx.fillText("QML测试",160,220)
/******绘制渐变色******/
var gradient = ctx.createLinearGradient(50,250,150,250)
//定义线性变化中某个位置的颜色
gradient.addColorStop(0,"blue")
gradient.addColorStop(0.5, "lightsteelblue")
ctx.fillStyle = gradient
//在线性变化的区域内绘制矩形
ctx.fillRect(50,250,150,50);
/******绘制带阴影效果的文字******/
//阴影的属性
ctx.shadowColor = "blue"
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 5;
//字体的样式
ctx.font="70px Arial";
ctx.fillStyle = "#33a9ff";
ctx.fillText("阴影文字效果",60,380);
/******绘制图片******/
//在x=350,y=310的位置绘制对应的图片
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.drawImage("qrc:/images/test.jpg", 500, 310)
}
//在绘制图片之前先缓存对应的图片
Component.onCompleted: {
loadImage("qrc:/images/test.jpg")
}
}
}
绘制效果如下图所示:
移动旋转坐标系
画布有多种方式来转换坐标系。这些操作非常类似于QML元素的转换。你可以通过缩放(scale),旋转(rotate),translate(移动)来转换坐标系。与QML元素不同的是,Canvas转换原点通常是画布的原点。
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
visible: true
width: 640
height: 480
title: qsTr("Canvas Element")
Canvas {
id: root
width: 400; height: 400
onPaint: {
var ctx = getContext("2d");
ctx.lineWidth = 4;
ctx.strokeStyle = "blue";
//移动画布的坐标系
ctx.translate(root.width/2, root.height/2);
// 绘制矩形
ctx.beginPath();
ctx.rect(-40, -40, 80, 80);
ctx.stroke();
// 旋转坐标系
ctx.rotate(Math.PI/3);
ctx.strokeStyle = "green";
// 旋转坐标系之后绘制矩形
ctx.beginPath();
ctx.rect(-40, -40, 80, 80);
ctx.stroke();
}
}
}
?显示效果如下图所示:
多个绘制元素的组合模式
组合允许你绘制一个形状与已经有的像素点集合混合。画布提供了多种组合模式,使用globalCompositeOperation(mode)来设置。
import QtQuick 2.8
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Canvas Element")
Canvas {
id: root
width: 600; height: 400
//各种组合模式
property var operation : [
'source-over', 'source-in', 'source-over',
'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 = '#4D9CF8'
ctx.fillRect(10+dx,10+dy,60,60)
//设置组合模式和透明度
ctx.globalCompositeOperation = root.operation[i]
ctx.fillStyle = '#FF33A7'
ctx.globalAlpha = 0.75
//绘制对应的圆弧
ctx.beginPath()
ctx.arc(60+dx, 60+dy, 30, 0, 2*Math.PI)
ctx.closePath()
ctx.fill()
ctx.restore()
}
}
}
}
Canvas对象实现像素缓冲
当你使用画布的时候,你可以检索读取画布上的像素数据,或者操作画布上的像素。读取像素数据使用createImageData(sw,sh)或者getImageData(sx,sy,sw,sh)。这两个函数都会返回一个含宽度(width)高度(height)和数据(data)的图像数据对象(ImageDta)。图像数据包含了一维数组像素数据,使用RGBA格式进行检索。每个像素的数据范围在0-255之间。设置画布像素数据可以使用putImageData(imagedata,dx,dy)函数来完成。也可以将画布数据保存到图片中。通过save(path)或者toDataURL(mimeType)来完成导出。
像素缓冲其实就是将画布上的内容保存成一个二进制块,可以将对二进制块中的数据进行处理也可以将二进制块保存成对应的图片。
import QtQuick 2.8
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Canvas Element")
Canvas {
id: root
width: 600; height: 400
onPaint: {
var ctx = getContext("2d");
draw(ctx);
}
//点击鼠标左键之后将渲染的结果保存到某个元素中
MouseArea{
anchors.fill: parent
onClicked: {
var url = root.toDataURL('image/png')
//将渲染的图片结构作为另一个元素的输入
print('image url =',url)
image.source = url
}
}
Image{
id: image
x: 200; y:100
width:300
height:300
}
Timer{
interval: 1000
running: true
triggeredOnStart: true
repeat: true
onTriggered: root.requestPaint()
}
function draw(ctx) {
//绘制一个渐变矩形
var gradient = ctx.createLinearGradient(50,250,150,250)
gradient.addColorStop(0,"blue")
gradient.addColorStop(0.5, "lightsteelblue")
ctx.fillStyle = gradient
ctx.fillRect(50,50,150,50);
}
}
}
绘制效果如下图所示:
?
通过Canvas实现一个简单的画板软件
依据简单的绘图需求,制作一个可以改变画笔颜色的画图程序,程序代码如下:
import QtQuick 2.8
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Canvas Element")
//最外面的大边框
Rectangle {
width: 400; height: 300
color: "#333333"
//颜色选择栏
Row {
id: colorTools
anchors {
horizontalCenter: parent.horizontalCenter
top: parent.top
topMargin: 8
}
property variant activeSquare: red
property color paintColor: "#33B5E5"
spacing: 4
Repeater {
model: ["#33B5E5", "#99CC00", "#FFBB33", "#FF4444"]
Rectangle {
id: red
color: modelData
width: 20
height: 20
MouseArea{
anchors.fill: parent
onClicked: {
colorTools.paintColor = color
}
}
}
}
}
//画图区的外边框
Rectangle {
anchors.fill: canvas
border.color: "#666666"
border.width: 4
}
//真正的绘图区
Canvas {
id: canvas
anchors {
left: parent.left
right: parent.right
top: colorTools.bottom
bottom: parent.bottom
margins: 8
}
property real lastX
property real lastY
property color color: colorTools.paintColor
//每次绘制都得重新计算绘制点
onPaint: {
var ctx = getContext('2d')
ctx.lineWidth = 1.5
ctx.strokeStyle = canvas.color
ctx.beginPath()
ctx.moveTo(lastX, lastY)
lastX = area.mouseX
lastY = area.mouseY
ctx.lineTo(lastX, lastY)
ctx.stroke()
}
//每次位置变换都得重绘
MouseArea {
id: area
anchors.fill: parent
onPressed: {
canvas.lastX = mouseX
canvas.lastY = mouseY
}
onPositionChanged: {
canvas.requestPaint()
}
}
}
}
}
程序的运行效果如下图所示:
?
|