2

I'm in the process of building an API that will take the JSON object output from a Konva stage and convert that into images on the server-side. I'm making use of the konva-node npm package and it works really well until it comes to loading in remote images that may have been a part of the original "design". I can see from this answer as to how we would solve the problem in the browser, however this doesn't appear to work in the same way in the nodejs implementation of Konva.

An example JSON input is the following:

let json = {
"attrs": {
    "width": 600,
    "height": 600
},
"className": "Stage",
"children": [{
    "attrs": {},
    "className": "Layer",
    "children": [{
        "attrs": {
            "src": "https://ichef.bbci.co.uk/onesport/cps/480/cpsprodpb/1859A/production/_101883799_gettyimages-844211624.jpg"
        },
        "className": "Image"
    }]
}, {
    "attrs": {},
    "className": "Layer",
    "children": [{
        "attrs": {
            "text": "Hello world.",
            "x": 50,
            "y": 50,
            "fontSize": 20,
            "fill": "blue"
        },
        "className": "Text"
    }]
}]

}

As you can see, I've subbed in src attributes as a sort of placeholder for when we come to load up the data into a canvas again.

The issue that I'm having is with actually getting those images to load in again once I process the JSON on the server-side.

Here is my current code

var fs = require('fs')
const Konva = require('konva-node')
var Request = require('pixl-request')

let json = {"attrs":{"width":600,"height":600},"className":"Stage","children":[{"attrs":{},"className":"Layer","children":[{"attrs":{"src":"https://ichef.bbci.co.uk/onesport/cps/480/cpsprodpb/1859A/production/_101883799_gettyimages-844211624.jpg"},"className":"Image"}]},{"attrs":{},"className":"Layer","children":[{"attrs":{"text":"Hello world.","x":50,"y":50,"fontSize":20,"fill":"blue"},"className":"Text"}]}] }

var stage = new Konva.Stage()

let loadedDesign = Konva.Node.create(json)


loadedDesign.find('Image').forEach((imageNode) => {
  const imageURL = imageNode.getAttr('src')
  var request = new Request();

  request.get(imageURL, function(err, resp, data) {
    var img = new Konva.window.Image()
    img.onerror = err => { throw err }
    img.onload = () => {
      imageNode.image(img);
      imageNode.getLayer().batchDraw();
    }

    img.src = data;
  });
});

loadedDesign.toDataURL({
  callback: function(data) {
    var base64Data = data.replace(/^data:image\/png;base64,/, '');
    fs.writeFile('./images/out.png', base64Data, 'base64', function(err) {
      err && console.log(err)
      console.log('See out.png')
    });
  }
});

The current output results in an image with the text on the canvas but the image never makes it in.

cgallagher
  • 235
  • 2
  • 10

1 Answers1

4

You need to load all images, only then use toDataURL(). I guess your image is not visible, because you convert stage to dataURL before images are loaded and rend

var request = new Request();

function loadImage(url) {
  return new Promise(resolve => {
    request.get(url, function(err, resp, data) {
      var img = new Konva.window.Image();
      img.onerror = err => {
        throw err;
      };
      img.onload = () => {
        resolve(img);
      };
      img.src = data;
    });
  });
}

async function run() {
  const stage = Konva.Node.create(json);
  const images = stage.find('Image');

  for (const imageNode of images) {
    const imageURL = imageNode.getAttr('src');
    const img = await loadImage(imageURL);
    imageNode.setImage(img);
  }

  stage.findOne('Layer').draw();
  const data = stage.toDataURL();
  var base64Data = data.replace(/^data:image\/png;base64,/, '');
  fs.writeFile('out.png', base64Data, 'base64', function(err) {
    err && console.log(err);
    console.log('See out.png');
  });
}

run().catch(e => {
  console.error(e);
});
lavrton
  • 18,973
  • 4
  • 30
  • 63