From 8967e2bb034e62518d9c5d14d24d9849a4fec041 Mon Sep 17 00:00:00 2001 From: Tom Date: Mon, 12 Feb 2024 09:30:26 -0500 Subject: [PATCH] support hot-swapping drawers with viewer.setDrawer() --- src/canvasdrawer.js | 1 + src/viewer.js | 81 ++++++++++++++++++++++++++++++--------------- src/webgldrawer.js | 28 +++++++++++++++- 3 files changed, 83 insertions(+), 27 deletions(-) diff --git a/src/canvasdrawer.js b/src/canvasdrawer.js index d4ec6231..b848c3ef 100644 --- a/src/canvasdrawer.js +++ b/src/canvasdrawer.js @@ -139,6 +139,7 @@ class CanvasDrawer extends OpenSeadragon.DrawerBase{ this.canvas.height = 1; this.sketchCanvas = null; this.sketchContext = null; + this.container.removeChild(this.canvas); } /** diff --git a/src/viewer.js b/src/viewer.js index 6a8865c8..c3c10938 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -455,35 +455,13 @@ $.Viewer = function( options ) { this.drawer = null; - for (let i = 0; i < drawerCandidates.length; i++) { - - let drawerCandidate = drawerCandidates[i]; - let Drawer = null; - - //if inherits from a drawer base, use it - if (drawerCandidate && drawerCandidate.prototype instanceof $.DrawerBase) { - Drawer = drawerCandidate; - drawerCandidate = 'custom'; - } else if (typeof drawerCandidate === "string") { - Drawer = $.determineDrawer(drawerCandidate); - } else { - $.console.warn('Unsupported drawer! Drawer must be an existing string type, or a class that extends OpenSeadragon.DrawerBase.'); - continue; - } - - // if the drawer is supported, create it and break the loop - if (Drawer && Drawer.isSupported()) { - this.drawer = new Drawer({ - viewer: this, - viewport: this.viewport, - element: this.canvas, - debugGridColor: this.debugGridColor, - options: this.drawerOptions[drawerCandidate], - }); - + for (const drawerCandidate of drawerCandidates){ + let success = this.setDrawer(drawerCandidate, false); + if(success){ break; } } + if (!this.drawer){ $.console.error('No drawer could be created!'); throw('Error with creating the selected drawer(s)'); @@ -950,6 +928,57 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, this.removeAllHandlers(); }, + /** + * Set the drawer for this viewer, as a supported string or drawer constructor. + * @param {String | OpenSeadragon.DrawerBase} drawerCandidate The type of drawer to try to construct + * @param { Boolean } [redrawImmediately] Whether to immediately draw a new frame. Default = true. + * @param { Object } [drawerOptions] Options for this drawer. If falsey, defaults to viewer.drawerOptions + * for this viewer type. See {@link OpenSeadragon.Options}. + * @returns {Boolean} whether the drawer was created successfully + */ + setDrawer(drawerCandidate, redrawImmediately = true, drawerOptions = null){ + const oldDrawer = this.drawer; + + let Drawer = null; + + //if inherits from a drawer base, use it + if (drawerCandidate && drawerCandidate.prototype instanceof $.DrawerBase) { + Drawer = drawerCandidate; + drawerCandidate = 'custom'; + } else if (typeof drawerCandidate === "string") { + Drawer = $.determineDrawer(drawerCandidate); + } + + if(!Drawer){ + $.console.warn('Unsupported drawer! Drawer must be an existing string type, or a class that extends OpenSeadragon.DrawerBase.'); + } + + // if the drawer is supported, create it and return true + if (Drawer && Drawer.isSupported()) { + + // first destroy the previous drawer + if(oldDrawer){ + oldDrawer.destroy(); + } + + // create the new drawer + this.drawer = new Drawer({ + viewer: this, + viewport: this.viewport, + element: this.canvas, + debugGridColor: this.debugGridColor, + options: drawerOptions || this.drawerOptions[drawerCandidate], + }); + + if(redrawImmediately){ + this.forceRedraw(); + } + return true; + } + + return false; + }, + /** * @function * @returns {Boolean} diff --git a/src/webgldrawer.js b/src/webgldrawer.js index 3cd46ffe..e5b8f811 100644 --- a/src/webgldrawer.js +++ b/src/webgldrawer.js @@ -158,6 +158,16 @@ // set our webgl context reference to null to enable garbage collection this._gl = null; + if(this._backupCanvasDrawer){ + this._backupCanvasDrawer.destroy(); + this._backupCanvasDrawer = null; + } + + this.container.removeChild(this.canvas); + if(this.viewer.drawer === this){ + this.viewer.drawer = null; + } + // set our destroyed flag to true this._destroyed = true; } @@ -355,6 +365,15 @@ let tileContext = tile.getCanvasContext(); let textureInfo = tileContext ? this._TextureMap.get(tileContext.canvas) : null; + if(!textureInfo){ + // tile was not processed in the tile-ready event (this can happen + // if this drawer was created after the tile was downloaded) + this._tileReadyHandler({tile: tile, tiledImage: tiledImage}); + + // retry getting textureInfo + textureInfo = tileContext ? this._TextureMap.get(tileContext.canvas) : null; + } + if(textureInfo){ this._getTileData(tile, tiledImage, textureInfo, overallMatrix, indexInDrawArray, texturePositionArray, textureDataArray, matrixArray, opacityArray); } else { @@ -840,11 +859,18 @@ _tileReadyHandler(event){ let tile = event.tile; let tiledImage = event.tiledImage; + + // If a tiledImage is already known to be tainted, don't try to upload any + // textures to webgl, because they won't be used even if it succeeds + if(tiledImage.isTainted()){ + return; + } + let tileContext = tile.getCanvasContext(); let canvas = tileContext && tileContext.canvas; // if the tile doesn't provide a canvas, or is tainted by cross-origin // data, marked the TiledImage as tainted so the canvas drawer can be - // used instead, and return immediately - data cannot be uploaded to webgl + // used instead, and return immediately - tainted data cannot be uploaded to webgl if(!canvas || $.isCanvasTainted(canvas)){ const wasTainted = tiledImage.isTainted(); if(!wasTainted){