diff --git a/Gruntfile.js b/Gruntfile.js index 1e87ab4b..e159dd03 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -59,7 +59,7 @@ module.exports = function(grunt) { "src/overlay.js", "src/drawerbase.js", "src/htmldrawer.js", - "src/canvasdrawer.js", + "src/context2ddrawer.js", "src/viewport.js", "src/tiledimage.js", "src/tilecache.js", diff --git a/src/canvasdrawer.js b/src/context2ddrawer.js similarity index 97% rename from src/canvasdrawer.js rename to src/context2ddrawer.js index 4b51daa4..20133570 100644 --- a/src/canvasdrawer.js +++ b/src/context2ddrawer.js @@ -35,9 +35,9 @@ (function( $ ){ /** - * @class CanvasDrawer + * @class Context2dDrawer * @memberof OpenSeadragon - * @classdesc Default implementation of CanvasDrawer for an {@link OpenSeadragon.Viewer}. + * @classdesc Default implementation of Context2dDrawer for an {@link OpenSeadragon.Viewer}. * @param {Object} options - Options for this Drawer. * @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this Drawer. * @param {OpenSeadragon.Viewport} options.viewport - Reference to Viewer viewport. @@ -45,7 +45,7 @@ * @param {Number} [options.debugGridColor] - See debugGridColor in {@link OpenSeadragon.Options} for details. */ -class CanvasDrawer extends $.DrawerBase{ +class Context2dDrawer extends $.DrawerBase{ constructor(){ super(...arguments); @@ -77,6 +77,26 @@ class CanvasDrawer extends $.DrawerBase{ this._imageSmoothingEnabled = true; } + + /** + * @returns {Boolean} true if canvas is supported by the browser, otherwise false + */ + isSupported(){ + return $.supportsCanvas; + } + + /** + * create the HTML element (e.g. canvas, div) that the image will be drawn into + * @returns {Element} the canvas to draw into + */ + createDrawingElement(){ + let canvas = $.makeNeutralElement("canvas"); + let viewportSize = this._calculateCanvasSize(); + canvas.width = viewportSize.x; + canvas.height = viewportSize.y; + return canvas; + } + /** * Draws the TiledImages */ @@ -543,7 +563,7 @@ class CanvasDrawer extends $.DrawerBase{ } context.save(); - context.globalAlpha = this.opacity; + // context.globalAlpha = this.options.opacity; // this was deprecated previously and should not be applied as it is set per TiledImage if (typeof scale === 'number' && scale !== 1) { // draw tile at a different scale @@ -982,7 +1002,7 @@ class CanvasDrawer extends $.DrawerBase{ }; } } -$.CanvasDrawer = CanvasDrawer; +$.Context2dDrawer = Context2dDrawer; /** diff --git a/src/drawerbase.js b/src/drawerbase.js index 0939b354..fd8e94f8 100644 --- a/src/drawerbase.js +++ b/src/drawerbase.js @@ -71,24 +71,16 @@ $.DrawerBase = class DrawerBase{ $.console.error( "[Drawer] options.source is no longer accepted; use TiledImage instead" ); } - - // Object.entries({}).forEach( ([key, value]) => { - // try{ - // this[key] = value; - // } catch(e) { - // $.console.error(`This Drawer implementation does not support option '${key}:${value}'`); - // } - // }); - this.viewer = options.viewer; this.viewport = options.viewport; this.debugGridColor = typeof options.debugGridColor === 'string' ? [options.debugGridColor] : options.debugGridColor || $.DEFAULT_SETTINGS.debugGridColor; + this.options = options.options || {}; + // TO DO: This was deprectated previously. Can we get rid of it at this point? if (options.opacity) { $.console.error( "[Drawer] options.opacity is no longer accepted; set the opacity on the TiledImage instead" ); } - this.useCanvas = $.supportsCanvas && ( this.viewer ? this.viewer.useCanvas : true ); /** * The parent element of this Drawer instance, passed in when the Drawer was created. * The parent of {@link OpenSeadragon.DrawerBase#canvas}. @@ -96,21 +88,6 @@ $.DrawerBase = class DrawerBase{ * @memberof OpenSeadragon.DrawerBase# */ this.container = $.getElement( options.element ); - /** - * A <canvas> element if the browser supports them, otherwise a <div> element. - * Child element of {@link OpenSeadragon.DrawerBase#container}. - * @member {Element} canvas - * @memberof OpenSeadragon.DrawerBase# - */ - this.canvas = $.makeNeutralElement( this.useCanvas ? "canvas" : "div" ); - - - /** - * @member {Element} element - * @memberof OpenSeadragon.DrawerBase# - * @deprecated Alias for {@link OpenSeadragon.DrawerBase#container}. - */ - this.element = this.container; // TO DO: Does this need to be in DrawerBase, or only in Drawer implementations? // We force our container to ltr because our drawing math doesn't work in rtl. @@ -118,16 +95,11 @@ $.DrawerBase = class DrawerBase{ // Note that this means overlays you want to be rtl need to be explicitly set to rtl. this.container.dir = 'ltr'; - if (this.useCanvas) { - var viewportSize = this._calculateCanvasSize(); - this.canvas.width = viewportSize.x; - this.canvas.height = viewportSize.y; - } - + // the first time this.canvas is accessed, implementations will create it this.canvas.style.width = "100%"; this.canvas.style.height = "100%"; this.canvas.style.position = "absolute"; - $.setElementOpacity( this.canvas, this.opacity, true ); + $.setElementOpacity( this.canvas, this.viewer.opacity, true ); // set // Allow pointer events to pass through the canvas element so implicit // pointer capture works on touch devices @@ -143,6 +115,32 @@ $.DrawerBase = class DrawerBase{ get isOpenSeadragonDrawer(){ return true; } + get canvas(){ + if(!this._renderingTarget){ + this._renderingTarget = this.createDrawingElement(); + } + return this._renderingTarget; + } + get element(){ + $.console.error('Drawer.element is deprecated. Use Drawer.container instead.'); + return this.container; + } + + /** + * @returns {Boolean} whether the drawer implementation is supported by the browser + */ + isSupported() { + $.console.error('Drawer.isSupported must be implemented by child class'); + } + + /** + * create the HTML element (e.g. canvas, div) that the image will be drawn into + * @returns {Element} the element to draw into + */ + createDrawingElement() { + $.console.error('Drawer.createDrawingElement must be implemented by child class'); + return null; + } /** * @param tiledImage the TiledImage that is ready to be drawn diff --git a/src/htmldrawer.js b/src/htmldrawer.js index 3d3b8bf8..7759781c 100644 --- a/src/htmldrawer.js +++ b/src/htmldrawer.js @@ -56,32 +56,22 @@ class HTMLDrawer extends $.DrawerBase{ */ this.context = null; + } - // We force our container to ltr because our drawing math doesn't work in rtl. - // This issue only affects our canvas renderer, but we do it always for consistency. - // Note that this means overlays you want to be rtl need to be explicitly set to rtl. - this.container.dir = 'ltr'; - - /** - * Override default element to enforce div for HTMLDrawer - */ - this.canvas.parentNode.removeChild(this.canvas); - this.canvas = $.makeNeutralElement( "div" ); - - this.canvas.style.width = "100%"; - this.canvas.style.height = "100%"; - this.canvas.style.position = "absolute"; - $.setElementOpacity( this.canvas, this.opacity, true ); - - // Allow pointer events to pass through the canvas element so implicit - // pointer capture works on touch devices - $.setElementPointerEventsNone( this.canvas ); - $.setElementTouchActionNone( this.canvas ); - - // explicit left-align - this.container.style.textAlign = "left"; - this.container.appendChild( this.canvas ); + /** + * @returns {Boolean} always true + */ + isSupported(){ + return true; + } + /** + * create the HTML element (e.g. canvas, div) that the image will be drawn into + * @returns {Element} the div to draw into + */ + createDrawingElement(){ + let canvas = $.makeNeutralElement("div"); + return canvas; } /** diff --git a/src/imagetilesource.js b/src/imagetilesource.js index e3b7a43b..1ed4f85c 100644 --- a/src/imagetilesource.js +++ b/src/imagetilesource.js @@ -42,8 +42,8 @@ * 1. viewer.open({type: 'image', url: fooUrl}); * 2. viewer.open(new OpenSeadragon.ImageTileSource({url: fooUrl})); * - * With the first syntax, the crossOriginPolicy, ajaxWithCredentials and - * useCanvas options are inherited from the viewer if they are not + * With the first syntax, the crossOriginPolicy and ajaxWithCredentials + * options are inherited from the viewer if they are not * specified directly in the options object. * * @memberof OpenSeadragon @@ -58,16 +58,13 @@ * domains. * @param {String|Boolean} [options.ajaxWithCredentials=false] Whether to set * the withCredentials XHR flag for AJAX requests (when loading tile sources). - * @param {Boolean} [options.useCanvas=true] Set to false to prevent any use - * of the canvas API. */ $.ImageTileSource = function (options) { options = $.extend({ buildPyramid: true, crossOriginPolicy: false, - ajaxWithCredentials: false, - useCanvas: true + ajaxWithCredentials: false }, options); $.TileSource.apply(this, [options]); @@ -214,7 +211,7 @@ height: this._image.naturalHeight }]; - if (!this.buildPyramid || !$.supportsCanvas || !this.useCanvas) { + if (!this.buildPyramid || !$.supportsCanvas) { // We don't need the image anymore. Allows it to be GC. delete this._image; return levels; diff --git a/src/openseadragon.js b/src/openseadragon.js index c03a0288..7f3858cd 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -190,6 +190,16 @@ * Zoom level to use when image is first opened or the home button is clicked. * If 0, adjusts to fit viewer. * + * @property {String|DrawerImplementation|Array} [drawer = ['context2d', 'html']] + * Which drawer to use. Valid strings are 'context2d' and 'html'. Valid drawer + * implementations are constructors of classes that extend OpenSeadragon.DrawerBase. + * An array of strings and/or constructors can be used to indicate the priority + * of different implementations, which will be tried in order based on browser support. + * + * @property {Object} [drawerOptions = {}] + * Options to pass to the selected drawer implementation. See documentation + * for Drawer classes that extend DrawerBase for further information. + * * @property {Number} [opacity=1] * Default proportional opacity of the tiled images (1=opaque, 0=hidden) * Hidden images do not draw and only load when preloading is allowed. @@ -502,7 +512,7 @@ * Milliseconds to wait after each tile retry if tileRetryMax is set. * * @property {Boolean} [useCanvas=true] - * Set to false to not use an HTML canvas element for image rendering even if canvas is supported. + * Deprecated. Use drawer option to specify preferred renderer. * * @property {Number} [minPixelRatio=0.5] * The higher the minPixelRatio, the lower the quality of the image that @@ -1332,12 +1342,19 @@ function OpenSeadragon( options ){ flipped: false, // APPEARANCE - opacity: 1, - preload: false, - compositeOperation: null, - imageSmoothingEnabled: true, - placeholderFillStyle: null, - subPixelRoundingForTransparency: null, + opacity: 1, // to be passed into each TiledImage + compositeOperation: null, // to be passed into each TiledImage + + // DRAWER SETTINGS + drawer: ['context2d', 'html'], // prefer using canvas, fallback to html + drawerOptions: {}, + useCanvas: true, // deprecated - set drawer and drawerOptions + + // TILED IMAGE SETTINGS + preload: false, // to be passed into each TiledImage + imageSmoothingEnabled: true, // to be passed into each TiledImage + placeholderFillStyle: null, // to be passed into each TiledImage + subPixelRoundingForTransparency: null, // to be passed into each TiledImage //REFERENCE STRIP SETTINGS showReferenceStrip: false, @@ -1360,7 +1377,6 @@ function OpenSeadragon( options ){ imageLoaderLimit: 0, maxImageCacheCount: 200, timeout: 30000, - useCanvas: true, // Use canvas element for drawing if available tileRetryMax: 0, tileRetryDelay: 2500, @@ -1430,16 +1446,6 @@ function OpenSeadragon( options ){ }, - - /** - * TODO: get rid of this. I can't see how it's required at all. Looks - * like an early legacy code artifact. - * @static - * @ignore - */ - SIGNAL: "----seadragon----", - - /** * Returns a function which invokes the method as if it were a method belonging to the object. * @function diff --git a/src/referencestrip.js b/src/referencestrip.js index ab21da50..ee516c6f 100644 --- a/src/referencestrip.js +++ b/src/referencestrip.js @@ -455,7 +455,7 @@ function loadPanels( strip, viewerSize, scroll ) { animationTime: 0, loadTilesWithAjax: strip.viewer.loadTilesWithAjax, ajaxHeaders: strip.viewer.ajaxHeaders, - useCanvas: strip.useCanvas + drawer: strip.viewer.drawerOptions.constructor, } ); // Allow pointer events to pass through miniViewer's canvas/container // elements so implicit pointer capture works on touch devices diff --git a/src/viewer.js b/src/viewer.js index d35156f0..71c1a75b 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -89,6 +89,21 @@ $.Viewer = function( options ) { delete options.config; } + // Move deprecated drawer options from the base options object into a sub-object + // This is an array to make it easy to add additional properties to convert to + // drawer options later if it makes sense to set at the drawer level rather than + // per tiled image (for example, subPixelRoundingForTransparency). + let drawerOptionList = [ + 'useCanvas', // deprecated + ]; + options.drawerOptions = Object.assign({}, + drawerOptionList.reduce((drawerOptions, option) => { + drawerOptions[option] = options[option]; + delete options[option]; + return drawerOptions; + }, {}), + options.drawerOptions); + //Public properties //Allow the options object to override global defaults $.extend( true, this, { @@ -198,6 +213,7 @@ $.Viewer = function( options ) { $.console.warn("Hash " + this.hash + " has already been used."); } + //Private state properties THIS[ this.hash ] = { fsBoundsDelta: new $.Point( 1, 1 ), @@ -418,35 +434,48 @@ $.Viewer = function( options ) { maxImageCacheCount: this.maxImageCacheCount }); - // Create the drawer - if(this.customDrawer){ - if(this.customDrawer.prototype.isOpenSeadragonDrawer){ - var Drawer = this.customDrawer; + //Create the drawer based on selected options + if (Object.prototype.hasOwnProperty.call(this.drawerOptions, 'useCanvas') ){ + $.console.error('useCanvas is deprecated, use the "drawer" option to indicate preferred drawer(s)'); + + // for backwards compatibility, use HTMLDrawer if useCanvas is defined an is falsey + if (!this.drawerOptions.useCanvas){ + this.drawer = $.HTMLDrawer; + } + + delete this.drawerOptions.useCanvas; + } + let drawerPriority = Array.isArray(this.drawer) ? this.drawer : [this.drawer]; + let drawersToTry = drawerPriority.filter(d => ['context2d', 'html'].includes(d) || (d.prototype && d.prototype.isOpenSeadragonDrawer) ); + if(drawerPriority.length !== drawersToTry.length){ + $.console.error('An invalid drawer was requested.'); + } + if(drawersToTry.length === 0){ + drawersToTry = [$.DEFAULT_SETTINGS.drawer].flat(); // ensure it is a list + $.console.warn('No valid drawers were selected. Using the default value.'); + } + // extend the drawerOptions object with additional properties to pass to the Drawer implementation + this.drawer = null; // TO DO: how to deal with the possibility that none of the requested drawers are supported? + for(let i = 0; i < drawersToTry.length; i++){ + let Drawer = drawersToTry[i]; + // replace text-based option with appropriate constructor + if (Drawer === 'context2d'){ + Drawer = $.Context2dDrawer; + } else if (Drawer === 'html'){ + Drawer = $.HTMLDrawer; + } + // if the drawer is supported, create it and break the loop + if (Drawer.prototype.isSupported()){ this.drawer = new Drawer({ viewer: this, viewport: this.viewport, element: this.canvas, - debugGridColor: this.debugGridColor + debugGridColor: this.debugGridColor, + options: this.drawerOptions, }); - } else { - // $.console.error('Viewer option customDrawer must derive from OpenSeadragon.DrawerBase'); - throw('Viewer option customDrawer must derive from OpenSeadragon.DrawerBase'); + this.drawerOptions.constructor = Drawer; + break; } - - } else if(this.useCanvas && $.supportsCanvas) { - this.drawer = new $.CanvasDrawer({ - viewer: this, - viewport: this.viewport, - element: this.canvas, - debugGridColor: this.debugGridColor - }); - } else { - this.drawer = new $.HTMLDrawer({ - viewer: this, - viewport: this.viewport, - element: this.canvas, - debugGridColor: this.debugGridColor - }); } @@ -455,7 +484,7 @@ $.Viewer = function( options ) { this.canvas.appendChild( this.overlaysContainer ); // Now that we have a drawer, see if it supports rotate. If not we need to remove the rotate buttons - if (!this.drawer.canRotate()) { + if (this.drawer && !this.drawer.canRotate()) { // Disable/remove the rotate left/right buttons since they aren't supported if (this.rotateLeft) { i = this.buttonGroup.buttons.indexOf(this.rotateLeft); @@ -520,11 +549,6 @@ $.Viewer = function( options ) { beginControlsAutoHide( _this ); } ); - // Initial canvas options - if ( this.imageSmoothingEnabled !== undefined && !this.imageSmoothingEnabled){ - this.drawer.setImageSmoothingEnabled(this.imageSmoothingEnabled); - } - // Register the viewer $._viewers.set(this.element, this); }; @@ -2426,7 +2450,6 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, width: this.referenceStripWidth, tileSources: this.tileSources, prefixUrl: this.prefixUrl, - useCanvas: this.useCanvas, viewer: this }); @@ -2575,7 +2598,6 @@ function getTileSourceImplementation( viewer, tileSource, imgOptions, successCal ajaxHeaders: imgOptions.ajaxHeaders ? imgOptions.ajaxHeaders : viewer.ajaxHeaders, splitHashDataForPost: viewer.splitHashDataForPost, - useCanvas: viewer.useCanvas, success: function( event ) { successCallback( event.tileSource ); } @@ -2593,9 +2615,6 @@ function getTileSourceImplementation( viewer, tileSource, imgOptions, successCal if (tileSource.ajaxWithCredentials === undefined) { tileSource.ajaxWithCredentials = viewer.ajaxWithCredentials; } - if (tileSource.useCanvas === undefined) { - tileSource.useCanvas = viewer.useCanvas; - } if ( $.isFunction( tileSource.getTileUrl ) ) { //Custom tile source diff --git a/test/demo/collections/main.js b/test/demo/collections/main.js index 136c3417..b5cf86ca 100644 --- a/test/demo/collections/main.js +++ b/test/demo/collections/main.js @@ -16,7 +16,7 @@ // debugMode: true, zoomPerScroll: 1.02, showNavigator: testNavigator, - useCanvas: true, + drawer: ['context2d', 'html'], // defaultZoomLevel: 2, // homeFillsViewer: true, // sequenceMode: true, diff --git a/test/demo/threejsdrawer.js b/test/demo/threejsdrawer.js index 299c7a07..73a257bf 100644 --- a/test/demo/threejsdrawer.js +++ b/test/demo/threejsdrawer.js @@ -145,6 +145,29 @@ export class ThreeJSDrawer extends OpenSeadragon.DrawerBase{ } // Public API required by all Drawer implementations + + /** + * @returns {Boolean} returns true if canvas and webgl are supported and + * three.js has been exposed as a global variable named THREE + */ + isSupported(){ + let canvasElement = document.createElement( 'canvas' ); + return !!( OpenSeadragon.isFunction( canvasElement.getContext ) && + canvasElement.getContext( 'webgl' ) ) && THREE; + } + + /** + * create the HTML element (e.g. canvas, div) that the image will be drawn into + * @returns {Element} the canvas to draw into + */ + createDrawingElement(){ + let canvas = OpenSeadragon.makeNeutralElement("canvas"); + let viewportSize = this._calculateCanvasSize(); + canvas.width = viewportSize.x; + canvas.height = viewportSize.y; + return canvas; + } + /** * * @param {Array} tiledImages Array of TiledImage objects to draw diff --git a/test/demo/webgl.html b/test/demo/webgl.html index 8e98f4e2..196a65bc 100644 --- a/test/demo/webgl.html +++ b/test/demo/webgl.html @@ -88,7 +88,7 @@ let viewer = OpenSeadragon({ ... - customDrawer: ThreeJSDrawer, + drawer: ThreeJSDrawer, ... }); @@ -173,17 +173,17 @@ HTML-based rendering can be selected in two different ways:
- // via the useCanvas option: + // via the 'html' drawer option: let viewer = OpenSeadragon({ ... - useCanvas: false, + drawer: 'html', ... }); // or by passing the HTMLDrawer constructor let viewer = OpenSeadragon({ ... - customDrawer:OpenSeadragon.HTMLDrawer, + drawer:OpenSeadragon.HTMLDrawer, ... });diff --git a/test/demo/webgl.js b/test/demo/webgl.js index e40044a3..cdcd71e5 100644 --- a/test/demo/webgl.js +++ b/test/demo/webgl.js @@ -25,7 +25,7 @@ var stats = null; // document.body.appendChild( stats.dom ); -//Double viewer setup for comparison - CanvasDrawer and ThreeJSDrawer +//Double viewer setup for comparison - Context2dDrawer and ThreeJSDrawer var viewer = window.viewer = OpenSeadragon({ id: "contentDiv", @@ -37,11 +37,11 @@ var viewer = window.viewer = OpenSeadragon({ smoothTileEdgesMinZoom:1.1, crossOriginPolicy: 'Anonymous', ajaxWithCredentials: false, - useCanvas:true, + drawer:'context2d', }); -// Mirror the interactive viewer with CanvasDrawer onto a separate canvas using ThreeJSDrawer +// Mirror the interactive viewer with Context2dDrawer onto a separate canvas using ThreeJSDrawer let threeRenderer = window.threeRenderer = new ThreeJSDrawer({viewer, viewport: viewer.viewport, element:viewer.element, stats: stats}); //make the test canvas mirror all changes to the viewer canvas let viewerCanvas = viewer.drawer.canvas; @@ -61,7 +61,7 @@ var viewer2 = window.viewer2 = OpenSeadragon({ id: "three-viewer", prefixUrl: "../../build/openseadragon/images/", minZoomImageRatio:0.01, - customDrawer: ThreeJSDrawer, + drawer: ThreeJSDrawer, tileSources: [sources['leaves'], sources['rainbow'], sources['duomo']], sequenceMode: true, imageSmoothingEnabled: false, @@ -73,6 +73,7 @@ var viewer2 = window.viewer2 = OpenSeadragon({ // Also shows sequence mode var viewer3 = window.viewer3 = OpenSeadragon({ id: "htmldrawer", + drawer:'html', prefixUrl: "../../build/openseadragon/images/", minZoomImageRatio:0.01, customDrawer: OpenSeadragon.HTMLDrawer,