Qt葵花宝典阅读感受3

Canvas Element(画布元素)

在早些时候的 Qt4 中加入 QML 时,一些开发者讨论如何在 QtQuick 中绘制一个圆形。 类似圆形的问题,一些开发者也对于其它的形状的支持进行了讨论。在QtQuick 中 没有圆形,只有矩形。在Qt4 中,如果你需要一个除了矩形外的形状,你需要使用 图片或者使用你自己写的C++ 圆形元素。
Qt5 中引进了画布元素(canvas element),允许脚本绘制。画布元素(canvas element)提供了一个依赖于分辨率的位图画布,你可以使用 JavaScript 脚本来绘制 图形,制作游戏或者其它的动态图像。画布元素(canvas element)是基于 HTML5 的画布元素来完成的。
画布元素(canvas element)的基本思想是使用一个 2D 对象来渲染路径。这个 2D 对象包括了必要的绘图函数画布元素(canvas element)充当绘制画布 2D 对象 支持画笔,填充,渐变,文本和绘制路径创建命令。
让我们看看一个简单的路径绘制的例子:
Window {//定义一个窗口
    visible: true //设置窗口的可见性为true,这意味着当您运行此代码时,窗口将立即显示出来。
    width: 600
    height: 480
    title: "Image Window"
    //这一行定义了一个Canvas元素(画布),
    //Canvas是Qt Quick中用于绘图的一个组件。
    Canvas {
        id: root
        width: 200; height: 200
        //这一行开始定义一个onPaint事件处理程序,
        //当Canvas需要重新绘制时,这个处理程序将被调用。
        onPaint: {
            //获取一个2D渲染上下文,并存储在变量ctx中。
            //这个上下文是用来绘制图形、文本和图像的
            var ctx = getContext("2d")
            //这一行设置了线条的宽度为4像素。
            ctx.lineWidth = 4
            //设置了笔触颜色为蓝色。
            ctx.strokeStyle = "blue"
            //设置了填充颜色为钢蓝色。
            ctx.fillStyle = "steelblue"
            //开始一个新的路径。所有后续的绘图命令都会添加到这个路径上
            ctx.beginPath()
            //将画笔移动到坐标(50,50)。
            ctx.moveTo(50,50)
            //从当前位置画一条线到坐标(150,50)。
            ctx.lineTo(150,50)
            ctx.lineTo(150,150)
            ctx.lineTo(50,150)
            //关闭路径,形成一个封闭的多边形。
            ctx.closePath()
            //通过ctx.fill()和ctx.stroke()命令,填充矩形并绘制其轮廓。
            ctx.fill()
            ctx.stroke()
        }
    }
}

画笔的宽度被设置为 4 个像素,并且定义 strokeStyle (画笔样式)为蓝色。最后的 形状由设置填充样式(fillStyle)为 steelblue 颜色,然后填充完成的。只有调用 stroke或者 fill 函数,创建的路径才会绘制,它们与其它的函数使用是相互独立的。
调用stroke或者fill 将会绘制当前的路径,创建的路径不可重用的,只有绘制状态能够被存储和恢复
QML 中,画布元素(canvas element)充当了绘制的容器 2D绘制对象提供了实际绘制的方法。绘制需要在onPaint 事件中完成
画布自身提供了典型的二维笛卡尔坐标系统,左上角是(0,0)坐标。 Y轴坐标轴向下,X 轴坐标轴向右
典型绘制命令调用如下:
1. 装载画笔或者填充模式
2. 创建绘制路径
3. 使用画笔或者填充绘制路径

 

注意
通常在你重置了路径后你将会设置一个开始点,所以,在 beginPath() 这个操作后,你需要使用moveTo 来设置开始点。如果是第一次设置,默认就是(0,0)

