In webgl drawer, fall back to canvas drawer for tiled images with tainted data

This commit is contained in:
Tom 2024-02-11 11:51:38 -05:00
parent f7d86ac244
commit 0a154a3b21
2 changed files with 213 additions and 142 deletions

View File

@ -166,6 +166,7 @@ $.TiledImage = function( options ) {
_lastDrawn: [], // array of tiles that were last fetched by the drawer _lastDrawn: [], // array of tiles that were last fetched by the drawer
_isBlending: false, // Are any tiles still being blended? _isBlending: false, // Are any tiles still being blended?
_wasBlending: false, // Were any tiles blending before the last draw? _wasBlending: false, // Were any tiles blending before the last draw?
_isTainted: false, // Has a Tile been found with tainted data?
//configurable settings //configurable settings
springStiffness: $.DEFAULT_SETTINGS.springStiffness, springStiffness: $.DEFAULT_SETTINGS.springStiffness,
animationTime: $.DEFAULT_SETTINGS.animationTime, animationTime: $.DEFAULT_SETTINGS.animationTime,
@ -326,6 +327,25 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
return this._needsDraw; return this._needsDraw;
}, },
/**
* Set the internal _isTainted flag for this TiledImage. Lazy loaded - not
* checked each time a Tile is loaded, but can be set if a consumer of the
* tiles (e.g. a Drawer) discovers a Tile to have tainted data so that further
* checks are not needed and alternative rendering strategies can be used.
* @private
*/
setTainted(isTainted){
this._isTainted = isTainted;
},
/**
* @private
* @returns {Boolean} whether the TiledImage has been marked as tainted
*/
isTainted(){
return this._isTainted;
},
/** /**
* Destroy the TiledImage (unload current loaded tiles). * Destroy the TiledImage (unload current loaded tiles).
*/ */

View File

@ -90,6 +90,7 @@
this._clippingCanvas = null; this._clippingCanvas = null;
this._clippingContext = null; this._clippingContext = null;
this._renderingCanvas = null; this._renderingCanvas = null;
this._backupCanvasDrawer = null;
// Add listeners for events that require modifying the scene or camera // Add listeners for events that require modifying the scene or camera
this.viewer.addHandler("tile-ready", ev => this._tileReadyHandler(ev)); this.viewer.addHandler("tile-ready", ev => this._tileReadyHandler(ev));
@ -206,6 +207,25 @@
return canvas; return canvas;
} }
/**
* Get the backup renderer (CanvasDrawer) to use if data cannot be used by webgl
* Lazy loaded
* @private
* @returns {CanvasDrawer}
*/
_getBackupCanvasDrawer(){
if(!this._backupCanvasDrawer){
this._backupCanvasDrawer = new $.CanvasDrawer({
viewer: this.viewer,
viewport: this.viewport,
element: this.container,
});
this._backupCanvasDrawer.canvas.style.setProperty('visibility', 'hidden');
}
return this._backupCanvasDrawer;
}
/** /**
* *
* @param {Array} tiledImages Array of TiledImage objects to draw * @param {Array} tiledImages Array of TiledImage objects to draw
@ -237,6 +257,22 @@
//iterate over tiled images and draw each one using a two-pass rendering pipeline if needed //iterate over tiled images and draw each one using a two-pass rendering pipeline if needed
tiledImages.forEach( (tiledImage, tiledImageIndex) => { tiledImages.forEach( (tiledImage, tiledImageIndex) => {
if(tiledImage.isTainted()){
// first, draw any data left in the rendering buffer onto the output canvas
if(renderingBufferHasImageData){
this._outputContext.drawImage(this._renderingCanvas, 0, 0);
// clear the buffer
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.clear(gl.COLOR_BUFFER_BIT); // clear the back buffer
renderingBufferHasImageData = false;
}
// next, use the backup canvas drawer to draw this tainted image
const canvasDrawer = this._getBackupCanvasDrawer();
canvasDrawer.draw([tiledImage]);
this._outputContext.drawImage(canvasDrawer.canvas, 0, 0);
} else {
let tilesToDraw = tiledImage.getTilesToDraw(); let tilesToDraw = tiledImage.getTilesToDraw();
if(tilesToDraw.length === 0 || tiledImage.getOpacity() === 0){ if(tilesToDraw.length === 0 || tiledImage.getOpacity() === 0){
@ -399,6 +435,9 @@
if(tiledImageIndex === 0){ if(tiledImageIndex === 0){
this._raiseTiledImageDrawnEvent(tiledImage, tilesToDraw.map(info=>info.tile)); this._raiseTiledImageDrawnEvent(tiledImage, tilesToDraw.map(info=>info.tile));
} }
}
}); });
@ -802,7 +841,19 @@
let tile = event.tile; let tile = event.tile;
let tiledImage = event.tiledImage; let tiledImage = event.tiledImage;
let tileContext = tile.getCanvasContext(); let tileContext = tile.getCanvasContext();
let canvas = tileContext.canvas; 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
if(!canvas || $.isCanvasTainted(canvas)){
const wasTainted = tiledImage.isTainted();
if(!wasTainted){
tiledImage.setTainted(true);
$.console.warn('WebGL cannot be used to draw this TiledImage because it has tainted data. Does crossOriginPolicy need to be set?');
}
return;
}
let textureInfo = this._TextureMap.get(canvas); let textureInfo = this._TextureMap.get(canvas);
// if this is a new image for us, create a texture // if this is a new image for us, create a texture