web workers
今天,我想谈谈使用纯JavaScriptHTML5中的图片操作。
测试用例
测试应用程序很简单。 左边是要处理的图片,右边是更新的结果(已应用了棕褐色效果):
该页面本身很简单,描述如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>PictureWorker</title>
<link href="default.css" rel="stylesheet" />
</head>
<body id="root">
<div id="sourceDiv">
<img id="source" src="mop.jpg" />
</div>
<div id="targetDiv">
<canvas id="target"></canvas>
</div>
<div id="log"></div>
</body>
</html>
应用棕褐色效果的整个过程需要您为现有源图片的每个像素计算一个新的RGB值,然后将其渲染到id =“ target”的<canvas>标签上。 以下是用于根据像素的现有RGB值创建新RGB值的公式:
finalRed =(红色* 0.393)+(绿色* 0.769)+(蓝色* 0.189);
finalGreen =(红色* 0.349)+(绿色* 0.686)+(蓝色* 0.168);
finalBlue =(红色* 0.272)+(绿色* 0.534)+(蓝色* 0.131);
为了使它更真实一些,我在棕褐色配方中添加了一些随机性。 我创建了一个范围从0.5到1的噪声值,该值确定我的最终像素输出与通过上述公式计算出的RGB值匹配的程度,以及其保留原始RGB值的程度。
function noise() {
//Returns a value between 0.5 and 1
return Math.random() * 0.5 + 0.5;
};
function colorDistance(scale, dest, src) {
// returns a red, blue or green value for the 'sepia' pixel
// which is a weighted average of the original value and the calculated value
return (scale * dest + (1 - scale) * src);
};
var processSepia = function (pixel) {
// takes a given pixel and updates its red, blue and green values
// using a randomly weighted average of the initial and calculated red/blue/green values
pixel.r = colorDistance(noise(), (pixel.r * 0.393) + (pixel.g * 0.769) + (pixel.b * 0.189), pixel.r);
pixel.g = colorDistance(noise(), (pixel.r * 0.349) + (pixel.g * 0.686) + (pixel.b * 0.168), pixel.g);
pixel.b = colorDistance(noise(), (pixel.r * 0.272) + (pixel.g * 0.534) + (pixel.b * 0.131), pixel.b);
};
蛮力
显然,第一个解决方案是使用蛮力,其功能是将先前的代码应用于每个像素。 要访问像素,可以将canvas上下文与以下代码结合使用,该代码创建指向源img和目标canvas的指针:
var source = document.getElementById("source");
source.onload = function () {
var canvas = document.getElementById("target");
canvas.width = source.clientWidth;
canvas.height = source.clientHeight;
// ... tempContext is the 2D context of canvas
tempContext.drawImage(source, 0, 0, canvas.width, canvas.height);
var canvasData = tempContext.getImageData(0, 0, canvas.width, canvas.height);
var binaryData = canvasData.data;
}
此时,binaryData对象包含每个像素的数组,可用于快速将数据直接读取或写入画布。 考虑到这一点,我们可以使用以下代码来应用整个效果:
var source = document.getElementById("source");
source.onload = function () {
var start = new Date();
var canvas = document.getElementById("target");
canvas.width = source.clientWidth;
canvas.height = source.clientHeight;
if (!canvas.getContext) {
log.innerText = "Canvas not supported. Please install a HTML5 compatible browser.";
return;
}
var tempContext = canvas.getContext("2d");
// len is the number of items in the binaryData array
// it is 4 times the number of pixels in the canvas object
var len = canvas.width * canvas.height * 4;
tempContext.drawImage(source, 0, 0, canvas.width, canvas.height);
var canvasData = tempContext.getImageData(0, 0, canvas.width, canvas.height);
var binaryData = canvasData.data;
// processSepia is a variation of the previous version. See below
processSepia(binaryData, len);
tempContext.putImageData(canvasData, 0, 0);
var diff = new Date() - start;
log.innerText = "Process done in " + diff + " ms (no web workers)";
}
processSepia函数只是前一个函数的变体:
var processSepia = function (binaryData, l) {
for (var i = 0; i < l; i += 4) {
var r = binaryData[i];
var g = binaryData[i + 1];
var b = binaryData[i + 2];
binaryData[i] = colorDistance(noise(), (r * 0.393) + (g * 0.769) + (b * 0.189), r);
binaryData[i + 1] = colorDistance(noise(), (r * 0.349) + (g * 0.686) + (b * 0.168), g);
binaryData[i + 2] = colorDistance(noise(), (r * 0.272) + (g * 0.534) + (b * 0.131), b);
}
};
使用此解决方案,在我的Intel Extreme处理器(12核)上,主进程耗时150ms,并且显然仅使用一个处理器:
输入网络工作者
处理SIMD(单指令多数据)时,最好的方法是使用并行化方法,尤其是当您要使用资源有限的低端硬件(例如电话设备)时。
在JavaScript中,要享受并行化的强大功能,您必须使用Web Workers。 我的朋友戴维·罗塞特(David Rousset) 就这一主题写了一篇出色的论文。
图片处理非常适合并行化,因为(如棕褐色调功能)每个处理都是独立的。 因此,可以采用以下方法:
为此,首先必须创建一个tools.js文件,以供其他脚本用作参考。
// add the below functions to tools.js
function noise() {
return Math.random() * 0.5 + 0.5;
};
function colorDistance(scale, dest, src) {
return (scale * dest + (1 - scale) * src);
};
var processSepia = function (binaryData, l) {
for (var i = 0; i < l; i += 4) {
var r = binaryData[i];
var g = binaryData[i + 1];
var b = binaryData[i + 2];
binaryData[i] = colorDistance(noise(), (r * 0.393) + (g * 0.769) + (b * 0.189), r);
binaryData[i + 1] = colorDistance(noise(), (r * 0.349) + (g * 0.686) + (b * 0.168), g);
binaryData[i + 2] = colorDistance(noise(), (r * 0.272) + (g * 0.534) + (b * 0.131), b);
}
};
该脚本的要点是,画布数据的一部分(即当前块要处理的部分)由JavaScript克隆并传递给工作程序。 工作人员不在初始源上工作,而是在它的副本上工作(使用结构化克隆算法 )。 副本本身确实非常快捷,并且仅限于图片的特定部分。
主客户端页面(default.js)必须创建四个工作程序,并为他们提供图片的正确部分。 然后,每个工作人员都将使用消息传递API( postMessage / onmessage )在主线程中回调一个函数,以返回结果:
var source = document.getElementById("source");
source.onload = function () {
// We use var start at the beginning of the code and stop at the end to measure turnaround time
var start = new Date();
var canvas = document.getElementById("target");
canvas.width = source.clientWidth;
canvas.height = source.clientHeight;
// Testing canvas support
if (!canvas.getContext) {
log.innerText = "Canvas not supported. Please install a HTML5 compatible browser.";
return;
}
var tempContext = canvas.getContext("2d");
var len = canvas.width * canvas.height * 4;
// Drawing the source image into the target canvas
tempContext.drawImage(source, 0, 0, canvas.width, canvas.height);
// If workers are not supported
// Perform all calculations in current thread as usual
if (!window.Worker) {
// Getting all the canvas data
var canvasData = tempContext.getImageData(0, 0, canvas.width, canvas.height);
var binaryData = canvasData.data;
// Processing all the pixel with the main thread
processSepia(binaryData, len);
// Copying back canvas data to canvas
tempContext.putImageData(canvasData, 0, 0);
var diff = new Date() - start;
log.innerText = "Process done in " + diff + " ms (no web workers)";
return;
}
// Let say we want to use 4 workers
// We will break up the image into 4 pieces as shown above, one for each web-worker
var workersCount = 4;
var finished = 0;
var segmentLength = len / workersCount; // This is the length of array sent to the worker
var blockSize = canvas.height / workersCount; // Height of the picture chunck for every worker
// Function called when a job is finished
var onWorkEnded = function (e) {
// Data is retrieved using a memory clone operation
var canvasData = e.data.result;
var index = e.data.index;
// Copying back canvas data to canvas
// If the first webworker (index 0) returns data, apply it at pixel (0, 0) onwards
// If the second webworker (index 1) returns data, apply it at pixel (0, canvas.height/4) onwards, and so on
tempContext.putImageData(canvasData, 0, blockSize * index);
finished++;
if (finished == workersCount) {
var diff = new Date() - start;
log.innerText = "Process done in " + diff + " ms";
}
};
// Launching every worker
for (var index = 0; index < workersCount; index++) {
var worker = new Worker("pictureProcessor.js");
worker.onmessage = onWorkEnded;
// Getting the picture
var canvasData = tempContext.getImageData(0, blockSize * index, canvas.width, blockSize);
// Sending canvas data to the worker using a copy memory operation
worker.postMessage({ data: canvasData, index: index, length: segmentLength });
}
};
source.src = "mop.jpg";
免费学习PHP!
全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。
原价$ 11.95 您的完全免费
使用此技术,整个过程在我的计算机上仅持续80毫秒(从150毫秒开始),并且显然使用了四个处理器:
在我的低端硬件(基于双核系统)上,处理时间从500ms减少到500ms。
可以在此处下载最终代码,并在此处发布一个工作示例。 为了进行比较,这是不带web worker的相同代码。
需要注意的重要一点是,在最近的计算机上,差异可能很小,甚至支持没有工作人员的代码。 内存副本的开销必须由工作人员使用的复杂代码来平衡。 上面的棕褐色调转换示例在某些情况下可能不足以保证切换到网络工作者。
但是,网络工作者在具有多核的低端硬件上确实有用。
移植到Windows 8
最终,我无法忍受移植我JavaScript代码以创建Windows 8应用程序的乐趣。 我花了大约10分钟的时间创建了一个空白JavaScript项目,然后将JavaScript代码复制/粘贴到其中。 您可以在此处获取Windows应用程序代码,并体验Windows 8的本机JavaScript代码的强大功能!
翻译自: https://www.sitepoint.com/using-web-workers-to-improve-image-manipulation-performance/
web workers