Search code examples
angularrxjsrxjs-pipeable-operators

How to return an array of Imagebitmaps from within an Observable


I have a requirement to create an array of Imagebitmaps from an array of URLs. HTTP requests should be done in parallel with the ability to cancel the array of requests e.g. if a new array of URLs is provided.

Upon the receipt of each image blob it should be transformed into an Imagebitmap and be subject to some additional processing before ALL the Imagebitmaps are returned as an array to a subscriber.

So far I have this:

this.nextImagesSubscription = this.nextImagesSubject.pipe(switchMap((urls: string[]) => {
      console.log(`received new images`)
      return forkJoin(urls.map((url, index) => this.beService.getImageBlob(url).pipe(map(blob => ({blob, url, index})))))
})).subscribe((blobData: {blob: Blob, url: string, index: number}[]) => {
      console.log(`Received blobs: ${blobData.length}`);
      blobData.forEach(image => console.log(`${image.index}: '${image.url}'`))
})

The above code satisfies:

  1. Parallel HTTP requests
  2. HTTP requests can be cancelled (upon calling nexImagesSubject.next())
  3. All data is returned as an array to the subscriber.

However it does NOT satisfy:

  1. Return an array of Imagebitmap
  2. Does not allow for additional processing of the Blob data upon receipt i.e. transformed into a ImageBitmap.

Creating an Imagebitmap is straightforward but it is the handling of the Promise that is causing me issues. For example:

return forkJoin(urls.map((url, index) => this.beService.getImageBlob(url)
    .pipe(map(blob => {
        createImageBitmap(blob).then(image => {
            /* Do some additional compute on image */
            return ({image, url, index})});
     }))))

This does not work because it is not {image, url, index} that is returned from the map.

How can I create the Imagebitmap in the pipe and have ALL the Imagebitmaps returned as an array to the subscriber (and fulfill the other requirments mentioned above)?


Solution

  • To process the images you can create an async function that accepts the Blob and returns the ImageBitmap, like:

    async function processImage(blob: Blob): Promise<ImageBitmap> {
      const imageBitmap = await createImageBitmap(blob);
    
      //image post process ...
    
      return imageBitmap;
    }
    

    And then in your forkJoin use from to create an Observable around the Promise of the function call.

    return forkJoin(
      urls.map((url, index) => 
        this.beService.getImageBlob(url).pipe(
          switchMap(blob => from(processImage(blob))),
          map(image => ({ image, url, index })) //<- Only required if you want to export also the url and index
        )
      )
    )
    

    Cheers