Pull to refresh

Web Workers работа с Canvas

Reading time3 min
Views14K

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


В процессе изучения способов многопоточной обработки изображений в браузере многие сталкиваются с проблемой отображения результата Web Workers в canvas. В основном это происходит из-за того, что в web worker нельзя передать элементы DOM. Решением может быть использование getImageData(). Данная статья описывает конкретный пример использования данной технологии на примере обработки фильтра blur в отдельном потоке.

Для этого нам понадобится 2 файла: index.html и filter_worker.js

Разметка страницы


В index.html необходимо расположить один элемент canvas, форму для указания радиуса размывания и span в котором будем записывать текущий процесс работы фильтра в процентах.
HTML
<!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.
JavaScript
// Загрузка изображения
var img = new Image();
img.src = "Malevich.jpg";

function draw(canvas_name) {
	// Поддерживает ли браузер Web Workers?
	if(!window.Worker) {
		return alert('Ваш браузер не поддерживает Web Workers!');
	}

	// Извлечение контекста
	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);

	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 + "%";
		}
	}
}



WebWorker


Алгоритм работы фильтра blur работает следующим образом: найти среднее арифметическое суммы цветов всех пикселей, попадающих в определённый радиус от текущего пикселя и записывать в него исходящий цвет.
WebWorker
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});
}



Вывод:


На основе этого примера вы сможете создавать свои скрипты обработки изображений, которые не приводят к зависанию веб-страницы.
Tags:
Hubs:
+8
Comments10

Articles