Skip to content

Commit

Permalink
feat: Add renderToCanvas functionality (#279)
Browse files Browse the repository at this point in the history
* feat: Add internal div that is the parent element for canvas and SVG layer

* feat: Add initial react-cornerstone-viewport

* fix: Tests now work with div elements instead of canvas

* feat: Enable using a div instead of a canvas for viewports

* feat: Renamed createCanvas and removed unnecessary usage in tests

* fix: Demos now work with div element instead of canvas

* feat: Add the imageLoadPool and imageRetrieval managers

* feat: Add renderToCanvas functionality

* feat: Add async handling to stackViewport

* fix: enableElement should not fire render on RenderingEngine

* fix: compile error
sedghi authored and swederik committed Mar 22, 2022

Verified

This commit was signed with the committer’s verified signature.
truthbk Jaime Fullaondo
1 parent a854cf4 commit dd2bfc8
Showing 12 changed files with 301 additions and 25 deletions.
16 changes: 10 additions & 6 deletions packages/cornerstone-render/src/RenderingEngine/RenderingEngine.ts
Original file line number Diff line number Diff line change
@@ -159,9 +159,6 @@ class RenderingEngine implements IRenderingEngine {
// 3 Add the requested viewport to rendering Engine
this.addCustomViewport(viewportInputEntry)
}

// 5. Add the new viewport to the queue to be rendered
this._setViewportsToBeRenderedNextFrame([viewportInputEntry.viewportUID])
}

