QML Book 第七章 画布(Canvas)元素 2

7.4 图片绘制

QML 画布支持来自多个来源的图像绘制。要在画布中使用图像,需要首先加载图像。我们将使用 Component.onCompleted 处理程序在我们的示例中加载映像。

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


        // draw an image
        ctx.drawImage('assets/ball.png', 10, 10)

        // store current context setup
        ctx.save()
        ctx.strokeStyle = '#ff2a68'
        // create a triangle as clip region
        ctx.beginPath()
        ctx.moveTo(110,10)
        ctx.lineTo(155,10)
        ctx.lineTo(135,55)
        ctx.closePath()
        // translate coordinate system
        ctx.clip()  // create clip from the path
        // draw image with clip applied
        ctx.drawImage('assets/ball.png', 100, 10)
        // draw stroke around path
        ctx.stroke()
        // restore previous context
        ctx.restore()

    }

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

左侧显示我们的球图像画在 10x10 的左上角位置。右侧的图像显示了应用了剪辑路径效果的球。 图像和任何其他路径可以使用路径剪辑。通过定义路径并调用 clip() 函数来应用剪辑。所有 clip() 函数以下绘图操作现在将被此路径剪辑。通过恢复先前的状态或将剪辑区域设置为整个画布,则会禁用剪辑效果。

QML Book 第七章 画布(Canvas)元素 2_第1张图片
canvas_image

7.5 转换效果

画布允许我们以多种方式转换坐标系。这与 QML 项目提供的转换非常相似。 我们可以缩放(scale),旋转(rotate),移动(translate)。与 QML 不同的是,转换的原点始终是画布原点。例如,如果要围绕它的中心扩展路径,我们需要将画布原点映射到路径的中心。我们也可以使用 transform 方法应用更复杂的形变。

// transform.qml

import QtQuick 2.5

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

        ctx.beginPath()
        ctx.rect(-20, -20, 40, 40)
        ctx.translate(120,60)
        ctx.stroke()

        // draw path now rotated
        ctx.strokeStyle = "green"
        ctx.rotate(Math.PI/4)
        ctx.stroke()
    }
}
QML Book 第七章 画布(Canvas)元素 2_第2张图片
transform.png

除了移动画布还可以使用 scale(x,y) 围绕 x 和 y 轴的刻度进行缩放,使用 rotate(angle) 旋转,其中角度以半径(360度= 2 * Math.PI)给出,并使用 setTransform(m11,m12,m21,m22,dx,dy) 进行矩阵变换。

** 注意: **
要重置任何转换,您可以调用 resetTransform() 函数将转换矩阵设置回单位矩阵:

ctx.resetTransform()

7.6 组合模式

组合允许我们绘制一个形状并将其与现有图像进行混合。画布支持使用 globalCompositeOperation(mode) 操作的多个组合模式:

  • source-over
  • source-in
  • source-out
  • source-atop
    onPaint: {
        var ctx = getContext("2d")
        ctx.globalCompositeOperation = "xor"
        ctx.fillStyle = "#33a9ff"

        for(var i=0; i<40; i++) {
            ctx.beginPath()
            ctx.arc(Math.random()*400, Math.random()*200, 20, 0, 2*Math.PI)
            ctx.closePath()
            ctx.fill()
        }
    }

这个小例子遍历了复合模式的列表,并生成一个带圆的矩形。

    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

7.7 像素缓冲

使用画布时,我们可以从画布中检索像素数据来读取或操作画布的像素。要读取图像数据,请使用 createImageData(sw,sh) 或getImageData(sx,sy,sw,sh)。两个函数都返回一个带宽度,高度和数据变量的 ImageData 对象。数据变量包含以 RGBA 格式检索的像素数据的一维数组,其中每个值在 0 到 255 的范围内变化。要在画布上设置像素,可以使用 putImageData(imagedata ,, dx,dy) 功能。

检索画布内容的另一种方法是将数据存储到图像中。 这可以通过 Canvas 函数 save(path) 或 toDataURL(mimeType) 来实现,后者函数返回一个可以被 Image 元素加载的图像 url。

