Render Window Screenshot Capture with svg elements (e.g. LabelWidget)

Hi vtk community and maintainers,

I would like to ask for help with regard to a screenshot capture for a render window with svg inline elements. For example, let’s say for vtk.js, I would like to screenshot both the background + the text labels.

I have tried merging the data URIs for both the values obtained from renderWindow.captureImages() and encoding the svg element that is created with the text. However, that doesn’t seem to work (might be that I’m doing it wrongly). Is there a more native solution to this? Is there a way to include the svg text elements in the canvas/renderWindow’s captureImages func?

Thank you!

1 Like

You can serialize your SVG element and draw it on top of the screenshot capture result. For one possible implementation, check out this post.

Hey, thanks! That really helped. I got it to work. Will post an example of my code soon that will work for any renderWindow with access to captureImages

// Note: This code is written in TypeScript + React, but you can use any other framework of choice.
// Note 2: Doing this way will unset styles from the inline svg element. You'll have to set them manually if you want to change the font-family for e.g.

// Let's pretend there's a <button onClick={takeScreenshot} >Screenshot</button>

function takeScreenshot(): void {
    // My example's render window has an openGLRenderWindow as view
    const renderWindow = view.renderWindow;
    // Get reference to hidden anchor element for download by user (using useRef in React here)
    const linkElement = screenshotLink.current;

    // Guard check
    if (!renderWindow || !linkElement) {
      return;
    }

    // Get the svg inline element encased within the container of your renderWindow.
    // It might be better to do widgetManager.getReferenceByName("svgRoot");
    const svgElement = viewDiv.current?.getElementsByTagName('svg')[0];

    // Define the images. We are going to write the render window image first to a proxy/fake canvas, before writing the svg to a combined img.
    const renderImg = new Image();
    const svgImg = new Image();
    const proxyCanvas = document.createElement('canvas');

    // Add event on load for svgImg to combine the images
    svgImg.addEventListener('load', () => {
      const ctx = proxyCanvas.getContext('2d');
      if (!ctx) {
        return;
      }
      // Draw the svg on top of our render image
      ctx.drawImage(svgImg, 0, 0);
      const imageType = `image/png`;

      // Add the data url to a href and simulate click for user to save the image
      linkElement.href = proxyCanvas.toDataURL(imageType);
      linkElement.download = 'screenshot.png';
      linkElement.click();
    });

    // Add event on load for initial render image
    renderImg.addEventListener('load', () => {
      const ctx = proxyCanvas.getContext('2d');
      if (!ctx) {
        return;
      }
      ctx.clearRect(0, 0, proxyCanvas.width, proxyCanvas.height);
      proxyCanvas.width = renderImg.width;
      proxyCanvas.height = renderImg.height;
      ctx.drawImage(renderImg, 0, 0);

      // This below checks if there is a svg element to be written as an image. We let it be an empty string if there isn't one, so it'll be a normal screenshot.
      let svgSrc = '';
      if (svgElement) {
        svgElement.setAttribute('width', renderImg.width.toString());
        svgElement.setAttribute('height', renderImg.height.toString());
        const svgSerialized = new XMLSerializer().serializeToString(svgElement);
        svgSrc = `data:image/svg+xml;base64,${Buffer.from(svgSerialized).toString('base64')}`;
      }
      // This triggers the svg image load function
      svgImg.src = svgSrc;
    });

    // We apply renderWindow.captureImages() here and trigger the render image load function by appending the value to the render image source
    const images = renderWindow.captureImages();
    images[0].then((value: string) => {
      renderImg.src = value;
    });
}

2 Likes