Capturing Snapshots

Snapshots are great for capturing features of a simulation for detailed discussions, inclusion in homework problems or a book, or even sharing on social media.

WebGPU is an evolving framework, and currently image capture of a canvas only works while the animation is active.

Snapshot at time ${time}.
Now we can capture images of our simulations.

Adding snapshots is a great opportunity to foray into custom elements1. This implementation uses the CanvasSnapshooter class to implement the snapshot process. This class listens for custom events, and when an appropriate event is received, it generates a snapshot of the provided canvas. The custom element places a button to generate these events. The loose coupling between the snapshooter and the button means the button can be placed anywhere on the page.

The CanvasSnapshooter constructor takes three arguments.

snapShotContainer
The parent HTML element for the snapshots. Snapshots are positioned within this element.
The Canvas
The canvas element we capture an image from via toDataURL.
id
The identifier for this particular canvas snapshooter instance. We listen for events with this target to trigger a snapshot.

  const snapShotContainer = document.getElementById("snapshots")
  const snapshooter = new vizit.lesson.CanvasSnapshooter(snapShotContainer, schrodingerRenderer.getCanvas(),
            "schrodingerResults");
            

The canvas-snapshooter element defines a button to trigger the snapshot. The companion snapshot-caption element defines the caption for that snapshot. Snapshots are absolutely positioned within the snapShotContainer. They can also be minimized, and dragged to position them anywhere on the page.

We see how it all comes together in the figure at the top of the page. The schrodingerResults canvas is the display target for the simulation, and we pass that same canvas to the CanvasSnapshooter constructor, and even pass the same string, "schrodingerResults" as the id for the CanvasSnapshooter. We could use any string here, as long as the canvas-snapshooter target matches the CanvasSnapshooter id. I just wanted an id that reflected what we were capturing.


  <figure class="center" id="snapshots">
      <canvas width="800" height="100" id="schrodingerResults">
      </canvas>
      <div class="buttonContainer">
          <button id="start">▶<br/>Play</button>
          <button id="stop">■<br/>Stop</button>
          <button data-bc="true" class="launcher">↺<br/>Restart</button>
          <canvas-snapshooter target="schrodingerResults">
              <snapshot-caption>Snapshot at time ${time}.</snapshot-caption>
          </canvas-snapshooter>
      </div>

      <figcaption>
          Now we can capture images of our simulations.
      </figcaption>
  </figure>
      
            

The caption is evaluated as a JavaScript template when each snapshot is generated. In this case we have a global variable, time, on the page so ${time} is replaced with the time value for each snapshot. The caption identifies how far into the simulation the image was captured.

A snapshot showing partial reflection.

We can clean this up a bit. Looking at the code, dt = 5.0E-07, we see that there are at most 6 decimal places of significance. Figures beyond this are simply numerical noise from the imprecise representation of floating point numbers.

JavaScript templates let us embed any JavaScript expression, so we can use ${time.toFixed(6)} to finesse the displayed time value.


      <snapshot-caption>Snapshot at time ${time.toFixed(6)}.</snapshot-caption>
            
A little finesse make a noticeable difference.

Buttons are losely coupled to the CanvasSnapshooter through JavaScript events, so we can place them anywhere on the page.

Snapshot at time ${time.toFixed(6)}.
The button is loosely coupled to the rendering, we can put it anywhere.
  1. Custom elements have been around for a while, but now they are broadly supported.
Creative Commons License
This work is licensed under a Creative Commons Attribution 4.0 International License.