All files / packages/core/src/utilities renderToCanvasGPU.ts

93.47% Statements 43/46
52.63% Branches 10/19
100% Functions 5/5
93.47% Lines 43/46

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159                                                                        2x       2x 2x 2x 2x 2x 2x 2x           2x 2x 2x 2x         2x 2x   2x     2x 2x     2x   2x     2x   2x 2x                 2x 2x     2x     2x     2x 2x         2x 2x                       2x     2x             2x 2x     2x     2x 2x     2x     2x 2x     2x   2x                 2x      
import getOrCreateCanvas, {
  EPSILON,
} from '../RenderingEngine/helpers/getOrCreateCanvas';
import { ViewportType, Events } from '../enums';
import StackViewport from '../RenderingEngine/StackViewport';
import { IImage, ViewportInputOptions } from '../types';
import { getRenderingEngine } from '../RenderingEngine/getRenderingEngine';
import RenderingEngine from '../RenderingEngine';
import isPTPrescaledWithSUV from './isPTPrescaledWithSUV';
 
/**
 * Renders an cornerstone image to a Canvas. This method will handle creation
 * of a temporary enabledElement, setting the imageId, and rendering the image via
 * a StackViewport, copying the canvas drawing to the given canvas Element, and
 * disabling the created temporary element. SuppressEvents argument is used to
 * prevent events from firing during the render process (e.g. during a series
 * of renders to a thumbnail image).
 *
 * @example
 * ```
 * const canvas = document.getElementById('myCanvas')
 *
 * renderToCanvasGPU(canvas, image)
 * ```
 * @param canvas - Canvas element to render to
 * @param image - The image to render
 * @param modality - [Default = undefined] The modality of the image
 * @returns - A promise that resolves when the image has been rendered with the imageId
 */
export default function renderToCanvasGPU(
  canvas: HTMLCanvasElement,
  image: IImage,
  modality = undefined,
  renderingEngineId = '_thumbnails',
  viewportOptions: ViewportInputOptions = { displayArea: { imageArea: [1, 1] } }
): Promise<string> {
  Iif (!canvas || !(canvas instanceof HTMLCanvasElement)) {
    throw new Error('canvas element is required');
  }
 
  const imageIdToPrint = image.imageId;
  const viewportId = `renderGPUViewport-${imageIdToPrint}`;
  const imageId = image.imageId;
  const element = document.createElement('div');
  const devicePixelRatio = window.devicePixelRatio || 1;
  const originalWidth = canvas.width;
  const originalHeight = canvas.height;
  // The canvas width/height are set by flooring the CSS size converted
  // into physical pixels, but because these are float values, the conversion
  // isn't exact, and using the exact value sometimes leads to an off by 1
  // in the actual size, so adding EPSILON to the size resolves
  // the problem.
  element.style.width = `${originalWidth + EPSILON}px`;
  element.style.height = `${originalHeight + EPSILON}px`;
  element.style.visibility = 'hidden';
  element.style.position = 'absolute';
 
  // Up-sampling the provided canvas to match the device pixel ratio
  // since we use device pixel ratio to determine the size of the canvas
  // inside the rendering engine.
  canvas.width = originalWidth * devicePixelRatio;
  canvas.height = originalHeight * devicePixelRatio;
 
  document.body.appendChild(element);
 
  // add id to the element so we can find it later, and fix the : which is not allowed in css
  const uniqueId = viewportId.split(':').join('-');
  element.setAttribute('viewport-id-for-remove', uniqueId);
 
  // get the canvas element that is the child of the div
  const temporaryCanvas = getOrCreateCanvas(element);
  const renderingEngine =
    (getRenderingEngine(renderingEngineId) as RenderingEngine) ||
    new RenderingEngine(renderingEngineId);
 
  let viewport = renderingEngine.getViewport(viewportId) as StackViewport;
 
  Eif (!viewport) {
    const stackViewportInput = {
      viewportId,
      type: ViewportType.STACK,
      element,
      defaultOptions: {
        ...viewportOptions,
        suppressEvents: true,
      },
    };
    renderingEngine.enableElement(stackViewportInput);
    viewport = renderingEngine.getViewport(viewportId) as StackViewport;
  }
 
  return new Promise((resolve) => {
    // Creating a temporary HTML element so that we can
    // enable it and later disable it without losing the canvas context
    let elementRendered = false;
 
    // Create a named function to handle the event
    const onImageRendered = (eventDetail) => {
      Iif (elementRendered) {
        return;
      }
 
      // Copy the temporary canvas to the given canvas
      const context = canvas.getContext('2d');
      context.drawImage(
        temporaryCanvas,
        0,
        0,
        temporaryCanvas.width,
        temporaryCanvas.height, // source dimensions
        0,
        0,
        canvas.width,
        canvas.height // destination dimensions
      );
 
      elementRendered = true;
 
      // remove based on id
      element.removeEventListener(Events.IMAGE_RENDERED, onImageRendered);
 
      // Ensure pending previous resize calls are done which might have been
      // triggered by the same disableElement call. This is to avoid potential
      // grab of the wrong canvas coordinate from the offscreen renderer since
      // disable might have not finished resizing yet and it will cause weird
      // copy to on screen from an incorrect location in the offscreen renderer.
      setTimeout(() => {
        renderingEngine.disableElement(viewportId);
 
        // remove all the elements that has the same id
        const elements = document.querySelectorAll(
          `[viewport-id-for-remove="${uniqueId}"]`
        );
        elements.forEach((element) => {
          element.remove();
        });
      }, 0);
      resolve(imageId);
    };
 
    element.addEventListener(Events.IMAGE_RENDERED, onImageRendered);
    viewport.renderImageObject(image);
 
    // force a reset camera to center the image and undo the small scaling
    viewport.resetCamera();
 
    Iif (modality === 'PT' && !isPTPrescaledWithSUV(image)) {
      viewport.setProperties({
        voiRange: {
          lower: image.minPixelValue,
          upper: image.maxPixelValue,
        },
      });
    }
 
    viewport.render();
  });
}