Портирование из элемента canvas HTML5 в QML
Перенос с элемента canvas HTML5 на элемент Canvas QML довольно прост. В этой главе мы рассмотрим пару примеров и выполним преобразование.
Спирограф
В качестве основы мы используем пример спирографа из проекта Mozilla. Исходный HTML5 был опубликован как часть руководства по canvas.
Нам нужно было изменить несколько строк:
- 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.
Как видите, перенос с HTML5 на QML возможен без изменений фактической логики и с относительно небольшими изменениями самого кода.
Светящиеся линии
Вот еще один, более сложный пример для портирования от организации 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');
// начальная позиция
var lastX = context.canvas.width * Math.random();
var lastY = context.canvas.height * Math.random();
var hue = 0;
// функция для рисования
// случайная кривая Безье со случайным цветом и эффектом свечения
function line() {
context.save();
// масштабирование с коэффициентом 0,9 вокруг центра холста
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;
// наша начальная позиция
context.moveTo(lastX, lastY);
// наша новая конечная позиция
lastX = context.canvas.width * Math.random();
lastY = context.canvas.height * Math.random();
// случайная кривая Безье, которая заканчивается на 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);
// эффект свечения
hue = hue + 10 * Math.random();
context.strokeStyle = 'hsl(' + hue + ', 50%, 50%)';
context.shadowColor = 'white';
context.shadowBlur = 10;
// обводим кривую
context.stroke();
context.restore();
}
// вызываем функцию line каждые 50 мс
setInterval(line, 50);
function blank() {
// при каждом вызове делает фон на 10% темнее
context.fillStyle = 'rgba(0,0,0,0.1)';
context.fillRect(0, 0, context.canvas.width, context.canvas.height);
}
// вызываем функцию blank каждые 50 мс
setInterval(blank, 40);
</script>
</body>
</html>
В HTML5 объект Context2D может рисовать на холсте в любое время. В QML он может рисовать только внутри обработчика onPaint. Таймер, используемый с setInterval, запускает в HTML5 отрисовку линии или очистку экрана. Из-за отличающейся обработки в QML просто вызвать эти функции невозможно потому, что нам нужно пройти через обработчик onPaint. Кроме того, должны быть адаптированы цветовые представления. Давайте пошагово пройдемся по изменениям.
Всё начинается с элемента холста. Для простоты мы просто используем элемент Canvas в качестве корневого элемента нашего файла QML.
import QtQuick
Canvas {
id: canvas
width: 800; height: 450
...
}
Чтобы распутать прямой вызов функций через setInterval, мы заменяем вызовы 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
}
}
Функция 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 … 1,0.
То же самое относится и к функции blank.
function blank(context) {
context.fillStyle = Qt.rgba(0,0,0,0.1)
context.fillRect(0, 0, canvas.width, canvas.height);
}
Окончательный результат будет выглядеть примерно так.
