Основная идея
Много кто в процессе изучения Web Workers сталкивался с проблемой работы с canvas-ом. В основном это происходит из-за того, что в web worker нельзя передать элементы DOM. Решением может быть использование getImageData(). Внимание! Статья не рассчитана для новичков. Для изучения данного материала рекомендую ознакомиться с основами canvas и webworker.
Я попытаюсь продемонстрировать вам работу с getImageData() на примере создания асинхронно работающего сложного фильтра размывания изображений — blur. Для этого нам понадобится 2 файла: index.html и filter_worker.js
Разметка страницы
В index.html необходимо расположить один элемент canvas, форму для указания радиуса размывания и span в котором будем записывать текущий процесс работы фильтра в процентах.
<!DOCTYPE html>
<html>
<head>
<title>Web Worker</title>
<style>
#my_canvas
{
border: #00ff00 solid 1px;
}
</style>
</head>
<body>
<form>
<input type="text" id="radius" value="5" />
<input type="button" onclick="draw('my_canvas')" value="Draw" />
</form><br>
<canvas id="my_canvas" width="200" height="200"></canvas><br>
<span id="load_info"></span>
</body>
</html>
JavaScript
Добавим javascript функцию, в которой настроим canvas и вызовем web worker.
// Загрузка изображения
var img = new Image();
img.src = "Malevich.jpg";
function draw(canvas_name)
{
// Извлечение контекста
var canvas = document.getElementById(canvas_name);
var ctx = canvas.getContext("2d");
// Рисование изображения
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(img,0,0,img.width,img.height,0,0,img.width,img.height);
// Поддерживает ли браузер Web Workers?
if(!!window.Worker)
{
var worker = new Worker('filter_worker.js'); // Создаём новый worker
worker.postMessage({
// Передача ImageData в worker
imagedata: ctx.getImageData(0, 0, canvas.width, canvas.height),
width: canvas.width,
height: canvas.height,
radius: document.getElementById("radius").value
});
worker.onmessage = function(event)
{
if (event.data.status == 'complite') // Если фильтр выполнил работу
{
// Переместить принятую Image Data в контекст canvas
ctx.putImageData(event.data.imagedata,0,0);
}
else
{
// Если фильтр не завершил работу, то показываем текущий прогресс
document.getElementById("load_info").innerHTML = event.data.progress + "%";
}
}
}
else
alert('Ваш браузер не поддерживает Web Workers!');
}
WebWorker
Алгоритм работы фильтра blur работает по такому принципу: найти среднее арифметическое суммы цветов всех пикселей, попадающих в определённый радиус от текущего пикселя и записывать в него исходящий цвет.
onmessage = function (event)
{
var imagedata = event.data.imagedata;
var width = event.data.width;
var height = event.data.height;
var radius = event.data.radius;
var sum_r, sum_g, sum_b, sum_a;
// Количество пикселей, попадающих в радиус размывания
var scale = (radius * 2 + 1) * (radius * 2 + 1)
var num_pixels = width * height;
function getPixel(x, y)
{
if (x < 0) { x = 0; }
if (y < 0) { y = 0; }
if (x >= width) { x = width - 1; }
if (y >= height) { y = height - 1; }
var index = (y * width + x) * 4;
return [
imagedata.data[index + 0],
imagedata.data[index + 1],
imagedata.data[index + 2],
imagedata.data[index + 3],
];
}
function setPixel(x, y, r, g, b, a)
{
var index = (y * width + x) * 4;
imagedata.data[index + 0] = r;
imagedata.data[index + 1] = g;
imagedata.data[index + 2] = b;
imagedata.data[index + 3] = a;
}
var lastprogress = 0;
for (y = 0; y < height; y++)
{
for (x = 0; x < width; x++)
{
var progress = Math.round((((y * width) + height) / num_pixels) * 100);
if (progress > lastprogress)
{
lastprogress = progress;
postMessage({status: 'progress', progress: progress});
}
sum_r = 0;
sum_g = 0;
sum_b = 0;
sum_a = 0;
for (var dy = -radius; dy <= radius; dy++)
{
for (var dx = -radius; dx <= radius; dx++)
{
var pixeldata = getPixel(x + dx, y + dy);
sum_r += pixeldata[0];
sum_g += pixeldata[1];
sum_b += pixeldata[2];
sum_a += pixeldata[3];
}
}
// Получение исходящего цвета (деление суммы цветов на количество
// пикселей в радиусе размывания
setPixel(
x, y,
Math.round(sum_r / scale),
Math.round(sum_g / scale),
Math.round(sum_b / scale),
Math.round(sum_a / scale)
);
}
}
postMessage({status: 'complite', imagedata: imagedata});
}
Вывод:
На основе этого примера вы сможете создавать много собственных фильтров, работающих асинхронно, которые не приводят к зависанию веб-страницы.
Автор: Dima_Amigo