Merge pull request #2310 from pearcetm/rotation-seams

Refactor drawing code, add WebGL drawer, and enable plugin renderers
This commit is contained in:
Ian Gilman 2024-01-31 09:38:56 -08:00 committed by GitHub
commit 52fc10ffa2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
69 changed files with 5926 additions and 2126 deletions

View File

@ -28,6 +28,7 @@ module.exports = function(grunt) {
coverageDir = 'coverage/' + dateFormat(new Date(), 'yyyymmdd-HHMMss'), coverageDir = 'coverage/' + dateFormat(new Date(), 'yyyymmdd-HHMMss'),
sources = [ sources = [
"src/openseadragon.js", "src/openseadragon.js",
"src/matrix3.js",
"src/fullscreen.js", "src/fullscreen.js",
"src/eventsource.js", "src/eventsource.js",
"src/mousetracker.js", "src/mousetracker.js",
@ -57,11 +58,14 @@ module.exports = function(grunt) {
"src/imageloader.js", "src/imageloader.js",
"src/tile.js", "src/tile.js",
"src/overlay.js", "src/overlay.js",
"src/drawer.js", "src/drawerbase.js",
"src/htmldrawer.js",
"src/canvasdrawer.js",
"src/webgldrawer.js",
"src/viewport.js", "src/viewport.js",
"src/tiledimage.js", "src/tiledimage.js",
"src/tilecache.js", "src/tilecache.js",
"src/world.js" "src/world.js",
]; ];
var banner = "//! <%= pkg.name %> <%= pkg.version %>\n" + var banner = "//! <%= pkg.name %> <%= pkg.version %>\n" +

View File

@ -1,5 +1,5 @@
Copyright (C) 2009 CodePlex Foundation Copyright (C) 2009 CodePlex Foundation
Copyright (C) 2010-2023 OpenSeadragon contributors Copyright (C) 2010-2024 OpenSeadragon contributors
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:

View File

@ -2,7 +2,7 @@
* OpenSeadragon - Button * OpenSeadragon - Button
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are

View File

@ -2,7 +2,7 @@
* OpenSeadragon - ButtonGroup * OpenSeadragon - ButtonGroup
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are

1090
src/canvasdrawer.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
* OpenSeadragon - Control * OpenSeadragon - Control
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are

View File

@ -2,7 +2,7 @@
* OpenSeadragon - ControlDock * OpenSeadragon - ControlDock
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are

View File

@ -2,7 +2,7 @@
* OpenSeadragon - DisplayRect * OpenSeadragon - DisplayRect
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are

View File

@ -1,767 +0,0 @@
/*
* OpenSeadragon - Drawer
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of CodePlex Foundation nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
(function( $ ){
/**
* @class Drawer
* @memberof OpenSeadragon
* @classdesc Handles rendering of tiles for an {@link OpenSeadragon.Viewer}.
* @param {Object} options - Options for this Drawer.
* @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this Drawer.
* @param {OpenSeadragon.Viewport} options.viewport - Reference to Viewer viewport.
* @param {Element} options.element - Parent element.
* @param {Number} [options.debugGridColor] - See debugGridColor in {@link OpenSeadragon.Options} for details.
*/
$.Drawer = function( options ) {
$.console.assert( options.viewer, "[Drawer] options.viewer is required" );
//backward compatibility for positional args while preferring more
//idiomatic javascript options object as the only argument
var args = arguments;
if( !$.isPlainObject( options ) ){
options = {
source: args[ 0 ], // Reference to Viewer tile source.
viewport: args[ 1 ], // Reference to Viewer viewport.
element: args[ 2 ] // Parent element.
};
}
$.console.assert( options.viewport, "[Drawer] options.viewport is required" );
$.console.assert( options.element, "[Drawer] options.element is required" );
if ( options.source ) {
$.console.error( "[Drawer] options.source is no longer accepted; use TiledImage instead" );
}
this.viewer = options.viewer;
this.viewport = options.viewport;
this.debugGridColor = typeof options.debugGridColor === 'string' ? [options.debugGridColor] : options.debugGridColor || $.DEFAULT_SETTINGS.debugGridColor;
if (options.opacity) {
$.console.error( "[Drawer] options.opacity is no longer accepted; set the opacity on the TiledImage instead" );
}
this.useCanvas = $.supportsCanvas && ( this.viewer ? this.viewer.useCanvas : true );
/**
* The parent element of this Drawer instance, passed in when the Drawer was created.
* The parent of {@link OpenSeadragon.Drawer#canvas}.
* @member {Element} container
* @memberof OpenSeadragon.Drawer#
*/
this.container = $.getElement( options.element );
/**
* A &lt;canvas&gt; element if the browser supports them, otherwise a &lt;div&gt; element.
* Child element of {@link OpenSeadragon.Drawer#container}.
* @member {Element} canvas
* @memberof OpenSeadragon.Drawer#
*/
this.canvas = $.makeNeutralElement( this.useCanvas ? "canvas" : "div" );
/**
* 2d drawing context for {@link OpenSeadragon.Drawer#canvas} if it's a &lt;canvas&gt; element, otherwise null.
* @member {Object} context
* @memberof OpenSeadragon.Drawer#
*/
this.context = this.useCanvas ? this.canvas.getContext( "2d" ) : null;
/**
* Sketch canvas used to temporarily draw tiles which cannot be drawn directly
* to the main canvas due to opacity. Lazily initialized.
*/
this.sketchCanvas = null;
this.sketchContext = null;
/**
* @member {Element} element
* @memberof OpenSeadragon.Drawer#
* @deprecated Alias for {@link OpenSeadragon.Drawer#container}.
*/
this.element = this.container;
// We force our container to ltr because our drawing math doesn't work in rtl.
// This issue only affects our canvas renderer, but we do it always for consistency.
// Note that this means overlays you want to be rtl need to be explicitly set to rtl.
this.container.dir = 'ltr';
// check canvas available width and height, set canvas width and height such that the canvas backing store is set to the proper pixel density
if (this.useCanvas) {
var viewportSize = this._calculateCanvasSize();
this.canvas.width = viewportSize.x;
this.canvas.height = viewportSize.y;
}
this.canvas.style.width = "100%";
this.canvas.style.height = "100%";
this.canvas.style.position = "absolute";
$.setElementOpacity( this.canvas, this.opacity, true );
// Allow pointer events to pass through the canvas element so implicit
// pointer capture works on touch devices
$.setElementPointerEventsNone( this.canvas );
$.setElementTouchActionNone( this.canvas );
// explicit left-align
this.container.style.textAlign = "left";
this.container.appendChild( this.canvas );
// Image smoothing for canvas rendering (only if canvas is used).
// Canvas default is "true", so this will only be changed if user specified "false".
this._imageSmoothingEnabled = true;
};
/** @lends OpenSeadragon.Drawer.prototype */
$.Drawer.prototype = {
// deprecated
addOverlay: function( element, location, placement, onDraw ) {
$.console.error("drawer.addOverlay is deprecated. Use viewer.addOverlay instead.");
this.viewer.addOverlay( element, location, placement, onDraw );
return this;
},
// deprecated
updateOverlay: function( element, location, placement ) {
$.console.error("drawer.updateOverlay is deprecated. Use viewer.updateOverlay instead.");
this.viewer.updateOverlay( element, location, placement );
return this;
},
// deprecated
removeOverlay: function( element ) {
$.console.error("drawer.removeOverlay is deprecated. Use viewer.removeOverlay instead.");
this.viewer.removeOverlay( element );
return this;
},
// deprecated
clearOverlays: function() {
$.console.error("drawer.clearOverlays is deprecated. Use viewer.clearOverlays instead.");
this.viewer.clearOverlays();
return this;
},
/**
* This function converts the given point from to the drawer coordinate by
* multiplying it with the pixel density.
* This function does not take rotation into account, thus assuming provided
* point is at 0 degree.
* @param {OpenSeadragon.Point} point - the pixel point to convert
* @returns {OpenSeadragon.Point} Point in drawer coordinate system.
*/
viewportCoordToDrawerCoord: function(point) {
var vpPoint = this.viewport.pixelFromPointNoRotate(point, true);
return new $.Point(
vpPoint.x * $.pixelDensityRatio,
vpPoint.y * $.pixelDensityRatio
);
},
/**
* This function will create multiple polygon paths on the drawing context by provided polygons,
* then clip the context to the paths.
* @param {OpenSeadragon.Point[][]} polygons - an array of polygons. A polygon is an array of OpenSeadragon.Point
* @param {Boolean} useSketch - Whether to use the sketch canvas or not.
*/
clipWithPolygons: function (polygons, useSketch) {
if (!this.useCanvas) {
return;
}
var context = this._getContext(useSketch);
context.beginPath();
polygons.forEach(function (polygon) {
polygon.forEach(function (coord, i) {
context[i === 0 ? 'moveTo' : 'lineTo'](coord.x, coord.y);
});
});
context.clip();
},
/**
* Set the opacity of the drawer.
* @param {Number} opacity
* @returns {OpenSeadragon.Drawer} Chainable.
*/
setOpacity: function( opacity ) {
$.console.error("drawer.setOpacity is deprecated. Use tiledImage.setOpacity instead.");
var world = this.viewer.world;
for (var i = 0; i < world.getItemCount(); i++) {
world.getItemAt( i ).setOpacity( opacity );
}
return this;
},
/**
* Get the opacity of the drawer.
* @returns {Number}
*/
getOpacity: function() {
$.console.error("drawer.getOpacity is deprecated. Use tiledImage.getOpacity instead.");
var world = this.viewer.world;
var maxOpacity = 0;
for (var i = 0; i < world.getItemCount(); i++) {
var opacity = world.getItemAt( i ).getOpacity();
if ( opacity > maxOpacity ) {
maxOpacity = opacity;
}
}
return maxOpacity;
},
// deprecated
needsUpdate: function() {
$.console.error( "[Drawer.needsUpdate] this function is deprecated. Use World.needsDraw instead." );
return this.viewer.world.needsDraw();
},
// deprecated
numTilesLoaded: function() {
$.console.error( "[Drawer.numTilesLoaded] this function is deprecated. Use TileCache.numTilesLoaded instead." );
return this.viewer.tileCache.numTilesLoaded();
},
// deprecated
reset: function() {
$.console.error( "[Drawer.reset] this function is deprecated. Use World.resetItems instead." );
this.viewer.world.resetItems();
return this;
},
// deprecated
update: function() {
$.console.error( "[Drawer.update] this function is deprecated. Use Drawer.clear and World.draw instead." );
this.clear();
this.viewer.world.draw();
return this;
},
/**
* @returns {Boolean} True if rotation is supported.
*/
canRotate: function() {
return this.useCanvas;
},
/**
* Destroy the drawer (unload current loaded tiles)
*/
destroy: function() {
//force unloading of current canvas (1x1 will be gc later, trick not necessarily needed)
this.canvas.width = 1;
this.canvas.height = 1;
this.sketchCanvas = null;
this.sketchContext = null;
},
/**
* Clears the Drawer so it's ready to draw another frame.
*/
clear: function() {
this.canvas.innerHTML = "";
if ( this.useCanvas ) {
var viewportSize = this._calculateCanvasSize();
if( this.canvas.width !== viewportSize.x ||
this.canvas.height !== viewportSize.y ) {
this.canvas.width = viewportSize.x;
this.canvas.height = viewportSize.y;
this._updateImageSmoothingEnabled(this.context);
if ( this.sketchCanvas !== null ) {
var sketchCanvasSize = this._calculateSketchCanvasSize();
this.sketchCanvas.width = sketchCanvasSize.x;
this.sketchCanvas.height = sketchCanvasSize.y;
this._updateImageSmoothingEnabled(this.sketchContext);
}
}
this._clear();
}
},
_clear: function (useSketch, bounds) {
if (!this.useCanvas) {
return;
}
var context = this._getContext(useSketch);
if (bounds) {
context.clearRect(bounds.x, bounds.y, bounds.width, bounds.height);
} else {
var canvas = context.canvas;
context.clearRect(0, 0, canvas.width, canvas.height);
}
},
/**
* Scale from OpenSeadragon viewer rectangle to drawer rectangle
* (ignoring rotation)
* @param {OpenSeadragon.Rect} rectangle - The rectangle in viewport coordinate system.
* @returns {OpenSeadragon.Rect} Rectangle in drawer coordinate system.
*/
viewportToDrawerRectangle: function(rectangle) {
var topLeft = this.viewport.pixelFromPointNoRotate(rectangle.getTopLeft(), true);
var size = this.viewport.deltaPixelsFromPointsNoRotate(rectangle.getSize(), true);
return new $.Rect(
topLeft.x * $.pixelDensityRatio,
topLeft.y * $.pixelDensityRatio,
size.x * $.pixelDensityRatio,
size.y * $.pixelDensityRatio
);
},
/**
* Draws the given tile.
* @param {OpenSeadragon.Tile} tile - The tile to draw.
* @param {Function} drawingHandler - Method for firing the drawing event if using canvas.
* drawingHandler({context, tile, rendered})
* @param {Boolean} useSketch - Whether to use the sketch canvas or not.
* where <code>rendered</code> is the context with the pre-drawn image.
* @param {Float} [scale=1] - Apply a scale to tile position and size. Defaults to 1.
* @param {OpenSeadragon.Point} [translate] A translation vector to offset tile position
* @param {Boolean} [shouldRoundPositionAndSize] - Tells whether to round
* position and size of tiles supporting alpha channel in non-transparency
* context.
* @param {OpenSeadragon.TileSource} source - The source specification of the tile.
*/
drawTile: function( tile, drawingHandler, useSketch, scale, translate, shouldRoundPositionAndSize, source) {
$.console.assert(tile, '[Drawer.drawTile] tile is required');
$.console.assert(drawingHandler, '[Drawer.drawTile] drawingHandler is required');
if (this.useCanvas) {
var context = this._getContext(useSketch);
scale = scale || 1;
tile.drawCanvas(context, drawingHandler, scale, translate, shouldRoundPositionAndSize, source);
} else {
tile.drawHTML( this.canvas );
}
},
_getContext: function( useSketch ) {
var context = this.context;
if ( useSketch ) {
if (this.sketchCanvas === null) {
this.sketchCanvas = document.createElement( "canvas" );
var sketchCanvasSize = this._calculateSketchCanvasSize();
this.sketchCanvas.width = sketchCanvasSize.x;
this.sketchCanvas.height = sketchCanvasSize.y;
this.sketchContext = this.sketchCanvas.getContext( "2d" );
// If the viewport is not currently rotated, the sketchCanvas
// will have the same size as the main canvas. However, if
// the viewport get rotated later on, we will need to resize it.
if (this.viewport.getRotation() === 0) {
var self = this;
this.viewer.addHandler('rotate', function resizeSketchCanvas() {
if (self.viewport.getRotation() === 0) {
return;
}
self.viewer.removeHandler('rotate', resizeSketchCanvas);
var sketchCanvasSize = self._calculateSketchCanvasSize();
self.sketchCanvas.width = sketchCanvasSize.x;
self.sketchCanvas.height = sketchCanvasSize.y;
});
}
this._updateImageSmoothingEnabled(this.sketchContext);
}
context = this.sketchContext;
}
return context;
},
// private
saveContext: function( useSketch ) {
if (!this.useCanvas) {
return;
}
this._getContext( useSketch ).save();
},
// private
restoreContext: function( useSketch ) {
if (!this.useCanvas) {
return;
}
this._getContext( useSketch ).restore();
},
// private
setClip: function(rect, useSketch) {
if (!this.useCanvas) {
return;
}
var context = this._getContext( useSketch );
context.beginPath();
context.rect(rect.x, rect.y, rect.width, rect.height);
context.clip();
},
// private
drawRectangle: function(rect, fillStyle, useSketch) {
if (!this.useCanvas) {
return;
}
var context = this._getContext( useSketch );
context.save();
context.fillStyle = fillStyle;
context.fillRect(rect.x, rect.y, rect.width, rect.height);
context.restore();
},
/**
* Blends the sketch canvas in the main canvas.
* @param {Object} options The options
* @param {Float} options.opacity The opacity of the blending.
* @param {Float} [options.scale=1] The scale at which tiles were drawn on
* the sketch. Default is 1.
* Use scale to draw at a lower scale and then enlarge onto the main canvas.
* @param {OpenSeadragon.Point} [options.translate] A translation vector
* that was used to draw the tiles
* @param {String} [options.compositeOperation] - How the image is
* composited onto other images; see compositeOperation in
* {@link OpenSeadragon.Options} for possible values.
* @param {OpenSeadragon.Rect} [options.bounds] The part of the sketch
* canvas to blend in the main canvas. If specified, options.scale and
* options.translate get ignored.
*/
blendSketch: function(opacity, scale, translate, compositeOperation) {
var options = opacity;
if (!$.isPlainObject(options)) {
options = {
opacity: opacity,
scale: scale,
translate: translate,
compositeOperation: compositeOperation
};
}
if (!this.useCanvas || !this.sketchCanvas) {
return;
}
opacity = options.opacity;
compositeOperation = options.compositeOperation;
var bounds = options.bounds;
this.context.save();
this.context.globalAlpha = opacity;
if (compositeOperation) {
this.context.globalCompositeOperation = compositeOperation;
}
if (bounds) {
// Internet Explorer, Microsoft Edge, and Safari have problems
// when you call context.drawImage with negative x or y
// or x + width or y + height greater than the canvas width or height respectively.
if (bounds.x < 0) {
bounds.width += bounds.x;
bounds.x = 0;
}
if (bounds.x + bounds.width > this.canvas.width) {
bounds.width = this.canvas.width - bounds.x;
}
if (bounds.y < 0) {
bounds.height += bounds.y;
bounds.y = 0;
}
if (bounds.y + bounds.height > this.canvas.height) {
bounds.height = this.canvas.height - bounds.y;
}
this.context.drawImage(
this.sketchCanvas,
bounds.x,
bounds.y,
bounds.width,
bounds.height,
bounds.x,
bounds.y,
bounds.width,
bounds.height
);
} else {
scale = options.scale || 1;
translate = options.translate;
var position = translate instanceof $.Point ?
translate : new $.Point(0, 0);
var widthExt = 0;
var heightExt = 0;
if (translate) {
var widthDiff = this.sketchCanvas.width - this.canvas.width;
var heightDiff = this.sketchCanvas.height - this.canvas.height;
widthExt = Math.round(widthDiff / 2);
heightExt = Math.round(heightDiff / 2);
}
this.context.drawImage(
this.sketchCanvas,
position.x - widthExt * scale,
position.y - heightExt * scale,
(this.canvas.width + 2 * widthExt) * scale,
(this.canvas.height + 2 * heightExt) * scale,
-widthExt,
-heightExt,
this.canvas.width + 2 * widthExt,
this.canvas.height + 2 * heightExt
);
}
this.context.restore();
},
// private
drawDebugInfo: function(tile, count, i, tiledImage) {
if ( !this.useCanvas ) {
return;
}
var colorIndex = this.viewer.world.getIndexOfItem(tiledImage) % this.debugGridColor.length;
var context = this.context;
context.save();
context.lineWidth = 2 * $.pixelDensityRatio;
context.font = 'small-caps bold ' + (13 * $.pixelDensityRatio) + 'px arial';
context.strokeStyle = this.debugGridColor[colorIndex];
context.fillStyle = this.debugGridColor[colorIndex];
if (this.viewport.getRotation(true) % 360 !== 0 ) {
this._offsetForRotation({degrees: this.viewport.getRotation(true)});
}
if (tiledImage.getRotation(true) % 360 !== 0) {
this._offsetForRotation({
degrees: tiledImage.getRotation(true),
point: tiledImage.viewport.pixelFromPointNoRotate(
tiledImage._getRotationPoint(true), true)
});
}
if (tiledImage.viewport.getRotation(true) % 360 === 0 &&
tiledImage.getRotation(true) % 360 === 0) {
if(tiledImage._drawer.viewer.viewport.getFlip()) {
tiledImage._drawer._flip();
}
}
context.strokeRect(
tile.position.x * $.pixelDensityRatio,
tile.position.y * $.pixelDensityRatio,
tile.size.x * $.pixelDensityRatio,
tile.size.y * $.pixelDensityRatio
);
var tileCenterX = (tile.position.x + (tile.size.x / 2)) * $.pixelDensityRatio;
var tileCenterY = (tile.position.y + (tile.size.y / 2)) * $.pixelDensityRatio;
// Rotate the text the right way around.
context.translate( tileCenterX, tileCenterY );
context.rotate( Math.PI / 180 * -this.viewport.getRotation(true) );
context.translate( -tileCenterX, -tileCenterY );
if( tile.x === 0 && tile.y === 0 ){
context.fillText(
"Zoom: " + this.viewport.getZoom(),
tile.position.x * $.pixelDensityRatio,
(tile.position.y - 30) * $.pixelDensityRatio
);
context.fillText(
"Pan: " + this.viewport.getBounds().toString(),
tile.position.x * $.pixelDensityRatio,
(tile.position.y - 20) * $.pixelDensityRatio
);
}
context.fillText(
"Level: " + tile.level,
(tile.position.x + 10) * $.pixelDensityRatio,
(tile.position.y + 20) * $.pixelDensityRatio
);
context.fillText(
"Column: " + tile.x,
(tile.position.x + 10) * $.pixelDensityRatio,
(tile.position.y + 30) * $.pixelDensityRatio
);
context.fillText(
"Row: " + tile.y,
(tile.position.x + 10) * $.pixelDensityRatio,
(tile.position.y + 40) * $.pixelDensityRatio
);
context.fillText(
"Order: " + i + " of " + count,
(tile.position.x + 10) * $.pixelDensityRatio,
(tile.position.y + 50) * $.pixelDensityRatio
);
context.fillText(
"Size: " + tile.size.toString(),
(tile.position.x + 10) * $.pixelDensityRatio,
(tile.position.y + 60) * $.pixelDensityRatio
);
context.fillText(
"Position: " + tile.position.toString(),
(tile.position.x + 10) * $.pixelDensityRatio,
(tile.position.y + 70) * $.pixelDensityRatio
);
if (this.viewport.getRotation(true) % 360 !== 0 ) {
this._restoreRotationChanges();
}
if (tiledImage.getRotation(true) % 360 !== 0) {
this._restoreRotationChanges();
}
if (tiledImage.viewport.getRotation(true) % 360 === 0 &&
tiledImage.getRotation(true) % 360 === 0) {
if(tiledImage._drawer.viewer.viewport.getFlip()) {
tiledImage._drawer._flip();
}
}
context.restore();
},
// private
debugRect: function(rect) {
if ( this.useCanvas ) {
var context = this.context;
context.save();
context.lineWidth = 2 * $.pixelDensityRatio;
context.strokeStyle = this.debugGridColor[0];
context.fillStyle = this.debugGridColor[0];
context.strokeRect(
rect.x * $.pixelDensityRatio,
rect.y * $.pixelDensityRatio,
rect.width * $.pixelDensityRatio,
rect.height * $.pixelDensityRatio
);
context.restore();
}
},
/**
* Turns image smoothing on or off for this viewer. Note: Ignored in some (especially older) browsers that do not support this property.
*
* @function
* @param {Boolean} [imageSmoothingEnabled] - Whether or not the image is
* drawn smoothly on the canvas; see imageSmoothingEnabled in
* {@link OpenSeadragon.Options} for more explanation.
*/
setImageSmoothingEnabled: function(imageSmoothingEnabled){
if ( this.useCanvas ) {
this._imageSmoothingEnabled = imageSmoothingEnabled;
this._updateImageSmoothingEnabled(this.context);
this.viewer.forceRedraw();
}
},
// private
_updateImageSmoothingEnabled: function(context){
context.msImageSmoothingEnabled = this._imageSmoothingEnabled;
context.imageSmoothingEnabled = this._imageSmoothingEnabled;
},
/**
* Get the canvas size
* @param {Boolean} sketch If set to true return the size of the sketch canvas
* @returns {OpenSeadragon.Point} The size of the canvas
*/
getCanvasSize: function(sketch) {
var canvas = this._getContext(sketch).canvas;
return new $.Point(canvas.width, canvas.height);
},
getCanvasCenter: function() {
return new $.Point(this.canvas.width / 2, this.canvas.height / 2);
},
// private
_offsetForRotation: function(options) {
var point = options.point ?
options.point.times($.pixelDensityRatio) :
this.getCanvasCenter();
var context = this._getContext(options.useSketch);
context.save();
context.translate(point.x, point.y);
if(this.viewer.viewport.flipped){
context.rotate(Math.PI / 180 * -options.degrees);
context.scale(-1, 1);
} else{
context.rotate(Math.PI / 180 * options.degrees);
}
context.translate(-point.x, -point.y);
},
// private
_flip: function(options) {
options = options || {};
var point = options.point ?
options.point.times($.pixelDensityRatio) :
this.getCanvasCenter();
var context = this._getContext(options.useSketch);
context.translate(point.x, 0);
context.scale(-1, 1);
context.translate(-point.x, 0);
},
// private
_restoreRotationChanges: function(useSketch) {
var context = this._getContext(useSketch);
context.restore();
},
// private
_calculateCanvasSize: function() {
var pixelDensityRatio = $.pixelDensityRatio;
var viewportSize = this.viewport.getContainerSize();
return {
// canvas width and height are integers
x: Math.round(viewportSize.x * pixelDensityRatio),
y: Math.round(viewportSize.y * pixelDensityRatio)
};
},
// private
_calculateSketchCanvasSize: function() {
var canvasSize = this._calculateCanvasSize();
if (this.viewport.getRotation() === 0) {
return canvasSize;
}
// If the viewport is rotated, we need a larger sketch canvas in order
// to support edge smoothing.
var sketchCanvasSize = Math.ceil(Math.sqrt(
canvasSize.x * canvasSize.x +
canvasSize.y * canvasSize.y));
return {
x: sketchCanvasSize,
y: sketchCanvasSize
};
}
};
}( OpenSeadragon ));

