function getTransformedImageData(
  img,
  naturalWidth,
  naturalHeight,
  imageDisplayedInBrowserWidth,
  imageDisplayedInBrowserHeight,
  viewportWidth,
  viewportHeight,
  zoomScale,
  zoomingPositionX,
  zoomingPositionY,
  negativezoom,
) {
  // If negative zoom is on, the image as displayed in the browser could actually be
  // _smaller_ than the viewport. Get the minimum size between these when creating
  // our ultimate canvas.
  let canvasWidth;
  let canvasHeight;

  if (negativezoom) {
    canvasWidth = Math.min(viewportWidth, imageDisplayedInBrowserWidth);
    canvasHeight = Math.min(viewportHeight, imageDisplayedInBrowserHeight);
  } else {
    canvasWidth = viewportWidth;
    canvasHeight = viewportHeight;
  }

  const canvas = document.createElement("canvas");

  canvas.width = canvasWidth;
  canvas.height = canvasHeight;

  const ctx = canvas.getContext("2d");

  const [viewportNaturalX, viewportNaturalY] = getActualZoomingPosition(
    naturalWidth,
    naturalHeight,
    imageDisplayedInBrowserWidth,
    imageDisplayedInBrowserHeight,
    zoomingPositionX,
    zoomingPositionY,
  );

  // The viewport dimensions are some percentage of the actual size of the image
  // shown in the browser; determine that then calculate the percentage dimension
  // of the viewport in natural coordinate space. If we are negative zooming then
  // the calculations are slightly different.
  let viewportNaturalWidth;
  let viewportNaturalHeight;

  if (negativezoom) {
    viewportNaturalWidth = naturalWidth;
    viewportNaturalHeight = naturalHeight;
  } else {
    viewportNaturalWidth = Math.ceil((viewportWidth / imageDisplayedInBrowserWidth) * naturalWidth);
    viewportNaturalHeight = Math.ceil((viewportHeight / imageDisplayedInBrowserHeight) * naturalHeight);
  }

  // Only draw the part of the image under transformations to the viewport that we will actually
  // use, so we can then efficiently get its pixel data for pixel-level tools.

  // Source dimensions.
  const sx = viewportNaturalX;
  const sy = viewportNaturalY;
  const sWidth = viewportNaturalWidth;
  const sHeight = viewportNaturalHeight;
  // Destination dimensions.
  const dx = 0;
  const dy = 0;
  const dWidth = canvasWidth;
  const dHeight = canvasHeight;

  ctx.drawImage(img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

  // Now grab the transformed pixels from the canvas for the values to actual do Magic Wanding on.
  // If an exception is thrown then CORS cross domain headers are probably not configured
  // correctly.
  let transformedData;

  try {
    transformedData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  } catch (err) {
    const msg = "Please configure CORS cross-domain headers correctly for getting image labeling data";

    alert(msg);
    console.error(msg);
    throw msg;
  }

  return [transformedData, canvas];
}
An image on the stage that is being labelled might be under some CSS transformations,
such as being zoomed in, negatively zoomed out, rotated, etc., while also being shown in a
viewport on top of the image that might cut parts of it off. For operations like the
Magic Wand we need to ultimately get raw pixel data of the image with these transforms applied.

Unfortunately it is impossible to get the raw pixel values exhibiting the actual CSS
transforms for an Image via JavaScript. Instead, we have to take the original untransformed
image and blit it to a Canvas with similar transforms but done through the Canvas API,
then getting the transformed raw pixels.

In addition, doing all of this on large images can burn performance cycles that can
make using tools like the Magic Wand onerous, so we also attempt to only transform & blit
the image to exactly the area currently being shown in the viewport, so that we don't
do wasted work.

We currently support zoomed in, negative zoom, and images being scaled in their viewport.
We do not support rotated images currently with this method.

@param {Image} img DOM Image object to ultimately get raw, transformed pixel values for.
@param {int} naturalWidth The actual size of the Image if it were loaded from disk and shown
 its full, real size.
@param {int} naturalHeight Same, but for the height.
@param imageDisplayedInBrowserWidth {int} When the image is displayed in an actual browser
 it can be shrunken or expanded based on its container and available screen real estate; this
 is that width.
@param imageDisplayedInBrowserHeight {int} Same, but for the height.
@param viewportWidth {int} The width in pixels of where the image is actually being displayed;
 this is different than the imageDisplayedInBrowserWidth as the size of the image might be
 clipped by the edges of the viewport when overflow: hidden is set, like looking through the
 edges of a window clipping a view of the world outside.
@param viewportHeight {int} Same, but for the height.
@param zoomScale {float} 1 if no zooming is happening, >1 if zooming is on, <1 if negatively
 zoomed outwards.
@param zoomingPositionX {float} If zoomed and panned away from the image origin at the upper
 left of the screen, relates negative float coordinates from that corner of the X value,
 where these coordinates are relative to the imageDisplayedInBrowserWidth values.
@param zoomingPositionY {float} Same, but for the height.
@param negativezoom {boolean} True If a template allows negative zooming (i.e. zooming outwards
 beyond the actual size of the image), and if the user is currently actually negative zooming,
 will be true.
@returns {[ImageData, Canvas]} Returns an array with the actual RGBA imagedata of the transformed
 image, as well as a Canvas with the transformed image drawn on it.