diff --git a/src/drawer.js b/src/drawer.js index e511aeea..a8d4d09d 100644 --- a/src/drawer.js +++ b/src/drawer.js @@ -37,7 +37,7 @@ /** * @class Drawer * @memberof OpenSeadragon - * @classdesc Handles rendering of tiles for an {@link OpenSeadragon.Viewer}. + * @classdesc Default implementation of Drawer 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. @@ -48,55 +48,12 @@ $.Drawer = function(options) { $.DrawerBase.call(this, options); - $.console.assert( options.viewer, "[Drawer] options.viewer is required" ); - - //backward compatibility for positional args while preferring more - //idiomatic javascript options object as the only argument - var args = arguments; - - if( !$.isPlainObject( options ) ){ - options = { - source: args[ 0 ], // Reference to Viewer tile source. - viewport: args[ 1 ], // Reference to Viewer viewport. - element: args[ 2 ] // Parent element. - }; - } - - $.console.assert( options.viewport, "[Drawer] options.viewport is required" ); - $.console.assert( options.element, "[Drawer] options.element is required" ); - - if ( options.source ) { - $.console.error( "[Drawer] options.source is no longer accepted; use TiledImage instead" ); - } - - this.viewer = options.viewer; - this.viewport = options.viewport; - this.debugGridColor = typeof options.debugGridColor === 'string' ? [options.debugGridColor] : options.debugGridColor || $.DEFAULT_SETTINGS.debugGridColor; - 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.Drawer#canvas}. - * @member {Element} container - * @memberof OpenSeadragon.Drawer# - */ - this.container = $.getElement( options.element ); - /** - * A <canvas> element if the browser supports them, otherwise a <div> element. - * Child element of {@link OpenSeadragon.Drawer#container}. - * @member {Element} canvas - * @memberof OpenSeadragon.Drawer# - */ - this.canvas = $.makeNeutralElement( this.useCanvas ? "canvas" : "div" ); /** * 2d drawing context for {@link OpenSeadragon.Drawer#canvas} if it's a <canvas> element, otherwise null. * @member {Object} context * @memberof OpenSeadragon.Drawer# */ - this.context = this.useCanvas ? this.canvas.getContext( this.useCanvas.contextType || "2d" ) : null; + this.context = this.useCanvas ? this.canvas.getContext( '2d' ) : null; /** * Sketch canvas used to temporarily draw tiles which cannot be drawn directly @@ -105,60 +62,35 @@ $.Drawer = function(options) { this.sketchCanvas = null; this.sketchContext = null; - /** - * @member {Element} element - * @memberof OpenSeadragon.Drawer# - * @deprecated Alias for {@link OpenSeadragon.Drawer#container}. - */ - this.element = this.container; - // 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'; - // check canvas available width and height, set canvas width and height such that the canvas backing store is set to the proper pixel density - if (this.useCanvas) { - var viewportSize = this._calculateCanvasSize(); - this.canvas.width = viewportSize.x; - this.canvas.height = viewportSize.y; - } - - 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 ); - // Image smoothing for canvas rendering (only if canvas is used). // Canvas default is "true", so this will only be changed if user specified "false". this._imageSmoothingEnabled = true; - - }; $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.Drawer.prototype */ { /** - * Draws the TiledImage to its Drawer. + * Draws the TiledImages */ - draw: function(tiledImage) { - if (tiledImage.opacity !== 0 || tiledImage._preload) { - tiledImage._midDraw = true; - this._updateViewport(tiledImage); - tiledImage._midDraw = false; - } - // Images with opacity 0 should not need to be drawn in future. this._needsDraw = false is set in this._updateViewport() for other images. - else { - tiledImage._needsDraw = false; - } + draw: function(tiledImages) { + var _this = this; + this._prepareNewFrame(); // prepare to draw a new frame + tiledImages.forEach(function(tiledImage){ + if (tiledImage.opacity !== 0 || tiledImage._preload) { + tiledImage._midDraw = true; + _this._updateViewportWithTiledImage(tiledImage); + tiledImage._midDraw = false; + } + else { + tiledImage._needsDraw = false; + } + }); + }, /** @@ -180,9 +112,51 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D }, /** - * Clears the Drawer so it's ready to draw another frame. + * Turns image smoothing on or off for this viewer. Note: Ignored in some (especially older) browsers that do not support this property. + * + * @function + * @param {Boolean} [imageSmoothingEnabled] - Whether or not the image is + * drawn smoothly on the canvas; see imageSmoothingEnabled in + * {@link OpenSeadragon.Options} for more explanation. */ - clear: function() { + setImageSmoothingEnabled: function(imageSmoothingEnabled){ + if ( this.useCanvas ) { + this._imageSmoothingEnabled = imageSmoothingEnabled; + this._updateImageSmoothingEnabled(this.context); + this.viewer.forceRedraw(); + } + }, + + /** + * Draw a rectangle onto the canvas + * @param {OpenSeadragon.Rect} rect + */ + drawDebuggingRect: function(rect) { + if ( this.useCanvas ) { + var context = this.context; + context.save(); + context.lineWidth = 2 * $.pixelDensityRatio; + context.strokeStyle = this.debugGridColor[0]; + context.fillStyle = this.debugGridColor[0]; + + context.strokeRect( + rect.x * $.pixelDensityRatio, + rect.y * $.pixelDensityRatio, + rect.width * $.pixelDensityRatio, + rect.height * $.pixelDensityRatio + ); + + context.restore(); + } + }, + + /** + * @private + * @inner + * Clears the Drawer so it's ready to draw another frame. + * + */ + _prepareNewFrame: function() { this.canvas.innerHTML = ""; if ( this.useCanvas ) { var viewportSize = this._calculateCanvasSize(); @@ -202,31 +176,33 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D } }, - _clear: function (useSketch, bounds) { - if (!this.useCanvas) { - return; - } - var context = this._getContext(useSketch); - if (bounds) { - context.clearRect(bounds.x, bounds.y, bounds.width, bounds.height); - } else { - var canvas = context.canvas; - context.clearRect(0, 0, canvas.width, canvas.height); + /** + * @private + * @inner + * @param {Boolean} useSketch Whether to clear sketch canvas or main canvas + * @param {OpenSeadragon.Rect} [bounds] The rectangle to clear + */ + _clear: function(useSketch, bounds){ + if( this.useCanvas ){ + var context = this._getContext(useSketch); + if (bounds) { + context.clearRect(bounds.x, bounds.y, bounds.width, bounds.height); + } else { + var canvas = context.canvas; + context.clearRect(0, 0, canvas.width, canvas.height); + } } }, /* Methods from TiledImage */ - - /** * @private * @inner - * Pretty much every other line in this needs to be documented so it's clear - * how each piece of this routine contributes to the drawing process. That's - * why there are so many TODO's inside this function. + * Handles drawing a single TiledImage to the canvas + * */ - _updateViewport: function(tiledImage) { + _updateViewportWithTiledImage: function(tiledImage) { var _this = this; tiledImage._needsDraw = false; tiledImage._tilesLoading = 0; @@ -265,7 +241,7 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D var infoArray = tiledImage.getTileInfoForDrawing(); infoArray.forEach(updateTile); - this._drawTiles(tiledImage, tiledImage.lastDrawn); + this._drawTiles(tiledImage); }, @@ -348,7 +324,7 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D if (lastDrawn.length > 1 && imageZoom > tiledImage.smoothTileEdgesMinZoom && !tiledImage.iOSDevice && - tiledImage.getRotation(true) % 360 === 0 && // TODO: support tile edge smoothing with tiled image rotation. + tiledImage.getRotation(true) % 360 === 0 && // TO DO: support tile edge smoothing with tiled image rotation. $.supportsCanvas && this.viewer.useCanvas) { // When zoomed in a lot (>100%) the tile edges are visible. // So we have to composite them at ~100% and scale them up together. @@ -356,8 +332,8 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D useSketch = true; sketchScale = tile.getScaleForEdgeSmoothing(); sketchTranslate = tile.getTranslationForEdgeSmoothing(sketchScale, - this.getCanvasSize(false), - this.getCanvasSize(true)); + this._getCanvasSize(false), + this._getCanvasSize(true)); } var bounds; @@ -409,39 +385,42 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D var usedClip = false; if ( tiledImage._clip ) { - this.saveContext(useSketch); + this._saveContext(useSketch); var box = tiledImage.imageToViewportRectangle(tiledImage._clip, true); box = box.rotate(-tiledImage.getRotation(true), tiledImage._getRotationPoint(true)); - var clipRect = this.viewportToDrawerRectangle(box); + var clipRect = this._viewportToDrawerRectangle(box); if (sketchScale) { clipRect = clipRect.times(sketchScale); } if (sketchTranslate) { clipRect = clipRect.translate(sketchTranslate); } - this.setClip(clipRect, useSketch); + this._setClip(clipRect, useSketch); usedClip = true; } if (tiledImage._croppingPolygons) { var self = this; - this.saveContext(useSketch); + this._saveContext(useSketch); try { var polygons = tiledImage._croppingPolygons.map(function (polygon) { return polygon.map(function (coord) { var point = tiledImage .imageToViewportCoordinates(coord.x, coord.y, true) .rotate(-tiledImage.getRotation(true), tiledImage._getRotationPoint(true)); - var clipPoint = self.viewportCoordToDrawerCoord(point); + var clipPoint = self._viewportCoordToDrawerCoord(point); if (sketchScale) { clipPoint = clipPoint.times(sketchScale); } + if (sketchTranslate) { // mostly fixes #2312 + clipPoint = clipPoint.plus(sketchTranslate); + } return clipPoint; }); }); - this.clipWithPolygons(polygons, useSketch); + this._clipWithPolygons(polygons, useSketch); } catch (e) { $.console.error(e); } @@ -449,7 +428,7 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D } if ( tiledImage.placeholderFillStyle && tiledImage._hasOpaqueTile === false ) { - var placeholderRect = this.viewportToDrawerRectangle(tiledImage.getBounds(true)); + var placeholderRect = this._viewportToDrawerRectangle(tiledImage.getBounds(true)); if (sketchScale) { placeholderRect = placeholderRect.times(sketchScale); } @@ -479,15 +458,16 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D shouldRoundPositionAndSize = !isAnimating; } + // Iterate over the tiles to draw, and draw them for (var i = lastDrawn.length - 1; i >= 0; i--) { tile = lastDrawn[ i ]; - this.drawTile( tile, tiledImage._drawingHandler, useSketch, sketchScale, + this._drawTile( tile, tiledImage._drawingHandler, useSketch, sketchScale, sketchTranslate, shouldRoundPositionAndSize, tiledImage.source ); tile.beingDrawn = true; if( this.viewer ){ /** - * - Needs documentation - + * Raised when a tile is drawn to the canvas * * @event tile-drawn * @memberof OpenSeadragon.Viewer @@ -505,7 +485,7 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D } if ( usedClip ) { - this.restoreContext( useSketch ); + this._restoreContext( useSketch ); } if (!sketchScale) { @@ -584,31 +564,15 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D /* Methods from Tile */ - - - /** - * This function converts the given point from to the drawer coordinate by - * multiplying it with the pixel density. - * This function does not take rotation into account, thus assuming provided - * point is at 0 degree. - * @param {OpenSeadragon.Point} point - the pixel point to convert - * @returns {OpenSeadragon.Point} Point in drawer coordinate system. - */ - viewportCoordToDrawerCoord: function(point) { - var vpPoint = this.viewport.pixelFromPointNoRotate(point, true); - return new $.Point( - vpPoint.x * $.pixelDensityRatio, - vpPoint.y * $.pixelDensityRatio - ); - }, - /** + * @private + * @inner * This function will create multiple polygon paths on the drawing context by provided polygons, * then clip the context to the paths. * @param {OpenSeadragon.Point[][]} polygons - an array of polygons. A polygon is an array of OpenSeadragon.Point * @param {Boolean} useSketch - Whether to use the sketch canvas or not. */ - clipWithPolygons: function (polygons, useSketch) { + _clipWithPolygons: function (polygons, useSketch) { if (!this.useCanvas) { return; } @@ -622,31 +586,9 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D context.clip(); }, - - - - - - - /** - * Scale from OpenSeadragon viewer rectangle to drawer rectangle - * (ignoring rotation) - * @param {OpenSeadragon.Rect} rectangle - The rectangle in viewport coordinate system. - * @returns {OpenSeadragon.Rect} Rectangle in drawer coordinate system. - */ - viewportToDrawerRectangle: function(rectangle) { - var topLeft = this.viewport.pixelFromPointNoRotate(rectangle.getTopLeft(), true); - var size = this.viewport.deltaPixelsFromPointsNoRotate(rectangle.getSize(), true); - - return new $.Rect( - topLeft.x * $.pixelDensityRatio, - topLeft.y * $.pixelDensityRatio, - size.x * $.pixelDensityRatio, - size.y * $.pixelDensityRatio - ); - }, - /** + * @private + * @inner * Draws the given tile. * @param {OpenSeadragon.Tile} tile - The tile to draw. * @param {Function} drawingHandler - Method for firing the drawing event if using canvas. @@ -660,35 +602,37 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D * context. * @param {OpenSeadragon.TileSource} source - The source specification of the tile. */ - drawTile: function( tile, drawingHandler, useSketch, scale, translate, shouldRoundPositionAndSize, source) { - $.console.assert(tile, '[Drawer.drawTile] tile is required'); - $.console.assert(drawingHandler, '[Drawer.drawTile] drawingHandler is required'); + _drawTile: function( tile, drawingHandler, useSketch, scale, translate, shouldRoundPositionAndSize, source) { + $.console.assert(tile, '[Drawer._drawTile] tile is required'); + $.console.assert(drawingHandler, '[Drawer._drawTile] drawingHandler is required'); if (this.useCanvas) { var context = this._getContext(useSketch); scale = scale || 1; - this.drawTileToCanvas(tile, context, drawingHandler, scale, translate, shouldRoundPositionAndSize, source); + this._drawTileToCanvas(tile, context, drawingHandler, scale, translate, shouldRoundPositionAndSize, source); } else { - tile.drawTileToHTML( tile, this.canvas ); + tile._drawTileToHTML( tile, this.canvas ); } }, /** - * Renders the tile in a canvas-based context. - * @function - * @param {OpenSeadragon.Tile} tile - the tile to draw to the canvas - * @param {Canvas} context - * @param {Function} drawingHandler - Method for firing the drawing event. - * drawingHandler({context, tile, rendered}) - * where rendered is the context with the pre-drawn image. - * @param {Number} [scale=1] - Apply a scale to position and size - * @param {OpenSeadragon.Point} [translate] - A translation vector - * @param {Boolean} [shouldRoundPositionAndSize] - Tells whether to round - * position and size of tiles supporting alpha channel in non-transparency - * context. - * @param {OpenSeadragon.TileSource} source - The source specification of the tile. - */ - drawTileToCanvas: function( tile, context, drawingHandler, scale, translate, shouldRoundPositionAndSize, source) { + * @private + * @inner + * Renders the tile in a canvas-based context. + * @function + * @param {OpenSeadragon.Tile} tile - the tile to draw to the canvas + * @param {Canvas} context + * @param {Function} drawingHandler - Method for firing the drawing event. + * drawingHandler({context, tile, rendered}) + * where rendered is the context with the pre-drawn image. + * @param {Number} [scale=1] - Apply a scale to position and size + * @param {OpenSeadragon.Point} [translate] - A translation vector + * @param {Boolean} [shouldRoundPositionAndSize] - Tells whether to round + * position and size of tiles supporting alpha channel in non-transparency + * context. + * @param {OpenSeadragon.TileSource} source - The source specification of the tile. + */ + _drawTileToCanvas: function( tile, context, drawingHandler, scale, translate, shouldRoundPositionAndSize, source) { var position = tile.position.times($.pixelDensityRatio), size = tile.size.times($.pixelDensityRatio), @@ -696,7 +640,7 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D if (!tile.context2D && !tile.cacheImageRecord) { $.console.warn( - '[Drawer.drawTileToCanvas] attempting to draw tile %s when it\'s not cached', + '[Drawer._drawTileToCanvas] attempting to draw tile %s when it\'s not cached', tile.toString()); return; } @@ -782,15 +726,17 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D }, /** - * Renders the tile in an html container. - * @function - * @param {OpenSeadragon.Tile} tile - * @param {Element} container - */ - drawTileToHTML: function( tile, container ) { + * @private + * @inner + * Renders the tile in an html container. + * @function + * @param {OpenSeadragon.Tile} tile + * @param {Element} container + */ + _drawTileToHTML: function( tile, container ) { if (!tile.cacheImageRecord) { $.console.warn( - '[Drawer.drawTileToHTML] attempting to draw tile %s when it\'s not cached', + '[Drawer._drawTileToHTML] attempting to draw tile %s when it\'s not cached', tile.toString()); return; } @@ -840,6 +786,13 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D $.setElementOpacity( tile.element, tile.opacity ); }, + /** + * @private + * @inner + * Get the context of the main or sketch canvas + * @param {Boolean} useSketch + * @returns + */ _getContext: function( useSketch ) { var context = this.context; if ( useSketch ) { @@ -872,8 +825,14 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D return context; }, - // private - saveContext: function( useSketch ) { + /** + * @private + * @inner + * Save the context of the main or sketch canvas + * @param {Boolean} useSketch + * @returns + */ + _saveContext: function( useSketch ) { if (!this.useCanvas) { return; } @@ -881,8 +840,14 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D this._getContext( useSketch ).save(); }, - // private - restoreContext: function( useSketch ) { + /** + * @private + * @inner + * Restore the context of the main or sketch canvas + * @param {Boolean} useSketch + * @returns + */ + _restoreContext: function( useSketch ) { if (!this.useCanvas) { return; } @@ -891,7 +856,7 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D }, // private - setClip: function(rect, useSketch) { + _setClip: function(rect, useSketch) { if (!this.useCanvas) { return; } @@ -1118,42 +1083,6 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D context.restore(); }, - // private - debugRect: function(rect) { - if ( this.useCanvas ) { - var context = this.context; - context.save(); - context.lineWidth = 2 * $.pixelDensityRatio; - context.strokeStyle = this.debugGridColor[0]; - context.fillStyle = this.debugGridColor[0]; - - context.strokeRect( - rect.x * $.pixelDensityRatio, - rect.y * $.pixelDensityRatio, - rect.width * $.pixelDensityRatio, - rect.height * $.pixelDensityRatio - ); - - context.restore(); - } - }, - - /** - * Turns image smoothing on or off for this viewer. Note: Ignored in some (especially older) browsers that do not support this property. - * - * @function - * @param {Boolean} [imageSmoothingEnabled] - Whether or not the image is - * drawn smoothly on the canvas; see imageSmoothingEnabled in - * {@link OpenSeadragon.Options} for more explanation. - */ - setImageSmoothingEnabled: function(imageSmoothingEnabled){ - if ( this.useCanvas ) { - this._imageSmoothingEnabled = imageSmoothingEnabled; - this._updateImageSmoothingEnabled(this.context); - this.viewer.forceRedraw(); - } - }, - // private _updateImageSmoothingEnabled: function(context){ context.msImageSmoothingEnabled = this._imageSmoothingEnabled; @@ -1161,16 +1090,25 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D }, /** + * @private + * @inner * Get the canvas size * @param {Boolean} sketch If set to true return the size of the sketch canvas * @returns {OpenSeadragon.Point} The size of the canvas */ - getCanvasSize: function(sketch) { + _getCanvasSize: function(sketch) { var canvas = this._getContext(sketch).canvas; return new $.Point(canvas.width, canvas.height); }, - getCanvasCenter: function() { + /** + * @private + * @inner + * Get the canvas center + * @param {Boolean} sketch If set to true return the center point of the sketch canvas + * @returns {OpenSeadragon.Point} The center point of the canvas + */ + _getCanvasCenter: function() { return new $.Point(this.canvas.width / 2, this.canvas.height / 2); }, @@ -1178,7 +1116,7 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D _offsetForRotation: function(options) { var point = options.point ? options.point.times($.pixelDensityRatio) : - this.getCanvasCenter(); + this._getCanvasCenter(); var context = this._getContext(options.useSketch); context.save(); @@ -1198,7 +1136,7 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D options = options || {}; var point = options.point ? options.point.times($.pixelDensityRatio) : - this.getCanvasCenter(); + this._getCanvasCenter(); var context = this._getContext(options.useSketch); context.translate(point.x, 0); @@ -1240,90 +1178,7 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D }; }, - // deprecated functions - // deprecated - addOverlay: function( element, location, placement, onDraw ) { - $.console.error("drawer.addOverlay is deprecated. Use viewer.addOverlay instead."); - this.viewer.addOverlay( element, location, placement, onDraw ); - return this; - }, - // deprecated - updateOverlay: function( element, location, placement ) { - $.console.error("drawer.updateOverlay is deprecated. Use viewer.updateOverlay instead."); - this.viewer.updateOverlay( element, location, placement ); - return this; - }, - - // deprecated - removeOverlay: function( element ) { - $.console.error("drawer.removeOverlay is deprecated. Use viewer.removeOverlay instead."); - this.viewer.removeOverlay( element ); - return this; - }, - - // deprecated - clearOverlays: function() { - $.console.error("drawer.clearOverlays is deprecated. Use viewer.clearOverlays instead."); - this.viewer.clearOverlays(); - return this; - }, - // deprecated - needsUpdate: function() { - $.console.error( "[Drawer.needsUpdate] this function is deprecated. Use World.needsDraw instead." ); - return this.viewer.world.needsDraw(); - }, - - // deprecated - numTilesLoaded: function() { - $.console.error( "[Drawer.numTilesLoaded] this function is deprecated. Use TileCache.numTilesLoaded instead." ); - return this.viewer.tileCache.numTilesLoaded(); - }, - - // deprecated - reset: function() { - $.console.error( "[Drawer.reset] this function is deprecated. Use World.resetItems instead." ); - this.viewer.world.resetItems(); - return this; - }, - - // deprecated - update: function() { - $.console.error( "[Drawer.update] this function is deprecated. Use Drawer.clear and World.draw instead." ); - this.clear(); - this.viewer.world.draw(); - return this; - }, - /** - * Set the opacity of the drawer. - * @param {Number} opacity - * @returns {OpenSeadragon.Drawer} Chainable. - */ - setOpacity: function( opacity ) { - $.console.error("drawer.setOpacity is deprecated. Use tiledImage.setOpacity instead."); - var world = this.viewer.world; - for (var i = 0; i < world.getItemCount(); i++) { - world.getItemAt( i ).setOpacity( opacity ); - } - return this; - }, - - /** - * Get the opacity of the drawer. - * @returns {Number} - */ - getOpacity: function() { - $.console.error("drawer.getOpacity is deprecated. Use tiledImage.getOpacity instead."); - var world = this.viewer.world; - var maxOpacity = 0; - for (var i = 0; i < world.getItemCount(); i++) { - var opacity = world.getItemAt( i ).getOpacity(); - if ( opacity > maxOpacity ) { - maxOpacity = opacity; - } - } - return maxOpacity; - }, }); diff --git a/src/drawerbase.js b/src/drawerbase.js index e556ee1c..42357b71 100644 --- a/src/drawerbase.js +++ b/src/drawerbase.js @@ -98,6 +98,7 @@ $.DrawerBase = function( options ) { */ 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. // 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. @@ -123,10 +124,6 @@ $.DrawerBase = function( options ) { this.container.style.textAlign = "left"; this.container.appendChild( this.canvas ); - // Image smoothing for canvas rendering (only if canvas is used). - // Canvas default is "true", so this will only be changed if user specified "false". - this._imageSmoothingEnabled = true; - this._checkForAPIOverrides(); }; @@ -134,14 +131,10 @@ $.DrawerBase = function( options ) { $.DrawerBase.prototype = { // Drawer implementaions must define the next four methods. These are called - // by core OSD, and forcing overrides (even for nullop methods) makes the + // by core OSD and/or public APIs, and forcing overrides (even for nullop methods) makes the // behavior of the implementations explicitly clear in the code. // Whether these have been overridden by child classes is checked in the - // constructor (via _checkForAPIOverrides). It could make sense to consolidate - // these a bit (e.g. by making `draw` take an array of `TiledImage`s and - // clearing the view as needed, rather than the existing pattern of - // `drawer.clear(); world.draw()` in the calling code), but they have been - // left as-is to maintain backwards compatibility. + // constructor (via _checkForAPIOverrides). /** * @param tiledImage the TiledImage that is ready to be drawn @@ -164,13 +157,6 @@ $.DrawerBase.prototype = { $.console.error('Drawer.destroy must be implemented by child class'); }, - /** - * Clears the Drawer so it's ready to draw another frame. - */ - clear: function() { - $.console.error('Drawer.clear must be implemented by child class'); - }, - /** * Turns image smoothing on or off for this viewer. Note: Ignored in some (especially older) browsers that do not support this property. * @@ -184,8 +170,26 @@ $.DrawerBase.prototype = { }, /** - * Ensures that child classes have provided implementations for API methods - * draw, canRotate, destroy, and clear. Throws an exception if the original + * Optional public API to draw a rectangle (e.g. for debugging purposes) + * Child classes can override this method if they wish to support this + * @param {OpenSeadragon.Rect} rect + */ + drawDebuggingRect: function(rect) { + $.console.warn('[drawer].drawDebuggingRect is not implemented by this drawer'); + }, + + // Deprecated functions + clear: function(){ + $.console.warn('[drawer].clear() is deprecated. The drawer is responsible for clearing itself as needed before drawing tiles.'); + }, + + // Private functions + + /** + * @private + * @inner + * Ensures that child classes have provided implementations for public API methods + * draw, canRotate, destroy, and setImageSmoothinEnabled. Throws an exception if the original * placeholder methods are still in place. */ _checkForAPIOverrides: function(){ @@ -198,21 +202,24 @@ $.DrawerBase.prototype = { if(this.destroy === $.DrawerBase.prototype.destroy){ throw("[drawer].destroy must be implemented by child class"); } - if(this.clear === $.DrawerBase.prototype.clear){ - throw("[drawer].clear must be implemented by child class"); - } + if(this.setImageSmoothingEnabled === $.DrawerBase.prototype.setImageSmoothingEnabled){ throw("[drawer].setImageSmoothingEnabled must be implemented by child class"); } }, + + // Utility functions internal API + /** + * @private + * @inner * Scale from OpenSeadragon viewer rectangle to drawer rectangle * (ignoring rotation) * @param {OpenSeadragon.Rect} rectangle - The rectangle in viewport coordinate system. * @returns {OpenSeadragon.Rect} Rectangle in drawer coordinate system. */ - viewportToDrawerRectangle: function(rectangle) { + _viewportToDrawerRectangle: function(rectangle) { var topLeft = this.viewport.pixelFromPointNoRotate(rectangle.getTopLeft(), true); var size = this.viewport.deltaPixelsFromPointsNoRotate(rectangle.getSize(), true); @@ -225,6 +232,8 @@ $.DrawerBase.prototype = { }, /** + * @private + * @inner * This function converts the given point from to the drawer coordinate by * multiplying it with the pixel density. * This function does not take rotation into account, thus assuming provided @@ -232,7 +241,7 @@ $.DrawerBase.prototype = { * @param {OpenSeadragon.Point} point - the pixel point to convert * @returns {OpenSeadragon.Point} Point in drawer coordinate system. */ - viewportCoordToDrawerCoord: function(point) { + _viewportCoordToDrawerCoord: function(point) { var vpPoint = this.viewport.pixelFromPointNoRotate(point, true); return new $.Point( vpPoint.x * $.pixelDensityRatio, @@ -240,8 +249,13 @@ $.DrawerBase.prototype = { ); }, - - // private + /** + * @private + * @inner + * Calculate width and height of the canvas based on viewport dimensions + * and pixelDensityRatio + * @returns {Dictionary} {x, y} size of the canvas + */ _calculateCanvasSize: function() { var pixelDensityRatio = $.pixelDensityRatio; var viewportSize = this.viewport.getContainerSize(); @@ -252,86 +266,6 @@ $.DrawerBase.prototype = { }; }, - - /* Deprecated Functions */ - - // deprecated - addOverlay: function( element, location, placement, onDraw ) { - $.console.error("drawer.addOverlay is deprecated. Use viewer.addOverlay instead."); - this.viewer.addOverlay( element, location, placement, onDraw ); - return this; - }, - - // deprecated - updateOverlay: function( element, location, placement ) { - $.console.error("drawer.updateOverlay is deprecated. Use viewer.updateOverlay instead."); - this.viewer.updateOverlay( element, location, placement ); - return this; - }, - - // deprecated - removeOverlay: function( element ) { - $.console.error("drawer.removeOverlay is deprecated. Use viewer.removeOverlay instead."); - this.viewer.removeOverlay( element ); - return this; - }, - - // deprecated - clearOverlays: function() { - $.console.error("drawer.clearOverlays is deprecated. Use viewer.clearOverlays instead."); - this.viewer.clearOverlays(); - return this; - }, - // deprecated - needsUpdate: function() { - $.console.error( "[Drawer.needsUpdate] this function is deprecated. Use World.needsDraw instead." ); - return this.viewer.world.needsDraw(); - }, - - // deprecated - numTilesLoaded: function() { - $.console.error( "[Drawer.numTilesLoaded] this function is deprecated. Use TileCache.numTilesLoaded instead." ); - return this.viewer.tileCache.numTilesLoaded(); - }, - - // deprecated - reset: function() { - $.console.error( "[Drawer.reset] this function is deprecated. Use World.resetItems instead." ); - this.viewer.world.resetItems(); - return this; - }, - - // deprecated - update: function() { - $.console.error( "[Drawer.update] this function is deprecated. Use Drawer.clear and World.draw instead." ); - this.clear(); - this.viewer.world.draw(); - return this; - }, - - // deprecated - setOpacity: function( opacity ) { - $.console.error("drawer.setOpacity is deprecated. Use tiledImage.setOpacity instead."); - var world = this.viewer.world; - for (var i = 0; i < world.getItemCount(); i++) { - world.getItemAt( i ).setOpacity( opacity ); - } - return this; - }, - - // deprecated - getOpacity: function() { - $.console.error("drawer.getOpacity is deprecated. Use tiledImage.getOpacity instead."); - var world = this.viewer.world; - var maxOpacity = 0; - for (var i = 0; i < world.getItemCount(); i++) { - var opacity = world.getItemAt( i ).getOpacity(); - if ( opacity > maxOpacity ) { - maxOpacity = opacity; - } - } - return maxOpacity; - }, }; Object.defineProperty($.DrawerBase.prototype, "isOpenSeadragonDrawer", { diff --git a/src/navigator.js b/src/navigator.js index 0665b58d..7b9d9a63 100644 --- a/src/navigator.js +++ b/src/navigator.js @@ -310,7 +310,6 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /* this.viewport.resize( containerSize, true ); this.viewport.goHome(true); this.oldContainerSize = containerSize; - this.drawer.clear(); this.world.draw(); } } diff --git a/src/tiledimage.js b/src/tiledimage.js index e18bc05f..ddb23aff 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -1238,6 +1238,10 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag }; }, + /** + * + * @returns {Array} Array of Tiles within the viewport which should be drawn + */ getTileInfoForDrawing: function(){ return this._tilesToDraw; }, @@ -1342,9 +1346,6 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag } else { this._setFullyLoaded(this._tilesLoading === 0); } - - - // return bestTile; }, /** diff --git a/src/viewer.js b/src/viewer.js index 587c7ccb..cbcd80db 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -3811,7 +3811,6 @@ function updateOnce( viewer ) { function drawWorld( viewer ) { viewer.imageLoader.clear(); - viewer.drawer.clear(); viewer.world.draw(); /** diff --git a/src/world.js b/src/world.js index 3763d237..c355d8e0 100644 --- a/src/world.js +++ b/src/world.js @@ -256,10 +256,7 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W * Draws all items. */ draw: function() { - for ( var i = 0; i < this._items.length; i++ ) { - this.viewer.drawer.draw(this._items[i]); - } - + this.viewer.drawer.draw(this._items); this._needsDraw = false; }, diff --git a/test/demo/collections/main.js b/test/demo/collections/main.js index 83563085..136c3417 100644 --- a/test/demo/collections/main.js +++ b/test/demo/collections/main.js @@ -131,8 +131,8 @@ var box = new OpenSeadragon.Rect(margins.left, margins.top, $('#contentDiv').width() - (margins.left + margins.right), $('#contentDiv').height() - (margins.top + margins.bottom)); - - self.viewer.drawer.debugRect(box); + // if drawDebuggingRect is implemented, use it to show the box + self.viewer.drawer.drawDebuggingRect(box); }); } diff --git a/test/demo/threejsdrawer.js b/test/demo/threejsdrawer.js index 32719110..6013d212 100644 --- a/test/demo/threejsdrawer.js +++ b/test/demo/threejsdrawer.js @@ -106,7 +106,9 @@ export class ThreeJSDrawer extends OpenSeadragon.DrawerBase{ } // Public API required by all Drawer implementations - + /** + * Clean up the renderer, removing all resources + */ destroy(){ // clear all resources used by the renderer, geometries, textures etc @@ -123,26 +125,61 @@ export class ThreeJSDrawer extends OpenSeadragon.DrawerBase{ this._camera = null; } - clear(){ - //not needed by this implementation - } + + // Public API required by all Drawer implementations + /** + * + * @returns true if the drawer supports rotation + */ canRotate(){ return true; } - draw(tiledImage){ + + // Public API required by all Drawer implementations + /** + * + * @param {Array} tiledImages Array of TiledImage objects to draw + */ + draw(tiledImages){ // actual drawing is handled by event listeneners - // just mark this tiledImage as having been drawn (possibly unnecessary) - tiledImage._needsDraw = false; + // just mark the tiledImages as having been drawn (possibly unnecessary) + tiledImages.forEach(tiledImage => tiledImage._needsDraw = false); } + + // Public API required by all Drawer implementations + /** + * Set the context2d imageSmoothingEnabled parameter + * @param {Boolean} enabled + */ setImageSmoothingEnabled(enabled){ this._clippingContext.imageSmoothingEnabled = enabled; this._outputContext.imageSmoothingEnabled = enabled; } + /** + * Draw a rect onto the output canvas for debugging purposes + * @param {OpenSeadragon.Rect} rect + */ + drawDebuggingRect(rect){ + let context = this._outputContext; + context.save(); + context.lineWidth = 2 * OpenSeadragon.pixelDensityRatio; + context.strokeStyle = this.debugGridColor[0]; + context.fillStyle = this.debugGridColor[0]; + + context.strokeRect( + rect.x * OpenSeadragon.pixelDensityRatio, + rect.y * OpenSeadragon.pixelDensityRatio, + rect.width * OpenSeadragon.pixelDensityRatio, + rect.height * OpenSeadragon.pixelDensityRatio + ); + + context.restore(); + } + // Private methods _setupRenderer(){ - //to do: test support for pages of sequence mode let viewerBounds = this.viewer.viewport.getBoundsNoRotate(); this._camera = new THREE.OrthographicCamera( @@ -171,13 +208,6 @@ export class ThreeJSDrawer extends OpenSeadragon.DrawerBase{ this.viewer.addHandler("viewport-change", () => this._viewportChangeHandler()); this.viewer.addHandler("home", () => this._viewportChangeHandler()); - // this.viewer.world.addHandler("item-index-change", () => this.renderFrame()); - // this.viewer.addHandler("crop-change", () => this.renderFrame()); - // this.viewer.addHandler("clip-change", () => this.renderFrame()); - // this.viewer.addHandler("opacity-change", () => this.renderFrame()); - // this.viewer.addHandler("composite-operation-change", () => this.renderFrame()); - - this.viewer.addHandler("update-viewport", () => this.renderFrame()); this._viewportChangeHandler(); @@ -476,7 +506,7 @@ export class ThreeJSDrawer extends OpenSeadragon.DrawerBase{ if(item._clip){ var box = item.imageToViewportRectangle(item._clip, true); - var rect = this.viewportToDrawerRectangle(box); + var rect = this._viewportToDrawerRectangle(box); this._clippingContext.beginPath(); this._clippingContext.rect(rect.x, rect.y, rect.width, rect.height); this._clippingContext.clip(); @@ -486,7 +516,7 @@ export class ThreeJSDrawer extends OpenSeadragon.DrawerBase{ return polygon.map(function (coord) { let point = item.imageToViewportCoordinates(coord.x, coord.y, true) .rotate(_this.viewer.viewport.getRotation(true), _this.viewer.viewport.getCenter(true)); - let clipPoint = _this.viewportCoordToDrawerCoord(point); + let clipPoint = _this._viewportCoordToDrawerCoord(point); return clipPoint; }); }); @@ -625,6 +655,7 @@ export class ThreeJSDrawer extends OpenSeadragon.DrawerBase{ context.restore(); } + // private _drawDebugInfo( tiledImage ) { let scene = this._tiledImageMap[tiledImage[this._uuid]]; let level = scene.userData.currentLevel; @@ -641,25 +672,6 @@ export class ThreeJSDrawer extends OpenSeadragon.DrawerBase{ } } - // private - _debugRect(rect) { - if ( this.useCanvas ) { - var context = this._outputContext; - context.save(); - context.lineWidth = 2 * OpenSeadragon.pixelDensityRatio; - context.strokeStyle = this.debugGridColor[0]; - context.fillStyle = this.debugGridColor[0]; - - context.strokeRect( - rect.x * OpenSeadragon.pixelDensityRatio, - rect.y * OpenSeadragon.pixelDensityRatio, - rect.width * OpenSeadragon.pixelDensityRatio, - rect.height * OpenSeadragon.pixelDensityRatio - ); - - context.restore(); - } - } // private _restoreRotationChanges() { diff --git a/test/demo/webgl.html b/test/demo/webgl.html index f3aa1c78..6d184feb 100644 --- a/test/demo/webgl.html +++ b/test/demo/webgl.html @@ -111,7 +111,7 @@

