0

I am trying to convert an img URL to a var that I can use elsewhere in my code.

So far, I have the following approach:

function toDataUrl(src) {
  // Create an Image object
  var img = new Image();
  // Add CORS approval to prevent a tainted canvas
  img.crossOrigin = 'Anonymous';
  // Load the image
  img.src = src;
  // make sure the load event fires for cached images too
  if (img.complete || img.complete === undefined) {
    // Flush cache
    img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==';
    // Try again
    img.src = src;
  }

  img.onload = function() {
    // Create an html canvas element
    var canvas = document.createElement('CANVAS');
    // Create a 2d context
    var ctx = canvas.getContext('2d');
    var dataURL;
    // Resize the canavas to the original image dimensions
    canvas.height = this.naturalHeight;
    canvas.width = this.naturalWidth;
    // Draw the image to a canvas
    ctx.drawImage(this, 0, 0);
    // Convert the canvas to a data url
    dataURL = canvas.toDataURL(outputFormat);
    // Mark the canvas to be ready for garbage
    // collection
    canvas = null;

    // Return the data url via callback
    // callback(dataURL);
    return dataURL;

  };
}

var myimg = toDataUrl('/media/signatures/signature.8.png');

However, myimg returns undefined. I think this is because it might be loading asynchronously. I have refered to this answer however it does not explain how to set the callback as a global variable.

How can I set myimg = dataURL? Is there a way to do this without a callback?

alias51
  • 8,178
  • 22
  • 94
  • 166
  • 1
    There's `FileReader` object doing that for you. – Robo Robok Apr 28 '20 at 00:32
  • 1
    The thing is, you can't return anything from within that `load` Event. Why not pass a function as an argument that you pass `dataURL` into? `function toDataURL(src, func){ /* code, code, code */ img.onload = function(){ /* code, code, code */ func(dataURL); }/*blah, blah */` – StackSlave Apr 28 '20 at 00:33
  • 1
    You can't break the laws of JavaScript physics and have an asynchronously derived result return immediately from a function call. You'll have to go the Promise or basic callback route to get what you need. There's no way around it. – Jacob Apr 28 '20 at 00:36
  • @RoboRobok per https://stackoverflow.com/a/20285053/2429989 doesn't FileReader also require a callback? – alias51 Apr 28 '20 at 01:46

1 Answers1

1

There's no way to break the entire execution model of JavaScript in order to get your asynchronously-populated variable returned from your function call.

Although this uses callbacks behind the scenes, you may find using an async function pattern to relieve the burden of needing to use callbacks.

function toDataUrl(src) {
  // Create an Image object
  var img = new Image();
  // Add CORS approval to prevent a tainted canvas
  img.crossOrigin = 'Anonymous';
  // Load the image
  img.src = src;
  // make sure the load event fires for cached images too
  if (img.complete || img.complete === undefined) {
    // Flush cache
    img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==';
    // Try again
    img.src = src;
  }

  return new Promise((resolve, reject) => {
    img.addEventListener('load', () => {
      // Create an html canvas element
      var canvas = document.createElement('CANVAS');
      // Create a 2d context
      var ctx = canvas.getContext('2d');
      var dataURL;
      // Resize the canavas to the original image dimensions
      canvas.height = this.naturalHeight;
      canvas.width = this.naturalWidth;
      // Draw the image to a canvas
      ctx.drawImage(this, 0, 0);
      // Convert the canvas to a data url
      dataURL = canvas.toDataURL(outputFormat);
      // Mark the canvas to be ready for garbage
      // collection
      canvas = null;

      resolve(dataURL);
    });
    img.addEventListener('error', reject);
  });
}

This now can be consumed within an async function thusly:

async function doSomething() {
  const myimg = await toDataUrl('/media/signatures/signature.8.png');
}

Keep in mind that doSomething is now also asynchronous, so anything calling it now has to await as well (or use Promise callbacks).

Jacob
  • 77,566
  • 24
  • 149
  • 228