import QtQuick 2.5

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()
    }
}

在我们的小例子中,我们每秒在画布左侧画一个小圆。当鼠标点击时,画布内容会被存储并且作为图像 url 赋值给在我们示例的右侧的图像并显示。

7.8 画布绘图

在这个例子中,我们想使用 Canvas 元素创建一个小的绘图应用程序。

QML Book 第七章 画布(Canvas)元素 2_第3张图片
canvaspaint

为此,我们使用行定位器在场景顶部安排四个彩色方块。彩色方块是一个填充颜色的简单矩形鼠标区域,用于检测鼠标点击事件。

    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
                }
            }
        }
    }

颜色存储在一个颜色数组作为模型数据和同时也将作为绘图的颜色。当用户点击一个正方形时,方形的颜色被分配给名为colorTools 的对象的 paintColor 属性。

为了能够跟踪画布上的鼠标事件,我们有一个 MouseArea 覆盖了 canvas 元素,并且连接了鼠标按下和位置改变的信号处理程序。

    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()
            }
        }
    }

鼠标按键将最初的鼠标位置存储在 lastX 和 lastY 属性中。鼠标位置上的每一个变化触发画布上的绘制请求,这将导致调用 onPaint 处理程序。

为了最终绘制用户笔画,在 onPaint 处理程序中,我们开始一个新的路径并移动到最后一个位置。然后,我们从鼠标区域收集新的位置,并将所选颜色的线条绘制到新位置。鼠标位置存储为新的最后一个位置。

7.9 从 HTML5 移植画布

将 HTML5 画布图形移植到 QML 画布上是相当容易的。从成千上万的例子中,我们选择了一个并进行了尝试。

** 螺旋图 **

我们使用 Mozilla 项目的 螺旋图 示例作为我们的基础。原始的HTML5被作为画布教程的一部分发布。

在这里我们需要改变几行:

  • Qt Quick 要求声明变量,所以我们需要添加一些 var 声明
for (var i=0;i<3;i++) {
   ...
}
  • 改编 draw 方法来接收 Context2D 对象
function draw(ctx) {
    ...
}
  • 我们需要根据不同的尺寸来调整每个螺旋的转换
ctx.translate(20+j*50,20+i*50);

最后我们完成了我们的 onPaint 处理程序。在里面我们获得一个上下文并调用我们的绘图函数

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

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

QML Book 第七章 画布(Canvas)元素 2_第4张图片
spirograph

是不是很容易?

** 荧光线 **

这是 W3C 组织的另一个更复杂的接口。原来漂亮的发光线条有一些很不错的方面,这使得移植更具挑战性。




    Pretty Glowing Lines







在 HTML5 中,Context2D 对象可以在画布上随时绘制。在 QML 中,它只能指向 onPaint 处理程序。使用 setInterval 的定时器会在 HTML5 中触发该行的行程或将屏幕空白。由于在 QML 中的不同处理,不可能只调用这些函数,因为我们需要通过 onPaint 处理程序。还需要调整颜色声明。我们来看一下变化吧。

一切都从 canvas 元素开始。为了简单起见,我们只使用 Canvas 元素作为 QML 文件的根元素。

import QtQuick 2.5

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

   ...
}

要通过 setInterval 解开对函数的直接调用,我们将使用两个定时器替换 setInterval 调用,这些计时器将请求重新绘制。在短时间间隔后触发定时器,并允许我们执行一些代码。因为我们不能告诉 paint 函数我们想要触发哪个操作,我们为每个操作定义一个 bool 标志来请求一个操作并触发一个重绘请求。

这是线操作的代码。空白操作是相似的。

...
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 元素中。

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
    }
}

为画布提取线函数作为参数。

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 ... 1.0 范围。

同样适用于空白功能。

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

最后的结果将会与此类似。

QML Book 第七章 画布(Canvas)元素 2_第5张图片
glowlines

一些比较有用的参考连接:

  • W3C HTML Canvas 2D Context Specification
  • Mozilla Canvas Documentation
  • HTML5 Canvas Tutorial

你可能感兴趣的:(QML Book 第七章 画布(Canvas)元素 2)