第九章:画布


第九章:画布

QtQt QuickQML https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Transformations
  • http://en.wikipedia.org/wiki/Spirograph (本链接需要kexue上网)
  • spirograph例子为基础展开。原生HTML5代码是画布教程 canvas tutorial里的一部分。
    有几行代码需要修改:

    • Qt Quick要求定义变量,所以要添加 var 声明:
    for (var i=0;i<3;i++) {
        ...
    }
    • 绘画方法要接收2D上下文对象
    function draw(ctx) {
        ...
    }
    • 由于尺寸不同,我们需要调整每个螺旋
    ctx.translate(20+j*50,20+i*50);

    最后,完成onPaint函数,在函数里获取上下文并调用绘图函数。

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

    结果是使用 QML 画布运行的移植螺旋图图形。

    如你所见,在不改变实际逻辑,仅改动相对较少的代码格式,就可以完成从HTML5到QML的移植。

    pretty glowing lines有着非常棒的特性,这让移植更具挑战性。

    HTML>
    <html lang="en">
    <head>
        <title>Pretty Glowing Linestitle>
    head>
    <body>
    
    <canvas width="800" height="450">canvas>
    <script>
    var context = document.getElementsByTagName('canvas')[0].getContext('2d');
    
    // initial start position
    var lastX = context.canvas.width * Math.random();
    var lastY = context.canvas.height * Math.random();
    var hue = 0;
    
    // closure function to draw
    // a random bezier curve with random color with a glow effect
    function line() {
    
        context.save();
    
        // scale with factor 0.9 around the center of canvas
        context.translate(context.canvas.width/2, context.canvas.height/2);
        context.scale(0.9, 0.9);
        context.translate(-context.canvas.width/2, -context.canvas.height/2);
    
        context.beginPath();
        context.lineWidth = 5 + Math.random() * 10;
    
        // our start position
        context.moveTo(lastX, lastY);
    
        // our new end position
        lastX = context.canvas.width * Math.random();
        lastY = context.canvas.height * Math.random();
    
        // random bezier curve, which ends on lastX, lastY
        context.bezierCurveTo(context.canvas.width * Math.random(),
        context.canvas.height * Math.random(),
        context.canvas.width * Math.random(),
        context.canvas.height * Math.random(),
        lastX, lastY);
    
        // glow effect
        hue = hue + 10 * Math.random();
        context.strokeStyle = 'hsl(' + hue + ', 50%, 50%)';
        context.shadowColor = 'white';
        context.shadowBlur = 10;
        // stroke the curve
        context.stroke();
        context.restore();
    }
    
    // call line function every 50msecs
    setInterval(line, 50);
    
    function blank() {
        // makes the background 10% darker on each call
        context.fillStyle = 'rgba(0,0,0,0.1)';
        context.fillRect(0, 0, context.canvas.width, context.canvas.height);
    }
    
    // call blank function every 50msecs
    setInterval(blank, 40);
    
    script>
    body>
    html>

    在HTML5里,上下文2D对象可以在画布上随时随处绘制。在QML里,只能在onPaint的处理函数处理。HTML5里使用setInterval定时器触发画线和清空背景。由于QML的不同处理方式,不能仅调用这些函数,因为需要通过onPaint函数来处理。而颜色展现也需要调整。一起逐个修改吧。
    一切从画布元素开始。简单起见,直接使用Canvas 元素做为QML文件的根元素。

    import QtQuick
    
    Canvas {
       id: canvas
       width: 800; height: 450
    
       ...
    }

    为了解耦setInterval对函数的直接调用,我们将对setInterval的调用替换为两个会请求重绘的定时器。Timer定时器定时触发并执行我们设定的代码。因为我们无法区分哪个绘制函数将被触发,我们为每个操作定义一个请求操作的布尔型的标记,然后触发一个重绘请求。
    这是画线操作的代码。清空背景的操作是类似的。

    ...
    property bool requestLine: false
    
    Timer {
        id: lineTimer
        interval: 40
        repeat: true
        triggeredOnStart: true
        onTriggered: {
            canvas.requestLine = true
            canvas.requestPaint()
        }
    }
    
    Component.onCompleted: {
        lineTimer.start()
    }
    ...

    现在我们就知道了哪此操作(画线、清空或二者都)需要在onPaint函数中进行。当我们为每个绘制请求进入onPaint时,我们需要将变量的初始化放在画布元素中。

    Canvas {
        ...
        property real hue: 0
        property real lastX: width * Math.random();
        property real lastY: height * Math.random();
        ...
    }

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

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

    line函数被抽象出来,将画布作为其参数。

    function line(context) {
        context.save();
        context.translate(canvas.width/2, canvas.height/2);
        context.scale(0.9, 0.9);
        context.translate(-canvas.width/2, -canvas.height/2);
        context.beginPath();
        context.lineWidth = 5 + Math.random() * 10;
        context.moveTo(lastX, lastY);
        lastX = canvas.width * Math.random();
        lastY = canvas.height * Math.random();
        context.bezierCurveTo(canvas.width * Math.random(),
            canvas.height * Math.random(),
            canvas.width * Math.random(),
            canvas.height * Math.random(),
            lastX, lastY);
    
        hue += Math.random()*0.1
        if(hue > 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();
    }

    最大的改动是QML函数Qt.rgba()Qt.hsla()的使用,其要求采用QML中使用的0.0...0.1范围内的值。
    同样应用到清屏函数blank

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

    最后的效果类似下图: