Porting from HTML5 Canvas
从HTML5画布移植
Porting from an HTML5 canvas to a QML canvas is fairly easy. In this chapter we will look at the example below and do the conversion.
从HTML5画布移植到QML画布相当容易。在本章中,我们将查看下面的示例并进行转换。
- https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Transformations(opens new window)
- http://en.wikipedia.org/wiki/Spirograph(opens new window)
Spirograph
呼吸运动记录器
We use a spirograph example from the Mozilla project as our foundation. The original HTML5 was posted as part of the canvas tutorial.
我们使用Mozilla项目的一个例子来作为我们的基础。最初的HTML5是作为画布教程的一部分发布的。
There were a few lines we needed to change:
有几行我们需要更改:
-
Qt Quick requires you to declare a variable, so we needed to add some var declarations
-
Qt Quick要求您声明一个变量,因此我们需要添加一些var声明
for (var i=0;i<3;i++) { ... }
-
We adapted the draw method to receive the Context2D object
-
我们调整了draw方法以接收Context2D对象
function draw(ctx) { ... }
-
We needed to adapt the translation for each spiro due to different sizes
-
由于尺寸不同,我们需要调整每个spiro的实现
ctx.translate(20+j*50,20+i*50);
Finally, we completed our onPaint
handler. Inside we acquire a context and call our draw function.
最后,我们完成了onPaint处理器。在内部,我们获取一个上下文并调用draw函数。
onPaint: {
var ctx = getContext("2d");
draw(ctx);
}
The result is a ported spiro graph graphics running using the QML canvas.
结果是一个使用QML画布运行的移植spiro graph图形。
As you can see, with no changes to the actual logic, and relatively few changes to the code itself, a port from HTML5 to QML is possible.
正如您所看到的,没有对实际逻辑的更改,代码本身的更改相对较少,从HTML5到QML的移植是可能的。
Glowing Lines
荧光线条
Here is another more complicated port from the W3C organization. The original pretty glowing lines has some pretty nice aspects, which makes the porting more challenging.
这里是来自W3C组织的另一个更复杂的移植。最初的漂亮发光线条有一些漂亮的方面,这使得移植更具挑战性。
<!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>
In HTML5 the Context2D object can paint at any time on the canvas. In QML it can only point inside the onPaint
handler. The timer in usage with setInterval
triggers in HTML5 the stroke of the line or to blank the screen. Due to the different handling in QML, it’s not possible to just call these functions, because we need to go through the onPaint
handler. Also, the color presentations need to be adapted. Let’s go through the changes on by one.
在HTML5中,Context2D对象可以随时在画布上绘制。在QML中,它只能在onPaint处理器内部执行。在HTML5中,与setInterval一起使用的计时器会触发线条的绘制或空白屏幕。由于QML中的处理方式不同,不可能只调用这些函数,因为我们需要通过onPaint处理器实现。此外,还需要调整颜色表示。让我们逐一看一下这些变化。
Everything starts with the canvas element. For simplicity, we just use the Canvas
element as the root element of our QML file.
一切都从canvas元素类型开始。为简单起见,我们只使用Canvas元素类型作为QML文件的根元素类型。
import QtQuick
Canvas {
id: canvas
width: 800; height: 450
...
}
To untangle the direct call of the functions through the setInterval
, we replace the setInterval
calls with two timers which will request a repaint. A Timer
is triggered after a short interval and allows us to execute some code. As we can’t tell the paint function which operation we would like to trigger we define for each operation a bool flag request an operation and trigger then a repaint request.
为了通过setInterval解开函数的直接调用,我们将setInterval调用替换为两个请求重新绘制的计时器。一个定时器在短时间间隔后被触发,允许我们执行一些代码。由于我们无法告诉paint函数要触发哪个操作,因此我们为每个操作定义了一个bool标志请求一个操作,然后触发一个重绘请求。
Here is the code for the line operation. The blank operation is similar.
这是行操作的代码。空白操作类似。
...
property bool requestLine: false
Timer {
id: lineTimer
interval: 40
repeat: true
triggeredOnStart: true
onTriggered: {
canvas.requestLine = true
canvas.requestPaint()
}
}
Component.onCompleted: {
lineTimer.start()
}
...
Now we have an indication which (line or blank or even both) operation we need to perform during the onPaint
operation. As we enter the onPaint
handler for each paint request we need to extract the initialization of the variable into the canvas element.
现在我们有了一个指示,在onPaint操作期间需要执行哪个操作(线条或空白,甚至两者都有)。在为每个绘制请求输入onPaint处理器时,我们需要将变量的初始化提取到canvas元素类型中。
Canvas {
...
property real hue: 0
property real lastX: width * Math.random();
property real lastY: height * Math.random();
...
}
Now our paint function should look like this:
现在,我们的绘制功能应该如下所示:
onPaint: {
var context = getContext('2d')
if(requestLine) {
line(context)
requestLine = false
}
if(requestBlank) {
blank(context)
requestBlank = false
}
}
The line function was extracted for a canvas as an argument.
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();
}
The biggest change was the use of the QML Qt.rgba()
and Qt.hsla()
functions, which required to adopt the values to the used 0.0 … 1.0 range in QML.
最大的变化是使用Qt.rgba()
和Qt.hsla()
函数,该函数需要调整到QML中使用的0.0…1.0范围的值。
Same applies to the blank function.
这同样的调整,适用于空白函数。
function blank(context) {
context.fillStyle = Qt.rgba(0,0,0,0.1)
context.fillRect(0, 0, canvas.width, canvas.height);
}
The final result will look similar to this.
最终结果与此类似。