@@ -11,6 +11,7 @@ import StackViewport from './StackViewport';
1111import viewportTypeUsesCustomRenderingPipeline from './helpers/viewportTypeUsesCustomRenderingPipeline' ;
1212import getOrCreateCanvas from './helpers/getOrCreateCanvas' ;
1313import { getShouldUseCPURendering , isCornerstoneInitialized } from '../init' ;
14+ import { calculateViewportOffsets } from './helpers/rectanglePacking' ;
1415import type IStackViewport from '../types/IStackViewport' ;
1516import type IVolumeViewport from '../types/IVolumeViewport' ;
1617import viewportTypeToViewportClass from './helpers/viewportTypeToViewportClass' ;
@@ -40,6 +41,10 @@ interface ViewportDisplayCoords {
4041// Rendering engines seem to not like rendering things less than 2 pixels per side
4142const VIEWPORT_MIN_SIZE = 2 ;
4243
44+ // Maximum size of the offscreen canvas
45+ const MAX_WIDTH = 16384 ;
46+ const MAX_HEIGHT = 16384 ;
47+
4348/**
4449 * A RenderingEngine takes care of the full pipeline of creating viewports and rendering
4550 * them on a large offscreen canvas and transmitting this data back to the screen. This allows us
@@ -681,19 +686,43 @@ class RenderingEngine {
681686
682687 // 2.d Re-position previous viewports on the offScreen Canvas based on the new
683688 // offScreen canvas size
684- const xOffset = this . _resize (
689+ this . _resize (
685690 viewportsDrivenByVtkJs as ( IStackViewport | IVolumeViewport ) [ ] ,
686691 offScreenCanvasWidth ,
687692 offScreenCanvasHeight
688693 ) ;
689694
690695 const internalViewportEntry = { ...viewportInputEntry , canvas } ;
691696
697+ // Calculate the position for the new viewport using packing
698+ const allCanvases = [ ...canvasesDrivenByVtkJs ] ;
699+ const viewportInputs = allCanvases . map ( ( c , index ) => ( {
700+ id :
701+ index === allCanvases . length - 1
702+ ? viewportInputEntry . viewportId
703+ : `viewport-${ index } ` ,
704+ canvas : {
705+ width : c . width ,
706+ height : c . height ,
707+ } ,
708+ } ) ) ;
709+
710+ const packedOffsets = calculateViewportOffsets (
711+ viewportInputs ,
712+ MAX_WIDTH ,
713+ MAX_HEIGHT
714+ ) ;
715+
716+ const newViewportOffset = packedOffsets . find (
717+ ( offset ) => offset . id === viewportInputEntry . viewportId
718+ ) || { xOffset : 0 , yOffset : 0 } ;
719+
692720 // 3 Add the requested viewport to rendering Engine
693721 this . addVtkjsDrivenViewport ( internalViewportEntry , {
694722 offScreenCanvasWidth,
695723 offScreenCanvasHeight,
696- xOffset,
724+ xOffset : newViewportOffset . xOffset ,
725+ yOffset : newViewportOffset . yOffset ,
697726 } ) ;
698727 }
699728
@@ -734,6 +763,7 @@ class RenderingEngine {
734763 offScreenCanvasWidth : number ;
735764 offScreenCanvasHeight : number ;
736765 xOffset : number ;
766+ yOffset ?: number ;
737767 }
738768 ) : void {
739769 const { element, canvas, viewportId, type, defaultOptions } =
@@ -742,8 +772,12 @@ class RenderingEngine {
742772 // Make the element not focusable, we use this for modifier keys to work
743773 element . tabIndex = - 1 ;
744774
745- const { offScreenCanvasWidth, offScreenCanvasHeight, xOffset } =
746- offscreenCanvasProperties ;
775+ const {
776+ offScreenCanvasWidth,
777+ offScreenCanvasHeight,
778+ xOffset,
779+ yOffset = 0 ,
780+ } = offscreenCanvasProperties ;
747781
748782 // 1. Calculate the size of location of the viewport on the offScreen canvas
749783 const {
@@ -759,7 +793,8 @@ class RenderingEngine {
759793 viewportInputEntry ,
760794 offScreenCanvasWidth ,
761795 offScreenCanvasHeight ,
762- xOffset
796+ xOffset ,
797+ yOffset
763798 ) ;
764799 // 2. Add a renderer to the offScreenMultiRenderWindow
765800 this . offscreenMultiRenderWindow . addRenderer ( {
@@ -925,8 +960,30 @@ class RenderingEngine {
925960 }*/
926961
927962 // 3. Adding the viewports based on the viewportInputEntry definition to the
928- // rendering engine.
929- let xOffset = 0 ;
963+ // rendering engine using rectangle packing
964+ const viewportInputs = vtkDrivenCanvases . map ( ( canvas , index ) => ( {
965+ id : viewportInputEntries [ index ] . viewportId ,
966+ canvas : {
967+ width : canvas . width ,
968+ height : canvas . height ,
969+ } ,
970+ } ) ) ;
971+
972+ const packedOffsets = calculateViewportOffsets (
973+ viewportInputs ,
974+ MAX_WIDTH ,
975+ MAX_HEIGHT
976+ ) ;
977+
978+ // Create a map for quick lookup
979+ const offsetMap = new Map < string , { xOffset : number ; yOffset : number } > ( ) ;
980+ packedOffsets . forEach ( ( offset ) => {
981+ offsetMap . set ( offset . id , {
982+ xOffset : offset . xOffset ,
983+ yOffset : offset . yOffset ,
984+ } ) ;
985+ } ) ;
986+
930987 for ( let i = 0 ; i < viewportInputEntries . length ; i ++ ) {
931988 const vtkDrivenViewportInputEntry = viewportInputEntries [ i ] ;
932989 const canvas = vtkDrivenCanvases [ i ] ;
@@ -935,15 +992,19 @@ class RenderingEngine {
935992 canvas,
936993 } ;
937994
995+ const offsets = offsetMap . get (
996+ vtkDrivenViewportInputEntry . viewportId
997+ ) || {
998+ xOffset : 0 ,
999+ yOffset : 0 ,
1000+ } ;
1001+
9381002 this . addVtkjsDrivenViewport ( internalViewportEntry , {
9391003 offScreenCanvasWidth,
9401004 offScreenCanvasHeight,
941- xOffset,
1005+ xOffset : offsets . xOffset ,
1006+ yOffset : offsets . yOffset ,
9421007 } ) ;
943-
944- // Incrementing the xOffset which provides the horizontal location of each
945- // viewport on the offScreen canvas
946- xOffset += canvas . width ;
9471008 }
9481009 }
9491010 }
@@ -959,19 +1020,40 @@ class RenderingEngine {
9591020 } {
9601021 const { offScreenCanvasContainer, offscreenMultiRenderWindow } = this ;
9611022
962- // 1. Calculated the height of the offScreen canvas to be the maximum height
963- // between canvases
964- const offScreenCanvasHeight = Math . max (
965- ...canvasesDrivenByVtkJs . map ( ( canvas ) => canvas . height )
1023+ // Use rectangle packing to calculate optimal canvas dimensions
1024+ const viewportInputs = canvasesDrivenByVtkJs . map ( ( canvas , index ) => ( {
1025+ id : `viewport-${ index } ` ,
1026+ canvas : {
1027+ width : canvas . width ,
1028+ height : canvas . height ,
1029+ } ,
1030+ } ) ) ;
1031+
1032+ const packedOffsets = calculateViewportOffsets (
1033+ viewportInputs ,
1034+ MAX_WIDTH ,
1035+ MAX_HEIGHT
9661036 ) ;
9671037
968- // 2. Calculating the width of the offScreen canvas to be the sum of all
1038+ // Calculate total dimensions from packed rectangles
9691039 let offScreenCanvasWidth = 0 ;
1040+ let offScreenCanvasHeight = 0 ;
9701041
971- canvasesDrivenByVtkJs . forEach ( ( canvas ) => {
972- offScreenCanvasWidth += canvas . width ;
1042+ packedOffsets . forEach ( ( offset ) => {
1043+ offScreenCanvasWidth = Math . max (
1044+ offScreenCanvasWidth ,
1045+ offset . xOffset + offset . width
1046+ ) ;
1047+ offScreenCanvasHeight = Math . max (
1048+ offScreenCanvasHeight ,
1049+ offset . yOffset + offset . height
1050+ ) ;
9731051 } ) ;
9741052
1053+ // Ensure minimum dimensions
1054+ offScreenCanvasWidth = Math . max ( offScreenCanvasWidth , 1 ) ;
1055+ offScreenCanvasHeight = Math . max ( offScreenCanvasHeight , 1 ) ;
1056+
9751057 // @ts -expect-error
9761058 offScreenCanvasContainer . width = offScreenCanvasWidth ;
9771059 // @ts -expect-error
@@ -996,12 +1078,36 @@ class RenderingEngine {
9961078 viewportsDrivenByVtkJs : ( IStackViewport | IVolumeViewport ) [ ] ,
9971079 offScreenCanvasWidth : number ,
9981080 offScreenCanvasHeight : number
999- ) : number {
1000- // Redefine viewport properties
1001- let _xOffset = 0 ;
1081+ ) : void {
1082+ // Use rectangle packing to calculate viewport positions
1083+ const viewportInputs = viewportsDrivenByVtkJs . map ( ( viewport ) => ( {
1084+ id : viewport . id ,
1085+ canvas : {
1086+ width : viewport . canvas . width ,
1087+ height : viewport . canvas . height ,
1088+ } ,
1089+ } ) ) ;
1090+
1091+ const packedOffsets = calculateViewportOffsets (
1092+ viewportInputs ,
1093+ MAX_WIDTH ,
1094+ MAX_HEIGHT
1095+ ) ;
1096+
1097+ // Create a map for quick lookup
1098+ const offsetMap = new Map < string , { xOffset : number ; yOffset : number } > ( ) ;
1099+ packedOffsets . forEach ( ( offset ) => {
1100+ offsetMap . set ( offset . id , {
1101+ xOffset : offset . xOffset ,
1102+ yOffset : offset . yOffset ,
1103+ } ) ;
1104+ } ) ;
10021105
1106+ // Update viewport positions using packed offsets
10031107 for ( let i = 0 ; i < viewportsDrivenByVtkJs . length ; i ++ ) {
10041108 const viewport = viewportsDrivenByVtkJs [ i ] ;
1109+ const offsets = offsetMap . get ( viewport . id ) || { xOffset : 0 , yOffset : 0 } ;
1110+
10051111 const {
10061112 sxStartDisplayCoords,
10071113 syStartDisplayCoords,
@@ -1015,11 +1121,10 @@ class RenderingEngine {
10151121 viewport as IViewport ,
10161122 offScreenCanvasWidth ,
10171123 offScreenCanvasHeight ,
1018- _xOffset
1124+ offsets . xOffset ,
1125+ offsets . yOffset
10191126 ) ;
10201127
1021- _xOffset += viewport . canvas . width ;
1022-
10231128 viewport . sx = sx ;
10241129 viewport . sy = sy ;
10251130 viewport . sWidth = sWidth ;
@@ -1034,9 +1139,6 @@ class RenderingEngine {
10341139 syEndDisplayCoords ,
10351140 ] ) ;
10361141 }
1037-
1038- // Returns the final xOffset
1039- return _xOffset ;
10401142 }
10411143
10421144 /**
@@ -1051,20 +1153,21 @@ class RenderingEngine {
10511153 viewport : InternalViewportInput | IViewport ,
10521154 offScreenCanvasWidth : number ,
10531155 offScreenCanvasHeight : number ,
1054- _xOffset : number
1156+ _xOffset : number ,
1157+ _yOffset : number = 0
10551158 ) : ViewportDisplayCoords {
10561159 const { canvas } = viewport ;
10571160 const { width : sWidth , height : sHeight } = canvas ;
10581161
10591162 // Update the canvas drawImage offsets.
10601163 const sx = _xOffset ;
1061- const sy = 0 ;
1164+ const sy = _yOffset ;
10621165
10631166 const sxStartDisplayCoords = sx / offScreenCanvasWidth ;
10641167
1065- // Need to offset y if it not max height
1066- const syStartDisplayCoords =
1067- sy + ( offScreenCanvasHeight - sHeight ) / offScreenCanvasHeight ;
1168+ // VTK uses bottom-left origin, while canvas uses top-left
1169+ // We need to invert the Y coordinate for VTK
1170+ const syStartDisplayCoords = 1 - ( sy + sHeight ) / offScreenCanvasHeight ;
10681171
10691172 const sWidthDisplayCoords = sWidth / offScreenCanvasWidth ;
10701173 const sHeightDisplayCoords = sHeight / offScreenCanvasHeight ;
0 commit comments