Issue
In my Angular I have a service that draws a picture on a canvas ang get its "ImageData".
This is my service.ts:
getColorArray (movie: MovieDb): Observable<any> {
var img = new Image();
img.crossOrigin = "Anonymous";
img.src = "https://www.themoviedb.org/t/p/w342/" + movie.poster;
var canvas = <HTMLCanvasElement>document.getElementById("canvas");
var ctx = canvas.getContext("2d");
canvas.height = 50;
canvas.width = 30;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
return of(imgData)
}
And my .ts where I use it:
pixelate(movie) {
this.getBackgroundColor.getColorArray(movie)
.subscribe(r => do something with r)
I’ve made it as an Observable thinking that imgData
would be returned when getImageData()
is done but it’s returned as soon as getColorArray()
is called.
How can I wait for getImageData()
to complete before returning its value?
Solution
the problem isn’t "waiting for getImageData
", it’s waiting for the image to load, and you need to do that explicitly. something like:
getColorArray (movie: MovieDb): Observable<any> {
// create an observable
return new Observable(obs => {
var img = new Image();
img.crossOrigin = "Anonymous";
img.src = "https://www.themoviedb.org/t/p/w342/" + movie.poster;
// once the image loads, draw it and the image data is available synchronously
var imgLoadListener = e => {
var canvas = <HTMLCanvasElement>document.getElementById("canvas");
var ctx = canvas.getContext("2d");
canvas.height = 50;
canvas.width = 30;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
obs.next(imgData);
obs.complete();
};
img.addEventListener('load', imgLoadListener);
return () => { // clean up after yourself.
img.removeEventListener('load', imgLoadListener)
}
})
}
a way to this a bit cleaner / less manually is like so, using defer
and fromEvent
, which will handle some of the cleanup work for you and handle errors a bit better:
getColorArray (movie: MovieDb): Observable<any> {
// defer to not create image until subscribe
return defer(() => {
var img = new Image();
img.crossOrigin = "Anonymous";
img.src = "https://www.themoviedb.org/t/p/w342/" + movie.poster;
return fromEvent(img, 'load').pipe(
map(() => {
var canvas = <HTMLCanvasElement>document.getElementById("canvas");
var ctx = canvas.getContext("2d");
canvas.height = 50;
canvas.width = 30;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
return imgData;
})
);
})
}
you COULD remove the defer
part above and it’d work just fine, but it’s standard practice in observables to make sure they do nothing until subscribed to, which the use of defer
accomplishes here.