Web Workers работа с Canvas

в 12:32, , рубрики: canvas, html, html5, javascript, web workers, метки: , , ,

Основная идея

Много кто в процессе изучения 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

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js