285
src/drawerbase.js Normal file
View File

@ -0,0 +1,285 @@
/*
* OpenSeadragon - DrawerBase
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of CodePlex Foundation nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
(function( $ ){
/**
* @class DrawerBase
* @memberof OpenSeadragon
* @classdesc Base class for Drawers that handle rendering of tiles for an {@link OpenSeadragon.Viewer}.
* @param {Object} options - Options for this Drawer.
* @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this Drawer.
* @param {OpenSeadragon.Viewport} options.viewport - Reference to Viewer viewport.
* @param {Element} options.element - Parent element.
*/
$.DrawerBase = class DrawerBase{
constructor(options){
$.console.assert( options.viewer, "[Drawer] options.viewer is required" );
$.console.assert( options.viewport, "[Drawer] options.viewport is required" );
$.console.assert( options.element, "[Drawer] options.element is required" );
this.viewer = options.viewer;
this.viewport = options.viewport;
this.debugGridColor = typeof options.debugGridColor === 'string' ? [options.debugGridColor] : options.debugGridColor || $.DEFAULT_SETTINGS.debugGridColor;
this.options = options.options || {};
/**
* The parent element of this Drawer instance, passed in when the Drawer was created.
* The parent of {@link OpenSeadragon.DrawerBase#canvas}.
* @member {Element} container
* @memberof OpenSeadragon.DrawerBase#
*/
this.container = $.getElement( options.element );
this._renderingTarget = this._createDrawingElement();
this.canvas.style.width = "100%";
this.canvas.style.height = "100%";
this.canvas.style.position = "absolute";
// set canvas.style.left = 0 so the canvas is positioned properly in ltr and rtl html
this.canvas.style.left = "0";
$.setElementOpacity( this.canvas, this.viewer.opacity, true );
// Allow pointer events to pass through the canvas element so implicit
// pointer capture works on touch devices
$.setElementPointerEventsNone( this.canvas );
$.setElementTouchActionNone( this.canvas );
// explicit left-align
this.container.style.textAlign = "left";
this.container.appendChild( this.canvas );
this._checkForAPIOverrides();
}
// protect the canvas member with a getter
get canvas(){
return this._renderingTarget;
}
get element(){
$.console.error('Drawer.element is deprecated. Use Drawer.container instead.');
return this.container;
}
/**
* @returns {String|undefined} What type of drawer this is.
*/
getType(){
$.console.error('Drawer.getType must be implemented by child class');
return undefined;
}
/**
* @returns {Boolean} whether the drawer implementation is supported by the browser
*/
static isSupported() {
$.console.error('Drawer.isSupported must be implemented by child class');
}
/**
* create the HTML element (e.g. canvas, div) that the image will be drawn into
* @returns {Element} the element to draw into
*/
_createDrawingElement() {
$.console.error('Drawer._createDrawingElement must be implemented by child class');
return null;
}
/**
* @param {Array} tiledImages - An array of TiledImages that are ready to be drawn
*/
draw(tiledImages) {
$.console.error('Drawer.draw must be implemented by child class');
}
/**
* @returns {Boolean} True if rotation is supported.
*/
canRotate() {
$.console.error('Drawer.canRotate must be implemented by child class');
}
/**
* Destroy the drawer (unload current loaded tiles)
*/
destroy() {
$.console.error('Drawer.destroy must be implemented by child class');
}
/**
* @returns {Boolean} Whether this drawer requires enforcing minimum tile overlap to avoid showing seams.
*/
minimumOverlapRequired() {
return false;
}
/**
* Turns image smoothing on or off for this viewer. Note: Ignored in some (especially older) browsers that do not support this property.
*
* @function
* @param {Boolean} [imageSmoothingEnabled] - Whether or not the image is
* drawn smoothly on the canvas; see imageSmoothingEnabled in
* {@link OpenSeadragon.Options} for more explanation.
*/
setImageSmoothingEnabled(imageSmoothingEnabled){
$.console.error('Drawer.setImageSmoothingEnabled must be implemented by child class');
}
/**
* Optional public API to draw a rectangle (e.g. for debugging purposes)
* Child classes can override this method if they wish to support this
* @param {OpenSeadragon.Rect} rect
*/
drawDebuggingRect(rect) {
$.console.warn('[drawer].drawDebuggingRect is not implemented by this drawer');
}
// Deprecated functions
clear(){
$.console.warn('[drawer].clear() is deprecated. The drawer is responsible for clearing itself as needed before drawing tiles.');
}
// Private functions
/**
* Ensures that child classes have provided implementations for public API methods
* draw, canRotate, destroy, and setImageSmoothinEnabled. Throws an exception if the original
* placeholder methods are still in place.
* @private
*
*/
_checkForAPIOverrides(){
if(this._createDrawingElement === $.DrawerBase.prototype._createDrawingElement){
throw(new Error("[drawer]._createDrawingElement must be implemented by child class"));
}
if(this.draw === $.DrawerBase.prototype.draw){
throw(new Error("[drawer].draw must be implemented by child class"));
}
if(this.canRotate === $.DrawerBase.prototype.canRotate){
throw(new Error("[drawer].canRotate must be implemented by child class"));
}
if(this.destroy === $.DrawerBase.prototype.destroy){
throw(new Error("[drawer].destroy must be implemented by child class"));
}
if(this.setImageSmoothingEnabled === $.DrawerBase.prototype.setImageSmoothingEnabled){
throw(new Error("[drawer].setImageSmoothingEnabled must be implemented by child class"));
}
}
// Utility functions
/**
* Scale from OpenSeadragon viewer rectangle to drawer rectangle
* (ignoring rotation)
* @param {OpenSeadragon.Rect} rectangle - The rectangle in viewport coordinate system.
* @returns {OpenSeadragon.Rect} Rectangle in drawer coordinate system.
*/
viewportToDrawerRectangle(rectangle) {
var topLeft = this.viewport.pixelFromPointNoRotate(rectangle.getTopLeft(), true);
var size = this.viewport.deltaPixelsFromPointsNoRotate(rectangle.getSize(), true);
return new $.Rect(
topLeft.x * $.pixelDensityRatio,
topLeft.y * $.pixelDensityRatio,
size.x * $.pixelDensityRatio,
size.y * $.pixelDensityRatio
);
}
/**
* This function converts the given point from to the drawer coordinate by
* multiplying it with the pixel density.
* This function does not take rotation into account, thus assuming provided
* point is at 0 degree.
* @param {OpenSeadragon.Point} point - the pixel point to convert
* @returns {OpenSeadragon.Point} Point in drawer coordinate system.
*/
viewportCoordToDrawerCoord(point) {
var vpPoint = this.viewport.pixelFromPointNoRotate(point, true);
return new $.Point(
vpPoint.x * $.pixelDensityRatio,
vpPoint.y * $.pixelDensityRatio
);
}
// Internal utility functions
/**
* Calculate width and height of the canvas based on viewport dimensions
* and pixelDensityRatio
* @private
* @returns {OpenSeadragon.Point} {x, y} size of the canvas
*/
_calculateCanvasSize() {
var pixelDensityRatio = $.pixelDensityRatio;
var viewportSize = this.viewport.getContainerSize();
return new OpenSeadragon.Point( Math.round(viewportSize.x * pixelDensityRatio), Math.round(viewportSize.y * pixelDensityRatio));
}
/**
* Called by implementations to fire the tiled-image-drawn event (used by tests)
* @private
*/
_raiseTiledImageDrawnEvent(tiledImage, tiles){
if(!this.viewer) {
return;
}
/**
* Raised when a tiled image is drawn to the canvas. Used internally for testing.
* The update-viewport event is preferred if you want to know when a frame has been drawn.
*
* @event tiled-image-drawn
* @memberof OpenSeadragon.Viewer
* @type {object}
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
* @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.
* @property {Array} tiles - An array of Tile objects that were drawn.
* @property {?Object} userData - Arbitrary subscriber-defined object.
* @private
*/
this.viewer.raiseEvent( 'tiled-image-drawn', {
tiledImage: tiledImage,
tiles: tiles,
});
}
};
}( OpenSeadragon ));

View File

@ -2,7 +2,7 @@
* OpenSeadragon - DziTileSource * OpenSeadragon - DziTileSource
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are

View File

@ -2,7 +2,7 @@
* OpenSeadragon - EventSource * OpenSeadragon - EventSource
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are
@ -51,6 +51,7 @@
*/ */
$.EventSource = function() { $.EventSource = function() {
this.events = {}; this.events = {};
this._rejectedEventList = {};
}; };
/** @lends OpenSeadragon.EventSource.prototype */ /** @lends OpenSeadragon.EventSource.prototype */
@ -68,6 +69,7 @@ $.EventSource.prototype = {
* @param {Number} [times=1] - The number of times to handle the event * @param {Number} [times=1] - The number of times to handle the event
* before removing it. * before removing it.
* @param {Number} [priority=0] - Handler priority. By default, all priorities are 0. Higher number = priority. * @param {Number} [priority=0] - Handler priority. By default, all priorities are 0. Higher number = priority.
* @returns {Boolean} - True if the handler was added, false if it was rejected
*/ */
addOnceHandler: function(eventName, handler, userData, times, priority) { addOnceHandler: function(eventName, handler, userData, times, priority) {
var self = this; var self = this;
@ -80,7 +82,7 @@ $.EventSource.prototype = {
} }
return handler(event); return handler(event);
}; };
this.addHandler(eventName, onceHandler, userData, priority); return this.addHandler(eventName, onceHandler, userData, priority);
}, },
/** /**
@ -90,8 +92,15 @@ $.EventSource.prototype = {
* @param {OpenSeadragon.EventHandler} handler - Function to call when event is triggered. * @param {OpenSeadragon.EventHandler} handler - Function to call when event is triggered.
* @param {Object} [userData=null] - Arbitrary object to be passed unchanged to the handler. * @param {Object} [userData=null] - Arbitrary object to be passed unchanged to the handler.
* @param {Number} [priority=0] - Handler priority. By default, all priorities are 0. Higher number = priority. * @param {Number} [priority=0] - Handler priority. By default, all priorities are 0. Higher number = priority.
* @returns {Boolean} - True if the handler was added, false if it was rejected
*/ */
addHandler: function ( eventName, handler, userData, priority ) { addHandler: function ( eventName, handler, userData, priority ) {
if(Object.prototype.hasOwnProperty.call(this._rejectedEventList, eventName)){
$.console.error(`Error adding handler for ${eventName}. ${this._rejectedEventList[eventName]}`);
return false;
}
var events = this.events[ eventName ]; var events = this.events[ eventName ];
if ( !events ) { if ( !events ) {
this.events[ eventName ] = events = []; this.events[ eventName ] = events = [];
@ -106,6 +115,7 @@ $.EventSource.prototype = {
index--; index--;
} }
} }
return true;
}, },
/** /**
@ -191,17 +201,45 @@ $.EventSource.prototype = {
* @function * @function
* @param {String} eventName - Name of event to register. * @param {String} eventName - Name of event to register.
* @param {Object} eventArgs - Event-specific data. * @param {Object} eventArgs - Event-specific data.
* @returns {Boolean} True if the event was fired, false if it was rejected because of rejectEventHandler(eventName)
*/ */
raiseEvent: function( eventName, eventArgs ) { raiseEvent: function( eventName, eventArgs ) {
//uncomment if you want to get a log of all events //uncomment if you want to get a log of all events
//$.console.log( eventName ); //$.console.log( eventName );
if(Object.prototype.hasOwnProperty.call(this._rejectedEventList, eventName)){
$.console.error(`Error adding handler for ${eventName}. ${this._rejectedEventList[eventName]}`);
return false;
}
var handler = this.getHandler( eventName ); var handler = this.getHandler( eventName );
if ( handler ) { if ( handler ) {
return handler( this, eventArgs || {} ); handler( this, eventArgs || {} );
} }
return undefined; return true;
},
/**
* Set an event name as being disabled, and provide an optional error message
* to be printed to the console
* @param {String} eventName - Name of the event
* @param {String} [errorMessage] - Optional string to print to the console
* @private
*/
rejectEventHandler(eventName, errorMessage = ''){
this._rejectedEventList[eventName] = errorMessage;
},
/**
* Explicitly allow an event handler to be added for this event type, undoing
* the effects of rejectEventHandler
* @param {String} eventName - Name of the event
* @private
*/
allowEventHandler(eventName){
delete this._rejectedEventList[eventName];
} }
}; };
}( OpenSeadragon )); }( OpenSeadragon ));

View File

@ -2,7 +2,7 @@
* OpenSeadragon - full-screen support functions * OpenSeadragon - full-screen support functions
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are

242
src/htmldrawer.js Normal file
View File