便捷的接口(Convenient API

绘制矩形时,我们提供了一个便捷的接口,而不需要调用 stroke或者fill 来完成。
注意
画笔的绘制区域由中间向两边延展。一个宽度为 4 像素的画笔将会在绘制路径的里 面绘制2 个像素,外面绘制 2 个像素。

渐变(Gradients

画布中可以使用颜色填充也可以使用渐变或者图像来填充。
 Canvas {
        id: root
        width: 150; height: 150
        onPaint: {
            var ctx = getContext("2d")
            //创建一个线性渐变。渐变的起始点是 (100, 0),结束点是 (100, 200)。
            //这意味着渐变的方向是从垂直向上到垂直向下
            //起始点和结束点都是针对画布而言的,渐变点的范围可以比较大,
            //垂直渐变实际上跟x关系不大,y的值范围包含下面的矩形就可以凸显渐变
            //渐变色节点比例实际上只跟这里的0-200有关系,跟下面矩形没关系
            var gradient = ctx.createLinearGradient(1000,0,1000,200)
            //这行代码在渐变的起始位置添加一个颜色停止点
            gradient.addColorStop(0, "blue")
            gradient.addColorStop(0.5, "lightsteelblue")
            //这行代码将填充样式设置为之前创建的渐变。
            ctx.fillStyle = gradient
            //矩形的左上角坐标是 (50, 50),宽度是 100,高度是 100。由于设置了
            //填充样式为渐变,这个矩形将显示为从蓝色到浅钢蓝色的渐变效果。
            ctx.fillRect(50,50,100,100)
        }
    }

在这个例子中,渐变色定义在开始点 100,0)到结束点(100,200 。在我们画布 中是一个中间垂直的线。渐变色在停止点定义一个颜色,范围从0.0 1.0 。这里我 们使用一个蓝色作为0.0 100,0 ),一个高亮刚蓝色作为 0.5 100,200 )。渐变色 的定义比我们想要绘制的矩形更大,所以矩形在它定义的范围内对渐变进行了裁剪。
 Canvas {
        id: root
        width: 150; height: 150
        onPaint: {
            var ctx = getContext("2d")
            //斜线(0,0)-(200,200)左上角往右下角的一条线渐变
            var gradient = ctx.createLinearGradient(0,0,200,200)
            //这行代码在渐变的起始位置添加一个颜色停止点
            gradient.addColorStop(0, "blue")
            gradient.addColorStop(0.5, "lightsteelblue")
            //这行代码将填充样式设置为之前创建的渐变。
            ctx.fillStyle = gradient
            //矩形的左上角坐标是 (50, 50),宽度是 100,高度是 100。由于设置了
            //填充样式为渐变,这个矩形将显示为从蓝色到浅钢蓝色的渐变效果。
            ctx.fillRect(50,50,100,100)
        }
    }

