How Canvas.save() and Canvas.restore() actually work

Maybe I don’t understand how this works, but I’m assuming I understand the underlying HTML5, and I’m assuming that functionality is being called directly.

The transformation matrix either isn’t being saved or restored. No errors are raised, but nothing happens, either. Color is correctly stored and restored, though. The HTML5 docs say pretty much everything SHOULD get saved. The Anvil docs imply that only transformations are saved.

https://anvil.works/build#clone:VIOOAHVF2QY2YERO=QEZR4UBMGRRIIO5GEQJ2EVVZ

From what I remember about working with canvas you restore to your last saved state.

Whereas I think you’re trying to use restore to reset the state to its original.

In your example you’re doing a transformation to the center, saving the state and then later restoring to that same state.

To adjust your code move c.save() earlier in your execution (before you do the transform to the center)

c.save()

# transform to center

# do some drawing

c.restore() # restore to the state before transforming to center
1 Like

Yep, you’re absolutely right. I failed to replicate the behavior I’m experiencing in another project, and didn’t realize what I’d done wrong!

Okay, then this is less likely to be a bug, and more likely to be a misunderstanding on my part. I’ve updated the sample app, do you mind taking a look at it?

To replicate the behavior I’m experiencing, I actually need to do a negative scale transformation. In the sample app, I transform to the center of the canvas, and save the transformation. There are two buttons. One restores the saved transformation matrix and draws an arrow (pointing left). The other restores the saved transformation matrix, then performs a [-1,0,0,1,0,0] transformation to mirror everything horizontally, and draws an arrow, which now points right.

The problem happens if you draw a flipped arrow, then draw a normal arrow. The second arrow is drawn mirrored. Since the saved transformation is restored before drawing arrows in both cases, I interpret this as the saved transformation not properly being restored.

I suspected that part of the issue was to do with the difference between Canvas.set_transform() and Canvas.transform(). I’m using set_transform() so that I can put my origin where I like it on the show_canvas event, then do “in-place” transformations without having to keep track of the transformation state. However, after testing, the behavior does not seem limited to “in-place” transformations.

https://anvil.works/build#clone:VIOOAHVF2QY2YERO=QEZR4UBMGRRIIO5GEQJ2EVVZ

Does it help to think of restore as popping the last saved state off the stack?

Typically you’d have one save for every call to restore. So I think what might be happening here is that there is no state to restore because you only call save on the canvas show event.

If that doesn’t help let me know and I can dig around a little more.

1 Like

Ah, it is indeed a poppable stack, not a single save point, and the MDN docs do make that clear. I just didn’t absorb that little bit of information. :slight_smile:

Suggest amending the Anvil docs. It’s weird to list save and restore under the transform heading, when they interact with many more canvas attributes.

save()

Adds the current canvas state (including transformation matrix, stroke_style, line_width, etc) to a stack, so that it can be restored later.

restore()

Removes the most recent saved state from the stack and applies it to the canvas. If the stack is empty, this method does nothing.

1 Like

In this case, the names save and restore are clearly leading folks astray. Since they act on a logical stack, they should probably be named push and pop (or push_state and pop_state).

Well, “folks” at this point only includes me, as far as we know. :smile:

I agree, those would be better names! Too bad W3C won’t change them at this point. I think you might want to favor transparency and just change the documentation.

Too bad, indeed.

If you’ve wrapped your own class around the object (e.g., for ease of use), of course you can name your wrapper functions anything you want.

1 Like