SpeedOfRound
SpeedOfRound

Reputation: 1278

Loading images for canvas object asynchronously

I'm on fabric 3.4 in a node environment. I am trying to load and create during the loading of an existing canvas.

The way it works, is I have an existing fabric stored as json, which has a rect object in it that acts as a placeholder for an image. So when the canvas is loaded, and image location is provided so that it can replace the existing rect object with the same size and position.

I think I'm pretty close, though any documentation for loading images without a url seems to be very outdated. These are local files, not available by any url.

import { fabric } from 'fabric';
const Canvas = require('canvas');

var canvasobj = new fabric.StaticCanvas(null, { 
  height: 1000, 
  width: 1000 
});

let template = JSON.parse(data);//data is canvas json

canvasobj.loadFromJSON(template, () => {
  canvasobj.renderAll();
}, 
(o, object) => {
     if(object.stitch.type === 'portrait'){//identifier for placeholder
         const c = Canvas.createCanvas(2304, 3456)
         const ctx = c.getContext('2d')

          Canvas.loadImage(binds.image).then((image) => {
              ctx.drawImage(image, 50, 0, 70, 70)

              fabric.Image.fromURL(c.toDataURL(), (img) => {
                  canvasobj.add(img)
                  canvasobj.remove(object);//remove the placeholder
              }
         }
     }
}
...

I think this method will work. But the problem I'm having is Canvas.loadImage is asynchronous, and this reviver that runs on each object is not. The revivers get run, and then the callback that renders the canvas gets called before the image has loaded. If I make the reviver async and await the image loading, this dons't change anything, since nothing is waiting for the reviver to resolve.

Is there a way to get this method to work? Is there a different way to approach this?

Upvotes: 1

Views: 2832

Answers (2)

leszek.hanusz
leszek.hanusz

Reputation: 5317

You can create an async function from a callback based method using a Promise.

async function fabricImageFromURL(image_url) {                                                                          
  return new Promise(function(resolve, reject) {                                                                        
    try {                                                                                                               
      fabric.Image.fromURL(image_url, function (image) {                                                                
        resolve(image);                                                                                                 
      });                                                                                                               
    } catch (error) {                                                                                                   
      reject(error);                                                                                                    
    }                                                                                                                   
  });                                                                                                                   
}

then you can use it in your async function using await:

async function doingMyStuff() {

  const image = await fabricImageFromURL(image_url);

  // here image is already loaded
}

Upvotes: 3

shkaper
shkaper

Reputation: 4988

If your only issue is that you need to wait for all the revivers, you can just create an empty array and push a new Promise each time the reviver is called. Resolve each of those promises when your async image load is done. In the meantime, call Promise.all() and wait for the whole async stuff to end. The revivers themselves seem to be called synchronously, so by the time your script reaches Promise.all(), you'll have all those promises in the Pending state.

const promiseArray = []

canvasobj.loadFromJSON(template, () => {}, 
(o, object) => {
  const p = new Promise((resolve) => {
   //...
    Canvas.loadImage(binds.image).then((image) => {
      //...
      fabric.Image.fromURL(c.toDataURL(), (img) => {
        //...
        resolve()
      })
    })
  })
  promiseArray.push(p)
})

Promise.all(promiseArray)
  .then(() => {
    // ...
  })

Upvotes: 2

Related Questions