0

If I single-step the following js code (using Firefox 44 on OS X), I get the desired result: the context.setTransform(...) is executed and the image is magnified by 2.

correct result obtained only when single stepping

If on the other hand I run the code in FF by simply loading the page, it is as if the transformation is never applied. (The outcome is then identical to having commented out the context.setTransform(...) line.)

otherwise an incorrect output is obtained

The culprit appears to be the context.save()/context.restore() pair. Commenting these two lines out produces the desired outcome directly. But it is good practice to wrap graphics code with save/restore to isolate the context, especially the current transformation.

You can generate the image on your side with convert -size 100x100 plasma:fractal plasma_fractal1.jpg.

I understand that the order of setting img.src is rather finicky, but I'm heeding the correct, multi-browser-safe, order.

What is going on?

myfrac.html

<!DOCTYPE html>
    <head>
        <meta charset="UTF-8">
        <style>
            div {
                width: 600px;
                height: 400px;
                background: #BBBBBB;
                margin: 20px;
                padding: 15px;
            }
            #myCanvas {
                background-color: white;
                border: 2px solid black;
                width: 400px;
                height: 300px;
            }
        </style>
    </head>
    <body>
        <div>
            <canvas id="myCanvas">
                <p>Canvas not available</p>
            </canvas>
        </div>
        <script src="myfrac.js" type="text/javascript"></script>
        <script>myDrawingFunction()</script>
    </body>
</html>

myfrac.js

function myDrawImage(context, imageURI, x, y, w, h)
{
    var img = new Image;
    img.onload = function(){
        context.drawImage(img, x, y, w, h);
    };
    img.src = imageURI;
}

function myDrawingFunction()
{
    var canvas = document.getElementById('myCanvas');
    var context = canvas.getContext('2d');

    context.clearRect(0, 0, canvas.width, canvas.height);

    context.save();
    context.setTransform(2, 0, 0, 2, 0, 0);
    myDrawImage(context, "plasma_fractal1.jpg", 0, 0, 200, 200);
    context.restore();
}
Community
  • 1
  • 1
Calaf
  • 8,037
  • 8
  • 46
  • 96
  • When do you call your js code? Are you waiting for the document to be read first? – bhspencer Feb 18 '16 at 23:20
  • I could imagine your image is loading and you transform it and then the dom is reflowed. Where as if you are stepping in with the debuger you might be running your code after the dom has been flowed. – bhspencer Feb 18 '16 at 23:21
  • If you have jQuery around Try wrapping your call to the draw function with '$(document).ready(myDrawingFunction);' – bhspencer Feb 18 '16 at 23:25
  • @bhspencer Wrapping with '$(document).ready(myDrawingFunction);' didn't help. Single-stepping still produces a different outcome. – Calaf Feb 18 '16 at 23:58
  • Does it work using `window.onload` event, like `window.onload = myDrawingFunction`? – BoltKey Feb 19 '16 at 00:04
  • @BoltKey I'm afraid `window.onload = myDrawingFunction` doesn't help either. The same strange effect persists. – Calaf Feb 19 '16 at 00:08

2 Answers2

2

You're facing a 'race' issue between two async function (the anonymous function hooked on the Image.onload that actually performs drawImage and the myDrawingFunction function).
In the debug case, the image has time to load, so the onload triggers at once and does what you want.
In the case where you just load regularly the page, the context is set/unset before image is loaded : when the image gets loaded it's too late (context was restored) and the image is drawn at 1:1 scale.

Proper way of coding such things is to separate concerns and wait for both the dom and all images to load before doing anything.

GameAlchemist
  • 17,761
  • 6
  • 28
  • 54
  • arf we felt on this one at the same time ;-) Actually `myDrawingFunction` is synchronous so it should not be called before the onload event, which is a buggy thing in webkit I think. – Kaiido Feb 19 '16 at 10:47
  • @Kaiido : ( :-) ) myDrawingFunction is called after the script loaded, and calls an async function, so i couldn't tell if it's async or not... I see no bug here, only a race that has a different winner in debug or std mode. – GameAlchemist Feb 19 '16 at 13:06
  • if there `async` attribute is not set, external ` – Kaiido Feb 19 '16 at 13:10
0

That's completely normal and expected behavior.

You are mixing synchronous and asynchronous calls.

Synchronous calls will be made one by one, in written order, while asynchronous ones should be stacked on top of the operations to execute.

So a step by step of your code would be :


  1. Fetch myfrac.js
  2. Define myDrawImage function and myDrawingFunction function *
  3. Call myDrawingFunction
  4. Define canvas and ctx variables
  5. Clear the context (useless if nothing was drawn before and if something was, previous var definitions should be made outta here)
  6. Save the context properties
  7. Call myDrawImage
  8. Define img
  9. Set its onload event handler (this handler will be stacked)
  10. Set img's src
  11. Restore the context properties (nothing was drawn on it yet)
  12. Do anything else has to be done by other scripts if any

 12.+x. Call the img onload handler = drawImage()


As you can see, at the time the onload handler is called, you already restored the context's properties, and reset the context's matrix.

*The order of these two can't be assured

Kaiido
  • 87,051
  • 7
  • 143
  • 194
  • That clarifies it perfectly. Though I'm still struggling with a fix (I should have asked for one :). I'm already putting the code at the end of the body (http://stackoverflow.com/a/9899701/704972). Replacing `` with `` doesn't help. Also, as mentioned in the comments, using JQuery and wrapping with '$(document).ready(myDrawingFunction);' doesn't solve this problem. – Calaf Feb 19 '16 at 14:57
  • IIUC, when you write "10. Set img's src" you are actually pointing out that the line "img.src = imageURI;" is non-blocking. The trick is that I am already waiting for the image to load ("img.onload = function(){...}"). The trouble is that "img.src = imageURI;" spawns (I'm suspecting) an image-load thread. – Calaf Feb 19 '16 at 15:07
  • Also, even though you used a smaller font and parentheses, I value your critique "(useless if nothing was drawn before and if something was, previous var definitions should be made outta here)". But I don't understand. It's not useless because something was indeed drawn before. How would I clearRect before declaring the canvas/context vars? – Calaf Feb 19 '16 at 15:08
  • @Calaf, this has nothing to do with the page loading, you could execute the code 3 hours after everything else was loaded, you'll still face the same issue. No setting the src of an img is non-blocking, this will just trigger the fetching and parsing of the resource pointed in the url you provided. The onload event may fire at any time after that. The handler will be stacked as soon as the event fired, but if it has something else to do, it should first finish all synchronous tasks. – Kaiido Feb 20 '16 at 06:01
  • For a real solution, see @GameAlchimist answer where he advice to first load all he resources before doing anything on your canvas. Definitely the best approach. What could be done otherwise, would be wrap the `setTransform()` in the load handler. For the small note, you should split your drawings and declarations in different functions. getContext() should be called only once, make these variable accessible for all your drawing scope. – Kaiido Feb 20 '16 at 06:07
  • Also, this sentence " But it is good practice to wrap graphics code with save/restore to isolate the context, especially the current transformation." is not true. `ctx.save()` will save all context's properties (transforms but also stroke and fill styles, clipping and everything else... That's a loud operation, and it can be source of problems if you don't use it correctly (each call will stack all these properties and release it only when `restore()` is called, if you miss one `restore()`, you're going to fullfill the memory. – Kaiido Feb 20 '16 at 06:11
  • If you only do play with transforms, reset it afterward with `setTransform(1,0,0,1,0,0)`. The same does apply with other properties. – Kaiido Feb 20 '16 at 06:11