QmlBook in chinese 编程之十五 ---(画布元素)

画布元素:

      Qt5中引进了画布元素(canvas element) ,允许脚本绘制。画布元素(canvaselement) 提供了一个依赖于分辨率的位图画布,你可以使用JavaScript脚本来绘制图形,制作游戏或者其它的动态图像。画布元素(canvas element) 是基于HTML5的画布元素来完成的。

      画布元素(canvas element) 的基本思想是使用一个2D对象来渲染路径。这个2D对象包括了必要的绘图函数,画布元素(canvas element) 充当绘制画布。2D对象
支持画笔,填充,渐变,文本和绘制路径创建命令。

让我们看看一个简单的路径绘制的例子:

QmlBook in chinese 编程之十五 ---(画布元素)_第1张图片

import QtQuick 2.12
import QtQuick.Window 2.12

Canvas{
    id: root
    // canvas size
    width: 200; height: 200
    // handler to override for drawing
    onPaint: {
        var ctx = getContext("2d") //绘制对象
        // setup the stroke
        ctx.lineWidth = 4
        ctx.strokeStyle = "blue"
        //setup  the  fill
        ctx.fillStyle = "steelblue"
         // begin a new path to draw
        ctx.beginPath()

        ctx.moveTo(50,50)
        ctx.lineTo(150,50)
        ctx.lineTo(150,150)
        ctx.lineTo(50,150)

        ctx.closePath()

        ctx.fill()
        ctx.stroke()
    }

}

     这个例子产生了一个在坐标(50,50) ,高宽为100的填充矩形框,并且使用了画笔来修饰边界。画笔的宽度被设置为4个像素,并且定义strokeStyle(画笔样式) 为蓝色。最后的形状由设置填充样式(fillStyle) 为steelblue颜色,然后填充完成的。只有调用stroke或者fill函数,创建的路径才会绘制,它们与其它的函数使用是相互独立的。调用stroke或者fill将会绘制当前的路径,创建的路径是不可重用的,只有绘制状态能够被存储和恢复。

    在QML中,画布元素(canvas element) 充当了绘制的容器。2D绘制对象提供了实际绘制的方法。绘制需要在onPaint事件中完成。


便捷的接口(Convenient API)

   在绘制矩形时,我们提供了一个便捷的接口,而不需要调用stroke或者fill来完成。

QmlBook in chinese 编程之十五 ---(画布元素)_第2张图片

import QtQuick 2.0

Canvas {
    id: root
    // canvas size
    width: 200; height: 200
    // handler to override for drawing

    onPaint: {
        var ctx = getContext("2d")
        // setup the stroke
        ctx.lineWidth = 4
        ctx.strokeStyle = "green"
        //setup  the  fill
        ctx.fillStyle = "steelblue"

        ctx.fillRect(20,20,80,80)
        ctx.clearRect(30,30, 60, 60)

        ctx.strokeRect(20,20, 40, 40)
    }

}

画笔的绘制区域由中间向两边延展。一个宽度为4像素的画笔将会在绘制路径的里面绘制2个像素,外面绘制2个像素。

渐变(Gradients)

  画布中可以使用颜色填充也可以使用渐变或者图像来填充。

QmlBook in chinese 编程之十五 ---(画布元素)_第3张图片

import QtQuick 2.0

Canvas {

    id: root
    width: 200; height: 200

    onPaint: {
        var ctx = getContext("2d")
        //(100,0)开始点 (100,200)结束点
        var gradient = ctx.createLinearGradient(100,0,100,200)
        gradient.addColorStop(0,"blue")
        gradient.addColorStop(0.5,"lightsteelblue")
        ctx.fillStyle = gradient
        ctx.fillRect(50,50,100,100)
    }

}

      在这个例子中,渐变色定义在开始点(100,0) 到结束点(100,200) 。在我们画布中是一个中间垂直的线。渐变色在停止点定义一个颜色,范围从0.0到1.0。这里我们使用一个蓝色作为0.0(100,0) ,一个高亮刚蓝色作为0.5(100,200) 。渐变色的定义比我们想要绘制的矩形更大,所以矩形在它定义的范围内对渐变进行了裁剪。

阴影(Shadows)

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

QmlBook in chinese 编程之十五 ---(画布元素)_第4张图片

import QtQuick 2.0

Canvas {
    id: canvas
    width: 400; height: 200
    smooth: true

    onPaint: {
        var ctx = getContext("2d")
        ctx.strokeStyle = "#333333"
        ctx.fillRect(0,0,canvas.width,canvas.height);

        ctx.shadowColor = "#2ed5fa";
        ctx.shadowOffsetX = 2;
        ctx.shadowOffsetY = 2;
        ctx.shadowBlur = 10;

        ctx.font = 'bold 80px Windows';
        ctx.fillStyle = "#24d12e";
        ctx.fillText("Canvas!",30,180);
    }
}

