UNPKG

@cornerstonejs/core

Version:
295 lines (294 loc) • 13.4 kB
import BaseRenderingEngine, { VIEWPORT_MIN_SIZE } from './BaseRenderingEngine.js'; import Events from '../enums/Events.js'; import eventTarget from '../eventTarget.js'; import triggerEvent from '../utilities/triggerEvent.js'; import viewportTypeUsesCustomRenderingPipeline from './helpers/viewportTypeUsesCustomRenderingPipeline.js'; import getOrCreateCanvas from './helpers/getOrCreateCanvas.js'; import { vtkOffscreenMultiRenderWindow } from './vtkClasses/index.js'; import { attachWebGLContextEvents } from './helpers/attachWebGLContextEvents.js'; import { setElementTouchActionNone } from './helpers/elementTouchAction.js'; import { getViewportClassForInput } from './helpers/viewportTypeToViewportClass.js'; class TiledRenderingEngine extends BaseRenderingEngine { constructor(id) { super(id); this._renderFlaggedViewports = () => { this._throwIfDestroyed(); if (this.offscreenMultiRenderWindow) { this.performVtkDrawCall(); } const viewports = this._getViewportsAsArray(); const eventDetailArray = []; for (let i = 0; i < viewports.length; i++) { const viewport = viewports[i]; if (this._needsRender.has(viewport.id)) { const eventDetail = this.renderViewportUsingCustomOrVtkPipeline(viewport); eventDetailArray.push(eventDetail); viewport.setRendered(); this._needsRender.delete(viewport.id); if (this._needsRender.size === 0) { break; } } } this._animationFrameSet = false; this._animationFrameHandle = null; eventDetailArray.forEach((eventDetail) => { if (!eventDetail?.element) { return; } triggerEvent(eventDetail.element, Events.IMAGE_RENDERED, eventDetail); }); }; if (!this.useCPURendering) { this.ensureOffscreenResources(); } } ensureOffscreenResources() { if (!this.offscreenMultiRenderWindow) { this.offscreenMultiRenderWindow = vtkOffscreenMultiRenderWindow.newInstance(); this.offScreenCanvasContainer = document.createElement('div'); this.offscreenMultiRenderWindow.setContainer(this.offScreenCanvasContainer); attachWebGLContextEvents(this.offscreenMultiRenderWindow, this.id); } } enableVTKjsDrivenViewport(viewportInputEntry) { this.ensureOffscreenResources(); const viewports = this._getViewportsAsArray(); const viewportsDrivenByVtkJs = viewports.filter((vp) => viewportTypeUsesCustomRenderingPipeline(vp.type) === false); const canvasesDrivenByVtkJs = viewportsDrivenByVtkJs.map((vp) => vp.canvas); const canvas = getOrCreateCanvas(viewportInputEntry.element); canvasesDrivenByVtkJs.push(canvas); const { offScreenCanvasWidth, offScreenCanvasHeight } = this._resizeOffScreenCanvas(canvasesDrivenByVtkJs); const xOffset = this._resize(viewportsDrivenByVtkJs, offScreenCanvasWidth, offScreenCanvasHeight); const internalViewportEntry = { ...viewportInputEntry, canvas }; this.addVtkjsDrivenViewport(internalViewportEntry, { offScreenCanvasWidth, offScreenCanvasHeight, xOffset, }); } addVtkjsDrivenViewport(viewportInputEntry, offscreenCanvasProperties) { this.ensureOffscreenResources(); const { element, canvas, viewportId, type, defaultOptions } = viewportInputEntry; element.tabIndex = -1; setElementTouchActionNone(element); const { offScreenCanvasWidth, offScreenCanvasHeight, xOffset } = offscreenCanvasProperties; const { sxStartDisplayCoords, syStartDisplayCoords, sxEndDisplayCoords, syEndDisplayCoords, sx, sy, sWidth, sHeight, } = this._getViewportCoordsOnOffScreenCanvas(viewportInputEntry, offScreenCanvasWidth, offScreenCanvasHeight, xOffset); this.offscreenMultiRenderWindow.addRenderer({ viewport: [ sxStartDisplayCoords, syStartDisplayCoords, sxEndDisplayCoords, syEndDisplayCoords, ], id: viewportId, background: defaultOptions.background ? defaultOptions.background : [0, 0, 0], }); const viewportInput = { id: viewportId, element, renderingEngineId: this.id, type, canvas, sx, sy, sWidth, sHeight, defaultOptions: defaultOptions || {}, }; const ViewportClass = getViewportClassForInput(viewportInputEntry); if (!ViewportClass) { throw new Error(`Viewport Type ${type} is not supported`); } const viewport = new ViewportClass(viewportInput); viewport.requestedType = viewportInputEntry.requestedType ?? type; this._viewports.set(viewportId, viewport); const eventDetail = { element, viewportId, renderingEngineId: this.id, }; if (!viewport.suppressEvents) { triggerEvent(eventTarget, Events.ELEMENT_ENABLED, eventDetail); } } setVtkjsDrivenViewports(viewportInputEntries) { if (viewportInputEntries.length) { const vtkDrivenCanvases = viewportInputEntries.map((vp) => getOrCreateCanvas(vp.element)); vtkDrivenCanvases.forEach((canvas) => { const devicePixelRatio = window.devicePixelRatio || 1; const rect = canvas.getBoundingClientRect(); canvas.width = rect.width * devicePixelRatio; canvas.height = rect.height * devicePixelRatio; }); const { offScreenCanvasWidth, offScreenCanvasHeight } = this._resizeOffScreenCanvas(vtkDrivenCanvases); let xOffset = 0; for (let i = 0; i < viewportInputEntries.length; i++) { const vtkDrivenViewportInputEntry = viewportInputEntries[i]; const canvas = vtkDrivenCanvases[i]; const internalViewportEntry = { ...vtkDrivenViewportInputEntry, canvas, }; this.addVtkjsDrivenViewport(internalViewportEntry, { offScreenCanvasWidth, offScreenCanvasHeight, xOffset, }); xOffset += canvas.width; } } } _resizeVTKViewports(vtkDrivenViewports, keepCamera = true, immediate = true) { const canvasesDrivenByVtkJs = vtkDrivenViewports.map((vp) => { return getOrCreateCanvas(vp.element); }); canvasesDrivenByVtkJs.forEach((canvas) => { const devicePixelRatio = window.devicePixelRatio || 1; canvas.width = canvas.clientWidth * devicePixelRatio; canvas.height = canvas.clientHeight * devicePixelRatio; }); if (canvasesDrivenByVtkJs.length) { const { offScreenCanvasWidth, offScreenCanvasHeight } = this._resizeOffScreenCanvas(canvasesDrivenByVtkJs); this._resize(vtkDrivenViewports, offScreenCanvasWidth, offScreenCanvasHeight); } vtkDrivenViewports.forEach((vp) => { const prevCamera = vp.getCamera(); const rotation = vp.getRotation(); const { flipHorizontal } = prevCamera; const resizeResetViewport = vp; if (resizeResetViewport.resetViewStateForResize) { resizeResetViewport.resetViewStateForResize(); } else { resizeResetViewport.resetCameraForResize(); } const displayArea = vp.getDisplayArea(); if (keepCamera) { if (displayArea) { if (flipHorizontal) { vp.setCamera({ flipHorizontal }); } if (rotation) { vp.setViewPresentation({ rotation }); } } else { vp.setCamera(prevCamera); } } }); if (immediate) { this.render(); } } performVtkDrawCall() { const { offscreenMultiRenderWindow } = this; const renderWindow = offscreenMultiRenderWindow.getRenderWindow(); const renderers = offscreenMultiRenderWindow.getRenderers(); if (!renderers.length) { return; } for (let i = 0; i < renderers.length; i++) { const { renderer, id } = renderers[i]; const viewportId = id.split('::')[0]; if (this._needsRender.has(viewportId)) { renderer.setDraw(true); } else { renderer.setDraw(false); } } renderWindow.render(); for (let i = 0; i < renderers.length; i++) { renderers[i].renderer.setDraw(false); } } renderViewportUsingCustomOrVtkPipeline(viewport) { let eventDetail; if (viewport.sWidth < VIEWPORT_MIN_SIZE || viewport.sHeight < VIEWPORT_MIN_SIZE) { console.warn('Viewport is too small', viewport.sWidth, viewport.sHeight); return; } if (viewportTypeUsesCustomRenderingPipeline(viewport.type) === true) { eventDetail = viewport.customRenderViewportToCanvas(); } else { if (!this.offscreenMultiRenderWindow) { throw new Error('GPU not available, and using a viewport with no custom render pipeline.'); } const { offscreenMultiRenderWindow } = this; const openGLRenderWindow = offscreenMultiRenderWindow.getOpenGLRenderWindow(); const context = openGLRenderWindow.get3DContext(); const offScreenCanvas = context.canvas; eventDetail = this._renderViewportFromVtkCanvasToOnscreenCanvas(viewport, offScreenCanvas); } return eventDetail; } _renderViewportFromVtkCanvasToOnscreenCanvas(viewport, offScreenCanvas) { const { element, canvas, sx, sy, sWidth, sHeight, id: viewportId, renderingEngineId, suppressEvents, } = viewport; const { width: dWidth, height: dHeight } = canvas; const onScreenContext = canvas.getContext('2d'); onScreenContext.drawImage(offScreenCanvas, sx, sy, sWidth, sHeight, 0, 0, dWidth, dHeight); return { element, suppressEvents, viewportId, renderingEngineId, viewportStatus: viewport.viewportStatus, }; } _resizeOffScreenCanvas(canvasesDrivenByVtkJs) { const { offScreenCanvasContainer, offscreenMultiRenderWindow } = this; const offScreenCanvasHeight = Math.max(...canvasesDrivenByVtkJs.map((canvas) => canvas.height)); let offScreenCanvasWidth = 0; canvasesDrivenByVtkJs.forEach((canvas) => { offScreenCanvasWidth += canvas.width; }); offScreenCanvasContainer.width = offScreenCanvasWidth; offScreenCanvasContainer.height = offScreenCanvasHeight; offscreenMultiRenderWindow.resize(); return { offScreenCanvasWidth, offScreenCanvasHeight }; } _resize(viewportsDrivenByVtkJs, offScreenCanvasWidth, offScreenCanvasHeight) { let _xOffset = 0; for (let i = 0; i < viewportsDrivenByVtkJs.length; i++) { const viewport = viewportsDrivenByVtkJs[i]; const { sxStartDisplayCoords, syStartDisplayCoords, sxEndDisplayCoords, syEndDisplayCoords, sx, sy, sWidth, sHeight, } = this._getViewportCoordsOnOffScreenCanvas(viewport, offScreenCanvasWidth, offScreenCanvasHeight, _xOffset); _xOffset += viewport.canvas.width; viewport.sx = sx; viewport.sy = sy; viewport.sWidth = sWidth; viewport.sHeight = sHeight; const renderer = this.offscreenMultiRenderWindow.getRenderer(viewport.id); renderer.setViewport(sxStartDisplayCoords, syStartDisplayCoords, sxEndDisplayCoords, syEndDisplayCoords); } return _xOffset; } _getViewportCoordsOnOffScreenCanvas(viewport, offScreenCanvasWidth, offScreenCanvasHeight, _xOffset) { const { canvas } = viewport; const { width: sWidth, height: sHeight } = canvas; const sx = _xOffset; const sy = 0; const sxStartDisplayCoords = sx / offScreenCanvasWidth; const syStartDisplayCoords = sy + (offScreenCanvasHeight - sHeight) / offScreenCanvasHeight; const sWidthDisplayCoords = sWidth / offScreenCanvasWidth; const sHeightDisplayCoords = sHeight / offScreenCanvasHeight; return { sxStartDisplayCoords, syStartDisplayCoords, sxEndDisplayCoords: sxStartDisplayCoords + sWidthDisplayCoords, syEndDisplayCoords: syStartDisplayCoords + sHeightDisplayCoords, sx, sy, sWidth, sHeight, }; } } export default TiledRenderingEngine;