Home > Back-end >  How to return an array of Imagebitmaps from within an Observable
How to return an array of Imagebitmaps from within an Observable

Time:02-06

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)?

CodePudding user response:

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

CodePudding user response:

You have pretty much to be done per url / image. I suggest defining all the per url actions in a method like this:

private async loadImageBitmap(url: string): Promise<ImageBitmap> {

    // Step 1: Load the blob for the single specified url.
    const blob = await this.beService.getImageBlob(url).toPromise();

    // Step 2: Create the ImageBitmap.
    const image = new ImageBitmap();

    // Step 3: Perform additional processing.
    /* Do some additional compute on image */

    return image;
}

With that specified the pipe looks way cleaner and more readable:

this.nextImagesSubscription = this.nextImagesSubject.pipe(
  map(urls => urls.map(url => this.loadImageBitmap(url))),
  switchMap(requests => combineLatest(requests))
).subscribe(images => {
  /* Do what ever you want with the images */
});

The only thing I am not sure about is the cancelation part: Neither in my code nor in yours. I don´t think the http requests will be canceled when another set of urls emit.

  •  Tags:  
  • Related