/**
@@ -733,7 +730,9 @@ class RenderingEngine implements IRenderingEngine {
renderingEngineUID: this.uid,
}

triggerEvent(eventTarget, EVENTS.ELEMENT_ENABLED, eventData)
if (!viewport.suppressEvents) {
triggerEvent(eventTarget, EVENTS.ELEMENT_ENABLED, eventData)
}
}

/**
@@ -1164,6 +1163,7 @@ class RenderingEngine implements IRenderingEngine {
viewportUID: string
sceneUID: string
renderingEngineUID: string
suppressEvents: boolean
} {
const {
element,
@@ -1175,6 +1175,7 @@ class RenderingEngine implements IRenderingEngine {
uid,
sceneUID,
renderingEngineUID,
suppressEvents,
} = viewport

const { width: dWidth, height: dHeight } = canvas
@@ -1197,6 +1198,7 @@ class RenderingEngine implements IRenderingEngine {
element,
canvas,
viewportUID: uid,
suppressEvents,
sceneUID,
renderingEngineUID,
}
@@ -1212,7 +1214,7 @@ class RenderingEngine implements IRenderingEngine {
private _resetViewport(viewport) {
const renderingEngineUID = this.uid

const { element, canvas, uid: viewportUID } = viewport
const { element, canvas, uid: viewportUID, suppressEvents } = viewport

const eventData = {
element,
@@ -1223,7 +1225,9 @@ class RenderingEngine implements IRenderingEngine {

// Trigger first before removing the data attributes, as we need the enabled
// element to remove tools associated with the viewport
triggerEvent(eventTarget, EVENTS.ELEMENT_DISABLED, eventData)
if (!suppressEvents) {
triggerEvent(eventTarget, EVENTS.ELEMENT_DISABLED, eventData)
}

element.removeAttribute('data-viewport-uid')
element.removeAttribute('data-scene-uid')
39 changes: 26 additions & 13 deletions packages/cornerstone-render/src/RenderingEngine/StackViewport.ts
Original file line number Diff line number Diff line change
@@ -1058,7 +1058,13 @@ class StackViewport extends Viewport {
delete this._cpuFallbackEnabledElement.viewport.colormap
}

// We draw over the previous stack with the background color while we
// wait for the next stack to load
ctx.fillStyle = fillStyle
ctx.fillRect(0, 0, canvas.width, canvas.height)

const imageId = await this._setImageIdIndex(currentImageIdIndex)

return imageId
}

@@ -1250,7 +1256,10 @@ class StackViewport extends Viewport {
imageId,
}

triggerEvent(eventTarget, ERROR_CODES.IMAGE_LOAD_ERROR, eventData)
if (!this.suppressEvents) {
triggerEvent(eventTarget, ERROR_CODES.IMAGE_LOAD_ERROR, eventData)
}

reject(error)
}

@@ -1283,7 +1292,7 @@ class StackViewport extends Viewport {
const type = 'Float32Array'

const priority = -5
const requestType = 'interaction'
const requestType = REQUEST_TYPE.Interaction
const additionalDetails = { imageId }
const options = {
targetBuffer: {
@@ -1296,7 +1305,7 @@ class StackViewport extends Viewport {
},
}

requestPoolManager.addRequest(
imageLoadPoolManager.addRequest(
sendRequest.bind(this, imageId, imageIdIndex, options),
requestType,
additionalDetails,
@@ -1531,13 +1540,11 @@ class StackViewport extends Viewport {
// Update the state of the viewport to the new imageIdIndex;
this.currentImageIdIndex = imageIdIndex

// Get the imageId from the stack
const imageId = this.imageIds[imageIdIndex]

// Todo: trigger an event to allow applications to hook into START of loading state
// Currently we use loadHandlerManagers for this

await this._loadImage(imageId, imageIdIndex)
const imageId = this._loadImage(this.imageIds[imageIdIndex], imageIdIndex)

return imageId
}

@@ -1574,14 +1581,16 @@ class StackViewport extends Viewport {
* @param imageIdIndex number represents imageId index in the list of
* provided imageIds in setStack
*/
public setImageIdIndex(imageIdIndex: number): void {
public async setImageIdIndex(imageIdIndex: number): Promise<string> {
// If we are already on this imageId index, stop here
if (this.currentImageIdIndex === imageIdIndex) {
return
}

// Otherwise, get the imageId and attempt to display it
this._setImageIdIndex(imageIdIndex)
const imageId = this._setImageIdIndex(imageIdIndex)

return imageId
}

/**
@@ -1657,8 +1666,10 @@ class StackViewport extends Viewport {
renderingEngineUID: this.renderingEngineUID,
}

// For crosshairs to adapt to new viewport size
triggerEvent(this.element, EVENTS.CAMERA_MODIFIED, eventDetail)
if (!this.suppressEvents) {
// For crosshairs to adapt to new viewport size
triggerEvent(this.element, EVENTS.CAMERA_MODIFIED, eventDetail)
}
}

private triggerCalibrationEvent() {
@@ -1678,8 +1689,10 @@ class StackViewport extends Viewport {
worldToIndex: imageData.getWorldToIndex(),
}

// Let the tools know the image spacing has been calibrated
triggerEvent(this.element, EVENTS.IMAGE_SPACING_CALIBRATED, eventDetail)
if (!this.suppressEvents) {
// Let the tools know the image spacing has been calibrated
triggerEvent(this.element, EVENTS.IMAGE_SPACING_CALIBRATED, eventDetail)
}

this._publishCalibratedEvent = false
}
9 changes: 7 additions & 2 deletions packages/cornerstone-render/src/RenderingEngine/Viewport.ts
Original file line number Diff line number Diff line change
@@ -36,6 +36,7 @@ class Viewport {
readonly defaultOptions: any
options: ViewportInputOptions
private _suppressCameraModifiedEvents = false
readonly suppressEvents: boolean

constructor(props: ViewportInput) {
this.uid = props.uid
@@ -61,8 +62,12 @@ class Viewport {
}

this.defaultOptions = _cloneDeep(props.defaultOptions)
this.suppressEvents = props.defaultOptions.suppressEvents
? props.defaultOptions.suppressEvents
: false
this.options = _cloneDeep(props.defaultOptions)
}

getFrameOfReferenceUID: () => string
canvasToWorld: (canvasPos: Point2) => Point3
worldToCanvas: (worldPos: Point3) => Point2
@@ -521,7 +526,7 @@ class Viewport {
// and do the right thing.
renderer.invokeEvent(RESET_CAMERA_EVENT)

if (!this._suppressCameraModifiedEvents) {
if (!this._suppressCameraModifiedEvents && !this.suppressEvents) {
const eventDetail = {
previousCamera: previousCamera,
camera: this.getCamera(),
@@ -680,7 +685,7 @@ class Viewport {
vtkCamera.setSlabThickness(slabThickness)
}

if (!this._suppressCameraModifiedEvents) {
if (!this._suppressCameraModifiedEvents && !this.suppressEvents) {
const eventDetail = {
previousCamera,
camera: updatedCamera,
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import createVolumeActor from './createVolumeActor'
import createVolumeMapper from './createVolumeMapper'
import getOrCreateCanvas from './getOrCreateCanvas'
import renderToCanvas from './renderToCanvas'

export { createVolumeActor, createVolumeMapper, getOrCreateCanvas }
export {
createVolumeActor,
createVolumeMapper,
getOrCreateCanvas,
renderToCanvas,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { getRenderingEngine } from '../getRenderingEngine'
import getOrCreateCanvas from './getOrCreateCanvas'
import VIEWPORT_TYPE from '../../constants/viewportType'
import ORIENTATION from '../../constants/orientation'
import StackViewport from '../StackViewport'
import Events from '../../enums/events'

/**
* Renders an imageId to a Canvas Element. This method will handle creation
* of a tempporary enabled element, 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).
* @param {string}imageId - The imageId to render
* @param {HTMLCanvasElement} canvas - Canvas element to render to
* @param {string} renderingEngineUID - The rendering engine UID to use
* @param {boolean} suppressEvents - boolean to suppress events during render
* @returns {Promise} - A promise that resolves when the image has been rendered with the imageId
*/
export default function renderToCanvas(
imageId: string,
canvas: HTMLCanvasElement,
renderingEngineUID: string,
suppressEvents = true
): Promise<string> {
return new Promise((resolve, reject) => {
const renderingEngine = getRenderingEngine(renderingEngineUID)

if (!canvas || !(canvas instanceof HTMLCanvasElement)) {
throw new Error('canvas element is required')
}

if (!renderingEngine) {
throw new Error(
`No rendering engine with UID of ${renderingEngineUID} found`
)
}

if (renderingEngine.hasBeenDestroyed) {
throw new Error(
`Rendering engine with UID of ${renderingEngineUID} has been destroyed`
)
}

// Creating a temporary HTML element so that we can
// enable it and later disable it without loosing the canvas context
const element = document.createElement('div')
element.style.width = `${canvas.width}px`
element.style.height = `${canvas.height}px`

// Todo: we should be able to use the temporary element without appending
// it to the DOM
element.style.visibility = 'hidden'
document.body.appendChild(element)

// Setting the viewportUID to imageId
const viewportUID = imageId

const stackViewportInput = {
viewportUID,
type: VIEWPORT_TYPE.STACK,
element,
defaultOptions: {
orientation: ORIENTATION.AXIAL,
suppressEvents,
},
}

renderingEngine.enableElement(stackViewportInput)
const viewport = renderingEngine.getViewport(viewportUID) as StackViewport

element.addEventListener(Events.IMAGE_RENDERED, () => {
// get the canvas element that is the child of the div
const temporaryCanvas = getOrCreateCanvas(element)

// Copy the temporary canvas to the given canvas
const context = canvas.getContext('2d')

context.drawImage(temporaryCanvas, 0, 0)
renderingEngine.disableElement(viewportUID)
document.body.removeChild(element)
resolve(imageId)
})

viewport.setStack([imageId])
})
}
2 changes: 2 additions & 0 deletions packages/cornerstone-render/src/RenderingEngine/index.ts
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ import {
createVolumeActor,
createVolumeMapper,
getOrCreateCanvas,
renderToCanvas,
} from './helpers'

export {
@@ -12,6 +13,7 @@ export {
createVolumeActor,
createVolumeMapper,
getOrCreateCanvas,
renderToCanvas,
}

export default RenderingEngine
2 changes: 2 additions & 0 deletions packages/cornerstone-render/src/index.ts
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ import {
createVolumeMapper,
getOrCreateCanvas,
} from './RenderingEngine'
import { renderToCanvas } from './RenderingEngine'
import RenderingEngine from './RenderingEngine'
import VolumeViewport from './RenderingEngine/VolumeViewport'
import StackViewport from './RenderingEngine/StackViewport'
@@ -106,6 +107,7 @@ export {
cache,
Cache,
getEnabledElement,
renderToCanvas,
//
eventTarget,
triggerEvent,
4 changes: 2 additions & 2 deletions packages/cornerstone-render/src/types/IViewport.ts
Original file line number Diff line number Diff line change
@@ -44,7 +44,7 @@ interface IViewport {
* This type defines the shape of input, so we can throw when it is incorrect.
*/
type PublicViewportInput = {
element: HTMLDivElement
element: HTMLElement
sceneUID?: string
viewportUID: string
type: string
@@ -71,7 +71,7 @@ type ViewportInput = {
sy: number
sWidth: number
sHeight: number
defaultOptions: any
defaultOptions: ViewportInputOptions
}

export type {
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ import Orientation from './Orientation'
type ViewportInputOptions = {
background?: Array<number>
orientation?: Orientation
suppressEvents: boolean
}

export default ViewportInputOptions
Loading

0 comments on commit dd2bfc8

Please sign in to comment.