阴影(Shadows

注意

Qt5 alpha 版本中,我们使用阴影遇到了一些问题。
2D 对象的路径可以使用阴影增强显示效果。阴影是一个区域的轮廓线使用偏移量, 颜色和模糊来实现的。所以你需要指定一个阴影颜色(shadowColor),阴影 X 轴 偏移值(shadowOffsetX),阴影 Y 轴偏移值(shadowOffsetY)和阴影模糊 (shadowBlur)。这些参数的定义都使用 2D context 来定义。 2D context 是唯一的 绘制操作接口。
阴影也可以用来创建发光的效果。在下面的例子中我们使用白色的光创建了一 个“Earth” 的文本。在一个黑色的背景上可以有更加好的显示效果。
首先我们绘制黑色背景:

图片(Images

QML 画布支持多种资源的图片绘制。在画布中使用一个图片需要先加载图片资源。
在我们的例子中我们使用 Component.onCompleted 操作来加载图片
Canvas {
        id: root
        width: 300; height: 300
        // 当Canvas需要重新绘制时,会触发onPaint事件。
        onPaint: {
            var ctx = getContext("2d")
            // 在坐标(10,10)处绘制一个名为'football.png'的图像。
            ctx.drawImage('images/football.png', 10, 10)
            //存储当前上下文设置
            ctx.save()
            ctx.strokeStyle = 'red'
            //创建一个三角形作为剪辑区域
            //将坐标系原点移动到(100,0)。
            ctx.translate(200,0)
            ctx.beginPath()
            ctx.moveTo(10,10)
            ctx.lineTo(55,10)
            ctx.lineTo(35,55)
            ctx.closePath()
            ctx.clip() //根据当前的路径创建一个剪切区域。
            //只显示剪切区域内的部分。
            // draw image with clip applied
            ctx.drawImage('images/football.png', 10, 10)
            // draw stroke around path
            ctx.stroke()
            // restore previous setup恢复之前保存的绘图上下文状态。
            ctx.restore()
        }
        //当组件完成加载时执行以下操作
        Component.onCompleted: {
            loadImage("images/football.png")
        }
    }

在左边,足球图片使用 10×10 的大小绘制在左上方的位置。在右边我们对足球图片 进行了裁剪。图片或者轮廓路径都可以使用一个路径来裁剪。裁剪需要定义一个裁 剪路径,然后调用clip() 函数来实现裁剪。在 clip() 之前所有的绘制操作都会用来进行 裁剪。如果还原了之前的状态或者定义裁剪区域为整个画布时,裁剪是无效的。

转换(Transformation

画布有多种方式来转换坐标系。这些操作非常类似于 QML 元素的转换。你可以通过 缩放(scale),旋转(rotate),translate(移动)来转换坐标系。与 QML 元素的 转换不同的是,转换原点通常就是画布原点。例如,从中心点放大一个封闭的路径,你需要先将画布原点移动到整个封闭的路径的中心点上。使用这些转换的方法 你可以创建一些更加复杂的转换。
 Canvas {
        id: root
        width: 240; height: 120
        onPaint: {
            var ctx = getContext("2d")
            ctx.translate(120,60)
            ctx.strokeStyle = "blue"
            ctx.lineWidth = 4
            ctx.beginPath()
            ctx.rect(-20, -20, 40, 40)
            ctx.stroke()
            // draw path now rotated
            ctx.rotate(Math.PI/4) //旋转整个画布45度
            //没有下面这两行根本显示不出来两个矩形框
            //因为旋转操作是在绘制第二个矩形之前完成的,
            //所以第一个矩形(蓝色)在旋转之后不会被重新绘制。
            ctx.beginPath()
            ctx.rect(-20, -20, 40, 40)
            ctx.strokeStyle = "green"
            ctx.stroke()
        }
    }

除了移动画布外,也可以使用 scale(x,y) 来缩放 x,y 坐标轴。旋转使用 rotate(angle) , angle是角度(
360 =2*Math.PI )。使用 setTransform(m11,m12,m21,m22,dx,dy) 来完成矩阵转换。
警告
QML 画布中的转换与 HTML5 画布中的机制有些不同。不确定这是不是一个 Bug
注意
重置矩阵你可以调用 resetTransform() 函数来完成,这个函数会将转换矩阵还原为 单位矩阵。

组合模式(Composition Mode

组合允许你绘制一个形状然后与已有的像素点集合混合。画布提供了多种组合模 式,使用globalCompositeOperation(mode) 来设置。
 Canvas {
        id: root
        width: 240; height: 120
        onPaint: {
            var ctx = getContext("2d")
            //设置全局的合成操作为“xor”。
            //这意味着绘制的新图形和已有的图形将进行XOR操作,而不是简单的覆盖。
            ctx.globalCompositeOperation = "xor"
            ctx.fillStyle = "#33a9ff"
            for(var i=0; i<40; i++) {
                ctx.beginPath()
                //在Canvas上绘制一个随机位置和大小的圆形。
                //这个圆形的位置由Math.random()*400和Math.random()*200决定,
                //半径为20像素,起始角度为0,结束角度为2*Math.PI(一个完整的圆)。
                ctx.arc(Math.random()*400, Math.random()*200, 20, 0, 2*Math.PI)
                ctx.closePath()
                ctx.fill()
            }

        }
    }

下面这个例子遍历了列表中的组合模式,使用对应的组合模式生成了一个矩形与圆形的组合。
Canvas {
        id: root
        width: 640; height: 120
        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.fillStyle = '#33a9ff'
                ctx.fillRect(10+dx,10+dy,60,60)
                // TODO: does not work yet
                //通过操作数组组合模式
                ctx.globalCompositeOperation = root.operation[i]
                ctx.fillStyle = '#ff33a9'
                ctx.globalAlpha = 0.75
                ctx.beginPath()
                ctx.arc(60+dx, 60+dy, 30, 0, 2*Math.PI)
                ctx.closePath()
                ctx.fill()
                //恢复
                ctx.restore()
            }
        }
    }

像素缓冲(Pixels Buffer

当你使用画布时,你可以检索读取画布上的像素数据,或者操作画布上的像素。读取图像数据使用createImageData(sw,sh) 或者getImageData(sx,sy,sw,sh) 。这两个函数都会返回一个包含宽度(width ),高度(height)和数据(data)的图像数据 (ImageData)对象。图像数据包含了一维数组像素数据,使用 RGBA 格式进行检索。每个数据的数据范围在0 255 之间。设置画布的像素数据你可以使用 putImageData(imagedata,dx,dy)函数来完成。
另一种检索画布内容的方法是将画布的数据存储进一张图片中。可以使用画布的函数save(path) 或者toDataURL(mimeType) 来完成,toDataURL(mimeType)会返回一个图片的地址,这个链接可以直接用Image 元素来读取。
 Rectangle {
        width: 240; height: 120
        Canvas {
            id: canvas
            x: 10; y: 10
            width: 100; height: 100
            property real hue: 0.0
            onPaint: {
                var ctx = getContext("2d")
                //随机生成一个点
                var x = 10 + Math.random(80)*80
                var y = 10 + Math.random(80)*80
                //生成0-0.1随机,当累计超过1时重置为0
                hue += Math.random()*0.1
                if(hue > 1.0) { hue -= 1 }
                //设置绘图的全局透明度为0.7。
                ctx.globalAlpha = 0.7
                //使用HSLA颜色模式设置填充颜色,其中hue是色相,
                //0.5是饱和度,0.5是亮度,1.0是alpha(透明度)
                ctx.fillStyle = Qt.hsla(hue, 0.5, 0.5, 1.0)
                ctx.beginPath()
                ctx.moveTo(x+5,y)
                //绘制一个从随机点开始的圆形路径,并填充该路径。x/10是半径
                ctx.arc(x,y, x/10, 0, 360)
                ctx.closePath()
                ctx.fill()
            }
            MouseArea {
                anchors.fill: parent
                onClicked: {
                    //将Canvas的内容转换为PNG格式的图像数据URL
                    var url = canvas.toDataURL('image/png')
                    print('image url=', url)
                    //将Image组件的源设置为这个URL,从而显示Canvas上的内容。
                    //因此随机绘画什么,点击绘画鼠标区域的时候,就会在右侧同步绘画
                    image.source = url
                }
            }
        }
        Image {
            id: image
            x: 130; y: 10
            width: 100; height: 100
        }
        Timer {
            //当Timer触发时触发的事件处理器,请求重新绘制Canvas。
            //不启动定时器也只能画一次
            interval: 1000
            //使Timer立即开始运行。
            running: true
            //在Timer开始时触发一次。
            triggeredOnStart: true
            //定时器重复触发事件。
            repeat: true
            //当Timer触发时触发的事件处理器,请求重新绘制Canvas。
            onTriggered: canvas.requestPaint()
        }
    }

画布绘制(Canvas Paint

在这个例子中我们将使用画布( Canvas )创建一个简单的绘制程序。
在我们场景的顶部我们使用行定位器排列四个方形的颜色块。一个颜色块是一个简 单的矩形,使用鼠标区域来检测点击。
Window {//定义一个窗口
    visible: true //设置窗口的可见性为true,这意味着当您运行此代码时,窗口将立即显示出来。
    width:  680
    height: 480
    title: "Image Window"
    //这一行定义了一个Canvas元素(画布),
    //Canvas是Qt Quick中用于绘图的一个组件。
    Row {
        id: colorTools
        anchors {
            horizontalCenter: parent.horizontalCenter
            top: parent.top
            topMargin: 8
        }
        property color paintColor: "#33B5E5"
        spacing: 4
        Repeater {
            model: ["#33B5E5", "#99CC00", "#FFBB33", "#FF4444"]
            Rectangle {
                id: red
                width: 48
                height: 48
                color: modelData
                MouseArea{
                    anchors.fill: parent
                    onClicked: {
                        //经点击的颜色赋值给自定义颜色
                        colorTools.paintColor = red.color
                    }
                }
            }
        }
    }
    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()
            }
        }
    }
}

HTML5画布移植(Porting from HTML5 Canvas

这个后面再研究

  • 30
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值