Browse Source

添加第七章Canvas Element

cwc 10 years ago
parent
commit
11420b00cc

+ 10 - 0
SUMMARY.md

@@ -39,4 +39,14 @@
    * [代理(Delegate)](model-view-delegate/delegate.md)
    * [高级用法(Advanced Techniques)](model-view-delegate/advanced_techniques.md)
    * [总结(Summary)](model-view-delegate/summary.md)
+* [Canvas Element](canvas_element/README.md)
+   * [便捷的接口(Convenient API)](canvas_element/convenient_api.md)
+   * [渐变(Gradients)](canvas_element/gradients.md)
+   * [阴影(Shadows)](canvas_element/shadows.md)
+   * [图片(Images)](canvas_element/images.md)
+   * [转换(Transformation)](canvas_element/transformation.md)
+   * [组合模式(Composition Mode)](canvas_element/composition_mode.md)
+   * [像素缓冲(Pixels Buffer)](canvas_element/pixels_buffer.md)
+   * [画布绘制(Canvas Paint)](canvas_element/canvas_paint.md)
+   * [HTML5画布移植(Porting from HTML5 Canvas)](canvas_element/html5porting_from_html5_canvas.md)
 

+ 108 - 0
canvas_element/README.md

@@ -0,0 +1,108 @@
+# Canvas Element
+
+**注意**
+
+**最后一次构建:2014年1月20日下午18:00。**
+
+**这章的源代码能够在[assetts folder](http://qmlbook.org/assets)找到。**
+
+![](http://qmlbook.org/_images/glowlines.png)
+
+在早些时候的Qt4中加入QML时,一些开发者讨论如何在QtQuick中绘制一个圆形。类似圆形的问题,一些开发者也对于其它的形状的支持进行了讨论。在QtQuick中没有圆形,只有矩形。在Qt4中,如果你需要一个除了矩形外的形状,你需要使用图片或者使用你自己写的C++圆形元素。
+
+Qt5中引进了画布元素(canvas element),允许脚本绘制。画布元素(canvas element)提供了一个依赖于分辨率的位图画布,你可以使用JavaScript脚本来绘制图形,制作游戏或者其它的动态图像。画布元素(canvas element)是基于HTML5的画布元素来完成的。
+
+画布元素(canvas element)的基本思想是使用一个2D对象来渲染路径。这个2D对象包括了必要的绘图函数,画布元素(canvas element)充当绘制画布。2D对象支持画笔,填充,渐变,文本和绘制路径创建命令。
+
+让我们看看一个简单的路径绘制的例子:
+
+```
+import QtQuick 2.0
+
+Canvas {
+    id: root
+    // canvas size
+    width: 200; height: 200
+    // handler to override for drawing
+    onPaint: {
+        // get context to draw with
+        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()
+        // top-left start point
+        ctx.moveTo(50,50)
+        // upper line
+        ctx.lineTo(150,50)
+        // right line
+        ctx.lineTo(150,150)
+        // bottom line
+        ctx.lineTo(50,150)
+        // left line through path closing
+        ctx.closePath()
+        // fill using fill style
+        ctx.fill()
+        // stroke using line width and stroke style
+        ctx.stroke()
+    }
+}
+```
+
+这个例子产生了一个在坐标(50,50),高宽为100的填充矩形框,并且使用了画笔来修饰边界。
+
+![](http://qmlbook.org/_images/rectangle.png)
+
+画笔的宽度被设置为4个像素,并且定义strokeStyle(画笔样式)为蓝色。最后的形状由设置填充样式(fillStyle)为steelblue颜色,然后填充完成的。只有调用stroke或者fill函数,创建的路径才会绘制,它们与其它的函数使用是相互独立的。调用stroke或者fill将会绘制当前的路径,创建的路径是不可重用的,只有绘制状态能够被存储和恢复。
+
+在QML中,画布元素(canvas element)充当了绘制的容器。2D绘制对象提供了实际绘制的方法。绘制需要在onPaint事件中完成。
+
+```
+Canvas {
+    width: 200; height: 200
+    onPaint: {
+        var ctx = getContext("2d")
+        // setup your path
+        // fill or/and stroke
+    }
+}
+```
+
+画布自身提供了典型的二维笛卡尔坐标系统,左上角是(0,0)坐标。Y轴坐标轴向下,X轴坐标轴向右。
+
+典型绘制命令调用如下:
+
+1. 装载画笔或者填充模式
+
+2. 创建绘制路径
+
+3. 使用画笔或者填充绘制路径
+
+```
+    onPaint: {
+        var ctx = getContext("2d")
+
+        // setup the stroke
+        ctx.strokeStyle = "red"
+
+        // create a path
+        ctx.beginPath()
+        ctx.moveTo(50,50)
+        ctx.lineTo(150,50)
+
+        // stroke path
+        ctx.stroke()
+    }
+```
+
+
+这将产生一个从P1(50,50)到P2(150,50)水平线。
+
+![](http://qmlbook.org/_images/line.png)
+
+**注意**
+
+**通常在你重置了路径后你将会设置一个开始点,所以,在beginPath()这个操作后,你需要使用moveTo来设置开始点。**

+ 79 - 0
canvas_element/canvas_paint.md

@@ -0,0 +1,79 @@
+# 画布绘制(Canvas Paint)
+
+在这个例子中我们将使用画布(Canvas)创建一个简单的绘制程序。
+
+![](http://qmlbook.org/_images/canvaspaint.png)
+
+在我们场景的顶部我们使用行定位器排列四个方形的颜色块。一个颜色块是一个简单的矩形,使用鼠标区域来检测点击。
+
+```
+    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 {
+        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()
+            }
+        }
+    }
+```
+
+鼠标点击存储在laxstX与lastY属性中。每次鼠标位置的改变会触发画布的重绘,这将会调用onPaint操作。
+
+最后绘制用户的笔划,在onPaint操作中,我们绘制从最近改变的点上开始绘制一条新的路径,然后我们从鼠标区域采集新的点,使用选择的颜色绘制线段到新的点上。鼠标位置被存储为新改变的位置。

+ 62 - 0
canvas_element/composition_mode.md

@@ -0,0 +1,62 @@
+# 组合模式(Composition Mode)
+
+组合允许你绘制一个形状然后与已有的像素点集合混合。画布提供了多种组合模式,使用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<operation.length; i++) {
+            var dx = Math.floor(i%6)*100
+            var dy = Math.floor(i/6)*100
+            ctx.save()
+            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()
+        }
+    }
+```

+ 34 - 0
canvas_element/convenient_api.md

@@ -0,0 +1,34 @@
+# 便捷的接口(Convenient API)
+
+在绘制矩形时,我们提供了一个便捷的接口,而不需要调用stroke或者fill来完成。
+
+```
+// convenient.qml
+
+import QtQuick 2.0
+
+Canvas {
+    id: root
+    width: 120; height: 120
+    onPaint: {
+        var ctx = getContext("2d")
+        ctx.fillStyle = 'green'
+        ctx.strokeStyle = "blue"
+        ctx.lineWidth = 4
+
+        // draw a filles rectangle
+        ctx.fillRect(20, 20, 80, 80)
+        // cut our an inner rectangle
+        ctx.clearRect(30,30, 60, 60)
+        // stroke a border from top-left to
+        // inner center of the larger rectangle
+        ctx.strokeRect(20,20, 40, 40)
+    }
+}
+```
+
+![](http://qmlbook.org/_images/convenient.png)
+
+**注意**
+
+**画笔的绘制区域由中间向两边延展。一个宽度为4像素的画笔将会在绘制路径的里面绘制2个像素,外面绘制2个像素。**

+ 23 - 0
canvas_element/gradients.md

@@ -0,0 +1,23 @@
+# 渐变(Gradients)
+
+画布中可以使用颜色填充也可以使用渐变或者图像来填充。
+
+```
+    onPaint: {
+        var ctx = getContext("2d")
+
+        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)。渐变色的定义比我们想要绘制的矩形更大,所以矩形在它定义的范围内对渐变进行了裁剪。
+
+![](http://qmlbook.org/_images/gradient.png)
+
+**注意**
+
+**渐变色是在画布坐标下定义的,而不是在绘制路径相对坐标下定义的。画布中没有相对坐标的概念。**

+ 246 - 0
canvas_element/html5porting_from_html5_canvas.md

@@ -0,0 +1,246 @@
+# HTML5画布移植(Porting from HTML5 Canvas)
+
+* [https://developer.mozilla.org/en/Canvas_tutorial/Transformations](https://developer.mozilla.org/en/Canvas_tutorial/Transformations)
+
+* [http://en.wikipedia.org/wiki/Spirograph](http://en.wikipedia.org/wiki/Spirograph)
+
+移植一个HTML5画布图像到QML画布非常简单。在成百上千的例子中,我们选择了一个来移植。
+
+**螺旋图形(Spiro Graph)**
+
+我们使用一个来自Mozila项目的螺旋图形例子来作为我们的基础示例。原始的HTML5代码被作为画布教程发布。
+
+下面是我们需要修改的代码:
+
+* Qt Quick要求定义变量使用,所以我们需要添加var的定义:
+```
+for (var i=0;i<3;i++) {
+    ...
+}
+```
+
+* 修改绘制方法接收Context2D对象:
+```
+function draw(ctx) {
+    ...
+}
+```
+
+* 由于不同的大小,我们需要对每个螺旋适配转换:
+```
+ctx.translate(20+j*50,20+i*50);
+```
+
+最后我们实现onPaint操作。在onPaint中我们请求一个context,并且调用我们的绘制方法。
+
+```
+    onPaint: {
+        var ctx = getContext("2d");
+        draw(ctx);
+    }
+```
+
+下面这个结果就是我们使用QML画布移植的螺旋图形。
+
+![](http://qmlbook.org/_images/spirograph.png)
+
+**发光线(Glowing Lines)**
+
+下面有一个更加复杂的移植来自W3C组织。[原始的发光线](http://www.w3.org/TR/2dcontext/#examples)有些很不错的地方,这使得移植更加具有挑战性。
+
+![](http://qmlbook.org/_images/html_glowlines.png)
+
+```
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+    <title>Pretty Glowing Lines</title>
+</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中,context2D对象可以随意在画布上绘制。在QML中,只能在onPaint操作中绘制。在HTML5中,通常调用setInterval使用计时器触发线段的绘制或者清屏。由于QML中不同的操作方法,仅仅只是调用这些函数不能实现我们想要的结果,因为我们需要通过onPaint操作来实现。我们也需要修改颜色的格式。让我们看看需要改变哪些东西。
+
+修改从画布元素开始。为了简单,我们使用画布元素(Canvas)作为我们QML文件的根元素。
+
+```
+import QtQuick 2.0
+
+Canvas {
+   id: canvas
+   width: 800; height: 450
+
+   ...
+}
+```
+
+代替直接调用的setInterval函数,我们使用两个计时器来请求重新绘制。一个计时器触发间隔较短,允许我们可以执行一些代码。我们无法告诉绘制函数哪个操作是我想触发的,我们为每个操作定义一个布尔标识,当重新绘制请求时,我们请求一个操作并且触发它。
+
+下面是线段绘制的代码,清屏操作类似。
+
+```
+...
+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
+    }
+}
+```
+
+线段绘制函数提取画布作为一个参数。
+
+```
+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);
+}
+```
+
+下面是最终结果(目前没有阴影)类似下面这样。
+
+![](http://qmlbook.org/_images/glowlines.png)
+
+查看下面的链接获得更多的信息:
+
+* [W3C HTML Canvas 2D Context Specification](http://www.w3.org/TR/2dcontext/)
+
+* [Mozilla Canvas Documentation](https://developer.mozilla.org/en/HTML/Canvas)
+
+* [HTML5 Canvas Tutorial](http://www.html5canvastutorials.com/)
+

+ 41 - 0
canvas_element/images.md

@@ -0,0 +1,41 @@
+# 图片(Images)
+
+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 = 'red'
+        // create a triangle as clip region
+        ctx.beginPath()
+        ctx.moveTo(10,10)
+        ctx.lineTo(55,10)
+        ctx.lineTo(35,55)
+        ctx.closePath()
+        // translate coordinate system
+        ctx.translate(100,0)
+        ctx.clip()  // create clip from triangle path
+        // draw image with clip applied
+        ctx.drawImage('assets/ball.png', 10, 10)
+        // draw stroke around path
+        ctx.stroke()
+        // restore previous setup
+        ctx.restore()
+
+    }
+
+    Component.onCompleted: {
+        loadImage("assets/ball.png")
+    }
+```
+
+在左边,足球图片使用10×10的大小绘制在左上方的位置。在右边我们对足球图片进行了裁剪。图片或者轮廓路径都可以使用一个路径来裁剪。裁剪需要定义一个裁剪路径,然后调用clip()函数来实现裁剪。在clip()之前所有的绘制操作都会用来进行裁剪。如果还原了之前的状态或者定义裁剪区域为整个画布时,裁剪是无效的。
+
+![](http://qmlbook.org/_images/canvas_image.png)

+ 61 - 0
canvas_element/pixels_buffer.md

@@ -0,0 +1,61 @@
+# 像素缓冲(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元素来读取。
+
+```
+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()
+    }
+}
+```
+
+在我们这个例子中,我们每秒在左边的画布中绘制一个的圆形。当使用鼠标点击画布内容时,会将内容存储为一个图片链接。在右边将会展示这个存储的图片。
+
+**注意**
+
+**在Qt5的Alpha版本中,检索图像数据似乎不能工作。**

+ 35 - 0
canvas_element/shadows.md

@@ -0,0 +1,35 @@
+# 阴影(Shadows)
+
+**注意**
+
+**在Qt5的alpha版本中,我们使用阴影遇到了一些问题。**
+
+2D对象的路径可以使用阴影增强显示效果。阴影是一个区域的轮廓线使用偏移量,颜色和模糊来实现的。所以你需要指定一个阴影颜色(shadowColor),阴影X轴偏移值(shadowOffsetX),阴影Y轴偏移值(shadowOffsetY)和阴影模糊(shadowBlur)。这些参数的定义都使用2D context来定义。2D context是唯一的绘制操作接口。
+
+阴影也可以用来创建发光的效果。在下面的例子中我们使用白色的光创建了一个“Earth”的文本。在一个黑色的背景上可以有更加好的显示效果。
+
+首先我们绘制黑色背景:
+
+```
+        // setup a dark background
+        ctx.strokeStyle = "#333"
+        ctx.fillRect(0,0,canvas.width,canvas.height);
+```
+
+然后定义我们的阴影配置:
+
+```
+        ctx.shadowColor = "blue";
+        ctx.shadowOffsetX = 2;
+        ctx.shadowOffsetY = 2;
+        // next line crashes
+        // ctx.shadowBlur = 10;
+```
+
+最后我们使用加粗的,80像素宽度的Ubuntu字体来绘制“Earth”文本:
+
+```
+        ctx.font = 'Bold 80px Ubuntu';
+        ctx.fillStyle = "#33a9ff";
+        ctx.fillText("Earth",30,180);
+```

+ 41 - 0
canvas_element/transformation.md

@@ -0,0 +1,41 @@
+# 转换(Transformation)
+
+画布有多种方式来转换坐标系。这些操作非常类似于QML元素的转换。你可以通过缩放(scale),旋转(rotate),translate(移动)来转换坐标系。与QML元素的转换不同的是,转换原点通常就是画布原点。例如,从中心点放大一个封闭的路径,你需要先将画布原点移动到整个封闭的路径的中心点上。使用这些转换的方法你可以创建一些更加复杂的转换。
+
+```
+// transform.qml
+
+import QtQuick 2.0
+
+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()
+    }
+}
+```
+
+![](http://qmlbook.org/_images/transform.png)
+
+除了移动画布外,也可以使用scale(x,y)来缩放x,y坐标轴。旋转使用rotate(angle),angle是角度(360度=2*Math.PI)。使用setTransform(m11,m12,m21,m22,dx,dy)来完成矩阵转换。
+
+**警告**
+
+**QML画布中的转换与HTML5画布中的机制有些不同。不确定这是不是一个Bug。**
+
+**注意**
+
+**重置矩阵你可以调用resetTransform()函数来完成,这个函数会将转换矩阵还原为单位矩阵。**