7

I have an image encoded in base64 and I need to draw it on a canvas in a synchronous fashion. Take a look at this code I had:

//convert base64 to actual Image()
var input = new Image();
input.src = dataurl;

//draw on canvas
var w = input.width, h = input.height;
canvas.width = w; canvas.height = h;
ctx.drawImage(input, 0, 0)

This used to work on Chrome. I know it is not a good practice to assuming the browser will be able to finish loading before the next instruction, but it worked. After several updates later, it finally fails.

I cannot listen to the load event because that would make it async, which is not good because that's a generator function and I need to return the canvas synchronously. The program is built around it and making it async would mean complete rewrite. That's not good.


Attempt

input.src = dataurl;

while(!input.width){ /* busy wait */ }

var w = input.width, h = input.height;

Does not work since the browser won't update it before my code finishes executing.


Right now I really can't think of any way to solve this dilemma, so any input is appreciated. (Do note that the datauri is generated from another canvas, which I do have access to.)

Derek 朕會功夫
  • 92,235
  • 44
  • 185
  • 247
  • Will [this](http://stackoverflow.com/questions/4405336/how-to-copy-contents-of-one-canvas-to-another-canvas-locally) work? – sabithpocker Oct 21 '15 at 18:32
  • 1
    I'm guessing your other code depends on the canvas having the image already drawn -- otherwise you could just return the new canvas element in it's "un-drawImage'ed" state and the drawing will be done after the image loads. Ok, how about refactoring the code that calls your generator to supply a callback function that gets executed by the generator code inside `image.onload`? – markE Oct 21 '15 at 23:41
  • 1
    @markE Thanks for the suggestion. I have thought about adding a callback function, but that would be too much work for me to rewrite other functions that are calling it, since they should be synchronous as well. Anyway I have tried @sabithpocker's suggestions and modified my code slightly to return a `` instead of the image in base64 form. – Derek 朕會功夫 Oct 21 '15 at 23:59

2 Answers2

4

It's not possible to draw an image to a canvas before it is loaded, which means waiting to a load event. This means you cannot load a data URI into an DOM image synchronously, so you will need to convert your API to an asynchronous one.

The only other way would be to have a JavaScript-based image decoder that converts the data URI into a ImageData object, which would not be trivial to do and require users to load a lot more code.

There used to be a different hack involving innerHTML that would work, but it too no longer works.

Now-Broken Example:

var base64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJ4AAACeCAYAAADDhbN7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMDE0IDc5LjE1Njc5NywgMjAxNC8wOC8yMC0wOTo1MzowMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RkE1MTZBNzk1MDE1MTFFNUEzODE5OTE2QTE2M0ExOTYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RkE1MTZBNzg1MDE1MTFFNUEzODE5OTE2QTE2M0ExOTYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpDM0YzRTJFODk1NDcxMUU0OUQ3NkI2RkYyOUFBMjg3RCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpDM0YzRTJFOTk1NDcxMUU0OUQ3NkI2RkYyOUFBMjg3RCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgtvbosAAAnjSURBVHja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';

var el = document.createElement('p');
el.innerHTML = '<img src="'+base64+'" />';
var input = el.firstChild;

//draw on canvas
var canvas = document.getElementById('canvas');
var w = input.width, h = input.height;
canvas.width = w; canvas.height = h;
var ctx = canvas.getContext('2d');
ctx.drawImage(input, 0, 0);
<canvas id="canvas"></canvas>
Alexander O'Mara
  • 58,688
  • 18
  • 163
  • 171
  • Great solution, I will definitely test this out later. – Derek 朕會功夫 Oct 21 '15 at 23:15
  • Unfortunately this does not work. It behaves the same as a normal `image.src = dataurl` and sometimes the image will not get loaded in time. – Derek 朕會功夫 Oct 21 '15 at 23:22
  • @Derek朕會功夫 Strange, seems to be working for me. It's certainly possible there is a race condition though. – Alexander O'Mara Oct 21 '15 at 23:48
  • There's definitely a race condition which I should've thought of when I wrote that part of the code... (race condition in a single threaded language. What...) But anyway, I have modified my code slightly to pass in a `canvas` node instead and it seems to be working now, though I still need more testing to see if there's any other hidden problems. Thanks again. – Derek 朕會功夫 Oct 21 '15 at 23:54
0

Just use Promise to make it async.

 return new Promise(resolve => {

          image.onload = function () {
                ctx.drawImage(input, 0, 0)
                resolve('resolved');
             }

    });