Search code examples
c#.netsystem.drawing

Is it safe to restore state multiple times / across different Graphics?


Is it safe to use saved state of one Graphics object with an other Graphics objects? For example, like in the following sample.

Graphics g1 = ..;
Graphics g2 = ..;

GraphicsState state1 = g1.Save();
g2.Restore(state1); // state from the other Graphics

Also, is it safe to restore the same state multiple times?

Graphics g1 = ..;
Graphics g2 = ..;

GraphicsState state1 = g1.Save();
g2.Restore(state1);
g1.Restore(state1); // the same state used second time

I am personally experience no issues with both techniques in my code but I want to be sure that they are safe / allowed.


Solution

  • I'm very skeptical that this is guaranteed to be safe. As the Graphics.Restore() documentation explains, when you call Save(), the current state is pushed onto a stack (not the stack…just some stack), and then when you call Restore(), the state is removed from the stack and applied to the Graphics instance.

    It's doubtful that two Graphics instances share the same stack. It's just as doubtful that it's a good idea to be restoring the state of one Graphics instance from the stack of another Graphics instance.

    The documentation makes pretty clear that the expected use is for you to balance Save() and Restore() calls, and for them to be applied on the same Graphics instance.


    EDIT:

    For completeness, I went ahead and looked more closely, and indeed while the program may or may not fail right away, it's definitely not the correct usage to restore state that was saved from a different Graphics instance:

    • In the managed code, the "stack" which is referenced by the documentation is in fact implemented as a linked list. When you call Restore(), after applying the state object being restored, the code searches the stack for that object; upon finding it, it disposes that object, along with all other state objects that had been saved after that one, but which hadn't yet been restored.

    If it fails to find the corresponding object, it just emits a Debug.Fail() message. So that won't crash your code, but it's clear it's not desirable behavior.

    • To actually restore the state, the managed code calls the unmanaged GdipRestoreGraphics() function. From the documentation for the approved OOP wrapper for that call (the GDI+ Graphics.Restore() function), reference to the 32-bit integer "state" parameter: "(returned by a previous call to the Graphics::Save method of this Graphics object)" [emphasis mine].

    That certainly makes the intent more clear. Also, that documentation page goes on to elaborate on the state stack being maintained and how it's managed when you restore state. The GDI+ stack is similar to the managed stack, implemented the same way. And like the managed stack, if you restore state not for that Graphics instance, it will fail to find the saved state in the stack.