@ -0,0 +1,242 @@
/*
* OpenSeadragon - HTMLDrawer
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of CodePlex Foundation nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
(function( $ ){
/**
* @class HTMLDrawer
* @memberof OpenSeadragon
* @classdesc HTML-based implementation of DrawerBase for an {@link OpenSeadragon.Viewer}.
* @param {Object} options - Options for this Drawer.
* @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this Drawer.
* @param {OpenSeadragon.Viewport} options.viewport - Reference to Viewer viewport.
* @param {Element} options.element - Parent element.
* @param {Number} [options.debugGridColor] - See debugGridColor in {@link OpenSeadragon.Options} for details.
*/
class HTMLDrawer extends $.DrawerBase{
constructor(options){
super(options);
// Reject listening for the tile-drawing event, which this drawer does not fire
this.viewer.rejectEventHandler("tile-drawing", "The HTMLDrawer does not raise the tile-drawing event");
// Since the tile-drawn event is fired by this drawer, make sure handlers can be added for it
this.viewer.allowEventHandler("tile-drawn");
}
/**
* @returns {Boolean} always true
*/
static isSupported(){
return true;
}
getType(){
return 'html';
}
/**
* @returns {Boolean} Whether this drawer requires enforcing minimum tile overlap to avoid showing seams.
*/
minimumOverlapRequired() {
return true;
}
/**
* create the HTML element (e.g. canvas, div) that the image will be drawn into
* @returns {Element} the div to draw into
*/
_createDrawingElement(){
let canvas = $.makeNeutralElement("div");
return canvas;
}
/**
* Draws the TiledImages
*/
draw(tiledImages) {
var _this = this;
this._prepareNewFrame(); // prepare to draw a new frame
tiledImages.forEach(function(tiledImage){
if (tiledImage.opacity !== 0 || tiledImage._preload) {
_this._drawTiles(tiledImage);
}
});
}
/**
* @returns {Boolean} False - rotation is not supported.
*/
canRotate() {
return false;
}
/**
* Destroy the drawer (unload current loaded tiles)
*/
destroy() {
this.canvas.innerHTML = "";
}
/**
* Turns image smoothing on or off for this viewer. Note: Ignored by HTML Drawer
*
* @function
* @param {Boolean} [imageSmoothingEnabled] - Whether or not the image is
* drawn smoothly on the canvas; see imageSmoothingEnabled in
* {@link OpenSeadragon.Options} for more explanation.
*/
setImageSmoothingEnabled(){
// noop - HTML Drawer does not deal with this property
}
/**
* Clears the Drawer so it's ready to draw another frame.
* @private
*
*/
_prepareNewFrame() {
this.canvas.innerHTML = "";
}
/**
* Draws a TiledImage.
* @private
*
*/
_drawTiles( tiledImage ) {
var lastDrawn = tiledImage.getTilesToDraw().map(info => info.tile);
if (tiledImage.opacity === 0 || (lastDrawn.length === 0 && !tiledImage.placeholderFillStyle)) {
return;
}
// Iterate over the tiles to draw, and draw them
for (var i = lastDrawn.length - 1; i >= 0; i--) {
var tile = lastDrawn[ i ];
this._drawTile( tile );
if( this.viewer ){
/**
* Raised when a tile is drawn to the canvas. Only valid for
* context2d and html drawers.
*
* @event tile-drawn
* @memberof OpenSeadragon.Viewer
* @type {object}
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
* @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.
* @property {OpenSeadragon.Tile} tile
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
this.viewer.raiseEvent( 'tile-drawn', {
tiledImage: tiledImage,
tile: tile
});
}
}
}
/**
* Draws the given tile.
* @private
* @param {OpenSeadragon.Tile} tile - The tile to draw.
* @param {Function} drawingHandler - Method for firing the drawing event if using canvas.
* drawingHandler({context, tile, rendered})
*/
_drawTile( tile ) {
$.console.assert(tile, '[Drawer._drawTile] tile is required');
let container = this.canvas;
if (!tile.cacheImageRecord) {
$.console.warn(
'[Drawer._drawTileToHTML] attempting to draw tile %s when it\'s not cached',
tile.toString());
return;
}
if ( !tile.loaded ) {
$.console.warn(
"Attempting to draw tile %s when it's not yet loaded.",
tile.toString()
);
return;
}
//EXPERIMENTAL - trying to figure out how to scale the container
// content during animation of the container size.
if ( !tile.element ) {
var image = tile.getImage();
if (!image) {
return;
}
tile.element = $.makeNeutralElement( "div" );
tile.imgElement = image.cloneNode();
tile.imgElement.style.msInterpolationMode = "nearest-neighbor";
tile.imgElement.style.width = "100%";
tile.imgElement.style.height = "100%";
tile.style = tile.element.style;
tile.style.position = "absolute";
}
if ( tile.element.parentNode !== container ) {
container.appendChild( tile.element );
}
if ( tile.imgElement.parentNode !== tile.element ) {
tile.element.appendChild( tile.imgElement );
}
tile.style.top = tile.position.y + "px";
tile.style.left = tile.position.x + "px";
tile.style.height = tile.size.y + "px";
tile.style.width = tile.size.x + "px";
if (tile.flipped) {
tile.style.transform = "scaleX(-1)";
}
$.setElementOpacity( tile.element, tile.opacity );
}
}
$.HTMLDrawer = HTMLDrawer;
}( OpenSeadragon ));

View File

@ -2,7 +2,7 @@
* OpenSeadragon - IIIFTileSource * OpenSeadragon - IIIFTileSource
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are

View File

@ -2,7 +2,7 @@
* OpenSeadragon - ImageLoader * OpenSeadragon - ImageLoader
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are
@ -37,6 +37,8 @@
/** /**
* @class ImageJob * @class ImageJob
* @classdesc Handles downloading of a single image. * @classdesc Handles downloading of a single image.
*
* @memberof OpenSeadragon
* @param {Object} options - Options for this ImageJob. * @param {Object} options - Options for this ImageJob.
* @param {String} [options.src] - URL of image to download. * @param {String} [options.src] - URL of image to download.
* @param {Tile} [options.tile] - Tile that belongs the data to. * @param {Tile} [options.tile] - Tile that belongs the data to.
@ -87,6 +89,7 @@ $.ImageJob.prototype = {
/** /**
* Starts the image job. * Starts the image job.
* @method * @method
* @memberof OpenSeadragon.ImageJob#
*/ */
start: function() { start: function() {
this.tries++; this.tries++;
@ -113,6 +116,7 @@ $.ImageJob.prototype = {
* @param {*} data data that has been downloaded * @param {*} data data that has been downloaded
* @param {XMLHttpRequest} request reference to the request if used * @param {XMLHttpRequest} request reference to the request if used
* @param {string} errorMessage description upon failure * @param {string} errorMessage description upon failure
* @memberof OpenSeadragon.ImageJob#
*/ */
finish: function(data, request, errorMessage ) { finish: function(data, request, errorMessage ) {
this.data = data; this.data = data;

View File

@ -2,7 +2,7 @@
* OpenSeadragon - ImageTileSource * OpenSeadragon - ImageTileSource
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are
@ -42,8 +42,8 @@
* 1. viewer.open({type: 'image', url: fooUrl}); * 1. viewer.open({type: 'image', url: fooUrl});
* 2. viewer.open(new OpenSeadragon.ImageTileSource({url: fooUrl})); * 2. viewer.open(new OpenSeadragon.ImageTileSource({url: fooUrl}));
* *
* With the first syntax, the crossOriginPolicy, ajaxWithCredentials and * With the first syntax, the crossOriginPolicy and ajaxWithCredentials
* useCanvas options are inherited from the viewer if they are not * options are inherited from the viewer if they are not
* specified directly in the options object. * specified directly in the options object.
* *
* @memberof OpenSeadragon * @memberof OpenSeadragon
@ -58,16 +58,13 @@
* domains. * domains.
* @param {String|Boolean} [options.ajaxWithCredentials=false] Whether to set * @param {String|Boolean} [options.ajaxWithCredentials=false] Whether to set
* the withCredentials XHR flag for AJAX requests (when loading tile sources). * the withCredentials XHR flag for AJAX requests (when loading tile sources).
* @param {Boolean} [options.useCanvas=true] Set to false to prevent any use
* of the canvas API.
*/ */
$.ImageTileSource = function (options) { $.ImageTileSource = function (options) {
options = $.extend({ options = $.extend({
buildPyramid: true, buildPyramid: true,
crossOriginPolicy: false, crossOriginPolicy: false,
ajaxWithCredentials: false, ajaxWithCredentials: false
useCanvas: true
}, options); }, options);
$.TileSource.apply(this, [options]); $.TileSource.apply(this, [options]);
@ -198,9 +195,11 @@
/** /**
* Destroys ImageTileSource * Destroys ImageTileSource
* @function * @function
* @param {OpenSeadragon.Viewer} viewer the viewer that is calling
* destroy on the ImageTileSource
*/ */
destroy: function () { destroy: function (viewer) {
this._freeupCanvasMemory(); this._freeupCanvasMemory(viewer);
}, },
// private // private
@ -214,7 +213,7 @@
height: this._image.naturalHeight height: this._image.naturalHeight
}]; }];
if (!this.buildPyramid || !$.supportsCanvas || !this.useCanvas) { if (!this.buildPyramid || !$.supportsCanvas) {
// We don't need the image anymore. Allows it to be GC. // We don't need the image anymore. Allows it to be GC.
delete this._image; delete this._image;
return levels; return levels;
@ -270,11 +269,26 @@
* and Safari keeps canvas until its height and width will be set to 0). * and Safari keeps canvas until its height and width will be set to 0).
* @function * @function
*/ */
_freeupCanvasMemory: function () { _freeupCanvasMemory: function (viewer) {
for (var i = 0; i < this.levels.length; i++) { for (var i = 0; i < this.levels.length; i++) {
if(this.levels[i].context2D){ if(this.levels[i].context2D){
this.levels[i].context2D.canvas.height = 0; this.levels[i].context2D.canvas.height = 0;
this.levels[i].context2D.canvas.width = 0; this.levels[i].context2D.canvas.width = 0;
if(viewer){
/**
* Triggered when an image has just been unloaded
*
* @event image-unloaded
* @memberof OpenSeadragon.Viewer
* @type {object}
* @property {CanvasRenderingContext2D} context2D - The context that is being unloaded
*/
viewer.raiseEvent("image-unloaded", {
context2D: this.levels[i].context2D
});
}
} }
} }
}, },

View File

@ -2,7 +2,7 @@
* OpenSeadragon - LegacyTileSource * OpenSeadragon - LegacyTileSource
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are

209
src/matrix3.js Normal file
View File

@ -0,0 +1,209 @@
/*
* OpenSeadragon - Mat3
*
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of CodePlex Foundation nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
/*
* Portions of this source file are taken from WegGL Fundamentals:
*
* Copyright 2012, Gregg Tavares.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Gregg Tavares. nor the names of his
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
(function( $ ){
// Modified from https://webglfundamentals.org/webgl/lessons/webgl-2d-matrices.html
/**
*
*
* @class Mat3
* @classdesc A left-to-right matrix representation, useful for affine transforms for
* positioning tiles for drawing
*
* @memberof OpenSeadragon
*
* @param {Array} [values] - Initial values for the matrix
*
**/
class Mat3{
constructor(values){
if(!values) {
values = [
0, 0, 0,
0, 0, 0,
0, 0, 0
];
}
this.values = values;
}
/**
* @function makeIdentity
* @memberof OpenSeadragon.Mat3
* @static
* @returns {OpenSeadragon.Mat3} an identity matrix
*/
static makeIdentity(){
return new Mat3([
1, 0, 0,
0, 1, 0,
0, 0, 1
]);
}
/**
* @function makeTranslation
* @memberof OpenSeadragon.Mat3
* @static
* @param {Number} tx The x value of the translation
* @param {Number} ty The y value of the translation
* @returns {OpenSeadragon.Mat3} A translation matrix
*/
static makeTranslation(tx, ty) {
return new Mat3([
1, 0, 0,
0, 1, 0,
tx, ty, 1,
]);
}
/**
* @function makeRotation
* @memberof OpenSeadragon.Mat3
* @static
* @param {Number} angleInRadians The desired rotation angle, in radians
* @returns {OpenSeadragon.Mat3} A rotation matrix
*/
static makeRotation(angleInRadians) {
var c = Math.cos(angleInRadians);
var s = Math.sin(angleInRadians);
return new Mat3([
c, -s, 0,
s, c, 0,
0, 0, 1,
]);
}
/**
* @function makeScaling
* @memberof OpenSeadragon.Mat3
* @static
* @param {Number} sx The x value of the scaling
* @param {Number} sy The y value of the scaling
* @returns {OpenSeadragon.Mat3} A scaling matrix
*/
static makeScaling(sx, sy) {
return new Mat3([
sx, 0, 0,
0, sy, 0,
0, 0, 1,
]);
}
/**
* @alias multiply
* @memberof! OpenSeadragon.Mat3
* @param {OpenSeadragon.Mat3} other the matrix to multiply with
* @returns {OpenSeadragon.Mat3} The result of matrix multiplication
*/
multiply(other) {
let a = this.values;
let b = other.values;
var a00 = a[0 * 3 + 0];
var a01 = a[0 * 3 + 1];
var a02 = a[0 * 3 + 2];
var a10 = a[1 * 3 + 0];
var a11 = a[1 * 3 + 1];
var a12 = a[1 * 3 + 2];
var a20 = a[2 * 3 + 0];
var a21 = a[2 * 3 + 1];
var a22 = a[2 * 3 + 2];
var b00 = b[0 * 3 + 0];
var b01 = b[0 * 3 + 1];
var b02 = b[0 * 3 + 2];
var b10 = b[1 * 3 + 0];
var b11 = b[1 * 3 + 1];
var b12 = b[1 * 3 + 2];
var b20 = b[2 * 3 + 0];
var b21 = b[2 * 3 + 1];
var b22 = b[2 * 3 + 2];
return new Mat3([
b00 * a00 + b01 * a10 + b02 * a20,
b00 * a01 + b01 * a11 + b02 * a21,
b00 * a02 + b01 * a12 + b02 * a22,
b10 * a00 + b11 * a10 + b12 * a20,
b10 * a01 + b11 * a11 + b12 * a21,
b10 * a02 + b11 * a12 + b12 * a22,
b20 * a00 + b21 * a10 + b22 * a20,
b20 * a01 + b21 * a11 + b22 * a21,
b20 * a02 + b21 * a12 + b22 * a22,
]);
}
}
$.Mat3 = Mat3;
}( OpenSeadragon ));

View File

@ -2,7 +2,7 @@
* OpenSeadragon - MouseTracker * OpenSeadragon - MouseTracker
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are
@ -357,7 +357,7 @@
getActivePointersListByType: function ( type ) { getActivePointersListByType: function ( type ) {
var delegate = THIS[ this.hash ], var delegate = THIS[ this.hash ],
i, i,
len = delegate.activePointersLists.length, len = delegate ? delegate.activePointersLists.length : 0,
list; list;
for ( i = 0; i < len; i++ ) { for ( i = 0; i < len; i++ ) {
@ -367,7 +367,9 @@
} }
list = new $.MouseTracker.GesturePointList( type ); list = new $.MouseTracker.GesturePointList( type );
delegate.activePointersLists.push( list ); if(delegate){
delegate.activePointersLists.push( list );
}
return list; return list;
}, },

View File

@ -2,7 +2,7 @@
* OpenSeadragon - Navigator * OpenSeadragon - Navigator
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are
@ -170,9 +170,6 @@ $.Navigator = function( options ){
style.border = borderWidth + 'px solid ' + options.displayRegionColor; style.border = borderWidth + 'px solid ' + options.displayRegionColor;
style.margin = '0px'; style.margin = '0px';
style.padding = '0px'; style.padding = '0px';
//TODO: IE doesn't like this property being set
//try{ style.outline = '2px auto #909'; }catch(e){/*ignore*/}
style.background = 'transparent'; style.background = 'transparent';
// We use square bracket notation on the statement below, because float is a keyword. // We use square bracket notation on the statement below, because float is a keyword.
@ -310,7 +307,7 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
this.viewport.resize( containerSize, true ); this.viewport.resize( containerSize, true );
this.viewport.goHome(true); this.viewport.goHome(true);
this.oldContainerSize = containerSize; this.oldContainerSize = containerSize;
this.drawer.clear(); this.world.update();
this.world.draw(); this.world.draw();
} }
} }

View File

