caleb531 / jcanvas

A jQuery plugin that makes the HTML5 canvas easy to work with.
https://projects.calebevans.me/jcanvas/
MIT License
626 stars 192 forks source link

restoreCanvas() doesn't restore the canvas after being scaled by scaleCanvas() #135

Closed shihyuan closed 10 years ago

shihyuan commented 10 years ago

Summary

The restoreCanvas() does not restore the canvas to the state saved by saveCanvas() before it is scaled by scaleCanvas().

Example

I reproduced the issue using the sandbox with the following codes:

First draw a circle

// Draw a circle
$("canvas").drawArc({
  fillStyle: "#000",
  x: 100, y: 100,
  radius: 20,
  layer: true
})

Then save the current canvas, before scaling it and re-draw the layers with:

// Save the original canvas
$("canvas").saveCanvas();

// Scale the canvas
$("canvas").scaleCanvas({
  scaleX:1,
  scaleY:0.5
});

// Redraw the layers to show the scaled circle
$("canvas").drawLayers();

At this point the circle shown is squished due to the scaling of the canvas.

Then let's restore the original non-scaled canvas with

// Restore to the non-scaled canvas
$("canvas").restoreCanvas();

Finally re-draw the circle by

// Redraw the layers
$("canvas").drawLayers();

The circle is still shown squished, which is not the expected behavior of saveCanvas() and restoreCanvas().

Things I've tried

caleb531 commented 10 years ago

A few things:

When transforming a canvas consisting of jCanvas layers, you should really also make your transformation a layer using the layer property, like so:

$("canvas").scaleCanvas({
  layer: true,
  scale: 0.9
});

However, when you do this, you must also call the restoreCanvas() method with layer: true. This tells jCanvas to reset the transformation at the end of every redraw (saving you from compounding transformations).

$("canvas").restoreCanvas({
  layer: true
});

However, the important thing here is that you must create these transformation layers (that is, scaleCanvas() and restoreCanvas()) before calling drawLayers(). Otherwise, jCanvas will reapply the transformation when redrawing layers because the scale transformation (in this case) is an actual layer itself.

Finally, note that all of the transformation methods (scaleCanvas(), translateCanvas(), etc.) will automatically call the saveCanvas() method before transforming so you don't have to.

With this in mind, here is a working version of your code:

// Cache canvas and grid properties
var $canvas = $("canvas");

// Scale the canvas
$canvas.scaleCanvas({
  layer: true,
  scaleX: 1,
  scaleY: 0.5
});

// Restore the canvas
$canvas.restoreCanvas({
  layer: true
});

// Draw a circle
$canvas.drawArc({
  layer: true,
  fillStyle: "#000",
  x: 100, y: 100,
  radius: 20
});

// Redraw the layers to show the scaled circle
$canvas.drawLayers();
shihyuan commented 10 years ago

Thanks @caleb531. That makes a lot of sense. I didn't realize that even without the layer: true, canvas manipulators are still treated as layers.