var viewer = new OpenSeadragon.Viewer(options);
var viewer = OpenSeadragon(options);
rendered
is the context with the pre-drawn image.\n * @param {Number} [scale=1] - Apply a scale to position and size\n * @param {OpenSeadragon.Point} [translate] - A translation vector\n */\n drawCanvas: function( context, drawingHandler, scale, translate ) {\n\n var position = this.position.times($.pixelDensityRatio),\n size = this.size.times($.pixelDensityRatio),\n rendered;\n\n if (!this.context2D && !this.cacheImageRecord) {\n $.console.warn(\n '[Tile.drawCanvas] attempting to draw tile %s when it\\'s not cached',\n this.toString());\n return;\n }\n\n rendered = this.context2D || this.cacheImageRecord.getRenderedContext();\n\n if ( !this.loaded || !rendered ){\n $.console.warn(\n \"Attempting to draw tile %s when it's not yet loaded.\",\n this.toString()\n );\n\n return;\n }\n\n context.save();\n\n context.globalAlpha = this.opacity;\n\n if (typeof scale === 'number' && scale !== 1) {\n // draw tile at a different scale\n position = position.times(scale);\n size = size.times(scale);\n }\n\n if (translate instanceof $.Point) {\n // shift tile position slightly\n position = position.plus(translate);\n }\n\n //if we are supposed to be rendering fully opaque rectangle,\n //ie its done fading or fading is turned off, and if we are drawing\n //an image with an alpha channel, then the only way\n //to avoid seeing the tile underneath is to clear the rectangle\n if (context.globalAlpha === 1 && this._hasTransparencyChannel()) {\n //clearing only the inside of the rectangle occupied\n //by the png prevents edge flikering\n context.clearRect(\n position.x,\n position.y,\n size.x,\n size.y\n );\n }\n\n // This gives the application a chance to make image manipulation\n // changes as we are rendering the image\n drawingHandler({context: context, tile: this, rendered: rendered});\n\n var sourceWidth, sourceHeight;\n if (this.sourceBounds) {\n sourceWidth = Math.min(this.sourceBounds.width, rendered.canvas.width);\n sourceHeight = Math.min(this.sourceBounds.height, rendered.canvas.height);\n } else {\n sourceWidth = rendered.canvas.width;\n sourceHeight = rendered.canvas.height;\n }\n\n context.drawImage(\n rendered.canvas,\n 0,\n 0,\n sourceWidth,\n sourceHeight,\n position.x,\n position.y,\n size.x,\n size.y\n );\n\n context.restore();\n },\n\n /**\n * Get the ratio between current and original size.\n * @function\n * @return {Float}\n */\n getScaleForEdgeSmoothing: function() {\n var context;\n if (this.cacheImageRecord) {\n context = this.cacheImageRecord.getRenderedContext();\n } else if (this.context2D) {\n context = this.context2D;\n } else {\n $.console.warn(\n '[Tile.drawCanvas] attempting to get tile scale %s when tile\\'s not cached',\n this.toString());\n return 1;\n }\n return context.canvas.width / (this.size.x * $.pixelDensityRatio);\n },\n\n /**\n * Get a translation vector that when applied to the tile position produces integer coordinates.\n * Needed to avoid swimming and twitching.\n * @function\n * @param {Number} [scale=1] - Scale to be applied to position.\n * @return {OpenSeadragon.Point}\n */\n getTranslationForEdgeSmoothing: function(scale, canvasSize, sketchCanvasSize) {\n // The translation vector must have positive values, otherwise the image goes a bit off\n // the sketch canvas to the top and left and we must use negative coordinates to repaint it\n // to the main canvas. In that case, some browsers throw:\n // INDEX_SIZE_ERR: DOM Exception 1: Index or size was negative, or greater than the allowed value.\n var x = Math.max(1, Math.ceil((sketchCanvasSize.x - canvasSize.x) / 2));\n var y = Math.max(1, Math.ceil((sketchCanvasSize.y - canvasSize.y) / 2));\n return new $.Point(x, y).minus(\n this.position\n .times($.pixelDensityRatio)\n .times(scale || 1)\n .apply(function(x) {\n return x % 1;\n })\n );\n },\n\n /**\n * Removes tile from its container.\n * @function\n */\n unload: function() {\n if ( this.imgElement && this.imgElement.parentNode ) {\n this.imgElement.parentNode.removeChild( this.imgElement );\n }\n if ( this.element && this.element.parentNode ) {\n this.element.parentNode.removeChild( this.element );\n }\n\n this.element = null;\n this.imgElement = null;\n this.loaded = false;\n this.loading = false;\n }\n};\n\n}( OpenSeadragon ));\n\n/*\n * OpenSeadragon - Overlay\n *\n * Copyright (C) 2009 CodePlex Foundation\n * Copyright (C) 2010-2013 OpenSeadragon contributors\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are\n * met:\n *\n * - Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * - Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n *\n * - Neither the name of CodePlex Foundation nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\n * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n(function($) {\n\n /**\n * An enumeration of positions that an overlay may be assigned relative to\n * the viewport.\n * It is identical to OpenSeadragon.Placement but is kept for backward\n * compatibility.\n * @member OverlayPlacement\n * @memberof OpenSeadragon\n * @see OpenSeadragon.Placement\n * @static\n * @readonly\n * @type {Object}\n * @property {Number} CENTER\n * @property {Number} TOP_LEFT\n * @property {Number} TOP\n * @property {Number} TOP_RIGHT\n * @property {Number} RIGHT\n * @property {Number} BOTTOM_RIGHT\n * @property {Number} BOTTOM\n * @property {Number} BOTTOM_LEFT\n * @property {Number} LEFT\n */\n $.OverlayPlacement = $.Placement;\n\n /**\n * An enumeration of possible ways to handle overlays rotation\n * @member OverlayRotationMode\n * @memberOf OpenSeadragon\n * @static\n * @readonly\n * @property {Number} NO_ROTATION The overlay ignore the viewport rotation.\n * @property {Number} EXACT The overlay use CSS 3 transforms to rotate with\n * the viewport. If the overlay contains text, it will get rotated as well.\n * @property {Number} BOUNDING_BOX The overlay adjusts for rotation by\n * taking the size of the bounding box of the rotated bounds.\n * Only valid for overlays with Rect location and scalable in both directions.\n */\n $.OverlayRotationMode = $.freezeObject({\n NO_ROTATION: 1,\n EXACT: 2,\n BOUNDING_BOX: 3\n });\n\n /**\n * @class Overlay\n * @classdesc Provides a way to float an HTML element on top of the viewer element.\n *\n * @memberof OpenSeadragon\n * @param {Object} options\n * @param {Element} options.element\n * @param {OpenSeadragon.Point|OpenSeadragon.Rect} options.location - The\n * location of the overlay on the image. If a {@link OpenSeadragon.Point}\n * is specified, the overlay will be located at this location with respect\n * to the placement option. If a {@link OpenSeadragon.Rect} is specified,\n * the overlay will be placed at this location with the corresponding width\n * and height and placement TOP_LEFT.\n * @param {OpenSeadragon.Placement} [options.placement=OpenSeadragon.Placement.TOP_LEFT]\n * Defines what part of the overlay should be at the specified options.location\n * @param {OpenSeadragon.Overlay.OnDrawCallback} [options.onDraw]\n * @param {Boolean} [options.checkResize=true] Set to false to avoid to\n * check the size of the overlay everytime it is drawn in the directions\n * which are not scaled. It will improve performances but will cause a\n * misalignment if the overlay size changes.\n * @param {Number} [options.width] The width of the overlay in viewport\n * coordinates. If specified, the width of the overlay will be adjusted when\n * the zoom changes.\n * @param {Number} [options.height] The height of the overlay in viewport\n * coordinates. If specified, the height of the overlay will be adjusted when\n * the zoom changes.\n * @param {Boolean} [options.rotationMode=OpenSeadragon.OverlayRotationMode.EXACT]\n * How to handle the rotation of the viewport.\n */\n $.Overlay = function(element, location, placement) {\n\n /**\n * onDraw callback signature used by {@link OpenSeadragon.Overlay}.\n *\n * @callback OnDrawCallback\n * @memberof OpenSeadragon.Overlay\n * @param {OpenSeadragon.Point} position\n * @param {OpenSeadragon.Point} size\n * @param {Element} element\n */\n\n var options;\n if ($.isPlainObject(element)) {\n options = element;\n } else {\n options = {\n element: element,\n location: location,\n placement: placement\n };\n }\n\n this.element = options.element;\n this.style = options.element.style;\n this._init(options);\n };\n\n /** @lends OpenSeadragon.Overlay.prototype */\n $.Overlay.prototype = {\n\n // private\n _init: function(options) {\n this.location = options.location;\n this.placement = options.placement === undefined ?\n $.Placement.TOP_LEFT : options.placement;\n this.onDraw = options.onDraw;\n this.checkResize = options.checkResize === undefined ?\n true : options.checkResize;\n\n // When this.width is not null, the overlay get scaled horizontally\n this.width = options.width === undefined ? null : options.width;\n\n // When this.height is not null, the overlay get scaled vertically\n this.height = options.height === undefined ? null : options.height;\n\n this.rotationMode = options.rotationMode || $.OverlayRotationMode.EXACT;\n\n // Having a rect as location is a syntactic sugar\n if (this.location instanceof $.Rect) {\n this.width = this.location.width;\n this.height = this.location.height;\n this.location = this.location.getTopLeft();\n this.placement = $.Placement.TOP_LEFT;\n }\n\n // Deprecated properties kept for backward compatibility.\n this.scales = this.width !== null && this.height !== null;\n this.bounds = new $.Rect(\n this.location.x, this.location.y, this.width, this.height);\n this.position = this.location;\n },\n\n /**\n * Internal function to adjust the position of an overlay\n * depending on it size and placement.\n * @function\n * @param {OpenSeadragon.Point} position\n * @param {OpenSeadragon.Point} size\n */\n adjust: function(position, size) {\n var properties = $.Placement.properties[this.placement];\n if (!properties) {\n return;\n }\n if (properties.isHorizontallyCentered) {\n position.x -= size.x / 2;\n } else if (properties.isRight) {\n position.x -= size.x;\n }\n if (properties.isVerticallyCentered) {\n position.y -= size.y / 2;\n } else if (properties.isBottom) {\n position.y -= size.y;\n }\n },\n\n /**\n * @function\n */\n destroy: function() {\n var element = this.element;\n var style = this.style;\n\n if (element.parentNode) {\n element.parentNode.removeChild(element);\n //this should allow us to preserve overlays when required between\n //pages\n if (element.prevElementParent) {\n style.display = 'none';\n //element.prevElementParent.insertBefore(\n // element,\n // element.prevNextSibling\n //);\n document.body.appendChild(element);\n }\n }\n\n // clear the onDraw callback\n this.onDraw = null;\n\n style.top = \"\";\n style.left = \"\";\n style.position = \"\";\n\n if (this.width !== null) {\n style.width = \"\";\n }\n if (this.height !== null) {\n style.height = \"\";\n }\n var transformOriginProp = $.getCssPropertyWithVendorPrefix(\n 'transformOrigin');\n var transformProp = $.getCssPropertyWithVendorPrefix(\n 'transform');\n if (transformOriginProp && transformProp) {\n style[transformOriginProp] = \"\";\n style[transformProp] = \"\";\n }\n },\n\n /**\n * @function\n * @param {Element} container\n */\n drawHTML: function(container, viewport) {\n var element = this.element;\n if (element.parentNode !== container) {\n //save the source parent for later if we need it\n element.prevElementParent = element.parentNode;\n element.prevNextSibling = element.nextSibling;\n container.appendChild(element);\n\n // have to set position before calculating size, fix #1116\n this.style.position = \"absolute\";\n // this.size is used by overlays which don't get scaled in at\n // least one direction when this.checkResize is set to false.\n this.size = $.getElementSize(element);\n }\n\n var positionAndSize = this._getOverlayPositionAndSize(viewport);\n\n var position = positionAndSize.position;\n var size = this.size = positionAndSize.size;\n var rotate = positionAndSize.rotate;\n\n // call the onDraw callback if it exists to allow one to overwrite\n // the drawing/positioning/sizing of the overlay\n if (this.onDraw) {\n this.onDraw(position, size, this.element);\n } else {\n var style = this.style;\n style.left = position.x + \"px\";\n style.top = position.y + \"px\";\n if (this.width !== null) {\n style.width = size.x + \"px\";\n }\n if (this.height !== null) {\n style.height = size.y + \"px\";\n }\n var transformOriginProp = $.getCssPropertyWithVendorPrefix(\n 'transformOrigin');\n var transformProp = $.getCssPropertyWithVendorPrefix(\n 'transform');\n if (transformOriginProp && transformProp) {\n if (rotate) {\n style[transformOriginProp] = this._getTransformOrigin();\n style[transformProp] = \"rotate(\" + rotate + \"deg)\";\n } else {\n style[transformOriginProp] = \"\";\n style[transformProp] = \"\";\n }\n }\n\n if (style.display !== 'none') {\n style.display = 'block';\n }\n }\n },\n\n // private\n _getOverlayPositionAndSize: function(viewport) {\n var position = viewport.pixelFromPoint(this.location, true);\n var size = this._getSizeInPixels(viewport);\n this.adjust(position, size);\n\n var rotate = 0;\n if (viewport.degrees &&\n this.rotationMode !== $.OverlayRotationMode.NO_ROTATION) {\n // BOUNDING_BOX is only valid if both directions get scaled.\n // Get replaced by EXACT otherwise.\n if (this.rotationMode === $.OverlayRotationMode.BOUNDING_BOX &&\n this.width !== null && this.height !== null) {\n var rect = new $.Rect(position.x, position.y, size.x, size.y);\n var boundingBox = this._getBoundingBox(rect, viewport.degrees);\n position = boundingBox.getTopLeft();\n size = boundingBox.getSize();\n } else {\n rotate = viewport.degrees;\n }\n }\n\n return {\n position: position,\n size: size,\n rotate: rotate\n };\n },\n\n // private\n _getSizeInPixels: function(viewport) {\n var width = this.size.x;\n var height = this.size.y;\n if (this.width !== null || this.height !== null) {\n var scaledSize = viewport.deltaPixelsFromPointsNoRotate(\n new $.Point(this.width || 0, this.height || 0), true);\n if (this.width !== null) {\n width = scaledSize.x;\n }\n if (this.height !== null) {\n height = scaledSize.y;\n }\n }\n if (this.checkResize &&\n (this.width === null || this.height === null)) {\n var eltSize = this.size = $.getElementSize(this.element);\n if (this.width === null) {\n width = eltSize.x;\n }\n if (this.height === null) {\n height = eltSize.y;\n }\n }\n return new $.Point(width, height);\n },\n\n // private\n _getBoundingBox: function(rect, degrees) {\n var refPoint = this._getPlacementPoint(rect);\n return rect.rotate(degrees, refPoint).getBoundingBox();\n },\n\n // private\n _getPlacementPoint: function(rect) {\n var result = new $.Point(rect.x, rect.y);\n var properties = $.Placement.properties[this.placement];\n if (properties) {\n if (properties.isHorizontallyCentered) {\n result.x += rect.width / 2;\n } else if (properties.isRight) {\n result.x += rect.width;\n }\n if (properties.isVerticallyCentered) {\n result.y += rect.height / 2;\n } else if (properties.isBottom) {\n result.y += rect.height;\n }\n }\n return result;\n },\n\n // private\n _getTransformOrigin: function() {\n var result = \"\";\n var properties = $.Placement.properties[this.placement];\n if (!properties) {\n return result;\n }\n if (properties.isLeft) {\n result = \"left\";\n } else if (properties.isRight) {\n result = \"right\";\n }\n if (properties.isTop) {\n result += \" top\";\n } else if (properties.isBottom) {\n result += \" bottom\";\n }\n return result;\n },\n\n /**\n * Changes the overlay settings.\n * @function\n * @param {OpenSeadragon.Point|OpenSeadragon.Rect|Object} location\n * If an object is specified, the options are the same than the constructor\n * except for the element which can not be changed.\n * @param {OpenSeadragon.Placement} placement\n */\n update: function(location, placement) {\n var options = $.isPlainObject(location) ? location : {\n location: location,\n placement: placement\n };\n this._init({\n location: options.location || this.location,\n placement: options.placement !== undefined ?\n options.placement : this.placement,\n onDraw: options.onDraw || this.onDraw,\n checkResize: options.checkResize || this.checkResize,\n width: options.width !== undefined ? options.width : this.width,\n height: options.height !== undefined ? options.height : this.height,\n rotationMode: options.rotationMode || this.rotationMode\n });\n },\n\n /**\n * Returns the current bounds of the overlay in viewport coordinates\n * @function\n * @param {OpenSeadragon.Viewport} viewport the viewport\n * @returns {OpenSeadragon.Rect} overlay bounds\n */\n getBounds: function(viewport) {\n $.console.assert(viewport,\n 'A viewport must now be passed to Overlay.getBounds.');\n var width = this.width;\n var height = this.height;\n if (width === null || height === null) {\n var size = viewport.deltaPointsFromPixelsNoRotate(this.size, true);\n if (width === null) {\n width = size.x;\n }\n if (height === null) {\n height = size.y;\n }\n }\n var location = this.location.clone();\n this.adjust(location, new $.Point(width, height));\n return this._adjustBoundsForRotation(\n viewport, new $.Rect(location.x, location.y, width, height));\n },\n\n // private\n _adjustBoundsForRotation: function(viewport, bounds) {\n if (!viewport ||\n viewport.degrees === 0 ||\n this.rotationMode === $.OverlayRotationMode.EXACT) {\n return bounds;\n }\n if (this.rotationMode === $.OverlayRotationMode.BOUNDING_BOX) {\n // If overlay not fully scalable, BOUNDING_BOX falls back to EXACT\n if (this.width === null || this.height === null) {\n return bounds;\n }\n // It is easier to just compute the position and size and\n // convert to viewport coordinates.\n var positionAndSize = this._getOverlayPositionAndSize(viewport);\n return viewport.viewerElementToViewportRectangle(new $.Rect(\n positionAndSize.position.x,\n positionAndSize.position.y,\n positionAndSize.size.x,\n positionAndSize.size.y));\n }\n\n // NO_ROTATION case\n return bounds.rotate(-viewport.degrees,\n this._getPlacementPoint(bounds));\n }\n };\n\n}(OpenSeadragon));\n\n/*\n * OpenSeadragon - Drawer\n *\n * Copyright (C) 2009 CodePlex Foundation\n * Copyright (C) 2010-2013 OpenSeadragon contributors\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are\n * met:\n *\n * - Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * - Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n *\n * - Neither the name of CodePlex Foundation nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\n * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n(function( $ ){\n\n/**\n * @class Drawer\n * @memberof OpenSeadragon\n * @classdesc Handles rendering of tiles for an {@link OpenSeadragon.Viewer}.\n * @param {Object} options - Options for this Drawer.\n * @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this Drawer.\n * @param {OpenSeadragon.Viewport} options.viewport - Reference to Viewer viewport.\n * @param {Element} options.element - Parent element.\n * @param {Number} [options.debugGridColor] - See debugGridColor in {@link OpenSeadragon.Options} for details.\n */\n$.Drawer = function( options ) {\n\n $.console.assert( options.viewer, \"[Drawer] options.viewer is required\" );\n\n //backward compatibility for positional args while prefering more\n //idiomatic javascript options object as the only argument\n var args = arguments;\n\n if( !$.isPlainObject( options ) ){\n options = {\n source: args[ 0 ], // Reference to Viewer tile source.\n viewport: args[ 1 ], // Reference to Viewer viewport.\n element: args[ 2 ] // Parent element.\n };\n }\n\n $.console.assert( options.viewport, \"[Drawer] options.viewport is required\" );\n $.console.assert( options.element, \"[Drawer] options.element is required\" );\n\n if ( options.source ) {\n $.console.error( \"[Drawer] options.source is no longer accepted; use TiledImage instead\" );\n }\n\n this.viewer = options.viewer;\n this.viewport = options.viewport;\n this.debugGridColor = typeof options.debugGridColor === 'string' ? [options.debugGridColor] : options.debugGridColor || $.DEFAULT_SETTINGS.debugGridColor;\n if (options.opacity) {\n $.console.error( \"[Drawer] options.opacity is no longer accepted; set the opacity on the TiledImage instead\" );\n }\n\n this.useCanvas = $.supportsCanvas && ( this.viewer ? this.viewer.useCanvas : true );\n /**\n * The parent element of this Drawer instance, passed in when the Drawer was created.\n * The parent of {@link OpenSeadragon.Drawer#canvas}.\n * @member {Element} container\n * @memberof OpenSeadragon.Drawer#\n */\n this.container = $.getElement( options.element );\n /**\n * A <canvas> element if the browser supports them, otherwise a <div> element.\n * Child element of {@link OpenSeadragon.Drawer#container}.\n * @member {Element} canvas\n * @memberof OpenSeadragon.Drawer#\n */\n this.canvas = $.makeNeutralElement( this.useCanvas ? \"canvas\" : \"div\" );\n /**\n * 2d drawing context for {@link OpenSeadragon.Drawer#canvas} if it's a <canvas> element, otherwise null.\n * @member {Object} context\n * @memberof OpenSeadragon.Drawer#\n */\n this.context = this.useCanvas ? this.canvas.getContext( \"2d\" ) : null;\n\n /**\n * Sketch canvas used to temporarily draw tiles which cannot be drawn directly\n * to the main canvas due to opacity. Lazily initialized.\n */\n this.sketchCanvas = null;\n this.sketchContext = null;\n\n /**\n * @member {Element} element\n * @memberof OpenSeadragon.Drawer#\n * @deprecated Alias for {@link OpenSeadragon.Drawer#container}.\n */\n this.element = this.container;\n\n // We force our container to ltr because our drawing math doesn't work in rtl.\n // This issue only affects our canvas renderer, but we do it always for consistency.\n // Note that this means overlays you want to be rtl need to be explicitly set to rtl.\n this.container.dir = 'ltr';\n\n // check canvas available width and height, set canvas width and height such that the canvas backing store is set to the proper pixel density\n if (this.useCanvas) {\n var viewportSize = this._calculateCanvasSize();\n this.canvas.width = viewportSize.x;\n this.canvas.height = viewportSize.y;\n }\n\n this.canvas.style.width = \"100%\";\n this.canvas.style.height = \"100%\";\n this.canvas.style.position = \"absolute\";\n $.setElementOpacity( this.canvas, this.opacity, true );\n\n // explicit left-align\n this.container.style.textAlign = \"left\";\n this.container.appendChild( this.canvas );\n};\n\n/** @lends OpenSeadragon.Drawer.prototype */\n$.Drawer.prototype = {\n // deprecated\n addOverlay: function( element, location, placement, onDraw ) {\n $.console.error(\"drawer.addOverlay is deprecated. Use viewer.addOverlay instead.\");\n this.viewer.addOverlay( element, location, placement, onDraw );\n return this;\n },\n\n // deprecated\n updateOverlay: function( element, location, placement ) {\n $.console.error(\"drawer.updateOverlay is deprecated. Use viewer.updateOverlay instead.\");\n this.viewer.updateOverlay( element, location, placement );\n return this;\n },\n\n // deprecated\n removeOverlay: function( element ) {\n $.console.error(\"drawer.removeOverlay is deprecated. Use viewer.removeOverlay instead.\");\n this.viewer.removeOverlay( element );\n return this;\n },\n\n // deprecated\n clearOverlays: function() {\n $.console.error(\"drawer.clearOverlays is deprecated. Use viewer.clearOverlays instead.\");\n this.viewer.clearOverlays();\n return this;\n },\n\n /**\n * Set the opacity of the drawer.\n * @param {Number} opacity\n * @return {OpenSeadragon.Drawer} Chainable.\n */\n setOpacity: function( opacity ) {\n $.console.error(\"drawer.setOpacity is deprecated. Use tiledImage.setOpacity instead.\");\n var world = this.viewer.world;\n for (var i = 0; i < world.getItemCount(); i++) {\n world.getItemAt( i ).setOpacity( opacity );\n }\n return this;\n },\n\n /**\n * Get the opacity of the drawer.\n * @returns {Number}\n */\n getOpacity: function() {\n $.console.error(\"drawer.getOpacity is deprecated. Use tiledImage.getOpacity instead.\");\n var world = this.viewer.world;\n var maxOpacity = 0;\n for (var i = 0; i < world.getItemCount(); i++) {\n var opacity = world.getItemAt( i ).getOpacity();\n if ( opacity > maxOpacity ) {\n maxOpacity = opacity;\n }\n }\n return maxOpacity;\n },\n\n // deprecated\n needsUpdate: function() {\n $.console.error( \"[Drawer.needsUpdate] this function is deprecated. Use World.needsDraw instead.\" );\n return this.viewer.world.needsDraw();\n },\n\n // deprecated\n numTilesLoaded: function() {\n $.console.error( \"[Drawer.numTilesLoaded] this function is deprecated. Use TileCache.numTilesLoaded instead.\" );\n return this.viewer.tileCache.numTilesLoaded();\n },\n\n // deprecated\n reset: function() {\n $.console.error( \"[Drawer.reset] this function is deprecated. Use World.resetItems instead.\" );\n this.viewer.world.resetItems();\n return this;\n },\n\n // deprecated\n update: function() {\n $.console.error( \"[Drawer.update] this function is deprecated. Use Drawer.clear and World.draw instead.\" );\n this.clear();\n this.viewer.world.draw();\n return this;\n },\n\n /**\n * @return {Boolean} True if rotation is supported.\n */\n canRotate: function() {\n return this.useCanvas;\n },\n\n /**\n * Destroy the drawer (unload current loaded tiles)\n */\n destroy: function() {\n //force unloading of current canvas (1x1 will be gc later, trick not necessarily needed)\n this.canvas.width = 1;\n this.canvas.height = 1;\n this.sketchCanvas = null;\n this.sketchContext = null;\n },\n\n /**\n * Clears the Drawer so it's ready to draw another frame.\n */\n clear: function() {\n this.canvas.innerHTML = \"\";\n if ( this.useCanvas ) {\n var viewportSize = this._calculateCanvasSize();\n if( this.canvas.width != viewportSize.x ||\n this.canvas.height != viewportSize.y ) {\n this.canvas.width = viewportSize.x;\n this.canvas.height = viewportSize.y;\n if ( this.sketchCanvas !== null ) {\n var sketchCanvasSize = this._calculateSketchCanvasSize();\n this.sketchCanvas.width = sketchCanvasSize.x;\n this.sketchCanvas.height = sketchCanvasSize.y;\n }\n }\n this._clear();\n }\n },\n\n _clear: function (useSketch, bounds) {\n if (!this.useCanvas) {\n return;\n }\n var context = this._getContext(useSketch);\n if (bounds) {\n context.clearRect(bounds.x, bounds.y, bounds.width, bounds.height);\n } else {\n var canvas = context.canvas;\n context.clearRect(0, 0, canvas.width, canvas.height);\n }\n },\n\n /**\n * Scale from OpenSeadragon viewer rectangle to drawer rectangle\n * (ignoring rotation)\n * @param {OpenSeadragon.Rect} rectangle - The rectangle in viewport coordinate system.\n * @return {OpenSeadragon.Rect} Rectangle in drawer coordinate system.\n */\n viewportToDrawerRectangle: function(rectangle) {\n var topLeft = this.viewport.pixelFromPointNoRotate(rectangle.getTopLeft(), true);\n var size = this.viewport.deltaPixelsFromPointsNoRotate(rectangle.getSize(), true);\n\n return new $.Rect(\n topLeft.x * $.pixelDensityRatio,\n topLeft.y * $.pixelDensityRatio,\n size.x * $.pixelDensityRatio,\n size.y * $.pixelDensityRatio\n );\n },\n\n /**\n * Draws the given tile.\n * @param {OpenSeadragon.Tile} tile - The tile to draw.\n * @param {Function} drawingHandler - Method for firing the drawing event if using canvas.\n * drawingHandler({context, tile, rendered})\n * @param {Boolean} useSketch - Whether to use the sketch canvas or not.\n * where rendered
is the context with the pre-drawn image.\n * @param {Float} [scale=1] - Apply a scale to tile position and size. Defaults to 1.\n * @param {OpenSeadragon.Point} [translate] A translation vector to offset tile position\n */\n drawTile: function(tile, drawingHandler, useSketch, scale, translate) {\n $.console.assert(tile, '[Drawer.drawTile] tile is required');\n $.console.assert(drawingHandler, '[Drawer.drawTile] drawingHandler is required');\n\n if (this.useCanvas) {\n var context = this._getContext(useSketch);\n scale = scale || 1;\n tile.drawCanvas(context, drawingHandler, scale, translate);\n } else {\n tile.drawHTML( this.canvas );\n }\n },\n\n _getContext: function( useSketch ) {\n var context = this.context;\n if ( useSketch ) {\n if (this.sketchCanvas === null) {\n this.sketchCanvas = document.createElement( \"canvas\" );\n var sketchCanvasSize = this._calculateSketchCanvasSize();\n this.sketchCanvas.width = sketchCanvasSize.x;\n this.sketchCanvas.height = sketchCanvasSize.y;\n this.sketchContext = this.sketchCanvas.getContext( \"2d\" );\n\n // If the viewport is not currently rotated, the sketchCanvas\n // will have the same size as the main canvas. However, if\n // the viewport get rotated later on, we will need to resize it.\n if (this.viewport.getRotation() === 0) {\n var self = this;\n this.viewer.addHandler('rotate', function resizeSketchCanvas() {\n if (self.viewport.getRotation() === 0) {\n return;\n }\n self.viewer.removeHandler('rotate', resizeSketchCanvas);\n var sketchCanvasSize = self._calculateSketchCanvasSize();\n self.sketchCanvas.width = sketchCanvasSize.x;\n self.sketchCanvas.height = sketchCanvasSize.y;\n });\n }\n }\n context = this.sketchContext;\n }\n return context;\n },\n\n // private\n saveContext: function( useSketch ) {\n if (!this.useCanvas) {\n return;\n }\n\n this._getContext( useSketch ).save();\n },\n\n // private\n restoreContext: function( useSketch ) {\n if (!this.useCanvas) {\n return;\n }\n\n this._getContext( useSketch ).restore();\n },\n\n // private\n setClip: function(rect, useSketch) {\n if (!this.useCanvas) {\n return;\n }\n\n var context = this._getContext( useSketch );\n context.beginPath();\n context.rect(rect.x, rect.y, rect.width, rect.height);\n context.clip();\n },\n\n // private\n drawRectangle: function(rect, fillStyle, useSketch) {\n if (!this.useCanvas) {\n return;\n }\n\n var context = this._getContext( useSketch );\n context.save();\n context.fillStyle = fillStyle;\n context.fillRect(rect.x, rect.y, rect.width, rect.height);\n context.restore();\n },\n\n /**\n * Blends the sketch canvas in the main canvas.\n * @param {Object} options The options\n * @param {Float} options.opacity The opacity of the blending.\n * @param {Float} [options.scale=1] The scale at which tiles were drawn on\n * the sketch. Default is 1.\n * Use scale to draw at a lower scale and then enlarge onto the main canvas.\n * @param {OpenSeadragon.Point} [options.translate] A translation vector\n * that was used to draw the tiles\n * @param {String} [options.compositeOperation] - How the image is\n * composited onto other images; see compositeOperation in\n * {@link OpenSeadragon.Options} for possible values.\n * @param {OpenSeadragon.Rect} [options.bounds] The part of the sketch\n * canvas to blend in the main canvas. If specified, options.scale and\n * options.translate get ignored.\n */\n blendSketch: function(opacity, scale, translate, compositeOperation) {\n var options = opacity;\n if (!$.isPlainObject(options)) {\n options = {\n opacity: opacity,\n scale: scale,\n translate: translate,\n compositeOperation: compositeOperation\n };\n }\n if (!this.useCanvas || !this.sketchCanvas) {\n return;\n }\n opacity = options.opacity;\n compositeOperation = options.compositeOperation;\n var bounds = options.bounds;\n\n this.context.save();\n this.context.globalAlpha = opacity;\n if (compositeOperation) {\n this.context.globalCompositeOperation = compositeOperation;\n }\n if (bounds) {\n // Internet Explorer, Microsoft Edge, and Safari have problems\n // when you call context.drawImage with negative x or y\n // or x + width or y + height greater than the canvas width or height respectively.\n if (bounds.x < 0) {\n bounds.width += bounds.x;\n bounds.x = 0;\n }\n if (bounds.x + bounds.width > this.canvas.width) {\n bounds.width = this.canvas.width - bounds.x;\n }\n if (bounds.y < 0) {\n bounds.height += bounds.y;\n bounds.y = 0;\n }\n if (bounds.y + bounds.height > this.canvas.height) {\n bounds.height = this.canvas.height - bounds.y;\n }\n\n this.context.drawImage(\n this.sketchCanvas,\n bounds.x,\n bounds.y,\n bounds.width,\n bounds.height,\n bounds.x,\n bounds.y,\n bounds.width,\n bounds.height\n );\n } else {\n scale = options.scale || 1;\n translate = options.translate;\n var position = translate instanceof $.Point ?\n translate : new $.Point(0, 0);\n\n var widthExt = 0;\n var heightExt = 0;\n if (translate) {\n var widthDiff = this.sketchCanvas.width - this.canvas.width;\n var heightDiff = this.sketchCanvas.height - this.canvas.height;\n widthExt = Math.round(widthDiff / 2);\n heightExt = Math.round(heightDiff / 2);\n }\n this.context.drawImage(\n this.sketchCanvas,\n position.x - widthExt * scale,\n position.y - heightExt * scale,\n (this.canvas.width + 2 * widthExt) * scale,\n (this.canvas.height + 2 * heightExt) * scale,\n -widthExt,\n -heightExt,\n this.canvas.width + 2 * widthExt,\n this.canvas.height + 2 * heightExt\n );\n }\n this.context.restore();\n },\n\n // private\n drawDebugInfo: function(tile, count, i, tiledImage) {\n if ( !this.useCanvas ) {\n return;\n }\n\n var colorIndex = this.viewer.world.getIndexOfItem(tiledImage) % this.debugGridColor.length;\n var context = this.context;\n context.save();\n context.lineWidth = 2 * $.pixelDensityRatio;\n context.font = 'small-caps bold ' + (13 * $.pixelDensityRatio) + 'px arial';\n context.strokeStyle = this.debugGridColor[colorIndex];\n context.fillStyle = this.debugGridColor[colorIndex];\n\n if ( this.viewport.degrees !== 0 ) {\n this._offsetForRotation({degrees: this.viewport.degrees});\n } else{\n if(this.viewer.viewport.flipped) {\n this._flip();\n }\n }\n if (tiledImage.getRotation(true) % 360 !== 0) {\n this._offsetForRotation({\n degrees: tiledImage.getRotation(true),\n point: tiledImage.viewport.pixelFromPointNoRotate(\n tiledImage._getRotationPoint(true), true)\n });\n }\n\n context.strokeRect(\n tile.position.x * $.pixelDensityRatio,\n tile.position.y * $.pixelDensityRatio,\n tile.size.x * $.pixelDensityRatio,\n tile.size.y * $.pixelDensityRatio\n );\n\n var tileCenterX = (tile.position.x + (tile.size.x / 2)) * $.pixelDensityRatio;\n var tileCenterY = (tile.position.y + (tile.size.y / 2)) * $.pixelDensityRatio;\n\n // Rotate the text the right way around.\n context.translate( tileCenterX, tileCenterY );\n context.rotate( Math.PI / 180 * -this.viewport.degrees );\n context.translate( -tileCenterX, -tileCenterY );\n\n if( tile.x === 0 && tile.y === 0 ){\n context.fillText(\n \"Zoom: \" + this.viewport.getZoom(),\n tile.position.x * $.pixelDensityRatio,\n (tile.position.y - 30) * $.pixelDensityRatio\n );\n context.fillText(\n \"Pan: \" + this.viewport.getBounds().toString(),\n tile.position.x * $.pixelDensityRatio,\n (tile.position.y - 20) * $.pixelDensityRatio\n );\n }\n context.fillText(\n \"Level: \" + tile.level,\n (tile.position.x + 10) * $.pixelDensityRatio,\n (tile.position.y + 20) * $.pixelDensityRatio\n );\n context.fillText(\n \"Column: \" + tile.x,\n (tile.position.x + 10) * $.pixelDensityRatio,\n (tile.position.y + 30) * $.pixelDensityRatio\n );\n context.fillText(\n \"Row: \" + tile.y,\n (tile.position.x + 10) * $.pixelDensityRatio,\n (tile.position.y + 40) * $.pixelDensityRatio\n );\n context.fillText(\n \"Order: \" + i + \" of \" + count,\n (tile.position.x + 10) * $.pixelDensityRatio,\n (tile.position.y + 50) * $.pixelDensityRatio\n );\n context.fillText(\n \"Size: \" + tile.size.toString(),\n (tile.position.x + 10) * $.pixelDensityRatio,\n (tile.position.y + 60) * $.pixelDensityRatio\n );\n context.fillText(\n \"Position: \" + tile.position.toString(),\n (tile.position.x + 10) * $.pixelDensityRatio,\n (tile.position.y + 70) * $.pixelDensityRatio\n );\n\n if ( this.viewport.degrees !== 0 ) {\n this._restoreRotationChanges();\n }\n if (tiledImage.getRotation(true) % 360 !== 0) {\n this._restoreRotationChanges();\n }\n context.restore();\n },\n\n // private\n debugRect: function(rect) {\n if ( this.useCanvas ) {\n var context = this.context;\n context.save();\n context.lineWidth = 2 * $.pixelDensityRatio;\n context.strokeStyle = this.debugGridColor[0];\n context.fillStyle = this.debugGridColor[0];\n\n context.strokeRect(\n rect.x * $.pixelDensityRatio,\n rect.y * $.pixelDensityRatio,\n rect.width * $.pixelDensityRatio,\n rect.height * $.pixelDensityRatio\n );\n\n context.restore();\n }\n },\n\n /**\n * Get the canvas size\n * @param {Boolean} sketch If set to true return the size of the sketch canvas\n * @returns {OpenSeadragon.Point} The size of the canvas\n */\n getCanvasSize: function(sketch) {\n var canvas = this._getContext(sketch).canvas;\n return new $.Point(canvas.width, canvas.height);\n },\n\n getCanvasCenter: function() {\n return new $.Point(this.canvas.width / 2, this.canvas.height / 2);\n },\n\n // private\n _offsetForRotation: function(options) {\n var point = options.point ?\n options.point.times($.pixelDensityRatio) :\n this.getCanvasCenter();\n\n var context = this._getContext(options.useSketch);\n context.save();\n\n context.translate(point.x, point.y);\n if(this.viewer.viewport.flipped){\n context.rotate(Math.PI / 180 * -options.degrees);\n context.scale(-1, 1);\n } else{\n context.rotate(Math.PI / 180 * options.degrees);\n }\n context.translate(-point.x, -point.y);\n },\n\n // private\n _flip: function(options) {\n options = options || {};\n var point = options.point ?\n options.point.times($.pixelDensityRatio) :\n this.getCanvasCenter();\n var context = this._getContext(options.useSketch);\n\n context.translate(point.x, 0);\n context.scale(-1, 1);\n context.translate(-point.x, 0);\n },\n\n // private\n _restoreRotationChanges: function(useSketch) {\n var context = this._getContext(useSketch);\n context.restore();\n },\n\n // private\n _calculateCanvasSize: function() {\n var pixelDensityRatio = $.pixelDensityRatio;\n var viewportSize = this.viewport.getContainerSize();\n return {\n x: viewportSize.x * pixelDensityRatio,\n y: viewportSize.y * pixelDensityRatio\n };\n },\n\n // private\n _calculateSketchCanvasSize: function() {\n var canvasSize = this._calculateCanvasSize();\n if (this.viewport.getRotation() === 0) {\n return canvasSize;\n }\n // If the viewport is rotated, we need a larger sketch canvas in order\n // to support edge smoothing.\n var sketchCanvasSize = Math.ceil(Math.sqrt(\n canvasSize.x * canvasSize.x +\n canvasSize.y * canvasSize.y));\n return {\n x: sketchCanvasSize,\n y: sketchCanvasSize\n };\n }\n};\n\n}( OpenSeadragon ));\n\n/*\n * OpenSeadragon - Viewport\n *\n * Copyright (C) 2009 CodePlex Foundation\n * Copyright (C) 2010-2013 OpenSeadragon contributors\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are\n * met:\n *\n * - Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * - Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n *\n * - Neither the name of CodePlex Foundation nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\n * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n(function( $ ){\n\n\n/**\n * @class Viewport\n * @memberof OpenSeadragon\n * @classdesc Handles coordinate-related functionality (zoom, pan, rotation, etc.)\n * for an {@link OpenSeadragon.Viewer}.\n * @param {Object} options - Options for this Viewport.\n * @param {Object} [options.margins] - See viewportMargins in {@link OpenSeadragon.Options}.\n * @param {Number} [options.springStiffness] - See springStiffness in {@link OpenSeadragon.Options}.\n * @param {Number} [options.animationTime] - See animationTime in {@link OpenSeadragon.Options}.\n * @param {Number} [options.minZoomImageRatio] - See minZoomImageRatio in {@link OpenSeadragon.Options}.\n * @param {Number} [options.maxZoomPixelRatio] - See maxZoomPixelRatio in {@link OpenSeadragon.Options}.\n * @param {Number} [options.visibilityRatio] - See visibilityRatio in {@link OpenSeadragon.Options}.\n * @param {Boolean} [options.wrapHorizontal] - See wrapHorizontal in {@link OpenSeadragon.Options}.\n * @param {Boolean} [options.wrapVertical] - See wrapVertical in {@link OpenSeadragon.Options}.\n * @param {Number} [options.defaultZoomLevel] - See defaultZoomLevel in {@link OpenSeadragon.Options}.\n * @param {Number} [options.minZoomLevel] - See minZoomLevel in {@link OpenSeadragon.Options}.\n * @param {Number} [options.maxZoomLevel] - See maxZoomLevel in {@link OpenSeadragon.Options}.\n * @param {Number} [options.degrees] - See degrees in {@link OpenSeadragon.Options}.\n * @param {Boolean} [options.homeFillsViewer] - See homeFillsViewer in {@link OpenSeadragon.Options}.\n */\n$.Viewport = function( options ) {\n\n //backward compatibility for positional args while prefering more\n //idiomatic javascript options object as the only argument\n var args = arguments;\n if (args.length && args[0] instanceof $.Point) {\n options = {\n containerSize: args[0],\n contentSize: args[1],\n config: args[2]\n };\n }\n\n //options.config and the general config argument are deprecated\n //in favor of the more direct specification of optional settings\n //being passed directly on the options object\n if ( options.config ){\n $.extend( true, options, options.config );\n delete options.config;\n }\n\n this._margins = $.extend({\n left: 0,\n top: 0,\n right: 0,\n bottom: 0\n }, options.margins || {});\n\n delete options.margins;\n\n $.extend( true, this, {\n\n //required settings\n containerSize: null,\n contentSize: null,\n\n //internal state properties\n zoomPoint: null,\n viewer: null,\n\n //configurable options\n springStiffness: $.DEFAULT_SETTINGS.springStiffness,\n animationTime: $.DEFAULT_SETTINGS.animationTime,\n minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio,\n maxZoomPixelRatio: $.DEFAULT_SETTINGS.maxZoomPixelRatio,\n visibilityRatio: $.DEFAULT_SETTINGS.visibilityRatio,\n wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal,\n wrapVertical: $.DEFAULT_SETTINGS.wrapVertical,\n defaultZoomLevel: $.DEFAULT_SETTINGS.defaultZoomLevel,\n minZoomLevel: $.DEFAULT_SETTINGS.minZoomLevel,\n maxZoomLevel: $.DEFAULT_SETTINGS.maxZoomLevel,\n degrees: $.DEFAULT_SETTINGS.degrees,\n flipped: $.DEFAULT_SETTINGS.flipped,\n homeFillsViewer: $.DEFAULT_SETTINGS.homeFillsViewer\n\n }, options );\n\n this._updateContainerInnerSize();\n\n this.centerSpringX = new $.Spring({\n initial: 0,\n springStiffness: this.springStiffness,\n animationTime: this.animationTime\n });\n this.centerSpringY = new $.Spring({\n initial: 0,\n springStiffness: this.springStiffness,\n animationTime: this.animationTime\n });\n this.zoomSpring = new $.Spring({\n exponential: true,\n initial: 1,\n springStiffness: this.springStiffness,\n animationTime: this.animationTime\n });\n\n this._oldCenterX = this.centerSpringX.current.value;\n this._oldCenterY = this.centerSpringY.current.value;\n this._oldZoom = this.zoomSpring.current.value;\n\n this._setContentBounds(new $.Rect(0, 0, 1, 1), 1);\n\n this.goHome(true);\n this.update();\n};\n\n/** @lends OpenSeadragon.Viewport.prototype */\n$.Viewport.prototype = {\n /**\n * Updates the viewport's home bounds and constraints for the given content size.\n * @function\n * @param {OpenSeadragon.Point} contentSize - size of the content in content units\n * @return {OpenSeadragon.Viewport} Chainable.\n * @fires OpenSeadragon.Viewer.event:reset-size\n */\n resetContentSize: function(contentSize) {\n $.console.assert(contentSize, \"[Viewport.resetContentSize] contentSize is required\");\n $.console.assert(contentSize instanceof $.Point, \"[Viewport.resetContentSize] contentSize must be an OpenSeadragon.Point\");\n $.console.assert(contentSize.x > 0, \"[Viewport.resetContentSize] contentSize.x must be greater than 0\");\n $.console.assert(contentSize.y > 0, \"[Viewport.resetContentSize] contentSize.y must be greater than 0\");\n\n this._setContentBounds(new $.Rect(0, 0, 1, contentSize.y / contentSize.x), contentSize.x);\n return this;\n },\n\n // deprecated\n setHomeBounds: function(bounds, contentFactor) {\n $.console.error(\"[Viewport.setHomeBounds] this function is deprecated; The content bounds should not be set manually.\");\n this._setContentBounds(bounds, contentFactor);\n },\n\n // Set the viewport's content bounds\n // @param {OpenSeadragon.Rect} bounds - the new bounds in viewport coordinates\n // without rotation\n // @param {Number} contentFactor - how many content units per viewport unit\n // @fires OpenSeadragon.Viewer.event:reset-size\n // @private\n _setContentBounds: function(bounds, contentFactor) {\n $.console.assert(bounds, \"[Viewport._setContentBounds] bounds is required\");\n $.console.assert(bounds instanceof $.Rect, \"[Viewport._setContentBounds] bounds must be an OpenSeadragon.Rect\");\n $.console.assert(bounds.width > 0, \"[Viewport._setContentBounds] bounds.width must be greater than 0\");\n $.console.assert(bounds.height > 0, \"[Viewport._setContentBounds] bounds.height must be greater than 0\");\n\n this._contentBoundsNoRotate = bounds.clone();\n this._contentSizeNoRotate = this._contentBoundsNoRotate.getSize().times(\n contentFactor);\n\n this._contentBounds = bounds.rotate(this.degrees).getBoundingBox();\n this._contentSize = this._contentBounds.getSize().times(contentFactor);\n this._contentAspectRatio = this._contentSize.x / this._contentSize.y;\n\n if (this.viewer) {\n /**\n * Raised when the viewer's content size or home bounds are reset\n * (see {@link OpenSeadragon.Viewport#resetContentSize}).\n *\n * @event reset-size\n * @memberof OpenSeadragon.Viewer\n * @type {object}\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.\n * @property {OpenSeadragon.Point} contentSize\n * @property {OpenSeadragon.Rect} contentBounds - Content bounds.\n * @property {OpenSeadragon.Rect} homeBounds - Content bounds.\n * Deprecated use contentBounds instead.\n * @property {Number} contentFactor\n * @property {?Object} userData - Arbitrary subscriber-defined object.\n */\n this.viewer.raiseEvent('reset-size', {\n contentSize: this._contentSizeNoRotate.clone(),\n contentFactor: contentFactor,\n homeBounds: this._contentBoundsNoRotate.clone(),\n contentBounds: this._contentBounds.clone()\n });\n }\n },\n\n /**\n * Returns the home zoom in \"viewport zoom\" value.\n * @function\n * @returns {Number} The home zoom in \"viewport zoom\".\n */\n getHomeZoom: function() {\n if (this.defaultZoomLevel) {\n return this.defaultZoomLevel;\n }\n\n var aspectFactor = this._contentAspectRatio / this.getAspectRatio();\n var output;\n if (this.homeFillsViewer) { // fill the viewer and clip the image\n output = aspectFactor >= 1 ? aspectFactor : 1;\n } else {\n output = aspectFactor >= 1 ? 1 : aspectFactor;\n }\n\n return output / this._contentBounds.width;\n },\n\n /**\n * Returns the home bounds in viewport coordinates.\n * @function\n * @returns {OpenSeadragon.Rect} The home bounds in vewport coordinates.\n */\n getHomeBounds: function() {\n return this.getHomeBoundsNoRotate().rotate(-this.getRotation());\n },\n\n /**\n * Returns the home bounds in viewport coordinates.\n * This method ignores the viewport rotation. Use\n * {@link OpenSeadragon.Viewport#getHomeBounds} to take it into account.\n * @function\n * @returns {OpenSeadragon.Rect} The home bounds in vewport coordinates.\n */\n getHomeBoundsNoRotate: function() {\n var center = this._contentBounds.getCenter();\n var width = 1.0 / this.getHomeZoom();\n var height = width / this.getAspectRatio();\n\n return new $.Rect(\n center.x - (width / 2.0),\n center.y - (height / 2.0),\n width,\n height\n );\n },\n\n /**\n * @function\n * @param {Boolean} immediately\n * @fires OpenSeadragon.Viewer.event:home\n */\n goHome: function(immediately) {\n if (this.viewer) {\n /**\n * Raised when the \"home\" operation occurs (see {@link OpenSeadragon.Viewport#goHome}).\n *\n * @event home\n * @memberof OpenSeadragon.Viewer\n * @type {object}\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.\n * @property {Boolean} immediately\n * @property {?Object} userData - Arbitrary subscriber-defined object.\n */\n this.viewer.raiseEvent('home', {\n immediately: immediately\n });\n }\n return this.fitBounds(this.getHomeBounds(), immediately);\n },\n\n /**\n * @function\n */\n getMinZoom: function() {\n var homeZoom = this.getHomeZoom(),\n zoom = this.minZoomLevel ?\n this.minZoomLevel :\n this.minZoomImageRatio * homeZoom;\n\n return zoom;\n },\n\n /**\n * @function\n */\n getMaxZoom: function() {\n var zoom = this.maxZoomLevel;\n if (!zoom) {\n zoom = this._contentSize.x * this.maxZoomPixelRatio / this._containerInnerSize.x;\n zoom /= this._contentBounds.width;\n }\n\n return Math.max( zoom, this.getHomeZoom() );\n },\n\n /**\n * @function\n */\n getAspectRatio: function() {\n return this._containerInnerSize.x / this._containerInnerSize.y;\n },\n\n /**\n * @function\n * @returns {OpenSeadragon.Point} The size of the container, in screen coordinates.\n */\n getContainerSize: function() {\n return new $.Point(\n this.containerSize.x,\n this.containerSize.y\n );\n },\n\n /**\n * The margins push the \"home\" region in from the sides by the specified amounts.\n * @function\n * @returns {Object} Properties (Numbers, in screen coordinates): left, top, right, bottom.\n */\n getMargins: function() {\n return $.extend({}, this._margins); // Make a copy so we are not returning our original\n },\n\n /**\n * The margins push the \"home\" region in from the sides by the specified amounts.\n * @function\n * @param {Object} margins - Properties (Numbers, in screen coordinates): left, top, right, bottom.\n */\n setMargins: function(margins) {\n $.console.assert($.type(margins) === 'object', '[Viewport.setMargins] margins must be an object');\n\n this._margins = $.extend({\n left: 0,\n top: 0,\n right: 0,\n bottom: 0\n }, margins);\n\n this._updateContainerInnerSize();\n if (this.viewer) {\n this.viewer.forceRedraw();\n }\n },\n\n /**\n * Returns the bounds of the visible area in viewport coordinates.\n * @function\n * @param {Boolean} current - Pass true for the current location; defaults to false (target location).\n * @returns {OpenSeadragon.Rect} The location you are zoomed/panned to, in viewport coordinates.\n */\n getBounds: function(current) {\n return this.getBoundsNoRotate(current).rotate(-this.getRotation());\n },\n\n /**\n * Returns the bounds of the visible area in viewport coordinates.\n * This method ignores the viewport rotation. Use\n * {@link OpenSeadragon.Viewport#getBounds} to take it into account.\n * @function\n * @param {Boolean} current - Pass true for the current location; defaults to false (target location).\n * @returns {OpenSeadragon.Rect} The location you are zoomed/panned to, in viewport coordinates.\n */\n getBoundsNoRotate: function(current) {\n var center = this.getCenter(current);\n var width = 1.0 / this.getZoom(current);\n var height = width / this.getAspectRatio();\n\n return new $.Rect(\n center.x - (width / 2.0),\n center.y - (height / 2.0),\n width,\n height\n );\n },\n\n /**\n * @function\n * @param {Boolean} current - Pass true for the current location; defaults to false (target location).\n * @returns {OpenSeadragon.Rect} The location you are zoomed/panned to,\n * including the space taken by margins, in viewport coordinates.\n */\n getBoundsWithMargins: function(current) {\n return this.getBoundsNoRotateWithMargins(current).rotate(\n -this.getRotation(), this.getCenter(current));\n },\n\n /**\n * @function\n * @param {Boolean} current - Pass true for the current location; defaults to false (target location).\n * @returns {OpenSeadragon.Rect} The location you are zoomed/panned to,\n * including the space taken by margins, in viewport coordinates.\n */\n getBoundsNoRotateWithMargins: function(current) {\n var bounds = this.getBoundsNoRotate(current);\n var factor = this._containerInnerSize.x * this.getZoom(current);\n bounds.x -= this._margins.left / factor;\n bounds.y -= this._margins.top / factor;\n bounds.width += (this._margins.left + this._margins.right) / factor;\n bounds.height += (this._margins.top + this._margins.bottom) / factor;\n return bounds;\n },\n\n /**\n * @function\n * @param {Boolean} current - Pass true for the current location; defaults to false (target location).\n */\n getCenter: function( current ) {\n var centerCurrent = new $.Point(\n this.centerSpringX.current.value,\n this.centerSpringY.current.value\n ),\n centerTarget = new $.Point(\n this.centerSpringX.target.value,\n this.centerSpringY.target.value\n ),\n oldZoomPixel,\n zoom,\n width,\n height,\n bounds,\n newZoomPixel,\n deltaZoomPixels,\n deltaZoomPoints;\n\n if ( current ) {\n return centerCurrent;\n } else if ( !this.zoomPoint ) {\n return centerTarget;\n }\n\n oldZoomPixel = this.pixelFromPoint(this.zoomPoint, true);\n\n zoom = this.getZoom();\n width = 1.0 / zoom;\n height = width / this.getAspectRatio();\n bounds = new $.Rect(\n centerCurrent.x - width / 2.0,\n centerCurrent.y - height / 2.0,\n width,\n height\n );\n\n newZoomPixel = this._pixelFromPoint(this.zoomPoint, bounds);\n deltaZoomPixels = newZoomPixel.minus( oldZoomPixel );\n deltaZoomPoints = deltaZoomPixels.divide( this._containerInnerSize.x * zoom );\n\n return centerTarget.plus( deltaZoomPoints );\n },\n\n /**\n * @function\n * @param {Boolean} current - Pass true for the current location; defaults to false (target location).\n */\n getZoom: function( current ) {\n if ( current ) {\n return this.zoomSpring.current.value;\n } else {\n return this.zoomSpring.target.value;\n }\n },\n\n // private\n _applyZoomConstraints: function(zoom) {\n return Math.max(\n Math.min(zoom, this.getMaxZoom()),\n this.getMinZoom());\n },\n\n /**\n * @function\n * @private\n * @param {OpenSeadragon.Rect} bounds\n * @return {OpenSeadragon.Rect} constrained bounds.\n */\n _applyBoundaryConstraints: function(bounds) {\n var newBounds = new $.Rect(\n bounds.x,\n bounds.y,\n bounds.width,\n bounds.height);\n\n if (this.wrapHorizontal) {\n //do nothing\n } else {\n var horizontalThreshold = this.visibilityRatio * newBounds.width;\n var boundsRight = newBounds.x + newBounds.width;\n var contentRight = this._contentBoundsNoRotate.x + this._contentBoundsNoRotate.width;\n var leftDx = this._contentBoundsNoRotate.x - boundsRight + horizontalThreshold;\n var rightDx = contentRight - newBounds.x - horizontalThreshold;\n\n if (horizontalThreshold > this._contentBoundsNoRotate.width) {\n newBounds.x += (leftDx + rightDx) / 2;\n } else if (rightDx < 0) {\n newBounds.x += rightDx;\n } else if (leftDx > 0) {\n newBounds.x += leftDx;\n }\n }\n\n if (this.wrapVertical) {\n //do nothing\n } else {\n var verticalThreshold = this.visibilityRatio * newBounds.height;\n var boundsBottom = newBounds.y + newBounds.height;\n var contentBottom = this._contentBoundsNoRotate.y + this._contentBoundsNoRotate.height;\n var topDy = this._contentBoundsNoRotate.y - boundsBottom + verticalThreshold;\n var bottomDy = contentBottom - newBounds.y - verticalThreshold;\n\n if (verticalThreshold > this._contentBoundsNoRotate.height) {\n newBounds.y += (topDy + bottomDy) / 2;\n } else if (bottomDy < 0) {\n newBounds.y += bottomDy;\n } else if (topDy > 0) {\n newBounds.y += topDy;\n }\n }\n\n return newBounds;\n },\n\n /**\n * @function\n * @private\n * @param {Boolean} [immediately=false] - whether the function that triggered this event was\n * called with the \"immediately\" flag\n */\n _raiseConstraintsEvent: function(immediately) {\n if (this.viewer) {\n /**\n * Raised when the viewport constraints are applied (see {@link OpenSeadragon.Viewport#applyConstraints}).\n *\n * @event constrain\n * @memberof OpenSeadragon.Viewer\n * @type {object}\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.\n * @property {Boolean} immediately - whether the function that triggered this event was\n * called with the \"immediately\" flag\n * @property {?Object} userData - Arbitrary subscriber-defined object.\n */\n this.viewer.raiseEvent( 'constrain', {\n immediately: immediately\n });\n }\n },\n\n /**\n * Enforces the minZoom, maxZoom and visibilityRatio constraints by\n * zooming and panning to the closest acceptable zoom and location.\n * @function\n * @param {Boolean} [immediately=false]\n * @return {OpenSeadragon.Viewport} Chainable.\n * @fires OpenSeadragon.Viewer.event:constrain\n */\n applyConstraints: function(immediately) {\n var actualZoom = this.getZoom();\n var constrainedZoom = this._applyZoomConstraints(actualZoom);\n\n if (actualZoom !== constrainedZoom) {\n this.zoomTo(constrainedZoom, this.zoomPoint, immediately);\n }\n\n var bounds = this.getBoundsNoRotate();\n var constrainedBounds = this._applyBoundaryConstraints(bounds);\n this._raiseConstraintsEvent(immediately);\n\n if (bounds.x !== constrainedBounds.x ||\n bounds.y !== constrainedBounds.y ||\n immediately) {\n this.fitBounds(\n constrainedBounds.rotate(-this.getRotation()),\n immediately);\n }\n return this;\n },\n\n /**\n * Equivalent to {@link OpenSeadragon.Viewport#applyConstraints}\n * @function\n * @param {Boolean} [immediately=false]\n * @return {OpenSeadragon.Viewport} Chainable.\n * @fires OpenSeadragon.Viewer.event:constrain\n */\n ensureVisible: function(immediately) {\n return this.applyConstraints(immediately);\n },\n\n /**\n * @function\n * @private\n * @param {OpenSeadragon.Rect} bounds\n * @param {Object} options (immediately=false, constraints=false)\n * @return {OpenSeadragon.Viewport} Chainable.\n */\n _fitBounds: function(bounds, options) {\n options = options || {};\n var immediately = options.immediately || false;\n var constraints = options.constraints || false;\n\n var aspect = this.getAspectRatio();\n var center = bounds.getCenter();\n\n // Compute width and height of bounding box.\n var newBounds = new $.Rect(\n bounds.x,\n bounds.y,\n bounds.width,\n bounds.height,\n bounds.degrees + this.getRotation())\n .getBoundingBox();\n\n if (newBounds.getAspectRatio() >= aspect) {\n newBounds.height = newBounds.width / aspect;\n } else {\n newBounds.width = newBounds.height * aspect;\n }\n\n // Compute x and y from width, height and center position\n newBounds.x = center.x - newBounds.width / 2;\n newBounds.y = center.y - newBounds.height / 2;\n var newZoom = 1.0 / newBounds.width;\n\n if (constraints) {\n var newBoundsAspectRatio = newBounds.getAspectRatio();\n var newConstrainedZoom = this._applyZoomConstraints(newZoom);\n\n if (newZoom !== newConstrainedZoom) {\n newZoom = newConstrainedZoom;\n newBounds.width = 1.0 / newZoom;\n newBounds.x = center.x - newBounds.width / 2;\n newBounds.height = newBounds.width / newBoundsAspectRatio;\n newBounds.y = center.y - newBounds.height / 2;\n }\n\n newBounds = this._applyBoundaryConstraints(newBounds);\n center = newBounds.getCenter();\n this._raiseConstraintsEvent(immediately);\n }\n\n if (immediately) {\n this.panTo(center, true);\n return this.zoomTo(newZoom, null, true);\n }\n\n this.panTo(this.getCenter(true), true);\n this.zoomTo(this.getZoom(true), null, true);\n\n var oldBounds = this.getBounds();\n var oldZoom = this.getZoom();\n\n if (oldZoom === 0 || Math.abs(newZoom / oldZoom - 1) < 0.00000001) {\n this.zoomTo(newZoom, true);\n return this.panTo(center, immediately);\n }\n\n newBounds = newBounds.rotate(-this.getRotation());\n var referencePoint = newBounds.getTopLeft().times(newZoom)\n .minus(oldBounds.getTopLeft().times(oldZoom))\n .divide(newZoom - oldZoom);\n\n return this.zoomTo(newZoom, referencePoint, immediately);\n },\n\n /**\n * Makes the viewport zoom and pan so that the specified bounds take\n * as much space as possible in the viewport.\n * Note: this method ignores the constraints (minZoom, maxZoom and\n * visibilityRatio).\n * Use {@link OpenSeadragon.Viewport#fitBoundsWithConstraints} to enforce\n * them.\n * @function\n * @param {OpenSeadragon.Rect} bounds\n * @param {Boolean} [immediately=false]\n * @return {OpenSeadragon.Viewport} Chainable.\n */\n fitBounds: function(bounds, immediately) {\n return this._fitBounds(bounds, {\n immediately: immediately,\n constraints: false\n });\n },\n\n /**\n * Makes the viewport zoom and pan so that the specified bounds take\n * as much space as possible in the viewport while enforcing the constraints\n * (minZoom, maxZoom and visibilityRatio).\n * Note: because this method enforces the constraints, part of the\n * provided bounds may end up outside of the viewport.\n * Use {@link OpenSeadragon.Viewport#fitBounds} to ignore them.\n * @function\n * @param {OpenSeadragon.Rect} bounds\n * @param {Boolean} [immediately=false]\n * @return {OpenSeadragon.Viewport} Chainable.\n */\n fitBoundsWithConstraints: function(bounds, immediately) {\n return this._fitBounds(bounds, {\n immediately: immediately,\n constraints: true\n });\n },\n\n /**\n * Zooms so the image just fills the viewer vertically.\n * @param {Boolean} immediately\n * @return {OpenSeadragon.Viewport} Chainable.\n */\n fitVertically: function(immediately) {\n var box = new $.Rect(\n this._contentBounds.x + (this._contentBounds.width / 2),\n this._contentBounds.y,\n 0,\n this._contentBounds.height);\n return this.fitBounds(box, immediately);\n },\n\n /**\n * Zooms so the image just fills the viewer horizontally.\n * @param {Boolean} immediately\n * @return {OpenSeadragon.Viewport} Chainable.\n */\n fitHorizontally: function(immediately) {\n var box = new $.Rect(\n this._contentBounds.x,\n this._contentBounds.y + (this._contentBounds.height / 2),\n this._contentBounds.width,\n 0);\n return this.fitBounds(box, immediately);\n },\n\n\n /**\n * Returns bounds taking constraints into account\n * Added to improve constrained panning\n * @param {Boolean} current - Pass true for the current location; defaults to false (target location).\n * @return {OpenSeadragon.Viewport} Chainable.\n */\n getConstrainedBounds: function(current) {\n var bounds,\n constrainedBounds;\n\n bounds = this.getBounds(current);\n\n constrainedBounds = this._applyBoundaryConstraints(bounds);\n\n return constrainedBounds;\n },\n\n /**\n * @function\n * @param {OpenSeadragon.Point} delta\n * @param {Boolean} immediately\n * @return {OpenSeadragon.Viewport} Chainable.\n * @fires OpenSeadragon.Viewer.event:pan\n */\n panBy: function( delta, immediately ) {\n var center = new $.Point(\n this.centerSpringX.target.value,\n this.centerSpringY.target.value\n );\n return this.panTo( center.plus( delta ), immediately );\n },\n\n /**\n * @function\n * @param {OpenSeadragon.Point} center\n * @param {Boolean} immediately\n * @return {OpenSeadragon.Viewport} Chainable.\n * @fires OpenSeadragon.Viewer.event:pan\n */\n panTo: function( center, immediately ) {\n if ( immediately ) {\n this.centerSpringX.resetTo( center.x );\n this.centerSpringY.resetTo( center.y );\n } else {\n this.centerSpringX.springTo( center.x );\n this.centerSpringY.springTo( center.y );\n }\n\n if( this.viewer ){\n /**\n * Raised when the viewport is panned (see {@link OpenSeadragon.Viewport#panBy} and {@link OpenSeadragon.Viewport#panTo}).\n *\n * @event pan\n * @memberof OpenSeadragon.Viewer\n * @type {object}\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.\n * @property {OpenSeadragon.Point} center\n * @property {Boolean} immediately\n * @property {?Object} userData - Arbitrary subscriber-defined object.\n */\n this.viewer.raiseEvent( 'pan', {\n center: center,\n immediately: immediately\n });\n }\n\n return this;\n },\n\n /**\n * @function\n * @return {OpenSeadragon.Viewport} Chainable.\n * @fires OpenSeadragon.Viewer.event:zoom\n */\n zoomBy: function(factor, refPoint, immediately) {\n return this.zoomTo(\n this.zoomSpring.target.value * factor, refPoint, immediately);\n },\n\n /**\n * Zooms to the specified zoom level\n * @function\n * @param {Number} zoom The zoom level to zoom to.\n * @param {OpenSeadragon.Point} [refPoint] The point which will stay at\n * the same screen location. Defaults to the viewport center.\n * @param {Boolean} [immediately=false]\n * @return {OpenSeadragon.Viewport} Chainable.\n * @fires OpenSeadragon.Viewer.event:zoom\n */\n zoomTo: function(zoom, refPoint, immediately) {\n var _this = this;\n\n this.zoomPoint = refPoint instanceof $.Point &&\n !isNaN(refPoint.x) &&\n !isNaN(refPoint.y) ?\n refPoint :\n null;\n\n if (immediately) {\n this._adjustCenterSpringsForZoomPoint(function() {\n _this.zoomSpring.resetTo(zoom);\n });\n } else {\n this.zoomSpring.springTo(zoom);\n }\n\n if (this.viewer) {\n /**\n * Raised when the viewport zoom level changes (see {@link OpenSeadragon.Viewport#zoomBy} and {@link OpenSeadragon.Viewport#zoomTo}).\n *\n * @event zoom\n * @memberof OpenSeadragon.Viewer\n * @type {object}\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.\n * @property {Number} zoom\n * @property {OpenSeadragon.Point} refPoint\n * @property {Boolean} immediately\n * @property {?Object} userData - Arbitrary subscriber-defined object.\n */\n this.viewer.raiseEvent('zoom', {\n zoom: zoom,\n refPoint: refPoint,\n immediately: immediately\n });\n }\n\n return this;\n },\n\n /**\n * Rotates this viewport to the angle specified.\n * @function\n * @return {OpenSeadragon.Viewport} Chainable.\n */\n setRotation: function(degrees) {\n if (!this.viewer || !this.viewer.drawer.canRotate()) {\n return this;\n }\n this.degrees = $.positiveModulo(degrees, 360);\n this._setContentBounds(\n this.viewer.world.getHomeBounds(),\n this.viewer.world.getContentFactor());\n this.viewer.forceRedraw();\n\n /**\n * Raised when rotation has been changed.\n *\n * @event rotate\n * @memberof OpenSeadragon.Viewer\n * @type {object}\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.\n * @property {Number} degrees - The number of degrees the rotation was set to.\n * @property {?Object} userData - Arbitrary subscriber-defined object.\n */\n this.viewer.raiseEvent('rotate', {\"degrees\": degrees});\n return this;\n },\n\n /**\n * Gets the current rotation in degrees.\n * @function\n * @return {Number} The current rotation in degrees.\n */\n getRotation: function() {\n return this.degrees;\n },\n\n /**\n * @function\n * @return {OpenSeadragon.Viewport} Chainable.\n * @fires OpenSeadragon.Viewer.event:resize\n */\n resize: function( newContainerSize, maintain ) {\n var oldBounds = this.getBoundsNoRotate(),\n newBounds = oldBounds,\n widthDeltaFactor;\n\n this.containerSize.x = newContainerSize.x;\n this.containerSize.y = newContainerSize.y;\n\n this._updateContainerInnerSize();\n\n if ( maintain ) {\n // TODO: widthDeltaFactor will always be 1; probably not what's intended\n widthDeltaFactor = newContainerSize.x / this.containerSize.x;\n newBounds.width = oldBounds.width * widthDeltaFactor;\n newBounds.height = newBounds.width / this.getAspectRatio();\n }\n\n if( this.viewer ){\n /**\n * Raised when the viewer is resized (see {@link OpenSeadragon.Viewport#resize}).\n *\n * @event resize\n * @memberof OpenSeadragon.Viewer\n * @type {object}\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.\n * @property {OpenSeadragon.Point} newContainerSize\n * @property {Boolean} maintain\n * @property {?Object} userData - Arbitrary subscriber-defined object.\n */\n this.viewer.raiseEvent( 'resize', {\n newContainerSize: newContainerSize,\n maintain: maintain\n });\n }\n\n return this.fitBounds( newBounds, true );\n },\n\n // private\n _updateContainerInnerSize: function() {\n this._containerInnerSize = new $.Point(\n Math.max(1, this.containerSize.x - (this._margins.left + this._margins.right)),\n Math.max(1, this.containerSize.y - (this._margins.top + this._margins.bottom))\n );\n },\n\n /**\n * Update the zoom and center (X and Y) springs.\n * @function\n * @returns {Boolean} True if any change has been made, false otherwise.\n */\n update: function() {\n var _this = this;\n this._adjustCenterSpringsForZoomPoint(function() {\n _this.zoomSpring.update();\n });\n\n this.centerSpringX.update();\n this.centerSpringY.update();\n\n var changed = this.centerSpringX.current.value !== this._oldCenterX ||\n this.centerSpringY.current.value !== this._oldCenterY ||\n this.zoomSpring.current.value !== this._oldZoom;\n\n this._oldCenterX = this.centerSpringX.current.value;\n this._oldCenterY = this.centerSpringY.current.value;\n this._oldZoom = this.zoomSpring.current.value;\n\n return changed;\n },\n\n _adjustCenterSpringsForZoomPoint: function(zoomSpringHandler) {\n if (this.zoomPoint) {\n var oldZoomPixel = this.pixelFromPoint(this.zoomPoint, true);\n zoomSpringHandler();\n var newZoomPixel = this.pixelFromPoint(this.zoomPoint, true);\n\n var deltaZoomPixels = newZoomPixel.minus(oldZoomPixel);\n var deltaZoomPoints = this.deltaPointsFromPixels(\n deltaZoomPixels, true);\n\n this.centerSpringX.shiftBy(deltaZoomPoints.x);\n this.centerSpringY.shiftBy(deltaZoomPoints.y);\n\n if (this.zoomSpring.isAtTargetValue()) {\n this.zoomPoint = null;\n }\n } else {\n zoomSpringHandler();\n }\n },\n\n /**\n * Convert a delta (translation vector) from viewport coordinates to pixels\n * coordinates. This method does not take rotation into account.\n * Consider using deltaPixelsFromPoints if you need to account for rotation.\n * @param {OpenSeadragon.Point} deltaPoints - The translation vector to convert.\n * @param {Boolean} [current=false] - Pass true for the current location;\n * defaults to false (target location).\n * @returns {OpenSeadragon.Point}\n */\n deltaPixelsFromPointsNoRotate: function(deltaPoints, current) {\n return deltaPoints.times(\n this._containerInnerSize.x * this.getZoom(current)\n );\n },\n\n /**\n * Convert a delta (translation vector) from viewport coordinates to pixels\n * coordinates.\n * @param {OpenSeadragon.Point} deltaPoints - The translation vector to convert.\n * @param {Boolean} [current=false] - Pass true for the current location;\n * defaults to false (target location).\n * @returns {OpenSeadragon.Point}\n */\n deltaPixelsFromPoints: function(deltaPoints, current) {\n return this.deltaPixelsFromPointsNoRotate(\n deltaPoints.rotate(this.getRotation()),\n current);\n },\n\n /**\n * Convert a delta (translation vector) from pixels coordinates to viewport\n * coordinates. This method does not take rotation into account.\n * Consider using deltaPointsFromPixels if you need to account for rotation.\n * @param {OpenSeadragon.Point} deltaPixels - The translation vector to convert.\n * @param {Boolean} [current=false] - Pass true for the current location;\n * defaults to false (target location).\n * @returns {OpenSeadragon.Point}\n */\n deltaPointsFromPixelsNoRotate: function(deltaPixels, current) {\n return deltaPixels.divide(\n this._containerInnerSize.x * this.getZoom(current)\n );\n },\n\n /**\n * Convert a delta (translation vector) from pixels coordinates to viewport\n * coordinates.\n * @param {OpenSeadragon.Point} deltaPixels - The translation vector to convert.\n * @param {Boolean} [current=false] - Pass true for the current location;\n * defaults to false (target location).\n * @returns {OpenSeadragon.Point}\n */\n deltaPointsFromPixels: function(deltaPixels, current) {\n return this.deltaPointsFromPixelsNoRotate(deltaPixels, current)\n .rotate(-this.getRotation());\n },\n\n /**\n * Convert viewport coordinates to pixels coordinates.\n * This method does not take rotation into account.\n * Consider using pixelFromPoint if you need to account for rotation.\n * @param {OpenSeadragon.Point} point the viewport coordinates\n * @param {Boolean} [current=false] - Pass true for the current location;\n * defaults to false (target location).\n * @returns {OpenSeadragon.Point}\n */\n pixelFromPointNoRotate: function(point, current) {\n return this._pixelFromPointNoRotate(\n point, this.getBoundsNoRotate(current));\n },\n\n /**\n * Convert viewport coordinates to pixel coordinates.\n * @param {OpenSeadragon.Point} point the viewport coordinates\n * @param {Boolean} [current=false] - Pass true for the current location;\n * defaults to false (target location).\n * @returns {OpenSeadragon.Point}\n */\n pixelFromPoint: function(point, current) {\n return this._pixelFromPoint(point, this.getBoundsNoRotate(current));\n },\n\n // private\n _pixelFromPointNoRotate: function(point, bounds) {\n return point.minus(\n bounds.getTopLeft()\n ).times(\n this._containerInnerSize.x / bounds.width\n ).plus(\n new $.Point(this._margins.left, this._margins.top)\n );\n },\n\n // private\n _pixelFromPoint: function(point, bounds) {\n return this._pixelFromPointNoRotate(\n point.rotate(this.getRotation(), this.getCenter(true)),\n bounds);\n },\n\n /**\n * Convert pixel coordinates to viewport coordinates.\n * This method does not take rotation into account.\n * Consider using pointFromPixel if you need to account for rotation.\n * @param {OpenSeadragon.Point} pixel Pixel coordinates\n * @param {Boolean} [current=false] - Pass true for the current location;\n * defaults to false (target location).\n * @returns {OpenSeadragon.Point}\n */\n pointFromPixelNoRotate: function(pixel, current) {\n var bounds = this.getBoundsNoRotate(current);\n return pixel.minus(\n new $.Point(this._margins.left, this._margins.top)\n ).divide(\n this._containerInnerSize.x / bounds.width\n ).plus(\n bounds.getTopLeft()\n );\n },\n\n /**\n * Convert pixel coordinates to viewport coordinates.\n * @param {OpenSeadragon.Point} pixel Pixel coordinates\n * @param {Boolean} [current=false] - Pass true for the current location;\n * defaults to false (target location).\n * @returns {OpenSeadragon.Point}\n */\n pointFromPixel: function(pixel, current) {\n return this.pointFromPixelNoRotate(pixel, current).rotate(\n -this.getRotation(),\n this.getCenter(true)\n );\n },\n\n // private\n _viewportToImageDelta: function( viewerX, viewerY ) {\n var scale = this._contentBoundsNoRotate.width;\n return new $.Point(\n viewerX * this._contentSizeNoRotate.x / scale,\n viewerY * this._contentSizeNoRotate.x / scale);\n },\n\n /**\n * Translates from OpenSeadragon viewer coordinate system to image coordinate system.\n * This method can be called either by passing X,Y coordinates or an\n * OpenSeadragon.Point\n * Note: not accurate with multi-image; use TiledImage.viewportToImageCoordinates instead.\n * @function\n * @param {(OpenSeadragon.Point|Number)} viewerX either a point or the X\n * coordinate in viewport coordinate system.\n * @param {Number} [viewerY] Y coordinate in viewport coordinate system.\n * @return {OpenSeadragon.Point} a point representing the coordinates in the image.\n */\n viewportToImageCoordinates: function(viewerX, viewerY) {\n if (viewerX instanceof $.Point) {\n //they passed a point instead of individual components\n return this.viewportToImageCoordinates(viewerX.x, viewerX.y);\n }\n\n if (this.viewer) {\n var count = this.viewer.world.getItemCount();\n if (count > 1) {\n $.console.error('[Viewport.viewportToImageCoordinates] is not accurate ' +\n 'with multi-image; use TiledImage.viewportToImageCoordinates instead.');\n } else if (count === 1) {\n // It is better to use TiledImage.viewportToImageCoordinates\n // because this._contentBoundsNoRotate can not be relied on\n // with clipping.\n var item = this.viewer.world.getItemAt(0);\n return item.viewportToImageCoordinates(viewerX, viewerY, true);\n }\n }\n\n return this._viewportToImageDelta(\n viewerX - this._contentBoundsNoRotate.x,\n viewerY - this._contentBoundsNoRotate.y);\n },\n\n // private\n _imageToViewportDelta: function( imageX, imageY ) {\n var scale = this._contentBoundsNoRotate.width;\n return new $.Point(\n imageX / this._contentSizeNoRotate.x * scale,\n imageY / this._contentSizeNoRotate.x * scale);\n },\n\n /**\n * Translates from image coordinate system to OpenSeadragon viewer coordinate system\n * This method can be called either by passing X,Y coordinates or an\n * OpenSeadragon.Point\n * Note: not accurate with multi-image; use TiledImage.imageToViewportCoordinates instead.\n * @function\n * @param {(OpenSeadragon.Point | Number)} imageX the point or the\n * X coordinate in image coordinate system.\n * @param {Number} [imageY] Y coordinate in image coordinate system.\n * @return {OpenSeadragon.Point} a point representing the coordinates in the viewport.\n */\n imageToViewportCoordinates: function(imageX, imageY) {\n if (imageX instanceof $.Point) {\n //they passed a point instead of individual components\n return this.imageToViewportCoordinates(imageX.x, imageX.y);\n }\n\n if (this.viewer) {\n var count = this.viewer.world.getItemCount();\n if (count > 1) {\n $.console.error('[Viewport.imageToViewportCoordinates] is not accurate ' +\n 'with multi-image; use TiledImage.imageToViewportCoordinates instead.');\n } else if (count === 1) {\n // It is better to use TiledImage.viewportToImageCoordinates\n // because this._contentBoundsNoRotate can not be relied on\n // with clipping.\n var item = this.viewer.world.getItemAt(0);\n return item.imageToViewportCoordinates(imageX, imageY, true);\n }\n }\n\n var point = this._imageToViewportDelta(imageX, imageY);\n point.x += this._contentBoundsNoRotate.x;\n point.y += this._contentBoundsNoRotate.y;\n return point;\n },\n\n /**\n * Translates from a rectangle which describes a portion of the image in\n * pixel coordinates to OpenSeadragon viewport rectangle coordinates.\n * This method can be called either by passing X,Y,width,height or an\n * OpenSeadragon.Rect\n * Note: not accurate with multi-image; use TiledImage.imageToViewportRectangle instead.\n * @function\n * @param {(OpenSeadragon.Rect | Number)} imageX the rectangle or the X\n * coordinate of the top left corner of the rectangle in image coordinate system.\n * @param {Number} [imageY] the Y coordinate of the top left corner of the rectangle\n * in image coordinate system.\n * @param {Number} [pixelWidth] the width in pixel of the rectangle.\n * @param {Number} [pixelHeight] the height in pixel of the rectangle.\n * @returns {OpenSeadragon.Rect} This image's bounds in viewport coordinates\n */\n imageToViewportRectangle: function(imageX, imageY, pixelWidth, pixelHeight) {\n var rect = imageX;\n if (!(rect instanceof $.Rect)) {\n //they passed individual components instead of a rectangle\n rect = new $.Rect(imageX, imageY, pixelWidth, pixelHeight);\n }\n\n if (this.viewer) {\n var count = this.viewer.world.getItemCount();\n if (count > 1) {\n $.console.error('[Viewport.imageToViewportRectangle] is not accurate ' +\n 'with multi-image; use TiledImage.imageToViewportRectangle instead.');\n } else if (count === 1) {\n // It is better to use TiledImage.imageToViewportRectangle\n // because this._contentBoundsNoRotate can not be relied on\n // with clipping.\n var item = this.viewer.world.getItemAt(0);\n return item.imageToViewportRectangle(\n imageX, imageY, pixelWidth, pixelHeight, true);\n }\n }\n\n var coordA = this.imageToViewportCoordinates(rect.x, rect.y);\n var coordB = this._imageToViewportDelta(rect.width, rect.height);\n return new $.Rect(\n coordA.x,\n coordA.y,\n coordB.x,\n coordB.y,\n rect.degrees\n );\n },\n\n /**\n * Translates from a rectangle which describes a portion of\n * the viewport in point coordinates to image rectangle coordinates.\n * This method can be called either by passing X,Y,width,height or an\n * OpenSeadragon.Rect\n * Note: not accurate with multi-image; use TiledImage.viewportToImageRectangle instead.\n * @function\n * @param {(OpenSeadragon.Rect | Number)} viewerX either a rectangle or\n * the X coordinate of the top left corner of the rectangle in viewport\n * coordinate system.\n * @param {Number} [viewerY] the Y coordinate of the top left corner of the rectangle\n * in viewport coordinate system.\n * @param {Number} [pointWidth] the width of the rectangle in viewport coordinate system.\n * @param {Number} [pointHeight] the height of the rectangle in viewport coordinate system.\n */\n viewportToImageRectangle: function(viewerX, viewerY, pointWidth, pointHeight) {\n var rect = viewerX;\n if (!(rect instanceof $.Rect)) {\n //they passed individual components instead of a rectangle\n rect = new $.Rect(viewerX, viewerY, pointWidth, pointHeight);\n }\n\n if (this.viewer) {\n var count = this.viewer.world.getItemCount();\n if (count > 1) {\n $.console.error('[Viewport.viewportToImageRectangle] is not accurate ' +\n 'with multi-image; use TiledImage.viewportToImageRectangle instead.');\n } else if (count === 1) {\n // It is better to use TiledImage.viewportToImageCoordinates\n // because this._contentBoundsNoRotate can not be relied on\n // with clipping.\n var item = this.viewer.world.getItemAt(0);\n return item.viewportToImageRectangle(\n viewerX, viewerY, pointWidth, pointHeight, true);\n }\n }\n\n var coordA = this.viewportToImageCoordinates(rect.x, rect.y);\n var coordB = this._viewportToImageDelta(rect.width, rect.height);\n return new $.Rect(\n coordA.x,\n coordA.y,\n coordB.x,\n coordB.y,\n rect.degrees\n );\n },\n\n /**\n * Convert pixel coordinates relative to the viewer element to image\n * coordinates.\n * Note: not accurate with multi-image.\n * @param {OpenSeadragon.Point} pixel\n * @returns {OpenSeadragon.Point}\n */\n viewerElementToImageCoordinates: function( pixel ) {\n var point = this.pointFromPixel( pixel, true );\n return this.viewportToImageCoordinates( point );\n },\n\n /**\n * Convert pixel coordinates relative to the image to\n * viewer element coordinates.\n * Note: not accurate with multi-image.\n * @param {OpenSeadragon.Point} pixel\n * @returns {OpenSeadragon.Point}\n */\n imageToViewerElementCoordinates: function( pixel ) {\n var point = this.imageToViewportCoordinates( pixel );\n return this.pixelFromPoint( point, true );\n },\n\n /**\n * Convert pixel coordinates relative to the window to image coordinates.\n * Note: not accurate with multi-image.\n * @param {OpenSeadragon.Point} pixel\n * @returns {OpenSeadragon.Point}\n */\n windowToImageCoordinates: function(pixel) {\n $.console.assert(this.viewer,\n \"[Viewport.windowToImageCoordinates] the viewport must have a viewer.\");\n var viewerCoordinates = pixel.minus(\n $.getElementPosition(this.viewer.element));\n return this.viewerElementToImageCoordinates(viewerCoordinates);\n },\n\n /**\n * Convert image coordinates to pixel coordinates relative to the window.\n * Note: not accurate with multi-image.\n * @param {OpenSeadragon.Point} pixel\n * @returns {OpenSeadragon.Point}\n */\n imageToWindowCoordinates: function(pixel) {\n $.console.assert(this.viewer,\n \"[Viewport.imageToWindowCoordinates] the viewport must have a viewer.\");\n var viewerCoordinates = this.imageToViewerElementCoordinates(pixel);\n return viewerCoordinates.plus(\n $.getElementPosition(this.viewer.element));\n },\n\n /**\n * Convert pixel coordinates relative to the viewer element to viewport\n * coordinates.\n * @param {OpenSeadragon.Point} pixel\n * @returns {OpenSeadragon.Point}\n */\n viewerElementToViewportCoordinates: function( pixel ) {\n return this.pointFromPixel( pixel, true );\n },\n\n /**\n * Convert viewport coordinates to pixel coordinates relative to the\n * viewer element.\n * @param {OpenSeadragon.Point} point\n * @returns {OpenSeadragon.Point}\n */\n viewportToViewerElementCoordinates: function( point ) {\n return this.pixelFromPoint( point, true );\n },\n\n /**\n * Convert a rectangle in pixel coordinates relative to the viewer element\n * to viewport coordinates.\n * @param {OpenSeadragon.Rect} rectangle the rectangle to convert\n * @returns {OpenSeadragon.Rect} the converted rectangle\n */\n viewerElementToViewportRectangle: function(rectangle) {\n return $.Rect.fromSummits(\n this.pointFromPixel(rectangle.getTopLeft(), true),\n this.pointFromPixel(rectangle.getTopRight(), true),\n this.pointFromPixel(rectangle.getBottomLeft(), true)\n );\n },\n\n /**\n * Convert a rectangle in viewport coordinates to pixel coordinates relative\n * to the viewer element.\n * @param {OpenSeadragon.Rect} rectangle the rectangle to convert\n * @returns {OpenSeadragon.Rect} the converted rectangle\n */\n viewportToViewerElementRectangle: function(rectangle) {\n return $.Rect.fromSummits(\n this.pixelFromPoint(rectangle.getTopLeft(), true),\n this.pixelFromPoint(rectangle.getTopRight(), true),\n this.pixelFromPoint(rectangle.getBottomLeft(), true)\n );\n },\n\n /**\n * Convert pixel coordinates relative to the window to viewport coordinates.\n * @param {OpenSeadragon.Point} pixel\n * @returns {OpenSeadragon.Point}\n */\n windowToViewportCoordinates: function(pixel) {\n $.console.assert(this.viewer,\n \"[Viewport.windowToViewportCoordinates] the viewport must have a viewer.\");\n var viewerCoordinates = pixel.minus(\n $.getElementPosition(this.viewer.element));\n return this.viewerElementToViewportCoordinates(viewerCoordinates);\n },\n\n /**\n * Convert viewport coordinates to pixel coordinates relative to the window.\n * @param {OpenSeadragon.Point} point\n * @returns {OpenSeadragon.Point}\n */\n viewportToWindowCoordinates: function(point) {\n $.console.assert(this.viewer,\n \"[Viewport.viewportToWindowCoordinates] the viewport must have a viewer.\");\n var viewerCoordinates = this.viewportToViewerElementCoordinates(point);\n return viewerCoordinates.plus(\n $.getElementPosition(this.viewer.element));\n },\n\n /**\n * Convert a viewport zoom to an image zoom.\n * Image zoom: ratio of the original image size to displayed image size.\n * 1 means original image size, 0.5 half size...\n * Viewport zoom: ratio of the displayed image's width to viewport's width.\n * 1 means identical width, 2 means image's width is twice the viewport's width...\n * Note: not accurate with multi-image.\n * @function\n * @param {Number} viewportZoom The viewport zoom\n * target zoom.\n * @returns {Number} imageZoom The image zoom\n */\n viewportToImageZoom: function(viewportZoom) {\n if (this.viewer) {\n var count = this.viewer.world.getItemCount();\n if (count > 1) {\n $.console.error('[Viewport.viewportToImageZoom] is not ' +\n 'accurate with multi-image.');\n } else if (count === 1) {\n // It is better to use TiledImage.viewportToImageZoom\n // because this._contentBoundsNoRotate can not be relied on\n // with clipping.\n var item = this.viewer.world.getItemAt(0);\n return item.viewportToImageZoom(viewportZoom);\n }\n }\n\n var imageWidth = this._contentSizeNoRotate.x;\n var containerWidth = this._containerInnerSize.x;\n var scale = this._contentBoundsNoRotate.width;\n var viewportToImageZoomRatio = (containerWidth / imageWidth) * scale;\n return viewportZoom * viewportToImageZoomRatio;\n },\n\n /**\n * Convert an image zoom to a viewport zoom.\n * Image zoom: ratio of the original image size to displayed image size.\n * 1 means original image size, 0.5 half size...\n * Viewport zoom: ratio of the displayed image's width to viewport's width.\n * 1 means identical width, 2 means image's width is twice the viewport's width...\n * Note: not accurate with multi-image.\n * @function\n * @param {Number} imageZoom The image zoom\n * target zoom.\n * @returns {Number} viewportZoom The viewport zoom\n */\n imageToViewportZoom: function(imageZoom) {\n if (this.viewer) {\n var count = this.viewer.world.getItemCount();\n if (count > 1) {\n $.console.error('[Viewport.imageToViewportZoom] is not accurate ' +\n 'with multi-image.');\n } else if (count === 1) {\n // It is better to use TiledImage.imageToViewportZoom\n // because this._contentBoundsNoRotate can not be relied on\n // with clipping.\n var item = this.viewer.world.getItemAt(0);\n return item.imageToViewportZoom(imageZoom);\n }\n }\n\n var imageWidth = this._contentSizeNoRotate.x;\n var containerWidth = this._containerInnerSize.x;\n var scale = this._contentBoundsNoRotate.width;\n var viewportToImageZoomRatio = (imageWidth / containerWidth) / scale;\n return imageZoom * viewportToImageZoomRatio;\n },\n\n /**\n * Toggles flip state and demands a new drawing on navigator and viewer objects.\n * @function\n * @return {OpenSeadragon.Viewport} Chainable.\n */\n toggleFlip: function() {\n this.setFlip(!this.getFlip());\n return this;\n },\n\n /**\n * Gets flip state stored on viewport.\n * @function\n * @return {Boolean} Flip state.\n */\n getFlip: function() {\n return this.flipped;\n },\n\n /**\n * Sets flip state according to the state input argument.\n * @function\n * @param {Boolean} state - Flip state to set.\n * @return {OpenSeadragon.Viewport} Chainable.\n */\n setFlip: function( state ) {\n if ( this.flipped === state ) {\n return this;\n }\n\n this.flipped = state;\n if(this.viewer.navigator){\n this.viewer.navigator.setFlip(this.getFlip());\n }\n this.viewer.forceRedraw();\n\n /**\n * Raised when flip state has been changed.\n *\n * @event flip\n * @memberof OpenSeadragon.Viewer\n * @type {object}\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.\n * @property {Number} flipped - The flip state after this change.\n * @property {?Object} userData - Arbitrary subscriber-defined object.\n */\n this.viewer.raiseEvent('flip', {\"flipped\": state});\n return this;\n }\n\n};\n\n}( OpenSeadragon ));\n\n/*\n * OpenSeadragon - TiledImage\n *\n * Copyright (C) 2009 CodePlex Foundation\n * Copyright (C) 2010-2013 OpenSeadragon contributors\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are\n * met:\n *\n * - Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * - Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n *\n * - Neither the name of CodePlex Foundation nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\n * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n(function( $ ){\n\n/**\n * You shouldn't have to create a TiledImage instance directly; get it asynchronously by\n * using {@link OpenSeadragon.Viewer#open} or {@link OpenSeadragon.Viewer#addTiledImage} instead.\n * @class TiledImage\n * @memberof OpenSeadragon\n * @extends OpenSeadragon.EventSource\n * @classdesc Handles rendering of tiles for an {@link OpenSeadragon.Viewer}.\n * A new instance is created for each TileSource opened.\n * @param {Object} options - Configuration for this TiledImage.\n * @param {OpenSeadragon.TileSource} options.source - The TileSource that defines this TiledImage.\n * @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this TiledImage.\n * @param {OpenSeadragon.TileCache} options.tileCache - The TileCache for this TiledImage to use.\n * @param {OpenSeadragon.Drawer} options.drawer - The Drawer for this TiledImage to draw onto.\n * @param {OpenSeadragon.ImageLoader} options.imageLoader - The ImageLoader for this TiledImage to use.\n * @param {Number} [options.x=0] - Left position, in viewport coordinates.\n * @param {Number} [options.y=0] - Top position, in viewport coordinates.\n * @param {Number} [options.width=1] - Width, in viewport coordinates.\n * @param {Number} [options.height] - Height, in viewport coordinates.\n * @param {OpenSeadragon.Rect} [options.fitBounds] The bounds in viewport coordinates\n * to fit the image into. If specified, x, y, width and height get ignored.\n * @param {OpenSeadragon.Placement} [options.fitBoundsPlacement=OpenSeadragon.Placement.CENTER]\n * How to anchor the image in the bounds if options.fitBounds is set.\n * @param {OpenSeadragon.Rect} [options.clip] - An area, in image pixels, to clip to\n * (portions of the image outside of this area will not be visible). Only works on\n * browsers that support the HTML5 canvas.\n * @param {Number} [options.springStiffness] - See {@link OpenSeadragon.Options}.\n * @param {Boolean} [options.animationTime] - See {@link OpenSeadragon.Options}.\n * @param {Number} [options.minZoomImageRatio] - See {@link OpenSeadragon.Options}.\n * @param {Boolean} [options.wrapHorizontal] - See {@link OpenSeadragon.Options}.\n * @param {Boolean} [options.wrapVertical] - See {@link OpenSeadragon.Options}.\n * @param {Boolean} [options.immediateRender] - See {@link OpenSeadragon.Options}.\n * @param {Number} [options.blendTime] - See {@link OpenSeadragon.Options}.\n * @param {Boolean} [options.alwaysBlend] - See {@link OpenSeadragon.Options}.\n * @param {Number} [options.minPixelRatio] - See {@link OpenSeadragon.Options}.\n * @param {Number} [options.smoothTileEdgesMinZoom] - See {@link OpenSeadragon.Options}.\n * @param {Boolean} [options.iOSDevice] - See {@link OpenSeadragon.Options}.\n * @param {Number} [options.opacity=1] - Set to draw at proportional opacity. If zero, images will not draw.\n * @param {Boolean} [options.preload=false] - Set true to load even when the image is hidden by zero opacity.\n * @param {String} [options.compositeOperation] - How the image is composited onto other images; see compositeOperation in {@link OpenSeadragon.Options} for possible values.\n * @param {Boolean} [options.debugMode] - See {@link OpenSeadragon.Options}.\n * @param {String|CanvasGradient|CanvasPattern|Function} [options.placeholderFillStyle] - See {@link OpenSeadragon.Options}.\n * @param {String|Boolean} [options.crossOriginPolicy] - See {@link OpenSeadragon.Options}.\n * @param {Boolean} [options.ajaxWithCredentials] - See {@link OpenSeadragon.Options}.\n * @param {Boolean} [options.loadTilesWithAjax]\n * Whether to load tile data using AJAX requests.\n * Defaults to the setting in {@link OpenSeadragon.Options}.\n * @param {Object} [options.ajaxHeaders={}]\n * A set of headers to include when making tile AJAX requests.\n */\n$.TiledImage = function( options ) {\n var _this = this;\n\n $.console.assert( options.tileCache, \"[TiledImage] options.tileCache is required\" );\n $.console.assert( options.drawer, \"[TiledImage] options.drawer is required\" );\n $.console.assert( options.viewer, \"[TiledImage] options.viewer is required\" );\n $.console.assert( options.imageLoader, \"[TiledImage] options.imageLoader is required\" );\n $.console.assert( options.source, \"[TiledImage] options.source is required\" );\n $.console.assert(!options.clip || options.clip instanceof $.Rect,\n \"[TiledImage] options.clip must be an OpenSeadragon.Rect if present\");\n\n $.EventSource.call( this );\n\n this._tileCache = options.tileCache;\n delete options.tileCache;\n\n this._drawer = options.drawer;\n delete options.drawer;\n\n this._imageLoader = options.imageLoader;\n delete options.imageLoader;\n\n if (options.clip instanceof $.Rect) {\n this._clip = options.clip.clone();\n }\n\n delete options.clip;\n\n var x = options.x || 0;\n delete options.x;\n var y = options.y || 0;\n delete options.y;\n\n // Ratio of zoomable image height to width.\n this.normHeight = options.source.dimensions.y / options.source.dimensions.x;\n this.contentAspectX = options.source.dimensions.x / options.source.dimensions.y;\n\n var scale = 1;\n if ( options.width ) {\n scale = options.width;\n delete options.width;\n\n if ( options.height ) {\n $.console.error( \"specifying both width and height to a tiledImage is not supported\" );\n delete options.height;\n }\n } else if ( options.height ) {\n scale = options.height / this.normHeight;\n delete options.height;\n }\n\n var fitBounds = options.fitBounds;\n delete options.fitBounds;\n var fitBoundsPlacement = options.fitBoundsPlacement || OpenSeadragon.Placement.CENTER;\n delete options.fitBoundsPlacement;\n\n var degrees = options.degrees || 0;\n delete options.degrees;\n\n $.extend( true, this, {\n\n //internal state properties\n viewer: null,\n tilesMatrix: {}, // A '3d' dictionary [level][x][y] --> Tile.\n coverage: {}, // A '3d' dictionary [level][x][y] --> Boolean; shows what areas have been drawn.\n loadingCoverage: {}, // A '3d' dictionary [level][x][y] --> Boolean; shows what areas are loaded or are being loaded/blended.\n lastDrawn: [], // An unordered list of Tiles drawn last frame.\n lastResetTime: 0, // Last time for which the tiledImage was reset.\n _midDraw: false, // Is the tiledImage currently updating the viewport?\n _needsDraw: true, // Does the tiledImage need to update the viewport again?\n _hasOpaqueTile: false, // Do we have even one fully opaque tile?\n _tilesLoading: 0, // The number of pending tile requests.\n //configurable settings\n springStiffness: $.DEFAULT_SETTINGS.springStiffness,\n animationTime: $.DEFAULT_SETTINGS.animationTime,\n minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio,\n wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal,\n wrapVertical: $.DEFAULT_SETTINGS.wrapVertical,\n immediateRender: $.DEFAULT_SETTINGS.immediateRender,\n blendTime: $.DEFAULT_SETTINGS.blendTime,\n alwaysBlend: $.DEFAULT_SETTINGS.alwaysBlend,\n minPixelRatio: $.DEFAULT_SETTINGS.minPixelRatio,\n smoothTileEdgesMinZoom: $.DEFAULT_SETTINGS.smoothTileEdgesMinZoom,\n iOSDevice: $.DEFAULT_SETTINGS.iOSDevice,\n debugMode: $.DEFAULT_SETTINGS.debugMode,\n crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy,\n ajaxWithCredentials: $.DEFAULT_SETTINGS.ajaxWithCredentials,\n placeholderFillStyle: $.DEFAULT_SETTINGS.placeholderFillStyle,\n opacity: $.DEFAULT_SETTINGS.opacity,\n preload: $.DEFAULT_SETTINGS.preload,\n compositeOperation: $.DEFAULT_SETTINGS.compositeOperation\n }, options );\n\n this._preload = this.preload;\n delete this.preload;\n\n this._fullyLoaded = false;\n\n this._xSpring = new $.Spring({\n initial: x,\n springStiffness: this.springStiffness,\n animationTime: this.animationTime\n });\n\n this._ySpring = new $.Spring({\n initial: y,\n springStiffness: this.springStiffness,\n animationTime: this.animationTime\n });\n\n this._scaleSpring = new $.Spring({\n initial: scale,\n springStiffness: this.springStiffness,\n animationTime: this.animationTime\n });\n\n this._degreesSpring = new $.Spring({\n initial: degrees,\n springStiffness: this.springStiffness,\n animationTime: this.animationTime\n });\n\n this._updateForScale();\n\n if (fitBounds) {\n this.fitBounds(fitBounds, fitBoundsPlacement, true);\n }\n\n // We need a callback to give image manipulation a chance to happen\n this._drawingHandler = function(args) {\n /**\n * This event is fired just before the tile is drawn giving the application a chance to alter the image.\n *\n * NOTE: This event is only fired when the drawer is using a <canvas>.\n *\n * @event tile-drawing\n * @memberof OpenSeadragon.Viewer\n * @type {object}\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.\n * @property {OpenSeadragon.Tile} tile - The Tile being drawn.\n * @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.\n * @property {OpenSeadragon.Tile} context - The HTML canvas context being drawn into.\n * @property {OpenSeadragon.Tile} rendered - The HTML canvas context containing the tile imagery.\n * @property {?Object} userData - Arbitrary subscriber-defined object.\n */\n _this.viewer.raiseEvent('tile-drawing', $.extend({\n tiledImage: _this\n }, args));\n };\n};\n\n$.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.TiledImage.prototype */{\n /**\n * @returns {Boolean} Whether the TiledImage needs to be drawn.\n */\n needsDraw: function() {\n return this._needsDraw;\n },\n\n /**\n * @returns {Boolean} Whether all tiles necessary for this TiledImage to draw at the current view have been loaded.\n */\n getFullyLoaded: function() {\n return this._fullyLoaded;\n },\n\n // private\n _setFullyLoaded: function(flag) {\n if (flag === this._fullyLoaded) {\n return;\n }\n\n this._fullyLoaded = flag;\n\n /**\n * Fired when the TiledImage's \"fully loaded\" flag (whether all tiles necessary for this TiledImage\n * to draw at the current view have been loaded) changes.\n *\n * @event fully-loaded-change\n * @memberof OpenSeadragon.TiledImage\n * @type {object}\n * @property {Boolean} fullyLoaded - The new \"fully loaded\" value.\n * @property {OpenSeadragon.TiledImage} eventSource - A reference to the TiledImage which raised the event.\n * @property {?Object} userData - Arbitrary subscriber-defined object.\n */\n this.raiseEvent('fully-loaded-change', {\n fullyLoaded: this._fullyLoaded\n });\n },\n\n /**\n * Clears all tiles and triggers an update on the next call to\n * {@link OpenSeadragon.TiledImage#update}.\n */\n reset: function() {\n this._tileCache.clearTilesFor(this);\n this.lastResetTime = $.now();\n this._needsDraw = true;\n },\n\n /**\n * Updates the TiledImage's bounds, animating if needed.\n * @returns {Boolean} Whether the TiledImage animated.\n */\n update: function() {\n var xUpdated = this._xSpring.update();\n var yUpdated = this._ySpring.update();\n var scaleUpdated = this._scaleSpring.update();\n var degreesUpdated = this._degreesSpring.update();\n\n if (xUpdated || yUpdated || scaleUpdated || degreesUpdated) {\n this._updateForScale();\n this._needsDraw = true;\n return true;\n }\n\n return false;\n },\n\n /**\n * Draws the TiledImage to its Drawer.\n */\n draw: function() {\n if (this.opacity !== 0 || this._preload) {\n this._midDraw = true;\n this._updateViewport();\n this._midDraw = false;\n }\n // Images with opacity 0 should not need to be drawn in future. this._needsDraw = false is set in this._updateViewport() for other images.\n else {\n this._needsDraw = false;\n }\n },\n\n /**\n * Destroy the TiledImage (unload current loaded tiles).\n */\n destroy: function() {\n this.reset();\n },\n\n /**\n * Get this TiledImage's bounds in viewport coordinates.\n * @param {Boolean} [current=false] - Pass true for the current location;\n * false for target location.\n * @returns {OpenSeadragon.Rect} This TiledImage's bounds in viewport coordinates.\n */\n getBounds: function(current) {\n return this.getBoundsNoRotate(current)\n .rotate(this.getRotation(current), this._getRotationPoint(current));\n },\n\n /**\n * Get this TiledImage's bounds in viewport coordinates without taking\n * rotation into account.\n * @param {Boolean} [current=false] - Pass true for the current location;\n * false for target location.\n * @returns {OpenSeadragon.Rect} This TiledImage's bounds in viewport coordinates.\n */\n getBoundsNoRotate: function(current) {\n return current ?\n new $.Rect(\n this._xSpring.current.value,\n this._ySpring.current.value,\n this._worldWidthCurrent,\n this._worldHeightCurrent) :\n new $.Rect(\n this._xSpring.target.value,\n this._ySpring.target.value,\n this._worldWidthTarget,\n this._worldHeightTarget);\n },\n\n // deprecated\n getWorldBounds: function() {\n $.console.error('[TiledImage.getWorldBounds] is deprecated; use TiledImage.getBounds instead');\n return this.getBounds();\n },\n\n /**\n * Get the bounds of the displayed part of the tiled image.\n * @param {Boolean} [current=false] Pass true for the current location,\n * false for the target location.\n * @returns {$.Rect} The clipped bounds in viewport coordinates.\n */\n getClippedBounds: function(current) {\n var bounds = this.getBoundsNoRotate(current);\n if (this._clip) {\n var worldWidth = current ?\n this._worldWidthCurrent : this._worldWidthTarget;\n var ratio = worldWidth / this.source.dimensions.x;\n var clip = this._clip.times(ratio);\n bounds = new $.Rect(\n bounds.x + clip.x,\n bounds.y + clip.y,\n clip.width,\n clip.height);\n }\n return bounds.rotate(this.getRotation(current), this._getRotationPoint(current));\n },\n\n /**\n * @returns {OpenSeadragon.Point} This TiledImage's content size, in original pixels.\n */\n getContentSize: function() {\n return new $.Point(this.source.dimensions.x, this.source.dimensions.y);\n },\n\n // private\n _viewportToImageDelta: function( viewerX, viewerY, current ) {\n var scale = (current ? this._scaleSpring.current.value : this._scaleSpring.target.value);\n return new $.Point(viewerX * (this.source.dimensions.x / scale),\n viewerY * ((this.source.dimensions.y * this.contentAspectX) / scale));\n },\n\n /**\n * Translates from OpenSeadragon viewer coordinate system to image coordinate system.\n * This method can be called either by passing X,Y coordinates or an {@link OpenSeadragon.Point}.\n * @param {Number|OpenSeadragon.Point} viewerX - The X coordinate or point in viewport coordinate system.\n * @param {Number} [viewerY] - The Y coordinate in viewport coordinate system.\n * @param {Boolean} [current=false] - Pass true to use the current location; false for target location.\n * @return {OpenSeadragon.Point} A point representing the coordinates in the image.\n */\n viewportToImageCoordinates: function(viewerX, viewerY, current) {\n var point;\n if (viewerX instanceof $.Point) {\n //they passed a point instead of individual components\n current = viewerY;\n point = viewerX;\n } else {\n point = new $.Point(viewerX, viewerY);\n }\n\n point = point.rotate(-this.getRotation(current), this._getRotationPoint(current));\n return current ?\n this._viewportToImageDelta(\n point.x - this._xSpring.current.value,\n point.y - this._ySpring.current.value) :\n this._viewportToImageDelta(\n point.x - this._xSpring.target.value,\n point.y - this._ySpring.target.value);\n },\n\n // private\n _imageToViewportDelta: function( imageX, imageY, current ) {\n var scale = (current ? this._scaleSpring.current.value : this._scaleSpring.target.value);\n return new $.Point((imageX / this.source.dimensions.x) * scale,\n (imageY / this.source.dimensions.y / this.contentAspectX) * scale);\n },\n\n /**\n * Translates from image coordinate system to OpenSeadragon viewer coordinate system\n * This method can be called either by passing X,Y coordinates or an {@link OpenSeadragon.Point}.\n * @param {Number|OpenSeadragon.Point} imageX - The X coordinate or point in image coordinate system.\n * @param {Number} [imageY] - The Y coordinate in image coordinate system.\n * @param {Boolean} [current=false] - Pass true to use the current location; false for target location.\n * @return {OpenSeadragon.Point} A point representing the coordinates in the viewport.\n */\n imageToViewportCoordinates: function(imageX, imageY, current) {\n if (imageX instanceof $.Point) {\n //they passed a point instead of individual components\n current = imageY;\n imageY = imageX.y;\n imageX = imageX.x;\n }\n\n var point = this._imageToViewportDelta(imageX, imageY);\n if (current) {\n point.x += this._xSpring.current.value;\n point.y += this._ySpring.current.value;\n } else {\n point.x += this._xSpring.target.value;\n point.y += this._ySpring.target.value;\n }\n\n return point.rotate(this.getRotation(current), this._getRotationPoint(current));\n },\n\n /**\n * Translates from a rectangle which describes a portion of the image in\n * pixel coordinates to OpenSeadragon viewport rectangle coordinates.\n * This method can be called either by passing X,Y,width,height or an {@link OpenSeadragon.Rect}.\n * @param {Number|OpenSeadragon.Rect} imageX - The left coordinate or rectangle in image coordinate system.\n * @param {Number} [imageY] - The top coordinate in image coordinate system.\n * @param {Number} [pixelWidth] - The width in pixel of the rectangle.\n * @param {Number} [pixelHeight] - The height in pixel of the rectangle.\n * @param {Boolean} [current=false] - Pass true to use the current location; false for target location.\n * @return {OpenSeadragon.Rect} A rect representing the coordinates in the viewport.\n */\n imageToViewportRectangle: function(imageX, imageY, pixelWidth, pixelHeight, current) {\n var rect = imageX;\n if (rect instanceof $.Rect) {\n //they passed a rect instead of individual components\n current = imageY;\n } else {\n rect = new $.Rect(imageX, imageY, pixelWidth, pixelHeight);\n }\n\n var coordA = this.imageToViewportCoordinates(rect.getTopLeft(), current);\n var coordB = this._imageToViewportDelta(rect.width, rect.height, current);\n\n return new $.Rect(\n coordA.x,\n coordA.y,\n coordB.x,\n coordB.y,\n rect.degrees + this.getRotation(current)\n );\n },\n\n /**\n * Translates from a rectangle which describes a portion of\n * the viewport in point coordinates to image rectangle coordinates.\n * This method can be called either by passing X,Y,width,height or an {@link OpenSeadragon.Rect}.\n * @param {Number|OpenSeadragon.Rect} viewerX - The left coordinate or rectangle in viewport coordinate system.\n * @param {Number} [viewerY] - The top coordinate in viewport coordinate system.\n * @param {Number} [pointWidth] - The width in viewport coordinate system.\n * @param {Number} [pointHeight] - The height in viewport coordinate system.\n * @param {Boolean} [current=false] - Pass true to use the current location; false for target location.\n * @return {OpenSeadragon.Rect} A rect representing the coordinates in the image.\n */\n viewportToImageRectangle: function( viewerX, viewerY, pointWidth, pointHeight, current ) {\n var rect = viewerX;\n if (viewerX instanceof $.Rect) {\n //they passed a rect instead of individual components\n current = viewerY;\n } else {\n rect = new $.Rect(viewerX, viewerY, pointWidth, pointHeight);\n }\n\n var coordA = this.viewportToImageCoordinates(rect.getTopLeft(), current);\n var coordB = this._viewportToImageDelta(rect.width, rect.height, current);\n\n return new $.Rect(\n coordA.x,\n coordA.y,\n coordB.x,\n coordB.y,\n rect.degrees - this.getRotation(current)\n );\n },\n\n /**\n * Convert pixel coordinates relative to the viewer element to image\n * coordinates.\n * @param {OpenSeadragon.Point} pixel\n * @returns {OpenSeadragon.Point}\n */\n viewerElementToImageCoordinates: function( pixel ) {\n var point = this.viewport.pointFromPixel( pixel, true );\n return this.viewportToImageCoordinates( point );\n },\n\n /**\n * Convert pixel coordinates relative to the image to\n * viewer element coordinates.\n * @param {OpenSeadragon.Point} pixel\n * @returns {OpenSeadragon.Point}\n */\n imageToViewerElementCoordinates: function( pixel ) {\n var point = this.imageToViewportCoordinates( pixel );\n return this.viewport.pixelFromPoint( point, true );\n },\n\n /**\n * Convert pixel coordinates relative to the window to image coordinates.\n * @param {OpenSeadragon.Point} pixel\n * @returns {OpenSeadragon.Point}\n */\n windowToImageCoordinates: function( pixel ) {\n var viewerCoordinates = pixel.minus(\n OpenSeadragon.getElementPosition( this.viewer.element ));\n return this.viewerElementToImageCoordinates( viewerCoordinates );\n },\n\n /**\n * Convert image coordinates to pixel coordinates relative to the window.\n * @param {OpenSeadragon.Point} pixel\n * @returns {OpenSeadragon.Point}\n */\n imageToWindowCoordinates: function( pixel ) {\n var viewerCoordinates = this.imageToViewerElementCoordinates( pixel );\n return viewerCoordinates.plus(\n OpenSeadragon.getElementPosition( this.viewer.element ));\n },\n\n // private\n // Convert rectangle in viewport coordinates to this tiled image point\n // coordinates (x in [0, 1] and y in [0, aspectRatio])\n _viewportToTiledImageRectangle: function(rect) {\n var scale = this._scaleSpring.current.value;\n rect = rect.rotate(-this.getRotation(true), this._getRotationPoint(true));\n return new $.Rect(\n (rect.x - this._xSpring.current.value) / scale,\n (rect.y - this._ySpring.current.value) / scale,\n rect.width / scale,\n rect.height / scale,\n rect.degrees);\n },\n\n /**\n * Convert a viewport zoom to an image zoom.\n * Image zoom: ratio of the original image size to displayed image size.\n * 1 means original image size, 0.5 half size...\n * Viewport zoom: ratio of the displayed image's width to viewport's width.\n * 1 means identical width, 2 means image's width is twice the viewport's width...\n * @function\n * @param {Number} viewportZoom The viewport zoom\n * @returns {Number} imageZoom The image zoom\n */\n viewportToImageZoom: function( viewportZoom ) {\n var ratio = this._scaleSpring.current.value *\n this.viewport._containerInnerSize.x / this.source.dimensions.x;\n return ratio * viewportZoom;\n },\n\n /**\n * Convert an image zoom to a viewport zoom.\n * Image zoom: ratio of the original image size to displayed image size.\n * 1 means original image size, 0.5 half size...\n * Viewport zoom: ratio of the displayed image's width to viewport's width.\n * 1 means identical width, 2 means image's width is twice the viewport's width...\n * Note: not accurate with multi-image.\n * @function\n * @param {Number} imageZoom The image zoom\n * @returns {Number} viewportZoom The viewport zoom\n */\n imageToViewportZoom: function( imageZoom ) {\n var ratio = this._scaleSpring.current.value *\n this.viewport._containerInnerSize.x / this.source.dimensions.x;\n return imageZoom / ratio;\n },\n\n /**\n * Sets the TiledImage's position in the world.\n * @param {OpenSeadragon.Point} position - The new position, in viewport coordinates.\n * @param {Boolean} [immediately=false] - Whether to animate to the new position or snap immediately.\n * @fires OpenSeadragon.TiledImage.event:bounds-change\n */\n setPosition: function(position, immediately) {\n var sameTarget = (this._xSpring.target.value === position.x &&\n this._ySpring.target.value === position.y);\n\n if (immediately) {\n if (sameTarget && this._xSpring.current.value === position.x &&\n this._ySpring.current.value === position.y) {\n return;\n }\n\n this._xSpring.resetTo(position.x);\n this._ySpring.resetTo(position.y);\n this._needsDraw = true;\n } else {\n if (sameTarget) {\n return;\n }\n\n this._xSpring.springTo(position.x);\n this._ySpring.springTo(position.y);\n this._needsDraw = true;\n }\n\n if (!sameTarget) {\n this._raiseBoundsChange();\n }\n },\n\n /**\n * Sets the TiledImage's width in the world, adjusting the height to match based on aspect ratio.\n * @param {Number} width - The new width, in viewport coordinates.\n * @param {Boolean} [immediately=false] - Whether to animate to the new size or snap immediately.\n * @fires OpenSeadragon.TiledImage.event:bounds-change\n */\n setWidth: function(width, immediately) {\n this._setScale(width, immediately);\n },\n\n /**\n * Sets the TiledImage's height in the world, adjusting the width to match based on aspect ratio.\n * @param {Number} height - The new height, in viewport coordinates.\n * @param {Boolean} [immediately=false] - Whether to animate to the new size or snap immediately.\n * @fires OpenSeadragon.TiledImage.event:bounds-change\n */\n setHeight: function(height, immediately) {\n this._setScale(height / this.normHeight, immediately);\n },\n\n /**\n * Positions and scales the TiledImage to fit in the specified bounds.\n * Note: this method fires OpenSeadragon.TiledImage.event:bounds-change\n * twice\n * @param {OpenSeadragon.Rect} bounds The bounds to fit the image into.\n * @param {OpenSeadragon.Placement} [anchor=OpenSeadragon.Placement.CENTER]\n * How to anchor the image in the bounds.\n * @param {Boolean} [immediately=false] Whether to animate to the new size\n * or snap immediately.\n * @fires OpenSeadragon.TiledImage.event:bounds-change\n */\n fitBounds: function(bounds, anchor, immediately) {\n anchor = anchor || $.Placement.CENTER;\n var anchorProperties = $.Placement.properties[anchor];\n var aspectRatio = this.contentAspectX;\n var xOffset = 0;\n var yOffset = 0;\n var displayedWidthRatio = 1;\n var displayedHeightRatio = 1;\n if (this._clip) {\n aspectRatio = this._clip.getAspectRatio();\n displayedWidthRatio = this._clip.width / this.source.dimensions.x;\n displayedHeightRatio = this._clip.height / this.source.dimensions.y;\n if (bounds.getAspectRatio() > aspectRatio) {\n xOffset = this._clip.x / this._clip.height * bounds.height;\n yOffset = this._clip.y / this._clip.height * bounds.height;\n } else {\n xOffset = this._clip.x / this._clip.width * bounds.width;\n yOffset = this._clip.y / this._clip.width * bounds.width;\n }\n }\n\n if (bounds.getAspectRatio() > aspectRatio) {\n // We will have margins on the X axis\n var height = bounds.height / displayedHeightRatio;\n var marginLeft = 0;\n if (anchorProperties.isHorizontallyCentered) {\n marginLeft = (bounds.width - bounds.height * aspectRatio) / 2;\n } else if (anchorProperties.isRight) {\n marginLeft = bounds.width - bounds.height * aspectRatio;\n }\n this.setPosition(\n new $.Point(bounds.x - xOffset + marginLeft, bounds.y - yOffset),\n immediately);\n this.setHeight(height, immediately);\n } else {\n // We will have margins on the Y axis\n var width = bounds.width / displayedWidthRatio;\n var marginTop = 0;\n if (anchorProperties.isVerticallyCentered) {\n marginTop = (bounds.height - bounds.width / aspectRatio) / 2;\n } else if (anchorProperties.isBottom) {\n marginTop = bounds.height - bounds.width / aspectRatio;\n }\n this.setPosition(\n new $.Point(bounds.x - xOffset, bounds.y - yOffset + marginTop),\n immediately);\n this.setWidth(width, immediately);\n }\n },\n\n /**\n * @returns {OpenSeadragon.Rect|null} The TiledImage's current clip rectangle,\n * in image pixels, or null if none.\n */\n getClip: function() {\n if (this._clip) {\n return this._clip.clone();\n }\n\n return null;\n },\n\n /**\n * @param {OpenSeadragon.Rect|null} newClip - An area, in image pixels, to clip to\n * (portions of the image outside of this area will not be visible). Only works on\n * browsers that support the HTML5 canvas.\n * @fires OpenSeadragon.TiledImage.event:clip-change\n */\n setClip: function(newClip) {\n $.console.assert(!newClip || newClip instanceof $.Rect,\n \"[TiledImage.setClip] newClip must be an OpenSeadragon.Rect or null\");\n\n if (newClip instanceof $.Rect) {\n this._clip = newClip.clone();\n } else {\n this._clip = null;\n }\n\n this._needsDraw = true;\n /**\n * Raised when the TiledImage's clip is changed.\n * @event clip-change\n * @memberOf OpenSeadragon.TiledImage\n * @type {object}\n * @property {OpenSeadragon.TiledImage} eventSource - A reference to the\n * TiledImage which raised the event.\n * @property {?Object} userData - Arbitrary subscriber-defined object.\n */\n this.raiseEvent('clip-change');\n },\n\n /**\n * @returns {Number} The TiledImage's current opacity.\n */\n getOpacity: function() {\n return this.opacity;\n },\n\n /**\n * @param {Number} opacity Opacity the tiled image should be drawn at.\n * @fires OpenSeadragon.TiledImage.event:opacity-change\n */\n setOpacity: function(opacity) {\n if (opacity === this.opacity) {\n return;\n }\n\n this.opacity = opacity;\n this._needsDraw = true;\n /**\n * Raised when the TiledImage's opacity is changed.\n * @event opacity-change\n * @memberOf OpenSeadragon.TiledImage\n * @type {object}\n * @property {Number} opacity - The new opacity value.\n * @property {OpenSeadragon.TiledImage} eventSource - A reference to the\n * TiledImage which raised the event.\n * @property {?Object} userData - Arbitrary subscriber-defined object.\n */\n this.raiseEvent('opacity-change', {\n opacity: this.opacity\n });\n },\n\n /**\n * @returns {Boolean} whether the tiledImage can load its tiles even when it has zero opacity.\n */\n getPreload: function() {\n return this._preload;\n },\n\n /**\n * Set true to load even when hidden. Set false to block loading when hidden.\n */\n setPreload: function(preload) {\n this._preload = !!preload;\n this._needsDraw = true;\n },\n\n /**\n * Get the rotation of this tiled image in degrees.\n * @param {Boolean} [current=false] True for current rotation, false for target.\n * @returns {Number} the rotation of this tiled image in degrees.\n */\n getRotation: function(current) {\n return current ?\n this._degreesSpring.current.value :\n this._degreesSpring.target.value;\n },\n\n /**\n * Set the current rotation of this tiled image in degrees.\n * @param {Number} degrees the rotation in degrees.\n * @param {Boolean} [immediately=false] Whether to animate to the new angle\n * or rotate immediately.\n * @fires OpenSeadragon.TiledImage.event:bounds-change\n */\n setRotation: function(degrees, immediately) {\n if (this._degreesSpring.target.value === degrees &&\n this._degreesSpring.isAtTargetValue()) {\n return;\n }\n if (immediately) {\n this._degreesSpring.resetTo(degrees);\n } else {\n this._degreesSpring.springTo(degrees);\n }\n this._needsDraw = true;\n this._raiseBoundsChange();\n },\n\n /**\n * Get the point around which this tiled image is rotated\n * @private\n * @param {Boolean} current True for current rotation point, false for target.\n * @returns {OpenSeadragon.Point}\n */\n _getRotationPoint: function(current) {\n return this.getBoundsNoRotate(current).getCenter();\n },\n\n /**\n * @returns {String} The TiledImage's current compositeOperation.\n */\n getCompositeOperation: function() {\n return this.compositeOperation;\n },\n\n /**\n * @param {String} compositeOperation the tiled image should be drawn with this globalCompositeOperation.\n * @fires OpenSeadragon.TiledImage.event:composite-operation-change\n */\n setCompositeOperation: function(compositeOperation) {\n if (compositeOperation === this.compositeOperation) {\n return;\n }\n\n this.compositeOperation = compositeOperation;\n this._needsDraw = true;\n /**\n * Raised when the TiledImage's opacity is changed.\n * @event composite-operation-change\n * @memberOf OpenSeadragon.TiledImage\n * @type {object}\n * @property {String} compositeOperation - The new compositeOperation value.\n * @property {OpenSeadragon.TiledImage} eventSource - A reference to the\n * TiledImage which raised the event.\n * @property {?Object} userData - Arbitrary subscriber-defined object.\n */\n this.raiseEvent('composite-operation-change', {\n compositeOperation: this.compositeOperation\n });\n },\n\n // private\n _setScale: function(scale, immediately) {\n var sameTarget = (this._scaleSpring.target.value === scale);\n if (immediately) {\n if (sameTarget && this._scaleSpring.current.value === scale) {\n return;\n }\n\n this._scaleSpring.resetTo(scale);\n this._updateForScale();\n this._needsDraw = true;\n } else {\n if (sameTarget) {\n return;\n }\n\n this._scaleSpring.springTo(scale);\n this._updateForScale();\n this._needsDraw = true;\n }\n\n if (!sameTarget) {\n this._raiseBoundsChange();\n }\n },\n\n // private\n _updateForScale: function() {\n this._worldWidthTarget = this._scaleSpring.target.value;\n this._worldHeightTarget = this.normHeight * this._scaleSpring.target.value;\n this._worldWidthCurrent = this._scaleSpring.current.value;\n this._worldHeightCurrent = this.normHeight * this._scaleSpring.current.value;\n },\n\n // private\n _raiseBoundsChange: function() {\n /**\n * Raised when the TiledImage's bounds are changed.\n * Note that this event is triggered only when the animation target is changed;\n * not for every frame of animation.\n * @event bounds-change\n * @memberOf OpenSeadragon.TiledImage\n * @type {object}\n * @property {OpenSeadragon.TiledImage} eventSource - A reference to the\n * TiledImage which raised the event.\n * @property {?Object} userData - Arbitrary subscriber-defined object.\n */\n this.raiseEvent('bounds-change');\n },\n\n // private\n _isBottomItem: function() {\n return this.viewer.world.getItemAt(0) === this;\n },\n\n // private\n _getLevelsInterval: function() {\n var lowestLevel = Math.max(\n this.source.minLevel,\n Math.floor(Math.log(this.minZoomImageRatio) / Math.log(2))\n );\n var currentZeroRatio = this.viewport.deltaPixelsFromPointsNoRotate(\n this.source.getPixelRatio(0), true).x *\n this._scaleSpring.current.value;\n var highestLevel = Math.min(\n Math.abs(this.source.maxLevel),\n Math.abs(Math.floor(\n Math.log(currentZeroRatio / this.minPixelRatio) / Math.log(2)\n ))\n );\n\n // Calculations for the interval of levels to draw\n // can return invalid intervals; fix that here if necessary\n lowestLevel = Math.min(lowestLevel, highestLevel);\n return {\n lowestLevel: lowestLevel,\n highestLevel: highestLevel\n };\n },\n\n /**\n * @private\n * @inner\n * Pretty much every other line in this needs to be documented so it's clear\n * how each piece of this routine contributes to the drawing process. That's\n * why there are so many TODO's inside this function.\n */\n _updateViewport: function() {\n this._needsDraw = false;\n this._tilesLoading = 0;\n this.loadingCoverage = {};\n\n // Reset tile's internal drawn state\n while (this.lastDrawn.length > 0) {\n var tile = this.lastDrawn.pop();\n tile.beingDrawn = false;\n }\n\n var viewport = this.viewport;\n var drawArea = this._viewportToTiledImageRectangle(\n viewport.getBoundsWithMargins(true));\n\n if (!this.wrapHorizontal && !this.wrapVertical) {\n var tiledImageBounds = this._viewportToTiledImageRectangle(\n this.getClippedBounds(true));\n drawArea = drawArea.intersection(tiledImageBounds);\n if (drawArea === null) {\n return;\n }\n }\n\n var levelsInterval = this._getLevelsInterval();\n var lowestLevel = levelsInterval.lowestLevel;\n var highestLevel = levelsInterval.highestLevel;\n var bestTile = null;\n var haveDrawn = false;\n var currentTime = $.now();\n\n // Update any level that will be drawn\n for (var level = highestLevel; level >= lowestLevel; level--) {\n var drawLevel = false;\n\n //Avoid calculations for draw if we have already drawn this\n var currentRenderPixelRatio = viewport.deltaPixelsFromPointsNoRotate(\n this.source.getPixelRatio(level),\n true\n ).x * this._scaleSpring.current.value;\n\n if (level === lowestLevel ||\n (!haveDrawn && currentRenderPixelRatio >= this.minPixelRatio)) {\n drawLevel = true;\n haveDrawn = true;\n } else if (!haveDrawn) {\n continue;\n }\n\n //Perform calculations for draw if we haven't drawn this\n var targetRenderPixelRatio = viewport.deltaPixelsFromPointsNoRotate(\n this.source.getPixelRatio(level),\n false\n ).x * this._scaleSpring.current.value;\n\n var targetZeroRatio = viewport.deltaPixelsFromPointsNoRotate(\n this.source.getPixelRatio(\n Math.max(\n this.source.getClosestLevel(),\n 0\n )\n ),\n false\n ).x * this._scaleSpring.current.value;\n\n var optimalRatio = this.immediateRender ? 1 : targetZeroRatio;\n var levelOpacity = Math.min(1, (currentRenderPixelRatio - 0.5) / 0.5);\n var levelVisibility = optimalRatio / Math.abs(\n optimalRatio - targetRenderPixelRatio\n );\n\n // Update the level and keep track of 'best' tile to load\n bestTile = updateLevel(\n this,\n haveDrawn,\n drawLevel,\n level,\n levelOpacity,\n levelVisibility,\n drawArea,\n currentTime,\n bestTile\n );\n\n // Stop the loop if lower-res tiles would all be covered by\n // already drawn tiles\n if (providesCoverage(this.coverage, level)) {\n break;\n }\n }\n\n // Perform the actual drawing\n drawTiles(this, this.lastDrawn);\n\n // Load the new 'best' tile\n if (bestTile && !bestTile.context2D) {\n loadTile(this, bestTile, currentTime);\n this._needsDraw = true;\n this._setFullyLoaded(false);\n } else {\n this._setFullyLoaded(this._tilesLoading === 0);\n }\n },\n\n // private\n _getCornerTiles: function(level, topLeftBound, bottomRightBound) {\n var leftX;\n var rightX;\n if (this.wrapHorizontal) {\n leftX = $.positiveModulo(topLeftBound.x, 1);\n rightX = $.positiveModulo(bottomRightBound.x, 1);\n } else {\n leftX = Math.max(0, topLeftBound.x);\n rightX = Math.min(1, bottomRightBound.x);\n }\n var topY;\n var bottomY;\n var aspectRatio = 1 / this.source.aspectRatio;\n if (this.wrapVertical) {\n topY = $.positiveModulo(topLeftBound.y, aspectRatio);\n bottomY = $.positiveModulo(bottomRightBound.y, aspectRatio);\n } else {\n topY = Math.max(0, topLeftBound.y);\n bottomY = Math.min(aspectRatio, bottomRightBound.y);\n }\n\n var topLeftTile = this.source.getTileAtPoint(level, new $.Point(leftX, topY));\n var bottomRightTile = this.source.getTileAtPoint(level, new $.Point(rightX, bottomY));\n var numTiles = this.source.getNumTiles(level);\n\n if (this.wrapHorizontal) {\n topLeftTile.x += numTiles.x * Math.floor(topLeftBound.x);\n bottomRightTile.x += numTiles.x * Math.floor(bottomRightBound.x);\n }\n if (this.wrapVertical) {\n topLeftTile.y += numTiles.y * Math.floor(topLeftBound.y / aspectRatio);\n bottomRightTile.y += numTiles.y * Math.floor(bottomRightBound.y / aspectRatio);\n }\n\n return {\n topLeft: topLeftTile,\n bottomRight: bottomRightTile,\n };\n }\n});\n\n/**\n * @private\n * @inner\n * Updates all tiles at a given resolution level.\n * @param {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.\n * @param {Boolean} haveDrawn\n * @param {Boolean} drawLevel\n * @param {Number} level\n * @param {Number} levelOpacity\n * @param {Number} levelVisibility\n * @param {OpenSeadragon.Point} viewportTL - The index of the most top-left visible tile.\n * @param {OpenSeadragon.Point} viewportBR - The index of the most bottom-right visible tile.\n * @param {Number} currentTime\n * @param {OpenSeadragon.Tile} best - The current \"best\" tile to draw.\n */\nfunction updateLevel(tiledImage, haveDrawn, drawLevel, level, levelOpacity,\n levelVisibility, drawArea, currentTime, best) {\n\n var topLeftBound = drawArea.getBoundingBox().getTopLeft();\n var bottomRightBound = drawArea.getBoundingBox().getBottomRight();\n\n if (tiledImage.viewer) {\n /**\n * - Needs documentation -\n *\n * @event update-level\n * @memberof OpenSeadragon.Viewer\n * @type {object}\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.\n * @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.\n * @property {Object} havedrawn\n * @property {Object} level\n * @property {Object} opacity\n * @property {Object} visibility\n * @property {OpenSeadragon.Rect} drawArea\n * @property {Object} topleft deprecated, use drawArea instead\n * @property {Object} bottomright deprecated, use drawArea instead\n * @property {Object} currenttime\n * @property {Object} best\n * @property {?Object} userData - Arbitrary subscriber-defined object.\n */\n tiledImage.viewer.raiseEvent('update-level', {\n tiledImage: tiledImage,\n havedrawn: haveDrawn,\n level: level,\n opacity: levelOpacity,\n visibility: levelVisibility,\n drawArea: drawArea,\n topleft: topLeftBound,\n bottomright: bottomRightBound,\n currenttime: currentTime,\n best: best\n });\n }\n\n resetCoverage(tiledImage.coverage, level);\n resetCoverage(tiledImage.loadingCoverage, level);\n\n //OK, a new drawing so do your calculations\n var cornerTiles = tiledImage._getCornerTiles(level, topLeftBound, bottomRightBound);\n var topLeftTile = cornerTiles.topLeft;\n var bottomRightTile = cornerTiles.bottomRight;\n var numberOfTiles = tiledImage.source.getNumTiles(level);\n\n var viewportCenter = tiledImage.viewport.pixelFromPoint(\n tiledImage.viewport.getCenter());\n for (var x = topLeftTile.x; x <= bottomRightTile.x; x++) {\n for (var y = topLeftTile.y; y <= bottomRightTile.y; y++) {\n\n // Optimisation disabled with wrapping because getTileBounds does not\n // work correctly with x and y outside of the number of tiles\n if (!tiledImage.wrapHorizontal && !tiledImage.wrapVertical) {\n var tileBounds = tiledImage.source.getTileBounds(level, x, y);\n if (drawArea.intersection(tileBounds) === null) {\n // This tile is outside of the viewport, no need to draw it\n continue;\n }\n }\n\n best = updateTile(\n tiledImage,\n drawLevel,\n haveDrawn,\n x, y,\n level,\n levelOpacity,\n levelVisibility,\n viewportCenter,\n numberOfTiles,\n currentTime,\n best\n );\n\n }\n }\n\n return best;\n}\n\n/**\n * @private\n * @inner\n * Update a single tile at a particular resolution level.\n * @param {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.\n * @param {Boolean} haveDrawn\n * @param {Boolean} drawLevel\n * @param {Number} x\n * @param {Number} y\n * @param {Number} level\n * @param {Number} levelOpacity\n * @param {Number} levelVisibility\n * @param {OpenSeadragon.Point} viewportCenter\n * @param {Number} numberOfTiles\n * @param {Number} currentTime\n * @param {OpenSeadragon.Tile} best - The current \"best\" tile to draw.\n */\nfunction updateTile( tiledImage, haveDrawn, drawLevel, x, y, level, levelOpacity, levelVisibility, viewportCenter, numberOfTiles, currentTime, best){\n\n var tile = getTile(\n x, y,\n level,\n tiledImage,\n tiledImage.source,\n tiledImage.tilesMatrix,\n currentTime,\n numberOfTiles,\n tiledImage._worldWidthCurrent,\n tiledImage._worldHeightCurrent\n ),\n drawTile = drawLevel;\n\n if( tiledImage.viewer ){\n /**\n * - Needs documentation -\n *\n * @event update-tile\n * @memberof OpenSeadragon.Viewer\n * @type {object}\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.\n * @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.\n * @property {OpenSeadragon.Tile} tile\n * @property {?Object} userData - Arbitrary subscriber-defined object.\n */\n tiledImage.viewer.raiseEvent( 'update-tile', {\n tiledImage: tiledImage,\n tile: tile\n });\n }\n\n setCoverage( tiledImage.coverage, level, x, y, false );\n\n var loadingCoverage = tile.loaded || tile.loading || isCovered(tiledImage.loadingCoverage, level, x, y);\n setCoverage(tiledImage.loadingCoverage, level, x, y, loadingCoverage);\n\n if ( !tile.exists ) {\n return best;\n }\n\n if ( haveDrawn && !drawTile ) {\n if ( isCovered( tiledImage.coverage, level, x, y ) ) {\n setCoverage( tiledImage.coverage, level, x, y, true );\n } else {\n drawTile = true;\n }\n }\n\n if ( !drawTile ) {\n return best;\n }\n\n positionTile(\n tile,\n tiledImage.source.tileOverlap,\n tiledImage.viewport,\n viewportCenter,\n levelVisibility,\n tiledImage\n );\n\n if (!tile.loaded) {\n if (tile.context2D) {\n setTileLoaded(tiledImage, tile);\n } else {\n var imageRecord = tiledImage._tileCache.getImageRecord(tile.cacheKey);\n if (imageRecord) {\n var image = imageRecord.getImage();\n setTileLoaded(tiledImage, tile, image);\n }\n }\n }\n\n if ( tile.loaded ) {\n var needsDraw = blendTile(\n tiledImage,\n tile,\n x, y,\n level,\n levelOpacity,\n currentTime\n );\n\n if ( needsDraw ) {\n tiledImage._needsDraw = true;\n }\n } else if ( tile.loading ) {\n // the tile is already in the download queue\n tiledImage._tilesLoading++;\n } else if (!loadingCoverage) {\n best = compareTiles( best, tile );\n }\n\n return best;\n}\n\n/**\n * @private\n * @inner\n * Obtains a tile at the given location.\n * @param {Number} x\n * @param {Number} y\n * @param {Number} level\n * @param {OpenSeadragon.TiledImage} tiledImage\n * @param {OpenSeadragon.TileSource} tileSource\n * @param {Object} tilesMatrix - A '3d' dictionary [level][x][y] --> Tile.\n * @param {Number} time\n * @param {Number} numTiles\n * @param {Number} worldWidth\n * @param {Number} worldHeight\n * @returns {OpenSeadragon.Tile}\n */\nfunction getTile(\n x, y,\n level,\n tiledImage,\n tileSource,\n tilesMatrix,\n time,\n numTiles,\n worldWidth,\n worldHeight\n) {\n var xMod,\n yMod,\n bounds,\n sourceBounds,\n exists,\n url,\n ajaxHeaders,\n context2D,\n tile;\n\n if ( !tilesMatrix[ level ] ) {\n tilesMatrix[ level ] = {};\n }\n if ( !tilesMatrix[ level ][ x ] ) {\n tilesMatrix[ level ][ x ] = {};\n }\n\n if ( !tilesMatrix[ level ][ x ][ y ] ) {\n xMod = ( numTiles.x + ( x % numTiles.x ) ) % numTiles.x;\n yMod = ( numTiles.y + ( y % numTiles.y ) ) % numTiles.y;\n bounds = tileSource.getTileBounds( level, xMod, yMod );\n sourceBounds = tileSource.getTileBounds( level, xMod, yMod, true );\n exists = tileSource.tileExists( level, xMod, yMod );\n url = tileSource.getTileUrl( level, xMod, yMod );\n\n // Headers are only applicable if loadTilesWithAjax is set\n if (tiledImage.loadTilesWithAjax) {\n ajaxHeaders = tileSource.getTileAjaxHeaders( level, xMod, yMod );\n // Combine tile AJAX headers with tiled image AJAX headers (if applicable)\n if ($.isPlainObject(tiledImage.ajaxHeaders)) {\n ajaxHeaders = $.extend({}, tiledImage.ajaxHeaders, ajaxHeaders);\n }\n } else {\n ajaxHeaders = null;\n }\n\n context2D = tileSource.getContext2D ?\n tileSource.getContext2D(level, xMod, yMod) : undefined;\n\n bounds.x += ( x - xMod ) / numTiles.x;\n bounds.y += (worldHeight / worldWidth) * (( y - yMod ) / numTiles.y);\n\n tile = new $.Tile(\n level,\n x,\n y,\n bounds,\n exists,\n url,\n context2D,\n tiledImage.loadTilesWithAjax,\n ajaxHeaders,\n sourceBounds\n );\n\n if (xMod === numTiles.x - 1) {\n tile.isRightMost = true;\n }\n\n if (yMod === numTiles.y - 1) {\n tile.isBottomMost = true;\n }\n\n tilesMatrix[ level ][ x ][ y ] = tile;\n }\n\n tile = tilesMatrix[ level ][ x ][ y ];\n tile.lastTouchTime = time;\n\n return tile;\n}\n\n/**\n * @private\n * @inner\n * Dispatch a job to the ImageLoader to load the Image for a Tile.\n * @param {OpenSeadragon.TiledImage} tiledImage\n * @param {OpenSeadragon.Tile} tile\n * @param {Number} time\n */\nfunction loadTile( tiledImage, tile, time ) {\n tile.loading = true;\n var customAjax;\n\n // Bind tiledImage if filtering Ajax\n if ($.isFunction(tiledImage.makeAjaxRequest)) {\n customAjax = tiledImage.makeAjaxRequest;\n }\n\n tiledImage._imageLoader.addJob({\n src: tile.url,\n makeAjaxRequest: customAjax,\n loadWithAjax: tile.loadWithAjax,\n ajaxHeaders: tile.ajaxHeaders,\n crossOriginPolicy: tiledImage.crossOriginPolicy,\n ajaxWithCredentials: tiledImage.ajaxWithCredentials,\n callback: function( image, errorMsg, tileRequest ){\n onTileLoad( tiledImage, tile, time, image, errorMsg, tileRequest );\n },\n abort: function() {\n tile.loading = false;\n }\n });\n}\n\n/**\n * @private\n * @inner\n * Callback fired when a Tile's Image finished downloading.\n * @param {OpenSeadragon.TiledImage} tiledImage\n * @param {OpenSeadragon.Tile} tile\n * @param {Number} time\n * @param {Image} image\n * @param {String} errorMsg\n * @param {XMLHttpRequest} tileRequest\n */\nfunction onTileLoad( tiledImage, tile, time, image, errorMsg, tileRequest ) {\n if ( !image ) {\n $.console.log( \"Tile %s failed to load: %s - error: %s\", tile, tile.url, errorMsg );\n /**\n * Triggered when a tile fails to load.\n *\n * @event tile-load-failed\n * @memberof OpenSeadragon.Viewer\n * @type {object}\n * @property {OpenSeadragon.Tile} tile - The tile that failed to load.\n * @property {OpenSeadragon.TiledImage} tiledImage - The tiled image the tile belongs to.\n * @property {number} time - The time in milliseconds when the tile load began.\n * @property {string} message - The error message.\n * @property {XMLHttpRequest} tileRequest - The XMLHttpRequest used to load the tile if available.\n */\n tiledImage.viewer.raiseEvent(\"tile-load-failed\", {\n tile: tile,\n tiledImage: tiledImage,\n time: time,\n message: errorMsg,\n tileRequest: tileRequest\n });\n tile.loading = false;\n tile.exists = false;\n return;\n }\n\n if ( time < tiledImage.lastResetTime ) {\n $.console.log( \"Ignoring tile %s loaded before reset: %s\", tile, tile.url );\n tile.loading = false;\n return;\n }\n\n var finish = function() {\n var cutoff = tiledImage.source.getClosestLevel();\n setTileLoaded(tiledImage, tile, image, cutoff, tileRequest);\n };\n\n // Check if we're mid-update; this can happen on IE8 because image load events for\n // cached images happen immediately there\n if ( !tiledImage._midDraw ) {\n finish();\n } else {\n // Wait until after the update, in case caching unloads any tiles\n window.setTimeout( finish, 1);\n }\n}\n\n/**\n * @private\n * @inner\n * @param {OpenSeadragon.TiledImage} tiledImage\n * @param {OpenSeadragon.Tile} tile\n * @param {Image} image\n * @param {Number} cutoff\n */\nfunction setTileLoaded(tiledImage, tile, image, cutoff, tileRequest) {\n var increment = 0;\n\n function getCompletionCallback() {\n increment++;\n return completionCallback;\n }\n\n function completionCallback() {\n increment--;\n if (increment === 0) {\n tile.loading = false;\n tile.loaded = true;\n if (!tile.context2D) {\n tiledImage._tileCache.cacheTile({\n image: image,\n tile: tile,\n cutoff: cutoff,\n tiledImage: tiledImage\n });\n }\n tiledImage._needsDraw = true;\n }\n }\n\n /**\n * Triggered when a tile has just been loaded in memory. That means that the\n * image has been downloaded and can be modified before being drawn to the canvas.\n *\n * @event tile-loaded\n * @memberof OpenSeadragon.Viewer\n * @type {object}\n * @property {Image} image - The image of the tile.\n * @property {OpenSeadragon.TiledImage} tiledImage - The tiled image of the loaded tile.\n * @property {OpenSeadragon.Tile} tile - The tile which has been loaded.\n * @property {XMLHttpRequest} tiledImage - The AJAX request that loaded this tile (if applicable).\n * @property {function} getCompletionCallback - A function giving a callback to call\n * when the asynchronous processing of the image is done. The image will be\n * marked as entirely loaded when the callback has been called once for each\n * call to getCompletionCallback.\n */\n tiledImage.viewer.raiseEvent(\"tile-loaded\", {\n tile: tile,\n tiledImage: tiledImage,\n tileRequest: tileRequest,\n image: image,\n getCompletionCallback: getCompletionCallback\n });\n // In case the completion callback is never called, we at least force it once.\n getCompletionCallback()();\n}\n\n/**\n * @private\n * @inner\n * @param {OpenSeadragon.Tile} tile\n * @param {Boolean} overlap\n * @param {OpenSeadragon.Viewport} viewport\n * @param {OpenSeadragon.Point} viewportCenter\n * @param {Number} levelVisibility\n * @param {OpenSeadragon.TiledImage} tiledImage\n */\nfunction positionTile( tile, overlap, viewport, viewportCenter, levelVisibility, tiledImage ){\n var boundsTL = tile.bounds.getTopLeft();\n\n boundsTL.x *= tiledImage._scaleSpring.current.value;\n boundsTL.y *= tiledImage._scaleSpring.current.value;\n boundsTL.x += tiledImage._xSpring.current.value;\n boundsTL.y += tiledImage._ySpring.current.value;\n\n var boundsSize = tile.bounds.getSize();\n\n boundsSize.x *= tiledImage._scaleSpring.current.value;\n boundsSize.y *= tiledImage._scaleSpring.current.value;\n\n var positionC = viewport.pixelFromPointNoRotate(boundsTL, true),\n positionT = viewport.pixelFromPointNoRotate(boundsTL, false),\n sizeC = viewport.deltaPixelsFromPointsNoRotate(boundsSize, true),\n sizeT = viewport.deltaPixelsFromPointsNoRotate(boundsSize, false),\n tileCenter = positionT.plus( sizeT.divide( 2 ) ),\n tileSquaredDistance = viewportCenter.squaredDistanceTo( tileCenter );\n\n if ( !overlap ) {\n sizeC = sizeC.plus( new $.Point( 1, 1 ) );\n }\n\n if (tile.isRightMost && tiledImage.wrapHorizontal) {\n sizeC.x += 0.75; // Otherwise Firefox and Safari show seams\n }\n\n if (tile.isBottomMost && tiledImage.wrapVertical) {\n sizeC.y += 0.75; // Otherwise Firefox and Safari show seams\n }\n\n tile.position = positionC;\n tile.size = sizeC;\n tile.squaredDistance = tileSquaredDistance;\n tile.visibility = levelVisibility;\n}\n\n/**\n * @private\n * @inner\n * Updates the opacity of a tile according to the time it has been on screen\n * to perform a fade-in.\n * Updates coverage once a tile is fully opaque.\n * Returns whether the fade-in has completed.\n *\n * @param {OpenSeadragon.TiledImage} tiledImage\n * @param {OpenSeadragon.Tile} tile\n * @param {Number} x\n * @param {Number} y\n * @param {Number} level\n * @param {Number} levelOpacity\n * @param {Number} currentTime\n * @returns {Boolean}\n */\nfunction blendTile( tiledImage, tile, x, y, level, levelOpacity, currentTime ){\n var blendTimeMillis = 1000 * tiledImage.blendTime,\n deltaTime,\n opacity;\n\n if ( !tile.blendStart ) {\n tile.blendStart = currentTime;\n }\n\n deltaTime = currentTime - tile.blendStart;\n opacity = blendTimeMillis ? Math.min( 1, deltaTime / ( blendTimeMillis ) ) : 1;\n\n if ( tiledImage.alwaysBlend ) {\n opacity *= levelOpacity;\n }\n\n tile.opacity = opacity;\n\n tiledImage.lastDrawn.push( tile );\n\n if ( opacity == 1 ) {\n setCoverage( tiledImage.coverage, level, x, y, true );\n tiledImage._hasOpaqueTile = true;\n } else if ( deltaTime < blendTimeMillis ) {\n return true;\n }\n\n return false;\n}\n\n/**\n * @private\n * @inner\n * Returns true if the given tile provides coverage to lower-level tiles of\n * lower resolution representing the same content. If neither x nor y is\n * given, returns true if the entire visible level provides coverage.\n *\n * Note that out-of-bounds tiles provide coverage in this sense, since\n * there's no content that they would need to cover. Tiles at non-existent\n * levels that are within the image bounds, however, do not.\n *\n * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean.\n * @param {Number} level - The resolution level of the tile.\n * @param {Number} x - The X position of the tile.\n * @param {Number} y - The Y position of the tile.\n * @returns {Boolean}\n */\nfunction providesCoverage( coverage, level, x, y ) {\n var rows,\n cols,\n i, j;\n\n if ( !coverage[ level ] ) {\n return false;\n }\n\n if ( x === undefined || y === undefined ) {\n rows = coverage[ level ];\n for ( i in rows ) {\n if ( rows.hasOwnProperty( i ) ) {\n cols = rows[ i ];\n for ( j in cols ) {\n if ( cols.hasOwnProperty( j ) && !cols[ j ] ) {\n return false;\n }\n }\n }\n }\n\n return true;\n }\n\n return (\n coverage[ level ][ x] === undefined ||\n coverage[ level ][ x ][ y ] === undefined ||\n coverage[ level ][ x ][ y ] === true\n );\n}\n\n/**\n * @private\n * @inner\n * Returns true if the given tile is completely covered by higher-level\n * tiles of higher resolution representing the same content. If neither x\n * nor y is given, returns true if the entire visible level is covered.\n *\n * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean.\n * @param {Number} level - The resolution level of the tile.\n * @param {Number} x - The X position of the tile.\n * @param {Number} y - The Y position of the tile.\n * @returns {Boolean}\n */\nfunction isCovered( coverage, level, x, y ) {\n if ( x === undefined || y === undefined ) {\n return providesCoverage( coverage, level + 1 );\n } else {\n return (\n providesCoverage( coverage, level + 1, 2 * x, 2 * y ) &&\n providesCoverage( coverage, level + 1, 2 * x, 2 * y + 1 ) &&\n providesCoverage( coverage, level + 1, 2 * x + 1, 2 * y ) &&\n providesCoverage( coverage, level + 1, 2 * x + 1, 2 * y + 1 )\n );\n }\n}\n\n/**\n * @private\n * @inner\n * Sets whether the given tile provides coverage or not.\n *\n * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean.\n * @param {Number} level - The resolution level of the tile.\n * @param {Number} x - The X position of the tile.\n * @param {Number} y - The Y position of the tile.\n * @param {Boolean} covers - Whether the tile provides coverage.\n */\nfunction setCoverage( coverage, level, x, y, covers ) {\n if ( !coverage[ level ] ) {\n $.console.warn(\n \"Setting coverage for a tile before its level's coverage has been reset: %s\",\n level\n );\n return;\n }\n\n if ( !coverage[ level ][ x ] ) {\n coverage[ level ][ x ] = {};\n }\n\n coverage[ level ][ x ][ y ] = covers;\n}\n\n/**\n * @private\n * @inner\n * Resets coverage information for the given level. This should be called\n * after every draw routine. Note that at the beginning of the next draw\n * routine, coverage for every visible tile should be explicitly set.\n *\n * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean.\n * @param {Number} level - The resolution level of tiles to completely reset.\n */\nfunction resetCoverage( coverage, level ) {\n coverage[ level ] = {};\n}\n\n/**\n * @private\n * @inner\n * Determines whether the 'last best' tile for the area is better than the\n * tile in question.\n *\n * @param {OpenSeadragon.Tile} previousBest\n * @param {OpenSeadragon.Tile} tile\n * @returns {OpenSeadragon.Tile} The new best tile.\n */\nfunction compareTiles( previousBest, tile ) {\n if ( !previousBest ) {\n return tile;\n }\n\n if ( tile.visibility > previousBest.visibility ) {\n return tile;\n } else if ( tile.visibility == previousBest.visibility ) {\n if ( tile.squaredDistance < previousBest.squaredDistance ) {\n return tile;\n }\n }\n\n return previousBest;\n}\n\n/**\n * @private\n * @inner\n * Draws a TiledImage.\n * @param {OpenSeadragon.TiledImage} tiledImage\n * @param {OpenSeadragon.Tile[]} lastDrawn - An unordered list of Tiles drawn last frame.\n */\nfunction drawTiles( tiledImage, lastDrawn ) {\n if (tiledImage.opacity === 0 || (lastDrawn.length === 0 && !tiledImage.placeholderFillStyle)) {\n return;\n }\n\n var tile = lastDrawn[0];\n var useSketch;\n\n if (tile) {\n useSketch = tiledImage.opacity < 1 ||\n (tiledImage.compositeOperation &&\n tiledImage.compositeOperation !== 'source-over') ||\n (!tiledImage._isBottomItem() && tile._hasTransparencyChannel());\n }\n\n var sketchScale;\n var sketchTranslate;\n\n var zoom = tiledImage.viewport.getZoom(true);\n var imageZoom = tiledImage.viewportToImageZoom(zoom);\n\n if (lastDrawn.length > 1 &&\n imageZoom > tiledImage.smoothTileEdgesMinZoom &&\n !tiledImage.iOSDevice &&\n tiledImage.getRotation(true) % 360 === 0 && // TODO: support tile edge smoothing with tiled image rotation.\n $.supportsCanvas) {\n // When zoomed in a lot (>100%) the tile edges are visible.\n // So we have to composite them at ~100% and scale them up together.\n // Note: Disabled on iOS devices per default as it causes a native crash\n useSketch = true;\n sketchScale = tile.getScaleForEdgeSmoothing();\n sketchTranslate = tile.getTranslationForEdgeSmoothing(sketchScale,\n tiledImage._drawer.getCanvasSize(false),\n tiledImage._drawer.getCanvasSize(true));\n }\n\n var bounds;\n if (useSketch) {\n if (!sketchScale) {\n // Except when edge smoothing, we only clean the part of the\n // sketch canvas we are going to use for performance reasons.\n bounds = tiledImage.viewport.viewportToViewerElementRectangle(\n tiledImage.getClippedBounds(true))\n .getIntegerBoundingBox()\n .times($.pixelDensityRatio);\n }\n tiledImage._drawer._clear(true, bounds);\n }\n\n // When scaling, we must rotate only when blending the sketch canvas to\n // avoid interpolation\n if (!sketchScale) {\n if (tiledImage.viewport.degrees !== 0) {\n tiledImage._drawer._offsetForRotation({\n degrees: tiledImage.viewport.degrees,\n useSketch: useSketch\n });\n } else {\n if(tiledImage._drawer.viewer.viewport.flipped) {\n tiledImage._drawer._flip({});\n }\n }\n if (tiledImage.getRotation(true) % 360 !== 0) {\n tiledImage._drawer._offsetForRotation({\n degrees: tiledImage.getRotation(true),\n point: tiledImage.viewport.pixelFromPointNoRotate(\n tiledImage._getRotationPoint(true), true),\n useSketch: useSketch\n });\n }\n }\n\n var usedClip = false;\n if ( tiledImage._clip ) {\n tiledImage._drawer.saveContext(useSketch);\n\n var box = tiledImage.imageToViewportRectangle(tiledImage._clip, true);\n box = box.rotate(-tiledImage.getRotation(true), tiledImage._getRotationPoint(true));\n var clipRect = tiledImage._drawer.viewportToDrawerRectangle(box);\n if (sketchScale) {\n clipRect = clipRect.times(sketchScale);\n }\n if (sketchTranslate) {\n clipRect = clipRect.translate(sketchTranslate);\n }\n tiledImage._drawer.setClip(clipRect, useSketch);\n\n usedClip = true;\n }\n\n if ( tiledImage.placeholderFillStyle && tiledImage._hasOpaqueTile === false ) {\n var placeholderRect = tiledImage._drawer.viewportToDrawerRectangle(tiledImage.getBounds(true));\n if (sketchScale) {\n placeholderRect = placeholderRect.times(sketchScale);\n }\n if (sketchTranslate) {\n placeholderRect = placeholderRect.translate(sketchTranslate);\n }\n\n var fillStyle = null;\n if ( typeof tiledImage.placeholderFillStyle === \"function\" ) {\n fillStyle = tiledImage.placeholderFillStyle(tiledImage, tiledImage._drawer.context);\n }\n else {\n fillStyle = tiledImage.placeholderFillStyle;\n }\n\n tiledImage._drawer.drawRectangle(placeholderRect, fillStyle, useSketch);\n }\n\n for (var i = lastDrawn.length - 1; i >= 0; i--) {\n tile = lastDrawn[ i ];\n tiledImage._drawer.drawTile( tile, tiledImage._drawingHandler, useSketch, sketchScale, sketchTranslate );\n tile.beingDrawn = true;\n\n if( tiledImage.viewer ){\n /**\n * - Needs documentation -\n *\n * @event tile-drawn\n * @memberof OpenSeadragon.Viewer\n * @type {object}\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.\n * @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.\n * @property {OpenSeadragon.Tile} tile\n * @property {?Object} userData - Arbitrary subscriber-defined object.\n */\n tiledImage.viewer.raiseEvent( 'tile-drawn', {\n tiledImage: tiledImage,\n tile: tile\n });\n }\n }\n\n if ( usedClip ) {\n tiledImage._drawer.restoreContext( useSketch );\n }\n\n if (!sketchScale) {\n if (tiledImage.getRotation(true) % 360 !== 0) {\n tiledImage._drawer._restoreRotationChanges(useSketch);\n }\n if (tiledImage.viewport.degrees !== 0) {\n tiledImage._drawer._restoreRotationChanges(useSketch);\n } else{\n if(tiledImage._drawer.viewer.viewport.flipped) {\n tiledImage._drawer._flip({});\n }\n }\n }\n\n if (useSketch) {\n if (sketchScale) {\n if (tiledImage.viewport.degrees !== 0) {\n tiledImage._drawer._offsetForRotation({\n degrees: tiledImage.viewport.degrees,\n useSketch: false\n });\n }\n if (tiledImage.getRotation(true) % 360 !== 0) {\n tiledImage._drawer._offsetForRotation({\n degrees: tiledImage.getRotation(true),\n point: tiledImage.viewport.pixelFromPointNoRotate(\n tiledImage._getRotationPoint(true), true),\n useSketch: false\n });\n }\n }\n tiledImage._drawer.blendSketch({\n opacity: tiledImage.opacity,\n scale: sketchScale,\n translate: sketchTranslate,\n compositeOperation: tiledImage.compositeOperation,\n bounds: bounds\n });\n if (sketchScale) {\n if (tiledImage.getRotation(true) % 360 !== 0) {\n tiledImage._drawer._restoreRotationChanges(false);\n }\n if (tiledImage.viewport.degrees !== 0) {\n tiledImage._drawer._restoreRotationChanges(false);\n }\n }\n }\n drawDebugInfo( tiledImage, lastDrawn );\n}\n\n/**\n * @private\n * @inner\n * Draws special debug information for a TiledImage if in debug mode.\n * @param {OpenSeadragon.TiledImage} tiledImage\n * @param {OpenSeadragon.Tile[]} lastDrawn - An unordered list of Tiles drawn last frame.\n */\nfunction drawDebugInfo( tiledImage, lastDrawn ) {\n if( tiledImage.debugMode ) {\n for ( var i = lastDrawn.length - 1; i >= 0; i-- ) {\n var tile = lastDrawn[ i ];\n try {\n tiledImage._drawer.drawDebugInfo(\n tile, lastDrawn.length, i, tiledImage);\n } catch(e) {\n $.console.error(e);\n }\n }\n }\n}\n\n}( OpenSeadragon ));\n\n/*\n * OpenSeadragon - TileCache\n *\n * Copyright (C) 2009 CodePlex Foundation\n * Copyright (C) 2010-2013 OpenSeadragon contributors\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are\n * met:\n *\n * - Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * - Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n *\n * - Neither the name of CodePlex Foundation nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\n * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n(function( $ ){\n\n// private class\nvar TileRecord = function( options ) {\n $.console.assert( options, \"[TileCache.cacheTile] options is required\" );\n $.console.assert( options.tile, \"[TileCache.cacheTile] options.tile is required\" );\n $.console.assert( options.tiledImage, \"[TileCache.cacheTile] options.tiledImage is required\" );\n this.tile = options.tile;\n this.tiledImage = options.tiledImage;\n};\n\n// private class\nvar ImageRecord = function(options) {\n $.console.assert( options, \"[ImageRecord] options is required\" );\n $.console.assert( options.image, \"[ImageRecord] options.image is required\" );\n this._image = options.image;\n this._tiles = [];\n};\n\nImageRecord.prototype = {\n destroy: function() {\n this._image = null;\n this._renderedContext = null;\n this._tiles = null;\n },\n\n getImage: function() {\n return this._image;\n },\n\n getRenderedContext: function() {\n if (!this._renderedContext) {\n var canvas = document.createElement( 'canvas' );\n canvas.width = this._image.width;\n canvas.height = this._image.height;\n this._renderedContext = canvas.getContext('2d');\n this._renderedContext.drawImage( this._image, 0, 0 );\n //since we are caching the prerendered image on a canvas\n //allow the image to not be held in memory\n this._image = null;\n }\n return this._renderedContext;\n },\n\n setRenderedContext: function(renderedContext) {\n $.console.error(\"ImageRecord.setRenderedContext is deprecated. \" +\n \"The rendered context should be created by the ImageRecord \" +\n \"itself when calling ImageRecord.getRenderedContext.\");\n this._renderedContext = renderedContext;\n },\n\n addTile: function(tile) {\n $.console.assert(tile, '[ImageRecord.addTile] tile is required');\n this._tiles.push(tile);\n },\n\n removeTile: function(tile) {\n for (var i = 0; i < this._tiles.length; i++) {\n if (this._tiles[i] === tile) {\n this._tiles.splice(i, 1);\n return;\n }\n }\n\n $.console.warn('[ImageRecord.removeTile] trying to remove unknown tile', tile);\n },\n\n getTileCount: function() {\n return this._tiles.length;\n }\n};\n\n/**\n * @class TileCache\n * @memberof OpenSeadragon\n * @classdesc Stores all the tiles displayed in a {@link OpenSeadragon.Viewer}.\n * You generally won't have to interact with the TileCache directly.\n * @param {Object} options - Configuration for this TileCache.\n * @param {Number} [options.maxImageCacheCount] - See maxImageCacheCount in\n * {@link OpenSeadragon.Options} for details.\n */\n$.TileCache = function( options ) {\n options = options || {};\n\n this._maxImageCacheCount = options.maxImageCacheCount || $.DEFAULT_SETTINGS.maxImageCacheCount;\n this._tilesLoaded = [];\n this._imagesLoaded = [];\n this._imagesLoadedCount = 0;\n};\n\n/** @lends OpenSeadragon.TileCache.prototype */\n$.TileCache.prototype = {\n /**\n * @returns {Number} The total number of tiles that have been loaded by\n * this TileCache.\n */\n numTilesLoaded: function() {\n return this._tilesLoaded.length;\n },\n\n /**\n * Caches the specified tile, removing an old tile if necessary to stay under the\n * maxImageCacheCount specified on construction. Note that if multiple tiles reference\n * the same image, there may be more tiles than maxImageCacheCount; the goal is to keep\n * the number of images below that number. Note, as well, that even the number of images\n * may temporarily surpass that number, but should eventually come back down to the max specified.\n * @param {Object} options - Tile info.\n * @param {OpenSeadragon.Tile} options.tile - The tile to cache.\n * @param {String} options.tile.cacheKey - The unique key used to identify this tile in the cache.\n * @param {Image} options.image - The image of the tile to cache.\n * @param {OpenSeadragon.TiledImage} options.tiledImage - The TiledImage that owns that tile.\n * @param {Number} [options.cutoff=0] - If adding this tile goes over the cache max count, this\n * function will release an old tile. The cutoff option specifies a tile level at or below which\n * tiles will not be released.\n */\n cacheTile: function( options ) {\n $.console.assert( options, \"[TileCache.cacheTile] options is required\" );\n $.console.assert( options.tile, \"[TileCache.cacheTile] options.tile is required\" );\n $.console.assert( options.tile.cacheKey, \"[TileCache.cacheTile] options.tile.cacheKey is required\" );\n $.console.assert( options.tiledImage, \"[TileCache.cacheTile] options.tiledImage is required\" );\n\n var cutoff = options.cutoff || 0;\n var insertionIndex = this._tilesLoaded.length;\n\n var imageRecord = this._imagesLoaded[options.tile.cacheKey];\n if (!imageRecord) {\n $.console.assert( options.image, \"[TileCache.cacheTile] options.image is required to create an ImageRecord\" );\n imageRecord = this._imagesLoaded[options.tile.cacheKey] = new ImageRecord({\n image: options.image\n });\n\n this._imagesLoadedCount++;\n }\n\n imageRecord.addTile(options.tile);\n options.tile.cacheImageRecord = imageRecord;\n\n // Note that just because we're unloading a tile doesn't necessarily mean\n // we're unloading an image. With repeated calls it should sort itself out, though.\n if ( this._imagesLoadedCount > this._maxImageCacheCount ) {\n var worstTile = null;\n var worstTileIndex = -1;\n var worstTileRecord = null;\n var prevTile, worstTime, worstLevel, prevTime, prevLevel, prevTileRecord;\n\n for ( var i = this._tilesLoaded.length - 1; i >= 0; i-- ) {\n prevTileRecord = this._tilesLoaded[ i ];\n prevTile = prevTileRecord.tile;\n\n if ( prevTile.level <= cutoff || prevTile.beingDrawn ) {\n continue;\n } else if ( !worstTile ) {\n worstTile = prevTile;\n worstTileIndex = i;\n worstTileRecord = prevTileRecord;\n continue;\n }\n\n prevTime = prevTile.lastTouchTime;\n worstTime = worstTile.lastTouchTime;\n prevLevel = prevTile.level;\n worstLevel = worstTile.level;\n\n if ( prevTime < worstTime ||\n ( prevTime == worstTime && prevLevel > worstLevel ) ) {\n worstTile = prevTile;\n worstTileIndex = i;\n worstTileRecord = prevTileRecord;\n }\n }\n\n if ( worstTile && worstTileIndex >= 0 ) {\n this._unloadTile(worstTileRecord);\n insertionIndex = worstTileIndex;\n }\n }\n\n this._tilesLoaded[ insertionIndex ] = new TileRecord({\n tile: options.tile,\n tiledImage: options.tiledImage\n });\n },\n\n /**\n * Clears all tiles associated with the specified tiledImage.\n * @param {OpenSeadragon.TiledImage} tiledImage\n */\n clearTilesFor: function( tiledImage ) {\n $.console.assert(tiledImage, '[TileCache.clearTilesFor] tiledImage is required');\n var tileRecord;\n for ( var i = 0; i < this._tilesLoaded.length; ++i ) {\n tileRecord = this._tilesLoaded[ i ];\n if ( tileRecord.tiledImage === tiledImage ) {\n this._unloadTile(tileRecord);\n this._tilesLoaded.splice( i, 1 );\n i--;\n }\n }\n },\n\n // private\n getImageRecord: function(cacheKey) {\n $.console.assert(cacheKey, '[TileCache.getImageRecord] cacheKey is required');\n return this._imagesLoaded[cacheKey];\n },\n\n // private\n _unloadTile: function(tileRecord) {\n $.console.assert(tileRecord, '[TileCache._unloadTile] tileRecord is required');\n var tile = tileRecord.tile;\n var tiledImage = tileRecord.tiledImage;\n\n tile.unload();\n tile.cacheImageRecord = null;\n\n var imageRecord = this._imagesLoaded[tile.cacheKey];\n imageRecord.removeTile(tile);\n if (!imageRecord.getTileCount()) {\n imageRecord.destroy();\n delete this._imagesLoaded[tile.cacheKey];\n this._imagesLoadedCount--;\n }\n\n /**\n * Triggered when a tile has just been unloaded from memory.\n *\n * @event tile-unloaded\n * @memberof OpenSeadragon.Viewer\n * @type {object}\n * @property {OpenSeadragon.TiledImage} tiledImage - The tiled image of the unloaded tile.\n * @property {OpenSeadragon.Tile} tile - The tile which has been unloaded.\n */\n tiledImage.viewer.raiseEvent(\"tile-unloaded\", {\n tile: tile,\n tiledImage: tiledImage\n });\n }\n};\n\n}( OpenSeadragon ));\n\n/*\n * OpenSeadragon - World\n *\n * Copyright (C) 2009 CodePlex Foundation\n * Copyright (C) 2010-2013 OpenSeadragon contributors\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are\n * met:\n *\n * - Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * - Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n *\n * - Neither the name of CodePlex Foundation nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\n * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n(function( $ ){\n\n/**\n * @class World\n * @memberof OpenSeadragon\n * @extends OpenSeadragon.EventSource\n * @classdesc Keeps track of all of the tiled images in the scene.\n * @param {Object} options - World options.\n * @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this World.\n **/\n$.World = function( options ) {\n var _this = this;\n\n $.console.assert( options.viewer, \"[World] options.viewer is required\" );\n\n $.EventSource.call( this );\n\n this.viewer = options.viewer;\n this._items = [];\n this._needsDraw = false;\n this._autoRefigureSizes = true;\n this._needsSizesFigured = false;\n this._delegatedFigureSizes = function(event) {\n if (_this._autoRefigureSizes) {\n _this._figureSizes();\n } else {\n _this._needsSizesFigured = true;\n }\n };\n\n this._figureSizes();\n};\n\n$.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.World.prototype */{\n /**\n * Add the specified item.\n * @param {OpenSeadragon.TiledImage} item - The item to add.\n * @param {Number} [options.index] - Index for the item. If not specified, goes at the top.\n * @fires OpenSeadragon.World.event:add-item\n * @fires OpenSeadragon.World.event:metrics-change\n */\n addItem: function( item, options ) {\n $.console.assert(item, \"[World.addItem] item is required\");\n $.console.assert(item instanceof $.TiledImage, \"[World.addItem] only TiledImages supported at this time\");\n\n options = options || {};\n if (options.index !== undefined) {\n var index = Math.max(0, Math.min(this._items.length, options.index));\n this._items.splice(index, 0, item);\n } else {\n this._items.push( item );\n }\n\n if (this._autoRefigureSizes) {\n this._figureSizes();\n } else {\n this._needsSizesFigured = true;\n }\n\n this._needsDraw = true;\n\n item.addHandler('bounds-change', this._delegatedFigureSizes);\n item.addHandler('clip-change', this._delegatedFigureSizes);\n\n /**\n * Raised when an item is added to the World.\n * @event add-item\n * @memberOf OpenSeadragon.World\n * @type {object}\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the World which raised the event.\n * @property {OpenSeadragon.TiledImage} item - The item that has been added.\n * @property {?Object} userData - Arbitrary subscriber-defined object.\n */\n this.raiseEvent( 'add-item', {\n item: item\n } );\n },\n\n /**\n * Get the item at the specified index.\n * @param {Number} index - The item's index.\n * @returns {OpenSeadragon.TiledImage} The item at the specified index.\n */\n getItemAt: function( index ) {\n $.console.assert(index !== undefined, \"[World.getItemAt] index is required\");\n return this._items[ index ];\n },\n\n /**\n * Get the index of the given item or -1 if not present.\n * @param {OpenSeadragon.TiledImage} item - The item.\n * @returns {Number} The index of the item or -1 if not present.\n */\n getIndexOfItem: function( item ) {\n $.console.assert(item, \"[World.getIndexOfItem] item is required\");\n return $.indexOf( this._items, item );\n },\n\n /**\n * @returns {Number} The number of items used.\n */\n getItemCount: function() {\n return this._items.length;\n },\n\n /**\n * Change the index of a item so that it appears over or under others.\n * @param {OpenSeadragon.TiledImage} item - The item to move.\n * @param {Number} index - The new index.\n * @fires OpenSeadragon.World.event:item-index-change\n */\n setItemIndex: function( item, index ) {\n $.console.assert(item, \"[World.setItemIndex] item is required\");\n $.console.assert(index !== undefined, \"[World.setItemIndex] index is required\");\n\n var oldIndex = this.getIndexOfItem( item );\n\n if ( index >= this._items.length ) {\n throw new Error( \"Index bigger than number of layers.\" );\n }\n\n if ( index === oldIndex || oldIndex === -1 ) {\n return;\n }\n\n this._items.splice( oldIndex, 1 );\n this._items.splice( index, 0, item );\n this._needsDraw = true;\n\n /**\n * Raised when the order of the indexes has been changed.\n * @event item-index-change\n * @memberOf OpenSeadragon.World\n * @type {object}\n * @property {OpenSeadragon.World} eventSource - A reference to the World which raised the event.\n * @property {OpenSeadragon.TiledImage} item - The item whose index has\n * been changed\n * @property {Number} previousIndex - The previous index of the item\n * @property {Number} newIndex - The new index of the item\n * @property {?Object} userData - Arbitrary subscriber-defined object.\n */\n this.raiseEvent( 'item-index-change', {\n item: item,\n previousIndex: oldIndex,\n newIndex: index\n } );\n },\n\n /**\n * Remove an item.\n * @param {OpenSeadragon.TiledImage} item - The item to remove.\n * @fires OpenSeadragon.World.event:remove-item\n * @fires OpenSeadragon.World.event:metrics-change\n */\n removeItem: function( item ) {\n $.console.assert(item, \"[World.removeItem] item is required\");\n\n var index = $.indexOf(this._items, item );\n if ( index === -1 ) {\n return;\n }\n\n item.removeHandler('bounds-change', this._delegatedFigureSizes);\n item.removeHandler('clip-change', this._delegatedFigureSizes);\n item.destroy();\n this._items.splice( index, 1 );\n this._figureSizes();\n this._needsDraw = true;\n this._raiseRemoveItem(item);\n },\n\n /**\n * Remove all items.\n * @fires OpenSeadragon.World.event:remove-item\n * @fires OpenSeadragon.World.event:metrics-change\n */\n removeAll: function() {\n // We need to make sure any pending images are canceled so the world items don't get messed up\n this.viewer._cancelPendingImages();\n var item;\n var i;\n for (i = 0; i < this._items.length; i++) {\n item = this._items[i];\n item.removeHandler('bounds-change', this._delegatedFigureSizes);\n item.removeHandler('clip-change', this._delegatedFigureSizes);\n item.destroy();\n }\n\n var removedItems = this._items;\n this._items = [];\n this._figureSizes();\n this._needsDraw = true;\n\n for (i = 0; i < removedItems.length; i++) {\n item = removedItems[i];\n this._raiseRemoveItem(item);\n }\n },\n\n /**\n * Clears all tiles and triggers updates for all items.\n */\n resetItems: function() {\n for ( var i = 0; i < this._items.length; i++ ) {\n this._items[i].reset();\n }\n },\n\n /**\n * Updates (i.e. animates bounds of) all items.\n */\n update: function() {\n var animated = false;\n for ( var i = 0; i < this._items.length; i++ ) {\n animated = this._items[i].update() || animated;\n }\n\n return animated;\n },\n\n /**\n * Draws all items.\n */\n draw: function() {\n for ( var i = 0; i < this._items.length; i++ ) {\n this._items[i].draw();\n }\n\n this._needsDraw = false;\n },\n\n /**\n * @returns {Boolean} true if any items need updating.\n */\n needsDraw: function() {\n for ( var i = 0; i < this._items.length; i++ ) {\n if ( this._items[i].needsDraw() ) {\n return true;\n }\n }\n return this._needsDraw;\n },\n\n /**\n * @returns {OpenSeadragon.Rect} The smallest rectangle that encloses all items, in viewport coordinates.\n */\n getHomeBounds: function() {\n return this._homeBounds.clone();\n },\n\n /**\n * To facilitate zoom constraints, we keep track of the pixel density of the\n * densest item in the World (i.e. the item whose content size to viewport size\n * ratio is the highest) and save it as this \"content factor\".\n * @returns {Number} the number of content units per viewport unit.\n */\n getContentFactor: function() {\n return this._contentFactor;\n },\n\n /**\n * As a performance optimization, setting this flag to false allows the bounds-change event handler\n * on tiledImages to skip calculations on the world bounds. If a lot of images are going to be positioned in\n * rapid succession, this is a good idea. When finished, setAutoRefigureSizes should be called with true\n * or the system may behave oddly.\n * @param {Boolean} [value] The value to which to set the flag.\n */\n setAutoRefigureSizes: function(value) {\n this._autoRefigureSizes = value;\n if (value & this._needsSizesFigured) {\n this._figureSizes();\n this._needsSizesFigured = false;\n }\n },\n\n /**\n * Arranges all of the TiledImages with the specified settings.\n * @param {Object} options - Specifies how to arrange.\n * @param {Boolean} [options.immediately=false] - Whether to animate to the new arrangement.\n * @param {String} [options.layout] - See collectionLayout in {@link OpenSeadragon.Options}.\n * @param {Number} [options.rows] - See collectionRows in {@link OpenSeadragon.Options}.\n * @param {Number} [options.columns] - See collectionColumns in {@link OpenSeadragon.Options}.\n * @param {Number} [options.tileSize] - See collectionTileSize in {@link OpenSeadragon.Options}.\n * @param {Number} [options.tileMargin] - See collectionTileMargin in {@link OpenSeadragon.Options}.\n * @fires OpenSeadragon.World.event:metrics-change\n */\n arrange: function(options) {\n options = options || {};\n var immediately = options.immediately || false;\n var layout = options.layout || $.DEFAULT_SETTINGS.collectionLayout;\n var rows = options.rows || $.DEFAULT_SETTINGS.collectionRows;\n var columns = options.columns || $.DEFAULT_SETTINGS.collectionColumns;\n var tileSize = options.tileSize || $.DEFAULT_SETTINGS.collectionTileSize;\n var tileMargin = options.tileMargin || $.DEFAULT_SETTINGS.collectionTileMargin;\n var increment = tileSize + tileMargin;\n var wrap;\n if (!options.rows && columns) {\n wrap = columns;\n } else {\n wrap = Math.ceil(this._items.length / rows);\n }\n var x = 0;\n var y = 0;\n var item, box, width, height, position;\n\n this.setAutoRefigureSizes(false);\n for (var i = 0; i < this._items.length; i++) {\n if (i && (i % wrap) === 0) {\n if (layout === 'horizontal') {\n y += increment;\n x = 0;\n } else {\n x += increment;\n y = 0;\n }\n }\n\n item = this._items[i];\n box = item.getBounds();\n if (box.width > box.height) {\n width = tileSize;\n } else {\n width = tileSize * (box.width / box.height);\n }\n\n height = width * (box.height / box.width);\n position = new $.Point(x + ((tileSize - width) / 2),\n y + ((tileSize - height) / 2));\n\n item.setPosition(position, immediately);\n item.setWidth(width, immediately);\n\n if (layout === 'horizontal') {\n x += increment;\n } else {\n y += increment;\n }\n }\n this.setAutoRefigureSizes(true);\n },\n\n // private\n _figureSizes: function() {\n var oldHomeBounds = this._homeBounds ? this._homeBounds.clone() : null;\n var oldContentSize = this._contentSize ? this._contentSize.clone() : null;\n var oldContentFactor = this._contentFactor || 0;\n\n if (!this._items.length) {\n this._homeBounds = new $.Rect(0, 0, 1, 1);\n this._contentSize = new $.Point(1, 1);\n this._contentFactor = 1;\n } else {\n var item = this._items[0];\n var bounds = item.getBounds();\n this._contentFactor = item.getContentSize().x / bounds.width;\n var clippedBounds = item.getClippedBounds().getBoundingBox();\n var left = clippedBounds.x;\n var top = clippedBounds.y;\n var right = clippedBounds.x + clippedBounds.width;\n var bottom = clippedBounds.y + clippedBounds.height;\n for (var i = 1; i < this._items.length; i++) {\n item = this._items[i];\n bounds = item.getBounds();\n this._contentFactor = Math.max(this._contentFactor,\n item.getContentSize().x / bounds.width);\n clippedBounds = item.getClippedBounds().getBoundingBox();\n left = Math.min(left, clippedBounds.x);\n top = Math.min(top, clippedBounds.y);\n right = Math.max(right, clippedBounds.x + clippedBounds.width);\n bottom = Math.max(bottom, clippedBounds.y + clippedBounds.height);\n }\n\n this._homeBounds = new $.Rect(left, top, right - left, bottom - top);\n this._contentSize = new $.Point(\n this._homeBounds.width * this._contentFactor,\n this._homeBounds.height * this._contentFactor);\n }\n\n if (this._contentFactor !== oldContentFactor ||\n !this._homeBounds.equals(oldHomeBounds) ||\n !this._contentSize.equals(oldContentSize)) {\n /**\n * Raised when the home bounds or content factor change.\n * @event metrics-change\n * @memberOf OpenSeadragon.World\n * @type {object}\n * @property {OpenSeadragon.World} eventSource - A reference to the World which raised the event.\n * @property {?Object} userData - Arbitrary subscriber-defined object.\n */\n this.raiseEvent('metrics-change', {});\n }\n },\n\n // private\n _raiseRemoveItem: function(item) {\n /**\n * Raised when an item is removed.\n * @event remove-item\n * @memberOf OpenSeadragon.World\n * @type {object}\n * @property {OpenSeadragon.World} eventSource - A reference to the World which raised the event.\n * @property {OpenSeadragon.TiledImage} item - The item's underlying item.\n * @property {?Object} userData - Arbitrary subscriber-defined object.\n */\n this.raiseEvent( 'remove-item', { item: item } );\n }\n});\n\n}( OpenSeadragon ));\n\n//# sourceMappingURL=openseadragon.js.map\n\n//# sourceURL=webpack:///./node_modules/openseadragon/build/openseadragon/openseadragon.js?");
+eval("var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;//! openseadragon 2.3.1\r\n//! Built on 2024-03-01\r\n//! Git commit: unknown\r\n//! http://openseadragon.github.io\r\n//! License: http://openseadragon.github.io/license/\r\n\r\n/*\r\n * OpenSeadragon\r\n *\r\n * Copyright (C) 2009 CodePlex Foundation\r\n * Copyright (C) 2010-2013 OpenSeadragon contributors\r\n *\r\n * Redistribution and use in source and binary forms, with or without\r\n * modification, are permitted provided that the following conditions are\r\n * met:\r\n *\r\n * - Redistributions of source code must retain the above copyright notice,\r\n * this list of conditions and the following disclaimer.\r\n *\r\n * - Redistributions in binary form must reproduce the above copyright\r\n * notice, this list of conditions and the following disclaimer in the\r\n * documentation and/or other materials provided with the distribution.\r\n *\r\n * - Neither the name of CodePlex Foundation nor the names of its\r\n * contributors may be used to endorse or promote products derived from\r\n * this software without specific prior written permission.\r\n *\r\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\r\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\r\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\r\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\r\n * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\r\n * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\r\n * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\r\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\r\n * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r\n * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r\n */\r\n\r\n/*\r\n * Portions of this source file taken from jQuery:\r\n *\r\n * Copyright 2011 John Resig\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining\r\n * a copy of this software and associated documentation files (the\r\n * \"Software\"), to deal in the Software without restriction, including\r\n * without limitation the rights to use, copy, modify, merge, publish,\r\n * distribute, sublicense, and/or sell copies of the Software, and to\r\n * permit persons to whom the Software is furnished to do so, subject to\r\n * the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\r\n * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\r\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\r\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\n\r\n/*\r\n * Portions of this source file taken from mattsnider.com:\r\n *\r\n * Copyright (c) 2006-2013 Matt Snider\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a\r\n * copy of this software and associated documentation files (the \"Software\"),\r\n * to deal in the Software without restriction, including without limitation\r\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\r\n * and/or sell copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be included\r\n * in all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\r\n * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\r\n * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\r\n * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT\r\n * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR\r\n * THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\n\r\n\r\n/**\r\n * @namespace OpenSeadragon\r\n * @version openseadragon 2.3.1\r\n * @classdesc The root namespace for OpenSeadragon. All utility methods\r\n * and classes are defined on or below this namespace.\r\n *\r\n */\r\n\r\n\r\n// Typedefs\r\n\r\n /**\r\n * All required and optional settings for instantiating a new instance of an OpenSeadragon image viewer.\r\n *\r\n * @typedef {Object} Options\r\n * @memberof OpenSeadragon\r\n *\r\n * @property {String} id\r\n * Id of the element to append the viewer's container element to. If not provided, the 'element' property must be provided.\r\n * If both the element and id properties are specified, the viewer is appended to the element provided in the element property.\r\n *\r\n * @property {Element} element\r\n * The element to append the viewer's container element to. If not provided, the 'id' property must be provided.\r\n * If both the element and id properties are specified, the viewer is appended to the element provided in the element property.\r\n *\r\n * @property {Array|String|Function|Object} [tileSources=null]\r\n * Tile source(s) to open initially. This is a complex parameter; see\r\n * {@link OpenSeadragon.Viewer#open} for details.\r\n *\r\n * @property {Number} [tabIndex=0]\r\n * Tabbing order index to assign to the viewer element. Positive values are selected in increasing order. When tabIndex is 0\r\n * source order is used. A negative value omits the viewer from the tabbing order.\r\n *\r\n * @property {Array} overlays Array of objects defining permanent overlays of\r\n * the viewer. The overlays added via this option and later removed with\r\n * {@link OpenSeadragon.Viewer#removeOverlay} will be added back when a new\r\n * image is opened.\r\n * To add overlays which can be definitively removed, one must use\r\n * {@link OpenSeadragon.Viewer#addOverlay}\r\n * If displaying a sequence of images, the overlays can be associated\r\n * with a specific page by passing the overlays array to the page's\r\n * tile source configuration.\r\n * Expected properties:\r\n * * x, y, (or px, py for pixel coordinates) to define the location.\r\n * * width, height in point if using x,y or in pixels if using px,py. If width\r\n * and height are specified, the overlay size is adjusted when zooming,\r\n * otherwise the size stays the size of the content (or the size defined by CSS).\r\n * * className to associate a class to the overlay\r\n * * id to set the overlay element. If an element with this id already exists,\r\n * it is reused, otherwise it is created. If not specified, a new element is\r\n * created.\r\n * * placement a string to define the relative position to the viewport.\r\n * Only used if no width and height are specified. Default: 'TOP_LEFT'.\r\n * See {@link OpenSeadragon.Placement} for possible values.\r\n *\r\n * @property {String} [xmlPath=null]\r\n * DEPRECATED. A relative path to load a DZI file from the server.\r\n * Prefer the newer Options.tileSources.\r\n *\r\n * @property {String} [prefixUrl='/images/']\r\n * Prepends the prefixUrl to navImages paths, which is very useful\r\n * since the default paths are rarely useful for production\r\n * environments.\r\n *\r\n * @property {OpenSeadragon.NavImages} [navImages]\r\n * An object with a property for each button or other built-in navigation\r\n * control, eg the current 'zoomIn', 'zoomOut', 'home', and 'fullpage'.\r\n * Each of those in turn provides an image path for each state of the button\r\n * or navigation control, eg 'REST', 'GROUP', 'HOVER', 'PRESS'. Finally the\r\n * image paths, by default assume there is a folder on the servers root path\r\n * called '/images', eg '/images/zoomin_rest.png'. If you need to adjust\r\n * these paths, prefer setting the option.prefixUrl rather than overriding\r\n * every image path directly through this setting.\r\n *\r\n * @property {Boolean} [debugMode=false]\r\n * TODO: provide an in-screen panel providing event detail feedback.\r\n *\r\n * @property {String} [debugGridColor=['#437AB2', '#1B9E77', '#D95F02', '#7570B3', '#E7298A', '#66A61E', '#E6AB02', '#A6761D', '#666666']]\r\n * The colors of grids in debug mode. Each tiled image's grid uses a consecutive color.\r\n * If there are more tiled images than provided colors, the color vector is recycled.\r\n *\r\n * @property {Number} [blendTime=0]\r\n * Specifies the duration of animation as higher or lower level tiles are\r\n * replacing the existing tile.\r\n *\r\n * @property {Boolean} [alwaysBlend=false]\r\n * Forces the tile to always blend. By default the tiles skip blending\r\n * when the blendTime is surpassed and the current animation frame would\r\n * not complete the blend.\r\n *\r\n * @property {Boolean} [autoHideControls=true]\r\n * If the user stops interacting with the viewport, fade the navigation\r\n * controls. Useful for presentation since the controls are by default\r\n * floated on top of the image the user is viewing.\r\n *\r\n * @property {Boolean} [immediateRender=false]\r\n * Render the best closest level first, ignoring the lowering levels which\r\n * provide the effect of very blurry to sharp. It is recommended to change\r\n * setting to true for mobile devices.\r\n *\r\n * @property {Number} [defaultZoomLevel=0]\r\n * Zoom level to use when image is first opened or the home button is clicked.\r\n * If 0, adjusts to fit viewer.\r\n *\r\n * @property {Number} [opacity=1]\r\n * Default proportional opacity of the tiled images (1=opaque, 0=hidden)\r\n * Hidden images do not draw and only load when preloading is allowed.\r\n *\r\n * @property {Boolean} [preload=false]\r\n * Default switch for loading hidden images (true loads, false blocks)\r\n *\r\n * @property {String} [compositeOperation=null]\r\n * Valid values are 'source-over', 'source-atop', 'source-in', 'source-out',\r\n * 'destination-over', 'destination-atop', 'destination-in',\r\n * 'destination-out', 'lighter', 'copy' or 'xor'\r\n *\r\n * @property {String|CanvasGradient|CanvasPattern|Function} [placeholderFillStyle=null]\r\n * Draws a colored rectangle behind the tile if it is not loaded yet.\r\n * You can pass a CSS color value like \"#FF8800\".\r\n * When passing a function the tiledImage and canvas context are available as argument which is useful when you draw a gradient or pattern.\r\n *\r\n * @property {Number} [degrees=0]\r\n * Initial rotation.\r\n *\r\n * @property {Boolean} [flipped=false]\r\n * Initial flip state.\r\n *\r\n * @property {Number} [minZoomLevel=null]\r\n *\r\n * @property {Number} [maxZoomLevel=null]\r\n *\r\n * @property {Boolean} [homeFillsViewer=false]\r\n * Make the 'home' button fill the viewer and clip the image, instead\r\n * of fitting the image to the viewer and letterboxing.\r\n *\r\n * @property {Boolean} [panHorizontal=true]\r\n * Allow horizontal pan.\r\n *\r\n * @property {Boolean} [panVertical=true]\r\n * Allow vertical pan.\r\n *\r\n * @property {Boolean} [constrainDuringPan=false]\r\n *\r\n * @property {Boolean} [wrapHorizontal=false]\r\n * Set to true to force the image to wrap horizontally within the viewport.\r\n * Useful for maps or images representing the surface of a sphere or cylinder.\r\n *\r\n * @property {Boolean} [wrapVertical=false]\r\n * Set to true to force the image to wrap vertically within the viewport.\r\n * Useful for maps or images representing the surface of a sphere or cylinder.\r\n *\r\n * @property {Number} [minZoomImageRatio=0.9]\r\n * The minimum percentage ( expressed as a number between 0 and 1 ) of\r\n * the viewport height or width at which the zoom out will be constrained.\r\n * Setting it to 0, for example will allow you to zoom out infinity.\r\n *\r\n * @property {Number} [maxZoomPixelRatio=1.1]\r\n * The maximum ratio to allow a zoom-in to affect the highest level pixel\r\n * ratio. This can be set to Infinity to allow 'infinite' zooming into the\r\n * image though it is less effective visually if the HTML5 Canvas is not\r\n * availble on the viewing device.\r\n *\r\n * @property {Number} [smoothTileEdgesMinZoom=1.1]\r\n * A zoom percentage ( where 1 is 100% ) of the highest resolution level.\r\n * When zoomed in beyond this value alternative compositing will be used to\r\n * smooth out the edges between tiles. This will have a performance impact.\r\n * Can be set to Infinity to turn it off.\r\n * Note: This setting is ignored on iOS devices due to a known bug (See {@link https://github.com/openseadragon/openseadragon/issues/952})\r\n *\r\n * @property {Boolean} [iOSDevice=?]\r\n * True if running on an iOS device, false otherwise.\r\n * Used to disable certain features that behave differently on iOS devices.\r\n *\r\n * @property {Boolean} [autoResize=true]\r\n * Set to false to prevent polling for viewer size changes. Useful for providing custom resize behavior.\r\n *\r\n * @property {Boolean} [preserveImageSizeOnResize=false]\r\n * Set to true to have the image size preserved when the viewer is resized. This requires autoResize=true (default).\r\n *\r\n * @property {Number} [minScrollDeltaTime=50]\r\n * Number of milliseconds between canvas-scroll events. This value helps normalize the rate of canvas-scroll\r\n * events between different devices, causing the faster devices to slow down enough to make the zoom control\r\n * more manageable.\r\n *\r\n * @property {Number} [pixelsPerWheelLine=40]\r\n * For pixel-resolution scrolling devices, the number of pixels equal to one scroll line.\r\n *\r\n * @property {Number} [pixelsPerArrowPress=40]\r\n * The number of pixels viewport moves when an arrow key is pressed.\r\n *\r\n * @property {Number} [visibilityRatio=0.5]\r\n * The percentage ( as a number from 0 to 1 ) of the source image which\r\n * must be kept within the viewport. If the image is dragged beyond that\r\n * limit, it will 'bounce' back until the minimum visibility ratio is\r\n * achieved. Setting this to 0 and wrapHorizontal ( or wrapVertical ) to\r\n * true will provide the effect of an infinitely scrolling viewport.\r\n *\r\n * @property {Object} [viewportMargins={}]\r\n * Pushes the \"home\" region in from the sides by the specified amounts.\r\n * Possible subproperties (Numbers, in screen coordinates): left, top, right, bottom.\r\n *\r\n * @property {Number} [imageLoaderLimit=0]\r\n * The maximum number of image requests to make concurrently. By default\r\n * it is set to 0 allowing the browser to make the maximum number of\r\n * image requests in parallel as allowed by the browsers policy.\r\n *\r\n * @property {Number} [clickTimeThreshold=300]\r\n * The number of milliseconds within which a pointer down-up event combination\r\n * will be treated as a click gesture.\r\n *\r\n * @property {Number} [clickDistThreshold=5]\r\n * The maximum distance allowed between a pointer down event and a pointer up event\r\n * to be treated as a click gesture.\r\n *\r\n * @property {Number} [dblClickTimeThreshold=300]\r\n * The number of milliseconds within which two pointer down-up event combinations\r\n * will be treated as a double-click gesture.\r\n *\r\n * @property {Number} [dblClickDistThreshold=20]\r\n * The maximum distance allowed between two pointer click events\r\n * to be treated as a double-click gesture.\r\n *\r\n * @property {Number} [springStiffness=6.5]\r\n *\r\n * @property {Number} [animationTime=1.2]\r\n * Specifies the animation duration per each {@link OpenSeadragon.Spring}\r\n * which occur when the image is dragged or zoomed.\r\n *\r\n * @property {OpenSeadragon.GestureSettings} [gestureSettingsMouse]\r\n * Settings for gestures generated by a mouse pointer device. (See {@link OpenSeadragon.GestureSettings})\r\n * @property {Boolean} [gestureSettingsMouse.scrollToZoom=true] - Zoom on scroll gesture\r\n * @property {Boolean} [gestureSettingsMouse.clickToZoom=true] - Zoom on click gesture\r\n * @property {Boolean} [gestureSettingsMouse.dblClickToZoom=false] - Zoom on double-click gesture. Note: If set to true\r\n * then clickToZoom should be set to false to prevent multiple zooms.\r\n * @property {Boolean} [gestureSettingsMouse.pinchToZoom=false] - Zoom on pinch gesture\r\n * @property {Boolean} [gestureSettingsMouse.zoomToRefPoint=true] - If zoomToRefPoint is true, the zoom is centered at the pointer position. Otherwise,\r\n * the zoom is centered at the canvas center.\r\n * @property {Boolean} [gestureSettingsMouse.flickEnabled=false] - Enable flick gesture\r\n * @property {Number} [gestureSettingsMouse.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)\r\n * @property {Number} [gestureSettingsMouse.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture\r\n * @property {Boolean} [gestureSettingsMouse.pinchRotate=false] - If pinchRotate is true, the user will have the ability to rotate the image using their fingers.\r\n *\r\n * @property {OpenSeadragon.GestureSettings} [gestureSettingsTouch]\r\n * Settings for gestures generated by a touch pointer device. (See {@link OpenSeadragon.GestureSettings})\r\n * @property {Boolean} [gestureSettingsTouch.scrollToZoom=false] - Zoom on scroll gesture\r\n * @property {Boolean} [gestureSettingsTouch.clickToZoom=false] - Zoom on click gesture\r\n * @property {Boolean} [gestureSettingsTouch.dblClickToZoom=true] - Zoom on double-click gesture. Note: If set to true\r\n * then clickToZoom should be set to false to prevent multiple zooms.\r\n * @property {Boolean} [gestureSettingsTouch.pinchToZoom=true] - Zoom on pinch gesture\r\n * @property {Boolean} [gestureSettingsTouch.zoomToRefPoint=true] - If zoomToRefPoint is true, the zoom is centered at the pointer position. Otherwise,\r\n * the zoom is centered at the canvas center.\r\n * @property {Boolean} [gestureSettingsTouch.flickEnabled=true] - Enable flick gesture\r\n * @property {Number} [gestureSettingsTouch.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)\r\n * @property {Number} [gestureSettingsTouch.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture\r\n * @property {Boolean} [gestureSettingsTouch.pinchRotate=false] - If pinchRotate is true, the user will have the ability to rotate the image using their fingers.\r\n *\r\n * @property {OpenSeadragon.GestureSettings} [gestureSettingsPen]\r\n * Settings for gestures generated by a pen pointer device. (See {@link OpenSeadragon.GestureSettings})\r\n * @property {Boolean} [gestureSettingsPen.scrollToZoom=false] - Zoom on scroll gesture\r\n * @property {Boolean} [gestureSettingsPen.clickToZoom=true] - Zoom on click gesture\r\n * @property {Boolean} [gestureSettingsPen.dblClickToZoom=false] - Zoom on double-click gesture. Note: If set to true\r\n * then clickToZoom should be set to false to prevent multiple zooms.\r\n * @property {Boolean} [gestureSettingsPen.pinchToZoom=false] - Zoom on pinch gesture\r\n * @property {Boolean} [gestureSettingsPan.zoomToRefPoint=true] - If zoomToRefPoint is true, the zoom is centered at the pointer position. Otherwise,\r\n * the zoom is centered at the canvas center.\r\n * @property {Boolean} [gestureSettingsPen.flickEnabled=false] - Enable flick gesture\r\n * @property {Number} [gestureSettingsPen.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)\r\n * @property {Number} [gestureSettingsPen.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture\r\n * @property {Boolean} [gestureSettingsPen.pinchRotate=false] - If pinchRotate is true, the user will have the ability to rotate the image using their fingers.\r\n *\r\n * @property {OpenSeadragon.GestureSettings} [gestureSettingsUnknown]\r\n * Settings for gestures generated by unknown pointer devices. (See {@link OpenSeadragon.GestureSettings})\r\n * @property {Boolean} [gestureSettingsUnknown.scrollToZoom=true] - Zoom on scroll gesture\r\n * @property {Boolean} [gestureSettingsUnknown.clickToZoom=false] - Zoom on click gesture\r\n * @property {Boolean} [gestureSettingsUnknown.dblClickToZoom=true] - Zoom on double-click gesture. Note: If set to true\r\n * then clickToZoom should be set to false to prevent multiple zooms.\r\n * @property {Boolean} [gestureSettingsUnknown.pinchToZoom=true] - Zoom on pinch gesture\r\n * @property {Boolean} [gestureSettingsUnknown.zoomToRefPoint=true] - If zoomToRefPoint is true, the zoom is centered at the pointer position. Otherwise,\r\n * the zoom is centered at the canvas center.\r\n * @property {Boolean} [gestureSettingsUnknown.flickEnabled=true] - Enable flick gesture\r\n * @property {Number} [gestureSettingsUnknown.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)\r\n * @property {Number} [gestureSettingsUnknown.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture\r\n * @property {Boolean} [gestureSettingsUnknown.pinchRotate=false] - If pinchRotate is true, the user will have the ability to rotate the image using their fingers.\r\n *\r\n * @property {Number} [zoomPerClick=2.0]\r\n * The \"zoom distance\" per mouse click or touch tap. Note: Setting this to 1.0 effectively disables the click-to-zoom feature (also see gestureSettings[Mouse|Touch|Pen].clickToZoom/dblClickToZoom).\r\n *\r\n * @property {Number} [zoomPerScroll=1.2]\r\n * The \"zoom distance\" per mouse scroll or touch pinch. Note: Setting this to 1.0 effectively disables the mouse-wheel zoom feature (also see gestureSettings[Mouse|Touch|Pen].scrollToZoom}).\r\n *\r\n * @property {Number} [zoomPerSecond=1.0]\r\n * The number of seconds to animate a single zoom event over.\r\n *\r\n * @property {Boolean} [showNavigator=false]\r\n * Set to true to make the navigator minimap appear.\r\n *\r\n * @property {String} [navigatorId=navigator-GENERATED DATE]\r\n * The ID of a div to hold the navigator minimap.\r\n * If an ID is specified, the navigatorPosition, navigatorSizeRatio, navigatorMaintainSizeRatio, navigator[Top|Left|Height|Width] and navigatorAutoFade options will be ignored.\r\n * If an ID is not specified, a div element will be generated and placed on top of the main image.\r\n *\r\n * @property {String} [navigatorPosition='TOP_RIGHT']\r\n * Valid values are 'TOP_LEFT', 'TOP_RIGHT', 'BOTTOM_LEFT', 'BOTTOM_RIGHT', or 'ABSOLUTE'.var viewer = new OpenSeadragon.Viewer(options);
var viewer = OpenSeadragon(options);
rendered
is the context with the pre-drawn image.\r\n * @param {Number} [scale=1] - Apply a scale to position and size\r\n * @param {OpenSeadragon.Point} [translate] - A translation vector\r\n */\r\n drawCanvas: function( context, drawingHandler, scale, translate ) {\r\n\r\n var position = this.position.times($.pixelDensityRatio),\r\n size = this.size.times($.pixelDensityRatio),\r\n rendered;\r\n\r\n if (!this.context2D && !this.cacheImageRecord) {\r\n $.console.warn(\r\n '[Tile.drawCanvas] attempting to draw tile %s when it\\'s not cached',\r\n this.toString());\r\n return;\r\n }\r\n\r\n rendered = this.context2D || this.cacheImageRecord.getRenderedContext();\r\n\r\n if ( !this.loaded || !rendered ){\r\n $.console.warn(\r\n \"Attempting to draw tile %s when it's not yet loaded.\",\r\n this.toString()\r\n );\r\n\r\n return;\r\n }\r\n\r\n context.save();\r\n\r\n context.globalAlpha = this.opacity;\r\n\r\n if (typeof scale === 'number' && scale !== 1) {\r\n // draw tile at a different scale\r\n position = position.times(scale);\r\n size = size.times(scale);\r\n }\r\n\r\n if (translate instanceof $.Point) {\r\n // shift tile position slightly\r\n position = position.plus(translate);\r\n }\r\n\r\n //if we are supposed to be rendering fully opaque rectangle,\r\n //ie its done fading or fading is turned off, and if we are drawing\r\n //an image with an alpha channel, then the only way\r\n //to avoid seeing the tile underneath is to clear the rectangle\r\n if (context.globalAlpha === 1 && this._hasTransparencyChannel()) {\r\n //clearing only the inside of the rectangle occupied\r\n //by the png prevents edge flikering\r\n context.clearRect(\r\n position.x,\r\n position.y,\r\n size.x,\r\n size.y\r\n );\r\n }\r\n\r\n // This gives the application a chance to make image manipulation\r\n // changes as we are rendering the image\r\n drawingHandler({context: context, tile: this, rendered: rendered});\r\n\r\n var sourceWidth, sourceHeight;\r\n if (this.sourceBounds) {\r\n sourceWidth = Math.min(this.sourceBounds.width, rendered.canvas.width);\r\n sourceHeight = Math.min(this.sourceBounds.height, rendered.canvas.height);\r\n } else {\r\n sourceWidth = rendered.canvas.width;\r\n sourceHeight = rendered.canvas.height;\r\n }\r\n\r\n context.drawImage(\r\n rendered.canvas,\r\n 0,\r\n 0,\r\n sourceWidth,\r\n sourceHeight,\r\n position.x,\r\n position.y,\r\n size.x,\r\n size.y\r\n );\r\n\r\n context.restore();\r\n },\r\n\r\n /**\r\n * Get the ratio between current and original size.\r\n * @function\r\n * @return {Float}\r\n */\r\n getScaleForEdgeSmoothing: function() {\r\n var context;\r\n if (this.cacheImageRecord) {\r\n context = this.cacheImageRecord.getRenderedContext();\r\n } else if (this.context2D) {\r\n context = this.context2D;\r\n } else {\r\n $.console.warn(\r\n '[Tile.drawCanvas] attempting to get tile scale %s when tile\\'s not cached',\r\n this.toString());\r\n return 1;\r\n }\r\n return context.canvas.width / (this.size.x * $.pixelDensityRatio);\r\n },\r\n\r\n /**\r\n * Get a translation vector that when applied to the tile position produces integer coordinates.\r\n * Needed to avoid swimming and twitching.\r\n * @function\r\n * @param {Number} [scale=1] - Scale to be applied to position.\r\n * @return {OpenSeadragon.Point}\r\n */\r\n getTranslationForEdgeSmoothing: function(scale, canvasSize, sketchCanvasSize) {\r\n // The translation vector must have positive values, otherwise the image goes a bit off\r\n // the sketch canvas to the top and left and we must use negative coordinates to repaint it\r\n // to the main canvas. In that case, some browsers throw:\r\n // INDEX_SIZE_ERR: DOM Exception 1: Index or size was negative, or greater than the allowed value.\r\n var x = Math.max(1, Math.ceil((sketchCanvasSize.x - canvasSize.x) / 2));\r\n var y = Math.max(1, Math.ceil((sketchCanvasSize.y - canvasSize.y) / 2));\r\n return new $.Point(x, y).minus(\r\n this.position\r\n .times($.pixelDensityRatio)\r\n .times(scale || 1)\r\n .apply(function(x) {\r\n return x % 1;\r\n })\r\n );\r\n },\r\n\r\n /**\r\n * Removes tile from its container.\r\n * @function\r\n */\r\n unload: function() {\r\n if ( this.imgElement && this.imgElement.parentNode ) {\r\n this.imgElement.parentNode.removeChild( this.imgElement );\r\n }\r\n if ( this.element && this.element.parentNode ) {\r\n this.element.parentNode.removeChild( this.element );\r\n }\r\n\r\n this.element = null;\r\n this.imgElement = null;\r\n this.loaded = false;\r\n this.loading = false;\r\n }\r\n};\r\n\r\n}( OpenSeadragon ));\r\n\r\n/*\r\n * OpenSeadragon - Overlay\r\n *\r\n * Copyright (C) 2009 CodePlex Foundation\r\n * Copyright (C) 2010-2013 OpenSeadragon contributors\r\n *\r\n * Redistribution and use in source and binary forms, with or without\r\n * modification, are permitted provided that the following conditions are\r\n * met:\r\n *\r\n * - Redistributions of source code must retain the above copyright notice,\r\n * this list of conditions and the following disclaimer.\r\n *\r\n * - Redistributions in binary form must reproduce the above copyright\r\n * notice, this list of conditions and the following disclaimer in the\r\n * documentation and/or other materials provided with the distribution.\r\n *\r\n * - Neither the name of CodePlex Foundation nor the names of its\r\n * contributors may be used to endorse or promote products derived from\r\n * this software without specific prior written permission.\r\n *\r\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\r\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\r\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\r\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\r\n * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\r\n * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\r\n * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\r\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\r\n * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r\n * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r\n */\r\n\r\n(function($) {\r\n\r\n /**\r\n * An enumeration of positions that an overlay may be assigned relative to\r\n * the viewport.\r\n * It is identical to OpenSeadragon.Placement but is kept for backward\r\n * compatibility.\r\n * @member OverlayPlacement\r\n * @memberof OpenSeadragon\r\n * @see OpenSeadragon.Placement\r\n * @static\r\n * @readonly\r\n * @type {Object}\r\n * @property {Number} CENTER\r\n * @property {Number} TOP_LEFT\r\n * @property {Number} TOP\r\n * @property {Number} TOP_RIGHT\r\n * @property {Number} RIGHT\r\n * @property {Number} BOTTOM_RIGHT\r\n * @property {Number} BOTTOM\r\n * @property {Number} BOTTOM_LEFT\r\n * @property {Number} LEFT\r\n */\r\n $.OverlayPlacement = $.Placement;\r\n\r\n /**\r\n * An enumeration of possible ways to handle overlays rotation\r\n * @member OverlayRotationMode\r\n * @memberOf OpenSeadragon\r\n * @static\r\n * @readonly\r\n * @property {Number} NO_ROTATION The overlay ignore the viewport rotation.\r\n * @property {Number} EXACT The overlay use CSS 3 transforms to rotate with\r\n * the viewport. If the overlay contains text, it will get rotated as well.\r\n * @property {Number} BOUNDING_BOX The overlay adjusts for rotation by\r\n * taking the size of the bounding box of the rotated bounds.\r\n * Only valid for overlays with Rect location and scalable in both directions.\r\n */\r\n $.OverlayRotationMode = $.freezeObject({\r\n NO_ROTATION: 1,\r\n EXACT: 2,\r\n BOUNDING_BOX: 3\r\n });\r\n\r\n /**\r\n * @class Overlay\r\n * @classdesc Provides a way to float an HTML element on top of the viewer element.\r\n *\r\n * @memberof OpenSeadragon\r\n * @param {Object} options\r\n * @param {Element} options.element\r\n * @param {OpenSeadragon.Point|OpenSeadragon.Rect} options.location - The\r\n * location of the overlay on the image. If a {@link OpenSeadragon.Point}\r\n * is specified, the overlay will be located at this location with respect\r\n * to the placement option. If a {@link OpenSeadragon.Rect} is specified,\r\n * the overlay will be placed at this location with the corresponding width\r\n * and height and placement TOP_LEFT.\r\n * @param {OpenSeadragon.Placement} [options.placement=OpenSeadragon.Placement.TOP_LEFT]\r\n * Defines what part of the overlay should be at the specified options.location\r\n * @param {OpenSeadragon.Overlay.OnDrawCallback} [options.onDraw]\r\n * @param {Boolean} [options.checkResize=true] Set to false to avoid to\r\n * check the size of the overlay everytime it is drawn in the directions\r\n * which are not scaled. It will improve performances but will cause a\r\n * misalignment if the overlay size changes.\r\n * @param {Number} [options.width] The width of the overlay in viewport\r\n * coordinates. If specified, the width of the overlay will be adjusted when\r\n * the zoom changes.\r\n * @param {Number} [options.height] The height of the overlay in viewport\r\n * coordinates. If specified, the height of the overlay will be adjusted when\r\n * the zoom changes.\r\n * @param {Boolean} [options.rotationMode=OpenSeadragon.OverlayRotationMode.EXACT]\r\n * How to handle the rotation of the viewport.\r\n */\r\n $.Overlay = function(element, location, placement) {\r\n\r\n /**\r\n * onDraw callback signature used by {@link OpenSeadragon.Overlay}.\r\n *\r\n * @callback OnDrawCallback\r\n * @memberof OpenSeadragon.Overlay\r\n * @param {OpenSeadragon.Point} position\r\n * @param {OpenSeadragon.Point} size\r\n * @param {Element} element\r\n */\r\n\r\n var options;\r\n if ($.isPlainObject(element)) {\r\n options = element;\r\n } else {\r\n options = {\r\n element: element,\r\n location: location,\r\n placement: placement\r\n };\r\n }\r\n\r\n this.element = options.element;\r\n this.style = options.element.style;\r\n this._init(options);\r\n };\r\n\r\n /** @lends OpenSeadragon.Overlay.prototype */\r\n $.Overlay.prototype = {\r\n\r\n // private\r\n _init: function(options) {\r\n this.location = options.location;\r\n this.placement = options.placement === undefined ?\r\n $.Placement.TOP_LEFT : options.placement;\r\n this.onDraw = options.onDraw;\r\n this.checkResize = options.checkResize === undefined ?\r\n true : options.checkResize;\r\n\r\n // When this.width is not null, the overlay get scaled horizontally\r\n this.width = options.width === undefined ? null : options.width;\r\n\r\n // When this.height is not null, the overlay get scaled vertically\r\n this.height = options.height === undefined ? null : options.height;\r\n\r\n this.rotationMode = options.rotationMode || $.OverlayRotationMode.EXACT;\r\n\r\n // Having a rect as location is a syntactic sugar\r\n if (this.location instanceof $.Rect) {\r\n this.width = this.location.width;\r\n this.height = this.location.height;\r\n this.location = this.location.getTopLeft();\r\n this.placement = $.Placement.TOP_LEFT;\r\n }\r\n\r\n // Deprecated properties kept for backward compatibility.\r\n this.scales = this.width !== null && this.height !== null;\r\n this.bounds = new $.Rect(\r\n this.location.x, this.location.y, this.width, this.height);\r\n this.position = this.location;\r\n },\r\n\r\n /**\r\n * Internal function to adjust the position of an overlay\r\n * depending on it size and placement.\r\n * @function\r\n * @param {OpenSeadragon.Point} position\r\n * @param {OpenSeadragon.Point} size\r\n */\r\n adjust: function(position, size) {\r\n var properties = $.Placement.properties[this.placement];\r\n if (!properties) {\r\n return;\r\n }\r\n if (properties.isHorizontallyCentered) {\r\n position.x -= size.x / 2;\r\n } else if (properties.isRight) {\r\n position.x -= size.x;\r\n }\r\n if (properties.isVerticallyCentered) {\r\n position.y -= size.y / 2;\r\n } else if (properties.isBottom) {\r\n position.y -= size.y;\r\n }\r\n },\r\n\r\n /**\r\n * @function\r\n */\r\n destroy: function() {\r\n var element = this.element;\r\n var style = this.style;\r\n\r\n if (element.parentNode) {\r\n element.parentNode.removeChild(element);\r\n //this should allow us to preserve overlays when required between\r\n //pages\r\n if (element.prevElementParent) {\r\n style.display = 'none';\r\n //element.prevElementParent.insertBefore(\r\n // element,\r\n // element.prevNextSibling\r\n //);\r\n document.body.appendChild(element);\r\n }\r\n }\r\n\r\n // clear the onDraw callback\r\n this.onDraw = null;\r\n\r\n style.top = \"\";\r\n style.left = \"\";\r\n style.position = \"\";\r\n\r\n if (this.width !== null) {\r\n style.width = \"\";\r\n }\r\n if (this.height !== null) {\r\n style.height = \"\";\r\n }\r\n var transformOriginProp = $.getCssPropertyWithVendorPrefix(\r\n 'transformOrigin');\r\n var transformProp = $.getCssPropertyWithVendorPrefix(\r\n 'transform');\r\n if (transformOriginProp && transformProp) {\r\n style[transformOriginProp] = \"\";\r\n style[transformProp] = \"\";\r\n }\r\n },\r\n\r\n /**\r\n * @function\r\n * @param {Element} container\r\n */\r\n drawHTML: function(container, viewport) {\r\n var element = this.element;\r\n if (element.parentNode !== container) {\r\n //save the source parent for later if we need it\r\n element.prevElementParent = element.parentNode;\r\n element.prevNextSibling = element.nextSibling;\r\n container.appendChild(element);\r\n\r\n // have to set position before calculating size, fix #1116\r\n this.style.position = \"absolute\";\r\n // this.size is used by overlays which don't get scaled in at\r\n // least one direction when this.checkResize is set to false.\r\n this.size = $.getElementSize(element);\r\n }\r\n\r\n var positionAndSize = this._getOverlayPositionAndSize(viewport);\r\n\r\n var position = positionAndSize.position;\r\n var size = this.size = positionAndSize.size;\r\n var rotate = positionAndSize.rotate;\r\n\r\n // call the onDraw callback if it exists to allow one to overwrite\r\n // the drawing/positioning/sizing of the overlay\r\n if (this.onDraw) {\r\n this.onDraw(position, size, this.element);\r\n } else {\r\n var style = this.style;\r\n style.left = position.x + \"px\";\r\n style.top = position.y + \"px\";\r\n if (this.width !== null) {\r\n style.width = size.x + \"px\";\r\n }\r\n if (this.height !== null) {\r\n style.height = size.y + \"px\";\r\n }\r\n var transformOriginProp = $.getCssPropertyWithVendorPrefix(\r\n 'transformOrigin');\r\n var transformProp = $.getCssPropertyWithVendorPrefix(\r\n 'transform');\r\n if (transformOriginProp && transformProp) {\r\n if (rotate) {\r\n style[transformOriginProp] = this._getTransformOrigin();\r\n style[transformProp] = \"rotate(\" + rotate + \"deg)\";\r\n } else {\r\n style[transformOriginProp] = \"\";\r\n style[transformProp] = \"\";\r\n }\r\n }\r\n\r\n if (style.display !== 'none') {\r\n style.display = 'block';\r\n }\r\n }\r\n },\r\n\r\n // private\r\n _getOverlayPositionAndSize: function(viewport) {\r\n var position = viewport.pixelFromPoint(this.location, true);\r\n var size = this._getSizeInPixels(viewport);\r\n this.adjust(position, size);\r\n\r\n var rotate = 0;\r\n if (viewport.degrees &&\r\n this.rotationMode !== $.OverlayRotationMode.NO_ROTATION) {\r\n // BOUNDING_BOX is only valid if both directions get scaled.\r\n // Get replaced by EXACT otherwise.\r\n if (this.rotationMode === $.OverlayRotationMode.BOUNDING_BOX &&\r\n this.width !== null && this.height !== null) {\r\n var rect = new $.Rect(position.x, position.y, size.x, size.y);\r\n var boundingBox = this._getBoundingBox(rect, viewport.degrees);\r\n position = boundingBox.getTopLeft();\r\n size = boundingBox.getSize();\r\n } else {\r\n rotate = viewport.degrees;\r\n }\r\n }\r\n\r\n return {\r\n position: position,\r\n size: size,\r\n rotate: rotate\r\n };\r\n },\r\n\r\n // private\r\n _getSizeInPixels: function(viewport) {\r\n var width = this.size.x;\r\n var height = this.size.y;\r\n if (this.width !== null || this.height !== null) {\r\n var scaledSize = viewport.deltaPixelsFromPointsNoRotate(\r\n new $.Point(this.width || 0, this.height || 0), true);\r\n if (this.width !== null) {\r\n width = scaledSize.x;\r\n }\r\n if (this.height !== null) {\r\n height = scaledSize.y;\r\n }\r\n }\r\n if (this.checkResize &&\r\n (this.width === null || this.height === null)) {\r\n var eltSize = this.size = $.getElementSize(this.element);\r\n if (this.width === null) {\r\n width = eltSize.x;\r\n }\r\n if (this.height === null) {\r\n height = eltSize.y;\r\n }\r\n }\r\n return new $.Point(width, height);\r\n },\r\n\r\n // private\r\n _getBoundingBox: function(rect, degrees) {\r\n var refPoint = this._getPlacementPoint(rect);\r\n return rect.rotate(degrees, refPoint).getBoundingBox();\r\n },\r\n\r\n // private\r\n _getPlacementPoint: function(rect) {\r\n var result = new $.Point(rect.x, rect.y);\r\n var properties = $.Placement.properties[this.placement];\r\n if (properties) {\r\n if (properties.isHorizontallyCentered) {\r\n result.x += rect.width / 2;\r\n } else if (properties.isRight) {\r\n result.x += rect.width;\r\n }\r\n if (properties.isVerticallyCentered) {\r\n result.y += rect.height / 2;\r\n } else if (properties.isBottom) {\r\n result.y += rect.height;\r\n }\r\n }\r\n return result;\r\n },\r\n\r\n // private\r\n _getTransformOrigin: function() {\r\n var result = \"\";\r\n var properties = $.Placement.properties[this.placement];\r\n if (!properties) {\r\n return result;\r\n }\r\n if (properties.isLeft) {\r\n result = \"left\";\r\n } else if (properties.isRight) {\r\n result = \"right\";\r\n }\r\n if (properties.isTop) {\r\n result += \" top\";\r\n } else if (properties.isBottom) {\r\n result += \" bottom\";\r\n }\r\n return result;\r\n },\r\n\r\n /**\r\n * Changes the overlay settings.\r\n * @function\r\n * @param {OpenSeadragon.Point|OpenSeadragon.Rect|Object} location\r\n * If an object is specified, the options are the same than the constructor\r\n * except for the element which can not be changed.\r\n * @param {OpenSeadragon.Placement} placement\r\n */\r\n update: function(location, placement) {\r\n var options = $.isPlainObject(location) ? location : {\r\n location: location,\r\n placement: placement\r\n };\r\n this._init({\r\n location: options.location || this.location,\r\n placement: options.placement !== undefined ?\r\n options.placement : this.placement,\r\n onDraw: options.onDraw || this.onDraw,\r\n checkResize: options.checkResize || this.checkResize,\r\n width: options.width !== undefined ? options.width : this.width,\r\n height: options.height !== undefined ? options.height : this.height,\r\n rotationMode: options.rotationMode || this.rotationMode\r\n });\r\n },\r\n\r\n /**\r\n * Returns the current bounds of the overlay in viewport coordinates\r\n * @function\r\n * @param {OpenSeadragon.Viewport} viewport the viewport\r\n * @returns {OpenSeadragon.Rect} overlay bounds\r\n */\r\n getBounds: function(viewport) {\r\n $.console.assert(viewport,\r\n 'A viewport must now be passed to Overlay.getBounds.');\r\n var width = this.width;\r\n var height = this.height;\r\n if (width === null || height === null) {\r\n var size = viewport.deltaPointsFromPixelsNoRotate(this.size, true);\r\n if (width === null) {\r\n width = size.x;\r\n }\r\n if (height === null) {\r\n height = size.y;\r\n }\r\n }\r\n var location = this.location.clone();\r\n this.adjust(location, new $.Point(width, height));\r\n return this._adjustBoundsForRotation(\r\n viewport, new $.Rect(location.x, location.y, width, height));\r\n },\r\n\r\n // private\r\n _adjustBoundsForRotation: function(viewport, bounds) {\r\n if (!viewport ||\r\n viewport.degrees === 0 ||\r\n this.rotationMode === $.OverlayRotationMode.EXACT) {\r\n return bounds;\r\n }\r\n if (this.rotationMode === $.OverlayRotationMode.BOUNDING_BOX) {\r\n // If overlay not fully scalable, BOUNDING_BOX falls back to EXACT\r\n if (this.width === null || this.height === null) {\r\n return bounds;\r\n }\r\n // It is easier to just compute the position and size and\r\n // convert to viewport coordinates.\r\n var positionAndSize = this._getOverlayPositionAndSize(viewport);\r\n return viewport.viewerElementToViewportRectangle(new $.Rect(\r\n positionAndSize.position.x,\r\n positionAndSize.position.y,\r\n positionAndSize.size.x,\r\n positionAndSize.size.y));\r\n }\r\n\r\n // NO_ROTATION case\r\n return bounds.rotate(-viewport.degrees,\r\n this._getPlacementPoint(bounds));\r\n }\r\n };\r\n\r\n}(OpenSeadragon));\r\n\r\n/*\r\n * OpenSeadragon - Drawer\r\n *\r\n * Copyright (C) 2009 CodePlex Foundation\r\n * Copyright (C) 2010-2013 OpenSeadragon contributors\r\n *\r\n * Redistribution and use in source and binary forms, with or without\r\n * modification, are permitted provided that the following conditions are\r\n * met:\r\n *\r\n * - Redistributions of source code must retain the above copyright notice,\r\n * this list of conditions and the following disclaimer.\r\n *\r\n * - Redistributions in binary form must reproduce the above copyright\r\n * notice, this list of conditions and the following disclaimer in the\r\n * documentation and/or other materials provided with the distribution.\r\n *\r\n * - Neither the name of CodePlex Foundation nor the names of its\r\n * contributors may be used to endorse or promote products derived from\r\n * this software without specific prior written permission.\r\n *\r\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\r\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\r\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\r\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\r\n * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\r\n * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\r\n * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\r\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\r\n * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r\n * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r\n */\r\n\r\n(function( $ ){\r\n\r\n/**\r\n * @class Drawer\r\n * @memberof OpenSeadragon\r\n * @classdesc Handles rendering of tiles for an {@link OpenSeadragon.Viewer}.\r\n * @param {Object} options - Options for this Drawer.\r\n * @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this Drawer.\r\n * @param {OpenSeadragon.Viewport} options.viewport - Reference to Viewer viewport.\r\n * @param {Element} options.element - Parent element.\r\n * @param {Number} [options.debugGridColor] - See debugGridColor in {@link OpenSeadragon.Options} for details.\r\n */\r\n$.Drawer = function( options ) {\r\n\r\n $.console.assert( options.viewer, \"[Drawer] options.viewer is required\" );\r\n\r\n //backward compatibility for positional args while prefering more\r\n //idiomatic javascript options object as the only argument\r\n var args = arguments;\r\n\r\n if( !$.isPlainObject( options ) ){\r\n options = {\r\n source: args[ 0 ], // Reference to Viewer tile source.\r\n viewport: args[ 1 ], // Reference to Viewer viewport.\r\n element: args[ 2 ] // Parent element.\r\n };\r\n }\r\n\r\n $.console.assert( options.viewport, \"[Drawer] options.viewport is required\" );\r\n $.console.assert( options.element, \"[Drawer] options.element is required\" );\r\n\r\n if ( options.source ) {\r\n $.console.error( \"[Drawer] options.source is no longer accepted; use TiledImage instead\" );\r\n }\r\n\r\n this.viewer = options.viewer;\r\n this.viewport = options.viewport;\r\n this.debugGridColor = typeof options.debugGridColor === 'string' ? [options.debugGridColor] : options.debugGridColor || $.DEFAULT_SETTINGS.debugGridColor;\r\n if (options.opacity) {\r\n $.console.error( \"[Drawer] options.opacity is no longer accepted; set the opacity on the TiledImage instead\" );\r\n }\r\n\r\n this.useCanvas = $.supportsCanvas && ( this.viewer ? this.viewer.useCanvas : true );\r\n /**\r\n * The parent element of this Drawer instance, passed in when the Drawer was created.\r\n * The parent of {@link OpenSeadragon.Drawer#canvas}.\r\n * @member {Element} container\r\n * @memberof OpenSeadragon.Drawer#\r\n */\r\n this.container = $.getElement( options.element );\r\n /**\r\n * A <canvas> element if the browser supports them, otherwise a <div> element.\r\n * Child element of {@link OpenSeadragon.Drawer#container}.\r\n * @member {Element} canvas\r\n * @memberof OpenSeadragon.Drawer#\r\n */\r\n this.canvas = $.makeNeutralElement( this.useCanvas ? \"canvas\" : \"div\" );\r\n /**\r\n * 2d drawing context for {@link OpenSeadragon.Drawer#canvas} if it's a <canvas> element, otherwise null.\r\n * @member {Object} context\r\n * @memberof OpenSeadragon.Drawer#\r\n */\r\n this.context = this.useCanvas ? this.canvas.getContext( \"2d\" ) : null;\r\n\r\n /**\r\n * Sketch canvas used to temporarily draw tiles which cannot be drawn directly\r\n * to the main canvas due to opacity. Lazily initialized.\r\n */\r\n this.sketchCanvas = null;\r\n this.sketchContext = null;\r\n\r\n /**\r\n * @member {Element} element\r\n * @memberof OpenSeadragon.Drawer#\r\n * @deprecated Alias for {@link OpenSeadragon.Drawer#container}.\r\n */\r\n this.element = this.container;\r\n\r\n // We force our container to ltr because our drawing math doesn't work in rtl.\r\n // This issue only affects our canvas renderer, but we do it always for consistency.\r\n // Note that this means overlays you want to be rtl need to be explicitly set to rtl.\r\n this.container.dir = 'ltr';\r\n\r\n // check canvas available width and height, set canvas width and height such that the canvas backing store is set to the proper pixel density\r\n if (this.useCanvas) {\r\n var viewportSize = this._calculateCanvasSize();\r\n this.canvas.width = viewportSize.x;\r\n this.canvas.height = viewportSize.y;\r\n }\r\n\r\n this.canvas.style.width = \"100%\";\r\n this.canvas.style.height = \"100%\";\r\n this.canvas.style.position = \"absolute\";\r\n $.setElementOpacity( this.canvas, this.opacity, true );\r\n\r\n // explicit left-align\r\n this.container.style.textAlign = \"left\";\r\n this.container.appendChild( this.canvas );\r\n};\r\n\r\n/** @lends OpenSeadragon.Drawer.prototype */\r\n$.Drawer.prototype = {\r\n // deprecated\r\n addOverlay: function( element, location, placement, onDraw ) {\r\n $.console.error(\"drawer.addOverlay is deprecated. Use viewer.addOverlay instead.\");\r\n this.viewer.addOverlay( element, location, placement, onDraw );\r\n return this;\r\n },\r\n\r\n // deprecated\r\n updateOverlay: function( element, location, placement ) {\r\n $.console.error(\"drawer.updateOverlay is deprecated. Use viewer.updateOverlay instead.\");\r\n this.viewer.updateOverlay( element, location, placement );\r\n return this;\r\n },\r\n\r\n // deprecated\r\n removeOverlay: function( element ) {\r\n $.console.error(\"drawer.removeOverlay is deprecated. Use viewer.removeOverlay instead.\");\r\n this.viewer.removeOverlay( element );\r\n return this;\r\n },\r\n\r\n // deprecated\r\n clearOverlays: function() {\r\n $.console.error(\"drawer.clearOverlays is deprecated. Use viewer.clearOverlays instead.\");\r\n this.viewer.clearOverlays();\r\n return this;\r\n },\r\n\r\n /**\r\n * Set the opacity of the drawer.\r\n * @param {Number} opacity\r\n * @return {OpenSeadragon.Drawer} Chainable.\r\n */\r\n setOpacity: function( opacity ) {\r\n $.console.error(\"drawer.setOpacity is deprecated. Use tiledImage.setOpacity instead.\");\r\n var world = this.viewer.world;\r\n for (var i = 0; i < world.getItemCount(); i++) {\r\n world.getItemAt( i ).setOpacity( opacity );\r\n }\r\n return this;\r\n },\r\n\r\n /**\r\n * Get the opacity of the drawer.\r\n * @returns {Number}\r\n */\r\n getOpacity: function() {\r\n $.console.error(\"drawer.getOpacity is deprecated. Use tiledImage.getOpacity instead.\");\r\n var world = this.viewer.world;\r\n var maxOpacity = 0;\r\n for (var i = 0; i < world.getItemCount(); i++) {\r\n var opacity = world.getItemAt( i ).getOpacity();\r\n if ( opacity > maxOpacity ) {\r\n maxOpacity = opacity;\r\n }\r\n }\r\n return maxOpacity;\r\n },\r\n\r\n // deprecated\r\n needsUpdate: function() {\r\n $.console.error( \"[Drawer.needsUpdate] this function is deprecated. Use World.needsDraw instead.\" );\r\n return this.viewer.world.needsDraw();\r\n },\r\n\r\n // deprecated\r\n numTilesLoaded: function() {\r\n $.console.error( \"[Drawer.numTilesLoaded] this function is deprecated. Use TileCache.numTilesLoaded instead.\" );\r\n return this.viewer.tileCache.numTilesLoaded();\r\n },\r\n\r\n // deprecated\r\n reset: function() {\r\n $.console.error( \"[Drawer.reset] this function is deprecated. Use World.resetItems instead.\" );\r\n this.viewer.world.resetItems();\r\n return this;\r\n },\r\n\r\n // deprecated\r\n update: function() {\r\n $.console.error( \"[Drawer.update] this function is deprecated. Use Drawer.clear and World.draw instead.\" );\r\n this.clear();\r\n this.viewer.world.draw();\r\n return this;\r\n },\r\n\r\n /**\r\n * @return {Boolean} True if rotation is supported.\r\n */\r\n canRotate: function() {\r\n return this.useCanvas;\r\n },\r\n\r\n /**\r\n * Destroy the drawer (unload current loaded tiles)\r\n */\r\n destroy: function() {\r\n //force unloading of current canvas (1x1 will be gc later, trick not necessarily needed)\r\n this.canvas.width = 1;\r\n this.canvas.height = 1;\r\n this.sketchCanvas = null;\r\n this.sketchContext = null;\r\n },\r\n\r\n /**\r\n * Clears the Drawer so it's ready to draw another frame.\r\n */\r\n clear: function() {\r\n this.canvas.innerHTML = \"\";\r\n if ( this.useCanvas ) {\r\n var viewportSize = this._calculateCanvasSize();\r\n if( this.canvas.width != viewportSize.x ||\r\n this.canvas.height != viewportSize.y ) {\r\n this.canvas.width = viewportSize.x;\r\n this.canvas.height = viewportSize.y;\r\n if ( this.sketchCanvas !== null ) {\r\n var sketchCanvasSize = this._calculateSketchCanvasSize();\r\n this.sketchCanvas.width = sketchCanvasSize.x;\r\n this.sketchCanvas.height = sketchCanvasSize.y;\r\n }\r\n }\r\n this._clear();\r\n }\r\n },\r\n\r\n _clear: function (useSketch, bounds) {\r\n if (!this.useCanvas) {\r\n return;\r\n }\r\n var context = this._getContext(useSketch);\r\n if (bounds) {\r\n context.clearRect(bounds.x, bounds.y, bounds.width, bounds.height);\r\n } else {\r\n var canvas = context.canvas;\r\n context.clearRect(0, 0, canvas.width, canvas.height);\r\n }\r\n },\r\n\r\n /**\r\n * Scale from OpenSeadragon viewer rectangle to drawer rectangle\r\n * (ignoring rotation)\r\n * @param {OpenSeadragon.Rect} rectangle - The rectangle in viewport coordinate system.\r\n * @return {OpenSeadragon.Rect} Rectangle in drawer coordinate system.\r\n */\r\n viewportToDrawerRectangle: function(rectangle) {\r\n var topLeft = this.viewport.pixelFromPointNoRotate(rectangle.getTopLeft(), true);\r\n var size = this.viewport.deltaPixelsFromPointsNoRotate(rectangle.getSize(), true);\r\n\r\n return new $.Rect(\r\n topLeft.x * $.pixelDensityRatio,\r\n topLeft.y * $.pixelDensityRatio,\r\n size.x * $.pixelDensityRatio,\r\n size.y * $.pixelDensityRatio\r\n );\r\n },\r\n\r\n /**\r\n * Draws the given tile.\r\n * @param {OpenSeadragon.Tile} tile - The tile to draw.\r\n * @param {Function} drawingHandler - Method for firing the drawing event if using canvas.\r\n * drawingHandler({context, tile, rendered})\r\n * @param {Boolean} useSketch - Whether to use the sketch canvas or not.\r\n * where rendered
is the context with the pre-drawn image.\r\n * @param {Float} [scale=1] - Apply a scale to tile position and size. Defaults to 1.\r\n * @param {OpenSeadragon.Point} [translate] A translation vector to offset tile position\r\n */\r\n drawTile: function(tile, drawingHandler, useSketch, scale, translate) {\r\n $.console.assert(tile, '[Drawer.drawTile] tile is required');\r\n $.console.assert(drawingHandler, '[Drawer.drawTile] drawingHandler is required');\r\n\r\n if (this.useCanvas) {\r\n var context = this._getContext(useSketch);\r\n scale = scale || 1;\r\n tile.drawCanvas(context, drawingHandler, scale, translate);\r\n } else {\r\n tile.drawHTML( this.canvas );\r\n }\r\n },\r\n\r\n _getContext: function( useSketch ) {\r\n var context = this.context;\r\n if ( useSketch ) {\r\n if (this.sketchCanvas === null) {\r\n this.sketchCanvas = document.createElement( \"canvas\" );\r\n var sketchCanvasSize = this._calculateSketchCanvasSize();\r\n this.sketchCanvas.width = sketchCanvasSize.x;\r\n this.sketchCanvas.height = sketchCanvasSize.y;\r\n this.sketchContext = this.sketchCanvas.getContext( \"2d\" );\r\n\r\n // If the viewport is not currently rotated, the sketchCanvas\r\n // will have the same size as the main canvas. However, if\r\n // the viewport get rotated later on, we will need to resize it.\r\n if (this.viewport.getRotation() === 0) {\r\n var self = this;\r\n this.viewer.addHandler('rotate', function resizeSketchCanvas() {\r\n if (self.viewport.getRotation() === 0) {\r\n return;\r\n }\r\n self.viewer.removeHandler('rotate', resizeSketchCanvas);\r\n var sketchCanvasSize = self._calculateSketchCanvasSize();\r\n self.sketchCanvas.width = sketchCanvasSize.x;\r\n self.sketchCanvas.height = sketchCanvasSize.y;\r\n });\r\n }\r\n }\r\n context = this.sketchContext;\r\n }\r\n return context;\r\n },\r\n\r\n // private\r\n saveContext: function( useSketch ) {\r\n if (!this.useCanvas) {\r\n return;\r\n }\r\n\r\n this._getContext( useSketch ).save();\r\n },\r\n\r\n // private\r\n restoreContext: function( useSketch ) {\r\n if (!this.useCanvas) {\r\n return;\r\n }\r\n\r\n this._getContext( useSketch ).restore();\r\n },\r\n\r\n // private\r\n setClip: function(rect, useSketch) {\r\n if (!this.useCanvas) {\r\n return;\r\n }\r\n\r\n var context = this._getContext( useSketch );\r\n context.beginPath();\r\n context.rect(rect.x, rect.y, rect.width, rect.height);\r\n context.clip();\r\n },\r\n\r\n // private\r\n drawRectangle: function(rect, fillStyle, useSketch) {\r\n if (!this.useCanvas) {\r\n return;\r\n }\r\n\r\n var context = this._getContext( useSketch );\r\n context.save();\r\n context.fillStyle = fillStyle;\r\n context.fillRect(rect.x, rect.y, rect.width, rect.height);\r\n context.restore();\r\n },\r\n\r\n /**\r\n * Blends the sketch canvas in the main canvas.\r\n * @param {Object} options The options\r\n * @param {Float} options.opacity The opacity of the blending.\r\n * @param {Float} [options.scale=1] The scale at which tiles were drawn on\r\n * the sketch. Default is 1.\r\n * Use scale to draw at a lower scale and then enlarge onto the main canvas.\r\n * @param {OpenSeadragon.Point} [options.translate] A translation vector\r\n * that was used to draw the tiles\r\n * @param {String} [options.compositeOperation] - How the image is\r\n * composited onto other images; see compositeOperation in\r\n * {@link OpenSeadragon.Options} for possible values.\r\n * @param {OpenSeadragon.Rect} [options.bounds] The part of the sketch\r\n * canvas to blend in the main canvas. If specified, options.scale and\r\n * options.translate get ignored.\r\n */\r\n blendSketch: function(opacity, scale, translate, compositeOperation) {\r\n var options = opacity;\r\n if (!$.isPlainObject(options)) {\r\n options = {\r\n opacity: opacity,\r\n scale: scale,\r\n translate: translate,\r\n compositeOperation: compositeOperation\r\n };\r\n }\r\n if (!this.useCanvas || !this.sketchCanvas) {\r\n return;\r\n }\r\n opacity = options.opacity;\r\n compositeOperation = options.compositeOperation;\r\n var bounds = options.bounds;\r\n\r\n this.context.save();\r\n this.context.globalAlpha = opacity;\r\n if (compositeOperation) {\r\n this.context.globalCompositeOperation = compositeOperation;\r\n }\r\n if (bounds) {\r\n // Internet Explorer, Microsoft Edge, and Safari have problems\r\n // when you call context.drawImage with negative x or y\r\n // or x + width or y + height greater than the canvas width or height respectively.\r\n if (bounds.x < 0) {\r\n bounds.width += bounds.x;\r\n bounds.x = 0;\r\n }\r\n if (bounds.x + bounds.width > this.canvas.width) {\r\n bounds.width = this.canvas.width - bounds.x;\r\n }\r\n if (bounds.y < 0) {\r\n bounds.height += bounds.y;\r\n bounds.y = 0;\r\n }\r\n if (bounds.y + bounds.height > this.canvas.height) {\r\n bounds.height = this.canvas.height - bounds.y;\r\n }\r\n\r\n this.context.drawImage(\r\n this.sketchCanvas,\r\n bounds.x,\r\n bounds.y,\r\n bounds.width,\r\n bounds.height,\r\n bounds.x,\r\n bounds.y,\r\n bounds.width,\r\n bounds.height\r\n );\r\n } else {\r\n scale = options.scale || 1;\r\n translate = options.translate;\r\n var position = translate instanceof $.Point ?\r\n translate : new $.Point(0, 0);\r\n\r\n var widthExt = 0;\r\n var heightExt = 0;\r\n if (translate) {\r\n var widthDiff = this.sketchCanvas.width - this.canvas.width;\r\n var heightDiff = this.sketchCanvas.height - this.canvas.height;\r\n widthExt = Math.round(widthDiff / 2);\r\n heightExt = Math.round(heightDiff / 2);\r\n }\r\n this.context.drawImage(\r\n this.sketchCanvas,\r\n position.x - widthExt * scale,\r\n position.y - heightExt * scale,\r\n (this.canvas.width + 2 * widthExt) * scale,\r\n (this.canvas.height + 2 * heightExt) * scale,\r\n -widthExt,\r\n -heightExt,\r\n this.canvas.width + 2 * widthExt,\r\n this.canvas.height + 2 * heightExt\r\n );\r\n }\r\n this.context.restore();\r\n },\r\n\r\n // private\r\n drawDebugInfo: function(tile, count, i, tiledImage) {\r\n if ( !this.useCanvas ) {\r\n return;\r\n }\r\n\r\n var colorIndex = this.viewer.world.getIndexOfItem(tiledImage) % this.debugGridColor.length;\r\n var context = this.context;\r\n context.save();\r\n context.lineWidth = 2 * $.pixelDensityRatio;\r\n context.font = 'small-caps bold ' + (13 * $.pixelDensityRatio) + 'px arial';\r\n context.strokeStyle = this.debugGridColor[colorIndex];\r\n context.fillStyle = this.debugGridColor[colorIndex];\r\n\r\n if ( this.viewport.degrees !== 0 ) {\r\n this._offsetForRotation({degrees: this.viewport.degrees});\r\n } else{\r\n if(this.viewer.viewport.flipped) {\r\n this._flip();\r\n }\r\n }\r\n if (tiledImage.getRotation(true) % 360 !== 0) {\r\n this._offsetForRotation({\r\n degrees: tiledImage.getRotation(true),\r\n point: tiledImage.viewport.pixelFromPointNoRotate(\r\n tiledImage._getRotationPoint(true), true)\r\n });\r\n }\r\n\r\n context.strokeRect(\r\n tile.position.x * $.pixelDensityRatio,\r\n tile.position.y * $.pixelDensityRatio,\r\n tile.size.x * $.pixelDensityRatio,\r\n tile.size.y * $.pixelDensityRatio\r\n );\r\n\r\n var tileCenterX = (tile.position.x + (tile.size.x / 2)) * $.pixelDensityRatio;\r\n var tileCenterY = (tile.position.y + (tile.size.y / 2)) * $.pixelDensityRatio;\r\n\r\n // Rotate the text the right way around.\r\n context.translate( tileCenterX, tileCenterY );\r\n context.rotate( Math.PI / 180 * -this.viewport.degrees );\r\n context.translate( -tileCenterX, -tileCenterY );\r\n\r\n if( tile.x === 0 && tile.y === 0 ){\r\n context.fillText(\r\n \"Zoom: \" + this.viewport.getZoom(),\r\n tile.position.x * $.pixelDensityRatio,\r\n (tile.position.y - 30) * $.pixelDensityRatio\r\n );\r\n context.fillText(\r\n \"Pan: \" + this.viewport.getBounds().toString(),\r\n tile.position.x * $.pixelDensityRatio,\r\n (tile.position.y - 20) * $.pixelDensityRatio\r\n );\r\n }\r\n context.fillText(\r\n \"Level: \" + tile.level,\r\n (tile.position.x + 10) * $.pixelDensityRatio,\r\n (tile.position.y + 20) * $.pixelDensityRatio\r\n );\r\n context.fillText(\r\n \"Column: \" + tile.x,\r\n (tile.position.x + 10) * $.pixelDensityRatio,\r\n (tile.position.y + 30) * $.pixelDensityRatio\r\n );\r\n context.fillText(\r\n \"Row: \" + tile.y,\r\n (tile.position.x + 10) * $.pixelDensityRatio,\r\n (tile.position.y + 40) * $.pixelDensityRatio\r\n );\r\n context.fillText(\r\n \"Order: \" + i + \" of \" + count,\r\n (tile.position.x + 10) * $.pixelDensityRatio,\r\n (tile.position.y + 50) * $.pixelDensityRatio\r\n );\r\n context.fillText(\r\n \"Size: \" + tile.size.toString(),\r\n (tile.position.x + 10) * $.pixelDensityRatio,\r\n (tile.position.y + 60) * $.pixelDensityRatio\r\n );\r\n context.fillText(\r\n \"Position: \" + tile.position.toString(),\r\n (tile.position.x + 10) * $.pixelDensityRatio,\r\n (tile.position.y + 70) * $.pixelDensityRatio\r\n );\r\n\r\n if ( this.viewport.degrees !== 0 ) {\r\n this._restoreRotationChanges();\r\n }\r\n if (tiledImage.getRotation(true) % 360 !== 0) {\r\n this._restoreRotationChanges();\r\n }\r\n context.restore();\r\n },\r\n\r\n // private\r\n debugRect: function(rect) {\r\n if ( this.useCanvas ) {\r\n var context = this.context;\r\n context.save();\r\n context.lineWidth = 2 * $.pixelDensityRatio;\r\n context.strokeStyle = this.debugGridColor[0];\r\n context.fillStyle = this.debugGridColor[0];\r\n\r\n context.strokeRect(\r\n rect.x * $.pixelDensityRatio,\r\n rect.y * $.pixelDensityRatio,\r\n rect.width * $.pixelDensityRatio,\r\n rect.height * $.pixelDensityRatio\r\n );\r\n\r\n context.restore();\r\n }\r\n },\r\n\r\n /**\r\n * Get the canvas size\r\n * @param {Boolean} sketch If set to true return the size of the sketch canvas\r\n * @returns {OpenSeadragon.Point} The size of the canvas\r\n */\r\n getCanvasSize: function(sketch) {\r\n var canvas = this._getContext(sketch).canvas;\r\n return new $.Point(canvas.width, canvas.height);\r\n },\r\n\r\n getCanvasCenter: function() {\r\n return new $.Point(this.canvas.width / 2, this.canvas.height / 2);\r\n },\r\n\r\n // private\r\n _offsetForRotation: function(options) {\r\n var point = options.point ?\r\n options.point.times($.pixelDensityRatio) :\r\n this.getCanvasCenter();\r\n\r\n var context = this._getContext(options.useSketch);\r\n context.save();\r\n\r\n context.translate(point.x, point.y);\r\n if(this.viewer.viewport.flipped){\r\n context.rotate(Math.PI / 180 * -options.degrees);\r\n context.scale(-1, 1);\r\n } else{\r\n context.rotate(Math.PI / 180 * options.degrees);\r\n }\r\n context.translate(-point.x, -point.y);\r\n },\r\n\r\n // private\r\n _flip: function(options) {\r\n options = options || {};\r\n var point = options.point ?\r\n options.point.times($.pixelDensityRatio) :\r\n this.getCanvasCenter();\r\n var context = this._getContext(options.useSketch);\r\n\r\n context.translate(point.x, 0);\r\n context.scale(-1, 1);\r\n context.translate(-point.x, 0);\r\n },\r\n\r\n // private\r\n _restoreRotationChanges: function(useSketch) {\r\n var context = this._getContext(useSketch);\r\n context.restore();\r\n },\r\n\r\n // private\r\n _calculateCanvasSize: function() {\r\n var pixelDensityRatio = $.pixelDensityRatio;\r\n var viewportSize = this.viewport.getContainerSize();\r\n return {\r\n x: viewportSize.x * pixelDensityRatio,\r\n y: viewportSize.y * pixelDensityRatio\r\n };\r\n },\r\n\r\n // private\r\n _calculateSketchCanvasSize: function() {\r\n var canvasSize = this._calculateCanvasSize();\r\n if (this.viewport.getRotation() === 0) {\r\n return canvasSize;\r\n }\r\n // If the viewport is rotated, we need a larger sketch canvas in order\r\n // to support edge smoothing.\r\n var sketchCanvasSize = Math.ceil(Math.sqrt(\r\n canvasSize.x * canvasSize.x +\r\n canvasSize.y * canvasSize.y));\r\n return {\r\n x: sketchCanvasSize,\r\n y: sketchCanvasSize\r\n };\r\n }\r\n};\r\n\r\n}( OpenSeadragon ));\r\n\r\n/*\r\n * OpenSeadragon - Viewport\r\n *\r\n * Copyright (C) 2009 CodePlex Foundation\r\n * Copyright (C) 2010-2013 OpenSeadragon contributors\r\n *\r\n * Redistribution and use in source and binary forms, with or without\r\n * modification, are permitted provided that the following conditions are\r\n * met:\r\n *\r\n * - Redistributions of source code must retain the above copyright notice,\r\n * this list of conditions and the following disclaimer.\r\n *\r\n * - Redistributions in binary form must reproduce the above copyright\r\n * notice, this list of conditions and the following disclaimer in the\r\n * documentation and/or other materials provided with the distribution.\r\n *\r\n * - Neither the name of CodePlex Foundation nor the names of its\r\n * contributors may be used to endorse or promote products derived from\r\n * this software without specific prior written permission.\r\n *\r\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\r\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\r\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\r\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\r\n * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\r\n * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\r\n * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\r\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\r\n * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r\n * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r\n */\r\n\r\n(function( $ ){\r\n\r\n\r\n/**\r\n * @class Viewport\r\n * @memberof OpenSeadragon\r\n * @classdesc Handles coordinate-related functionality (zoom, pan, rotation, etc.)\r\n * for an {@link OpenSeadragon.Viewer}.\r\n * @param {Object} options - Options for this Viewport.\r\n * @param {Object} [options.margins] - See viewportMargins in {@link OpenSeadragon.Options}.\r\n * @param {Number} [options.springStiffness] - See springStiffness in {@link OpenSeadragon.Options}.\r\n * @param {Number} [options.animationTime] - See animationTime in {@link OpenSeadragon.Options}.\r\n * @param {Number} [options.minZoomImageRatio] - See minZoomImageRatio in {@link OpenSeadragon.Options}.\r\n * @param {Number} [options.maxZoomPixelRatio] - See maxZoomPixelRatio in {@link OpenSeadragon.Options}.\r\n * @param {Number} [options.visibilityRatio] - See visibilityRatio in {@link OpenSeadragon.Options}.\r\n * @param {Boolean} [options.wrapHorizontal] - See wrapHorizontal in {@link OpenSeadragon.Options}.\r\n * @param {Boolean} [options.wrapVertical] - See wrapVertical in {@link OpenSeadragon.Options}.\r\n * @param {Number} [options.defaultZoomLevel] - See defaultZoomLevel in {@link OpenSeadragon.Options}.\r\n * @param {Number} [options.minZoomLevel] - See minZoomLevel in {@link OpenSeadragon.Options}.\r\n * @param {Number} [options.maxZoomLevel] - See maxZoomLevel in {@link OpenSeadragon.Options}.\r\n * @param {Number} [options.degrees] - See degrees in {@link OpenSeadragon.Options}.\r\n * @param {Boolean} [options.homeFillsViewer] - See homeFillsViewer in {@link OpenSeadragon.Options}.\r\n */\r\n$.Viewport = function( options ) {\r\n\r\n //backward compatibility for positional args while prefering more\r\n //idiomatic javascript options object as the only argument\r\n var args = arguments;\r\n if (args.length && args[0] instanceof $.Point) {\r\n options = {\r\n containerSize: args[0],\r\n contentSize: args[1],\r\n config: args[2]\r\n };\r\n }\r\n\r\n //options.config and the general config argument are deprecated\r\n //in favor of the more direct specification of optional settings\r\n //being passed directly on the options object\r\n if ( options.config ){\r\n $.extend( true, options, options.config );\r\n delete options.config;\r\n }\r\n\r\n this._margins = $.extend({\r\n left: 0,\r\n top: 0,\r\n right: 0,\r\n bottom: 0\r\n }, options.margins || {});\r\n\r\n delete options.margins;\r\n\r\n $.extend( true, this, {\r\n\r\n //required settings\r\n containerSize: null,\r\n contentSize: null,\r\n\r\n //internal state properties\r\n zoomPoint: null,\r\n viewer: null,\r\n\r\n //configurable options\r\n springStiffness: $.DEFAULT_SETTINGS.springStiffness,\r\n animationTime: $.DEFAULT_SETTINGS.animationTime,\r\n minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio,\r\n maxZoomPixelRatio: $.DEFAULT_SETTINGS.maxZoomPixelRatio,\r\n visibilityRatio: $.DEFAULT_SETTINGS.visibilityRatio,\r\n wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal,\r\n wrapVertical: $.DEFAULT_SETTINGS.wrapVertical,\r\n defaultZoomLevel: $.DEFAULT_SETTINGS.defaultZoomLevel,\r\n minZoomLevel: $.DEFAULT_SETTINGS.minZoomLevel,\r\n maxZoomLevel: $.DEFAULT_SETTINGS.maxZoomLevel,\r\n degrees: $.DEFAULT_SETTINGS.degrees,\r\n flipped: $.DEFAULT_SETTINGS.flipped,\r\n homeFillsViewer: $.DEFAULT_SETTINGS.homeFillsViewer\r\n\r\n }, options );\r\n\r\n this._updateContainerInnerSize();\r\n\r\n this.centerSpringX = new $.Spring({\r\n initial: 0,\r\n springStiffness: this.springStiffness,\r\n animationTime: this.animationTime\r\n });\r\n this.centerSpringY = new $.Spring({\r\n initial: 0,\r\n springStiffness: this.springStiffness,\r\n animationTime: this.animationTime\r\n });\r\n this.zoomSpring = new $.Spring({\r\n exponential: true,\r\n initial: 1,\r\n springStiffness: this.springStiffness,\r\n animationTime: this.animationTime\r\n });\r\n\r\n this._oldCenterX = this.centerSpringX.current.value;\r\n this._oldCenterY = this.centerSpringY.current.value;\r\n this._oldZoom = this.zoomSpring.current.value;\r\n\r\n this._setContentBounds(new $.Rect(0, 0, 1, 1), 1);\r\n\r\n this.goHome(true);\r\n this.update();\r\n};\r\n\r\n/** @lends OpenSeadragon.Viewport.prototype */\r\n$.Viewport.prototype = {\r\n /**\r\n * Updates the viewport's home bounds and constraints for the given content size.\r\n * @function\r\n * @param {OpenSeadragon.Point} contentSize - size of the content in content units\r\n * @return {OpenSeadragon.Viewport} Chainable.\r\n * @fires OpenSeadragon.Viewer.event:reset-size\r\n */\r\n resetContentSize: function(contentSize) {\r\n $.console.assert(contentSize, \"[Viewport.resetContentSize] contentSize is required\");\r\n $.console.assert(contentSize instanceof $.Point, \"[Viewport.resetContentSize] contentSize must be an OpenSeadragon.Point\");\r\n $.console.assert(contentSize.x > 0, \"[Viewport.resetContentSize] contentSize.x must be greater than 0\");\r\n $.console.assert(contentSize.y > 0, \"[Viewport.resetContentSize] contentSize.y must be greater than 0\");\r\n\r\n this._setContentBounds(new $.Rect(0, 0, 1, contentSize.y / contentSize.x), contentSize.x);\r\n return this;\r\n },\r\n\r\n // deprecated\r\n setHomeBounds: function(bounds, contentFactor) {\r\n $.console.error(\"[Viewport.setHomeBounds] this function is deprecated; The content bounds should not be set manually.\");\r\n this._setContentBounds(bounds, contentFactor);\r\n },\r\n\r\n // Set the viewport's content bounds\r\n // @param {OpenSeadragon.Rect} bounds - the new bounds in viewport coordinates\r\n // without rotation\r\n // @param {Number} contentFactor - how many content units per viewport unit\r\n // @fires OpenSeadragon.Viewer.event:reset-size\r\n // @private\r\n _setContentBounds: function(bounds, contentFactor) {\r\n $.console.assert(bounds, \"[Viewport._setContentBounds] bounds is required\");\r\n $.console.assert(bounds instanceof $.Rect, \"[Viewport._setContentBounds] bounds must be an OpenSeadragon.Rect\");\r\n $.console.assert(bounds.width > 0, \"[Viewport._setContentBounds] bounds.width must be greater than 0\");\r\n $.console.assert(bounds.height > 0, \"[Viewport._setContentBounds] bounds.height must be greater than 0\");\r\n\r\n this._contentBoundsNoRotate = bounds.clone();\r\n this._contentSizeNoRotate = this._contentBoundsNoRotate.getSize().times(\r\n contentFactor);\r\n\r\n this._contentBounds = bounds.rotate(this.degrees).getBoundingBox();\r\n this._contentSize = this._contentBounds.getSize().times(contentFactor);\r\n this._contentAspectRatio = this._contentSize.x / this._contentSize.y;\r\n\r\n if (this.viewer) {\r\n /**\r\n * Raised when the viewer's content size or home bounds are reset\r\n * (see {@link OpenSeadragon.Viewport#resetContentSize}).\r\n *\r\n * @event reset-size\r\n * @memberof OpenSeadragon.Viewer\r\n * @type {object}\r\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.\r\n * @property {OpenSeadragon.Point} contentSize\r\n * @property {OpenSeadragon.Rect} contentBounds - Content bounds.\r\n * @property {OpenSeadragon.Rect} homeBounds - Content bounds.\r\n * Deprecated use contentBounds instead.\r\n * @property {Number} contentFactor\r\n * @property {?Object} userData - Arbitrary subscriber-defined object.\r\n */\r\n this.viewer.raiseEvent('reset-size', {\r\n contentSize: this._contentSizeNoRotate.clone(),\r\n contentFactor: contentFactor,\r\n homeBounds: this._contentBoundsNoRotate.clone(),\r\n contentBounds: this._contentBounds.clone()\r\n });\r\n }\r\n },\r\n\r\n /**\r\n * Returns the home zoom in \"viewport zoom\" value.\r\n * @function\r\n * @returns {Number} The home zoom in \"viewport zoom\".\r\n */\r\n getHomeZoom: function() {\r\n if (this.defaultZoomLevel) {\r\n return this.defaultZoomLevel;\r\n }\r\n\r\n var aspectFactor = this._contentAspectRatio / this.getAspectRatio();\r\n var output;\r\n if (this.homeFillsViewer) { // fill the viewer and clip the image\r\n output = aspectFactor >= 1 ? aspectFactor : 1;\r\n } else {\r\n output = aspectFactor >= 1 ? 1 : aspectFactor;\r\n }\r\n\r\n return output / this._contentBounds.width;\r\n },\r\n\r\n /**\r\n * Returns the home bounds in viewport coordinates.\r\n * @function\r\n * @returns {OpenSeadragon.Rect} The home bounds in vewport coordinates.\r\n */\r\n getHomeBounds: function() {\r\n return this.getHomeBoundsNoRotate().rotate(-this.getRotation());\r\n },\r\n\r\n /**\r\n * Returns the home bounds in viewport coordinates.\r\n * This method ignores the viewport rotation. Use\r\n * {@link OpenSeadragon.Viewport#getHomeBounds} to take it into account.\r\n * @function\r\n * @returns {OpenSeadragon.Rect} The home bounds in vewport coordinates.\r\n */\r\n getHomeBoundsNoRotate: function() {\r\n var center = this._contentBounds.getCenter();\r\n var width = 1.0 / this.getHomeZoom();\r\n var height = width / this.getAspectRatio();\r\n\r\n return new $.Rect(\r\n center.x - (width / 2.0),\r\n center.y - (height / 2.0),\r\n width,\r\n height\r\n );\r\n },\r\n\r\n /**\r\n * @function\r\n * @param {Boolean} immediately\r\n * @fires OpenSeadragon.Viewer.event:home\r\n */\r\n goHome: function(immediately) {\r\n if (this.viewer) {\r\n /**\r\n * Raised when the \"home\" operation occurs (see {@link OpenSeadragon.Viewport#goHome}).\r\n *\r\n * @event home\r\n * @memberof OpenSeadragon.Viewer\r\n * @type {object}\r\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.\r\n * @property {Boolean} immediately\r\n * @property {?Object} userData - Arbitrary subscriber-defined object.\r\n */\r\n this.viewer.raiseEvent('home', {\r\n immediately: immediately\r\n });\r\n }\r\n return this.fitBounds(this.getHomeBounds(), immediately);\r\n },\r\n\r\n /**\r\n * @function\r\n */\r\n getMinZoom: function() {\r\n var homeZoom = this.getHomeZoom(),\r\n zoom = this.minZoomLevel ?\r\n this.minZoomLevel :\r\n this.minZoomImageRatio * homeZoom;\r\n\r\n return zoom;\r\n },\r\n\r\n /**\r\n * @function\r\n */\r\n getMaxZoom: function() {\r\n var zoom = this.maxZoomLevel;\r\n if (!zoom) {\r\n zoom = this._contentSize.x * this.maxZoomPixelRatio / this._containerInnerSize.x;\r\n zoom /= this._contentBounds.width;\r\n }\r\n\r\n return Math.max( zoom, this.getHomeZoom() );\r\n },\r\n\r\n /**\r\n * @function\r\n */\r\n getAspectRatio: function() {\r\n return this._containerInnerSize.x / this._containerInnerSize.y;\r\n },\r\n\r\n /**\r\n * @function\r\n * @returns {OpenSeadragon.Point} The size of the container, in screen coordinates.\r\n */\r\n getContainerSize: function() {\r\n return new $.Point(\r\n this.containerSize.x,\r\n this.containerSize.y\r\n );\r\n },\r\n\r\n /**\r\n * The margins push the \"home\" region in from the sides by the specified amounts.\r\n * @function\r\n * @returns {Object} Properties (Numbers, in screen coordinates): left, top, right, bottom.\r\n */\r\n getMargins: function() {\r\n return $.extend({}, this._margins); // Make a copy so we are not returning our original\r\n },\r\n\r\n /**\r\n * The margins push the \"home\" region in from the sides by the specified amounts.\r\n * @function\r\n * @param {Object} margins - Properties (Numbers, in screen coordinates): left, top, right, bottom.\r\n */\r\n setMargins: function(margins) {\r\n $.console.assert($.type(margins) === 'object', '[Viewport.setMargins] margins must be an object');\r\n\r\n this._margins = $.extend({\r\n left: 0,\r\n top: 0,\r\n right: 0,\r\n bottom: 0\r\n }, margins);\r\n\r\n this._updateContainerInnerSize();\r\n if (this.viewer) {\r\n this.viewer.forceRedraw();\r\n }\r\n },\r\n\r\n /**\r\n * Returns the bounds of the visible area in viewport coordinates.\r\n * @function\r\n * @param {Boolean} current - Pass true for the current location; defaults to false (target location).\r\n * @returns {OpenSeadragon.Rect} The location you are zoomed/panned to, in viewport coordinates.\r\n */\r\n getBounds: function(current) {\r\n return this.getBoundsNoRotate(current).rotate(-this.getRotation());\r\n },\r\n\r\n /**\r\n * Returns the bounds of the visible area in viewport coordinates.\r\n * This method ignores the viewport rotation. Use\r\n * {@link OpenSeadragon.Viewport#getBounds} to take it into account.\r\n * @function\r\n * @param {Boolean} current - Pass true for the current location; defaults to false (target location).\r\n * @returns {OpenSeadragon.Rect} The location you are zoomed/panned to, in viewport coordinates.\r\n */\r\n getBoundsNoRotate: function(current) {\r\n var center = this.getCenter(current);\r\n var width = 1.0 / this.getZoom(current);\r\n var height = width / this.getAspectRatio();\r\n\r\n return new $.Rect(\r\n center.x - (width / 2.0),\r\n center.y - (height / 2.0),\r\n width,\r\n height\r\n );\r\n },\r\n\r\n /**\r\n * @function\r\n * @param {Boolean} current - Pass true for the current location; defaults to false (target location).\r\n * @returns {OpenSeadragon.Rect} The location you are zoomed/panned to,\r\n * including the space taken by margins, in viewport coordinates.\r\n */\r\n getBoundsWithMargins: function(current) {\r\n return this.getBoundsNoRotateWithMargins(current).rotate(\r\n -this.getRotation(), this.getCenter(current));\r\n },\r\n\r\n /**\r\n * @function\r\n * @param {Boolean} current - Pass true for the current location; defaults to false (target location).\r\n * @returns {OpenSeadragon.Rect} The location you are zoomed/panned to,\r\n * including the space taken by margins, in viewport coordinates.\r\n */\r\n getBoundsNoRotateWithMargins: function(current) {\r\n var bounds = this.getBoundsNoRotate(current);\r\n var factor = this._containerInnerSize.x * this.getZoom(current);\r\n bounds.x -= this._margins.left / factor;\r\n bounds.y -= this._margins.top / factor;\r\n bounds.width += (this._margins.left + this._margins.right) / factor;\r\n bounds.height += (this._margins.top + this._margins.bottom) / factor;\r\n return bounds;\r\n },\r\n\r\n /**\r\n * @function\r\n * @param {Boolean} current - Pass true for the current location; defaults to false (target location).\r\n */\r\n getCenter: function( current ) {\r\n var centerCurrent = new $.Point(\r\n this.centerSpringX.current.value,\r\n this.centerSpringY.current.value\r\n ),\r\n centerTarget = new $.Point(\r\n this.centerSpringX.target.value,\r\n this.centerSpringY.target.value\r\n ),\r\n oldZoomPixel,\r\n zoom,\r\n width,\r\n height,\r\n bounds,\r\n newZoomPixel,\r\n deltaZoomPixels,\r\n deltaZoomPoints;\r\n\r\n if ( current ) {\r\n return centerCurrent;\r\n } else if ( !this.zoomPoint ) {\r\n return centerTarget;\r\n }\r\n\r\n oldZoomPixel = this.pixelFromPoint(this.zoomPoint, true);\r\n\r\n zoom = this.getZoom();\r\n width = 1.0 / zoom;\r\n height = width / this.getAspectRatio();\r\n bounds = new $.Rect(\r\n centerCurrent.x - width / 2.0,\r\n centerCurrent.y - height / 2.0,\r\n width,\r\n height\r\n );\r\n\r\n newZoomPixel = this._pixelFromPoint(this.zoomPoint, bounds);\r\n deltaZoomPixels = newZoomPixel.minus( oldZoomPixel );\r\n deltaZoomPoints = deltaZoomPixels.divide( this._containerInnerSize.x * zoom );\r\n\r\n return centerTarget.plus( deltaZoomPoints );\r\n },\r\n\r\n /**\r\n * @function\r\n * @param {Boolean} current - Pass true for the current location; defaults to false (target location).\r\n */\r\n getZoom: function( current ) {\r\n if ( current ) {\r\n return this.zoomSpring.current.value;\r\n } else {\r\n return this.zoomSpring.target.value;\r\n }\r\n },\r\n\r\n // private\r\n _applyZoomConstraints: function(zoom) {\r\n return Math.max(\r\n Math.min(zoom, this.getMaxZoom()),\r\n this.getMinZoom());\r\n },\r\n\r\n /**\r\n * @function\r\n * @private\r\n * @param {OpenSeadragon.Rect} bounds\r\n * @return {OpenSeadragon.Rect} constrained bounds.\r\n */\r\n _applyBoundaryConstraints: function(bounds) {\r\n var newBounds = new $.Rect(\r\n bounds.x,\r\n bounds.y,\r\n bounds.width,\r\n bounds.height);\r\n\r\n if (this.wrapHorizontal) {\r\n //do nothing\r\n } else {\r\n var horizontalThreshold = this.visibilityRatio * newBounds.width;\r\n var boundsRight = newBounds.x + newBounds.width;\r\n var contentRight = this._contentBoundsNoRotate.x + this._contentBoundsNoRotate.width;\r\n var leftDx = this._contentBoundsNoRotate.x - boundsRight + horizontalThreshold;\r\n var rightDx = contentRight - newBounds.x - horizontalThreshold;\r\n\r\n if (horizontalThreshold > this._contentBoundsNoRotate.width) {\r\n newBounds.x += (leftDx + rightDx) / 2;\r\n } else if (rightDx < 0) {\r\n newBounds.x += rightDx;\r\n } else if (leftDx > 0) {\r\n newBounds.x += leftDx;\r\n }\r\n }\r\n\r\n if (this.wrapVertical) {\r\n //do nothing\r\n } else {\r\n var verticalThreshold = this.visibilityRatio * newBounds.height;\r\n var boundsBottom = newBounds.y + newBounds.height;\r\n var contentBottom = this._contentBoundsNoRotate.y + this._contentBoundsNoRotate.height;\r\n var topDy = this._contentBoundsNoRotate.y - boundsBottom + verticalThreshold;\r\n var bottomDy = contentBottom - newBounds.y - verticalThreshold;\r\n\r\n if (verticalThreshold > this._contentBoundsNoRotate.height) {\r\n newBounds.y += (topDy + bottomDy) / 2;\r\n } else if (bottomDy < 0) {\r\n newBounds.y += bottomDy;\r\n } else if (topDy > 0) {\r\n newBounds.y += topDy;\r\n }\r\n }\r\n\r\n return newBounds;\r\n },\r\n\r\n /**\r\n * @function\r\n * @private\r\n * @param {Boolean} [immediately=false] - whether the function that triggered this event was\r\n * called with the \"immediately\" flag\r\n */\r\n _raiseConstraintsEvent: function(immediately) {\r\n if (this.viewer) {\r\n /**\r\n * Raised when the viewport constraints are applied (see {@link OpenSeadragon.Viewport#applyConstraints}).\r\n *\r\n * @event constrain\r\n * @memberof OpenSeadragon.Viewer\r\n * @type {object}\r\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.\r\n * @property {Boolean} immediately - whether the function that triggered this event was\r\n * called with the \"immediately\" flag\r\n * @property {?Object} userData - Arbitrary subscriber-defined object.\r\n */\r\n this.viewer.raiseEvent( 'constrain', {\r\n immediately: immediately\r\n });\r\n }\r\n },\r\n\r\n /**\r\n * Enforces the minZoom, maxZoom and visibilityRatio constraints by\r\n * zooming and panning to the closest acceptable zoom and location.\r\n * @function\r\n * @param {Boolean} [immediately=false]\r\n * @return {OpenSeadragon.Viewport} Chainable.\r\n * @fires OpenSeadragon.Viewer.event:constrain\r\n */\r\n applyConstraints: function(immediately) {\r\n var actualZoom = this.getZoom();\r\n var constrainedZoom = this._applyZoomConstraints(actualZoom);\r\n\r\n if (actualZoom !== constrainedZoom) {\r\n this.zoomTo(constrainedZoom, this.zoomPoint, immediately);\r\n }\r\n\r\n var bounds = this.getBoundsNoRotate();\r\n var constrainedBounds = this._applyBoundaryConstraints(bounds);\r\n this._raiseConstraintsEvent(immediately);\r\n\r\n if (bounds.x !== constrainedBounds.x ||\r\n bounds.y !== constrainedBounds.y ||\r\n immediately) {\r\n this.fitBounds(\r\n constrainedBounds.rotate(-this.getRotation()),\r\n immediately);\r\n }\r\n return this;\r\n },\r\n\r\n /**\r\n * Equivalent to {@link OpenSeadragon.Viewport#applyConstraints}\r\n * @function\r\n * @param {Boolean} [immediately=false]\r\n * @return {OpenSeadragon.Viewport} Chainable.\r\n * @fires OpenSeadragon.Viewer.event:constrain\r\n */\r\n ensureVisible: function(immediately) {\r\n return this.applyConstraints(immediately);\r\n },\r\n\r\n /**\r\n * @function\r\n * @private\r\n * @param {OpenSeadragon.Rect} bounds\r\n * @param {Object} options (immediately=false, constraints=false)\r\n * @return {OpenSeadragon.Viewport} Chainable.\r\n */\r\n _fitBounds: function(bounds, options) {\r\n options = options || {};\r\n var immediately = options.immediately || false;\r\n var constraints = options.constraints || false;\r\n\r\n var aspect = this.getAspectRatio();\r\n var center = bounds.getCenter();\r\n\r\n // Compute width and height of bounding box.\r\n var newBounds = new $.Rect(\r\n bounds.x,\r\n bounds.y,\r\n bounds.width,\r\n bounds.height,\r\n bounds.degrees + this.getRotation())\r\n .getBoundingBox();\r\n\r\n if (newBounds.getAspectRatio() >= aspect) {\r\n newBounds.height = newBounds.width / aspect;\r\n } else {\r\n newBounds.width = newBounds.height * aspect;\r\n }\r\n\r\n // Compute x and y from width, height and center position\r\n newBounds.x = center.x - newBounds.width / 2;\r\n newBounds.y = center.y - newBounds.height / 2;\r\n var newZoom = 1.0 / newBounds.width;\r\n\r\n if (constraints) {\r\n var newBoundsAspectRatio = newBounds.getAspectRatio();\r\n var newConstrainedZoom = this._applyZoomConstraints(newZoom);\r\n\r\n if (newZoom !== newConstrainedZoom) {\r\n newZoom = newConstrainedZoom;\r\n newBounds.width = 1.0 / newZoom;\r\n newBounds.x = center.x - newBounds.width / 2;\r\n newBounds.height = newBounds.width / newBoundsAspectRatio;\r\n newBounds.y = center.y - newBounds.height / 2;\r\n }\r\n\r\n newBounds = this._applyBoundaryConstraints(newBounds);\r\n center = newBounds.getCenter();\r\n this._raiseConstraintsEvent(immediately);\r\n }\r\n\r\n if (immediately) {\r\n this.panTo(center, true);\r\n return this.zoomTo(newZoom, null, true);\r\n }\r\n\r\n this.panTo(this.getCenter(true), true);\r\n this.zoomTo(this.getZoom(true), null, true);\r\n\r\n var oldBounds = this.getBounds();\r\n var oldZoom = this.getZoom();\r\n\r\n if (oldZoom === 0 || Math.abs(newZoom / oldZoom - 1) < 0.00000001) {\r\n this.zoomTo(newZoom, true);\r\n return this.panTo(center, immediately);\r\n }\r\n\r\n newBounds = newBounds.rotate(-this.getRotation());\r\n var referencePoint = newBounds.getTopLeft().times(newZoom)\r\n .minus(oldBounds.getTopLeft().times(oldZoom))\r\n .divide(newZoom - oldZoom);\r\n\r\n return this.zoomTo(newZoom, referencePoint, immediately);\r\n },\r\n\r\n /**\r\n * Makes the viewport zoom and pan so that the specified bounds take\r\n * as much space as possible in the viewport.\r\n * Note: this method ignores the constraints (minZoom, maxZoom and\r\n * visibilityRatio).\r\n * Use {@link OpenSeadragon.Viewport#fitBoundsWithConstraints} to enforce\r\n * them.\r\n * @function\r\n * @param {OpenSeadragon.Rect} bounds\r\n * @param {Boolean} [immediately=false]\r\n * @return {OpenSeadragon.Viewport} Chainable.\r\n */\r\n fitBounds: function(bounds, immediately) {\r\n return this._fitBounds(bounds, {\r\n immediately: immediately,\r\n constraints: false\r\n });\r\n },\r\n\r\n /**\r\n * Makes the viewport zoom and pan so that the specified bounds take\r\n * as much space as possible in the viewport while enforcing the constraints\r\n * (minZoom, maxZoom and visibilityRatio).\r\n * Note: because this method enforces the constraints, part of the\r\n * provided bounds may end up outside of the viewport.\r\n * Use {@link OpenSeadragon.Viewport#fitBounds} to ignore them.\r\n * @function\r\n * @param {OpenSeadragon.Rect} bounds\r\n * @param {Boolean} [immediately=false]\r\n * @return {OpenSeadragon.Viewport} Chainable.\r\n */\r\n fitBoundsWithConstraints: function(bounds, immediately) {\r\n return this._fitBounds(bounds, {\r\n immediately: immediately,\r\n constraints: true\r\n });\r\n },\r\n\r\n /**\r\n * Zooms so the image just fills the viewer vertically.\r\n * @param {Boolean} immediately\r\n * @return {OpenSeadragon.Viewport} Chainable.\r\n */\r\n fitVertically: function(immediately) {\r\n var box = new $.Rect(\r\n this._contentBounds.x + (this._contentBounds.width / 2),\r\n this._contentBounds.y,\r\n 0,\r\n this._contentBounds.height);\r\n return this.fitBounds(box, immediately);\r\n },\r\n\r\n /**\r\n * Zooms so the image just fills the viewer horizontally.\r\n * @param {Boolean} immediately\r\n * @return {OpenSeadragon.Viewport} Chainable.\r\n */\r\n fitHorizontally: function(immediately) {\r\n var box = new $.Rect(\r\n this._contentBounds.x,\r\n this._contentBounds.y + (this._contentBounds.height / 2),\r\n this._contentBounds.width,\r\n 0);\r\n return this.fitBounds(box, immediately);\r\n },\r\n\r\n\r\n /**\r\n * Returns bounds taking constraints into account\r\n * Added to improve constrained panning\r\n * @param {Boolean} current - Pass true for the current location; defaults to false (target location).\r\n * @return {OpenSeadragon.Viewport} Chainable.\r\n */\r\n getConstrainedBounds: function(current) {\r\n var bounds,\r\n constrainedBounds;\r\n\r\n bounds = this.getBounds(current);\r\n\r\n constrainedBounds = this._applyBoundaryConstraints(bounds);\r\n\r\n return constrainedBounds;\r\n },\r\n\r\n /**\r\n * @function\r\n * @param {OpenSeadragon.Point} delta\r\n * @param {Boolean} immediately\r\n * @return {OpenSeadragon.Viewport} Chainable.\r\n * @fires OpenSeadragon.Viewer.event:pan\r\n */\r\n panBy: function( delta, immediately ) {\r\n var center = new $.Point(\r\n this.centerSpringX.target.value,\r\n this.centerSpringY.target.value\r\n );\r\n return this.panTo( center.plus( delta ), immediately );\r\n },\r\n\r\n /**\r\n * @function\r\n * @param {OpenSeadragon.Point} center\r\n * @param {Boolean} immediately\r\n * @return {OpenSeadragon.Viewport} Chainable.\r\n * @fires OpenSeadragon.Viewer.event:pan\r\n */\r\n panTo: function( center, immediately ) {\r\n if ( immediately ) {\r\n this.centerSpringX.resetTo( center.x );\r\n this.centerSpringY.resetTo( center.y );\r\n } else {\r\n this.centerSpringX.springTo( center.x );\r\n this.centerSpringY.springTo( center.y );\r\n }\r\n\r\n if( this.viewer ){\r\n /**\r\n * Raised when the viewport is panned (see {@link OpenSeadragon.Viewport#panBy} and {@link OpenSeadragon.Viewport#panTo}).\r\n *\r\n * @event pan\r\n * @memberof OpenSeadragon.Viewer\r\n * @type {object}\r\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.\r\n * @property {OpenSeadragon.Point} center\r\n * @property {Boolean} immediately\r\n * @property {?Object} userData - Arbitrary subscriber-defined object.\r\n */\r\n this.viewer.raiseEvent( 'pan', {\r\n center: center,\r\n immediately: immediately\r\n });\r\n }\r\n\r\n return this;\r\n },\r\n\r\n /**\r\n * @function\r\n * @return {OpenSeadragon.Viewport} Chainable.\r\n * @fires OpenSeadragon.Viewer.event:zoom\r\n */\r\n zoomBy: function(factor, refPoint, immediately) {\r\n return this.zoomTo(\r\n this.zoomSpring.target.value * factor, refPoint, immediately);\r\n },\r\n\r\n /**\r\n * Zooms to the specified zoom level\r\n * @function\r\n * @param {Number} zoom The zoom level to zoom to.\r\n * @param {OpenSeadragon.Point} [refPoint] The point which will stay at\r\n * the same screen location. Defaults to the viewport center.\r\n * @param {Boolean} [immediately=false]\r\n * @return {OpenSeadragon.Viewport} Chainable.\r\n * @fires OpenSeadragon.Viewer.event:zoom\r\n */\r\n zoomTo: function(zoom, refPoint, immediately) {\r\n var _this = this;\r\n\r\n this.zoomPoint = refPoint instanceof $.Point &&\r\n !isNaN(refPoint.x) &&\r\n !isNaN(refPoint.y) ?\r\n refPoint :\r\n null;\r\n\r\n if (immediately) {\r\n this._adjustCenterSpringsForZoomPoint(function() {\r\n _this.zoomSpring.resetTo(zoom);\r\n });\r\n } else {\r\n this.zoomSpring.springTo(zoom);\r\n }\r\n\r\n if (this.viewer) {\r\n /**\r\n * Raised when the viewport zoom level changes (see {@link OpenSeadragon.Viewport#zoomBy} and {@link OpenSeadragon.Viewport#zoomTo}).\r\n *\r\n * @event zoom\r\n * @memberof OpenSeadragon.Viewer\r\n * @type {object}\r\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.\r\n * @property {Number} zoom\r\n * @property {OpenSeadragon.Point} refPoint\r\n * @property {Boolean} immediately\r\n * @property {?Object} userData - Arbitrary subscriber-defined object.\r\n */\r\n this.viewer.raiseEvent('zoom', {\r\n zoom: zoom,\r\n refPoint: refPoint,\r\n immediately: immediately\r\n });\r\n }\r\n\r\n return this;\r\n },\r\n\r\n /**\r\n * Rotates this viewport to the angle specified.\r\n * @function\r\n * @return {OpenSeadragon.Viewport} Chainable.\r\n */\r\n setRotation: function(degrees) {\r\n if (!this.viewer || !this.viewer.drawer.canRotate()) {\r\n return this;\r\n }\r\n this.degrees = $.positiveModulo(degrees, 360);\r\n this._setContentBounds(\r\n this.viewer.world.getHomeBounds(),\r\n this.viewer.world.getContentFactor());\r\n this.viewer.forceRedraw();\r\n\r\n /**\r\n * Raised when rotation has been changed.\r\n *\r\n * @event rotate\r\n * @memberof OpenSeadragon.Viewer\r\n * @type {object}\r\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.\r\n * @property {Number} degrees - The number of degrees the rotation was set to.\r\n * @property {?Object} userData - Arbitrary subscriber-defined object.\r\n */\r\n this.viewer.raiseEvent('rotate', {\"degrees\": degrees});\r\n return this;\r\n },\r\n\r\n /**\r\n * Gets the current rotation in degrees.\r\n * @function\r\n * @return {Number} The current rotation in degrees.\r\n */\r\n getRotation: function() {\r\n return this.degrees;\r\n },\r\n\r\n /**\r\n * @function\r\n * @return {OpenSeadragon.Viewport} Chainable.\r\n * @fires OpenSeadragon.Viewer.event:resize\r\n */\r\n resize: function( newContainerSize, maintain ) {\r\n var oldBounds = this.getBoundsNoRotate(),\r\n newBounds = oldBounds,\r\n widthDeltaFactor;\r\n\r\n this.containerSize.x = newContainerSize.x;\r\n this.containerSize.y = newContainerSize.y;\r\n\r\n this._updateContainerInnerSize();\r\n\r\n if ( maintain ) {\r\n // TODO: widthDeltaFactor will always be 1; probably not what's intended\r\n widthDeltaFactor = newContainerSize.x / this.containerSize.x;\r\n newBounds.width = oldBounds.width * widthDeltaFactor;\r\n newBounds.height = newBounds.width / this.getAspectRatio();\r\n }\r\n\r\n if( this.viewer ){\r\n /**\r\n * Raised when the viewer is resized (see {@link OpenSeadragon.Viewport#resize}).\r\n *\r\n * @event resize\r\n * @memberof OpenSeadragon.Viewer\r\n * @type {object}\r\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.\r\n * @property {OpenSeadragon.Point} newContainerSize\r\n * @property {Boolean} maintain\r\n * @property {?Object} userData - Arbitrary subscriber-defined object.\r\n */\r\n this.viewer.raiseEvent( 'resize', {\r\n newContainerSize: newContainerSize,\r\n maintain: maintain\r\n });\r\n }\r\n\r\n return this.fitBounds( newBounds, true );\r\n },\r\n\r\n // private\r\n _updateContainerInnerSize: function() {\r\n this._containerInnerSize = new $.Point(\r\n Math.max(1, this.containerSize.x - (this._margins.left + this._margins.right)),\r\n Math.max(1, this.containerSize.y - (this._margins.top + this._margins.bottom))\r\n );\r\n },\r\n\r\n /**\r\n * Update the zoom and center (X and Y) springs.\r\n * @function\r\n * @returns {Boolean} True if any change has been made, false otherwise.\r\n */\r\n update: function() {\r\n var _this = this;\r\n this._adjustCenterSpringsForZoomPoint(function() {\r\n _this.zoomSpring.update();\r\n });\r\n\r\n this.centerSpringX.update();\r\n this.centerSpringY.update();\r\n\r\n var changed = this.centerSpringX.current.value !== this._oldCenterX ||\r\n this.centerSpringY.current.value !== this._oldCenterY ||\r\n this.zoomSpring.current.value !== this._oldZoom;\r\n\r\n this._oldCenterX = this.centerSpringX.current.value;\r\n this._oldCenterY = this.centerSpringY.current.value;\r\n this._oldZoom = this.zoomSpring.current.value;\r\n\r\n return changed;\r\n },\r\n\r\n _adjustCenterSpringsForZoomPoint: function(zoomSpringHandler) {\r\n if (this.zoomPoint) {\r\n var oldZoomPixel = this.pixelFromPoint(this.zoomPoint, true);\r\n zoomSpringHandler();\r\n var newZoomPixel = this.pixelFromPoint(this.zoomPoint, true);\r\n\r\n var deltaZoomPixels = newZoomPixel.minus(oldZoomPixel);\r\n var deltaZoomPoints = this.deltaPointsFromPixels(\r\n deltaZoomPixels, true);\r\n\r\n this.centerSpringX.shiftBy(deltaZoomPoints.x);\r\n this.centerSpringY.shiftBy(deltaZoomPoints.y);\r\n\r\n if (this.zoomSpring.isAtTargetValue()) {\r\n this.zoomPoint = null;\r\n }\r\n } else {\r\n zoomSpringHandler();\r\n }\r\n },\r\n\r\n /**\r\n * Convert a delta (translation vector) from viewport coordinates to pixels\r\n * coordinates. This method does not take rotation into account.\r\n * Consider using deltaPixelsFromPoints if you need to account for rotation.\r\n * @param {OpenSeadragon.Point} deltaPoints - The translation vector to convert.\r\n * @param {Boolean} [current=false] - Pass true for the current location;\r\n * defaults to false (target location).\r\n * @returns {OpenSeadragon.Point}\r\n */\r\n deltaPixelsFromPointsNoRotate: function(deltaPoints, current) {\r\n return deltaPoints.times(\r\n this._containerInnerSize.x * this.getZoom(current)\r\n );\r\n },\r\n\r\n /**\r\n * Convert a delta (translation vector) from viewport coordinates to pixels\r\n * coordinates.\r\n * @param {OpenSeadragon.Point} deltaPoints - The translation vector to convert.\r\n * @param {Boolean} [current=false] - Pass true for the current location;\r\n * defaults to false (target location).\r\n * @returns {OpenSeadragon.Point}\r\n */\r\n deltaPixelsFromPoints: function(deltaPoints, current) {\r\n return this.deltaPixelsFromPointsNoRotate(\r\n deltaPoints.rotate(this.getRotation()),\r\n current);\r\n },\r\n\r\n /**\r\n * Convert a delta (translation vector) from pixels coordinates to viewport\r\n * coordinates. This method does not take rotation into account.\r\n * Consider using deltaPointsFromPixels if you need to account for rotation.\r\n * @param {OpenSeadragon.Point} deltaPixels - The translation vector to convert.\r\n * @param {Boolean} [current=false] - Pass true for the current location;\r\n * defaults to false (target location).\r\n * @returns {OpenSeadragon.Point}\r\n */\r\n deltaPointsFromPixelsNoRotate: function(deltaPixels, current) {\r\n return deltaPixels.divide(\r\n this._containerInnerSize.x * this.getZoom(current)\r\n );\r\n },\r\n\r\n /**\r\n * Convert a delta (translation vector) from pixels coordinates to viewport\r\n * coordinates.\r\n * @param {OpenSeadragon.Point} deltaPixels - The translation vector to convert.\r\n * @param {Boolean} [current=false] - Pass true for the current location;\r\n * defaults to false (target location).\r\n * @returns {OpenSeadragon.Point}\r\n */\r\n deltaPointsFromPixels: function(deltaPixels, current) {\r\n return this.deltaPointsFromPixelsNoRotate(deltaPixels, current)\r\n .rotate(-this.getRotation());\r\n },\r\n\r\n /**\r\n * Convert viewport coordinates to pixels coordinates.\r\n * This method does not take rotation into account.\r\n * Consider using pixelFromPoint if you need to account for rotation.\r\n * @param {OpenSeadragon.Point} point the viewport coordinates\r\n * @param {Boolean} [current=false] - Pass true for the current location;\r\n * defaults to false (target location).\r\n * @returns {OpenSeadragon.Point}\r\n */\r\n pixelFromPointNoRotate: function(point, current) {\r\n return this._pixelFromPointNoRotate(\r\n point, this.getBoundsNoRotate(current));\r\n },\r\n\r\n /**\r\n * Convert viewport coordinates to pixel coordinates.\r\n * @param {OpenSeadragon.Point} point the viewport coordinates\r\n * @param {Boolean} [current=false] - Pass true for the current location;\r\n * defaults to false (target location).\r\n * @returns {OpenSeadragon.Point}\r\n */\r\n pixelFromPoint: function(point, current) {\r\n return this._pixelFromPoint(point, this.getBoundsNoRotate(current));\r\n },\r\n\r\n // private\r\n _pixelFromPointNoRotate: function(point, bounds) {\r\n return point.minus(\r\n bounds.getTopLeft()\r\n ).times(\r\n this._containerInnerSize.x / bounds.width\r\n ).plus(\r\n new $.Point(this._margins.left, this._margins.top)\r\n );\r\n },\r\n\r\n // private\r\n _pixelFromPoint: function(point, bounds) {\r\n return this._pixelFromPointNoRotate(\r\n point.rotate(this.getRotation(), this.getCenter(true)),\r\n bounds);\r\n },\r\n\r\n /**\r\n * Convert pixel coordinates to viewport coordinates.\r\n * This method does not take rotation into account.\r\n * Consider using pointFromPixel if you need to account for rotation.\r\n * @param {OpenSeadragon.Point} pixel Pixel coordinates\r\n * @param {Boolean} [current=false] - Pass true for the current location;\r\n * defaults to false (target location).\r\n * @returns {OpenSeadragon.Point}\r\n */\r\n pointFromPixelNoRotate: function(pixel, current) {\r\n var bounds = this.getBoundsNoRotate(current);\r\n return pixel.minus(\r\n new $.Point(this._margins.left, this._margins.top)\r\n ).divide(\r\n this._containerInnerSize.x / bounds.width\r\n ).plus(\r\n bounds.getTopLeft()\r\n );\r\n },\r\n\r\n /**\r\n * Convert pixel coordinates to viewport coordinates.\r\n * @param {OpenSeadragon.Point} pixel Pixel coordinates\r\n * @param {Boolean} [current=false] - Pass true for the current location;\r\n * defaults to false (target location).\r\n * @returns {OpenSeadragon.Point}\r\n */\r\n pointFromPixel: function(pixel, current) {\r\n return this.pointFromPixelNoRotate(pixel, current).rotate(\r\n -this.getRotation(),\r\n this.getCenter(true)\r\n );\r\n },\r\n\r\n // private\r\n _viewportToImageDelta: function( viewerX, viewerY ) {\r\n var scale = this._contentBoundsNoRotate.width;\r\n return new $.Point(\r\n viewerX * this._contentSizeNoRotate.x / scale,\r\n viewerY * this._contentSizeNoRotate.x / scale);\r\n },\r\n\r\n /**\r\n * Translates from OpenSeadragon viewer coordinate system to image coordinate system.\r\n * This method can be called either by passing X,Y coordinates or an\r\n * OpenSeadragon.Point\r\n * Note: not accurate with multi-image; use TiledImage.viewportToImageCoordinates instead.\r\n * @function\r\n * @param {(OpenSeadragon.Point|Number)} viewerX either a point or the X\r\n * coordinate in viewport coordinate system.\r\n * @param {Number} [viewerY] Y coordinate in viewport coordinate system.\r\n * @return {OpenSeadragon.Point} a point representing the coordinates in the image.\r\n */\r\n viewportToImageCoordinates: function(viewerX, viewerY) {\r\n if (viewerX instanceof $.Point) {\r\n //they passed a point instead of individual components\r\n return this.viewportToImageCoordinates(viewerX.x, viewerX.y);\r\n }\r\n\r\n if (this.viewer) {\r\n var count = this.viewer.world.getItemCount();\r\n if (count > 1) {\r\n $.console.error('[Viewport.viewportToImageCoordinates] is not accurate ' +\r\n 'with multi-image; use TiledImage.viewportToImageCoordinates instead.');\r\n } else if (count === 1) {\r\n // It is better to use TiledImage.viewportToImageCoordinates\r\n // because this._contentBoundsNoRotate can not be relied on\r\n // with clipping.\r\n var item = this.viewer.world.getItemAt(0);\r\n return item.viewportToImageCoordinates(viewerX, viewerY, true);\r\n }\r\n }\r\n\r\n return this._viewportToImageDelta(\r\n viewerX - this._contentBoundsNoRotate.x,\r\n viewerY - this._contentBoundsNoRotate.y);\r\n },\r\n\r\n // private\r\n _imageToViewportDelta: function( imageX, imageY ) {\r\n var scale = this._contentBoundsNoRotate.width;\r\n return new $.Point(\r\n imageX / this._contentSizeNoRotate.x * scale,\r\n imageY / this._contentSizeNoRotate.x * scale);\r\n },\r\n\r\n /**\r\n * Translates from image coordinate system to OpenSeadragon viewer coordinate system\r\n * This method can be called either by passing X,Y coordinates or an\r\n * OpenSeadragon.Point\r\n * Note: not accurate with multi-image; use TiledImage.imageToViewportCoordinates instead.\r\n * @function\r\n * @param {(OpenSeadragon.Point | Number)} imageX the point or the\r\n * X coordinate in image coordinate system.\r\n * @param {Number} [imageY] Y coordinate in image coordinate system.\r\n * @return {OpenSeadragon.Point} a point representing the coordinates in the viewport.\r\n */\r\n imageToViewportCoordinates: function(imageX, imageY) {\r\n if (imageX instanceof $.Point) {\r\n //they passed a point instead of individual components\r\n return this.imageToViewportCoordinates(imageX.x, imageX.y);\r\n }\r\n\r\n if (this.viewer) {\r\n var count = this.viewer.world.getItemCount();\r\n if (count > 1) {\r\n $.console.error('[Viewport.imageToViewportCoordinates] is not accurate ' +\r\n 'with multi-image; use TiledImage.imageToViewportCoordinates instead.');\r\n } else if (count === 1) {\r\n // It is better to use TiledImage.viewportToImageCoordinates\r\n // because this._contentBoundsNoRotate can not be relied on\r\n // with clipping.\r\n var item = this.viewer.world.getItemAt(0);\r\n return item.imageToViewportCoordinates(imageX, imageY, true);\r\n }\r\n }\r\n\r\n var point = this._imageToViewportDelta(imageX, imageY);\r\n point.x += this._contentBoundsNoRotate.x;\r\n point.y += this._contentBoundsNoRotate.y;\r\n return point;\r\n },\r\n\r\n /**\r\n * Translates from a rectangle which describes a portion of the image in\r\n * pixel coordinates to OpenSeadragon viewport rectangle coordinates.\r\n * This method can be called either by passing X,Y,width,height or an\r\n * OpenSeadragon.Rect\r\n * Note: not accurate with multi-image; use TiledImage.imageToViewportRectangle instead.\r\n * @function\r\n * @param {(OpenSeadragon.Rect | Number)} imageX the rectangle or the X\r\n * coordinate of the top left corner of the rectangle in image coordinate system.\r\n * @param {Number} [imageY] the Y coordinate of the top left corner of the rectangle\r\n * in image coordinate system.\r\n * @param {Number} [pixelWidth] the width in pixel of the rectangle.\r\n * @param {Number} [pixelHeight] the height in pixel of the rectangle.\r\n * @returns {OpenSeadragon.Rect} This image's bounds in viewport coordinates\r\n */\r\n imageToViewportRectangle: function(imageX, imageY, pixelWidth, pixelHeight) {\r\n var rect = imageX;\r\n if (!(rect instanceof $.Rect)) {\r\n //they passed individual components instead of a rectangle\r\n rect = new $.Rect(imageX, imageY, pixelWidth, pixelHeight);\r\n }\r\n\r\n if (this.viewer) {\r\n var count = this.viewer.world.getItemCount();\r\n if (count > 1) {\r\n $.console.error('[Viewport.imageToViewportRectangle] is not accurate ' +\r\n 'with multi-image; use TiledImage.imageToViewportRectangle instead.');\r\n } else if (count === 1) {\r\n // It is better to use TiledImage.imageToViewportRectangle\r\n // because this._contentBoundsNoRotate can not be relied on\r\n // with clipping.\r\n var item = this.viewer.world.getItemAt(0);\r\n return item.imageToViewportRectangle(\r\n imageX, imageY, pixelWidth, pixelHeight, true);\r\n }\r\n }\r\n\r\n var coordA = this.imageToViewportCoordinates(rect.x, rect.y);\r\n var coordB = this._imageToViewportDelta(rect.width, rect.height);\r\n return new $.Rect(\r\n coordA.x,\r\n coordA.y,\r\n coordB.x,\r\n coordB.y,\r\n rect.degrees\r\n );\r\n },\r\n\r\n /**\r\n * Translates from a rectangle which describes a portion of\r\n * the viewport in point coordinates to image rectangle coordinates.\r\n * This method can be called either by passing X,Y,width,height or an\r\n * OpenSeadragon.Rect\r\n * Note: not accurate with multi-image; use TiledImage.viewportToImageRectangle instead.\r\n * @function\r\n * @param {(OpenSeadragon.Rect | Number)} viewerX either a rectangle or\r\n * the X coordinate of the top left corner of the rectangle in viewport\r\n * coordinate system.\r\n * @param {Number} [viewerY] the Y coordinate of the top left corner of the rectangle\r\n * in viewport coordinate system.\r\n * @param {Number} [pointWidth] the width of the rectangle in viewport coordinate system.\r\n * @param {Number} [pointHeight] the height of the rectangle in viewport coordinate system.\r\n */\r\n viewportToImageRectangle: function(viewerX, viewerY, pointWidth, pointHeight) {\r\n var rect = viewerX;\r\n if (!(rect instanceof $.Rect)) {\r\n //they passed individual components instead of a rectangle\r\n rect = new $.Rect(viewerX, viewerY, pointWidth, pointHeight);\r\n }\r\n\r\n if (this.viewer) {\r\n var count = this.viewer.world.getItemCount();\r\n if (count > 1) {\r\n $.console.error('[Viewport.viewportToImageRectangle] is not accurate ' +\r\n 'with multi-image; use TiledImage.viewportToImageRectangle instead.');\r\n } else if (count === 1) {\r\n // It is better to use TiledImage.viewportToImageCoordinates\r\n // because this._contentBoundsNoRotate can not be relied on\r\n // with clipping.\r\n var item = this.viewer.world.getItemAt(0);\r\n return item.viewportToImageRectangle(\r\n viewerX, viewerY, pointWidth, pointHeight, true);\r\n }\r\n }\r\n\r\n var coordA = this.viewportToImageCoordinates(rect.x, rect.y);\r\n var coordB = this._viewportToImageDelta(rect.width, rect.height);\r\n return new $.Rect(\r\n coordA.x,\r\n coordA.y,\r\n coordB.x,\r\n coordB.y,\r\n rect.degrees\r\n );\r\n },\r\n\r\n /**\r\n * Convert pixel coordinates relative to the viewer element to image\r\n * coordinates.\r\n * Note: not accurate with multi-image.\r\n * @param {OpenSeadragon.Point} pixel\r\n * @returns {OpenSeadragon.Point}\r\n */\r\n viewerElementToImageCoordinates: function( pixel ) {\r\n var point = this.pointFromPixel( pixel, true );\r\n return this.viewportToImageCoordinates( point );\r\n },\r\n\r\n /**\r\n * Convert pixel coordinates relative to the image to\r\n * viewer element coordinates.\r\n * Note: not accurate with multi-image.\r\n * @param {OpenSeadragon.Point} pixel\r\n * @returns {OpenSeadragon.Point}\r\n */\r\n imageToViewerElementCoordinates: function( pixel ) {\r\n var point = this.imageToViewportCoordinates( pixel );\r\n return this.pixelFromPoint( point, true );\r\n },\r\n\r\n /**\r\n * Convert pixel coordinates relative to the window to image coordinates.\r\n * Note: not accurate with multi-image.\r\n * @param {OpenSeadragon.Point} pixel\r\n * @returns {OpenSeadragon.Point}\r\n */\r\n windowToImageCoordinates: function(pixel) {\r\n $.console.assert(this.viewer,\r\n \"[Viewport.windowToImageCoordinates] the viewport must have a viewer.\");\r\n var viewerCoordinates = pixel.minus(\r\n $.getElementPosition(this.viewer.element));\r\n return this.viewerElementToImageCoordinates(viewerCoordinates);\r\n },\r\n\r\n /**\r\n * Convert image coordinates to pixel coordinates relative to the window.\r\n * Note: not accurate with multi-image.\r\n * @param {OpenSeadragon.Point} pixel\r\n * @returns {OpenSeadragon.Point}\r\n */\r\n imageToWindowCoordinates: function(pixel) {\r\n $.console.assert(this.viewer,\r\n \"[Viewport.imageToWindowCoordinates] the viewport must have a viewer.\");\r\n var viewerCoordinates = this.imageToViewerElementCoordinates(pixel);\r\n return viewerCoordinates.plus(\r\n $.getElementPosition(this.viewer.element));\r\n },\r\n\r\n /**\r\n * Convert pixel coordinates relative to the viewer element to viewport\r\n * coordinates.\r\n * @param {OpenSeadragon.Point} pixel\r\n * @returns {OpenSeadragon.Point}\r\n */\r\n viewerElementToViewportCoordinates: function( pixel ) {\r\n return this.pointFromPixel( pixel, true );\r\n },\r\n\r\n /**\r\n * Convert viewport coordinates to pixel coordinates relative to the\r\n * viewer element.\r\n * @param {OpenSeadragon.Point} point\r\n * @returns {OpenSeadragon.Point}\r\n */\r\n viewportToViewerElementCoordinates: function( point ) {\r\n return this.pixelFromPoint( point, true );\r\n },\r\n\r\n /**\r\n * Convert a rectangle in pixel coordinates relative to the viewer element\r\n * to viewport coordinates.\r\n * @param {OpenSeadragon.Rect} rectangle the rectangle to convert\r\n * @returns {OpenSeadragon.Rect} the converted rectangle\r\n */\r\n viewerElementToViewportRectangle: function(rectangle) {\r\n return $.Rect.fromSummits(\r\n this.pointFromPixel(rectangle.getTopLeft(), true),\r\n this.pointFromPixel(rectangle.getTopRight(), true),\r\n this.pointFromPixel(rectangle.getBottomLeft(), true)\r\n );\r\n },\r\n\r\n /**\r\n * Convert a rectangle in viewport coordinates to pixel coordinates relative\r\n * to the viewer element.\r\n * @param {OpenSeadragon.Rect} rectangle the rectangle to convert\r\n * @returns {OpenSeadragon.Rect} the converted rectangle\r\n */\r\n viewportToViewerElementRectangle: function(rectangle) {\r\n return $.Rect.fromSummits(\r\n this.pixelFromPoint(rectangle.getTopLeft(), true),\r\n this.pixelFromPoint(rectangle.getTopRight(), true),\r\n this.pixelFromPoint(rectangle.getBottomLeft(), true)\r\n );\r\n },\r\n\r\n /**\r\n * Convert pixel coordinates relative to the window to viewport coordinates.\r\n * @param {OpenSeadragon.Point} pixel\r\n * @returns {OpenSeadragon.Point}\r\n */\r\n windowToViewportCoordinates: function(pixel) {\r\n $.console.assert(this.viewer,\r\n \"[Viewport.windowToViewportCoordinates] the viewport must have a viewer.\");\r\n var viewerCoordinates = pixel.minus(\r\n $.getElementPosition(this.viewer.element));\r\n return this.viewerElementToViewportCoordinates(viewerCoordinates);\r\n },\r\n\r\n /**\r\n * Convert viewport coordinates to pixel coordinates relative to the window.\r\n * @param {OpenSeadragon.Point} point\r\n * @returns {OpenSeadragon.Point}\r\n */\r\n viewportToWindowCoordinates: function(point) {\r\n $.console.assert(this.viewer,\r\n \"[Viewport.viewportToWindowCoordinates] the viewport must have a viewer.\");\r\n var viewerCoordinates = this.viewportToViewerElementCoordinates(point);\r\n return viewerCoordinates.plus(\r\n $.getElementPosition(this.viewer.element));\r\n },\r\n\r\n /**\r\n * Convert a viewport zoom to an image zoom.\r\n * Image zoom: ratio of the original image size to displayed image size.\r\n * 1 means original image size, 0.5 half size...\r\n * Viewport zoom: ratio of the displayed image's width to viewport's width.\r\n * 1 means identical width, 2 means image's width is twice the viewport's width...\r\n * Note: not accurate with multi-image.\r\n * @function\r\n * @param {Number} viewportZoom The viewport zoom\r\n * target zoom.\r\n * @returns {Number} imageZoom The image zoom\r\n */\r\n viewportToImageZoom: function(viewportZoom) {\r\n if (this.viewer) {\r\n var count = this.viewer.world.getItemCount();\r\n if (count > 1) {\r\n $.console.error('[Viewport.viewportToImageZoom] is not ' +\r\n 'accurate with multi-image.');\r\n } else if (count === 1) {\r\n // It is better to use TiledImage.viewportToImageZoom\r\n // because this._contentBoundsNoRotate can not be relied on\r\n // with clipping.\r\n var item = this.viewer.world.getItemAt(0);\r\n return item.viewportToImageZoom(viewportZoom);\r\n }\r\n }\r\n\r\n var imageWidth = this._contentSizeNoRotate.x;\r\n var containerWidth = this._containerInnerSize.x;\r\n var scale = this._contentBoundsNoRotate.width;\r\n var viewportToImageZoomRatio = (containerWidth / imageWidth) * scale;\r\n return viewportZoom * viewportToImageZoomRatio;\r\n },\r\n\r\n /**\r\n * Convert an image zoom to a viewport zoom.\r\n * Image zoom: ratio of the original image size to displayed image size.\r\n * 1 means original image size, 0.5 half size...\r\n * Viewport zoom: ratio of the displayed image's width to viewport's width.\r\n * 1 means identical width, 2 means image's width is twice the viewport's width...\r\n * Note: not accurate with multi-image.\r\n * @function\r\n * @param {Number} imageZoom The image zoom\r\n * target zoom.\r\n * @returns {Number} viewportZoom The viewport zoom\r\n */\r\n imageToViewportZoom: function(imageZoom) {\r\n if (this.viewer) {\r\n var count = this.viewer.world.getItemCount();\r\n if (count > 1) {\r\n $.console.error('[Viewport.imageToViewportZoom] is not accurate ' +\r\n 'with multi-image.');\r\n } else if (count === 1) {\r\n // It is better to use TiledImage.imageToViewportZoom\r\n // because this._contentBoundsNoRotate can not be relied on\r\n // with clipping.\r\n var item = this.viewer.world.getItemAt(0);\r\n return item.imageToViewportZoom(imageZoom);\r\n }\r\n }\r\n\r\n var imageWidth = this._contentSizeNoRotate.x;\r\n var containerWidth = this._containerInnerSize.x;\r\n var scale = this._contentBoundsNoRotate.width;\r\n var viewportToImageZoomRatio = (imageWidth / containerWidth) / scale;\r\n return imageZoom * viewportToImageZoomRatio;\r\n },\r\n\r\n /**\r\n * Toggles flip state and demands a new drawing on navigator and viewer objects.\r\n * @function\r\n * @return {OpenSeadragon.Viewport} Chainable.\r\n */\r\n toggleFlip: function() {\r\n this.setFlip(!this.getFlip());\r\n return this;\r\n },\r\n\r\n /**\r\n * Gets flip state stored on viewport.\r\n * @function\r\n * @return {Boolean} Flip state.\r\n */\r\n getFlip: function() {\r\n return this.flipped;\r\n },\r\n\r\n /**\r\n * Sets flip state according to the state input argument.\r\n * @function\r\n * @param {Boolean} state - Flip state to set.\r\n * @return {OpenSeadragon.Viewport} Chainable.\r\n */\r\n setFlip: function( state ) {\r\n if ( this.flipped === state ) {\r\n return this;\r\n }\r\n\r\n this.flipped = state;\r\n if(this.viewer.navigator){\r\n this.viewer.navigator.setFlip(this.getFlip());\r\n }\r\n this.viewer.forceRedraw();\r\n\r\n /**\r\n * Raised when flip state has been changed.\r\n *\r\n * @event flip\r\n * @memberof OpenSeadragon.Viewer\r\n * @type {object}\r\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.\r\n * @property {Number} flipped - The flip state after this change.\r\n * @property {?Object} userData - Arbitrary subscriber-defined object.\r\n */\r\n this.viewer.raiseEvent('flip', {\"flipped\": state});\r\n return this;\r\n }\r\n\r\n};\r\n\r\n}( OpenSeadragon ));\r\n\r\n/*\r\n * OpenSeadragon - TiledImage\r\n *\r\n * Copyright (C) 2009 CodePlex Foundation\r\n * Copyright (C) 2010-2013 OpenSeadragon contributors\r\n *\r\n * Redistribution and use in source and binary forms, with or without\r\n * modification, are permitted provided that the following conditions are\r\n * met:\r\n *\r\n * - Redistributions of source code must retain the above copyright notice,\r\n * this list of conditions and the following disclaimer.\r\n *\r\n * - Redistributions in binary form must reproduce the above copyright\r\n * notice, this list of conditions and the following disclaimer in the\r\n * documentation and/or other materials provided with the distribution.\r\n *\r\n * - Neither the name of CodePlex Foundation nor the names of its\r\n * contributors may be used to endorse or promote products derived from\r\n * this software without specific prior written permission.\r\n *\r\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\r\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\r\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\r\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\r\n * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\r\n * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\r\n * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\r\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\r\n * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r\n * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r\n */\r\n\r\n(function( $ ){\r\n\r\n/**\r\n * You shouldn't have to create a TiledImage instance directly; get it asynchronously by\r\n * using {@link OpenSeadragon.Viewer#open} or {@link OpenSeadragon.Viewer#addTiledImage} instead.\r\n * @class TiledImage\r\n * @memberof OpenSeadragon\r\n * @extends OpenSeadragon.EventSource\r\n * @classdesc Handles rendering of tiles for an {@link OpenSeadragon.Viewer}.\r\n * A new instance is created for each TileSource opened.\r\n * @param {Object} options - Configuration for this TiledImage.\r\n * @param {OpenSeadragon.TileSource} options.source - The TileSource that defines this TiledImage.\r\n * @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this TiledImage.\r\n * @param {OpenSeadragon.TileCache} options.tileCache - The TileCache for this TiledImage to use.\r\n * @param {OpenSeadragon.Drawer} options.drawer - The Drawer for this TiledImage to draw onto.\r\n * @param {OpenSeadragon.ImageLoader} options.imageLoader - The ImageLoader for this TiledImage to use.\r\n * @param {Number} [options.x=0] - Left position, in viewport coordinates.\r\n * @param {Number} [options.y=0] - Top position, in viewport coordinates.\r\n * @param {Number} [options.width=1] - Width, in viewport coordinates.\r\n * @param {Number} [options.height] - Height, in viewport coordinates.\r\n * @param {OpenSeadragon.Rect} [options.fitBounds] The bounds in viewport coordinates\r\n * to fit the image into. If specified, x, y, width and height get ignored.\r\n * @param {OpenSeadragon.Placement} [options.fitBoundsPlacement=OpenSeadragon.Placement.CENTER]\r\n * How to anchor the image in the bounds if options.fitBounds is set.\r\n * @param {OpenSeadragon.Rect} [options.clip] - An area, in image pixels, to clip to\r\n * (portions of the image outside of this area will not be visible). Only works on\r\n * browsers that support the HTML5 canvas.\r\n * @param {Number} [options.springStiffness] - See {@link OpenSeadragon.Options}.\r\n * @param {Boolean} [options.animationTime] - See {@link OpenSeadragon.Options}.\r\n * @param {Number} [options.minZoomImageRatio] - See {@link OpenSeadragon.Options}.\r\n * @param {Boolean} [options.wrapHorizontal] - See {@link OpenSeadragon.Options}.\r\n * @param {Boolean} [options.wrapVertical] - See {@link OpenSeadragon.Options}.\r\n * @param {Boolean} [options.immediateRender] - See {@link OpenSeadragon.Options}.\r\n * @param {Number} [options.blendTime] - See {@link OpenSeadragon.Options}.\r\n * @param {Boolean} [options.alwaysBlend] - See {@link OpenSeadragon.Options}.\r\n * @param {Number} [options.minPixelRatio] - See {@link OpenSeadragon.Options}.\r\n * @param {Number} [options.smoothTileEdgesMinZoom] - See {@link OpenSeadragon.Options}.\r\n * @param {Boolean} [options.iOSDevice] - See {@link OpenSeadragon.Options}.\r\n * @param {Number} [options.opacity=1] - Set to draw at proportional opacity. If zero, images will not draw.\r\n * @param {Boolean} [options.preload=false] - Set true to load even when the image is hidden by zero opacity.\r\n * @param {String} [options.compositeOperation] - How the image is composited onto other images; see compositeOperation in {@link OpenSeadragon.Options} for possible values.\r\n * @param {Boolean} [options.debugMode] - See {@link OpenSeadragon.Options}.\r\n * @param {String|CanvasGradient|CanvasPattern|Function} [options.placeholderFillStyle] - See {@link OpenSeadragon.Options}.\r\n * @param {String|Boolean} [options.crossOriginPolicy] - See {@link OpenSeadragon.Options}.\r\n * @param {Boolean} [options.ajaxWithCredentials] - See {@link OpenSeadragon.Options}.\r\n * @param {Boolean} [options.loadTilesWithAjax]\r\n * Whether to load tile data using AJAX requests.\r\n * Defaults to the setting in {@link OpenSeadragon.Options}.\r\n * @param {Object} [options.ajaxHeaders={}]\r\n * A set of headers to include when making tile AJAX requests.\r\n */\r\n$.TiledImage = function( options ) {\r\n var _this = this;\r\n\r\n $.console.assert( options.tileCache, \"[TiledImage] options.tileCache is required\" );\r\n $.console.assert( options.drawer, \"[TiledImage] options.drawer is required\" );\r\n $.console.assert( options.viewer, \"[TiledImage] options.viewer is required\" );\r\n $.console.assert( options.imageLoader, \"[TiledImage] options.imageLoader is required\" );\r\n $.console.assert( options.source, \"[TiledImage] options.source is required\" );\r\n $.console.assert(!options.clip || options.clip instanceof $.Rect,\r\n \"[TiledImage] options.clip must be an OpenSeadragon.Rect if present\");\r\n\r\n $.EventSource.call( this );\r\n\r\n this._tileCache = options.tileCache;\r\n delete options.tileCache;\r\n\r\n this._drawer = options.drawer;\r\n delete options.drawer;\r\n\r\n this._imageLoader = options.imageLoader;\r\n delete options.imageLoader;\r\n\r\n if (options.clip instanceof $.Rect) {\r\n this._clip = options.clip.clone();\r\n }\r\n\r\n delete options.clip;\r\n\r\n var x = options.x || 0;\r\n delete options.x;\r\n var y = options.y || 0;\r\n delete options.y;\r\n\r\n // Ratio of zoomable image height to width.\r\n this.normHeight = options.source.dimensions.y / options.source.dimensions.x;\r\n this.contentAspectX = options.source.dimensions.x / options.source.dimensions.y;\r\n\r\n var scale = 1;\r\n if ( options.width ) {\r\n scale = options.width;\r\n delete options.width;\r\n\r\n if ( options.height ) {\r\n $.console.error( \"specifying both width and height to a tiledImage is not supported\" );\r\n delete options.height;\r\n }\r\n } else if ( options.height ) {\r\n scale = options.height / this.normHeight;\r\n delete options.height;\r\n }\r\n\r\n var fitBounds = options.fitBounds;\r\n delete options.fitBounds;\r\n var fitBoundsPlacement = options.fitBoundsPlacement || OpenSeadragon.Placement.CENTER;\r\n delete options.fitBoundsPlacement;\r\n\r\n var degrees = options.degrees || 0;\r\n delete options.degrees;\r\n\r\n $.extend( true, this, {\r\n\r\n //internal state properties\r\n viewer: null,\r\n tilesMatrix: {}, // A '3d' dictionary [level][x][y] --> Tile.\r\n coverage: {}, // A '3d' dictionary [level][x][y] --> Boolean; shows what areas have been drawn.\r\n loadingCoverage: {}, // A '3d' dictionary [level][x][y] --> Boolean; shows what areas are loaded or are being loaded/blended.\r\n lastDrawn: [], // An unordered list of Tiles drawn last frame.\r\n lastResetTime: 0, // Last time for which the tiledImage was reset.\r\n _midDraw: false, // Is the tiledImage currently updating the viewport?\r\n _needsDraw: true, // Does the tiledImage need to update the viewport again?\r\n _hasOpaqueTile: false, // Do we have even one fully opaque tile?\r\n _tilesLoading: 0, // The number of pending tile requests.\r\n //configurable settings\r\n springStiffness: $.DEFAULT_SETTINGS.springStiffness,\r\n animationTime: $.DEFAULT_SETTINGS.animationTime,\r\n minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio,\r\n wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal,\r\n wrapVertical: $.DEFAULT_SETTINGS.wrapVertical,\r\n immediateRender: $.DEFAULT_SETTINGS.immediateRender,\r\n blendTime: $.DEFAULT_SETTINGS.blendTime,\r\n alwaysBlend: $.DEFAULT_SETTINGS.alwaysBlend,\r\n minPixelRatio: $.DEFAULT_SETTINGS.minPixelRatio,\r\n smoothTileEdgesMinZoom: $.DEFAULT_SETTINGS.smoothTileEdgesMinZoom,\r\n iOSDevice: $.DEFAULT_SETTINGS.iOSDevice,\r\n debugMode: $.DEFAULT_SETTINGS.debugMode,\r\n crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy,\r\n ajaxWithCredentials: $.DEFAULT_SETTINGS.ajaxWithCredentials,\r\n placeholderFillStyle: $.DEFAULT_SETTINGS.placeholderFillStyle,\r\n opacity: $.DEFAULT_SETTINGS.opacity,\r\n preload: $.DEFAULT_SETTINGS.preload,\r\n compositeOperation: $.DEFAULT_SETTINGS.compositeOperation\r\n }, options );\r\n\r\n this._preload = this.preload;\r\n delete this.preload;\r\n\r\n this._fullyLoaded = false;\r\n\r\n this._xSpring = new $.Spring({\r\n initial: x,\r\n springStiffness: this.springStiffness,\r\n animationTime: this.animationTime\r\n });\r\n\r\n this._ySpring = new $.Spring({\r\n initial: y,\r\n springStiffness: this.springStiffness,\r\n animationTime: this.animationTime\r\n });\r\n\r\n this._scaleSpring = new $.Spring({\r\n initial: scale,\r\n springStiffness: this.springStiffness,\r\n animationTime: this.animationTime\r\n });\r\n\r\n this._degreesSpring = new $.Spring({\r\n initial: degrees,\r\n springStiffness: this.springStiffness,\r\n animationTime: this.animationTime\r\n });\r\n\r\n this._updateForScale();\r\n\r\n if (fitBounds) {\r\n this.fitBounds(fitBounds, fitBoundsPlacement, true);\r\n }\r\n\r\n // We need a callback to give image manipulation a chance to happen\r\n this._drawingHandler = function(args) {\r\n /**\r\n * This event is fired just before the tile is drawn giving the application a chance to alter the image.\r\n *\r\n * NOTE: This event is only fired when the drawer is using a <canvas>.\r\n *\r\n * @event tile-drawing\r\n * @memberof OpenSeadragon.Viewer\r\n * @type {object}\r\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.\r\n * @property {OpenSeadragon.Tile} tile - The Tile being drawn.\r\n * @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.\r\n * @property {OpenSeadragon.Tile} context - The HTML canvas context being drawn into.\r\n * @property {OpenSeadragon.Tile} rendered - The HTML canvas context containing the tile imagery.\r\n * @property {?Object} userData - Arbitrary subscriber-defined object.\r\n */\r\n _this.viewer.raiseEvent('tile-drawing', $.extend({\r\n tiledImage: _this\r\n }, args));\r\n };\r\n};\r\n\r\n$.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.TiledImage.prototype */{\r\n /**\r\n * @returns {Boolean} Whether the TiledImage needs to be drawn.\r\n */\r\n needsDraw: function() {\r\n return this._needsDraw;\r\n },\r\n\r\n /**\r\n * @returns {Boolean} Whether all tiles necessary for this TiledImage to draw at the current view have been loaded.\r\n */\r\n getFullyLoaded: function() {\r\n return this._fullyLoaded;\r\n },\r\n\r\n // private\r\n _setFullyLoaded: function(flag) {\r\n if (flag === this._fullyLoaded) {\r\n return;\r\n }\r\n\r\n this._fullyLoaded = flag;\r\n\r\n /**\r\n * Fired when the TiledImage's \"fully loaded\" flag (whether all tiles necessary for this TiledImage\r\n * to draw at the current view have been loaded) changes.\r\n *\r\n * @event fully-loaded-change\r\n * @memberof OpenSeadragon.TiledImage\r\n * @type {object}\r\n * @property {Boolean} fullyLoaded - The new \"fully loaded\" value.\r\n * @property {OpenSeadragon.TiledImage} eventSource - A reference to the TiledImage which raised the event.\r\n * @property {?Object} userData - Arbitrary subscriber-defined object.\r\n */\r\n this.raiseEvent('fully-loaded-change', {\r\n fullyLoaded: this._fullyLoaded\r\n });\r\n },\r\n\r\n /**\r\n * Clears all tiles and triggers an update on the next call to\r\n * {@link OpenSeadragon.TiledImage#update}.\r\n */\r\n reset: function() {\r\n this._tileCache.clearTilesFor(this);\r\n this.lastResetTime = $.now();\r\n this._needsDraw = true;\r\n },\r\n\r\n /**\r\n * Updates the TiledImage's bounds, animating if needed.\r\n * @returns {Boolean} Whether the TiledImage animated.\r\n */\r\n update: function() {\r\n var xUpdated = this._xSpring.update();\r\n var yUpdated = this._ySpring.update();\r\n var scaleUpdated = this._scaleSpring.update();\r\n var degreesUpdated = this._degreesSpring.update();\r\n\r\n if (xUpdated || yUpdated || scaleUpdated || degreesUpdated) {\r\n this._updateForScale();\r\n this._needsDraw = true;\r\n return true;\r\n }\r\n\r\n return false;\r\n },\r\n\r\n /**\r\n * Draws the TiledImage to its Drawer.\r\n */\r\n draw: function() {\r\n if (this.opacity !== 0 || this._preload) {\r\n this._midDraw = true;\r\n this._updateViewport();\r\n this._midDraw = false;\r\n }\r\n // Images with opacity 0 should not need to be drawn in future. this._needsDraw = false is set in this._updateViewport() for other images.\r\n else {\r\n this._needsDraw = false;\r\n }\r\n },\r\n\r\n /**\r\n * Destroy the TiledImage (unload current loaded tiles).\r\n */\r\n destroy: function() {\r\n this.reset();\r\n },\r\n\r\n /**\r\n * Get this TiledImage's bounds in viewport coordinates.\r\n * @param {Boolean} [current=false] - Pass true for the current location;\r\n * false for target location.\r\n * @returns {OpenSeadragon.Rect} This TiledImage's bounds in viewport coordinates.\r\n */\r\n getBounds: function(current) {\r\n return this.getBoundsNoRotate(current)\r\n .rotate(this.getRotation(current), this._getRotationPoint(current));\r\n },\r\n\r\n /**\r\n * Get this TiledImage's bounds in viewport coordinates without taking\r\n * rotation into account.\r\n * @param {Boolean} [current=false] - Pass true for the current location;\r\n * false for target location.\r\n * @returns {OpenSeadragon.Rect} This TiledImage's bounds in viewport coordinates.\r\n */\r\n getBoundsNoRotate: function(current) {\r\n return current ?\r\n new $.Rect(\r\n this._xSpring.current.value,\r\n this._ySpring.current.value,\r\n this._worldWidthCurrent,\r\n this._worldHeightCurrent) :\r\n new $.Rect(\r\n this._xSpring.target.value,\r\n this._ySpring.target.value,\r\n this._worldWidthTarget,\r\n this._worldHeightTarget);\r\n },\r\n\r\n // deprecated\r\n getWorldBounds: function() {\r\n $.console.error('[TiledImage.getWorldBounds] is deprecated; use TiledImage.getBounds instead');\r\n return this.getBounds();\r\n },\r\n\r\n /**\r\n * Get the bounds of the displayed part of the tiled image.\r\n * @param {Boolean} [current=false] Pass true for the current location,\r\n * false for the target location.\r\n * @returns {$.Rect} The clipped bounds in viewport coordinates.\r\n */\r\n getClippedBounds: function(current) {\r\n var bounds = this.getBoundsNoRotate(current);\r\n if (this._clip) {\r\n var worldWidth = current ?\r\n this._worldWidthCurrent : this._worldWidthTarget;\r\n var ratio = worldWidth / this.source.dimensions.x;\r\n var clip = this._clip.times(ratio);\r\n bounds = new $.Rect(\r\n bounds.x + clip.x,\r\n bounds.y + clip.y,\r\n clip.width,\r\n clip.height);\r\n }\r\n return bounds.rotate(this.getRotation(current), this._getRotationPoint(current));\r\n },\r\n\r\n /**\r\n * @returns {OpenSeadragon.Point} This TiledImage's content size, in original pixels.\r\n */\r\n getContentSize: function() {\r\n return new $.Point(this.source.dimensions.x, this.source.dimensions.y);\r\n },\r\n\r\n // private\r\n _viewportToImageDelta: function( viewerX, viewerY, current ) {\r\n var scale = (current ? this._scaleSpring.current.value : this._scaleSpring.target.value);\r\n return new $.Point(viewerX * (this.source.dimensions.x / scale),\r\n viewerY * ((this.source.dimensions.y * this.contentAspectX) / scale));\r\n },\r\n\r\n /**\r\n * Translates from OpenSeadragon viewer coordinate system to image coordinate system.\r\n * This method can be called either by passing X,Y coordinates or an {@link OpenSeadragon.Point}.\r\n * @param {Number|OpenSeadragon.Point} viewerX - The X coordinate or point in viewport coordinate system.\r\n * @param {Number} [viewerY] - The Y coordinate in viewport coordinate system.\r\n * @param {Boolean} [current=false] - Pass true to use the current location; false for target location.\r\n * @return {OpenSeadragon.Point} A point representing the coordinates in the image.\r\n */\r\n viewportToImageCoordinates: function(viewerX, viewerY, current) {\r\n var point;\r\n if (viewerX instanceof $.Point) {\r\n //they passed a point instead of individual components\r\n current = viewerY;\r\n point = viewerX;\r\n } else {\r\n point = new $.Point(viewerX, viewerY);\r\n }\r\n\r\n point = point.rotate(-this.getRotation(current), this._getRotationPoint(current));\r\n return current ?\r\n this._viewportToImageDelta(\r\n point.x - this._xSpring.current.value,\r\n point.y - this._ySpring.current.value) :\r\n this._viewportToImageDelta(\r\n point.x - this._xSpring.target.value,\r\n point.y - this._ySpring.target.value);\r\n },\r\n\r\n // private\r\n _imageToViewportDelta: function( imageX, imageY, current ) {\r\n var scale = (current ? this._scaleSpring.current.value : this._scaleSpring.target.value);\r\n return new $.Point((imageX / this.source.dimensions.x) * scale,\r\n (imageY / this.source.dimensions.y / this.contentAspectX) * scale);\r\n },\r\n\r\n /**\r\n * Translates from image coordinate system to OpenSeadragon viewer coordinate system\r\n * This method can be called either by passing X,Y coordinates or an {@link OpenSeadragon.Point}.\r\n * @param {Number|OpenSeadragon.Point} imageX - The X coordinate or point in image coordinate system.\r\n * @param {Number} [imageY] - The Y coordinate in image coordinate system.\r\n * @param {Boolean} [current=false] - Pass true to use the current location; false for target location.\r\n * @return {OpenSeadragon.Point} A point representing the coordinates in the viewport.\r\n */\r\n imageToViewportCoordinates: function(imageX, imageY, current) {\r\n if (imageX instanceof $.Point) {\r\n //they passed a point instead of individual components\r\n current = imageY;\r\n imageY = imageX.y;\r\n imageX = imageX.x;\r\n }\r\n\r\n var point = this._imageToViewportDelta(imageX, imageY);\r\n if (current) {\r\n point.x += this._xSpring.current.value;\r\n point.y += this._ySpring.current.value;\r\n } else {\r\n point.x += this._xSpring.target.value;\r\n point.y += this._ySpring.target.value;\r\n }\r\n\r\n return point.rotate(this.getRotation(current), this._getRotationPoint(current));\r\n },\r\n\r\n /**\r\n * Translates from a rectangle which describes a portion of the image in\r\n * pixel coordinates to OpenSeadragon viewport rectangle coordinates.\r\n * This method can be called either by passing X,Y,width,height or an {@link OpenSeadragon.Rect}.\r\n * @param {Number|OpenSeadragon.Rect} imageX - The left coordinate or rectangle in image coordinate system.\r\n * @param {Number} [imageY] - The top coordinate in image coordinate system.\r\n * @param {Number} [pixelWidth] - The width in pixel of the rectangle.\r\n * @param {Number} [pixelHeight] - The height in pixel of the rectangle.\r\n * @param {Boolean} [current=false] - Pass true to use the current location; false for target location.\r\n * @return {OpenSeadragon.Rect} A rect representing the coordinates in the viewport.\r\n */\r\n imageToViewportRectangle: function(imageX, imageY, pixelWidth, pixelHeight, current) {\r\n var rect = imageX;\r\n if (rect instanceof $.Rect) {\r\n //they passed a rect instead of individual components\r\n current = imageY;\r\n } else {\r\n rect = new $.Rect(imageX, imageY, pixelWidth, pixelHeight);\r\n }\r\n\r\n var coordA = this.imageToViewportCoordinates(rect.getTopLeft(), current);\r\n var coordB = this._imageToViewportDelta(rect.width, rect.height, current);\r\n\r\n return new $.Rect(\r\n coordA.x,\r\n coordA.y,\r\n coordB.x,\r\n coordB.y,\r\n rect.degrees + this.getRotation(current)\r\n );\r\n },\r\n\r\n /**\r\n * Translates from a rectangle which describes a portion of\r\n * the viewport in point coordinates to image rectangle coordinates.\r\n * This method can be called either by passing X,Y,width,height or an {@link OpenSeadragon.Rect}.\r\n * @param {Number|OpenSeadragon.Rect} viewerX - The left coordinate or rectangle in viewport coordinate system.\r\n * @param {Number} [viewerY] - The top coordinate in viewport coordinate system.\r\n * @param {Number} [pointWidth] - The width in viewport coordinate system.\r\n * @param {Number} [pointHeight] - The height in viewport coordinate system.\r\n * @param {Boolean} [current=false] - Pass true to use the current location; false for target location.\r\n * @return {OpenSeadragon.Rect} A rect representing the coordinates in the image.\r\n */\r\n viewportToImageRectangle: function( viewerX, viewerY, pointWidth, pointHeight, current ) {\r\n var rect = viewerX;\r\n if (viewerX instanceof $.Rect) {\r\n //they passed a rect instead of individual components\r\n current = viewerY;\r\n } else {\r\n rect = new $.Rect(viewerX, viewerY, pointWidth, pointHeight);\r\n }\r\n\r\n var coordA = this.viewportToImageCoordinates(rect.getTopLeft(), current);\r\n var coordB = this._viewportToImageDelta(rect.width, rect.height, current);\r\n\r\n return new $.Rect(\r\n coordA.x,\r\n coordA.y,\r\n coordB.x,\r\n coordB.y,\r\n rect.degrees - this.getRotation(current)\r\n );\r\n },\r\n\r\n /**\r\n * Convert pixel coordinates relative to the viewer element to image\r\n * coordinates.\r\n * @param {OpenSeadragon.Point} pixel\r\n * @returns {OpenSeadragon.Point}\r\n */\r\n viewerElementToImageCoordinates: function( pixel ) {\r\n var point = this.viewport.pointFromPixel( pixel, true );\r\n return this.viewportToImageCoordinates( point );\r\n },\r\n\r\n /**\r\n * Convert pixel coordinates relative to the image to\r\n * viewer element coordinates.\r\n * @param {OpenSeadragon.Point} pixel\r\n * @returns {OpenSeadragon.Point}\r\n */\r\n imageToViewerElementCoordinates: function( pixel ) {\r\n var point = this.imageToViewportCoordinates( pixel );\r\n return this.viewport.pixelFromPoint( point, true );\r\n },\r\n\r\n /**\r\n * Convert pixel coordinates relative to the window to image coordinates.\r\n * @param {OpenSeadragon.Point} pixel\r\n * @returns {OpenSeadragon.Point}\r\n */\r\n windowToImageCoordinates: function( pixel ) {\r\n var viewerCoordinates = pixel.minus(\r\n OpenSeadragon.getElementPosition( this.viewer.element ));\r\n return this.viewerElementToImageCoordinates( viewerCoordinates );\r\n },\r\n\r\n /**\r\n * Convert image coordinates to pixel coordinates relative to the window.\r\n * @param {OpenSeadragon.Point} pixel\r\n * @returns {OpenSeadragon.Point}\r\n */\r\n imageToWindowCoordinates: function( pixel ) {\r\n var viewerCoordinates = this.imageToViewerElementCoordinates( pixel );\r\n return viewerCoordinates.plus(\r\n OpenSeadragon.getElementPosition( this.viewer.element ));\r\n },\r\n\r\n // private\r\n // Convert rectangle in viewport coordinates to this tiled image point\r\n // coordinates (x in [0, 1] and y in [0, aspectRatio])\r\n _viewportToTiledImageRectangle: function(rect) {\r\n var scale = this._scaleSpring.current.value;\r\n rect = rect.rotate(-this.getRotation(true), this._getRotationPoint(true));\r\n return new $.Rect(\r\n (rect.x - this._xSpring.current.value) / scale,\r\n (rect.y - this._ySpring.current.value) / scale,\r\n rect.width / scale,\r\n rect.height / scale,\r\n rect.degrees);\r\n },\r\n\r\n /**\r\n * Convert a viewport zoom to an image zoom.\r\n * Image zoom: ratio of the original image size to displayed image size.\r\n * 1 means original image size, 0.5 half size...\r\n * Viewport zoom: ratio of the displayed image's width to viewport's width.\r\n * 1 means identical width, 2 means image's width is twice the viewport's width...\r\n * @function\r\n * @param {Number} viewportZoom The viewport zoom\r\n * @returns {Number} imageZoom The image zoom\r\n */\r\n viewportToImageZoom: function( viewportZoom ) {\r\n var ratio = this._scaleSpring.current.value *\r\n this.viewport._containerInnerSize.x / this.source.dimensions.x;\r\n return ratio * viewportZoom;\r\n },\r\n\r\n /**\r\n * Convert an image zoom to a viewport zoom.\r\n * Image zoom: ratio of the original image size to displayed image size.\r\n * 1 means original image size, 0.5 half size...\r\n * Viewport zoom: ratio of the displayed image's width to viewport's width.\r\n * 1 means identical width, 2 means image's width is twice the viewport's width...\r\n * Note: not accurate with multi-image.\r\n * @function\r\n * @param {Number} imageZoom The image zoom\r\n * @returns {Number} viewportZoom The viewport zoom\r\n */\r\n imageToViewportZoom: function( imageZoom ) {\r\n var ratio = this._scaleSpring.current.value *\r\n this.viewport._containerInnerSize.x / this.source.dimensions.x;\r\n return imageZoom / ratio;\r\n },\r\n\r\n /**\r\n * Sets the TiledImage's position in the world.\r\n * @param {OpenSeadragon.Point} position - The new position, in viewport coordinates.\r\n * @param {Boolean} [immediately=false] - Whether to animate to the new position or snap immediately.\r\n * @fires OpenSeadragon.TiledImage.event:bounds-change\r\n */\r\n setPosition: function(position, immediately) {\r\n var sameTarget = (this._xSpring.target.value === position.x &&\r\n this._ySpring.target.value === position.y);\r\n\r\n if (immediately) {\r\n if (sameTarget && this._xSpring.current.value === position.x &&\r\n this._ySpring.current.value === position.y) {\r\n return;\r\n }\r\n\r\n this._xSpring.resetTo(position.x);\r\n this._ySpring.resetTo(position.y);\r\n this._needsDraw = true;\r\n } else {\r\n if (sameTarget) {\r\n return;\r\n }\r\n\r\n this._xSpring.springTo(position.x);\r\n this._ySpring.springTo(position.y);\r\n this._needsDraw = true;\r\n }\r\n\r\n if (!sameTarget) {\r\n this._raiseBoundsChange();\r\n }\r\n },\r\n\r\n /**\r\n * Sets the TiledImage's width in the world, adjusting the height to match based on aspect ratio.\r\n * @param {Number} width - The new width, in viewport coordinates.\r\n * @param {Boolean} [immediately=false] - Whether to animate to the new size or snap immediately.\r\n * @fires OpenSeadragon.TiledImage.event:bounds-change\r\n */\r\n setWidth: function(width, immediately) {\r\n this._setScale(width, immediately);\r\n },\r\n\r\n /**\r\n * Sets the TiledImage's height in the world, adjusting the width to match based on aspect ratio.\r\n * @param {Number} height - The new height, in viewport coordinates.\r\n * @param {Boolean} [immediately=false] - Whether to animate to the new size or snap immediately.\r\n * @fires OpenSeadragon.TiledImage.event:bounds-change\r\n */\r\n setHeight: function(height, immediately) {\r\n this._setScale(height / this.normHeight, immediately);\r\n },\r\n\r\n /**\r\n * Positions and scales the TiledImage to fit in the specified bounds.\r\n * Note: this method fires OpenSeadragon.TiledImage.event:bounds-change\r\n * twice\r\n * @param {OpenSeadragon.Rect} bounds The bounds to fit the image into.\r\n * @param {OpenSeadragon.Placement} [anchor=OpenSeadragon.Placement.CENTER]\r\n * How to anchor the image in the bounds.\r\n * @param {Boolean} [immediately=false] Whether to animate to the new size\r\n * or snap immediately.\r\n * @fires OpenSeadragon.TiledImage.event:bounds-change\r\n */\r\n fitBounds: function(bounds, anchor, immediately) {\r\n anchor = anchor || $.Placement.CENTER;\r\n var anchorProperties = $.Placement.properties[anchor];\r\n var aspectRatio = this.contentAspectX;\r\n var xOffset = 0;\r\n var yOffset = 0;\r\n var displayedWidthRatio = 1;\r\n var displayedHeightRatio = 1;\r\n if (this._clip) {\r\n aspectRatio = this._clip.getAspectRatio();\r\n displayedWidthRatio = this._clip.width / this.source.dimensions.x;\r\n displayedHeightRatio = this._clip.height / this.source.dimensions.y;\r\n if (bounds.getAspectRatio() > aspectRatio) {\r\n xOffset = this._clip.x / this._clip.height * bounds.height;\r\n yOffset = this._clip.y / this._clip.height * bounds.height;\r\n } else {\r\n xOffset = this._clip.x / this._clip.width * bounds.width;\r\n yOffset = this._clip.y / this._clip.width * bounds.width;\r\n }\r\n }\r\n\r\n if (bounds.getAspectRatio() > aspectRatio) {\r\n // We will have margins on the X axis\r\n var height = bounds.height / displayedHeightRatio;\r\n var marginLeft = 0;\r\n if (anchorProperties.isHorizontallyCentered) {\r\n marginLeft = (bounds.width - bounds.height * aspectRatio) / 2;\r\n } else if (anchorProperties.isRight) {\r\n marginLeft = bounds.width - bounds.height * aspectRatio;\r\n }\r\n this.setPosition(\r\n new $.Point(bounds.x - xOffset + marginLeft, bounds.y - yOffset),\r\n immediately);\r\n this.setHeight(height, immediately);\r\n } else {\r\n // We will have margins on the Y axis\r\n var width = bounds.width / displayedWidthRatio;\r\n var marginTop = 0;\r\n if (anchorProperties.isVerticallyCentered) {\r\n marginTop = (bounds.height - bounds.width / aspectRatio) / 2;\r\n } else if (anchorProperties.isBottom) {\r\n marginTop = bounds.height - bounds.width / aspectRatio;\r\n }\r\n this.setPosition(\r\n new $.Point(bounds.x - xOffset, bounds.y - yOffset + marginTop),\r\n immediately);\r\n this.setWidth(width, immediately);\r\n }\r\n },\r\n\r\n /**\r\n * @returns {OpenSeadragon.Rect|null} The TiledImage's current clip rectangle,\r\n * in image pixels, or null if none.\r\n */\r\n getClip: function() {\r\n if (this._clip) {\r\n return this._clip.clone();\r\n }\r\n\r\n return null;\r\n },\r\n\r\n /**\r\n * @param {OpenSeadragon.Rect|null} newClip - An area, in image pixels, to clip to\r\n * (portions of the image outside of this area will not be visible). Only works on\r\n * browsers that support the HTML5 canvas.\r\n * @fires OpenSeadragon.TiledImage.event:clip-change\r\n */\r\n setClip: function(newClip) {\r\n $.console.assert(!newClip || newClip instanceof $.Rect,\r\n \"[TiledImage.setClip] newClip must be an OpenSeadragon.Rect or null\");\r\n\r\n if (newClip instanceof $.Rect) {\r\n this._clip = newClip.clone();\r\n } else {\r\n this._clip = null;\r\n }\r\n\r\n this._needsDraw = true;\r\n /**\r\n * Raised when the TiledImage's clip is changed.\r\n * @event clip-change\r\n * @memberOf OpenSeadragon.TiledImage\r\n * @type {object}\r\n * @property {OpenSeadragon.TiledImage} eventSource - A reference to the\r\n * TiledImage which raised the event.\r\n * @property {?Object} userData - Arbitrary subscriber-defined object.\r\n */\r\n this.raiseEvent('clip-change');\r\n },\r\n\r\n /**\r\n * @returns {Number} The TiledImage's current opacity.\r\n */\r\n getOpacity: function() {\r\n return this.opacity;\r\n },\r\n\r\n /**\r\n * @param {Number} opacity Opacity the tiled image should be drawn at.\r\n * @fires OpenSeadragon.TiledImage.event:opacity-change\r\n */\r\n setOpacity: function(opacity) {\r\n if (opacity === this.opacity) {\r\n return;\r\n }\r\n\r\n this.opacity = opacity;\r\n this._needsDraw = true;\r\n /**\r\n * Raised when the TiledImage's opacity is changed.\r\n * @event opacity-change\r\n * @memberOf OpenSeadragon.TiledImage\r\n * @type {object}\r\n * @property {Number} opacity - The new opacity value.\r\n * @property {OpenSeadragon.TiledImage} eventSource - A reference to the\r\n * TiledImage which raised the event.\r\n * @property {?Object} userData - Arbitrary subscriber-defined object.\r\n */\r\n this.raiseEvent('opacity-change', {\r\n opacity: this.opacity\r\n });\r\n },\r\n\r\n /**\r\n * @returns {Boolean} whether the tiledImage can load its tiles even when it has zero opacity.\r\n */\r\n getPreload: function() {\r\n return this._preload;\r\n },\r\n\r\n /**\r\n * Set true to load even when hidden. Set false to block loading when hidden.\r\n */\r\n setPreload: function(preload) {\r\n this._preload = !!preload;\r\n this._needsDraw = true;\r\n },\r\n\r\n /**\r\n * Get the rotation of this tiled image in degrees.\r\n * @param {Boolean} [current=false] True for current rotation, false for target.\r\n * @returns {Number} the rotation of this tiled image in degrees.\r\n */\r\n getRotation: function(current) {\r\n return current ?\r\n this._degreesSpring.current.value :\r\n this._degreesSpring.target.value;\r\n },\r\n\r\n /**\r\n * Set the current rotation of this tiled image in degrees.\r\n * @param {Number} degrees the rotation in degrees.\r\n * @param {Boolean} [immediately=false] Whether to animate to the new angle\r\n * or rotate immediately.\r\n * @fires OpenSeadragon.TiledImage.event:bounds-change\r\n */\r\n setRotation: function(degrees, immediately) {\r\n if (this._degreesSpring.target.value === degrees &&\r\n this._degreesSpring.isAtTargetValue()) {\r\n return;\r\n }\r\n if (immediately) {\r\n this._degreesSpring.resetTo(degrees);\r\n } else {\r\n this._degreesSpring.springTo(degrees);\r\n }\r\n this._needsDraw = true;\r\n this._raiseBoundsChange();\r\n },\r\n\r\n /**\r\n * Get the point around which this tiled image is rotated\r\n * @private\r\n * @param {Boolean} current True for current rotation point, false for target.\r\n * @returns {OpenSeadragon.Point}\r\n */\r\n _getRotationPoint: function(current) {\r\n return this.getBoundsNoRotate(current).getCenter();\r\n },\r\n\r\n /**\r\n * @returns {String} The TiledImage's current compositeOperation.\r\n */\r\n getCompositeOperation: function() {\r\n return this.compositeOperation;\r\n },\r\n\r\n /**\r\n * @param {String} compositeOperation the tiled image should be drawn with this globalCompositeOperation.\r\n * @fires OpenSeadragon.TiledImage.event:composite-operation-change\r\n */\r\n setCompositeOperation: function(compositeOperation) {\r\n if (compositeOperation === this.compositeOperation) {\r\n return;\r\n }\r\n\r\n this.compositeOperation = compositeOperation;\r\n this._needsDraw = true;\r\n /**\r\n * Raised when the TiledImage's opacity is changed.\r\n * @event composite-operation-change\r\n * @memberOf OpenSeadragon.TiledImage\r\n * @type {object}\r\n * @property {String} compositeOperation - The new compositeOperation value.\r\n * @property {OpenSeadragon.TiledImage} eventSource - A reference to the\r\n * TiledImage which raised the event.\r\n * @property {?Object} userData - Arbitrary subscriber-defined object.\r\n */\r\n this.raiseEvent('composite-operation-change', {\r\n compositeOperation: this.compositeOperation\r\n });\r\n },\r\n\r\n // private\r\n _setScale: function(scale, immediately) {\r\n var sameTarget = (this._scaleSpring.target.value === scale);\r\n if (immediately) {\r\n if (sameTarget && this._scaleSpring.current.value === scale) {\r\n return;\r\n }\r\n\r\n this._scaleSpring.resetTo(scale);\r\n this._updateForScale();\r\n this._needsDraw = true;\r\n } else {\r\n if (sameTarget) {\r\n return;\r\n }\r\n\r\n this._scaleSpring.springTo(scale);\r\n this._updateForScale();\r\n this._needsDraw = true;\r\n }\r\n\r\n if (!sameTarget) {\r\n this._raiseBoundsChange();\r\n }\r\n },\r\n\r\n // private\r\n _updateForScale: function() {\r\n this._worldWidthTarget = this._scaleSpring.target.value;\r\n this._worldHeightTarget = this.normHeight * this._scaleSpring.target.value;\r\n this._worldWidthCurrent = this._scaleSpring.current.value;\r\n this._worldHeightCurrent = this.normHeight * this._scaleSpring.current.value;\r\n },\r\n\r\n // private\r\n _raiseBoundsChange: function() {\r\n /**\r\n * Raised when the TiledImage's bounds are changed.\r\n * Note that this event is triggered only when the animation target is changed;\r\n * not for every frame of animation.\r\n * @event bounds-change\r\n * @memberOf OpenSeadragon.TiledImage\r\n * @type {object}\r\n * @property {OpenSeadragon.TiledImage} eventSource - A reference to the\r\n * TiledImage which raised the event.\r\n * @property {?Object} userData - Arbitrary subscriber-defined object.\r\n */\r\n this.raiseEvent('bounds-change');\r\n },\r\n\r\n // private\r\n _isBottomItem: function() {\r\n return this.viewer.world.getItemAt(0) === this;\r\n },\r\n\r\n // private\r\n _getLevelsInterval: function() {\r\n var lowestLevel = Math.max(\r\n this.source.minLevel,\r\n Math.floor(Math.log(this.minZoomImageRatio) / Math.log(2))\r\n );\r\n var currentZeroRatio = this.viewport.deltaPixelsFromPointsNoRotate(\r\n this.source.getPixelRatio(0), true).x *\r\n this._scaleSpring.current.value;\r\n var highestLevel = Math.min(\r\n Math.abs(this.source.maxLevel),\r\n Math.abs(Math.floor(\r\n Math.log(currentZeroRatio / this.minPixelRatio) / Math.log(2)\r\n ))\r\n );\r\n\r\n // Calculations for the interval of levels to draw\r\n // can return invalid intervals; fix that here if necessary\r\n lowestLevel = Math.min(lowestLevel, highestLevel);\r\n return {\r\n lowestLevel: lowestLevel,\r\n highestLevel: highestLevel\r\n };\r\n },\r\n\r\n /**\r\n * @private\r\n * @inner\r\n * Pretty much every other line in this needs to be documented so it's clear\r\n * how each piece of this routine contributes to the drawing process. That's\r\n * why there are so many TODO's inside this function.\r\n */\r\n _updateViewport: function() {\r\n this._needsDraw = false;\r\n this._tilesLoading = 0;\r\n this.loadingCoverage = {};\r\n\r\n // Reset tile's internal drawn state\r\n while (this.lastDrawn.length > 0) {\r\n var tile = this.lastDrawn.pop();\r\n tile.beingDrawn = false;\r\n }\r\n\r\n var viewport = this.viewport;\r\n var drawArea = this._viewportToTiledImageRectangle(\r\n viewport.getBoundsWithMargins(true));\r\n\r\n if (!this.wrapHorizontal && !this.wrapVertical) {\r\n var tiledImageBounds = this._viewportToTiledImageRectangle(\r\n this.getClippedBounds(true));\r\n drawArea = drawArea.intersection(tiledImageBounds);\r\n if (drawArea === null) {\r\n return;\r\n }\r\n }\r\n\r\n var levelsInterval = this._getLevelsInterval();\r\n var lowestLevel = levelsInterval.lowestLevel;\r\n var highestLevel = levelsInterval.highestLevel;\r\n var bestTile = null;\r\n var haveDrawn = false;\r\n var currentTime = $.now();\r\n\r\n // Update any level that will be drawn\r\n for (var level = highestLevel; level >= lowestLevel; level--) {\r\n var drawLevel = false;\r\n\r\n //Avoid calculations for draw if we have already drawn this\r\n var currentRenderPixelRatio = viewport.deltaPixelsFromPointsNoRotate(\r\n this.source.getPixelRatio(level),\r\n true\r\n ).x * this._scaleSpring.current.value;\r\n\r\n if (level === lowestLevel ||\r\n (!haveDrawn && currentRenderPixelRatio >= this.minPixelRatio)) {\r\n drawLevel = true;\r\n haveDrawn = true;\r\n } else if (!haveDrawn) {\r\n continue;\r\n }\r\n\r\n //Perform calculations for draw if we haven't drawn this\r\n var targetRenderPixelRatio = viewport.deltaPixelsFromPointsNoRotate(\r\n this.source.getPixelRatio(level),\r\n false\r\n ).x * this._scaleSpring.current.value;\r\n\r\n var targetZeroRatio = viewport.deltaPixelsFromPointsNoRotate(\r\n this.source.getPixelRatio(\r\n Math.max(\r\n this.source.getClosestLevel(),\r\n 0\r\n )\r\n ),\r\n false\r\n ).x * this._scaleSpring.current.value;\r\n\r\n var optimalRatio = this.immediateRender ? 1 : targetZeroRatio;\r\n var levelOpacity = Math.min(1, (currentRenderPixelRatio - 0.5) / 0.5);\r\n var levelVisibility = optimalRatio / Math.abs(\r\n optimalRatio - targetRenderPixelRatio\r\n );\r\n\r\n // Update the level and keep track of 'best' tile to load\r\n bestTile = updateLevel(\r\n this,\r\n haveDrawn,\r\n drawLevel,\r\n level,\r\n levelOpacity,\r\n levelVisibility,\r\n drawArea,\r\n currentTime,\r\n bestTile\r\n );\r\n\r\n // Stop the loop if lower-res tiles would all be covered by\r\n // already drawn tiles\r\n if (providesCoverage(this.coverage, level)) {\r\n break;\r\n }\r\n }\r\n\r\n // Perform the actual drawing\r\n drawTiles(this, this.lastDrawn);\r\n\r\n // Load the new 'best' tile\r\n if (bestTile && !bestTile.context2D) {\r\n loadTile(this, bestTile, currentTime);\r\n this._needsDraw = true;\r\n this._setFullyLoaded(false);\r\n } else {\r\n this._setFullyLoaded(this._tilesLoading === 0);\r\n }\r\n },\r\n\r\n // private\r\n _getCornerTiles: function(level, topLeftBound, bottomRightBound) {\r\n var leftX;\r\n var rightX;\r\n if (this.wrapHorizontal) {\r\n leftX = $.positiveModulo(topLeftBound.x, 1);\r\n rightX = $.positiveModulo(bottomRightBound.x, 1);\r\n } else {\r\n leftX = Math.max(0, topLeftBound.x);\r\n rightX = Math.min(1, bottomRightBound.x);\r\n }\r\n var topY;\r\n var bottomY;\r\n var aspectRatio = 1 / this.source.aspectRatio;\r\n if (this.wrapVertical) {\r\n topY = $.positiveModulo(topLeftBound.y, aspectRatio);\r\n bottomY = $.positiveModulo(bottomRightBound.y, aspectRatio);\r\n } else {\r\n topY = Math.max(0, topLeftBound.y);\r\n bottomY = Math.min(aspectRatio, bottomRightBound.y);\r\n }\r\n\r\n var topLeftTile = this.source.getTileAtPoint(level, new $.Point(leftX, topY));\r\n var bottomRightTile = this.source.getTileAtPoint(level, new $.Point(rightX, bottomY));\r\n var numTiles = this.source.getNumTiles(level);\r\n\r\n if (this.wrapHorizontal) {\r\n topLeftTile.x += numTiles.x * Math.floor(topLeftBound.x);\r\n bottomRightTile.x += numTiles.x * Math.floor(bottomRightBound.x);\r\n }\r\n if (this.wrapVertical) {\r\n topLeftTile.y += numTiles.y * Math.floor(topLeftBound.y / aspectRatio);\r\n bottomRightTile.y += numTiles.y * Math.floor(bottomRightBound.y / aspectRatio);\r\n }\r\n\r\n return {\r\n topLeft: topLeftTile,\r\n bottomRight: bottomRightTile,\r\n };\r\n }\r\n});\r\n\r\n/**\r\n * @private\r\n * @inner\r\n * Updates all tiles at a given resolution level.\r\n * @param {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.\r\n * @param {Boolean} haveDrawn\r\n * @param {Boolean} drawLevel\r\n * @param {Number} level\r\n * @param {Number} levelOpacity\r\n * @param {Number} levelVisibility\r\n * @param {OpenSeadragon.Point} viewportTL - The index of the most top-left visible tile.\r\n * @param {OpenSeadragon.Point} viewportBR - The index of the most bottom-right visible tile.\r\n * @param {Number} currentTime\r\n * @param {OpenSeadragon.Tile} best - The current \"best\" tile to draw.\r\n */\r\nfunction updateLevel(tiledImage, haveDrawn, drawLevel, level, levelOpacity,\r\n levelVisibility, drawArea, currentTime, best) {\r\n\r\n var topLeftBound = drawArea.getBoundingBox().getTopLeft();\r\n var bottomRightBound = drawArea.getBoundingBox().getBottomRight();\r\n\r\n if (tiledImage.viewer) {\r\n /**\r\n * - Needs documentation -\r\n *\r\n * @event update-level\r\n * @memberof OpenSeadragon.Viewer\r\n * @type {object}\r\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.\r\n * @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.\r\n * @property {Object} havedrawn\r\n * @property {Object} level\r\n * @property {Object} opacity\r\n * @property {Object} visibility\r\n * @property {OpenSeadragon.Rect} drawArea\r\n * @property {Object} topleft deprecated, use drawArea instead\r\n * @property {Object} bottomright deprecated, use drawArea instead\r\n * @property {Object} currenttime\r\n * @property {Object} best\r\n * @property {?Object} userData - Arbitrary subscriber-defined object.\r\n */\r\n tiledImage.viewer.raiseEvent('update-level', {\r\n tiledImage: tiledImage,\r\n havedrawn: haveDrawn,\r\n level: level,\r\n opacity: levelOpacity,\r\n visibility: levelVisibility,\r\n drawArea: drawArea,\r\n topleft: topLeftBound,\r\n bottomright: bottomRightBound,\r\n currenttime: currentTime,\r\n best: best\r\n });\r\n }\r\n\r\n resetCoverage(tiledImage.coverage, level);\r\n resetCoverage(tiledImage.loadingCoverage, level);\r\n\r\n //OK, a new drawing so do your calculations\r\n var cornerTiles = tiledImage._getCornerTiles(level, topLeftBound, bottomRightBound);\r\n var topLeftTile = cornerTiles.topLeft;\r\n var bottomRightTile = cornerTiles.bottomRight;\r\n var numberOfTiles = tiledImage.source.getNumTiles(level);\r\n\r\n var viewportCenter = tiledImage.viewport.pixelFromPoint(\r\n tiledImage.viewport.getCenter());\r\n for (var x = topLeftTile.x; x <= bottomRightTile.x; x++) {\r\n for (var y = topLeftTile.y; y <= bottomRightTile.y; y++) {\r\n\r\n // Optimisation disabled with wrapping because getTileBounds does not\r\n // work correctly with x and y outside of the number of tiles\r\n if (!tiledImage.wrapHorizontal && !tiledImage.wrapVertical) {\r\n var tileBounds = tiledImage.source.getTileBounds(level, x, y);\r\n if (drawArea.intersection(tileBounds) === null) {\r\n // This tile is outside of the viewport, no need to draw it\r\n continue;\r\n }\r\n }\r\n\r\n best = updateTile(\r\n tiledImage,\r\n drawLevel,\r\n haveDrawn,\r\n x, y,\r\n level,\r\n levelOpacity,\r\n levelVisibility,\r\n viewportCenter,\r\n numberOfTiles,\r\n currentTime,\r\n best\r\n );\r\n\r\n }\r\n }\r\n\r\n return best;\r\n}\r\n\r\n/**\r\n * @private\r\n * @inner\r\n * Update a single tile at a particular resolution level.\r\n * @param {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.\r\n * @param {Boolean} haveDrawn\r\n * @param {Boolean} drawLevel\r\n * @param {Number} x\r\n * @param {Number} y\r\n * @param {Number} level\r\n * @param {Number} levelOpacity\r\n * @param {Number} levelVisibility\r\n * @param {OpenSeadragon.Point} viewportCenter\r\n * @param {Number} numberOfTiles\r\n * @param {Number} currentTime\r\n * @param {OpenSeadragon.Tile} best - The current \"best\" tile to draw.\r\n */\r\nfunction updateTile( tiledImage, haveDrawn, drawLevel, x, y, level, levelOpacity, levelVisibility, viewportCenter, numberOfTiles, currentTime, best){\r\n\r\n var tile = getTile(\r\n x, y,\r\n level,\r\n tiledImage,\r\n tiledImage.source,\r\n tiledImage.tilesMatrix,\r\n currentTime,\r\n numberOfTiles,\r\n tiledImage._worldWidthCurrent,\r\n tiledImage._worldHeightCurrent\r\n ),\r\n drawTile = drawLevel;\r\n\r\n if( tiledImage.viewer ){\r\n /**\r\n * - Needs documentation -\r\n *\r\n * @event update-tile\r\n * @memberof OpenSeadragon.Viewer\r\n * @type {object}\r\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.\r\n * @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.\r\n * @property {OpenSeadragon.Tile} tile\r\n * @property {?Object} userData - Arbitrary subscriber-defined object.\r\n */\r\n tiledImage.viewer.raiseEvent( 'update-tile', {\r\n tiledImage: tiledImage,\r\n tile: tile\r\n });\r\n }\r\n\r\n setCoverage( tiledImage.coverage, level, x, y, false );\r\n\r\n var loadingCoverage = tile.loaded || tile.loading || isCovered(tiledImage.loadingCoverage, level, x, y);\r\n setCoverage(tiledImage.loadingCoverage, level, x, y, loadingCoverage);\r\n\r\n if ( !tile.exists ) {\r\n return best;\r\n }\r\n\r\n if ( haveDrawn && !drawTile ) {\r\n if ( isCovered( tiledImage.coverage, level, x, y ) ) {\r\n setCoverage( tiledImage.coverage, level, x, y, true );\r\n } else {\r\n drawTile = true;\r\n }\r\n }\r\n\r\n if ( !drawTile ) {\r\n return best;\r\n }\r\n\r\n positionTile(\r\n tile,\r\n tiledImage.source.tileOverlap,\r\n tiledImage.viewport,\r\n viewportCenter,\r\n levelVisibility,\r\n tiledImage\r\n );\r\n\r\n if (!tile.loaded) {\r\n if (tile.context2D) {\r\n setTileLoaded(tiledImage, tile);\r\n } else {\r\n var imageRecord = tiledImage._tileCache.getImageRecord(tile.cacheKey);\r\n if (imageRecord) {\r\n var image = imageRecord.getImage();\r\n setTileLoaded(tiledImage, tile, image);\r\n }\r\n }\r\n }\r\n\r\n if ( tile.loaded ) {\r\n var needsDraw = blendTile(\r\n tiledImage,\r\n tile,\r\n x, y,\r\n level,\r\n levelOpacity,\r\n currentTime\r\n );\r\n\r\n if ( needsDraw ) {\r\n tiledImage._needsDraw = true;\r\n }\r\n } else if ( tile.loading ) {\r\n // the tile is already in the download queue\r\n tiledImage._tilesLoading++;\r\n } else if (!loadingCoverage) {\r\n best = compareTiles( best, tile );\r\n }\r\n\r\n return best;\r\n}\r\n\r\n/**\r\n * @private\r\n * @inner\r\n * Obtains a tile at the given location.\r\n * @param {Number} x\r\n * @param {Number} y\r\n * @param {Number} level\r\n * @param {OpenSeadragon.TiledImage} tiledImage\r\n * @param {OpenSeadragon.TileSource} tileSource\r\n * @param {Object} tilesMatrix - A '3d' dictionary [level][x][y] --> Tile.\r\n * @param {Number} time\r\n * @param {Number} numTiles\r\n * @param {Number} worldWidth\r\n * @param {Number} worldHeight\r\n * @returns {OpenSeadragon.Tile}\r\n */\r\nfunction getTile(\r\n x, y,\r\n level,\r\n tiledImage,\r\n tileSource,\r\n tilesMatrix,\r\n time,\r\n numTiles,\r\n worldWidth,\r\n worldHeight\r\n) {\r\n var xMod,\r\n yMod,\r\n bounds,\r\n sourceBounds,\r\n exists,\r\n url,\r\n ajaxHeaders,\r\n context2D,\r\n tile;\r\n\r\n if ( !tilesMatrix[ level ] ) {\r\n tilesMatrix[ level ] = {};\r\n }\r\n if ( !tilesMatrix[ level ][ x ] ) {\r\n tilesMatrix[ level ][ x ] = {};\r\n }\r\n\r\n if ( !tilesMatrix[ level ][ x ][ y ] ) {\r\n xMod = ( numTiles.x + ( x % numTiles.x ) ) % numTiles.x;\r\n yMod = ( numTiles.y + ( y % numTiles.y ) ) % numTiles.y;\r\n bounds = tileSource.getTileBounds( level, xMod, yMod );\r\n sourceBounds = tileSource.getTileBounds( level, xMod, yMod, true );\r\n exists = tileSource.tileExists( level, xMod, yMod );\r\n url = tileSource.getTileUrl( level, xMod, yMod );\r\n\r\n // Headers are only applicable if loadTilesWithAjax is set\r\n if (tiledImage.loadTilesWithAjax) {\r\n ajaxHeaders = tileSource.getTileAjaxHeaders( level, xMod, yMod );\r\n // Combine tile AJAX headers with tiled image AJAX headers (if applicable)\r\n if ($.isPlainObject(tiledImage.ajaxHeaders)) {\r\n ajaxHeaders = $.extend({}, tiledImage.ajaxHeaders, ajaxHeaders);\r\n }\r\n } else {\r\n ajaxHeaders = null;\r\n }\r\n\r\n context2D = tileSource.getContext2D ?\r\n tileSource.getContext2D(level, xMod, yMod) : undefined;\r\n\r\n bounds.x += ( x - xMod ) / numTiles.x;\r\n bounds.y += (worldHeight / worldWidth) * (( y - yMod ) / numTiles.y);\r\n\r\n tile = new $.Tile(\r\n level,\r\n x,\r\n y,\r\n bounds,\r\n exists,\r\n url,\r\n context2D,\r\n tiledImage.loadTilesWithAjax,\r\n ajaxHeaders,\r\n sourceBounds\r\n );\r\n\r\n if (xMod === numTiles.x - 1) {\r\n tile.isRightMost = true;\r\n }\r\n\r\n if (yMod === numTiles.y - 1) {\r\n tile.isBottomMost = true;\r\n }\r\n\r\n tilesMatrix[ level ][ x ][ y ] = tile;\r\n }\r\n\r\n tile = tilesMatrix[ level ][ x ][ y ];\r\n tile.lastTouchTime = time;\r\n\r\n return tile;\r\n}\r\n\r\n/**\r\n * @private\r\n * @inner\r\n * Dispatch a job to the ImageLoader to load the Image for a Tile.\r\n * @param {OpenSeadragon.TiledImage} tiledImage\r\n * @param {OpenSeadragon.Tile} tile\r\n * @param {Number} time\r\n */\r\nfunction loadTile( tiledImage, tile, time ) {\r\n tile.loading = true;\r\n var customAjax;\r\n\r\n // Bind tiledImage if filtering Ajax\r\n if ($.isFunction(tiledImage.makeAjaxRequest)) {\r\n customAjax = tiledImage.makeAjaxRequest;\r\n }\r\n\r\n tiledImage._imageLoader.addJob({\r\n src: tile.url,\r\n makeAjaxRequest: customAjax,\r\n loadWithAjax: tile.loadWithAjax,\r\n ajaxHeaders: tile.ajaxHeaders,\r\n crossOriginPolicy: tiledImage.crossOriginPolicy,\r\n ajaxWithCredentials: tiledImage.ajaxWithCredentials,\r\n callback: function( image, errorMsg, tileRequest ){\r\n onTileLoad( tiledImage, tile, time, image, errorMsg, tileRequest );\r\n },\r\n abort: function() {\r\n tile.loading = false;\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * @private\r\n * @inner\r\n * Callback fired when a Tile's Image finished downloading.\r\n * @param {OpenSeadragon.TiledImage} tiledImage\r\n * @param {OpenSeadragon.Tile} tile\r\n * @param {Number} time\r\n * @param {Image} image\r\n * @param {String} errorMsg\r\n * @param {XMLHttpRequest} tileRequest\r\n */\r\nfunction onTileLoad( tiledImage, tile, time, image, errorMsg, tileRequest ) {\r\n if ( !image ) {\r\n $.console.log( \"Tile %s failed to load: %s - error: %s\", tile, tile.url, errorMsg );\r\n /**\r\n * Triggered when a tile fails to load.\r\n *\r\n * @event tile-load-failed\r\n * @memberof OpenSeadragon.Viewer\r\n * @type {object}\r\n * @property {OpenSeadragon.Tile} tile - The tile that failed to load.\r\n * @property {OpenSeadragon.TiledImage} tiledImage - The tiled image the tile belongs to.\r\n * @property {number} time - The time in milliseconds when the tile load began.\r\n * @property {string} message - The error message.\r\n * @property {XMLHttpRequest} tileRequest - The XMLHttpRequest used to load the tile if available.\r\n */\r\n tiledImage.viewer.raiseEvent(\"tile-load-failed\", {\r\n tile: tile,\r\n tiledImage: tiledImage,\r\n time: time,\r\n message: errorMsg,\r\n tileRequest: tileRequest\r\n });\r\n tile.loading = false;\r\n tile.exists = false;\r\n return;\r\n }\r\n\r\n if ( time < tiledImage.lastResetTime ) {\r\n $.console.log( \"Ignoring tile %s loaded before reset: %s\", tile, tile.url );\r\n tile.loading = false;\r\n return;\r\n }\r\n\r\n var finish = function() {\r\n var cutoff = tiledImage.source.getClosestLevel();\r\n setTileLoaded(tiledImage, tile, image, cutoff, tileRequest);\r\n };\r\n\r\n // Check if we're mid-update; this can happen on IE8 because image load events for\r\n // cached images happen immediately there\r\n if ( !tiledImage._midDraw ) {\r\n finish();\r\n } else {\r\n // Wait until after the update, in case caching unloads any tiles\r\n window.setTimeout( finish, 1);\r\n }\r\n}\r\n\r\n/**\r\n * @private\r\n * @inner\r\n * @param {OpenSeadragon.TiledImage} tiledImage\r\n * @param {OpenSeadragon.Tile} tile\r\n * @param {Image} image\r\n * @param {Number} cutoff\r\n */\r\nfunction setTileLoaded(tiledImage, tile, image, cutoff, tileRequest) {\r\n var increment = 0;\r\n\r\n function getCompletionCallback() {\r\n increment++;\r\n return completionCallback;\r\n }\r\n\r\n function completionCallback() {\r\n increment--;\r\n if (increment === 0) {\r\n tile.loading = false;\r\n tile.loaded = true;\r\n if (!tile.context2D) {\r\n tiledImage._tileCache.cacheTile({\r\n image: image,\r\n tile: tile,\r\n cutoff: cutoff,\r\n tiledImage: tiledImage\r\n });\r\n }\r\n tiledImage._needsDraw = true;\r\n }\r\n }\r\n\r\n /**\r\n * Triggered when a tile has just been loaded in memory. That means that the\r\n * image has been downloaded and can be modified before being drawn to the canvas.\r\n *\r\n * @event tile-loaded\r\n * @memberof OpenSeadragon.Viewer\r\n * @type {object}\r\n * @property {Image} image - The image of the tile.\r\n * @property {OpenSeadragon.TiledImage} tiledImage - The tiled image of the loaded tile.\r\n * @property {OpenSeadragon.Tile} tile - The tile which has been loaded.\r\n * @property {XMLHttpRequest} tiledImage - The AJAX request that loaded this tile (if applicable).\r\n * @property {function} getCompletionCallback - A function giving a callback to call\r\n * when the asynchronous processing of the image is done. The image will be\r\n * marked as entirely loaded when the callback has been called once for each\r\n * call to getCompletionCallback.\r\n */\r\n tiledImage.viewer.raiseEvent(\"tile-loaded\", {\r\n tile: tile,\r\n tiledImage: tiledImage,\r\n tileRequest: tileRequest,\r\n image: image,\r\n getCompletionCallback: getCompletionCallback\r\n });\r\n // In case the completion callback is never called, we at least force it once.\r\n getCompletionCallback()();\r\n}\r\n\r\n/**\r\n * @private\r\n * @inner\r\n * @param {OpenSeadragon.Tile} tile\r\n * @param {Boolean} overlap\r\n * @param {OpenSeadragon.Viewport} viewport\r\n * @param {OpenSeadragon.Point} viewportCenter\r\n * @param {Number} levelVisibility\r\n * @param {OpenSeadragon.TiledImage} tiledImage\r\n */\r\nfunction positionTile( tile, overlap, viewport, viewportCenter, levelVisibility, tiledImage ){\r\n var boundsTL = tile.bounds.getTopLeft();\r\n\r\n boundsTL.x *= tiledImage._scaleSpring.current.value;\r\n boundsTL.y *= tiledImage._scaleSpring.current.value;\r\n boundsTL.x += tiledImage._xSpring.current.value;\r\n boundsTL.y += tiledImage._ySpring.current.value;\r\n\r\n var boundsSize = tile.bounds.getSize();\r\n\r\n boundsSize.x *= tiledImage._scaleSpring.current.value;\r\n boundsSize.y *= tiledImage._scaleSpring.current.value;\r\n\r\n var positionC = viewport.pixelFromPointNoRotate(boundsTL, true),\r\n positionT = viewport.pixelFromPointNoRotate(boundsTL, false),\r\n sizeC = viewport.deltaPixelsFromPointsNoRotate(boundsSize, true),\r\n sizeT = viewport.deltaPixelsFromPointsNoRotate(boundsSize, false),\r\n tileCenter = positionT.plus( sizeT.divide( 2 ) ),\r\n tileSquaredDistance = viewportCenter.squaredDistanceTo( tileCenter );\r\n\r\n if ( !overlap ) {\r\n sizeC = sizeC.plus( new $.Point( 1, 1 ) );\r\n }\r\n\r\n if (tile.isRightMost && tiledImage.wrapHorizontal) {\r\n sizeC.x += 0.75; // Otherwise Firefox and Safari show seams\r\n }\r\n\r\n if (tile.isBottomMost && tiledImage.wrapVertical) {\r\n sizeC.y += 0.75; // Otherwise Firefox and Safari show seams\r\n }\r\n\r\n tile.position = positionC;\r\n tile.size = sizeC;\r\n tile.squaredDistance = tileSquaredDistance;\r\n tile.visibility = levelVisibility;\r\n}\r\n\r\n/**\r\n * @private\r\n * @inner\r\n * Updates the opacity of a tile according to the time it has been on screen\r\n * to perform a fade-in.\r\n * Updates coverage once a tile is fully opaque.\r\n * Returns whether the fade-in has completed.\r\n *\r\n * @param {OpenSeadragon.TiledImage} tiledImage\r\n * @param {OpenSeadragon.Tile} tile\r\n * @param {Number} x\r\n * @param {Number} y\r\n * @param {Number} level\r\n * @param {Number} levelOpacity\r\n * @param {Number} currentTime\r\n * @returns {Boolean}\r\n */\r\nfunction blendTile( tiledImage, tile, x, y, level, levelOpacity, currentTime ){\r\n var blendTimeMillis = 1000 * tiledImage.blendTime,\r\n deltaTime,\r\n opacity;\r\n\r\n if ( !tile.blendStart ) {\r\n tile.blendStart = currentTime;\r\n }\r\n\r\n deltaTime = currentTime - tile.blendStart;\r\n opacity = blendTimeMillis ? Math.min( 1, deltaTime / ( blendTimeMillis ) ) : 1;\r\n\r\n if ( tiledImage.alwaysBlend ) {\r\n opacity *= levelOpacity;\r\n }\r\n\r\n tile.opacity = opacity;\r\n\r\n tiledImage.lastDrawn.push( tile );\r\n\r\n if ( opacity == 1 ) {\r\n setCoverage( tiledImage.coverage, level, x, y, true );\r\n tiledImage._hasOpaqueTile = true;\r\n } else if ( deltaTime < blendTimeMillis ) {\r\n return true;\r\n }\r\n\r\n return false;\r\n}\r\n\r\n/**\r\n * @private\r\n * @inner\r\n * Returns true if the given tile provides coverage to lower-level tiles of\r\n * lower resolution representing the same content. If neither x nor y is\r\n * given, returns true if the entire visible level provides coverage.\r\n *\r\n * Note that out-of-bounds tiles provide coverage in this sense, since\r\n * there's no content that they would need to cover. Tiles at non-existent\r\n * levels that are within the image bounds, however, do not.\r\n *\r\n * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean.\r\n * @param {Number} level - The resolution level of the tile.\r\n * @param {Number} x - The X position of the tile.\r\n * @param {Number} y - The Y position of the tile.\r\n * @returns {Boolean}\r\n */\r\nfunction providesCoverage( coverage, level, x, y ) {\r\n var rows,\r\n cols,\r\n i, j;\r\n\r\n if ( !coverage[ level ] ) {\r\n return false;\r\n }\r\n\r\n if ( x === undefined || y === undefined ) {\r\n rows = coverage[ level ];\r\n for ( i in rows ) {\r\n if ( rows.hasOwnProperty( i ) ) {\r\n cols = rows[ i ];\r\n for ( j in cols ) {\r\n if ( cols.hasOwnProperty( j ) && !cols[ j ] ) {\r\n return false;\r\n }\r\n }\r\n }\r\n }\r\n\r\n return true;\r\n }\r\n\r\n return (\r\n coverage[ level ][ x] === undefined ||\r\n coverage[ level ][ x ][ y ] === undefined ||\r\n coverage[ level ][ x ][ y ] === true\r\n );\r\n}\r\n\r\n/**\r\n * @private\r\n * @inner\r\n * Returns true if the given tile is completely covered by higher-level\r\n * tiles of higher resolution representing the same content. If neither x\r\n * nor y is given, returns true if the entire visible level is covered.\r\n *\r\n * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean.\r\n * @param {Number} level - The resolution level of the tile.\r\n * @param {Number} x - The X position of the tile.\r\n * @param {Number} y - The Y position of the tile.\r\n * @returns {Boolean}\r\n */\r\nfunction isCovered( coverage, level, x, y ) {\r\n if ( x === undefined || y === undefined ) {\r\n return providesCoverage( coverage, level + 1 );\r\n } else {\r\n return (\r\n providesCoverage( coverage, level + 1, 2 * x, 2 * y ) &&\r\n providesCoverage( coverage, level + 1, 2 * x, 2 * y + 1 ) &&\r\n providesCoverage( coverage, level + 1, 2 * x + 1, 2 * y ) &&\r\n providesCoverage( coverage, level + 1, 2 * x + 1, 2 * y + 1 )\r\n );\r\n }\r\n}\r\n\r\n/**\r\n * @private\r\n * @inner\r\n * Sets whether the given tile provides coverage or not.\r\n *\r\n * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean.\r\n * @param {Number} level - The resolution level of the tile.\r\n * @param {Number} x - The X position of the tile.\r\n * @param {Number} y - The Y position of the tile.\r\n * @param {Boolean} covers - Whether the tile provides coverage.\r\n */\r\nfunction setCoverage( coverage, level, x, y, covers ) {\r\n if ( !coverage[ level ] ) {\r\n $.console.warn(\r\n \"Setting coverage for a tile before its level's coverage has been reset: %s\",\r\n level\r\n );\r\n return;\r\n }\r\n\r\n if ( !coverage[ level ][ x ] ) {\r\n coverage[ level ][ x ] = {};\r\n }\r\n\r\n coverage[ level ][ x ][ y ] = covers;\r\n}\r\n\r\n/**\r\n * @private\r\n * @inner\r\n * Resets coverage information for the given level. This should be called\r\n * after every draw routine. Note that at the beginning of the next draw\r\n * routine, coverage for every visible tile should be explicitly set.\r\n *\r\n * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean.\r\n * @param {Number} level - The resolution level of tiles to completely reset.\r\n */\r\nfunction resetCoverage( coverage, level ) {\r\n coverage[ level ] = {};\r\n}\r\n\r\n/**\r\n * @private\r\n * @inner\r\n * Determines whether the 'last best' tile for the area is better than the\r\n * tile in question.\r\n *\r\n * @param {OpenSeadragon.Tile} previousBest\r\n * @param {OpenSeadragon.Tile} tile\r\n * @returns {OpenSeadragon.Tile} The new best tile.\r\n */\r\nfunction compareTiles( previousBest, tile ) {\r\n if ( !previousBest ) {\r\n return tile;\r\n }\r\n\r\n if ( tile.visibility > previousBest.visibility ) {\r\n return tile;\r\n } else if ( tile.visibility == previousBest.visibility ) {\r\n if ( tile.squaredDistance < previousBest.squaredDistance ) {\r\n return tile;\r\n }\r\n }\r\n\r\n return previousBest;\r\n}\r\n\r\n/**\r\n * @private\r\n * @inner\r\n * Draws a TiledImage.\r\n * @param {OpenSeadragon.TiledImage} tiledImage\r\n * @param {OpenSeadragon.Tile[]} lastDrawn - An unordered list of Tiles drawn last frame.\r\n */\r\nfunction drawTiles( tiledImage, lastDrawn ) {\r\n if (tiledImage.opacity === 0 || (lastDrawn.length === 0 && !tiledImage.placeholderFillStyle)) {\r\n return;\r\n }\r\n\r\n var tile = lastDrawn[0];\r\n var useSketch;\r\n\r\n if (tile) {\r\n useSketch = tiledImage.opacity < 1 ||\r\n (tiledImage.compositeOperation &&\r\n tiledImage.compositeOperation !== 'source-over') ||\r\n (!tiledImage._isBottomItem() && tile._hasTransparencyChannel());\r\n }\r\n\r\n var sketchScale;\r\n var sketchTranslate;\r\n\r\n var zoom = tiledImage.viewport.getZoom(true);\r\n var imageZoom = tiledImage.viewportToImageZoom(zoom);\r\n\r\n if (lastDrawn.length > 1 &&\r\n imageZoom > tiledImage.smoothTileEdgesMinZoom &&\r\n !tiledImage.iOSDevice &&\r\n tiledImage.getRotation(true) % 360 === 0 && // TODO: support tile edge smoothing with tiled image rotation.\r\n $.supportsCanvas) {\r\n // When zoomed in a lot (>100%) the tile edges are visible.\r\n // So we have to composite them at ~100% and scale them up together.\r\n // Note: Disabled on iOS devices per default as it causes a native crash\r\n useSketch = true;\r\n sketchScale = tile.getScaleForEdgeSmoothing();\r\n sketchTranslate = tile.getTranslationForEdgeSmoothing(sketchScale,\r\n tiledImage._drawer.getCanvasSize(false),\r\n tiledImage._drawer.getCanvasSize(true));\r\n }\r\n\r\n var bounds;\r\n if (useSketch) {\r\n if (!sketchScale) {\r\n // Except when edge smoothing, we only clean the part of the\r\n // sketch canvas we are going to use for performance reasons.\r\n bounds = tiledImage.viewport.viewportToViewerElementRectangle(\r\n tiledImage.getClippedBounds(true))\r\n .getIntegerBoundingBox()\r\n .times($.pixelDensityRatio);\r\n }\r\n tiledImage._drawer._clear(true, bounds);\r\n }\r\n\r\n // When scaling, we must rotate only when blending the sketch canvas to\r\n // avoid interpolation\r\n if (!sketchScale) {\r\n if (tiledImage.viewport.degrees !== 0) {\r\n tiledImage._drawer._offsetForRotation({\r\n degrees: tiledImage.viewport.degrees,\r\n useSketch: useSketch\r\n });\r\n } else {\r\n if(tiledImage._drawer.viewer.viewport.flipped) {\r\n tiledImage._drawer._flip({});\r\n }\r\n }\r\n if (tiledImage.getRotation(true) % 360 !== 0) {\r\n tiledImage._drawer._offsetForRotation({\r\n degrees: tiledImage.getRotation(true),\r\n point: tiledImage.viewport.pixelFromPointNoRotate(\r\n tiledImage._getRotationPoint(true), true),\r\n useSketch: useSketch\r\n });\r\n }\r\n }\r\n\r\n var usedClip = false;\r\n if ( tiledImage._clip ) {\r\n tiledImage._drawer.saveContext(useSketch);\r\n\r\n var box = tiledImage.imageToViewportRectangle(tiledImage._clip, true);\r\n box = box.rotate(-tiledImage.getRotation(true), tiledImage._getRotationPoint(true));\r\n var clipRect = tiledImage._drawer.viewportToDrawerRectangle(box);\r\n if (sketchScale) {\r\n clipRect = clipRect.times(sketchScale);\r\n }\r\n if (sketchTranslate) {\r\n clipRect = clipRect.translate(sketchTranslate);\r\n }\r\n tiledImage._drawer.setClip(clipRect, useSketch);\r\n\r\n usedClip = true;\r\n }\r\n\r\n if ( tiledImage.placeholderFillStyle && tiledImage._hasOpaqueTile === false ) {\r\n var placeholderRect = tiledImage._drawer.viewportToDrawerRectangle(tiledImage.getBounds(true));\r\n if (sketchScale) {\r\n placeholderRect = placeholderRect.times(sketchScale);\r\n }\r\n if (sketchTranslate) {\r\n placeholderRect = placeholderRect.translate(sketchTranslate);\r\n }\r\n\r\n var fillStyle = null;\r\n if ( typeof tiledImage.placeholderFillStyle === \"function\" ) {\r\n fillStyle = tiledImage.placeholderFillStyle(tiledImage, tiledImage._drawer.context);\r\n }\r\n else {\r\n fillStyle = tiledImage.placeholderFillStyle;\r\n }\r\n\r\n tiledImage._drawer.drawRectangle(placeholderRect, fillStyle, useSketch);\r\n }\r\n\r\n for (var i = lastDrawn.length - 1; i >= 0; i--) {\r\n tile = lastDrawn[ i ];\r\n tiledImage._drawer.drawTile( tile, tiledImage._drawingHandler, useSketch, sketchScale, sketchTranslate );\r\n tile.beingDrawn = true;\r\n\r\n if( tiledImage.viewer ){\r\n /**\r\n * - Needs documentation -\r\n *\r\n * @event tile-drawn\r\n * @memberof OpenSeadragon.Viewer\r\n * @type {object}\r\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.\r\n * @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.\r\n * @property {OpenSeadragon.Tile} tile\r\n * @property {?Object} userData - Arbitrary subscriber-defined object.\r\n */\r\n tiledImage.viewer.raiseEvent( 'tile-drawn', {\r\n tiledImage: tiledImage,\r\n tile: tile\r\n });\r\n }\r\n }\r\n\r\n if ( usedClip ) {\r\n tiledImage._drawer.restoreContext( useSketch );\r\n }\r\n\r\n if (!sketchScale) {\r\n if (tiledImage.getRotation(true) % 360 !== 0) {\r\n tiledImage._drawer._restoreRotationChanges(useSketch);\r\n }\r\n if (tiledImage.viewport.degrees !== 0) {\r\n tiledImage._drawer._restoreRotationChanges(useSketch);\r\n } else{\r\n if(tiledImage._drawer.viewer.viewport.flipped) {\r\n tiledImage._drawer._flip({});\r\n }\r\n }\r\n }\r\n\r\n if (useSketch) {\r\n if (sketchScale) {\r\n if (tiledImage.viewport.degrees !== 0) {\r\n tiledImage._drawer._offsetForRotation({\r\n degrees: tiledImage.viewport.degrees,\r\n useSketch: false\r\n });\r\n }\r\n if (tiledImage.getRotation(true) % 360 !== 0) {\r\n tiledImage._drawer._offsetForRotation({\r\n degrees: tiledImage.getRotation(true),\r\n point: tiledImage.viewport.pixelFromPointNoRotate(\r\n tiledImage._getRotationPoint(true), true),\r\n useSketch: false\r\n });\r\n }\r\n }\r\n tiledImage._drawer.blendSketch({\r\n opacity: tiledImage.opacity,\r\n scale: sketchScale,\r\n translate: sketchTranslate,\r\n compositeOperation: tiledImage.compositeOperation,\r\n bounds: bounds\r\n });\r\n if (sketchScale) {\r\n if (tiledImage.getRotation(true) % 360 !== 0) {\r\n tiledImage._drawer._restoreRotationChanges(false);\r\n }\r\n if (tiledImage.viewport.degrees !== 0) {\r\n tiledImage._drawer._restoreRotationChanges(false);\r\n }\r\n }\r\n }\r\n drawDebugInfo( tiledImage, lastDrawn );\r\n}\r\n\r\n/**\r\n * @private\r\n * @inner\r\n * Draws special debug information for a TiledImage if in debug mode.\r\n * @param {OpenSeadragon.TiledImage} tiledImage\r\n * @param {OpenSeadragon.Tile[]} lastDrawn - An unordered list of Tiles drawn last frame.\r\n */\r\nfunction drawDebugInfo( tiledImage, lastDrawn ) {\r\n if( tiledImage.debugMode ) {\r\n for ( var i = lastDrawn.length - 1; i >= 0; i-- ) {\r\n var tile = lastDrawn[ i ];\r\n try {\r\n tiledImage._drawer.drawDebugInfo(\r\n tile, lastDrawn.length, i, tiledImage);\r\n } catch(e) {\r\n $.console.error(e);\r\n }\r\n }\r\n }\r\n}\r\n\r\n}( OpenSeadragon ));\r\n\r\n/*\r\n * OpenSeadragon - TileCache\r\n *\r\n * Copyright (C) 2009 CodePlex Foundation\r\n * Copyright (C) 2010-2013 OpenSeadragon contributors\r\n *\r\n * Redistribution and use in source and binary forms, with or without\r\n * modification, are permitted provided that the following conditions are\r\n * met:\r\n *\r\n * - Redistributions of source code must retain the above copyright notice,\r\n * this list of conditions and the following disclaimer.\r\n *\r\n * - Redistributions in binary form must reproduce the above copyright\r\n * notice, this list of conditions and the following disclaimer in the\r\n * documentation and/or other materials provided with the distribution.\r\n *\r\n * - Neither the name of CodePlex Foundation nor the names of its\r\n * contributors may be used to endorse or promote products derived from\r\n * this software without specific prior written permission.\r\n *\r\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\r\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\r\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\r\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\r\n * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\r\n * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\r\n * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\r\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\r\n * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r\n * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r\n */\r\n\r\n(function( $ ){\r\n\r\n// private class\r\nvar TileRecord = function( options ) {\r\n $.console.assert( options, \"[TileCache.cacheTile] options is required\" );\r\n $.console.assert( options.tile, \"[TileCache.cacheTile] options.tile is required\" );\r\n $.console.assert( options.tiledImage, \"[TileCache.cacheTile] options.tiledImage is required\" );\r\n this.tile = options.tile;\r\n this.tiledImage = options.tiledImage;\r\n};\r\n\r\n// private class\r\nvar ImageRecord = function(options) {\r\n $.console.assert( options, \"[ImageRecord] options is required\" );\r\n $.console.assert( options.image, \"[ImageRecord] options.image is required\" );\r\n this._image = options.image;\r\n this._tiles = [];\r\n};\r\n\r\nImageRecord.prototype = {\r\n destroy: function() {\r\n this._image = null;\r\n this._renderedContext = null;\r\n this._tiles = null;\r\n },\r\n\r\n getImage: function() {\r\n return this._image;\r\n },\r\n\r\n getRenderedContext: function() {\r\n if (!this._renderedContext) {\r\n var canvas = document.createElement( 'canvas' );\r\n canvas.width = this._image.width;\r\n canvas.height = this._image.height;\r\n this._renderedContext = canvas.getContext('2d');\r\n this._renderedContext.drawImage( this._image, 0, 0 );\r\n //since we are caching the prerendered image on a canvas\r\n //allow the image to not be held in memory\r\n this._image = null;\r\n }\r\n return this._renderedContext;\r\n },\r\n\r\n setRenderedContext: function(renderedContext) {\r\n $.console.error(\"ImageRecord.setRenderedContext is deprecated. \" +\r\n \"The rendered context should be created by the ImageRecord \" +\r\n \"itself when calling ImageRecord.getRenderedContext.\");\r\n this._renderedContext = renderedContext;\r\n },\r\n\r\n addTile: function(tile) {\r\n $.console.assert(tile, '[ImageRecord.addTile] tile is required');\r\n this._tiles.push(tile);\r\n },\r\n\r\n removeTile: function(tile) {\r\n for (var i = 0; i < this._tiles.length; i++) {\r\n if (this._tiles[i] === tile) {\r\n this._tiles.splice(i, 1);\r\n return;\r\n }\r\n }\r\n\r\n $.console.warn('[ImageRecord.removeTile] trying to remove unknown tile', tile);\r\n },\r\n\r\n getTileCount: function() {\r\n return this._tiles.length;\r\n }\r\n};\r\n\r\n/**\r\n * @class TileCache\r\n * @memberof OpenSeadragon\r\n * @classdesc Stores all the tiles displayed in a {@link OpenSeadragon.Viewer}.\r\n * You generally won't have to interact with the TileCache directly.\r\n * @param {Object} options - Configuration for this TileCache.\r\n * @param {Number} [options.maxImageCacheCount] - See maxImageCacheCount in\r\n * {@link OpenSeadragon.Options} for details.\r\n */\r\n$.TileCache = function( options ) {\r\n options = options || {};\r\n\r\n this._maxImageCacheCount = options.maxImageCacheCount || $.DEFAULT_SETTINGS.maxImageCacheCount;\r\n this._tilesLoaded = [];\r\n this._imagesLoaded = [];\r\n this._imagesLoadedCount = 0;\r\n};\r\n\r\n/** @lends OpenSeadragon.TileCache.prototype */\r\n$.TileCache.prototype = {\r\n /**\r\n * @returns {Number} The total number of tiles that have been loaded by\r\n * this TileCache.\r\n */\r\n numTilesLoaded: function() {\r\n return this._tilesLoaded.length;\r\n },\r\n\r\n /**\r\n * Caches the specified tile, removing an old tile if necessary to stay under the\r\n * maxImageCacheCount specified on construction. Note that if multiple tiles reference\r\n * the same image, there may be more tiles than maxImageCacheCount; the goal is to keep\r\n * the number of images below that number. Note, as well, that even the number of images\r\n * may temporarily surpass that number, but should eventually come back down to the max specified.\r\n * @param {Object} options - Tile info.\r\n * @param {OpenSeadragon.Tile} options.tile - The tile to cache.\r\n * @param {String} options.tile.cacheKey - The unique key used to identify this tile in the cache.\r\n * @param {Image} options.image - The image of the tile to cache.\r\n * @param {OpenSeadragon.TiledImage} options.tiledImage - The TiledImage that owns that tile.\r\n * @param {Number} [options.cutoff=0] - If adding this tile goes over the cache max count, this\r\n * function will release an old tile. The cutoff option specifies a tile level at or below which\r\n * tiles will not be released.\r\n */\r\n cacheTile: function( options ) {\r\n $.console.assert( options, \"[TileCache.cacheTile] options is required\" );\r\n $.console.assert( options.tile, \"[TileCache.cacheTile] options.tile is required\" );\r\n $.console.assert( options.tile.cacheKey, \"[TileCache.cacheTile] options.tile.cacheKey is required\" );\r\n $.console.assert( options.tiledImage, \"[TileCache.cacheTile] options.tiledImage is required\" );\r\n\r\n var cutoff = options.cutoff || 0;\r\n var insertionIndex = this._tilesLoaded.length;\r\n\r\n var imageRecord = this._imagesLoaded[options.tile.cacheKey];\r\n if (!imageRecord) {\r\n $.console.assert( options.image, \"[TileCache.cacheTile] options.image is required to create an ImageRecord\" );\r\n imageRecord = this._imagesLoaded[options.tile.cacheKey] = new ImageRecord({\r\n image: options.image\r\n });\r\n\r\n this._imagesLoadedCount++;\r\n }\r\n\r\n imageRecord.addTile(options.tile);\r\n options.tile.cacheImageRecord = imageRecord;\r\n\r\n // Note that just because we're unloading a tile doesn't necessarily mean\r\n // we're unloading an image. With repeated calls it should sort itself out, though.\r\n if ( this._imagesLoadedCount > this._maxImageCacheCount ) {\r\n var worstTile = null;\r\n var worstTileIndex = -1;\r\n var worstTileRecord = null;\r\n var prevTile, worstTime, worstLevel, prevTime, prevLevel, prevTileRecord;\r\n\r\n for ( var i = this._tilesLoaded.length - 1; i >= 0; i-- ) {\r\n prevTileRecord = this._tilesLoaded[ i ];\r\n prevTile = prevTileRecord.tile;\r\n\r\n if ( prevTile.level <= cutoff || prevTile.beingDrawn ) {\r\n continue;\r\n } else if ( !worstTile ) {\r\n worstTile = prevTile;\r\n worstTileIndex = i;\r\n worstTileRecord = prevTileRecord;\r\n continue;\r\n }\r\n\r\n prevTime = prevTile.lastTouchTime;\r\n worstTime = worstTile.lastTouchTime;\r\n prevLevel = prevTile.level;\r\n worstLevel = worstTile.level;\r\n\r\n if ( prevTime < worstTime ||\r\n ( prevTime == worstTime && prevLevel > worstLevel ) ) {\r\n worstTile = prevTile;\r\n worstTileIndex = i;\r\n worstTileRecord = prevTileRecord;\r\n }\r\n }\r\n\r\n if ( worstTile && worstTileIndex >= 0 ) {\r\n this._unloadTile(worstTileRecord);\r\n insertionIndex = worstTileIndex;\r\n }\r\n }\r\n\r\n this._tilesLoaded[ insertionIndex ] = new TileRecord({\r\n tile: options.tile,\r\n tiledImage: options.tiledImage\r\n });\r\n },\r\n\r\n /**\r\n * Clears all tiles associated with the specified tiledImage.\r\n * @param {OpenSeadragon.TiledImage} tiledImage\r\n */\r\n clearTilesFor: function( tiledImage ) {\r\n $.console.assert(tiledImage, '[TileCache.clearTilesFor] tiledImage is required');\r\n var tileRecord;\r\n for ( var i = 0; i < this._tilesLoaded.length; ++i ) {\r\n tileRecord = this._tilesLoaded[ i ];\r\n if ( tileRecord.tiledImage === tiledImage ) {\r\n this._unloadTile(tileRecord);\r\n this._tilesLoaded.splice( i, 1 );\r\n i--;\r\n }\r\n }\r\n },\r\n\r\n // private\r\n getImageRecord: function(cacheKey) {\r\n $.console.assert(cacheKey, '[TileCache.getImageRecord] cacheKey is required');\r\n return this._imagesLoaded[cacheKey];\r\n },\r\n\r\n // private\r\n _unloadTile: function(tileRecord) {\r\n $.console.assert(tileRecord, '[TileCache._unloadTile] tileRecord is required');\r\n var tile = tileRecord.tile;\r\n var tiledImage = tileRecord.tiledImage;\r\n\r\n tile.unload();\r\n tile.cacheImageRecord = null;\r\n\r\n var imageRecord = this._imagesLoaded[tile.cacheKey];\r\n imageRecord.removeTile(tile);\r\n if (!imageRecord.getTileCount()) {\r\n imageRecord.destroy();\r\n delete this._imagesLoaded[tile.cacheKey];\r\n this._imagesLoadedCount--;\r\n }\r\n\r\n /**\r\n * Triggered when a tile has just been unloaded from memory.\r\n *\r\n * @event tile-unloaded\r\n * @memberof OpenSeadragon.Viewer\r\n * @type {object}\r\n * @property {OpenSeadragon.TiledImage} tiledImage - The tiled image of the unloaded tile.\r\n * @property {OpenSeadragon.Tile} tile - The tile which has been unloaded.\r\n */\r\n tiledImage.viewer.raiseEvent(\"tile-unloaded\", {\r\n tile: tile,\r\n tiledImage: tiledImage\r\n });\r\n }\r\n};\r\n\r\n}( OpenSeadragon ));\r\n\r\n/*\r\n * OpenSeadragon - World\r\n *\r\n * Copyright (C) 2009 CodePlex Foundation\r\n * Copyright (C) 2010-2013 OpenSeadragon contributors\r\n *\r\n * Redistribution and use in source and binary forms, with or without\r\n * modification, are permitted provided that the following conditions are\r\n * met:\r\n *\r\n * - Redistributions of source code must retain the above copyright notice,\r\n * this list of conditions and the following disclaimer.\r\n *\r\n * - Redistributions in binary form must reproduce the above copyright\r\n * notice, this list of conditions and the following disclaimer in the\r\n * documentation and/or other materials provided with the distribution.\r\n *\r\n * - Neither the name of CodePlex Foundation nor the names of its\r\n * contributors may be used to endorse or promote products derived from\r\n * this software without specific prior written permission.\r\n *\r\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\r\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\r\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\r\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\r\n * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\r\n * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\r\n * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\r\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\r\n * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r\n * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r\n */\r\n\r\n(function( $ ){\r\n\r\n/**\r\n * @class World\r\n * @memberof OpenSeadragon\r\n * @extends OpenSeadragon.EventSource\r\n * @classdesc Keeps track of all of the tiled images in the scene.\r\n * @param {Object} options - World options.\r\n * @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this World.\r\n **/\r\n$.World = function( options ) {\r\n var _this = this;\r\n\r\n $.console.assert( options.viewer, \"[World] options.viewer is required\" );\r\n\r\n $.EventSource.call( this );\r\n\r\n this.viewer = options.viewer;\r\n this._items = [];\r\n this._needsDraw = false;\r\n this._autoRefigureSizes = true;\r\n this._needsSizesFigured = false;\r\n this._delegatedFigureSizes = function(event) {\r\n if (_this._autoRefigureSizes) {\r\n _this._figureSizes();\r\n } else {\r\n _this._needsSizesFigured = true;\r\n }\r\n };\r\n\r\n this._figureSizes();\r\n};\r\n\r\n$.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.World.prototype */{\r\n /**\r\n * Add the specified item.\r\n * @param {OpenSeadragon.TiledImage} item - The item to add.\r\n * @param {Number} [options.index] - Index for the item. If not specified, goes at the top.\r\n * @fires OpenSeadragon.World.event:add-item\r\n * @fires OpenSeadragon.World.event:metrics-change\r\n */\r\n addItem: function( item, options ) {\r\n $.console.assert(item, \"[World.addItem] item is required\");\r\n $.console.assert(item instanceof $.TiledImage, \"[World.addItem] only TiledImages supported at this time\");\r\n\r\n options = options || {};\r\n if (options.index !== undefined) {\r\n var index = Math.max(0, Math.min(this._items.length, options.index));\r\n this._items.splice(index, 0, item);\r\n } else {\r\n this._items.push( item );\r\n }\r\n\r\n if (this._autoRefigureSizes) {\r\n this._figureSizes();\r\n } else {\r\n this._needsSizesFigured = true;\r\n }\r\n\r\n this._needsDraw = true;\r\n\r\n item.addHandler('bounds-change', this._delegatedFigureSizes);\r\n item.addHandler('clip-change', this._delegatedFigureSizes);\r\n\r\n /**\r\n * Raised when an item is added to the World.\r\n * @event add-item\r\n * @memberOf OpenSeadragon.World\r\n * @type {object}\r\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the World which raised the event.\r\n * @property {OpenSeadragon.TiledImage} item - The item that has been added.\r\n * @property {?Object} userData - Arbitrary subscriber-defined object.\r\n */\r\n this.raiseEvent( 'add-item', {\r\n item: item\r\n } );\r\n },\r\n\r\n /**\r\n * Get the item at the specified index.\r\n * @param {Number} index - The item's index.\r\n * @returns {OpenSeadragon.TiledImage} The item at the specified index.\r\n */\r\n getItemAt: function( index ) {\r\n $.console.assert(index !== undefined, \"[World.getItemAt] index is required\");\r\n return this._items[ index ];\r\n },\r\n\r\n /**\r\n * Get the index of the given item or -1 if not present.\r\n * @param {OpenSeadragon.TiledImage} item - The item.\r\n * @returns {Number} The index of the item or -1 if not present.\r\n */\r\n getIndexOfItem: function( item ) {\r\n $.console.assert(item, \"[World.getIndexOfItem] item is required\");\r\n return $.indexOf( this._items, item );\r\n },\r\n\r\n /**\r\n * @returns {Number} The number of items used.\r\n */\r\n getItemCount: function() {\r\n return this._items.length;\r\n },\r\n\r\n /**\r\n * Change the index of a item so that it appears over or under others.\r\n * @param {OpenSeadragon.TiledImage} item - The item to move.\r\n * @param {Number} index - The new index.\r\n * @fires OpenSeadragon.World.event:item-index-change\r\n */\r\n setItemIndex: function( item, index ) {\r\n $.console.assert(item, \"[World.setItemIndex] item is required\");\r\n $.console.assert(index !== undefined, \"[World.setItemIndex] index is required\");\r\n\r\n var oldIndex = this.getIndexOfItem( item );\r\n\r\n if ( index >= this._items.length ) {\r\n throw new Error( \"Index bigger than number of layers.\" );\r\n }\r\n\r\n if ( index === oldIndex || oldIndex === -1 ) {\r\n return;\r\n }\r\n\r\n this._items.splice( oldIndex, 1 );\r\n this._items.splice( index, 0, item );\r\n this._needsDraw = true;\r\n\r\n /**\r\n * Raised when the order of the indexes has been changed.\r\n * @event item-index-change\r\n * @memberOf OpenSeadragon.World\r\n * @type {object}\r\n * @property {OpenSeadragon.World} eventSource - A reference to the World which raised the event.\r\n * @property {OpenSeadragon.TiledImage} item - The item whose index has\r\n * been changed\r\n * @property {Number} previousIndex - The previous index of the item\r\n * @property {Number} newIndex - The new index of the item\r\n * @property {?Object} userData - Arbitrary subscriber-defined object.\r\n */\r\n this.raiseEvent( 'item-index-change', {\r\n item: item,\r\n previousIndex: oldIndex,\r\n newIndex: index\r\n } );\r\n },\r\n\r\n /**\r\n * Remove an item.\r\n * @param {OpenSeadragon.TiledImage} item - The item to remove.\r\n * @fires OpenSeadragon.World.event:remove-item\r\n * @fires OpenSeadragon.World.event:metrics-change\r\n */\r\n removeItem: function( item ) {\r\n $.console.assert(item, \"[World.removeItem] item is required\");\r\n\r\n var index = $.indexOf(this._items, item );\r\n if ( index === -1 ) {\r\n return;\r\n }\r\n\r\n item.removeHandler('bounds-change', this._delegatedFigureSizes);\r\n item.removeHandler('clip-change', this._delegatedFigureSizes);\r\n item.destroy();\r\n this._items.splice( index, 1 );\r\n this._figureSizes();\r\n this._needsDraw = true;\r\n this._raiseRemoveItem(item);\r\n },\r\n\r\n /**\r\n * Remove all items.\r\n * @fires OpenSeadragon.World.event:remove-item\r\n * @fires OpenSeadragon.World.event:metrics-change\r\n */\r\n removeAll: function() {\r\n // We need to make sure any pending images are canceled so the world items don't get messed up\r\n this.viewer._cancelPendingImages();\r\n var item;\r\n var i;\r\n for (i = 0; i < this._items.length; i++) {\r\n item = this._items[i];\r\n item.removeHandler('bounds-change', this._delegatedFigureSizes);\r\n item.removeHandler('clip-change', this._delegatedFigureSizes);\r\n item.destroy();\r\n }\r\n\r\n var removedItems = this._items;\r\n this._items = [];\r\n this._figureSizes();\r\n this._needsDraw = true;\r\n\r\n for (i = 0; i < removedItems.length; i++) {\r\n item = removedItems[i];\r\n this._raiseRemoveItem(item);\r\n }\r\n },\r\n\r\n /**\r\n * Clears all tiles and triggers updates for all items.\r\n */\r\n resetItems: function() {\r\n for ( var i = 0; i < this._items.length; i++ ) {\r\n this._items[i].reset();\r\n }\r\n },\r\n\r\n /**\r\n * Updates (i.e. animates bounds of) all items.\r\n */\r\n update: function() {\r\n var animated = false;\r\n for ( var i = 0; i < this._items.length; i++ ) {\r\n animated = this._items[i].update() || animated;\r\n }\r\n\r\n return animated;\r\n },\r\n\r\n /**\r\n * Draws all items.\r\n */\r\n draw: function() {\r\n for ( var i = 0; i < this._items.length; i++ ) {\r\n this._items[i].draw();\r\n }\r\n\r\n this._needsDraw = false;\r\n },\r\n\r\n /**\r\n * @returns {Boolean} true if any items need updating.\r\n */\r\n needsDraw: function() {\r\n for ( var i = 0; i < this._items.length; i++ ) {\r\n if ( this._items[i].needsDraw() ) {\r\n return true;\r\n }\r\n }\r\n return this._needsDraw;\r\n },\r\n\r\n /**\r\n * @returns {OpenSeadragon.Rect} The smallest rectangle that encloses all items, in viewport coordinates.\r\n */\r\n getHomeBounds: function() {\r\n return this._homeBounds.clone();\r\n },\r\n\r\n /**\r\n * To facilitate zoom constraints, we keep track of the pixel density of the\r\n * densest item in the World (i.e. the item whose content size to viewport size\r\n * ratio is the highest) and save it as this \"content factor\".\r\n * @returns {Number} the number of content units per viewport unit.\r\n */\r\n getContentFactor: function() {\r\n return this._contentFactor;\r\n },\r\n\r\n /**\r\n * As a performance optimization, setting this flag to false allows the bounds-change event handler\r\n * on tiledImages to skip calculations on the world bounds. If a lot of images are going to be positioned in\r\n * rapid succession, this is a good idea. When finished, setAutoRefigureSizes should be called with true\r\n * or the system may behave oddly.\r\n * @param {Boolean} [value] The value to which to set the flag.\r\n */\r\n setAutoRefigureSizes: function(value) {\r\n this._autoRefigureSizes = value;\r\n if (value & this._needsSizesFigured) {\r\n this._figureSizes();\r\n this._needsSizesFigured = false;\r\n }\r\n },\r\n\r\n /**\r\n * Arranges all of the TiledImages with the specified settings.\r\n * @param {Object} options - Specifies how to arrange.\r\n * @param {Boolean} [options.immediately=false] - Whether to animate to the new arrangement.\r\n * @param {String} [options.layout] - See collectionLayout in {@link OpenSeadragon.Options}.\r\n * @param {Number} [options.rows] - See collectionRows in {@link OpenSeadragon.Options}.\r\n * @param {Number} [options.columns] - See collectionColumns in {@link OpenSeadragon.Options}.\r\n * @param {Number} [options.tileSize] - See collectionTileSize in {@link OpenSeadragon.Options}.\r\n * @param {Number} [options.tileMargin] - See collectionTileMargin in {@link OpenSeadragon.Options}.\r\n * @fires OpenSeadragon.World.event:metrics-change\r\n */\r\n arrange: function(options) {\r\n options = options || {};\r\n var immediately = options.immediately || false;\r\n var layout = options.layout || $.DEFAULT_SETTINGS.collectionLayout;\r\n var rows = options.rows || $.DEFAULT_SETTINGS.collectionRows;\r\n var columns = options.columns || $.DEFAULT_SETTINGS.collectionColumns;\r\n var tileSize = options.tileSize || $.DEFAULT_SETTINGS.collectionTileSize;\r\n var tileMargin = options.tileMargin || $.DEFAULT_SETTINGS.collectionTileMargin;\r\n var increment = tileSize + tileMargin;\r\n var wrap;\r\n if (!options.rows && columns) {\r\n wrap = columns;\r\n } else {\r\n wrap = Math.ceil(this._items.length / rows);\r\n }\r\n var x = 0;\r\n var y = 0;\r\n var item, box, width, height, position;\r\n\r\n this.setAutoRefigureSizes(false);\r\n for (var i = 0; i < this._items.length; i++) {\r\n if (i && (i % wrap) === 0) {\r\n if (layout === 'horizontal') {\r\n y += increment;\r\n x = 0;\r\n } else {\r\n x += increment;\r\n y = 0;\r\n }\r\n }\r\n\r\n item = this._items[i];\r\n box = item.getBounds();\r\n if (box.width > box.height) {\r\n width = tileSize;\r\n } else {\r\n width = tileSize * (box.width / box.height);\r\n }\r\n\r\n height = width * (box.height / box.width);\r\n position = new $.Point(x + ((tileSize - width) / 2),\r\n y + ((tileSize - height) / 2));\r\n\r\n item.setPosition(position, immediately);\r\n item.setWidth(width, immediately);\r\n\r\n if (layout === 'horizontal') {\r\n x += increment;\r\n } else {\r\n y += increment;\r\n }\r\n }\r\n this.setAutoRefigureSizes(true);\r\n },\r\n\r\n // private\r\n _figureSizes: function() {\r\n var oldHomeBounds = this._homeBounds ? this._homeBounds.clone() : null;\r\n var oldContentSize = this._contentSize ? this._contentSize.clone() : null;\r\n var oldContentFactor = this._contentFactor || 0;\r\n\r\n if (!this._items.length) {\r\n this._homeBounds = new $.Rect(0, 0, 1, 1);\r\n this._contentSize = new $.Point(1, 1);\r\n this._contentFactor = 1;\r\n } else {\r\n var item = this._items[0];\r\n var bounds = item.getBounds();\r\n this._contentFactor = item.getContentSize().x / bounds.width;\r\n var clippedBounds = item.getClippedBounds().getBoundingBox();\r\n var left = clippedBounds.x;\r\n var top = clippedBounds.y;\r\n var right = clippedBounds.x + clippedBounds.width;\r\n var bottom = clippedBounds.y + clippedBounds.height;\r\n for (var i = 1; i < this._items.length; i++) {\r\n item = this._items[i];\r\n bounds = item.getBounds();\r\n this._contentFactor = Math.max(this._contentFactor,\r\n item.getContentSize().x / bounds.width);\r\n clippedBounds = item.getClippedBounds().getBoundingBox();\r\n left = Math.min(left, clippedBounds.x);\r\n top = Math.min(top, clippedBounds.y);\r\n right = Math.max(right, clippedBounds.x + clippedBounds.width);\r\n bottom = Math.max(bottom, clippedBounds.y + clippedBounds.height);\r\n }\r\n\r\n this._homeBounds = new $.Rect(left, top, right - left, bottom - top);\r\n this._contentSize = new $.Point(\r\n this._homeBounds.width * this._contentFactor,\r\n this._homeBounds.height * this._contentFactor);\r\n }\r\n\r\n if (this._contentFactor !== oldContentFactor ||\r\n !this._homeBounds.equals(oldHomeBounds) ||\r\n !this._contentSize.equals(oldContentSize)) {\r\n /**\r\n * Raised when the home bounds or content factor change.\r\n * @event metrics-change\r\n * @memberOf OpenSeadragon.World\r\n * @type {object}\r\n * @property {OpenSeadragon.World} eventSource - A reference to the World which raised the event.\r\n * @property {?Object} userData - Arbitrary subscriber-defined object.\r\n */\r\n this.raiseEvent('metrics-change', {});\r\n }\r\n },\r\n\r\n // private\r\n _raiseRemoveItem: function(item) {\r\n /**\r\n * Raised when an item is removed.\r\n * @event remove-item\r\n * @memberOf OpenSeadragon.World\r\n * @type {object}\r\n * @property {OpenSeadragon.World} eventSource - A reference to the World which raised the event.\r\n * @property {OpenSeadragon.TiledImage} item - The item's underlying item.\r\n * @property {?Object} userData - Arbitrary subscriber-defined object.\r\n */\r\n this.raiseEvent( 'remove-item', { item: item } );\r\n }\r\n});\r\n\r\n}( OpenSeadragon ));\r\n\n//# sourceMappingURL=openseadragon.js.map\n\n//# sourceURL=webpack:///./node_modules/openseadragon/build/openseadragon/openseadragon.js?");
/***/ }),
@@ -9161,7 +9161,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var buff
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
-eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"ViewerManager\", function() { return ViewerManager; });\n/* harmony import */ var _babel_runtime_corejs2_core_js_object_keys__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime-corejs2/core-js/object/keys */ \"./node_modules/@babel/runtime-corejs2/core-js/object/keys.js\");\n/* harmony import */ var _babel_runtime_corejs2_core_js_object_keys__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs2_core_js_object_keys__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _babel_runtime_corejs2_core_js_object_get_own_property_symbols__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @babel/runtime-corejs2/core-js/object/get-own-property-symbols */ \"./node_modules/@babel/runtime-corejs2/core-js/object/get-own-property-symbols.js\");\n/* harmony import */ var _babel_runtime_corejs2_core_js_object_get_own_property_symbols__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs2_core_js_object_get_own_property_symbols__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _babel_runtime_corejs2_core_js_object_get_own_property_descriptor__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @babel/runtime-corejs2/core-js/object/get-own-property-descriptor */ \"./node_modules/@babel/runtime-corejs2/core-js/object/get-own-property-descriptor.js\");\n/* harmony import */ var _babel_runtime_corejs2_core_js_object_get_own_property_descriptor__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs2_core_js_object_get_own_property_descriptor__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _babel_runtime_corejs2_core_js_object_get_own_property_descriptors__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @babel/runtime-corejs2/core-js/object/get-own-property-descriptors */ \"./node_modules/@babel/runtime-corejs2/core-js/object/get-own-property-descriptors.js\");\n/* harmony import */ var _babel_runtime_corejs2_core_js_object_get_own_property_descriptors__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs2_core_js_object_get_own_property_descriptors__WEBPACK_IMPORTED_MODULE_3__);\n/* harmony import */ var _babel_runtime_corejs2_core_js_object_define_properties__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @babel/runtime-corejs2/core-js/object/define-properties */ \"./node_modules/@babel/runtime-corejs2/core-js/object/define-properties.js\");\n/* harmony import */ var _babel_runtime_corejs2_core_js_object_define_properties__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs2_core_js_object_define_properties__WEBPACK_IMPORTED_MODULE_4__);\n/* harmony import */ var _babel_runtime_corejs2_core_js_object_define_property__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @babel/runtime-corejs2/core-js/object/define-property */ \"./node_modules/@babel/runtime-corejs2/core-js/object/define-property.js\");\n/* harmony import */ var _babel_runtime_corejs2_core_js_object_define_property__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs2_core_js_object_define_property__WEBPACK_IMPORTED_MODULE_5__);\n/* harmony import */ var _babel_runtime_corejs2_helpers_classCallCheck__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @babel/runtime-corejs2/helpers/classCallCheck */ \"./node_modules/@babel/runtime-corejs2/helpers/classCallCheck.js\");\n/* harmony import */ var _babel_runtime_corejs2_helpers_classCallCheck__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs2_helpers_classCallCheck__WEBPACK_IMPORTED_MODULE_6__);\n/* harmony import */ var _babel_runtime_corejs2_helpers_createClass__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! @babel/runtime-corejs2/helpers/createClass */ \"./node_modules/@babel/runtime-corejs2/helpers/createClass.js\");\n/* harmony import */ var _babel_runtime_corejs2_helpers_createClass__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs2_helpers_createClass__WEBPACK_IMPORTED_MODULE_7__);\n/* harmony import */ var _babel_runtime_corejs2_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! @babel/runtime-corejs2/helpers/defineProperty */ \"./node_modules/@babel/runtime-corejs2/helpers/defineProperty.js\");\n/* harmony import */ var _babel_runtime_corejs2_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_8___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs2_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_8__);\n/* harmony import */ var _babel_runtime_corejs2_helpers_slicedToArray__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! @babel/runtime-corejs2/helpers/slicedToArray */ \"./node_modules/@babel/runtime-corejs2/helpers/slicedToArray.js\");\n/* harmony import */ var _babel_runtime_corejs2_helpers_slicedToArray__WEBPACK_IMPORTED_MODULE_9___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs2_helpers_slicedToArray__WEBPACK_IMPORTED_MODULE_9__);\n/* harmony import */ var regenerator_runtime_runtime_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! regenerator-runtime/runtime.js */ \"./node_modules/regenerator-runtime/runtime.js\");\n/* harmony import */ var regenerator_runtime_runtime_js__WEBPACK_IMPORTED_MODULE_10___default = /*#__PURE__*/__webpack_require__.n(regenerator_runtime_runtime_js__WEBPACK_IMPORTED_MODULE_10__);\n\n\n\n\n\n\n\n\n\n\n\nfunction ownKeys(object, enumerableOnly) { var keys = _babel_runtime_corejs2_core_js_object_keys__WEBPACK_IMPORTED_MODULE_0___default()(object); if (_babel_runtime_corejs2_core_js_object_get_own_property_symbols__WEBPACK_IMPORTED_MODULE_1___default.a) { var symbols = _babel_runtime_corejs2_core_js_object_get_own_property_symbols__WEBPACK_IMPORTED_MODULE_1___default()(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return _babel_runtime_corejs2_core_js_object_get_own_property_descriptor__WEBPACK_IMPORTED_MODULE_2___default()(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }\n\nfunction _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _babel_runtime_corejs2_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_8___default()(target, key, source[key]); }) : _babel_runtime_corejs2_core_js_object_get_own_property_descriptors__WEBPACK_IMPORTED_MODULE_3___default.a ? _babel_runtime_corejs2_core_js_object_define_properties__WEBPACK_IMPORTED_MODULE_4___default()(target, _babel_runtime_corejs2_core_js_object_get_own_property_descriptors__WEBPACK_IMPORTED_MODULE_3___default()(source)) : ownKeys(Object(source)).forEach(function (key) { _babel_runtime_corejs2_core_js_object_define_property__WEBPACK_IMPORTED_MODULE_5___default()(target, key, _babel_runtime_corejs2_core_js_object_get_own_property_descriptor__WEBPACK_IMPORTED_MODULE_2___default()(source, key)); }); } return target; }\n\n\n/**\n * @function toIdealTile -- full tile dimension in full image pixels\n * @param fullScale - scale factor to full image\n * @param useY - 0 for x and 1 for y\n * @returns Number\n */\n\nfunction toIdealTile(fullScale, useY) {\n var _tileWidth = this._tileWidth,\n _tileHeight = this._tileHeight;\n return [_tileWidth, _tileHeight][useY] * fullScale;\n}\n/**\n * @function toIdealTile -- clipped tile dimension in full image pixels\n * @param fullScale - scale factor to full image\n * @param v - x or y index of tile\n * @param useY - x=0 and y=1\n * @returns Number\n */\n\n\nfunction toRealTile(fullScale, v, useY) {\n var shape = [this.width, this.height][useY];\n var tileShape = this.toIdealTile(fullScale, useY);\n return Math.min(shape - v * tileShape, tileShape);\n}\n/**\n * @function toTileBoundary -- tile start and size in full image pixels\n * @param fullScale - scale factor to full image\n * @param v - x or y index of tile\n * @param useY - x=0 and y=1\n * @typedef {object} Bound\n * @property {number} start - full image pixel start of tile\n * @property {number} size - full image pixel size of tile\n * @returns Bound\n */\n\n\nfunction toTileBoundary(fullScale, v, useY) {\n var start = v * this.toIdealTile(fullScale, useY);\n var size = this.toRealTile(fullScale, v, useY);\n return {\n start: start,\n size: size\n };\n}\n/**\n * @function toMagnifiedBounds -- return bounds of magnified tile\n * @param _level - openseadragon tile level\n * @param _x - openseadragon tile x index\n * @param _y - openseadragon tile y index\n * @typedef {object} Bounds\n * @property {Array} x - start and end image x-coordinates\n * @property {Array} y - start and end image y-coordinates\n * @returns Bounds\n */\n\n\nfunction toMagnifiedBounds(_level, _x, _y) {\n var _this = this;\n\n var tl = this.toTileLevels(_level, _x, _y);\n\n if (tl.relativeImageScale >= 1) {\n return {\n x: [0, 1],\n y: [0, 1]\n };\n }\n\n var ownScale = tl.outputFullScale;\n var parentScale = tl.inputFullScale;\n\n var _map = [tl.outputTile.x, tl.outputTile.y].map(function (parentOffset, i) {\n var hd = _this.toTileBoundary(ownScale, [_x, _y][i], i);\n\n var sd = _this.toTileBoundary(parentScale, parentOffset, i);\n\n var start = (hd.start - sd.start) / sd.size;\n var end = start + hd.size / sd.size;\n return [[start, end], [1 - end, 1 - start]][i];\n }),\n _map2 = _babel_runtime_corejs2_helpers_slicedToArray__WEBPACK_IMPORTED_MODULE_9___default()(_map, 2),\n x = _map2[0],\n y = _map2[1];\n\n return {\n x: x,\n y: y\n };\n}\n/**\n * @function toTileLevels -- measure scaled/non-scaled tile details\n * @param level - openseadragon tile level\n * @param x - openseadragon tile x index\n * @param y - openseadragon tile y index\n * @typedef {object} TileLevels\n * @property {number} inputFullScale - full scale of source tile\n * @property {number} outputFullScale - full scale of renedered tile\n * @property {number} relativeImageScale - scale relative to image pixels\n * @property {object} inputTile - level, x, and y of source tile\n * @property {object} outputTile - level, x, and y of rendered tile\n * @returns TileLevels\n */\n\n\nfunction toTileLevels(level, x, y) {\n var extraZoomLevels = this.extraZoomLevels;\n var flipped = this.maxLevel - level;\n var relativeLevel = flipped - extraZoomLevels;\n var sourceLevel = Math.max(relativeLevel, 0);\n var extraZoom = sourceLevel - relativeLevel;\n var inputTile = {\n x: Math.floor(x / Math.pow(2, extraZoom)),\n y: Math.floor(y / Math.pow(2, extraZoom)),\n level: sourceLevel\n };\n\n var outputTile = _objectSpread(_objectSpread({}, inputTile), {}, {\n level: level - extraZoom\n });\n\n return {\n inputFullScale: Math.pow(2, flipped + extraZoom),\n relativeImageScale: Math.pow(2, relativeLevel),\n outputFullScale: Math.pow(2, flipped),\n inputTile: inputTile,\n outputTile: outputTile\n };\n}\n/**\n * @function getTileUrl -- return url for tile\n * @param level - openseadragon tile level\n * @param x - openseadragon tile x index\n * @param y - openseadragon tile y index\n * @returns string\n */\n\n\nfunction getTileUrl(level, x, y) {\n var s = this.toTileLevels(level, x, y).inputTile;\n return \"\".concat(this.src).concat(s.level, \"/\").concat(s.x, \"_\").concat(s.y, \".png\");\n}\n/**\n * @function getTileKey -- return string key for tile\n * @param level - openseadragon tile level\n * @param x - openseadragon tile x index\n * @param y - openseadragon tile y index\n * @returns string\n */\n\n\nfunction getTileKey(level, x, y) {\n var srcIdx = this.srcIdx,\n tileFormat = this.tileFormat;\n var s = this.toTileLevels(level, x, y).inputTile;\n return \"\".concat(tileFormat, \"-\").concat(srcIdx, \"-\").concat(s.level, \"-\").concat(s.x, \"-\").concat(s.y);\n}\n/**\n * @function getImagePixel -- return image pixel for screen position\n * @param tiledImage - openseadragon tiled image\n * @param position - screen position\n * @returns array\n */\n\n\nfunction getImagePixel(tiledImage, position) {\n var tileScale = Math.pow(2, this.extraZoomLevels);\n var frac = tiledImage.viewport.pointFromPixel(position);\n var zoomed = tiledImage.viewportToImageCoordinates(frac);\n return [zoomed.x, zoomed.y].map(function (v) {\n return v / tileScale;\n });\n}\n/**\n * @class ViewerManager\n */\n\n\nvar ViewerManager = /*#__PURE__*/function () {\n /**\n * Constructs a ColorManager instance before delegating initialization.\n *\n * @param imageViewer - ImageViewer instance\n * @param channelList - ChannelList instance\n */\n function ViewerManager(imageViewer, channelList) {\n _babel_runtime_corejs2_helpers_classCallCheck__WEBPACK_IMPORTED_MODULE_6___default()(this, ViewerManager);\n\n _babel_runtime_corejs2_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_8___default()(this, \"colorConnector\", {});\n\n _babel_runtime_corejs2_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_8___default()(this, \"rangeConnector\", {});\n\n _babel_runtime_corejs2_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_8___default()(this, \"show_sel\", true);\n\n _babel_runtime_corejs2_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_8___default()(this, \"sel_outlines\", true);\n\n this.viewer = imageViewer.viewer;\n this.imageViewer = imageViewer;\n this.channelList = channelList;\n this.init();\n }\n /**\n * @function init\n * Setups up the color manager.\n */\n\n\n _babel_runtime_corejs2_helpers_createClass__WEBPACK_IMPORTED_MODULE_7___default()(ViewerManager, [{\n key: \"init\",\n value: function init() {\n // Load label image\n this.load_label_image();\n }\n /**\n * @function channel_add\n * Add channel to multi-channel rendering\n * @param srcIdx - integer id of channel to add\n */\n\n }, {\n key: \"channel_add\",\n value: function channel_add(srcIdx) {\n // If already exists\n if (srcIdx in this.channelList.currentChannels) {\n return;\n }\n\n var url = this.imageViewer.config[\"imageData\"][srcIdx][\"src\"];\n var _this$imageViewer$con = this.imageViewer.config,\n maxLevel = _this$imageViewer$con.maxLevel,\n extraZoomLevels = _this$imageViewer$con.extraZoomLevels;\n var magnification = Math.pow(2, extraZoomLevels); // Define url and suburl\n\n var group = url.split(\"/\");\n var sub_url = group[group.length - 2];\n var range = this.channelList.rangeConnector[srcIdx];\n\n var _ref = this.channelList.colorConnector[srcIdx] || {},\n color = _ref.color;\n\n var viewerChannel = {\n url: url,\n sub_url: sub_url,\n color: color || d3.color(\"white\"),\n range: range || this.imageViewer.numericData.bitRange\n };\n this.channelList.currentChannels[srcIdx] = viewerChannel;\n this.viewer.addTiledImage({\n tileSource: {\n height: this.imageViewer.config.height * magnification,\n width: this.imageViewer.config.width * magnification,\n maxLevel: extraZoomLevels + maxLevel - 1,\n compositeOperation: \"lighter\",\n tileWidth: this.imageViewer.config.tileWidth,\n tileHeight: this.imageViewer.config.tileHeight,\n toMagnifiedBounds: toMagnifiedBounds,\n extraZoomLevels: extraZoomLevels,\n toTileBoundary: toTileBoundary,\n getImagePixel: getImagePixel,\n toTileLevels: toTileLevels,\n toIdealTile: toIdealTile,\n toRealTile: toRealTile,\n getTileUrl: getTileUrl,\n getTileKey: getTileKey,\n tileFormat: 16,\n srcIdx: srcIdx,\n src: url\n },\n // index: 0,\n opacity: 1,\n preload: true\n });\n }\n /**\n * @function channel_remove - remove channel from multichannel rendering\n * @param srcIdx - integer id of channel to remove\n */\n\n }, {\n key: \"channel_remove\",\n value: function channel_remove(srcIdx) {\n var img_count = this.viewer.world.getItemCount(); // remove channel\n\n if (srcIdx in this.channelList.currentChannels) {\n // remove channel - first find it\n for (var i = 0; i < img_count; i = i + 1) {\n var _this$channelList$cur;\n\n var url = this.viewer.world.getItemAt(i).source.src;\n\n if (url === ((_this$channelList$cur = this.channelList.currentChannels[srcIdx]) === null || _this$channelList$cur === void 0 ? void 0 : _this$channelList$cur.url)) {\n this.viewer.world.removeItem(this.viewer.world.getItemAt(i));\n delete this.channelList.currentChannels[srcIdx];\n break;\n }\n }\n }\n }\n /**\n * @function evaluateTF - finds color for value in transfer function\n * @param val - input to transfer function\n * @param tf - colors of transfer function\n * @returns object\n */\n\n }, {\n key: \"evaluateTF\",\n value: function evaluateTF(val, tf) {\n var lerpFactor = Math.round((val - tf.min) / (tf.max - tf.min) * (tf.num_bins - 1));\n\n if (lerpFactor >= tf.num_bins) {\n lerpFactor = tf.num_bins - 1;\n }\n\n if (lerpFactor < 0) {\n lerpFactor = 0;\n }\n\n return tf.tf[lerpFactor];\n }\n /**\n * @function force_repaint\n */\n\n }, {\n key: \"force_repaint\",\n value: function force_repaint() {\n // Refilter, redraw\n // this.viewer.forceRefilter();\n this.viewer.forceRedraw();\n }\n /**\n * @function load_label_image\n */\n\n }, {\n key: \"load_label_image\",\n value: function load_label_image() {\n var self = this; // Load label image in background if it exists\n\n if (this.imageViewer.config[\"imageData\"][0][\"src\"] && this.imageViewer.config[\"imageData\"][0][\"src\"] !== \"\") {\n var url = this.imageViewer.config[\"imageData\"][0][\"src\"];\n var _this$imageViewer$con2 = this.imageViewer.config,\n maxLevel = _this$imageViewer$con2.maxLevel,\n extraZoomLevels = _this$imageViewer$con2.extraZoomLevels;\n var magnification = Math.pow(2, extraZoomLevels);\n this.viewer.addTiledImage({\n tileSource: {\n height: this.imageViewer.config.height * magnification,\n width: this.imageViewer.config.width * magnification,\n maxLevel: extraZoomLevels + maxLevel - 1,\n maxImageCacheCount: 50,\n compositeOperation: \"source-over\",\n tileWidth: this.imageViewer.config.tileWidth,\n tileHeight: this.imageViewer.config.tileHeight,\n toMagnifiedBounds: toMagnifiedBounds,\n extraZoomLevels: extraZoomLevels,\n toTileBoundary: toTileBoundary,\n getImagePixel: getImagePixel,\n toTileLevels: toTileLevels,\n toIdealTile: toIdealTile,\n toRealTile: toRealTile,\n getTileUrl: getTileUrl,\n getTileKey: getTileKey,\n tileFormat: 32,\n srcIdx: 0,\n src: url\n },\n index: 0,\n opacity: 1,\n success: function success(e) {\n // Open Event is Necessary for ViaWebGl to init\n self.viewer.raiseEvent(\"open\", e.item);\n }\n });\n } else {\n this.imageViewer.noLabel = true;\n }\n }\n }]);\n\n return ViewerManager;\n}();\n\n//# sourceURL=webpack:///./src/js/views/viewerManager.js?");
+eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"ViewerManager\", function() { return ViewerManager; });\n/* harmony import */ var _babel_runtime_corejs2_core_js_object_keys__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime-corejs2/core-js/object/keys */ \"./node_modules/@babel/runtime-corejs2/core-js/object/keys.js\");\n/* harmony import */ var _babel_runtime_corejs2_core_js_object_keys__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs2_core_js_object_keys__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _babel_runtime_corejs2_core_js_object_get_own_property_symbols__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @babel/runtime-corejs2/core-js/object/get-own-property-symbols */ \"./node_modules/@babel/runtime-corejs2/core-js/object/get-own-property-symbols.js\");\n/* harmony import */ var _babel_runtime_corejs2_core_js_object_get_own_property_symbols__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs2_core_js_object_get_own_property_symbols__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _babel_runtime_corejs2_core_js_object_get_own_property_descriptor__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @babel/runtime-corejs2/core-js/object/get-own-property-descriptor */ \"./node_modules/@babel/runtime-corejs2/core-js/object/get-own-property-descriptor.js\");\n/* harmony import */ var _babel_runtime_corejs2_core_js_object_get_own_property_descriptor__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs2_core_js_object_get_own_property_descriptor__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _babel_runtime_corejs2_core_js_object_get_own_property_descriptors__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @babel/runtime-corejs2/core-js/object/get-own-property-descriptors */ \"./node_modules/@babel/runtime-corejs2/core-js/object/get-own-property-descriptors.js\");\n/* harmony import */ var _babel_runtime_corejs2_core_js_object_get_own_property_descriptors__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs2_core_js_object_get_own_property_descriptors__WEBPACK_IMPORTED_MODULE_3__);\n/* harmony import */ var _babel_runtime_corejs2_core_js_object_define_properties__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @babel/runtime-corejs2/core-js/object/define-properties */ \"./node_modules/@babel/runtime-corejs2/core-js/object/define-properties.js\");\n/* harmony import */ var _babel_runtime_corejs2_core_js_object_define_properties__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs2_core_js_object_define_properties__WEBPACK_IMPORTED_MODULE_4__);\n/* harmony import */ var _babel_runtime_corejs2_core_js_object_define_property__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @babel/runtime-corejs2/core-js/object/define-property */ \"./node_modules/@babel/runtime-corejs2/core-js/object/define-property.js\");\n/* harmony import */ var _babel_runtime_corejs2_core_js_object_define_property__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs2_core_js_object_define_property__WEBPACK_IMPORTED_MODULE_5__);\n/* harmony import */ var _babel_runtime_corejs2_helpers_classCallCheck__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @babel/runtime-corejs2/helpers/classCallCheck */ \"./node_modules/@babel/runtime-corejs2/helpers/classCallCheck.js\");\n/* harmony import */ var _babel_runtime_corejs2_helpers_classCallCheck__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs2_helpers_classCallCheck__WEBPACK_IMPORTED_MODULE_6__);\n/* harmony import */ var _babel_runtime_corejs2_helpers_createClass__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! @babel/runtime-corejs2/helpers/createClass */ \"./node_modules/@babel/runtime-corejs2/helpers/createClass.js\");\n/* harmony import */ var _babel_runtime_corejs2_helpers_createClass__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs2_helpers_createClass__WEBPACK_IMPORTED_MODULE_7__);\n/* harmony import */ var _babel_runtime_corejs2_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! @babel/runtime-corejs2/helpers/defineProperty */ \"./node_modules/@babel/runtime-corejs2/helpers/defineProperty.js\");\n/* harmony import */ var _babel_runtime_corejs2_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_8___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs2_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_8__);\n/* harmony import */ var _babel_runtime_corejs2_helpers_slicedToArray__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! @babel/runtime-corejs2/helpers/slicedToArray */ \"./node_modules/@babel/runtime-corejs2/helpers/slicedToArray.js\");\n/* harmony import */ var _babel_runtime_corejs2_helpers_slicedToArray__WEBPACK_IMPORTED_MODULE_9___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs2_helpers_slicedToArray__WEBPACK_IMPORTED_MODULE_9__);\n/* harmony import */ var regenerator_runtime_runtime_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! regenerator-runtime/runtime.js */ \"./node_modules/regenerator-runtime/runtime.js\");\n/* harmony import */ var regenerator_runtime_runtime_js__WEBPACK_IMPORTED_MODULE_10___default = /*#__PURE__*/__webpack_require__.n(regenerator_runtime_runtime_js__WEBPACK_IMPORTED_MODULE_10__);\n\n\n\n\n\n\n\n\n\n\n\nfunction ownKeys(object, enumerableOnly) { var keys = _babel_runtime_corejs2_core_js_object_keys__WEBPACK_IMPORTED_MODULE_0___default()(object); if (_babel_runtime_corejs2_core_js_object_get_own_property_symbols__WEBPACK_IMPORTED_MODULE_1___default.a) { var symbols = _babel_runtime_corejs2_core_js_object_get_own_property_symbols__WEBPACK_IMPORTED_MODULE_1___default()(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return _babel_runtime_corejs2_core_js_object_get_own_property_descriptor__WEBPACK_IMPORTED_MODULE_2___default()(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }\n\nfunction _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _babel_runtime_corejs2_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_8___default()(target, key, source[key]); }) : _babel_runtime_corejs2_core_js_object_get_own_property_descriptors__WEBPACK_IMPORTED_MODULE_3___default.a ? _babel_runtime_corejs2_core_js_object_define_properties__WEBPACK_IMPORTED_MODULE_4___default()(target, _babel_runtime_corejs2_core_js_object_get_own_property_descriptors__WEBPACK_IMPORTED_MODULE_3___default()(source)) : ownKeys(Object(source)).forEach(function (key) { _babel_runtime_corejs2_core_js_object_define_property__WEBPACK_IMPORTED_MODULE_5___default()(target, key, _babel_runtime_corejs2_core_js_object_get_own_property_descriptor__WEBPACK_IMPORTED_MODULE_2___default()(source, key)); }); } return target; }\n\n\n/**\r\n * @function toIdealTile -- full tile dimension in full image pixels\r\n * @param fullScale - scale factor to full image\r\n * @param useY - 0 for x and 1 for y\r\n * @returns Number\r\n */\n\nfunction toIdealTile(fullScale, useY) {\n var _tileWidth = this._tileWidth,\n _tileHeight = this._tileHeight;\n return [_tileWidth, _tileHeight][useY] * fullScale;\n}\n/**\r\n * @function toIdealTile -- clipped tile dimension in full image pixels\r\n * @param fullScale - scale factor to full image\r\n * @param v - x or y index of tile\r\n * @param useY - x=0 and y=1\r\n * @returns Number\r\n */\n\n\nfunction toRealTile(fullScale, v, useY) {\n var shape = [this.width, this.height][useY];\n var tileShape = this.toIdealTile(fullScale, useY);\n return Math.min(shape - v * tileShape, tileShape);\n}\n/**\r\n * @function toTileBoundary -- tile start and size in full image pixels\r\n * @param fullScale - scale factor to full image\r\n * @param v - x or y index of tile\r\n * @param useY - x=0 and y=1\r\n * @typedef {object} Bound\r\n * @property {number} start - full image pixel start of tile\r\n * @property {number} size - full image pixel size of tile\r\n * @returns Bound\r\n */\n\n\nfunction toTileBoundary(fullScale, v, useY) {\n var start = v * this.toIdealTile(fullScale, useY);\n var size = this.toRealTile(fullScale, v, useY);\n return {\n start: start,\n size: size\n };\n}\n/**\r\n * @function toMagnifiedBounds -- return bounds of magnified tile\r\n * @param _level - openseadragon tile level\r\n * @param _x - openseadragon tile x index\r\n * @param _y - openseadragon tile y index\r\n * @typedef {object} Bounds\r\n * @property {Array} x - start and end image x-coordinates\r\n * @property {Array} y - start and end image y-coordinates\r\n * @returns Bounds\r\n */\n\n\nfunction toMagnifiedBounds(_level, _x, _y) {\n var _this = this;\n\n var tl = this.toTileLevels(_level, _x, _y);\n\n if (tl.relativeImageScale >= 1) {\n return {\n x: [0, 1],\n y: [0, 1]\n };\n }\n\n var ownScale = tl.outputFullScale;\n var parentScale = tl.inputFullScale;\n\n var _map = [tl.outputTile.x, tl.outputTile.y].map(function (parentOffset, i) {\n var hd = _this.toTileBoundary(ownScale, [_x, _y][i], i);\n\n var sd = _this.toTileBoundary(parentScale, parentOffset, i);\n\n var start = (hd.start - sd.start) / sd.size;\n var end = start + hd.size / sd.size;\n return [[start, end], [1 - end, 1 - start]][i];\n }),\n _map2 = _babel_runtime_corejs2_helpers_slicedToArray__WEBPACK_IMPORTED_MODULE_9___default()(_map, 2),\n x = _map2[0],\n y = _map2[1];\n\n return {\n x: x,\n y: y\n };\n}\n/**\r\n * @function toTileLevels -- measure scaled/non-scaled tile details\r\n * @param level - openseadragon tile level\r\n * @param x - openseadragon tile x index\r\n * @param y - openseadragon tile y index\r\n * @typedef {object} TileLevels\r\n * @property {number} inputFullScale - full scale of source tile\r\n * @property {number} outputFullScale - full scale of renedered tile\r\n * @property {number} relativeImageScale - scale relative to image pixels\r\n * @property {object} inputTile - level, x, and y of source tile\r\n * @property {object} outputTile - level, x, and y of rendered tile\r\n * @returns TileLevels\r\n */\n\n\nfunction toTileLevels(level, x, y) {\n var extraZoomLevels = this.extraZoomLevels;\n var flipped = this.maxLevel - level;\n var relativeLevel = flipped - extraZoomLevels;\n var sourceLevel = Math.max(relativeLevel, 0);\n var extraZoom = sourceLevel - relativeLevel;\n var inputTile = {\n x: Math.floor(x / Math.pow(2, extraZoom)),\n y: Math.floor(y / Math.pow(2, extraZoom)),\n level: sourceLevel\n };\n\n var outputTile = _objectSpread(_objectSpread({}, inputTile), {}, {\n level: level - extraZoom\n });\n\n return {\n inputFullScale: Math.pow(2, flipped + extraZoom),\n relativeImageScale: Math.pow(2, relativeLevel),\n outputFullScale: Math.pow(2, flipped),\n inputTile: inputTile,\n outputTile: outputTile\n };\n}\n/**\r\n * @function getTileUrl -- return url for tile\r\n * @param level - openseadragon tile level\r\n * @param x - openseadragon tile x index\r\n * @param y - openseadragon tile y index\r\n * @returns string\r\n */\n\n\nfunction getTileUrl(level, x, y) {\n var s = this.toTileLevels(level, x, y).inputTile;\n return \"\".concat(this.src).concat(s.level, \"/\").concat(s.x, \"_\").concat(s.y, \".png\");\n}\n/**\r\n * @function getTileKey -- return string key for tile\r\n * @param level - openseadragon tile level\r\n * @param x - openseadragon tile x index\r\n * @param y - openseadragon tile y index\r\n * @returns string\r\n */\n\n\nfunction getTileKey(level, x, y) {\n var srcIdx = this.srcIdx,\n tileFormat = this.tileFormat;\n var s = this.toTileLevels(level, x, y).inputTile;\n return \"\".concat(tileFormat, \"-\").concat(srcIdx, \"-\").concat(s.level, \"-\").concat(s.x, \"-\").concat(s.y);\n}\n/**\r\n * @function getImagePixel -- return image pixel for screen position\r\n * @param tiledImage - openseadragon tiled image\r\n * @param position - screen position\r\n * @returns array\r\n */\n\n\nfunction getImagePixel(tiledImage, position) {\n var tileScale = Math.pow(2, this.extraZoomLevels);\n var frac = tiledImage.viewport.pointFromPixel(position);\n var zoomed = tiledImage.viewportToImageCoordinates(frac);\n return [zoomed.x, zoomed.y].map(function (v) {\n return v / tileScale;\n });\n}\n/**\r\n * @class ViewerManager\r\n */\n\n\nvar ViewerManager = /*#__PURE__*/function () {\n /**\r\n * Constructs a ColorManager instance before delegating initialization.\r\n *\r\n * @param imageViewer - ImageViewer instance\r\n * @param channelList - ChannelList instance\r\n */\n function ViewerManager(imageViewer, channelList) {\n _babel_runtime_corejs2_helpers_classCallCheck__WEBPACK_IMPORTED_MODULE_6___default()(this, ViewerManager);\n\n _babel_runtime_corejs2_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_8___default()(this, \"colorConnector\", {});\n\n _babel_runtime_corejs2_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_8___default()(this, \"rangeConnector\", {});\n\n _babel_runtime_corejs2_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_8___default()(this, \"show_sel\", true);\n\n _babel_runtime_corejs2_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_8___default()(this, \"sel_outlines\", true);\n\n this.viewer = imageViewer.viewer;\n this.imageViewer = imageViewer;\n this.channelList = channelList;\n this.init();\n }\n /**\r\n * @function init\r\n * Setups up the color manager.\r\n */\n\n\n _babel_runtime_corejs2_helpers_createClass__WEBPACK_IMPORTED_MODULE_7___default()(ViewerManager, [{\n key: \"init\",\n value: function init() {\n // Load label image\n this.load_label_image();\n }\n /**\r\n * @function channel_add\r\n * Add channel to multi-channel rendering\r\n * @param srcIdx - integer id of channel to add\r\n */\n\n }, {\n key: \"channel_add\",\n value: function channel_add(srcIdx) {\n // If already exists\n if (srcIdx in this.channelList.currentChannels) {\n return;\n }\n\n var url = this.imageViewer.config[\"imageData\"][srcIdx][\"src\"];\n var _this$imageViewer$con = this.imageViewer.config,\n maxLevel = _this$imageViewer$con.maxLevel,\n extraZoomLevels = _this$imageViewer$con.extraZoomLevels;\n var magnification = Math.pow(2, extraZoomLevels); // Define url and suburl\n\n var group = url.split(\"/\");\n var sub_url = group[group.length - 2];\n var range = this.channelList.rangeConnector[srcIdx];\n\n var _ref = this.channelList.colorConnector[srcIdx] || {},\n color = _ref.color;\n\n var viewerChannel = {\n url: url,\n sub_url: sub_url,\n color: color || d3.color(\"white\"),\n range: range || this.imageViewer.numericData.bitRange\n };\n this.channelList.currentChannels[srcIdx] = viewerChannel;\n this.viewer.addTiledImage({\n tileSource: {\n height: this.imageViewer.config.height * magnification,\n width: this.imageViewer.config.width * magnification,\n maxLevel: extraZoomLevels + maxLevel - 1,\n compositeOperation: \"lighter\",\n tileWidth: this.imageViewer.config.tileWidth,\n tileHeight: this.imageViewer.config.tileHeight,\n toMagnifiedBounds: toMagnifiedBounds,\n extraZoomLevels: extraZoomLevels,\n toTileBoundary: toTileBoundary,\n getImagePixel: getImagePixel,\n toTileLevels: toTileLevels,\n toIdealTile: toIdealTile,\n toRealTile: toRealTile,\n getTileUrl: getTileUrl,\n getTileKey: getTileKey,\n tileFormat: 16,\n srcIdx: srcIdx,\n src: url\n },\n // index: 0,\n opacity: 1,\n preload: true\n });\n }\n /**\r\n * @function channel_remove - remove channel from multichannel rendering\r\n * @param srcIdx - integer id of channel to remove\r\n */\n\n }, {\n key: \"channel_remove\",\n value: function channel_remove(srcIdx) {\n var img_count = this.viewer.world.getItemCount(); // remove channel\n\n if (srcIdx in this.channelList.currentChannels) {\n // remove channel - first find it\n for (var i = 0; i < img_count; i = i + 1) {\n var _this$channelList$cur;\n\n var url = this.viewer.world.getItemAt(i).source.src;\n\n if (url === ((_this$channelList$cur = this.channelList.currentChannels[srcIdx]) === null || _this$channelList$cur === void 0 ? void 0 : _this$channelList$cur.url)) {\n this.viewer.world.removeItem(this.viewer.world.getItemAt(i));\n delete this.channelList.currentChannels[srcIdx];\n break;\n }\n }\n }\n }\n /**\r\n * @function evaluateTF - finds color for value in transfer function\r\n * @param val - input to transfer function\r\n * @param tf - colors of transfer function\r\n * @returns object\r\n */\n\n }, {\n key: \"evaluateTF\",\n value: function evaluateTF(val, tf) {\n var lerpFactor = Math.round((val - tf.min) / (tf.max - tf.min) * (tf.num_bins - 1));\n\n if (lerpFactor >= tf.num_bins) {\n lerpFactor = tf.num_bins - 1;\n }\n\n if (lerpFactor < 0) {\n lerpFactor = 0;\n }\n\n return tf.tf[lerpFactor];\n }\n /**\r\n * @function force_repaint\r\n */\n\n }, {\n key: \"force_repaint\",\n value: function force_repaint() {\n // Refilter, redraw\n // this.viewer.forceRefilter();\n this.viewer.forceRedraw();\n }\n /**\r\n * @function load_label_image\r\n */\n\n }, {\n key: \"load_label_image\",\n value: function load_label_image() {\n var self = this; // Load label image in background if it exists\n\n if (this.imageViewer.config[\"imageData\"][0][\"src\"] && this.imageViewer.config[\"imageData\"][0][\"src\"] !== \"\") {\n var url = this.imageViewer.config[\"imageData\"][0][\"src\"];\n var _this$imageViewer$con2 = this.imageViewer.config,\n maxLevel = _this$imageViewer$con2.maxLevel,\n extraZoomLevels = _this$imageViewer$con2.extraZoomLevels;\n var magnification = Math.pow(2, extraZoomLevels);\n this.viewer.addTiledImage({\n tileSource: {\n height: this.imageViewer.config.height * magnification,\n width: this.imageViewer.config.width * magnification,\n maxLevel: extraZoomLevels + maxLevel - 1,\n maxImageCacheCount: 50,\n compositeOperation: \"source-over\",\n tileWidth: this.imageViewer.config.tileWidth,\n tileHeight: this.imageViewer.config.tileHeight,\n toMagnifiedBounds: toMagnifiedBounds,\n extraZoomLevels: extraZoomLevels,\n toTileBoundary: toTileBoundary,\n getImagePixel: getImagePixel,\n toTileLevels: toTileLevels,\n toIdealTile: toIdealTile,\n toRealTile: toRealTile,\n getTileUrl: getTileUrl,\n getTileKey: getTileKey,\n tileFormat: 32,\n srcIdx: 0,\n src: url\n },\n index: 0,\n opacity: 1,\n success: function success(e) {\n // Open Event is Necessary for ViaWebGl to init\n self.viewer.raiseEvent(\"open\", e.item);\n }\n });\n } else {\n this.imageViewer.noLabel = true;\n }\n }\n }]);\n\n return ViewerManager;\n}();\n\n//# sourceURL=webpack:///./src/js/views/viewerManager.js?");
/***/ }),
diff --git a/minerva_analysis/client/package-lock.json b/minerva_analysis/client/package-lock.json
index 77981e176..17744c56c 100644
--- a/minerva_analysis/client/package-lock.json
+++ b/minerva_analysis/client/package-lock.json
@@ -37,6 +37,7 @@
},
"devDependencies": {
"@babel/core": "^7.17.12",
+ "@babel/plugin-proposal-class-properties": "latest",
"@babel/plugin-transform-runtime": "^7.12.1",
"@babel/preset-env": "^7.17.12",
"@babel/preset-typescript": "^7.17.12",
diff --git a/minerva_analysis/client/templates/upload.html b/minerva_analysis/client/templates/upload.html
index 3038c1125..d745b872d 100644
--- a/minerva_analysis/client/templates/upload.html
+++ b/minerva_analysis/client/templates/upload.html
@@ -31,7 +31,7 @@