@ -2,7 +2,7 @@
* OpenSeadragon * OpenSeadragon
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are
@ -190,6 +190,16 @@
* Zoom level to use when image is first opened or the home button is clicked. * Zoom level to use when image is first opened or the home button is clicked.
* If 0, adjusts to fit viewer. * If 0, adjusts to fit viewer.
* *
* @property {String|DrawerImplementation|Array} [drawer = ['webgl', 'canvas', 'html']]
* Which drawer to use. Valid strings are 'webgl', 'canvas', and 'html'. Valid drawer
* implementations are constructors of classes that extend OpenSeadragon.DrawerBase.
* An array of strings and/or constructors can be used to indicate the priority
* of different implementations, which will be tried in order based on browser support.
*
* @property {Object} drawerOptions
* Options to pass to the selected drawer implementation. For details
* please see {@link OpenSeadragon.DrawerOptions}.
*
* @property {Number} [opacity=1] * @property {Number} [opacity=1]
* Default proportional opacity of the tiled images (1=opaque, 0=hidden) * Default proportional opacity of the tiled images (1=opaque, 0=hidden)
* Hidden images do not draw and only load when preloading is allowed. * Hidden images do not draw and only load when preloading is allowed.
@ -204,9 +214,9 @@
* For complete list of modes, please @see {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation/ globalCompositeOperation} * For complete list of modes, please @see {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation/ globalCompositeOperation}
* *
* @property {Boolean} [imageSmoothingEnabled=true] * @property {Boolean} [imageSmoothingEnabled=true]
* Image smoothing for canvas rendering (only if canvas is used). Note: Ignored * Image smoothing for canvas rendering (only if the canvas drawer is used). Note: Ignored
* by some (especially older) browsers which do not support this canvas property. * by some (especially older) browsers which do not support this canvas property.
* This property can be changed in {@link Viewer.Drawer.setImageSmoothingEnabled}. * This property can be changed in {@link Viewer.DrawerBase.setImageSmoothingEnabled}.
* *
* @property {String|CanvasGradient|CanvasPattern|Function} [placeholderFillStyle=null] * @property {String|CanvasGradient|CanvasPattern|Function} [placeholderFillStyle=null]
* Draws a colored rectangle behind the tile if it is not loaded yet. * Draws a colored rectangle behind the tile if it is not loaded yet.
@ -508,7 +518,7 @@
* Milliseconds to wait after each tile retry if tileRetryMax is set. * Milliseconds to wait after each tile retry if tileRetryMax is set.
* *
* @property {Boolean} [useCanvas=true] * @property {Boolean} [useCanvas=true]
* Set to false to not use an HTML canvas element for image rendering even if canvas is supported. * Deprecated. Use the `drawer` option to specify preferred renderer.
* *
* @property {Number} [minPixelRatio=0.5] * @property {Number} [minPixelRatio=0.5]
* The higher the minPixelRatio, the lower the quality of the image that * The higher the minPixelRatio, the lower the quality of the image that
@ -744,6 +754,16 @@
* *
*/ */
/**
* @typedef {Object} DrawerOptions
* @memberof OpenSeadragon
* @property {Object} webgl - options if the WebGLDrawer is used. No options are currently supported.
* @property {Object} canvas - options if the CanvasDrawer is used. No options are currently supported.
* @property {Object} html - options if the HTMLDrawer is used. No options are currently supported.
* @property {Object} custom - options if a custom drawer is used. No options are currently supported.
*/
/** /**
* The names for the image resources used for the image navigation buttons. * The names for the image resources used for the image navigation buttons.
* *
@ -1339,12 +1359,32 @@ function OpenSeadragon( options ){
flipped: false, flipped: false,
// APPEARANCE // APPEARANCE
opacity: 1, opacity: 1, // to be passed into each TiledImage
preload: false, compositeOperation: null, // to be passed into each TiledImage
compositeOperation: null,
imageSmoothingEnabled: true, // DRAWER SETTINGS
placeholderFillStyle: null, drawer: ['webgl', 'canvas', 'html'], // prefer using webgl, then canvas (i.e. context2d), then fallback to html
subPixelRoundingForTransparency: null,
drawerOptions: {
webgl: {
},
canvas: {
},
html: {
},
custom: {
}
},
// TILED IMAGE SETTINGS
preload: false, // to be passed into each TiledImage
imageSmoothingEnabled: true, // to be passed into each TiledImage
placeholderFillStyle: null, // to be passed into each TiledImage
subPixelRoundingForTransparency: null, // to be passed into each TiledImage
//REFERENCE STRIP SETTINGS //REFERENCE STRIP SETTINGS
showReferenceStrip: false, showReferenceStrip: false,
@ -1367,7 +1407,6 @@ function OpenSeadragon( options ){
imageLoaderLimit: 0, imageLoaderLimit: 0,
maxImageCacheCount: 200, maxImageCacheCount: 200,
timeout: 30000, timeout: 30000,
useCanvas: true, // Use canvas element for drawing if available
tileRetryMax: 0, tileRetryMax: 0,
tileRetryDelay: 2500, tileRetryDelay: 2500,
@ -1437,16 +1476,6 @@ function OpenSeadragon( options ){
}, },
/**
* TODO: get rid of this. I can't see how it's required at all. Looks
* like an early legacy code artifact.
* @static
* @ignore
*/
SIGNAL: "----seadragon----",
/** /**
* Returns a function which invokes the method as if it were a method belonging to the object. * Returns a function which invokes the method as if it were a method belonging to the object.
* @function * @function

View File

@ -2,7 +2,7 @@
* OpenSeadragon - OsmTileSource * OpenSeadragon - OsmTileSource
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are

View File

@ -2,7 +2,7 @@
* OpenSeadragon - Overlay * OpenSeadragon - Overlay
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are

View File

@ -1,7 +1,7 @@
/* /*
* OpenSeadragon - Placement * OpenSeadragon - Placement
* *
* Copyright (C) 2010-2016 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are

View File

@ -2,7 +2,7 @@
* OpenSeadragon - Point * OpenSeadragon - Point
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are

View File

@ -2,7 +2,7 @@
* OpenSeadragon - Profiler * OpenSeadragon - Profiler
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are

View File

@ -2,7 +2,7 @@
* OpenSeadragon - Rect * OpenSeadragon - Rect
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are

View File

@ -2,7 +2,7 @@
* OpenSeadragon - ReferenceStrip * OpenSeadragon - ReferenceStrip
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are
@ -46,7 +46,7 @@ var THIS = {};
* *
* This idea is a reexpression of the idea of dzi collections * This idea is a reexpression of the idea of dzi collections
* which allows a clearer algorithm to reuse the tile sources already * which allows a clearer algorithm to reuse the tile sources already
* supported by OpenSeadragon, in heterogenious or homogenious * supported by OpenSeadragon, in heterogeneous or homogeneous
* sequences just like mixed groups already supported by the viewer * sequences just like mixed groups already supported by the viewer
* for the purpose of image sequnces. * for the purpose of image sequnces.
* *
@ -455,7 +455,7 @@ function loadPanels( strip, viewerSize, scroll ) {
animationTime: 0, animationTime: 0,
loadTilesWithAjax: strip.viewer.loadTilesWithAjax, loadTilesWithAjax: strip.viewer.loadTilesWithAjax,
ajaxHeaders: strip.viewer.ajaxHeaders, ajaxHeaders: strip.viewer.ajaxHeaders,
useCanvas: strip.useCanvas drawer: 'canvas', //always use canvas for the reference strip
} ); } );
// Allow pointer events to pass through miniViewer's canvas/container // Allow pointer events to pass through miniViewer's canvas/container
// elements so implicit pointer capture works on touch devices // elements so implicit pointer capture works on touch devices

View File

@ -2,7 +2,7 @@
* OpenSeadragon - Spring * OpenSeadragon - Spring
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are
@ -206,7 +206,8 @@ $.Spring.prototype = {
/** /**
* @function * @function
* @returns true if the value got updated, false otherwise * @returns true if the spring is still updating its value, false if it is
* already at the target value.
*/ */
update: function() { update: function() {
this.current.time = $.now(); this.current.time = $.now();
@ -230,14 +231,13 @@ $.Spring.prototype = {
( this.target.time - this.start.time ) ( this.target.time - this.start.time )
); );
var oldValue = this.current.value;
if (this._exponential) { if (this._exponential) {
this.current.value = Math.exp(currentValue); this.current.value = Math.exp(currentValue);
} else { } else {
this.current.value = currentValue; this.current.value = currentValue;
} }
return oldValue !== this.current.value; return currentValue !== targetValue;
}, },
/** /**

View File

@ -2,7 +2,7 @@
* OpenSeadragon - getString/setString * OpenSeadragon - getString/setString
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are

View File

@ -2,7 +2,7 @@
* OpenSeadragon - Tile * OpenSeadragon - Tile
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are
@ -81,6 +81,12 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, aja
* @memberof OpenSeadragon.Tile# * @memberof OpenSeadragon.Tile#
*/ */
this.bounds = bounds; this.bounds = bounds;
/**
* Where this tile fits, in normalized coordinates, after positioning
* @member {OpenSeadragon.Rect} positionedBounds
* @memberof OpenSeadragon.Tile#
*/
this.positionedBounds = new OpenSeadragon.Rect(bounds.x, bounds.y, bounds.width, bounds.height);
/** /**
* The portion of the tile to use as the source of the drawing operation, in pixels. Note that * The portion of the tile to use as the source of the drawing operation, in pixels. Note that
* this only works when drawing with canvas; when drawing with HTML the entire tile is always used. * this only works when drawing with canvas; when drawing with HTML the entire tile is always used.
@ -274,64 +280,6 @@ $.Tile.prototype = {
return !!this.context2D || this.getUrl().match('.png'); return !!this.context2D || this.getUrl().match('.png');
}, },
/**
* Renders the tile in an html container.
* @function
* @param {Element} container
*/
drawHTML: function( container ) {
if (!this.cacheImageRecord) {
$.console.warn(
'[Tile.drawHTML] attempting to draw tile %s when it\'s not cached',
this.toString());
return;
}
if ( !this.loaded ) {
$.console.warn(
"Attempting to draw tile %s when it's not yet loaded.",
this.toString()
);
return;
}
//EXPERIMENTAL - trying to figure out how to scale the container
// content during animation of the container size.
if ( !this.element ) {
var image = this.getImage();
if (!image) {
return;
}
this.element = $.makeNeutralElement( "div" );
this.imgElement = image.cloneNode();
this.imgElement.style.msInterpolationMode = "nearest-neighbor";
this.imgElement.style.width = "100%";
this.imgElement.style.height = "100%";
this.style = this.element.style;
this.style.position = "absolute";
}
if ( this.element.parentNode !== container ) {
container.appendChild( this.element );
}
if ( this.imgElement.parentNode !== this.element ) {
this.element.appendChild( this.imgElement );
}
this.style.top = this.position.y + "px";
this.style.left = this.position.x + "px";
this.style.height = this.size.y + "px";
this.style.width = this.size.x + "px";
if (this.flipped) {
this.style.transform = "scaleX(-1)";
}
$.setElementOpacity( this.element, this.opacity );
},
/** /**
* The Image object for this tile. * The Image object for this tile.
* @member {Object} image * @member {Object} image
@ -382,114 +330,7 @@ $.Tile.prototype = {
* @returns {CanvasRenderingContext2D} * @returns {CanvasRenderingContext2D}
*/ */
getCanvasContext: function() { getCanvasContext: function() {
return this.context2D || this.cacheImageRecord.getRenderedContext(); return this.context2D || (this.cacheImageRecord && this.cacheImageRecord.getRenderedContext());
},
/**
* Renders the tile in a canvas-based context.
* @function
* @param {Canvas} context
* @param {Function} drawingHandler - Method for firing the drawing event.
* drawingHandler({context, tile, rendered})
* where <code>rendered</code> is the context with the pre-drawn image.
* @param {Number} [scale=1] - Apply a scale to position and size
* @param {OpenSeadragon.Point} [translate] - A translation vector
* @param {Boolean} [shouldRoundPositionAndSize] - Tells whether to round
* position and size of tiles supporting alpha channel in non-transparency
* context.
* @param {OpenSeadragon.TileSource} source - The source specification of the tile.
*/
drawCanvas: function( context, drawingHandler, scale, translate, shouldRoundPositionAndSize, source) {
var position = this.position.times($.pixelDensityRatio),
size = this.size.times($.pixelDensityRatio),
rendered;
if (!this.context2D && !this.cacheImageRecord) {
$.console.warn(
'[Tile.drawCanvas] attempting to draw tile %s when it\'s not cached',
this.toString());
return;
}
rendered = this.getCanvasContext();
if ( !this.loaded || !rendered ){
$.console.warn(
"Attempting to draw tile %s when it's not yet loaded.",
this.toString()
);
return;
}
context.save();
context.globalAlpha = this.opacity;
if (typeof scale === 'number' && scale !== 1) {
// draw tile at a different scale
position = position.times(scale);
size = size.times(scale);
}
if (translate instanceof $.Point) {
// shift tile position slightly
position = position.plus(translate);
}
//if we are supposed to be rendering fully opaque rectangle,
//ie its done fading or fading is turned off, and if we are drawing
//an image with an alpha channel, then the only way
//to avoid seeing the tile underneath is to clear the rectangle
if (context.globalAlpha === 1 && this.hasTransparency) {
if (shouldRoundPositionAndSize) {
// Round to the nearest whole pixel so we don't get seams from overlap.
position.x = Math.round(position.x);
position.y = Math.round(position.y);
size.x = Math.round(size.x);
size.y = Math.round(size.y);
}
//clearing only the inside of the rectangle occupied
//by the png prevents edge flikering
context.clearRect(
position.x,
position.y,
size.x,
size.y
);
}
// This gives the application a chance to make image manipulation
// changes as we are rendering the image
drawingHandler({context: context, tile: this, rendered: rendered});
var sourceWidth, sourceHeight;
if (this.sourceBounds) {
sourceWidth = Math.min(this.sourceBounds.width, rendered.canvas.width);
sourceHeight = Math.min(this.sourceBounds.height, rendered.canvas.height);
} else {
sourceWidth = rendered.canvas.width;
sourceHeight = rendered.canvas.height;
}
context.translate(position.x + size.x / 2, 0);
if (this.flipped) {
context.scale(-1, 1);
}
context.drawImage(
rendered.canvas,
0,
0,
sourceWidth,
sourceHeight,
-size.x / 2,
position.y,
size.x,
size.y
);
context.restore();
}, },
/** /**

View File

@ -2,7 +2,7 @@
* OpenSeadragon - TileCache * OpenSeadragon - TileCache
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are
@ -236,19 +236,51 @@ $.TileCache.prototype = {
var tile = tileRecord.tile; var tile = tileRecord.tile;
var tiledImage = tileRecord.tiledImage; var tiledImage = tileRecord.tiledImage;
// tile.getCanvasContext should always exist in normal usage (with $.Tile)
// but the tile cache test passes in a dummy object
let context2D = tile.getCanvasContext && tile.getCanvasContext();
tile.unload(); tile.unload();
tile.cacheImageRecord = null; tile.cacheImageRecord = null;
var imageRecord = this._imagesLoaded[tile.cacheKey]; var imageRecord = this._imagesLoaded[tile.cacheKey];
if(!imageRecord){
return;
}
imageRecord.removeTile(tile); imageRecord.removeTile(tile);
if (!imageRecord.getTileCount()) { if (!imageRecord.getTileCount()) {
imageRecord.destroy(); imageRecord.destroy();
delete this._imagesLoaded[tile.cacheKey]; delete this._imagesLoaded[tile.cacheKey];
this._imagesLoadedCount--; this._imagesLoadedCount--;
if(context2D){
/**
* Free up canvas memory
* (iOS 12 or higher on 2GB RAM device has only 224MB canvas memory,
* and Safari keeps canvas until its height and width will be set to 0).
*/
context2D.canvas.width = 0;
context2D.canvas.height = 0;
/**
* Triggered when an image has just been unloaded
*
* @event image-unloaded
* @memberof OpenSeadragon.Viewer
* @type {object}
* @property {CanvasRenderingContext2D} context2D - The context that is being unloaded
*/
tiledImage.viewer.raiseEvent("image-unloaded", {
context2D: context2D,
tile: tile
});
}
} }
/** /**
* Triggered when a tile has just been unloaded from memory. * Triggered when a tile has just been unloaded from the cache.
* *
* @event tile-unloaded * @event tile-unloaded
* @memberof OpenSeadragon.Viewer * @memberof OpenSeadragon.Viewer
@ -260,6 +292,7 @@ $.TileCache.prototype = {
tile: tile, tile: tile,
tiledImage: tiledImage tiledImage: tiledImage
}); });
} }
}; };

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
* OpenSeadragon - TileSource * OpenSeadragon - TileSource
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are
@ -370,6 +370,7 @@ $.TileSource.prototype = {
point.y >= 0 && point.y <= 1 / this.aspectRatio; point.y >= 0 && point.y <= 1 / this.aspectRatio;
$.console.assert(validPoint, "[TileSource.getTileAtPoint] must be called with a valid point."); $.console.assert(validPoint, "[TileSource.getTileAtPoint] must be called with a valid point.");
var widthScaled = this.dimensions.x * this.getLevelScale(level); var widthScaled = this.dimensions.x * this.getLevelScale(level);
var pixelX = point.x * widthScaled; var pixelX = point.x * widthScaled;
var pixelY = point.y * widthScaled; var pixelY = point.y * widthScaled;

View File

@ -2,7 +2,7 @@
* OpenSeadragon - TileSourceCollection * OpenSeadragon - TileSourceCollection
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are

View File

@ -2,7 +2,7 @@
* OpenSeadragon - TmsTileSource * OpenSeadragon - TmsTileSource
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are

View File

@ -2,7 +2,7 @@
* OpenSeadragon - Viewer * OpenSeadragon - Viewer
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are
@ -89,6 +89,21 @@ $.Viewer = function( options ) {
delete options.config; delete options.config;
} }
// Move deprecated drawer options from the base options object into a sub-object
// This is an array to make it easy to add additional properties to convert to
// drawer options later if it makes sense to set at the drawer level rather than
// per tiled image (for example, subPixelRoundingForTransparency).
let drawerOptionList = [
'useCanvas', // deprecated
];
options.drawerOptions = Object.assign({},
drawerOptionList.reduce((drawerOptions, option) => {
drawerOptions[option] = options[option];
delete options[option];
return drawerOptions;
}, {}),
options.drawerOptions);
//Public properties //Public properties
//Allow the options object to override global defaults //Allow the options object to override global defaults
$.extend( true, this, { $.extend( true, this, {
@ -198,6 +213,7 @@ $.Viewer = function( options ) {
$.console.warn("Hash " + this.hash + " has already been used."); $.console.warn("Hash " + this.hash + " has already been used.");
} }
//Private state properties //Private state properties
THIS[ this.hash ] = { THIS[ this.hash ] = {
fsBoundsDelta: new $.Point( 1, 1 ), fsBoundsDelta: new $.Point( 1, 1 ),
@ -418,13 +434,63 @@ $.Viewer = function( options ) {
maxImageCacheCount: this.maxImageCacheCount maxImageCacheCount: this.maxImageCacheCount
}); });
// Create the drawer //Create the drawer based on selected options
this.drawer = new $.Drawer({ if (Object.prototype.hasOwnProperty.call(this.drawerOptions, 'useCanvas') ){
viewer: this, $.console.error('useCanvas is deprecated, use the "drawer" option to indicate preferred drawer(s)');
viewport: this.viewport,
element: this.canvas, // for backwards compatibility, use HTMLDrawer if useCanvas is defined and is falsey
debugGridColor: this.debugGridColor if (!this.drawerOptions.useCanvas){
}); this.drawer = $.HTMLDrawer;
}
delete this.drawerOptions.useCanvas;
}
let drawerCandidates = Array.isArray(this.drawer) ? this.drawer : [this.drawer];
if (drawerCandidates.length === 0){
// if an empty array was passed in, throw a warning and use the defaults
// note: if the drawer option is not specified, the defaults will already be set so this won't apply
drawerCandidates = [$.DEFAULT_SETTINGS.drawer].flat(); // ensure it is a list
$.console.warn('No valid drawers were selected. Using the default value.');
}
this.drawer = null;
for (let i = 0; i < drawerCandidates.length; i++) {
let drawerCandidate = drawerCandidates[i];
let Drawer = null;
//if inherits from a drawer base, use it
if (drawerCandidate && drawerCandidate.prototype instanceof $.DrawerBase) {
Drawer = drawerCandidate;
drawerCandidate = 'custom';
} else if (typeof drawerCandidate === "string") {
Drawer = $.determineDrawer(drawerCandidate);
} else {
$.console.warn('Unsupported drawer! Drawer must be an existing string type, or a class that extends OpenSeadragon.DrawerBase.');
continue;
}
// if the drawer is supported, create it and break the loop
if (Drawer && Drawer.isSupported()) {
this.drawer = new Drawer({
viewer: this,
viewport: this.viewport,
element: this.canvas,
debugGridColor: this.debugGridColor,
options: this.drawerOptions[drawerCandidate],
});
break;
}
}
if (!this.drawer){
$.console.error('No drawer could be created!');
throw('Error with creating the selected drawer(s)');
}
// Pass the imageSmoothingEnabled option along to the drawer
this.drawer.setImageSmoothingEnabled(this.imageSmoothingEnabled);
// Overlay container // Overlay container
this.overlaysContainer = $.makeNeutralElement( "div" ); this.overlaysContainer = $.makeNeutralElement( "div" );
@ -470,6 +536,7 @@ $.Viewer = function( options ) {
displayRegionColor: this.navigatorDisplayRegionColor, displayRegionColor: this.navigatorDisplayRegionColor,
crossOriginPolicy: this.crossOriginPolicy, crossOriginPolicy: this.crossOriginPolicy,
animationTime: this.animationTime, animationTime: this.animationTime,
drawer: this.drawer.getType(),
}); });
} }
@ -496,11 +563,6 @@ $.Viewer = function( options ) {
beginControlsAutoHide( _this ); beginControlsAutoHide( _this );
} ); } );
// Initial canvas options
if ( this.imageSmoothingEnabled !== undefined && !this.imageSmoothingEnabled){
this.drawer.setImageSmoothingEnabled(this.imageSmoothingEnabled);
}
// Register the viewer // Register the viewer
$._viewers.set(this.element, this); $._viewers.set(this.element, this);
}; };
@ -1040,7 +1102,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
* @returns {Boolean} * @returns {Boolean}
*/ */
isFullPage: function () { isFullPage: function () {
return THIS[ this.hash ].fullPage; return THIS[this.hash] && THIS[ this.hash ].fullPage;
}, },
@ -1087,7 +1149,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
return this; return this;
} }
if ( fullPage ) { if ( fullPage && this.element ) {
this.elementSize = $.getElementSize( this.element ); this.elementSize = $.getElementSize( this.element );
this.pageScroll = $.getPageScroll(); this.pageScroll = $.getPageScroll();
@ -2403,7 +2465,6 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
width: this.referenceStripWidth, width: this.referenceStripWidth,
tileSources: this.tileSources, tileSources: this.tileSources,
prefixUrl: this.prefixUrl, prefixUrl: this.prefixUrl,
useCanvas: this.useCanvas,
viewer: this viewer: this
}); });
@ -2552,7 +2613,6 @@ function getTileSourceImplementation( viewer, tileSource, imgOptions, successCal
ajaxHeaders: imgOptions.ajaxHeaders ? ajaxHeaders: imgOptions.ajaxHeaders ?
imgOptions.ajaxHeaders : viewer.ajaxHeaders, imgOptions.ajaxHeaders : viewer.ajaxHeaders,
splitHashDataForPost: viewer.splitHashDataForPost, splitHashDataForPost: viewer.splitHashDataForPost,
useCanvas: viewer.useCanvas,
success: function( event ) { success: function( event ) {
successCallback( event.tileSource ); successCallback( event.tileSource );
} }
@ -2570,9 +2630,6 @@ function getTileSourceImplementation( viewer, tileSource, imgOptions, successCal
if (tileSource.ajaxWithCredentials === undefined) { if (tileSource.ajaxWithCredentials === undefined) {
tileSource.ajaxWithCredentials = viewer.ajaxWithCredentials; tileSource.ajaxWithCredentials = viewer.ajaxWithCredentials;
} }
if (tileSource.useCanvas === undefined) {
tileSource.useCanvas = viewer.useCanvas;
}
if ( $.isFunction( tileSource.getTileUrl ) ) { if ( $.isFunction( tileSource.getTileUrl ) ) {
//Custom tile source //Custom tile source
@ -3705,7 +3762,7 @@ function updateOnce( viewer ) {
var viewportChange = viewer.viewport.update(); var viewportChange = viewer.viewport.update();
var animated = viewer.world.update() || viewportChange; var animated = viewer.world.update(viewportChange) || viewportChange;
if (viewportChange) { if (viewportChange) {
/** /**
@ -3795,7 +3852,6 @@ function updateOnce( viewer ) {
function drawWorld( viewer ) { function drawWorld( viewer ) {
viewer.imageLoader.clear(); viewer.imageLoader.clear();
viewer.drawer.clear();
viewer.world.draw(); viewer.world.draw();
/** /**
@ -3949,4 +4005,22 @@ function onFlip() {
this.viewport.toggleFlip(); this.viewport.toggleFlip();
} }
/**
* Find drawer
*/
$.determineDrawer = function( id ){
for (let property in OpenSeadragon) {
const drawer = OpenSeadragon[ property ],
proto = drawer.prototype;
if( proto &&
proto instanceof OpenSeadragon.DrawerBase &&
$.isFunction( proto.getType ) &&
proto.getType.call( drawer ) === id
){
return drawer;
}
}
return null;
};
}( OpenSeadragon )); }( OpenSeadragon ));

View File

@ -2,7 +2,7 @@
* OpenSeadragon - Viewport * OpenSeadragon - Viewport
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are
@ -1133,7 +1133,7 @@ $.Viewport.prototype = {
/** /**
* Update the zoom, degrees, and center (X and Y) springs. * Update the zoom, degrees, and center (X and Y) springs.
* @function * @function
* @returns {Boolean} True if any change has been made, false otherwise. * @returns {Boolean} True if the viewport is still animating, false otherwise.
*/ */
update: function() { update: function() {
var _this = this; var _this = this;
@ -1165,7 +1165,13 @@ $.Viewport.prototype = {
this._oldZoom = this.zoomSpring.current.value; this._oldZoom = this.zoomSpring.current.value;
this._oldDegrees = this.degreesSpring.current.value; this._oldDegrees = this.degreesSpring.current.value;
return changed; var isAnimating = changed ||
!this.zoomSpring.isAtTargetValue() ||
!this.centerSpringX.isAtTargetValue() ||
!this.centerSpringY.isAtTargetValue() ||
!this.degreesSpring.isAtTargetValue();
return isAnimating;
}, },
// private - pass true to use spring, or a number for degrees for immediate rotation // private - pass true to use spring, or a number for degrees for immediate rotation

1124
src/webgldrawer.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
* OpenSeadragon - World * OpenSeadragon - World
* *
* Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2023 OpenSeadragon contributors * Copyright (C) 2010-2024 OpenSeadragon contributors
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are
@ -242,11 +242,14 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
/** /**
* Updates (i.e. animates bounds of) all items. * Updates (i.e. animates bounds of) all items.
* @function
* @param viewportChanged Whether the viewport changed, which indicates that
* all TiledImages need to be updated.
*/ */
update: function() { update: function(viewportChanged) {
var animated = false; var animated = false;
for ( var i = 0; i < this._items.length; i++ ) { for ( var i = 0; i < this._items.length; i++ ) {
animated = this._items[i].update() || animated; animated = this._items[i].update(viewportChanged) || animated;
} }
return animated; return animated;
@ -256,11 +259,11 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
* Draws all items. * Draws all items.
*/ */
draw: function() { draw: function() {
for ( var i = 0; i < this._items.length; i++ ) { this.viewer.drawer.draw(this._items);
this._items[i].draw();
}
this._needsDraw = false; this._needsDraw = false;
this._items.forEach(function(item){
this._needsDraw = item.setDrawn() || this._needsDraw;
});
}, },
/** /**

View File

@ -25,7 +25,7 @@
id: "contentDiv", id: "contentDiv",
prefixUrl: "../../build/openseadragon/images/", prefixUrl: "../../build/openseadragon/images/",
tileSources: "../data/testpattern.dzi", tileSources: "../data/testpattern.dzi",
showNavigator: true showNavigator: true,
}); });
</script> </script>
</body> </body>

View File

@ -16,7 +16,6 @@
// debugMode: true, // debugMode: true,
zoomPerScroll: 1.02, zoomPerScroll: 1.02,
showNavigator: testNavigator, showNavigator: testNavigator,
useCanvas: true,
// defaultZoomLevel: 2, // defaultZoomLevel: 2,
// homeFillsViewer: true, // homeFillsViewer: true,
// sequenceMode: true, // sequenceMode: true,
@ -131,8 +130,9 @@
var box = new OpenSeadragon.Rect(margins.left, margins.top, var box = new OpenSeadragon.Rect(margins.left, margins.top,
$('#contentDiv').width() - (margins.left + margins.right), $('#contentDiv').width() - (margins.left + margins.right),
$('#contentDiv').height() - (margins.top + margins.bottom)); $('#contentDiv').height() - (margins.top + margins.bottom));
// If drawDebuggingRect is implemented, use it to show the box.
self.viewer.drawer.debugRect(box); // This is not implemented by all drawers however.
self.viewer.drawer.drawDebuggingRect(box);
}); });
} }

View File

@ -0,0 +1,114 @@
<!DOCTYPE html>
<html>
<head>
<title>Drawer Comparison Demo</title>
<script type="text/javascript" src='../../build/openseadragon/openseadragon.js'></script>
<script type="text/javascript" src='../lib/jquery-1.9.1.min.js'></script>
<script type="text/javascript" src='../lib/jquery-ui-1.10.2/js/jquery-ui-1.10.2.min.js'></script>
<link rel="stylesheet" href="../lib/jquery-ui-1.10.2/css/smoothness/jquery-ui-1.10.2.min.css">
<script type="module" src="./drawercomparison.js"></script>
<style type="text/css">
.content{
max-width:960px;
margin: 0 auto;
}
.mirrored{
display:grid;
grid-template-columns:50% 50%;
gap: 2em;
}
.viewer-container {
/* width: 600px;
height: 400px; */
aspect-ratio: 4 / 3;
border: thin gray solid;
position:relative;
}
.example-code{
background-color:tan;
border: thin black solid;
padding:10px;
display:inline-block;
width:95%;
}
.description pre{
display:inline-block;
background-color:gainsboro;
padding:0;
margin:0;
}
.image-options{
display: grid;
grid-template-columns: 2em 9em 1fr;
padding:3px;
border: thin gray solid;
}
.option-grid{
display: grid;
grid-template-columns: 7em 7em 9em 9em 10em 9em;
/* grid-template-columns: repeat(5, auto); */
}
.image-options input[type=number]{
width: 5em;
}
.image-options select{
width: 5em;
}
</style>
</head>
<body>
<div class="content">
<h2>Compare behavior of <strong>Context2d</strong> and <strong>WebGL</strong> drawers</h2>
<div class="mirrored">
<div>
<h3 id="title-w1">Loading...</h3>
<div id="canvasdrawer" class="viewer-container"></div>
</div>
<div>
<h3 id="title-w2">Loading...</h3>
<div id="webgl" class="viewer-container"></div>
</div>
</div>
<div id="image-picker">
<h3>Image options (drag and drop to re-order images)</h3>
</div>
<h2>HTMLDrawer: legacy pre-HTML5 drawer that uses &lt;img&gt; elements for tiles</h2>
<div class="mirrored">
<div>
<div class="description">
HTML-based rendering can be selected in two different ways:
</div>
<pre class="example-code">
// via the 'html' drawer option:
let viewer = OpenSeadragon({
...
drawer: 'html',
...
});
// or by passing the HTMLDrawer constructor
let viewer = OpenSeadragon({
...
drawer:OpenSeadragon.HTMLDrawer,
...
});
</pre>
</div>
<div id="htmldrawer" class="viewer-container"></div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,356 @@
const sources = {
"rainbow":"../data/testpattern.dzi",
"leaves":"../data/iiif_2_0_sizes/info.json",
"bblue":{
type:'image',
url: "../data/BBlue.png",
},
"duomo":"https://openseadragon.github.io/example-images/duomo/duomo.dzi",
}
const labels = {
rainbow: 'Rainbow Grid',
leaves: 'Leaves',
bblue: 'Blue B',
duomo: 'Duomo',
}
const drawers = {
canvas: "Context2d drawer (default in OSD &lt;= 4.1.0)",
webgl: "New WebGL drawer"
}
//Support drawer type from the url
const url = new URL(window.location.href);
const drawer1 = url.searchParams.get("left") || 'canvas';
const drawer2 = url.searchParams.get("right") || 'webgl';
$("#title-w1").html(drawers[drawer1]);
$("#title-w2").html(drawers[drawer2]);
//Double viewer setup for comparison - CanvasDrawer and WebGLDrawer
// viewer1: canvas drawer
let viewer1 = window.viewer1 = OpenSeadragon({
id: "canvasdrawer",
prefixUrl: "../../build/openseadragon/images/",
minZoomImageRatio:0.01,
maxZoomPixelRatio:100,
smoothTileEdgesMinZoom:1.1,
crossOriginPolicy: 'Anonymous',
ajaxWithCredentials: false,
// maxImageCacheCount: 30,
drawer:drawer1,
blendTime:0,
showNavigator:true,
});
// viewer2: webgl drawer
let viewer2 = window.viewer2 = OpenSeadragon({
id: "webgl",
prefixUrl: "../../build/openseadragon/images/",
minZoomImageRatio:0.01,
maxZoomPixelRatio:100,
smoothTileEdgesMinZoom:1.1,
crossOriginPolicy: 'Anonymous',
ajaxWithCredentials: false,
// maxImageCacheCount: 30,
drawer:drawer2,
blendTime:0,
showNavigator:true,
});
// // viewer3: html drawer, unused
var viewer3 = window.viewer3 = OpenSeadragon({
id: "htmldrawer",
drawer:'html',
blendTime:2,
prefixUrl: "../../build/openseadragon/images/",
minZoomImageRatio:0.01,
customDrawer: OpenSeadragon.HTMLDrawer,
tileSources: [sources['leaves'], sources['rainbow'], sources['duomo']],
sequenceMode: true,
crossOriginPolicy: 'Anonymous',
ajaxWithCredentials: false
});
// Sync navigation of viewer1 and viewer 2
var viewer1Leading = false;
var viewer2Leading = false;
var viewer1Handler = function() {
if (viewer2Leading) {
return;
}
viewer1Leading = true;
viewer2.viewport.zoomTo(viewer1.viewport.getZoom());
viewer2.viewport.panTo(viewer1.viewport.getCenter());
viewer2.viewport.rotateTo(viewer1.viewport.getRotation());
viewer2.viewport.setFlip(viewer1.viewport.flipped);
viewer1Leading = false;
};
var viewer2Handler = function() {
if (viewer1Leading) {
return;
}
viewer2Leading = true;
viewer1.viewport.zoomTo(viewer2.viewport.getZoom());
viewer1.viewport.panTo(viewer2.viewport.getCenter());
viewer1.viewport.rotateTo(viewer2.viewport.getRotation());
viewer1.viewport.setFlip(viewer1.viewport.flipped);
viewer2Leading = false;
};
viewer1.addHandler('zoom', viewer1Handler);
viewer2.addHandler('zoom', viewer2Handler);
viewer1.addHandler('pan', viewer1Handler);
viewer2.addHandler('pan', viewer2Handler);
viewer1.addHandler('rotate', viewer1Handler);
viewer2.addHandler('rotate', viewer2Handler);
viewer1.addHandler('flip', viewer1Handler);
viewer2.addHandler('flip', viewer2Handler);
$('#image-picker').sortable({
update: function(event, ui){
let thisItem = ui.item.find('.toggle').data('item1');
let items = $('#image-picker input.toggle:checked').toArray().map(item=>$(item).data('item1'));
let newIndex = items.indexOf(thisItem);
if(thisItem){
viewer1.world.setItemIndex(thisItem, newIndex);
}
thisItem = ui.item.find('.toggle').data('item2');
items = $('#image-picker input.toggle:checked').toArray().map(item=>$(item).data('item2'));
newIndex = items.indexOf(thisItem);
if(thisItem){
viewer2.world.setItemIndex(thisItem, newIndex);
}
}
});
Object.keys(sources).forEach((key, index)=>{
let element = makeImagePickerElement(key, labels[key])
$('#image-picker').append(element);
if(index === 0){
element.find('.toggle').prop('checked',true);
}
})
$('#image-picker').append(makeComparisonSwitcher());
$('#image-picker input.toggle').on('change',function(){
let data = $(this).data();
if(this.checked){
addTileSource(viewer1, data.image, this);
addTileSource(viewer2, data.image, this);
} else {
if(data.item1){
viewer1.world.removeItem(data.item1);
viewer2.world.removeItem(data.item2);
$(this).data({item1: null, item2: null});
}
}
}).trigger('change');
$('#image-picker input:not(.toggle)').on('change',function(){
let data = $(this).data();
let value = $(this).val();
let tiledImage1 = $(`#image-picker input.toggle[data-image=${data.image}]`).data('item1');
let tiledImage2 = $(`#image-picker input.toggle[data-image=${data.image}]`).data('item2');
updateTiledImage(tiledImage1, data, value, this);
updateTiledImage(tiledImage2, data, value, this);
});
function updateTiledImage(tiledImage, data, value, item){
let field = data.field;
if(tiledImage){
//item = tiledImage
if(field == 'x'){
let bounds = tiledImage.getBoundsNoRotate();
let position = new OpenSeadragon.Point(Number(value), bounds.y);
tiledImage.setPosition(position);
} else if ( field == 'y'){
let bounds = tiledImage.getBoundsNoRotate();
let position = new OpenSeadragon.Point(bounds.x, Number(value));
tiledImage.setPosition(position);
} else if (field == 'width'){
tiledImage.setWidth(Number(value));
} else if (field == 'degrees'){
tiledImage.setRotation(Number(value));
} else if (field == 'opacity'){
tiledImage.setOpacity(Number(value));
} else if (field == 'flipped'){
tiledImage.setFlip($(item).prop('checked'));
} else if (field == 'cropped'){
if( $(item).prop('checked') ){
let scale = tiledImage.source.width;
let croppingPolygons = [ [{x:0.2*scale, y:0.2*scale}, {x:0.8*scale, y:0.2*scale}, {x:0.5*scale, y:0.8*scale}] ];
tiledImage.setCroppingPolygons(croppingPolygons);
} else {
tiledImage.resetCroppingPolygons();
}
} else if (field == 'clipped'){
if( $(item).prop('checked') ){
let scale = tiledImage.source.width;
let clipRect = new OpenSeadragon.Rect(0.1*scale, 0.2*scale, 0.6*scale, 0.4*scale);
tiledImage.setClip(clipRect);
} else {
tiledImage.setClip(null);
}
} else if (field == 'debug'){
if( $(item).prop('checked') ){
tiledImage.debugMode = true;
} else {
tiledImage.debugMode = false;
}
}
} else {
//viewer-level option
}
}
$('.image-options select[data-field=composite]').append(getCompositeOperationOptions()).on('change',function(){
let data = $(this).data();
let tiledImage1 = $(`#image-picker input.toggle[data-image=${data.image}]`).data('item1');
if(tiledImage1){
tiledImage1.setCompositeOperation(this.value == 'null' ? null : this.value);
}
let tiledImage2 = $(`#image-picker input.toggle[data-image=${data.image}]`).data('item2');
if(tiledImage2){
tiledImage2.setCompositeOperation(this.value == 'null' ? null : this.value);
}
}).trigger('change');
$('.image-options select[data-field=wrapping]').append(getWrappingOptions()).on('change',function(){
let data = $(this).data();
let tiledImage = $(`#image-picker input.toggle[data-image=${data.image}]`).data('item1');
if(tiledImage){
switch(this.value){
case "None": tiledImage.wrapHorizontal = tiledImage.wrapVertical = false; break;
case "Horizontal": tiledImage.wrapHorizontal = true; tiledImage.wrapVertical = false; break;
case "Vertical": tiledImage.wrapHorizontal = false; tiledImage.wrapVertical = true; break;
case "Both": tiledImage.wrapHorizontal = tiledImage.wrapVertical = true; break;
}
tiledImage.redraw();//trigger a redraw for the webgl renderer.
}
tiledImage = $(`#image-picker input.toggle[data-image=${data.image}]`).data('item2');
if(tiledImage){
switch(this.value){
case "None": tiledImage.wrapHorizontal = tiledImage.wrapVertical = false; break;
case "Horizontal": tiledImage.wrapHorizontal = true; tiledImage.wrapVertical = false; break;
case "Vertical": tiledImage.wrapHorizontal = false; tiledImage.wrapVertical = true; break;
case "Both": tiledImage.wrapHorizontal = tiledImage.wrapVertical = true; break;
}
tiledImage.redraw();//trigger a redraw for the webgl renderer.
}
}).trigger('change');
function getWrappingOptions(){
let opts = ['None', 'Horizontal', 'Vertical', 'Both'];
let elements = opts.map((opt, i)=>{
let el = $('<option>',{value:opt}).text(opt);
if(i===0){
el.attr('selected',true);
}
return el[0];
// $('.image-options select').append(el);
});
return $(elements);
}
function getCompositeOperationOptions(){
let opts = [null,'source-over','source-in','source-out','source-atop',
'destination-over','destination-in','destination-out','destination-atop',
'lighten','darken','copy','xor','multiply','screen','overlay','color-dodge',
'color-burn','hard-light','soft-light','difference','exclusion',
'hue','saturation','color','luminosity'];
let elements = opts.map((opt, i)=>{
let el = $('<option>',{value:opt}).text(opt);
if(i===0){
el.attr('selected',true);
}
return el[0];
// $('.image-options select').append(el);
});
return $(elements);
}
function addTileSource(viewer, image, checkbox){
let options = $(`#image-picker input[data-image=${image}][type=number]`).toArray().reduce((acc, input)=>{
let field = $(input).data('field');
if(field){
acc[field] = Number(input.value);
}
return acc;
}, {});
options.flipped = $(`#image-picker input[data-image=${image}][data-type=flipped]`).prop('checked');
let items = $('#image-picker input.toggle:checked').toArray();
let insertionIndex = items.indexOf(checkbox);
let tileSource = sources[image];
if(tileSource){
viewer&&viewer.addTiledImage({tileSource: tileSource, ...options, index: insertionIndex});
viewer&&viewer.world.addOnceHandler('add-item',function(ev){
let item = ev.item;
let field = viewer === viewer1 ? 'item1' : 'item2';
$(checkbox).data(field,item);
// item.source.hasTransparency = ()=>true; //simulate image with transparency, to show seams in default renderer
});
}
}
function getAvailableDrawerSelect(name, selectedDrawer) {
return `
<select name="${name}">
${Object.entries(drawers).map(([k, v]) => {
const selected = selectedDrawer === k ? "selected" : "";
return `<option value="${k}" ${selected}>${v}</option>`;
}).join("\n")}
</select>`;
}
function makeComparisonSwitcher() {
const left = getAvailableDrawerSelect("left", drawer1),
right = getAvailableDrawerSelect("right", drawer2);
return `
<div>
Note: you can run the comparison with desired drawers like this: drawercomparison.html?left=[type]&right=[type]
<form method="get">
${left}
${right}
<button>Submit</button>
</form>
</div>`;
}
function makeImagePickerElement(key, label){
return $(`<div class="image-options">
<span class="ui-icon ui-icon-arrowthick-2-n-s"></span>
<label><input type="checkbox" data-image="" class="toggle"> __title__</label>
<div class="option-grid">
<label>X: <input type="number" value="0" data-image="" data-field="x"> </label>
<label>Y: <input type="number" value="0" data-image="" data-field="y"> </label>
<label>Width: <input type="number" value="1" data-image="" data-field="width" min="0"> </label>
<label>Degrees: <input type="number" value="0" data-image="" data-field="degrees"> </label>
<label>Opacity: <input type="number" value="1" data-image="" data-field="opacity" min="0" max="1" step="0.2"> </label>
<span></span>
<label>Flipped: <input type="checkbox" data-image="" data-field="flipped"></label>
<label>Cropped: <input type="checkbox" data-image="" data-field="cropped"></label>
<label>Clipped: <input type="checkbox" data-image="" data-field="clipped"></label>
<label>Chess Tile Opacity: <input type="checkbox" data-image="" data-field="tile-level-opecity"></label>
<label>Debug: <input type="checkbox" data-image="" data-field="debug"></label>
<label>Composite: <select data-image="" data-field="composite"></select></label>
<label>Wrap: <select data-image="" data-field="wrapping"></select></label>
</div>
</div>`.replaceAll('data-image=""', `data-image="${key}"`).replace('__title__', label));
}

View File

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<title>Drawer Comparison Demo</title>
<script type="text/javascript" src='../../build/openseadragon/openseadragon.js'></script>
<script type="text/javascript" src='../lib/jquery-1.9.1.min.js'></script>
<script type="text/javascript" src='../lib/jquery-ui-1.10.2/js/jquery-ui-1.10.2.min.js'></script>
<link rel="stylesheet" href="../lib/jquery-ui-1.10.2/css/smoothness/jquery-ui-1.10.2.min.css">
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/stats.js/17/Stats.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script> -->
<script type="module" src="./drawerperformance.js"></script>
<style type="text/css">
.content{
max-width:960px;
margin: 0 auto;
}
#drawer{
height:800px;
}
</style>
</head>
<body>
<div class="content">
<h2>Compare performance of drawer implementations</h2>
<div>
<label>Select a drawer: </label>
<select id="select-drawer">
</select>
<label>Num images: </label>
<input id="input-number" type="number" value="1" min="1" step="1">
<button id="create-drawer">Create drawer</button>
</div>
<div id="drawer"></div>
</div>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -96,4 +96,4 @@
</script> </script>
</body> </body>
</html> </html>

87
test/demo/rstats.css Normal file
View File

@ -0,0 +1,87 @@
.alarm{
color: #b70000;
text-shadow: 0 0 0 #b70000,
0 0 1px #fff,
0 0 1px #fff,
0 0 2px #fff,
0 0 2px #fff,
0 0 3px #fff,
0 0 3px #fff,
0 0 4px #fff,
0 0 4px #fff;
}
.rs-base{
position: absolute;
z-index: 10000;
padding: 10px;
background-color: #222;
font-size: 10px;
line-height: 1.2em;
width: 350px;
font-family: 'Roboto Condensed', tahoma, sans-serif;
left: 0;
top: 0;
overflow: hidden;
color:white;
}
.rs-base h1{
margin: 0;
padding: 0;
font-size: 1.4em;
color: #fff;
margin-bottom: 5px;
cursor: pointer;
}
.rs-base div.rs-group{
margin-bottom: 10px;
}
.rs-base div.rs-group.hidden{
display: none;
}
.rs-base div.rs-fraction{
position: relative;
margin-bottom: 5px;
}
.rs-base div.rs-fraction p{
width: 120px;
text-align: right;
margin: 0;
padding: 0;
}
.rs-base div.rs-legend{
position: absolute;
line-height: 1em;
}
.rs-base div.rs-counter-base{
position: relative;
margin: 2px 0;
height: 1em;
}
.rs-base span.rs-counter-id{
position: absolute;
left: 0;
top: 0;
}
.rs-base div.rs-counter-value{
position: absolute;
left: 90px;
width: 30px;
height: 1em;
top: 0;
text-align: right;
}
.rs-base canvas.rs-canvas{
position: absolute;
right: 0;
}

View File

@ -80,12 +80,12 @@
// The changeover will look better if we wait for the first tile to be drawn. // The changeover will look better if we wait for the first tile to be drawn.
var tileDrawnHandler = function(event) { var tileDrawnHandler = function(event) {
if (event.tiledImage === fullImage) { if (event.tiledImage === fullImage) {
viewer.removeHandler('tile-drawn', tileDrawnHandler); viewer.removeHandler('update-viewport', tileDrawnHandler);
viewer.world.removeItem(standin); viewer.world.removeItem(standin);
} }
}; };
viewer.addHandler('tile-drawn', tileDrawnHandler); viewer.addHandler('update-viewport', tileDrawnHandler);
} }
}); });
} }

