The expected workflow of my code is getting the data from getData
.
getData
calls the worker that will do ImageUtil.getHex
on the input. ImageUtil.getHex
is a heavy function that needs to iterate every pixel of an image area area
, so that's why I want to create it runs in the background and done in multithreading. The function is also independent, which I guess is a good candidate to put in the worker.
This is the chunk of code that needs the getData
function:
class Mosaic {
// ...
build() {
for (let y = 0; y < canvas.height; y += options.tileHeight) {
for (let x = 0; x < canvas.width; x += options.tileWidth) {
//... some code here
// imgContext: a context of a canvas containing the image
let area = imgContext.getImageData(x, y, w, h);
this.getData(area, x, y)
.then((data) => {
// do something with data
});
//...
}
}
// ...
}
// ...
}
this is the getData function:
getData(data, x, y) {
return new Promise((resolve, reject) => {
let worker = new Worker('js/worker.js');
worker.onmessage = (e) => {
let hex = e.data;
let img = new Image();
let loc = `image/${hex}`
img.onload = (e) => {
resolve({
hex: hex,
x: x,
y: y
});
}
img.src = loc;
}
worker.postMessage(data);
});
js/worker.js
self.addEventListener('message', function(e) {
let hex = ImageUtil.getHex(e.data); // another function
self.postMessage(hex);
self.close();
}, false);
class ImageUtil {
static getHex(imgData) {
let data = imgData.data;
let r = 0,
g = 0,
b = 0,
for (let i = 0; i < data.length; i += 4) {
// count rgb here
}
let count = data.length / 4;
r = ("0" + (Math.floor(r / count)).toString(16)).slice(-2);
g = ("0" + (Math.floor(g / count)).toString(16)).slice(-2);
b = ("0" + (Math.floor(b / count)).toString(16)).slice(-2);
let hex = `${r}${g}${b}`;
return hex;
}
}
The problem is, when ran, it makes the browser crash. Even if it's not crashing, the performance is much slower than without using worker.
Steps to reproduce:
Mosaic
object. Then, we call build()
on that Mosaic object.I think I misunderstood the way of workers work. Is it the right way, and how do I fix the code so it won't crash anymore?
Thanks!
The issue is that you are calling makeTile
within a nested for
loop, where makeTile
creates worker
. You are creating 950 Worker
instances. Each Worker
instance calls postMessage
. That is the reason the browser is crashing.
You need to adjust you scripts to handle arrays of promises, instead of a single Promise
. worker.js
should be called once, not 950 times.
You can create an array before the for
loops, pass the data as an array to Promise.resolve()
var arr = [];
for (let y = 0; y < canvas.height; y += options.tileHeight) {
for (let x = 0; x < canvas.width; x += options.tileWidth) {
let areaData = imgContext
.getImageData(x, y, options.tileWidth, options.tileHeight);
arr.push(Promise.resolve([areaData, x, y]))
}
};
then after the for
loops use Promise.all()
to process the data
Promise.all(arr.map(function(tile) {
this.makeTile(/* tile data here */) // make necessary changes at `makeTile`
.then(function(tiles) {
// do stuff with `tiles` array
})
}))
Move
let worker = new Worker('worker.js');
outside of makeTile()
, or create logic so that the call is only made once.
Similarly, at worker.js
, adjust the script to handle an array of data, instead of a single value.
When message
event is fired at main thread, process the data as an array of values.
The gist of the solution is to refactor your code base to handle arrays, and arrays of promises; both at main thread and at worker.js
; with the object being to call worker.js
at most once at change
event of <input type="file">
element. Where a single message
is posted to Worker
, and single message
event is expected from Worker
. Then do stuff with the returned array containing the processed data.