glance icon indicating copy to clipboard operation
glance copied to clipboard

Include Measurements in screenshots

Open thomas-beznik opened this issue 4 years ago • 7 comments

Hello everyone,

It would be an interesting feature for the screenshots to also include measurements; any lead on what needs to be done in order to implement this? Thank you!

thomas-beznik avatar Jul 24 '20 12:07 thomas-beznik

That would be quite useful! Since measurements exist in a separate SVG layer on top of the canvas, we (1) need to convert the SVG into an image (I think the SVG is accessible via view.getReferenceByName('widgetManager').getReferenceByName('svgRoot')), and then use a hidden canvas to draw the SVG on top of the screenshot image. I haven't researched the details of how to do this, but if you would like to try implementing it I'd be happy to provide feedback.

floryst avatar Jul 24 '20 13:07 floryst

Thank you for this explanation! I will experiment with this and come back to you.

thomas-beznik avatar Jul 24 '20 13:07 thomas-beznik

I was able to include the measurements in the screenshot by changing the generateImage() function from the ScreenshotDialog to this:

function generateImage() {
  const img = new Image();
  const measurementImg = new Image();

  const measurementSvg = this.$proxyManager
    .getActiveView()
    .getReferenceByName('widgetManager')
    .getReferenceByName('svgRoot');

  measurementImg.addEventListener('load', () => {
    const ctx = this.canvas.getContext('2d');
    ctx.drawImage(measurementImg, 0, 0);

    const imageType = `image/${this.fileType.substr(1)}`;
    this.imageUrl = this.canvas.toDataURL(imageType);
  });

  img.addEventListener('load', () => {
    const ctx = this.canvas.getContext('2d');
    ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);

    this.canvas.width = img.width;
    this.canvas.height = img.height;

    if (!this.transparentBackground) {
      ctx.fillStyle = this.backgroundToFillStyle(
        this.screenshot.viewData.background
      );
      ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
    }

    ctx.drawImage(img, 0, 0);

    // Add the measurements

    // Set same height and width as image
    measurementSvg.setAttribute('width', img.width);
    measurementSvg.setAttribute('height', img.height);

    const measurementSvgSerialized = new XMLSerializer()
      .serializeToString(measurementSvg)
      .replace('°', ' deg'); // ! This is the fix to avoid an Encoding error with btoa
    // TODO: find nicer fix

    const measurementSvgSrc = `data:image/svg+xml;base64,${btoa(
      measurementSvgSerialized
    )}`; // make it ready to be read as a source in an image
    measurementImg.src = measurementSvgSrc; // then draw the measurement
  });

  img.src = this.screenshot.imgSrc; // 1st draw the data and then the measurements
}

...But it doesn't work with angles, because of the '°' symbol; it then gives an encoding error, I am guessing because of btoa. Any idea on how this could be worked around? For now, I just changed the '°' symbol to 'deg'.

thomas-beznik avatar Jul 28 '20 16:07 thomas-beznik

Running btoa('°') works for me. But as a broader point, check out the Unicode section of the btoa docs. The general idea is that JS strings are UTF-16, and so chars that can't fit in 1 byte can't be managed by btoa, since btoa operates on a byte-by-byte basis. Though the "°" char in your post is a single byte, so I wonder what else is happening there.

floryst avatar Jul 29 '20 18:07 floryst

Ah strange! I get this error when opening the src data:image/svg+xml;base64,${btoa(measurementSvgSerialized)} in another window.

thomas-beznik avatar Jul 31 '20 08:07 thomas-beznik

I was able to solve it by using a UTF-8 encoding:

const measurementSvgSrc = `data:image/svg+xml;utf8,${encodeURIComponent(
      new XMLSerializer().serializeToString(measurementSvg)
    )}`; // make it ready to be read as a source in an image

There's still one thing that I would like to do: I want the screenshot to be cropped around the image, rather than having it inside of a background. Do you have an idea of how I could do this? I also wonder where I can find the coordinates of the image, which I could then use to crop it. Thanks!

thomas-beznik avatar Aug 03 '20 13:08 thomas-beznik

What you are proposing will be more difficult, since a scene could have much more than just an image. But if you only constrain yourself to an image, you would need to get the 4 coordinates of your image corners and use vtkPixelSpaceCallbackMapper to get the display coordinates for the bounding box of your image.

floryst avatar Aug 05 '20 14:08 floryst