View File

@ -1,4 +1,4 @@
/* global $, Util */ /* global $, QUnit, Util */
(function () { (function () {
@ -146,9 +146,15 @@
obj0[member0](); obj0[member0]();
assert.equal(called, true, 'called through for ' + member0); assert.equal(called, true, 'called through for ' + member0);
assert.equal(errored, true, 'errored for ' + member0); assert.equal(errored, true, 'errored for ' + member0);
} },
}; };
// Log the name of the currently running test when it starts. Uses console.log rather than
// $.console.log so that the message is printed even after the $.console is diverted (see below).
QUnit.testStart((details) => {
console.log(`Starting test ${details.module}.${details.name}`);
});
/* /*
Test console log capture Test console log capture

View File

@ -27,6 +27,7 @@
}; };
var viewer = null; var viewer = null;
var DONE = false;
var OriginalLoader = OpenSeadragon.ImageLoader; var OriginalLoader = OpenSeadragon.ImageLoader;
var OriginalAjax = OpenSeadragon.makeAjaxRequest; var OriginalAjax = OpenSeadragon.makeAjaxRequest;
@ -156,7 +157,7 @@
ASSERT = null; ASSERT = null;
if (viewer && viewer.close) { if (viewer && viewer.close) {
viewer.close(); DONE ? viewer.destroy () : viewer.close();
} }
viewer = null; viewer = null;
@ -271,6 +272,7 @@
return "d1=a1&d2=a2###"; return "d1=a1&d2=a2###";
}, },
}, true, true); }, true, true);
DONE = true; // mark the module as completed so the viewer can be destroyed
}); });
})(); })();

View File

@ -53,8 +53,8 @@
}); });
}, },
afterEach: function() { afterEach: function() {
if (viewer && viewer.close) { if (viewer){
viewer.close(); viewer.destroy();
} }
viewer = null; viewer = null;

View File

@ -16,8 +16,8 @@
}); });
}, },
afterEach: function () { afterEach: function () {
if (viewer && viewer.close) { if (viewer){
viewer.close(); viewer.destroy();
} }
viewer = null; viewer = null;
@ -223,49 +223,50 @@
viewer.open('/test/data/testpattern.dzi'); viewer.open('/test/data/testpattern.dzi');
}); });
QUnit.test('FullScreen', function(assert) { // TODO: can this be enabled without breaking tests due to lack of short-duration user interaction?
const done = assert.async(); // QUnit.test('FullScreen', function(assert) {
if (!OpenSeadragon.supportsFullScreen) { // const done = assert.async();
assert.expect(0); // if (!OpenSeadragon.supportsFullScreen) {
done(); // assert.expect(0);
return; // done();
} // return;
// }
viewer.addHandler('open', function () { // viewer.addHandler('open', function () {
assert.ok(!OpenSeadragon.isFullScreen(), 'Started out not fullscreen'); // assert.ok(!OpenSeadragon.isFullScreen(), 'Started out not fullscreen');
const checkEnteringPreFullScreen = (event) => { // const checkEnteringPreFullScreen = (event) => {
viewer.removeHandler('pre-full-screen', checkEnteringPreFullScreen); // viewer.removeHandler('pre-full-screen', checkEnteringPreFullScreen);
assert.ok(event.fullScreen, 'Switching to fullscreen'); // assert.ok(event.fullScreen, 'Switching to fullscreen');
assert.ok(!OpenSeadragon.isFullScreen(), 'Not yet fullscreen'); // assert.ok(!OpenSeadragon.isFullScreen(), 'Not yet fullscreen');
}; // };
const checkExitingFullScreen = (event) => { // const checkExitingFullScreen = (event) => {
viewer.removeHandler('full-screen', checkExitingFullScreen); // viewer.removeHandler('full-screen', checkExitingFullScreen);
assert.ok(!event.fullScreen, 'Disabling fullscreen'); // assert.ok(!event.fullScreen, 'Disabling fullscreen');
assert.ok(!OpenSeadragon.isFullScreen(), 'Fullscreen disabled'); // assert.ok(!OpenSeadragon.isFullScreen(), 'Fullscreen disabled');
done(); // done();
} // }
// The 'new' headless mode allows us to enter fullscreen, so verify // // The 'new' headless mode allows us to enter fullscreen, so verify
// that we see the correct values returned. We will then close out // // that we see the correct values returned. We will then close out
// of fullscreen to check the same values when exiting. // // of fullscreen to check the same values when exiting.
const checkAcquiredFullScreen = (event) => { // const checkAcquiredFullScreen = (event) => {
viewer.removeHandler('full-screen', checkAcquiredFullScreen); // viewer.removeHandler('full-screen', checkAcquiredFullScreen);
viewer.addHandler('full-screen', checkExitingFullScreen); // viewer.addHandler('full-screen', checkExitingFullScreen);
assert.ok(event.fullScreen, 'Acquired fullscreen'); // assert.ok(event.fullScreen, 'Acquired fullscreen');
assert.ok(OpenSeadragon.isFullScreen(), 'Fullscreen enabled'); // assert.ok(OpenSeadragon.isFullScreen(), 'Fullscreen enabled');
viewer.setFullScreen(false); // viewer.setFullScreen(false);
}; // };
viewer.addHandler('pre-full-screen', checkEnteringPreFullScreen); // viewer.addHandler('pre-full-screen', checkEnteringPreFullScreen);
viewer.addHandler('full-screen', checkAcquiredFullScreen); // viewer.addHandler('full-screen', checkAcquiredFullScreen);
viewer.setFullScreen(true); // viewer.setFullScreen(true);
}); // });
viewer.open('/test/data/testpattern.dzi'); // viewer.open('/test/data/testpattern.dzi');
}); // });
QUnit.test('Close', function(assert) { QUnit.test('Close', function(assert) {
var done = assert.async(); var done = assert.async();
@ -330,8 +331,8 @@
height: 155 height: 155
} ] } ]
} ); } );
viewer.addOnceHandler('tile-drawn', function() { viewer.addOnceHandler('tiled-image-drawn', function(event) {
assert.ok(OpenSeadragon.isCanvasTainted(viewer.drawer.context.canvas), assert.ok(OpenSeadragon.isCanvasTainted(event.tiles[0].getCanvasContext().canvas),
"Canvas should be tainted."); "Canvas should be tainted.");
done(); done();
}); });
@ -350,8 +351,8 @@
height: 155 height: 155
} ] } ]
} ); } );
viewer.addOnceHandler('tile-drawn', function() { viewer.addOnceHandler('tiled-image-drawn', function(event) {
assert.ok(!OpenSeadragon.isCanvasTainted(viewer.drawer.context.canvas), assert.ok(!OpenSeadragon.isCanvasTainted(event.tiles[0].getCanvasContext().canvas),
"Canvas should not be tainted."); "Canvas should not be tainted.");
done(); done();
}); });
@ -374,8 +375,8 @@
}, },
crossOriginPolicy : false crossOriginPolicy : false
} ); } );
viewer.addOnceHandler('tile-drawn', function() { viewer.addOnceHandler('tiled-image-drawn', function(event) {
assert.ok(OpenSeadragon.isCanvasTainted(viewer.drawer.context.canvas), assert.ok(OpenSeadragon.isCanvasTainted(event.tiles[0].getCanvasContext().canvas),
"Canvas should be tainted."); "Canvas should be tainted.");
done(); done();
}); });
@ -398,8 +399,8 @@
crossOriginPolicy : "Anonymous" crossOriginPolicy : "Anonymous"
} }
} ); } );
viewer.addOnceHandler('tile-drawn', function() { viewer.addOnceHandler('tiled-image-drawn', function(event) {
assert.ok(!OpenSeadragon.isCanvasTainted(viewer.drawer.context.canvas), assert.ok(!OpenSeadragon.isCanvasTainted(event.tiles[0].getCanvasContext().canvas),
"Canvas should not be tainted."); "Canvas should not be tainted.");
done(); done();
}); });

View File

@ -16,8 +16,8 @@
}, },
afterEach: function () { afterEach: function () {
if (viewer && viewer.close) { if (viewer){
viewer.close(); viewer.destroy();
} }
viewer = null; viewer = null;

View File

@ -2,133 +2,154 @@
(function() { (function() {
var viewer; var viewer;
const drawerTypes = ['webgl','canvas','html'];
drawerTypes.forEach(runDrawerTests);
QUnit.module('Drawer', { function runDrawerTests(drawerType){
beforeEach: function () {
$('<div id="example"></div>').appendTo("#qunit-fixture");
testLog.reset(); QUnit.module('Drawer-'+drawerType, {
}, beforeEach: function () {
afterEach: function () { $('<div id="example"></div>').appendTo("#qunit-fixture");
if (viewer && viewer.close) {
viewer.close(); testLog.reset();
},
afterEach: function () {
if (viewer){
viewer.destroy();
}
viewer = null;
} }
viewer = null;
}
});
// ----------
var createViewer = function(options) {
options = options || {};
// eslint-disable-next-line new-cap
viewer = OpenSeadragon(OpenSeadragon.extend({
id: 'example',
prefixUrl: '/build/openseadragon/images/',
springStiffness: 100 // Faster animation = faster tests
}, options));
};
// ----------
QUnit.test('basics', function(assert) {
var done = assert.async();
createViewer();
assert.ok(viewer.drawer, 'Drawer exists');
assert.equal(viewer.drawer.canRotate(), OpenSeadragon.supportsCanvas, 'we can rotate if we have canvas');
done();
});
// ----------
QUnit.test('rotation', function(assert) {
var done = assert.async();
createViewer({
tileSources: '/test/data/testpattern.dzi'
}); });
viewer.addHandler('open', function handler(event) { // ----------
viewer.viewport.setRotation(30, true); var createViewer = function(options) {
Util.spyOnce(viewer.drawer.context, 'rotate', function() { options = options || {};
assert.ok(true, 'drawing with new rotation'); // eslint-disable-next-line new-cap
viewer = OpenSeadragon(OpenSeadragon.extend({
id: 'example',
prefixUrl: '/build/openseadragon/images/',
springStiffness: 100, // Faster animation = faster tests
drawer: drawerType,
}, options));
};
// ----------
QUnit.test('basics', function(assert) {
var done = assert.async();
createViewer();
assert.ok(viewer.drawer, 'Drawer exists');
assert.equal(viewer.drawer.canRotate(), ['webgl','canvas'].includes(drawerType), 'we can rotate if we have canvas');
done();
});
// ----------
QUnit.test('rotation', function(assert) {
var done = assert.async();
createViewer({
tileSources: '/test/data/testpattern.dzi'
});
// this test only makes sense for canvas drawer because of how it is
// detected by watching for the canvas context rotate function
// TODO: add test for actual rotation of the drawn image in a way that
// applies to the webgl drawer as well as the canvas drawer.
if(viewer.drawer.getType() !== 'canvas'){
assert.expect(0);
done();
};
viewer.addHandler('open', function handler(event) {
viewer.viewport.setRotation(30, true);
Util.spyOnce(viewer.drawer.context, 'rotate', function() {
assert.ok(true, 'drawing with new rotation');
done();
});
});
});
// ----------
QUnit.test('debug', function(assert) {
var done = assert.async();
createViewer({
tileSources: '/test/data/testpattern.dzi',
debugMode: true
});
// only test this for canvas and webgl drawers
if( !['canvas','webgl'].includes(viewer.drawer.getType() )){
assert.expect(0);
done()
}
Util.spyOnce(viewer.drawer, '_drawDebugInfo', function() {
assert.ok(true, '_drawDebugInfo is called');
done(); done();
}); });
}); });
});
// ---------- // ----------
QUnit.test('debug', function(assert) { QUnit.test('sketchCanvas', function(assert) {
var done = assert.async(); var done = assert.async();
createViewer({
tileSources: '/test/data/testpattern.dzi',
debugMode: true
});
Util.spyOnce(viewer.drawer, 'drawDebugInfo', function() { createViewer({
assert.ok(true, 'drawDebugInfo is called'); tileSources: '/test/data/testpattern.dzi',
done(); });
}); var drawer = viewer.drawer;
});
// ---------- // this test only makes sense for canvas drawer
QUnit.test('sketchCanvas', function(assert) { if(viewer.drawer.getType() !== 'canvas'){
var done = assert.async(); assert.expect(0);
createViewer({ done();
tileSources: '/test/data/testpattern.dzi' };
});
var drawer = viewer.drawer;
viewer.addHandler('tile-drawn', function noOpacityHandler() { viewer.addHandler('tile-drawn', function noOpacityHandler() {
viewer.removeHandler('tile-drawn', noOpacityHandler); viewer.removeHandler('tile-drawn', noOpacityHandler);
assert.equal(drawer.sketchCanvas, null, assert.equal(drawer.sketchCanvas, null,
'The sketch canvas should be null if no decimal opacity is used.'); 'The sketch canvas should be null if no decimal opacity is used.');
assert.equal(drawer.sketchContext, null, assert.equal(drawer.sketchContext, null,
'The sketch context should be null if no decimal opacity is used.'); 'The sketch context should be null if no decimal opacity is used.');
testOpacityDecimal(); testOpacityDecimal();
});
function testOpacityDecimal() {
var tiledImage;
viewer.addTiledImage({
tileSource: '/test/data/testpattern.dzi',
opacity: 0.5,
success: function(event) {
tiledImage = event.item;
}
}); });
viewer.addHandler('tile-drawn', function opacityDecimalHandler(event) { function testOpacityDecimal() {
if (tiledImage !== event.tiledImage) { var tiledImage;
return; viewer.addTiledImage({
} tileSource: '/test/data/testpattern.dzi',
viewer.removeHandler('tile-drawn', opacityDecimalHandler); opacity: 0.5,
assert.notEqual(drawer.sketchCanvas, null, success: function(event) {
'The sketch canvas should not be null once a decimal opacity has been used.'); tiledImage = event.item;
assert.notEqual(drawer.sketchContext, null, }
'The sketch context should not be null once a decimal opacity has been used.'); });
viewer.addHandler('tile-drawn', function opacityDecimalHandler(event) {
if (tiledImage !== event.tiledImage) {
return;
}
viewer.removeHandler('tile-drawn', opacityDecimalHandler);
assert.notEqual(drawer.sketchCanvas, null,
'The sketch canvas should not be null once a decimal opacity has been used.');
assert.notEqual(drawer.sketchContext, null,
'The sketch context should not be null once a decimal opacity has been used.');
done();
});
}
});
// ----------
QUnit.test('deprecations', function(assert) {
var done = assert.async();
createViewer({
tileSources: '/test/data/testpattern.dzi'
});
viewer.world.addHandler('add-item', function() {
// no current deprecated methods
assert.expect(0);
done(); done();
}); });
}
});
// ----------
QUnit.test('deprecations', function(assert) {
var done = assert.async();
createViewer({
tileSources: '/test/data/testpattern.dzi'
}); });
viewer.world.addHandler('add-item', function() {
Util.testDeprecation(assert, viewer.drawer, 'addOverlay', viewer, 'addOverlay'); }
Util.testDeprecation(assert, viewer.drawer, 'updateOverlay', viewer, 'updateOverlay');
Util.testDeprecation(assert, viewer.drawer, 'removeOverlay', viewer, 'removeOverlay');
Util.testDeprecation(assert, viewer.drawer, 'clearOverlays', viewer, 'clearOverlays');
Util.testDeprecation(assert, viewer.drawer, 'needsUpdate', viewer.world, 'needsDraw');
Util.testDeprecation(assert, viewer.drawer, 'numTilesLoaded', viewer.tileCache, 'numTilesLoaded');
Util.testDeprecation(assert, viewer.drawer, 'reset', viewer.world, 'resetItems');
Util.testDeprecation(assert, viewer.drawer, 'update', viewer.world, 'draw');
Util.testDeprecation(assert, viewer.drawer, 'setOpacity', viewer.world.getItemAt(0), 'setOpacity');
Util.testDeprecation(assert, viewer.drawer, 'getOpacity', viewer.world.getItemAt(0), 'getOpacity');
done();
});
});
})(); })();

View File

@ -17,10 +17,9 @@
} ); } );
}, },
afterEach: function () { afterEach: function () {
if ( viewer && viewer.close ) { if (viewer){
viewer.close(); viewer.destroy();
} }
viewer = null; viewer = null;
} }
} ); } );
@ -1155,6 +1154,12 @@
// ---------- // ----------
QUnit.test( 'Viewer: event count test with \'tile-drawing\'', function (assert) { QUnit.test( 'Viewer: event count test with \'tile-drawing\'', function (assert) {
var done = assert.async(); var done = assert.async();
if(viewer.drawer.getType() !== 'canvas'){
assert.expect(0);
done();
return;
}
assert.ok(viewer.numberOfHandlers('tile-drawing') === 0, assert.ok(viewer.numberOfHandlers('tile-drawing') === 0,
"'tile-drawing' event is empty by default."); "'tile-drawing' event is empty by default.");
@ -1162,6 +1167,7 @@
viewer.removeHandler( 'tile-drawing', tileDrawing ); viewer.removeHandler( 'tile-drawing', tileDrawing );
assert.ok(viewer.numberOfHandlers('tile-drawing') === 0, assert.ok(viewer.numberOfHandlers('tile-drawing') === 0,
"'tile-drawing' deleted: count is 0."); "'tile-drawing' deleted: count is 0.");
viewer.close(); viewer.close();
done(); done();
}; };
@ -1185,6 +1191,12 @@
QUnit.test( 'Viewer: tile-drawing event', function (assert) { QUnit.test( 'Viewer: tile-drawing event', function (assert) {
var done = assert.async(); var done = assert.async();
if(viewer.drawer.getType() !== 'canvas'){
assert.expect(0);
done();
return;
}
var tileDrawing = function ( event ) { var tileDrawing = function ( event ) {
viewer.removeHandler( 'tile-drawing', tileDrawing ); viewer.removeHandler( 'tile-drawing', tileDrawing );
assert.ok( event, 'Event handler should be invoked' ); assert.ok( event, 'Event handler should be invoked' );

View File

@ -5,15 +5,23 @@
// This module tests whether our various file formats can be opened. // This module tests whether our various file formats can be opened.
// TODO: Add more file formats (with corresponding test data). // TODO: Add more file formats (with corresponding test data).
var viewer = null;
QUnit.module('Formats', { QUnit.module('Formats', {
beforeEach: function () { beforeEach: function () {
var example = document.createElement("div"); var example = document.createElement("div");
example.id = "example"; example.id = "example";
document.getElementById("qunit-fixture").appendChild(example); document.getElementById("qunit-fixture").appendChild(example);
},
afterEach: function () {
if (viewer){
viewer.destroy();
}
viewer = null;
} }
}); });
var viewer = null;
// ---------- // ----------
var testOpenUrl = function(relativeUrl, assert) { var testOpenUrl = function(relativeUrl, assert) {
@ -34,12 +42,12 @@
var openHandler = function(event) { var openHandler = function(event) {
viewer.removeHandler('open', openHandler); viewer.removeHandler('open', openHandler);
assert.ok(true, 'Open event was sent'); assert.ok(true, 'Open event was sent');
viewer.addHandler('tile-drawn', tileDrawnHandler); viewer.addHandler('tiled-image-drawn', tileDrawnHandler);
}; };
var tileDrawnHandler = function(event) { var tileDrawnHandler = function(event) {
viewer.removeHandler('tile-drawn', tileDrawnHandler); viewer.removeHandler('tiled-image-drawn', tileDrawnHandler);
assert.ok(true, 'A tile has been drawn'); assert.ok(true, 'A tiled image has been drawn');
viewer.addHandler('close', closeHandler); viewer.addHandler('close', closeHandler);
viewer.close(); viewer.close();
}; };

View File

@ -15,8 +15,8 @@
testLog.reset(); testLog.reset();
}, },
afterEach: function () { afterEach: function () {
if (viewer && viewer.close) { if (viewer){
viewer.close(); viewer.destroy();
} }
viewer = null; viewer = null;

View File

@ -2,275 +2,287 @@
( function() { ( function() {
var viewer; var viewer;
const drawerTypes = ['webgl','canvas'];
drawerTypes.forEach(runDrawerTests);
QUnit.module( 'Multi-Image', { function runDrawerTests(drawerType){
beforeEach: function() {
$( '<div id="example"></div>' ).appendTo( "#qunit-fixture" );
testLog.reset(); QUnit.module( 'Multi-Image-'+drawerType, {
beforeEach: function() {
$( '<div id="example"></div>' ).appendTo( "#qunit-fixture" );
viewer = OpenSeadragon( { testLog.reset();
id: 'example',
prefixUrl: '/build/openseadragon/images/',
springStiffness: 100 // Faster animation = faster tests
});
},
afterEach: function() {
if ( viewer && viewer.close ) {
viewer.close();
}
viewer = null; viewer = OpenSeadragon( {
$("#example").remove(); id: 'example',
} prefixUrl: '/build/openseadragon/images/',
} ); springStiffness: 100, // Faster animation = faster tests
drawer: drawerType
// ---------- });
QUnit.test( 'Multi-image operations', function(assert) { },
var done = assert.async(); afterEach: function() {
assert.expect( 24 ); if (viewer){
viewer.addHandler( "open", function( ) { viewer.destroy();
assert.equal( 1, viewer.world.getItemCount( ),
"One item should be present after opening." );
var options = {
tileSource: {
type: 'legacy-image-pyramid',
levels: [ {
url: "data/A.png",
width: 1000,
height: 1000
} ]
} }
};
viewer.addTiledImage( options );
viewer.world.addHandler( "add-item", function addFirstItemHandler( event ) {
viewer.world.removeHandler( "add-item", addFirstItemHandler );
var item1 = event.item;
assert.equal( viewer.world.getItemCount( ), 2,
"2 items should be present after adding a item." );
assert.equal( viewer.world.getIndexOfItem( item1 ), 1,
"The first added item should have a index of 1" );
assert.equal( viewer.world.getItemAt( 1 ), item1,
"The item at index 1 should be the first added item." );
viewer = null;
$("#example").remove();
}
} );
// ----------
QUnit.test( 'Multi-image operations', function(assert) {
var done = assert.async();
assert.expect( 24 );
viewer.addHandler( "open", function( ) {
assert.equal( 1, viewer.world.getItemCount( ),
"One item should be present after opening." );
var options = {
tileSource: {
type: 'legacy-image-pyramid',
levels: [ {
url: "data/A.png",
width: 1000,
height: 1000
} ]
}
};
viewer.addTiledImage( options ); viewer.addTiledImage( options );
viewer.world.addHandler( "add-item", function addSecondItemHandler( event ) { viewer.world.addHandler( "add-item", function addFirstItemHandler( event ) {
viewer.world.removeHandler( "add-item", addSecondItemHandler ); viewer.world.removeHandler( "add-item", addFirstItemHandler );
var item2 = event.item; var item1 = event.item;
assert.equal( viewer.world.getItemCount( ), 3, assert.equal( viewer.world.getItemCount( ), 2,
"3 items should be present after adding a second item." ); "2 items should be present after adding a item." );
assert.equal( viewer.world.getIndexOfItem( item2 ), 2, assert.equal( viewer.world.getIndexOfItem( item1 ), 1,
"If not specified, a item should be added with the highest index." ); "The first added item should have a index of 1" );
assert.equal( viewer.world.getItemAt( 2 ), item2, assert.equal( viewer.world.getItemAt( 1 ), item1,
"The item at index 2 should be the second added item." ); "The item at index 1 should be the first added item." );
viewer.world.addHandler( "item-index-change",
function itemIndexChangedHandler( event ) {
viewer.world.removeHandler( "item-index-change",
itemIndexChangedHandler );
assert.equal( event.item, item2,
"The item which changed index should be item2" );
assert.equal( event.previousIndex, 2, "Previous index should be 2." );
assert.equal( event.newIndex, 1, "New index should be 1." );
});
viewer.world.setItemIndex( item2, 1 );
assert.equal( viewer.world.getIndexOfItem( item2 ), 1,
"Item2 index should be 1 after setItemIndex." );
assert.equal( viewer.world.getIndexOfItem( item1 ), 2,
"Item1 index should be 2 after setItemIndex." );
assert.equal( viewer.world.getItemAt( 1 ), item2,
"The item at index 1 should be item2." );
assert.equal( viewer.world.getItemAt( 2 ), item1,
"The item at index 2 should be item1." );
options.index = 2;
options.tileSource.levels[0].url = "data/CCyan.png";
viewer.addTiledImage( options ); viewer.addTiledImage( options );
viewer.world.addHandler( "add-item", function addThirdItemHandler( event ) { viewer.world.addHandler( "add-item", function addSecondItemHandler( event ) {
viewer.world.removeHandler( "add-item", addThirdItemHandler ); viewer.world.removeHandler( "add-item", addSecondItemHandler );
var item3 = event.item; var item2 = event.item;
assert.equal( viewer.world.getItemCount( ), 4, assert.equal( viewer.world.getItemCount( ), 3,
"4 items should be present after adding a third item." ); "3 items should be present after adding a second item." );
assert.equal( viewer.world.getIndexOfItem( item3 ), 2, assert.equal( viewer.world.getIndexOfItem( item2 ), 2,
"Item 3 should be added with index 2." ); "If not specified, a item should be added with the highest index." );
assert.equal( viewer.world.getItemAt( 2 ), item2,
"The item at index 2 should be the second added item." );
viewer.world.addHandler( "item-index-change",
function itemIndexChangedHandler( event ) {
viewer.world.removeHandler( "item-index-change",
itemIndexChangedHandler );
assert.equal( event.item, item2,
"The item which changed index should be item2" );
assert.equal( event.previousIndex, 2, "Previous index should be 2." );
assert.equal( event.newIndex, 1, "New index should be 1." );
});
viewer.world.setItemIndex( item2, 1 );
assert.equal( viewer.world.getIndexOfItem( item2 ), 1, assert.equal( viewer.world.getIndexOfItem( item2 ), 1,
"Item 2 should stay at index 1." ); "Item2 index should be 1 after setItemIndex." );
assert.equal( viewer.world.getIndexOfItem( item1 ), 2,
"Item1 index should be 2 after setItemIndex." );
assert.equal( viewer.world.getItemAt( 1 ), item2,
"The item at index 1 should be item2." );
assert.equal( viewer.world.getItemAt( 2 ), item1,
"The item at index 2 should be item1." );
options.index = 2; options.index = 2;
options.replace = true; options.tileSource.levels[0].url = "data/CCyan.png";
viewer.addTiledImage( options ); viewer.addTiledImage( options );
viewer.world.addHandler( "add-item", function replaceAddItemHandler( event ) { viewer.world.addHandler( "add-item", function addThirdItemHandler( event ) {
viewer.world.removeHandler( "add-item", replaceAddItemHandler ); viewer.world.removeHandler( "add-item", addThirdItemHandler );
var item4 = event.item; var item3 = event.item;
assert.equal( viewer.world.getItemCount( ), 4, assert.equal( viewer.world.getItemCount( ), 4,
"4 items should still be present after replacing the second item." ); "4 items should be present after adding a third item." );
assert.equal( viewer.world.getIndexOfItem( item4 ), 2, assert.equal( viewer.world.getIndexOfItem( item3 ), 2,
"Item 4 should be added with index 2." ); "Item 3 should be added with index 2." );
assert.equal( viewer.world.getIndexOfItem( item3 ), -1, assert.equal( viewer.world.getIndexOfItem( item2 ), 1,
"Item 3 should be at index -1." ); "Item 2 should stay at index 1." );
viewer.world.addHandler( "remove-item", function removeItemHandler( event ) { options.index = 2;
viewer.world.removeHandler( "remove-item", removeItemHandler ); options.replace = true;
viewer.addTiledImage( options );
viewer.world.addHandler( "add-item", function replaceAddItemHandler( event ) {
viewer.world.removeHandler( "add-item", replaceAddItemHandler );
var item4 = event.item;
assert.equal( viewer.world.getItemCount( ), 4,
"4 items should still be present after replacing the second item." );
assert.equal( viewer.world.getIndexOfItem( item4 ), 2,
"Item 4 should be added with index 2." );
assert.equal( viewer.world.getIndexOfItem( item3 ), -1,
"Item 3 should be at index -1." );
assert.equal( item2, event.item, "Removed item should be item2." ); viewer.world.addHandler( "remove-item", function removeItemHandler( event ) {
viewer.world.removeHandler( "remove-item", removeItemHandler );
assert.equal( viewer.world.getIndexOfItem( item1 ), 2, assert.equal( item2, event.item, "Removed item should be item2." );
"Item 1 should be at index 2." );
assert.equal( viewer.world.getIndexOfItem( item2 ), -1,
"Item 2 should be at index -1." );
assert.equal( viewer.world.getIndexOfItem( item4 ), 1,
"Item 4 should be at index 1." );
done(); assert.equal( viewer.world.getIndexOfItem( item1 ), 2,
"Item 1 should be at index 2." );
assert.equal( viewer.world.getIndexOfItem( item2 ), -1,
"Item 2 should be at index -1." );
assert.equal( viewer.world.getIndexOfItem( item4 ), 1,
"Item 4 should be at index 1." );
done();
});
viewer.world.removeItem( item2 );
}); });
viewer.world.removeItem( item2 );
}); });
}); });
}); });
}); });
}); viewer.open( '/test/data/testpattern.dzi' );
viewer.open( '/test/data/testpattern.dzi' );
});
// ----------
QUnit.test( 'Sequences as items', function(assert) {
var done = assert.async();
var options = {
tileSource: [{
type: 'legacy-image-pyramid',
levels: [{
url: "data/A.png",
width: 1000,
height: 1000
}]
}, {
type: 'legacy-image-pyramid',
levels: [{
url: "data/BBlue.png",
width: 1000,
height: 1000
}]
}]
};
viewer.addHandler( "open", function openHandler() {
viewer.removeHandler( "open", openHandler );
viewer.addHandler( "add-item-failed",
function addItemFailedHandler( event ) {
viewer.removeHandler( "add-item-failed", addItemFailedHandler );
assert.equal( event.message, "[Viewer.addTiledImage] Sequences can not be added; add them one at a time instead." );
assert.equal( event.options, options, "Item failed event should give the options." );
done();
} );
viewer.addTiledImage( options );
});
viewer.open( '/test/data/testpattern.dzi' );
});
// ----------
QUnit.test('items are added in order', function(assert) {
var done = assert.async();
viewer.addHandler('open', function(event) {
assert.equal(viewer.world.getItemAt(0).getContentSize().y, 2000, 'first image is tall');
assert.equal(viewer.world.getItemAt(0).getBounds().width, 4, 'first image has 4 width');
assert.equal(viewer.world.getItemAt(1).getContentSize().x, 2000, 'second image is wide');
assert.equal(viewer.world.getItemAt(1).getBounds().width, 2, 'second image has 2 width');
done();
}); });
viewer.open([ // ----------
{ QUnit.test( 'Sequences as items', function(assert) {
tileSource: '/test/data/tall.dzi', var done = assert.async();
width: 4 var options = {
}, { tileSource: [{
tileSource: '/test/data/wide.dzi', type: 'legacy-image-pyramid',
width: 2 levels: [{
} url: "data/A.png",
]); width: 1000,
}); height: 1000
}]
}, {
type: 'legacy-image-pyramid',
levels: [{
url: "data/BBlue.png",
width: 1000,
height: 1000
}]
}]
};
QUnit.test('Viewer.addSimpleImage', function(assert) { viewer.addHandler( "open", function openHandler() {
var done = assert.async(); viewer.removeHandler( "open", openHandler );
viewer.addHandler("open", function openHandler() {
viewer.removeHandler("open", openHandler);
viewer.world.addHandler('add-item', function itemAdded(event) { viewer.addHandler( "add-item-failed",
viewer.world.removeHandler('add-item', itemAdded); function addItemFailedHandler( event ) {
assert.equal(event.item.opacity, 0.5, viewer.removeHandler( "add-item-failed", addItemFailedHandler );
'Opacity option should be set when using addSimpleImage'); assert.equal( event.message, "[Viewer.addTiledImage] Sequences can not be added; add them one at a time instead." );
assert.equal( event.options, options, "Item failed event should give the options." );
done();
} );
viewer.addTiledImage( options );
});
viewer.open( '/test/data/testpattern.dzi' );
});
// ----------
QUnit.test('items are added in order', function(assert) {
var done = assert.async();
viewer.addHandler('open', function(event) {
assert.equal(viewer.world.getItemAt(0).getContentSize().y, 2000, 'first image is tall');
assert.equal(viewer.world.getItemAt(0).getBounds().width, 4, 'first image has 4 width');
assert.equal(viewer.world.getItemAt(1).getContentSize().x, 2000, 'second image is wide');
assert.equal(viewer.world.getItemAt(1).getBounds().width, 2, 'second image has 2 width');
done(); done();
}); });
viewer.addSimpleImage({ viewer.open([
url: '/test/data/A.png', {
opacity: 0.5 tileSource: '/test/data/tall.dzi',
}); width: 4
}, {
tileSource: '/test/data/wide.dzi',
width: 2
}
]);
}); });
viewer.open('/test/data/testpattern.dzi');
});
QUnit.test('Transparent image on top of others', function(assert) { QUnit.test('Viewer.addSimpleImage', function(assert) {
var done = assert.async(); var done = assert.async();
viewer.open('/test/data/testpattern.dzi'); viewer.addHandler("open", function openHandler() {
viewer.removeHandler("open", openHandler);
var density = OpenSeadragon.pixelDensityRatio; viewer.world.addHandler('add-item', function itemAdded(event) {
viewer.world.removeHandler('add-item', itemAdded);
viewer.addHandler('open', function() { assert.equal(event.item.opacity, 0.5,
var firstImage = viewer.world.getItemAt(0); 'Opacity option should be set when using addSimpleImage');
firstImage.addHandler('fully-loaded-change', function() { done();
var imageData = viewer.drawer.context.getImageData(0, 0, });
500 * density, 500 * density);
// Pixel 250,250 will be in the hole of the A
var expectedVal = getPixelValue(imageData, 250 * density, 250 * density);
assert.notEqual(expectedVal.r, 0, 'Red channel should not be 0');
assert.notEqual(expectedVal.g, 0, 'Green channel should not be 0');
assert.notEqual(expectedVal.b, 0, 'Blue channel should not be 0');
assert.notEqual(expectedVal.a, 0, 'Alpha channel should not be 0');
viewer.addSimpleImage({ viewer.addSimpleImage({
url: '/test/data/A.png', url: '/test/data/A.png',
success: function() { opacity: 0.5
var secondImage = viewer.world.getItemAt(1);
secondImage.addHandler('fully-loaded-change', function() {
var imageData = viewer.drawer.context.getImageData(0, 0, 500 * density, 500 * density);
var actualVal = getPixelValue(imageData, 250 * density, 250 * density);
assert.equal(actualVal.r, expectedVal.r,
'Red channel should not change in transparent part of the A');
assert.equal(actualVal.g, expectedVal.g,
'Green channel should not change in transparent part of the A');
assert.equal(actualVal.b, expectedVal.b,
'Blue channel should not change in transparent part of the A');
assert.equal(actualVal.a, expectedVal.a,
'Alpha channel should not change in transparent part of the A');
var onAVal = getPixelValue(imageData, 333 * density, 250 * density);
assert.equal(onAVal.r, 0, 'Red channel should be null on the A');
assert.equal(onAVal.g, 0, 'Green channel should be null on the A');
assert.equal(onAVal.b, 0, 'Blue channel should be null on the A');
assert.equal(onAVal.a, 255, 'Alpha channel should be 255 on the A');
done();
});
}
}); });
}); });
viewer.open('/test/data/testpattern.dzi');
}); });
function getPixelValue(imageData, x, y) { QUnit.test('Transparent image on top of others', function(assert) {
var offset = 4 * (y * imageData.width + x); var done = assert.async();
return { viewer.open('/test/data/testpattern.dzi');
r: imageData.data[offset],
g: imageData.data[offset + 1], var density = OpenSeadragon.pixelDensityRatio;
b: imageData.data[offset + 2],
a: imageData.data[offset + 3] viewer.addHandler('open', function() {
}; var firstImage = viewer.world.getItemAt(0);
} firstImage.addHandler('fully-loaded-change', function() {
}); viewer.addOnceHandler('update-viewport', function(){
var imageData = viewer.drawer.context.getImageData(0, 0,
500 * density, 500 * density);
// Pixel 250,250 will be in the hole of the A
var expectedVal = getPixelValue(imageData, 250 * density, 250 * density);
assert.notEqual(expectedVal.r, 0, 'Red channel should not be 0');
assert.notEqual(expectedVal.g, 0, 'Green channel should not be 0');
assert.notEqual(expectedVal.b, 0, 'Blue channel should not be 0');
assert.notEqual(expectedVal.a, 0, 'Alpha channel should not be 0');
viewer.addSimpleImage({
url: '/test/data/A.png',
success: function() {
var secondImage = viewer.world.getItemAt(1);
secondImage.addHandler('fully-loaded-change', function() {
viewer.addOnceHandler('update-viewport',function(){
var imageData = viewer.drawer.context.getImageData(0, 0, 500 * density, 500 * density);
var actualVal = getPixelValue(imageData, 250 * density, 250 * density);
assert.equal(actualVal.r, expectedVal.r,
'Red channel should not change in transparent part of the A');
assert.equal(actualVal.g, expectedVal.g,
'Green channel should not change in transparent part of the A');
assert.equal(actualVal.b, expectedVal.b,
'Blue channel should not change in transparent part of the A');
assert.equal(actualVal.a, expectedVal.a,
'Alpha channel should not change in transparent part of the A');
var onAVal = getPixelValue(imageData, 333 * density, 250 * density);
assert.equal(onAVal.r, 0, 'Red channel should be null on the A');
assert.equal(onAVal.g, 0, 'Green channel should be null on the A');
assert.equal(onAVal.b, 0, 'Blue channel should be null on the A');
assert.equal(onAVal.a, 255, 'Alpha channel should be 255 on the A');
done();
});
// trigger a redraw so the event fires
firstImage.redraw();
});
}
});
});
});
});
function getPixelValue(imageData, x, y) {
var offset = 4 * (y * imageData.width + x);
return {
r: imageData.data[offset],
g: imageData.data[offset + 1],
b: imageData.data[offset + 2],
a: imageData.data[offset + 3]
};
}
});
}
})(); })();

View File

@ -41,6 +41,12 @@
} }
resetTestVariables(); resetTestVariables();
if (viewer){
viewer.destroy();
}
viewer = null;
} }
}); });

View File

@ -16,12 +16,22 @@
}, },
afterEach: function() { afterEach: function() {
resetTestVariables(); resetTestVariables();
if (viewer){
viewer.destroy();
}
viewer = null;
} }
}); });
var resetTestVariables = function() { var resetTestVariables = function() {
if (viewer) { if (viewer){
viewer.close(); let errors = viewer.drawer._numGlMaxTextureErrors;
if(errors > 0){
console.log('Number of times MAX_TEXTURE_IMAGE_UNITS had a bad value:', errors);
}
viewer.destroy();
} }
}; };

View File

@ -10,8 +10,8 @@
testLog.reset(); testLog.reset();
}, },
afterEach: function () { afterEach: function () {
if (viewer && viewer.close) { if (viewer){
viewer.close(); viewer.destroy();
} }
viewer = null; viewer = null;

View File

@ -1,7 +1,9 @@
/* global QUnit, $, Util, testLog */ /* global QUnit, $, Util, testLog */
(function() { (function() {
var viewer; let viewer;
let precision = 0.000000001;
QUnit.module('TiledImage', { QUnit.module('TiledImage', {
beforeEach: function() { beforeEach: function() {
@ -13,12 +15,13 @@
viewer = OpenSeadragon({ viewer = OpenSeadragon({
id: 'example', id: 'example',
prefixUrl: '/build/openseadragon/images/', prefixUrl: '/build/openseadragon/images/',
springStiffness: 100 // Faster animation = faster tests springStiffness: 100, // Faster animation = faster tests
drawer: 'canvas', // always use canvas drawer for these tests
}); });
}, },
afterEach: function() { afterEach: function() {
if (viewer && viewer.close) { if (viewer){
viewer.close(); viewer.destroy();
} }
viewer = null; viewer = null;
@ -119,8 +122,8 @@
viewer.addHandler('animation-finish', function animationHandler() { viewer.addHandler('animation-finish', function animationHandler() {
viewer.removeHandler('animation-finish', animationHandler); viewer.removeHandler('animation-finish', animationHandler);
assert.propEqual(image.getBounds(), new OpenSeadragon.Rect(1, 2, 3, 3), 'target bounds after animation'); Util.assertRectangleEquals(assert, new OpenSeadragon.Rect(1, 2, 3, 3), image.getBounds(), precision, 'target bounds after animation');
assert.propEqual(image.getBounds(true), new OpenSeadragon.Rect(1, 2, 3, 3), 'current bounds after animation'); Util.assertRectangleEquals(assert, new OpenSeadragon.Rect(1, 2, 3, 3), image.getBounds(true), precision, 'target bounds after animation');
done(); done();
}); });
}); });
@ -132,6 +135,7 @@
QUnit.test('update', function(assert) { QUnit.test('update', function(assert) {
var done = assert.async(); var done = assert.async();
var handlerCount = 0; var handlerCount = 0;
let expectedHandlers = 4;
viewer.addHandler('open', function(event) { viewer.addHandler('open', function(event) {
var image = viewer.world.getItemAt(0); var image = viewer.world.getItemAt(0);
@ -160,6 +164,7 @@
assert.ok(event.tile, 'update-tile event includes tile'); assert.ok(event.tile, 'update-tile event includes tile');
}); });
viewer.addHandler('tile-drawing', function tileDrawingHandler(event) { viewer.addHandler('tile-drawing', function tileDrawingHandler(event) {
viewer.removeHandler('tile-drawing', tileDrawingHandler); viewer.removeHandler('tile-drawing', tileDrawingHandler);
handlerCount++; handlerCount++;
@ -170,18 +175,20 @@
assert.ok(event.rendered, 'tile-drawing event includes a rendered'); assert.ok(event.rendered, 'tile-drawing event includes a rendered');
}); });
viewer.addHandler('tile-drawn', function tileDrawnHandler(event) {
viewer.removeHandler('tile-drawn', tileDrawnHandler);
handlerCount++;
assert.equal(event.eventSource, viewer, 'sender of tile-drawn event was viewer');
assert.equal(event.tiledImage, image, 'tiledImage of update-level event is correct');
assert.ok(event.tile, 'tile-drawn event includes tile');
assert.equal(handlerCount, 4, 'correct number of handlers called');
viewer.addHandler('tiled-image-drawn', function tileDrawnHandler(event) {
viewer.removeHandler('tiled-image-drawn', tileDrawnHandler);
handlerCount++;
assert.equal(event.eventSource, viewer, 'sender of tiled-image-drawn event was viewer');
assert.equal(event.tiledImage, image, 'tiledImage of update-level event is correct');
assert.ok(event.tiles, 'tiled-image-drawn event includes tiles');
assert.equal(handlerCount, expectedHandlers, 'correct number of handlers called');
done(); done();
}); });
image.draw(); viewer.drawer.draw( [ image ] );
}); });
viewer.open('/test/data/testpattern.dzi'); viewer.open('/test/data/testpattern.dzi');
@ -190,14 +197,14 @@
// ---------- // ----------
QUnit.test('reset', function(assert) { QUnit.test('reset', function(assert) {
var done = assert.async(); var done = assert.async();
viewer.addHandler('tile-drawn', function updateHandler() { viewer.addHandler('tiled-image-drawn', function updateHandler() {
viewer.removeHandler('tile-drawn', updateHandler); viewer.removeHandler('tiled-image-drawn', updateHandler);
assert.ok(viewer.tileCache.numTilesLoaded() > 0, 'we have tiles after tile-drawn'); assert.ok(viewer.tileCache.numTilesLoaded() > 0, 'we have tiles after tiled-image-drawn');
viewer.world.getItemAt(0).reset(); viewer.world.getItemAt(0).reset();
assert.equal(viewer.tileCache.numTilesLoaded(), 0, 'no tiles after reset'); assert.equal(viewer.tileCache.numTilesLoaded(), 0, 'no tiles after reset');
viewer.addHandler('tile-drawn', function updateHandler2() { viewer.addHandler('tiled-image-drawn', function updateHandler2() {
viewer.removeHandler('tile-drawn', updateHandler2); viewer.removeHandler('tiled-image-drawn', updateHandler2);
assert.ok(viewer.tileCache.numTilesLoaded() > 0, 'more tiles load'); assert.ok(viewer.tileCache.numTilesLoaded() > 0, 'more tiles load');
viewer.world.getItemAt(0).destroy(); viewer.world.getItemAt(0).destroy();
assert.equal(viewer.tileCache.numTilesLoaded(), 0, 'no tiles after destroy'); assert.equal(viewer.tileCache.numTilesLoaded(), 0, 'no tiles after destroy');
@ -225,7 +232,7 @@
image.setClip(clip); image.setClip(clip);
assert.propEqual(image.getClip(), clip, 'clip is set correctly'); assert.propEqual(image.getClip(), clip, 'clip is set correctly');
Util.spyOnce(viewer.drawer, 'setClip', function(rect) { Util.spyOnce(viewer.drawer, '_setClip', function(rect) {
var homeBounds = viewer.viewport.getHomeBounds(); var homeBounds = viewer.viewport.getHomeBounds();
var canvasClip = viewer.drawer var canvasClip = viewer.drawer
.viewportToDrawerRectangle(homeBounds); .viewportToDrawerRectangle(homeBounds);

View File

@ -4,6 +4,7 @@
(function() { (function() {
var ASSERT = null; var ASSERT = null;
var done = null;
var DYNAMIC_URL = ""; var DYNAMIC_URL = "";
var viewer = null; var viewer = null;
var OriginalAjax = OpenSeadragon.makeAjaxRequest; var OriginalAjax = OpenSeadragon.makeAjaxRequest;
@ -16,9 +17,10 @@
/** /**
* Set up shared variables for test * Set up shared variables for test
*/ */
var configure = function(assert, url) { var configure = function(assert, url, assertAsyncDone) {
ASSERT = assert; ASSERT = assert;
DYNAMIC_URL = url; DYNAMIC_URL = url;
done = assertAsyncDone;
firstUrlPromise = new Promise(resolve => { firstUrlPromise = new Promise(resolve => {
firstUrlPromiseResolver = () => { firstUrlPromiseResolver = () => {
isFirstUrlPromiseResolved = true; isFirstUrlPromiseResolved = true;
@ -94,6 +96,7 @@
// Otherwise close viewer // Otherwise close viewer
if (isFirstUrlPromiseResolved) { if (isFirstUrlPromiseResolved) {
viewer.close(); viewer.close();
done();
} else { } else {
firstUrlPromiseResolver(); firstUrlPromiseResolver();
} }
@ -119,7 +122,11 @@
OpenSeadragon.extend( Tile.prototype, OpenSeadragon.Tile.prototype, { OpenSeadragon.extend( Tile.prototype, OpenSeadragon.Tile.prototype, {
getUrl: function() { getUrl: function() {
ASSERT.ok(true, 'Tile.getUrl called'); // if ASSERT is still truthy, call ASSERT.ok. If the viewer
// has already been destroyed and ASSERT has set to null, ignore this
if(ASSERT){
ASSERT.ok(true, 'Tile.getUrl called');
}
return OriginalTile.prototype.getUrl.apply(this); return OriginalTile.prototype.getUrl.apply(this);
} }
}); });
@ -129,9 +136,10 @@
afterEach: function () { afterEach: function () {
ASSERT = null; ASSERT = null;
if (viewer && viewer.close) { if (viewer){
viewer.close(); viewer.destroy();
} }
viewer = null; viewer = null;
OpenSeadragon.makeAjaxRequest = OriginalAjax; OpenSeadragon.makeAjaxRequest = OriginalAjax;
@ -188,11 +196,12 @@
// ---------- // ----------
QUnit.test('TileSource.getTileUrl supports returning a function', function(assert) { QUnit.test('TileSource.getTileUrl supports returning a function', function(assert) {
configure(assert, 'dynamicUrl'); const done = assert.async();
configure(assert, 'dynamicUrl', done);
const viewer = testUrlCall('dynamicUrl'); const viewer = testUrlCall('dynamicUrl');
firstUrlPromise.then(() => { firstUrlPromise.then(() => {
// after querying with first dynamic url, update the url and trigger new request // after querying with first dynamic url, update the url and trigger new request
DYNAMIC_URL = 'dyanmicUrl2'; DYNAMIC_URL = 'dynamicUrl2';
delete viewer.world.getItemAt(0).tilesMatrix[1][0][0]; delete viewer.world.getItemAt(0).tilesMatrix[1][0][0];
}) })
}); });

View File

@ -19,8 +19,8 @@
}); });
}, },
afterEach: function () { afterEach: function () {
if (viewer && viewer.close) { if (viewer){
viewer.close(); viewer.destroy();
} }
viewer = null; viewer = null;

View File

@ -25,10 +25,10 @@
}, },
afterEach: function () { afterEach: function () {
if (viewer1 && viewer1.destroy) { if (viewer1){
viewer1.destroy(); viewer1.destroy();
} }
if (viewer2 && viewer2.destroy) { if (viewer2){
viewer2.destroy(); viewer2.destroy();
} }
viewer1 = viewer2 = null; viewer1 = viewer2 = null;

View File

@ -21,8 +21,8 @@
}); });
}, },
afterEach: function () { afterEach: function () {
if (viewer && viewer.close) { if (viewer){
viewer.close(); viewer.destroy();
} }
viewer = null; viewer = null;
@ -80,6 +80,9 @@
springStiffness: SPRING_STIFFNESS springStiffness: SPRING_STIFFNESS
}; };
if (viewer){
viewer.destroy();
}
viewerConfig[config.property] = level; viewerConfig[config.property] = level;
viewer = OpenSeadragon(viewerConfig); viewer = OpenSeadragon(viewerConfig);
viewer.addOnceHandler('open', openHandler); viewer.addOnceHandler('open', openHandler);
@ -96,6 +99,10 @@
}; };
viewerConfig[config.property] = level; viewerConfig[config.property] = level;
if (viewer){
viewer.destroy();
}
viewer = OpenSeadragon(viewerConfig); viewer = OpenSeadragon(viewerConfig);
viewer.addOnceHandler('open', openHandler); viewer.addOnceHandler('open', openHandler);
viewer.open(DZI_PATH); viewer.open(DZI_PATH);
@ -372,6 +379,9 @@
); );
i++; i++;
if (i < testZoomLevels.length) { if (i < testZoomLevels.length) {
if (viewer){
viewer.destroy();
}
viewer = OpenSeadragon({ viewer = OpenSeadragon({
id: VIEWER_ID, id: VIEWER_ID,
prefixUrl: PREFIX_URL, prefixUrl: PREFIX_URL,
@ -385,6 +395,9 @@
done(); done();
} }
}; };
if (viewer){
viewer.destroy();
}
viewer = OpenSeadragon({ viewer = OpenSeadragon({
id: VIEWER_ID, id: VIEWER_ID,
prefixUrl: PREFIX_URL, prefixUrl: PREFIX_URL,

View File

@ -16,8 +16,8 @@
}); });
}, },
afterEach: function () { afterEach: function () {
if (viewer && viewer.close) { if (viewer){
viewer.close(); viewer.destroy();
} }
viewer = null; viewer = null;
@ -159,7 +159,7 @@
handlerCount++; handlerCount++;
}); });
viewer.world.draw(); viewer.world.update();
assert.equal(handlerCount, 1, 'correct number of handlers called'); assert.equal(handlerCount, 1, 'correct number of handlers called');
done(); done();
@ -173,9 +173,9 @@
// ---------- // ----------
QUnit.test('resetItems', function(assert) { QUnit.test('resetItems', function(assert) {
var done = assert.async(); var done = assert.async();
viewer.addHandler('tile-drawn', function updateHandler() { viewer.addHandler('tiled-image-drawn', function updateHandler() {
viewer.removeHandler('tile-drawn', updateHandler); viewer.removeHandler('tiled-image-drawn', updateHandler);
assert.ok(viewer.tileCache.numTilesLoaded() > 0, 'we have tiles after tile-drawn'); assert.ok(viewer.tileCache.numTilesLoaded() > 0, 'we have tiles after tiled-image-drawn');
viewer.world.resetItems(); viewer.world.resetItems();
assert.equal(viewer.tileCache.numTilesLoaded(), 0, 'no tiles after reset'); assert.equal(viewer.tileCache.numTilesLoaded(), 0, 'no tiles after reset');
done(); done();