Image options (drag and drop to re-order images)

-
+
diff --git a/test/demo/webgl.js b/test/demo/webgl.js index 5ccd5d30..a44fbc65 100644 --- a/test/demo/webgl.js +++ b/test/demo/webgl.js @@ -10,11 +10,24 @@ const sources = { type:'image', url: "../data/BBlue.png", }, + "duomo":"https://openseadragon.github.io/example-images/duomo/duomo.dzi", +} +const labels = { + rainbow: 'Rainbow Grid', + leaves: 'Leaves', + bblue: 'Blue B', + duomo: 'Duomo', } var viewer = window.viewer = OpenSeadragon({ id: "contentDiv", prefixUrl: "../../build/openseadragon/images/", + // minZoomImageRatio:0.8, + // maxZoomPixelRatio:0.5, minZoomImageRatio:0.01, + maxZoomPixelRatio:100, + smoothTileEdgesMinZoom:1.1, + crossOriginPolicy: 'Anonymous', + ajaxWithCredentials: false }); let threeRenderer = window.threeRenderer = new ThreeJSDrawer({viewer, viewport: viewer.viewport, element:viewer.element}); @@ -24,8 +37,11 @@ var viewer2 = window.viewer2 = OpenSeadragon({ prefixUrl: "../../build/openseadragon/images/", minZoomImageRatio:0.01, customDrawer: ThreeJSDrawer, - tileSources: sources['leaves'], + tileSources: [sources['leaves'], sources['rainbow'], sources['duomo']], + sequenceMode: true, imageSmoothingEnabled: false, + crossOriginPolicy: 'Anonymous', + ajaxWithCredentials: false }); //make the test canvas mirror all changes to the viewer canvas @@ -54,6 +70,13 @@ $('#image-picker').sortable({ } }); +Object.keys(sources).forEach((key, index)=>{ + let element = makeImagePickerElement(key, labels[key]) + $('#image-picker').append(element); + if(index === 0){ + element.find('.toggle').prop('checked',true); + } +}) $('#image-picker input.toggle').on('change',function(){ let data = $(this).data(); @@ -193,7 +216,26 @@ function addTileSource(image, checkbox){ } } - +function makeImagePickerElement(key, label){ + return $(`
+ + +
+ + + + + + + + + + +
+ +
`.replaceAll('data-image=""', `data-image="${key}"`).replace('__title__', label)); + +} diff --git a/test/modules/tiledimage.js b/test/modules/tiledimage.js index a6b962f7..a580e188 100644 --- a/test/modules/tiledimage.js +++ b/test/modules/tiledimage.js @@ -181,7 +181,8 @@ done(); }); - image.draw(); + //image.draw(); // TO DO: Is this necessary for the test? It will now fail since tiledImage.draw() is not a thing. + viewer.drawer.draw( [ image ] ); }); viewer.open('/test/data/testpattern.dzi'); @@ -225,7 +226,7 @@ image.setClip(clip); assert.propEqual(image.getClip(), clip, 'clip is set correctly'); - Util.spyOnce(viewer.drawer, 'setClip', function(rect) { + Util.spyOnce(viewer.drawer, '_setClip', function(rect) { var homeBounds = viewer.viewport.getHomeBounds(); var canvasClip = viewer.drawer .viewportToDrawerRectangle(homeBounds);