图片(Images)

QML画布支持多种资源的图片绘制。在画布中使用一个图片需要先加载图片资源。在我们的例子中我们使用Component.onCompleted操作来加载图片。

在项目中放了一个图片资源,运行:

QmlBook in chinese 编程之十五 ---(画布元素)_第5张图片

import QtQuick 2.0

Canvas {

    id: root
    width: 400; height: 120

    onPaint: {
        var ctx = getContext("2d")
        ctx.drawImage('assets/ball.png', 10, 10)

        ctx.save() //保存
        ctx.strokeStyle = '#ff2a68'
        //开始画切割图
        ctx.beginPath()
        ctx.moveTo(110,10)
        ctx.lineTo(155,10)
        ctx.lineTo(135,55)
        ctx.closePath()

        ctx.clip()// create clip from the path
        ctx.drawImage('assets/ball.png', 100, 10)
        ctx.stroke()
        // restore previous context
        ctx.restore()

    }

    Component.onCompleted: {
        loadImage('assets/ball.png')
    }

}

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

转换(Transformation)

     画布有多种方式来转换坐标系。这些操作非常类似于QML元素的转换。你可以通过缩放(scale) ,旋转(rotate) ,translate(移动) 来转换坐标系。与QML元素的转换不同的是,转换原点通常就是画布原点。例如,从中心点放大一个封闭的路径,你需要先将画布原点移动到整个封闭的路径的中心点上。使用这些转换的方法你可以创建一些更加复杂的转换。

QmlBook in chinese 编程之十五 ---(画布元素)_第6张图片

import QtQuick 2.0

Canvas {
    id: root
    width: 240; height: 120
    onPaint: {
       var ctx = getContext("2d");

        ctx.lineWidth = 4;
        ctx.strokeStyle = "blue";
        ctx.translate(root.width/2, root.height/2);

        ctx.beginPath();  //开始
        ctx.rect(-20, -20, 40, 40);
        ctx.stroke();

        // rotate coordinate system
        ctx.rotate(Math.PI/4);  //旋转
        ctx.strokeStyle = "green";

        // draw path
        ctx.beginPath();
        ctx.rect(-20, -20, 40, 40);
        ctx.stroke();

    }
}

除了移动画布外,也可以使用scale(x,y)来缩放x,y坐标轴。旋转使用rotate(angle),angle是角度(360度=2*Math.PI) 。使用setTransform(m11,m12,m21,m22,dx,dy) 来完成矩阵转换。


组合模式(Composition Mode)

组合允许你绘制一个形状然后与已有的像素点集合混合。画布提供了多种组合模式,使用globalCompositeOperation(mode)来设置。

QmlBook in chinese 编程之十五 ---(画布元素)_第7张图片

import QtQuick 2.0

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

像素缓冲(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元素来读取。

QmlBook in chinese 编程之十五 ---(画布元素)_第8张图片

import QtQuick 2.0

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

            hue += Math.random()*0.1
            if(hue > 1.0)
            {
                 hue -= 1
            }
            ctx.globalAlpha = 0.7
            ctx.fillStyle = Qt.hsla(hue, 0.5, 0.5, 1.0)
            ctx.beginPath()
            ctx.moveTo(x+5,y)
            ctx.arc(x,y, x/10, 0, 360)
            ctx.closePath()
            ctx.fill()
        }

        MouseArea{
            anchors.fill: parent

            onClicked: {
                var url = canvas.toDataURL('image/png')
                print('image url=', url)
                image.source = url
            }
        }

    }

    Image {
        id: image
        x: 130; y: 10
        width: 100; height: 100
    }

    Timer {
        interval: 1000
        running: true
        triggeredOnStart: true
        repeat: true
        onTriggered: canvas.requestPaint()
    }
}

      在我们这个例子中,我们每秒在左边的画布中绘制一个的圆形。当使用鼠标点击画布内容时,会将内容存储为一个图片链接。在右边将会展示这个存储的图片。

画布绘制(Canvas Paint)

在这个例子中我们将使用画布(Canvas) 创建一个简单的绘制程序。

QmlBook in chinese 编程之十五 ---(画布元素)_第9张图片

   在我们场景的顶部我们使用行定位器排列四个方形的颜色块。一个颜色块是一个简单的矩形,使用鼠标区域来检测点击。
 

