Ensuring balanced canvas save / restore in case of error

When rendering graphics in HTML5 / ECMAScript / JavaScript, a very common pattern is to execute context.save (), apply coordinate transformations, and then execute context.restore (). Unfortunately, HTML5 design seems to keep things like transforms as part of the canvas, even though the drawing context is discarded and recreated; if an error occurs during coordinate transformation, the drawing context will be left in a messy state "forever".

If every piece of code that uses save / restore is protected by a block try

, it can ensure that the context is restored after it exits. However, if any piece of code that is using save

throws an exception without restoring the context, this will cause any restore operation by the calling context to restore the wrong state.

Is there any clean way to do something like:

var savedContext = contextCapturer.capture(context);
try
{
   ... code that might call a save without restore
}
finally
{      
  savedContext.restore();
}

      

I think it is possible that the context save object will change the context save / restore methods to keep a count that it could use to balance things, but I'm not sure if the code could be written to work correctly even if it is called or called by other code that does the same. The saved context does not need to be useful after execution restore

; which is necessary if the code in the try block is doing, for example, five saves and three restores, the operation savedContext.restore()

must perform two additional restores in the context to compensate for the imbalance.

To clarify, I'm thinking about things like the showFramed method; subject to a procedure that is calculated within the specified range, inRect

it transforms coordinates and clips so that they appear in the specified range outRect

(and also in this case draws a yellow frame).

function showFramed(ctx, inRect, outRect, bColor, bWidth, proc)
{
  ctx.save();
  ctx.strokeStyle=bColor;
  ctx.lineWidth=bWidth;
  ctx.beginPath();
  ctx.rect(outRect[0],outRect[1],outRect[2],outRect[3]);
  ctx.stroke();
  ctx.restore();
  
  ctx.save();
  ctx.clip();
  ctx.translate(outRect[0],outRect[1]);
  ctx.scale(outRect[2]/inRect[2], outRect[3]/inRect[3]);
  ctx.translate(-inRect[0],-inRect[1]);
  proc();
  ctx.restore();
}

function showScreen(ctx)
{
  var i;
  ctx.beginPath();
  for (i=0; i<=160; i+=10)
    {
      ctx.moveTo(i,0);
      ctx.lineTo(0,160-i);
      ctx.lineTo(i/2+80,i/2+80);
      ctx.lineTo(160-i,160);
    }
  ctx.stroke();
}

function showScreen2(ctx)
{
  showScreen(ctx);
  ctx.save();
  ctx.strokeStyle="#007F00";
  showFramed(ctx, [0,0,160,160], [20,90,60,60], "#FFFF00", 3,
            function() {showScreen(ctx); });
  ctx.strokeStyle="#7F0000";
  showFramed(ctx, [0,40,80,80], [90,90,60,60], "#FFFF00", 3,
            function() {showScreen(ctx); });
  ctx.restore();  
}


function demo1()
{
  var c=document.getElementById("canv");
  var ctx=c.getContext("2d");
  ctx.strokeStyle="#0000FF";
  showScreen2(ctx);
}

function demo2()
{
  var c=document.getElementById("canv");
  var ctx=c.getContext("2d");

  ctx.strokeStyle="#00FF00";
  showFramed(ctx, [0,0,160,160], [80,10,60,60], "#FFFF00", 3,
            function() {showScreen2(ctx); });
}
      

<canvas id="canv" Width=160 height=160></canvas>
<button onClick="demo1()">Demo1</button>
<button onClick="demo2()">Demo2</button>
      

Run codeHide result


The method drawFramed

has no idea what transformation is applicable when it is introduced. If an exception is thrown from the pass-in procedure, it should not be swallowed, but I would suggest that I showFramed

should nevertheless restore the context to what it was before it was introduced; if it isn't?

+3


source to share





All Articles