import QtQuick 2.0

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"]
            ColorSquare
            {
                id:red
                color: modelData
                active: parent.paintColor ==color
                onClicked: {
                parent.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()
            }
        }

    }
}

     颜色存储在一个数组中,作为绘制颜色使用。当用户点击一个矩形时,矩形内的颜色被设置为colorTools的paintColor属性。
     为了在画布上跟踪鼠标事件,我们使用鼠标区域(MouseArea) 覆盖画布元素,并连接点击和移动操作。
     鼠标点击存储在laxstX与lastY属性中。每次鼠标位置的改变会触发画布的重绘,这将会调用onPaint操作。

      最后绘制用户的笔划,在onPaint操作中,我们绘制从最近改变的点上开始绘制一条新的路径,然后我们从鼠标区域采集新的点,使用选择的颜色绘制线段到新的点
上。鼠标位置被存储为新改变的位置。
 

HTML5画布移植(Porting from HTML5 Canvas)

螺旋图形(Spiro Graph)
我们使用一个来自Mozila项目的螺旋图形例子来作为我们的基础示例。原始的HTML5代码被作为画布教程发布。

QmlBook in chinese 编程之十五 ---(画布元素)_第10张图片

import QtQuick 2.0

Canvas {

    id: root
    width: 300; height: 300

    onPaint: {
        var ctx = getContext("2d")
        draw(ctx)
    }

    function draw(ctx){
        ctx.fillRect(0,0,300,300);
        for(var i = 0; i<3; i++)
        {
            for(var j=0;j<3;j++)
            {
                ctx.save();
                ctx.strokeStyle = "#9CFF00";
                ctx.translate(20+j*50,20+i*50);
                drawSpirograph(ctx,20*(j+2)/(j+1),-8*(i+3)/(i+1),10);
                ctx.restore();
            }

        }

    }

    function drawSpirograph(ctx,R,r,O)
    {
        var x1 = R-O;
        var y1 = 0;
        var i  = 1;
        ctx.beginPath();
        ctx.moveTo(x1,y1);
        do {
            if (i>20000) break;
            var x2 = (R+r)*Math.cos(i*Math.PI/72) - (r+O)*Math.cos(((R+r)/r)*(i*Math.PI/72))
            var y2 = (R+r)*Math.sin(i*Math.PI/72) - (r+O)*Math.sin(((R+r)/r)*(i*Math.PI/72))
            ctx.lineTo(x2,y2);
            x1 = x2;
            y1 = y2;
            i++;
        } while (x2 != R-O && y2 != 0 );
        ctx.stroke();

    }
}

发光线(Glowing Lines)

import QtQuick 2.0

Canvas {
    id: canvas
    width: 800; height: 450

    property real hue: 0
    property real lastX: width * Math.random()
    property real lastY: height * Math.random()

    // M1>>
    property bool requestLine: false
    property bool requestBlank: false
    // < 1.0) {
            hue -= 1
        }
        context.strokeStyle = Qt.hsla(hue, 0.5, 0.5, 1.0)
        context.shadowColor = 'white';
        context.shadowBlur = 10;
        context.stroke()
        context.restore()
    }

    function blank(context) {
        context.fillStyle = Qt.rgba(0,0,0,0.1)
        context.fillRect(0, 0, canvas.width, canvas.height)
    }

    Component.onCompleted: {
        lineTimer.start()
        blankTimer.start()
    }
}

在QML中,只能在onPaint操作中绘制。在HTML5中,通常调用setInterval使用计时器触发线段的绘制或者清屏。由于QML中不同的操作方法,仅仅只是调用这些函数不能实现我们想要的结
果,因为我们需要通过onPaint操作来实现。我们也需要修改颜色的格式。让我们看看需要改变哪些东西。

我们使用画布元素(Canvas) 作为我们QML文件的根元素。

代替直接调用的setInterval函数,我们使用两个计时器来请求重新绘制。一个计时器触发间隔较短,允许我们可以执行一些代码。我们无法告诉绘制函数哪个操作是我想触发的,我们为每个操作定义一个布尔标识,当重新绘制请求时,我们请求一个操作并且触发它。

    property bool requestLine: false
    property bool requestBlank: false
    // <

现在我们的绘制函数应该像这样:

    onPaint: {
        var context = getContext('2d')
        if(requestLine)
        {
            line(context)
            requestLine = false
        }
        if(requestBlank)
        {
            blank(context)
            requestBlank = false
        }
    }

最大的变化是使用QML的Qt.rgba()和Qt.hsla()。在QML中需要把变量值适配在0.0到1.0之间。同样应用在清屏函数中。

    function blank(context) {
        context.fillStyle = Qt.rgba(0,0,0,0.1)
        context.fillRect(0, 0, canvas.width, canvas.height)
    }

本章内容就是这莫多了,喜欢的小伙伴可以关注,一起学习qml book知识点。
 

 

 

 

你可能感兴趣的:(Qt,Quick,基础笔记,qml,画布,canvas,c++)