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

View File

@ -1,5 +1,5 @@
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
modification, are permitted provided that the following conditions are met:

View File

@ -2,7 +2,7 @@
* OpenSeadragon - Button
*
* 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
* modification, are permitted provided that the following conditions are

View File

@ -2,7 +2,7 @@
* OpenSeadragon - ButtonGroup
*
* 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
* 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
*
* 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
* modification, are permitted provided that the following conditions are

View File

@ -2,7 +2,7 @@
* OpenSeadragon - ControlDock
*
* 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
* modification, are permitted provided that the following conditions are

View File

@ -2,7 +2,7 @@
* OpenSeadragon - DisplayRect
*
* 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
* 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
*
* 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
* modification, are permitted provided that the following conditions are

View File

@ -2,7 +2,7 @@
* OpenSeadragon - EventSource
*
* 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
* modification, are permitted provided that the following conditions are
@ -51,6 +51,7 @@
*/
$.EventSource = function() {
this.events = {};
this._rejectedEventList = {};
};
/** @lends OpenSeadragon.EventSource.prototype */
@ -68,6 +69,7 @@ $.EventSource.prototype = {
* @param {Number} [times=1] - The number of times to handle the event
* before removing it.
* @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) {
var self = this;
@ -80,7 +82,7 @@ $.EventSource.prototype = {
}
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 {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.
* @returns {Boolean} - True if the handler was added, false if it was rejected
*/
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 ];
if ( !events ) {
this.events[ eventName ] = events = [];
@ -106,6 +115,7 @@ $.EventSource.prototype = {
index--;
}
}
return true;
},
/**
@ -191,17 +201,45 @@ $.EventSource.prototype = {
* @function
* @param {String} eventName - Name of event to register.
* @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 ) {
//uncomment if you want to get a log of all events
//$.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 );
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 ));

View File

@ -2,7 +2,7 @@
* OpenSeadragon - full-screen support functions
*
* 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
* 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
*
* 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
* modification, are permitted provided that the following conditions are

View File

@ -2,7 +2,7 @@
* OpenSeadragon - ImageLoader
*
* 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
* modification, are permitted provided that the following conditions are
@ -37,6 +37,8 @@
/**
* @class ImageJob
* @classdesc Handles downloading of a single image.
*
* @memberof OpenSeadragon
* @param {Object} options - Options for this ImageJob.
* @param {String} [options.src] - URL of image to download.
* @param {Tile} [options.tile] - Tile that belongs the data to.
@ -87,6 +89,7 @@ $.ImageJob.prototype = {
/**
* Starts the image job.
* @method
* @memberof OpenSeadragon.ImageJob#
*/
start: function() {
this.tries++;
@ -113,6 +116,7 @@ $.ImageJob.prototype = {
* @param {*} data data that has been downloaded
* @param {XMLHttpRequest} request reference to the request if used
* @param {string} errorMessage description upon failure
* @memberof OpenSeadragon.ImageJob#
*/
finish: function(data, request, errorMessage ) {
this.data = data;

View File

@ -2,7 +2,7 @@
* OpenSeadragon - ImageTileSource
*
* 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
* modification, are permitted provided that the following conditions are
@ -42,8 +42,8 @@
* 1. viewer.open({type: 'image', url: fooUrl});
* 2. viewer.open(new OpenSeadragon.ImageTileSource({url: fooUrl}));
*
* With the first syntax, the crossOriginPolicy, ajaxWithCredentials and
* useCanvas options are inherited from the viewer if they are not
* With the first syntax, the crossOriginPolicy and ajaxWithCredentials
* options are inherited from the viewer if they are not
* specified directly in the options object.
*
* @memberof OpenSeadragon
@ -58,16 +58,13 @@
* domains.
* @param {String|Boolean} [options.ajaxWithCredentials=false] Whether to set
* the withCredentials XHR flag for AJAX requests (when loading tile sources).
* @param {Boolean} [options.useCanvas=true] Set to false to prevent any use
* of the canvas API.
*/
$.ImageTileSource = function (options) {
options = $.extend({
buildPyramid: true,
crossOriginPolicy: false,
ajaxWithCredentials: false,
useCanvas: true
ajaxWithCredentials: false
}, options);
$.TileSource.apply(this, [options]);
@ -198,9 +195,11 @@
/**
* Destroys ImageTileSource
* @function
* @param {OpenSeadragon.Viewer} viewer the viewer that is calling
* destroy on the ImageTileSource
*/
destroy: function () {
this._freeupCanvasMemory();
destroy: function (viewer) {
this._freeupCanvasMemory(viewer);
},
// private
@ -214,7 +213,7 @@
height: this._image.naturalHeight
}];
if (!this.buildPyramid || !$.supportsCanvas || !this.useCanvas) {
if (!this.buildPyramid || !$.supportsCanvas) {
// We don't need the image anymore. Allows it to be GC.
delete this._image;
return levels;
@ -270,11 +269,26 @@
* and Safari keeps canvas until its height and width will be set to 0).
* @function
*/
_freeupCanvasMemory: function () {
_freeupCanvasMemory: function (viewer) {
for (var i = 0; i < this.levels.length; i++) {
if(this.levels[i].context2D){
this.levels[i].context2D.canvas.height = 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
*
* 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
* 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
*
* 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
* modification, are permitted provided that the following conditions are
@ -357,7 +357,7 @@
getActivePointersListByType: function ( type ) {
var delegate = THIS[ this.hash ],
i,
len = delegate.activePointersLists.length,
len = delegate ? delegate.activePointersLists.length : 0,
list;
for ( i = 0; i < len; i++ ) {
@ -367,7 +367,9 @@
}
list = new $.MouseTracker.GesturePointList( type );
delegate.activePointersLists.push( list );
if(delegate){
delegate.activePointersLists.push( list );
}
return list;
},

View File

@ -2,7 +2,7 @@
* OpenSeadragon - Navigator
*
* 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
* modification, are permitted provided that the following conditions are
@ -170,9 +170,6 @@ $.Navigator = function( options ){
style.border = borderWidth + 'px solid ' + options.displayRegionColor;
style.margin = '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';
// 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.goHome(true);
this.oldContainerSize = containerSize;
this.drawer.clear();
this.world.update();
this.world.draw();
}
}

View File

@ -2,7 +2,7 @@
* OpenSeadragon
*
* 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
* 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.
* 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]
* Default proportional opacity of the tiled images (1=opaque, 0=hidden)
* 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}
*
* @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.
* 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]
* 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.
*
* @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]
* 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.
*
@ -1339,12 +1359,32 @@ function OpenSeadragon( options ){
flipped: false,
// APPEARANCE
opacity: 1,
preload: false,
compositeOperation: null,
imageSmoothingEnabled: true,
placeholderFillStyle: null,
subPixelRoundingForTransparency: null,
opacity: 1, // to be passed into each TiledImage
compositeOperation: null, // to be passed into each TiledImage
// DRAWER SETTINGS
drawer: ['webgl', 'canvas', 'html'], // prefer using webgl, then canvas (i.e. context2d), then fallback to html
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
showReferenceStrip: false,
@ -1367,7 +1407,6 @@ function OpenSeadragon( options ){
imageLoaderLimit: 0,
maxImageCacheCount: 200,
timeout: 30000,
useCanvas: true, // Use canvas element for drawing if available
tileRetryMax: 0,
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.
* @function

View File

@ -2,7 +2,7 @@
* OpenSeadragon - OsmTileSource
*
* 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
* modification, are permitted provided that the following conditions are

View File

@ -2,7 +2,7 @@
* OpenSeadragon - Overlay
*
* 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
* modification, are permitted provided that the following conditions are

View File

@ -1,7 +1,7 @@
/*
* 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
* modification, are permitted provided that the following conditions are

View File

@ -2,7 +2,7 @@
* OpenSeadragon - Point
*
* 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
* modification, are permitted provided that the following conditions are

View File

@ -2,7 +2,7 @@
* OpenSeadragon - Profiler
*
* 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
* modification, are permitted provided that the following conditions are

View File

@ -2,7 +2,7 @@
* OpenSeadragon - Rect
*
* 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
* modification, are permitted provided that the following conditions are

View File

@ -2,7 +2,7 @@
* OpenSeadragon - ReferenceStrip
*
* 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
* 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
* 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
* for the purpose of image sequnces.
*
@ -455,7 +455,7 @@ function loadPanels( strip, viewerSize, scroll ) {
animationTime: 0,
loadTilesWithAjax: strip.viewer.loadTilesWithAjax,
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
// elements so implicit pointer capture works on touch devices

View File

@ -2,7 +2,7 @@
* OpenSeadragon - Spring
*
* 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
* modification, are permitted provided that the following conditions are
@ -206,7 +206,8 @@ $.Spring.prototype = {
/**
* @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() {
this.current.time = $.now();
@ -230,14 +231,13 @@ $.Spring.prototype = {
( this.target.time - this.start.time )
);
var oldValue = this.current.value;
if (this._exponential) {
this.current.value = Math.exp(currentValue);
} else {
this.current.value = currentValue;
}
return oldValue !== this.current.value;
return currentValue !== targetValue;
},
/**

View File

@ -2,7 +2,7 @@
* OpenSeadragon - getString/setString
*
* 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
* modification, are permitted provided that the following conditions are

View File

@ -2,7 +2,7 @@
* OpenSeadragon - Tile
*
* 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
* 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#
*/
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
* 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');
},
/**
* 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.
* @member {Object} image
@ -382,114 +330,7 @@ $.Tile.prototype = {
* @returns {CanvasRenderingContext2D}
*/
getCanvasContext: function() {
return this.context2D || 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();
return this.context2D || (this.cacheImageRecord && this.cacheImageRecord.getRenderedContext());
},
/**

View File

@ -2,7 +2,7 @@
* OpenSeadragon - TileCache
*
* 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
* modification, are permitted provided that the following conditions are
@ -236,19 +236,51 @@ $.TileCache.prototype = {
var tile = tileRecord.tile;
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.cacheImageRecord = null;
var imageRecord = this._imagesLoaded[tile.cacheKey];
if(!imageRecord){
return;
}
imageRecord.removeTile(tile);
if (!imageRecord.getTileCount()) {
imageRecord.destroy();
delete this._imagesLoaded[tile.cacheKey];
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
* @memberof OpenSeadragon.Viewer
@ -260,6 +292,7 @@ $.TileCache.prototype = {
tile: tile,
tiledImage: tiledImage
});
}
};

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
* OpenSeadragon - TileSource
*
* 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
* modification, are permitted provided that the following conditions are
@ -370,6 +370,7 @@ $.TileSource.prototype = {
point.y >= 0 && point.y <= 1 / this.aspectRatio;
$.console.assert(validPoint, "[TileSource.getTileAtPoint] must be called with a valid point.");
var widthScaled = this.dimensions.x * this.getLevelScale(level);
var pixelX = point.x * widthScaled;
var pixelY = point.y * widthScaled;

View File

@ -2,7 +2,7 @@
* OpenSeadragon - TileSourceCollection
*
* 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
* modification, are permitted provided that the following conditions are

View File

@ -2,7 +2,7 @@
* OpenSeadragon - TmsTileSource
*
* 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
* modification, are permitted provided that the following conditions are

View File

@ -2,7 +2,7 @@
* OpenSeadragon - Viewer
*
* 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
* modification, are permitted provided that the following conditions are
@ -89,6 +89,21 @@ $.Viewer = function( options ) {
delete options.config;
}
// Move deprecated drawer options from the base options object into a sub-object
// This is an array to make it easy to add additional properties to convert to
// drawer options later if it makes sense to set at the drawer level rather than
// per tiled image (for example, subPixelRoundingForTransparency).
let drawerOptionList = [
'useCanvas', // deprecated
];
options.drawerOptions = Object.assign({},
drawerOptionList.reduce((drawerOptions, option) => {
drawerOptions[option] = options[option];
delete options[option];
return drawerOptions;
}, {}),
options.drawerOptions);
//Public properties
//Allow the options object to override global defaults
$.extend( true, this, {
@ -198,6 +213,7 @@ $.Viewer = function( options ) {
$.console.warn("Hash " + this.hash + " has already been used.");
}
//Private state properties
THIS[ this.hash ] = {
fsBoundsDelta: new $.Point( 1, 1 ),
@ -418,13 +434,63 @@ $.Viewer = function( options ) {
maxImageCacheCount: this.maxImageCacheCount
});
// Create the drawer
this.drawer = new $.Drawer({
viewer: this,
viewport: this.viewport,
element: this.canvas,
debugGridColor: this.debugGridColor
});
//Create the drawer based on selected options
if (Object.prototype.hasOwnProperty.call(this.drawerOptions, 'useCanvas') ){
$.console.error('useCanvas is deprecated, use the "drawer" option to indicate preferred drawer(s)');
// for backwards compatibility, use HTMLDrawer if useCanvas is defined and is falsey
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
this.overlaysContainer = $.makeNeutralElement( "div" );
@ -470,6 +536,7 @@ $.Viewer = function( options ) {
displayRegionColor: this.navigatorDisplayRegionColor,
crossOriginPolicy: this.crossOriginPolicy,
animationTime: this.animationTime,
drawer: this.drawer.getType(),
});
}
@ -496,11 +563,6 @@ $.Viewer = function( options ) {
beginControlsAutoHide( _this );
} );
// Initial canvas options
if ( this.imageSmoothingEnabled !== undefined && !this.imageSmoothingEnabled){
this.drawer.setImageSmoothingEnabled(this.imageSmoothingEnabled);
}
// Register the viewer
$._viewers.set(this.element, this);
};
@ -1040,7 +1102,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
* @returns {Boolean}
*/
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;
}
if ( fullPage ) {
if ( fullPage && this.element ) {
this.elementSize = $.getElementSize( this.element );
this.pageScroll = $.getPageScroll();
@ -2403,7 +2465,6 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
width: this.referenceStripWidth,
tileSources: this.tileSources,
prefixUrl: this.prefixUrl,
useCanvas: this.useCanvas,
viewer: this
});
@ -2552,7 +2613,6 @@ function getTileSourceImplementation( viewer, tileSource, imgOptions, successCal
ajaxHeaders: imgOptions.ajaxHeaders ?
imgOptions.ajaxHeaders : viewer.ajaxHeaders,
splitHashDataForPost: viewer.splitHashDataForPost,
useCanvas: viewer.useCanvas,
success: function( event ) {
successCallback( event.tileSource );
}
@ -2570,9 +2630,6 @@ function getTileSourceImplementation( viewer, tileSource, imgOptions, successCal
if (tileSource.ajaxWithCredentials === undefined) {
tileSource.ajaxWithCredentials = viewer.ajaxWithCredentials;
}
if (tileSource.useCanvas === undefined) {
tileSource.useCanvas = viewer.useCanvas;
}
if ( $.isFunction( tileSource.getTileUrl ) ) {
//Custom tile source
@ -3705,7 +3762,7 @@ function updateOnce( viewer ) {
var viewportChange = viewer.viewport.update();
var animated = viewer.world.update() || viewportChange;
var animated = viewer.world.update(viewportChange) || viewportChange;
if (viewportChange) {
/**
@ -3795,7 +3852,6 @@ function updateOnce( viewer ) {
function drawWorld( viewer ) {
viewer.imageLoader.clear();
viewer.drawer.clear();
viewer.world.draw();
/**
@ -3949,4 +4005,22 @@ function onFlip() {
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 ));

View File

@ -2,7 +2,7 @@
* OpenSeadragon - Viewport
*
* 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
* 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.
* @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() {
var _this = this;
@ -1165,7 +1165,13 @@ $.Viewport.prototype = {
this._oldZoom = this.zoomSpring.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

1124
src/webgldrawer.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
* OpenSeadragon - World
*
* 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
* 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.
* @function
* @param viewportChanged Whether the viewport changed, which indicates that
* all TiledImages need to be updated.
*/
update: function() {
update: function(viewportChanged) {
var animated = false;
for ( var i = 0; i < this._items.length; i++ ) {
animated = this._items[i].update() || animated;
animated = this._items[i].update(viewportChanged) || animated;
}
return animated;
@ -256,11 +259,11 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
* Draws all items.
*/
draw: function() {
for ( var i = 0; i < this._items.length; i++ ) {
this._items[i].draw();
}
this.viewer.drawer.draw(this._items);
this._needsDraw = false;
this._items.forEach(function(item){
this._needsDraw = item.setDrawn() || this._needsDraw;
});
},
/**

View File

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

View File

@ -16,7 +16,6 @@
// debugMode: true,
zoomPerScroll: 1.02,
showNavigator: testNavigator,
useCanvas: true,
// defaultZoomLevel: 2,
// homeFillsViewer: true,
// sequenceMode: true,
@ -131,8 +130,9 @@
var box = new OpenSeadragon.Rect(margins.left, margins.top,
$('#contentDiv').width() - (margins.left + margins.right),
$('#contentDiv').height() - (margins.top + margins.bottom));
self.viewer.drawer.debugRect(box);
// If drawDebuggingRect is implemented, use it to show the box.
// 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>
</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.
var tileDrawnHandler = function(event) {
if (event.tiledImage === fullImage) {
viewer.removeHandler('tile-drawn', tileDrawnHandler);
viewer.removeHandler('update-viewport', tileDrawnHandler);
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 () {
@ -146,9 +146,15 @@
obj0[member0]();
assert.equal(called, true, 'called through 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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,133 +2,154 @@
(function() {
var viewer;
const drawerTypes = ['webgl','canvas','html'];
drawerTypes.forEach(runDrawerTests);
QUnit.module('Drawer', {
beforeEach: function () {
$('<div id="example"></div>').appendTo("#qunit-fixture");
function runDrawerTests(drawerType){
testLog.reset();
},
afterEach: function () {
if (viewer && viewer.close) {
viewer.close();
QUnit.module('Drawer-'+drawerType, {
beforeEach: function () {
$('<div id="example"></div>').appendTo("#qunit-fixture");
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);
Util.spyOnce(viewer.drawer.context, 'rotate', function() {
assert.ok(true, 'drawing with new rotation');
// ----------
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
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();
});
});
});
// ----------
QUnit.test('debug', function(assert) {
var done = assert.async();
createViewer({
tileSources: '/test/data/testpattern.dzi',
debugMode: true
});
// ----------
QUnit.test('sketchCanvas', function(assert) {
var done = assert.async();
Util.spyOnce(viewer.drawer, 'drawDebugInfo', function() {
assert.ok(true, 'drawDebugInfo is called');
done();
});
});
createViewer({
tileSources: '/test/data/testpattern.dzi',
});
var drawer = viewer.drawer;
// ----------
QUnit.test('sketchCanvas', function(assert) {
var done = assert.async();
createViewer({
tileSources: '/test/data/testpattern.dzi'
});
var drawer = viewer.drawer;
// this test only makes sense for canvas drawer
if(viewer.drawer.getType() !== 'canvas'){
assert.expect(0);
done();
};
viewer.addHandler('tile-drawn', function noOpacityHandler() {
viewer.removeHandler('tile-drawn', noOpacityHandler);
assert.equal(drawer.sketchCanvas, null,
'The sketch canvas should be null if no decimal opacity is used.');
assert.equal(drawer.sketchContext, null,
'The sketch context should be null if no decimal opacity is used.');
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 noOpacityHandler() {
viewer.removeHandler('tile-drawn', noOpacityHandler);
assert.equal(drawer.sketchCanvas, null,
'The sketch canvas should be null if no decimal opacity is used.');
assert.equal(drawer.sketchContext, null,
'The sketch context should be null if no decimal opacity is used.');
testOpacityDecimal();
});
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.');
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) {
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();
});
}
});
// ----------
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 () {
if ( viewer && viewer.close ) {
viewer.close();
if (viewer){
viewer.destroy();
}
viewer = null;
}
} );
@ -1155,6 +1154,12 @@
// ----------
QUnit.test( 'Viewer: event count test with \'tile-drawing\'', function (assert) {
var done = assert.async();
if(viewer.drawer.getType() !== 'canvas'){
assert.expect(0);
done();
return;
}
assert.ok(viewer.numberOfHandlers('tile-drawing') === 0,
"'tile-drawing' event is empty by default.");
@ -1162,6 +1167,7 @@
viewer.removeHandler( 'tile-drawing', tileDrawing );
assert.ok(viewer.numberOfHandlers('tile-drawing') === 0,
"'tile-drawing' deleted: count is 0.");
viewer.close();
done();
};
@ -1185,6 +1191,12 @@
QUnit.test( 'Viewer: tile-drawing event', function (assert) {
var done = assert.async();
if(viewer.drawer.getType() !== 'canvas'){
assert.expect(0);
done();
return;
}
var tileDrawing = function ( event ) {
viewer.removeHandler( 'tile-drawing', tileDrawing );
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.
// TODO: Add more file formats (with corresponding test data).
var viewer = null;
QUnit.module('Formats', {
beforeEach: function () {
var example = document.createElement("div");
example.id = "example";
document.getElementById("qunit-fixture").appendChild(example);
},
afterEach: function () {
if (viewer){
viewer.destroy();
}
viewer = null;
}
});
var viewer = null;
// ----------
var testOpenUrl = function(relativeUrl, assert) {
@ -34,12 +42,12 @@
var openHandler = function(event) {
viewer.removeHandler('open', openHandler);
assert.ok(true, 'Open event was sent');
viewer.addHandler('tile-drawn', tileDrawnHandler);
viewer.addHandler('tiled-image-drawn', tileDrawnHandler);
};
var tileDrawnHandler = function(event) {
viewer.removeHandler('tile-drawn', tileDrawnHandler);
assert.ok(true, 'A tile has been drawn');
viewer.removeHandler('tiled-image-drawn', tileDrawnHandler);
assert.ok(true, 'A tiled image has been drawn');
viewer.addHandler('close', closeHandler);
viewer.close();
};

View File

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

View File

@ -2,275 +2,287 @@
( function() {
var viewer;
const drawerTypes = ['webgl','canvas'];
drawerTypes.forEach(runDrawerTests);
QUnit.module( 'Multi-Image', {
beforeEach: function() {
$( '<div id="example"></div>' ).appendTo( "#qunit-fixture" );
function runDrawerTests(drawerType){
testLog.reset();
QUnit.module( 'Multi-Image-'+drawerType, {
beforeEach: function() {
$( '<div id="example"></div>' ).appendTo( "#qunit-fixture" );
viewer = OpenSeadragon( {
id: 'example',
prefixUrl: '/build/openseadragon/images/',
springStiffness: 100 // Faster animation = faster tests
});
},
afterEach: function() {
if ( viewer && viewer.close ) {
viewer.close();
}
testLog.reset();
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 = OpenSeadragon( {
id: 'example',
prefixUrl: '/build/openseadragon/images/',
springStiffness: 100, // Faster animation = faster tests
drawer: drawerType
});
},
afterEach: function() {
if (viewer){
viewer.destroy();
}
};
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.world.addHandler( "add-item", function addSecondItemHandler( event ) {
viewer.world.removeHandler( "add-item", addSecondItemHandler );
var item2 = event.item;
assert.equal( viewer.world.getItemCount( ), 3,
"3 items should be present after adding a second item." );
assert.equal( viewer.world.getIndexOfItem( item2 ), 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( "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.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.world.addHandler( "add-item", function addThirdItemHandler( event ) {
viewer.world.removeHandler( "add-item", addThirdItemHandler );
var item3 = event.item;
assert.equal( viewer.world.getItemCount( ), 4,
"4 items should be present after adding a third item." );
assert.equal( viewer.world.getIndexOfItem( item3 ), 2,
"Item 3 should be added with index 2." );
viewer.world.addHandler( "add-item", function addSecondItemHandler( event ) {
viewer.world.removeHandler( "add-item", addSecondItemHandler );
var item2 = event.item;
assert.equal( viewer.world.getItemCount( ), 3,
"3 items should be present after adding a second item." );
assert.equal( viewer.world.getIndexOfItem( item2 ), 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,
"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.replace = true;
options.tileSource.levels[0].url = "data/CCyan.png";
viewer.addTiledImage( options );
viewer.world.addHandler( "add-item", function replaceAddItemHandler( event ) {
viewer.world.removeHandler( "add-item", replaceAddItemHandler );
var item4 = event.item;
viewer.world.addHandler( "add-item", function addThirdItemHandler( event ) {
viewer.world.removeHandler( "add-item", addThirdItemHandler );
var item3 = 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." );
"4 items should be present after adding a third item." );
assert.equal( viewer.world.getIndexOfItem( item3 ), 2,
"Item 3 should be added with index 2." );
assert.equal( viewer.world.getIndexOfItem( item2 ), 1,
"Item 2 should stay at index 1." );
viewer.world.addHandler( "remove-item", function removeItemHandler( event ) {
viewer.world.removeHandler( "remove-item", removeItemHandler );
options.index = 2;
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,
"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." );
assert.equal( item2, event.item, "Removed item should be item2." );
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' );
});
// ----------
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( '/test/data/testpattern.dzi' );
});
viewer.open([
{
tileSource: '/test/data/tall.dzi',
width: 4
}, {
tileSource: '/test/data/wide.dzi',
width: 2
}
]);
});
// ----------
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
}]
}]
};
QUnit.test('Viewer.addSimpleImage', function(assert) {
var done = assert.async();
viewer.addHandler("open", function openHandler() {
viewer.removeHandler("open", openHandler);
viewer.addHandler( "open", function openHandler() {
viewer.removeHandler( "open", openHandler );
viewer.world.addHandler('add-item', function itemAdded(event) {
viewer.world.removeHandler('add-item', itemAdded);
assert.equal(event.item.opacity, 0.5,
'Opacity option should be set when using addSimpleImage');
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.addSimpleImage({
url: '/test/data/A.png',
opacity: 0.5
});
viewer.open([
{
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) {
var done = assert.async();
viewer.open('/test/data/testpattern.dzi');
QUnit.test('Viewer.addSimpleImage', function(assert) {
var done = assert.async();
viewer.addHandler("open", function openHandler() {
viewer.removeHandler("open", openHandler);
var density = OpenSeadragon.pixelDensityRatio;
viewer.addHandler('open', function() {
var firstImage = viewer.world.getItemAt(0);
firstImage.addHandler('fully-loaded-change', 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.world.addHandler('add-item', function itemAdded(event) {
viewer.world.removeHandler('add-item', itemAdded);
assert.equal(event.item.opacity, 0.5,
'Opacity option should be set when using addSimpleImage');
done();
});
viewer.addSimpleImage({
url: '/test/data/A.png',
success: function() {
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();
});
}
opacity: 0.5
});
});
viewer.open('/test/data/testpattern.dzi');
});
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]
};
}
});
QUnit.test('Transparent image on top of others', function(assert) {
var done = assert.async();
viewer.open('/test/data/testpattern.dzi');
var density = OpenSeadragon.pixelDensityRatio;
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();
if (viewer){
viewer.destroy();
}
viewer = null;
}
});

View File

@ -16,12 +16,22 @@
},
afterEach: function() {
resetTestVariables();
if (viewer){
viewer.destroy();
}
viewer = null;
}
});
var resetTestVariables = function() {
if (viewer) {
viewer.close();
if (viewer){
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();
},
afterEach: function () {
if (viewer && viewer.close) {
viewer.close();
if (viewer){
viewer.destroy();
}
viewer = null;

View File

@ -1,7 +1,9 @@
/* global QUnit, $, Util, testLog */
(function() {
var viewer;
let viewer;
let precision = 0.000000001;
QUnit.module('TiledImage', {
beforeEach: function() {
@ -13,12 +15,13 @@
viewer = OpenSeadragon({
id: 'example',
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() {
if (viewer && viewer.close) {
viewer.close();
if (viewer){
viewer.destroy();
}
viewer = null;
@ -119,8 +122,8 @@
viewer.addHandler('animation-finish', function animationHandler() {
viewer.removeHandler('animation-finish', animationHandler);
assert.propEqual(image.getBounds(), new OpenSeadragon.Rect(1, 2, 3, 3), '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(), precision, 'target bounds after animation');
Util.assertRectangleEquals(assert, new OpenSeadragon.Rect(1, 2, 3, 3), image.getBounds(true), precision, 'target bounds after animation');
done();
});
});
@ -132,6 +135,7 @@
QUnit.test('update', function(assert) {
var done = assert.async();
var handlerCount = 0;
let expectedHandlers = 4;
viewer.addHandler('open', function(event) {
var image = viewer.world.getItemAt(0);
@ -160,6 +164,7 @@
assert.ok(event.tile, 'update-tile event includes tile');
});
viewer.addHandler('tile-drawing', function tileDrawingHandler(event) {
viewer.removeHandler('tile-drawing', tileDrawingHandler);
handlerCount++;
@ -170,18 +175,20 @@
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();
});
image.draw();
viewer.drawer.draw( [ image ] );
});
viewer.open('/test/data/testpattern.dzi');
@ -190,14 +197,14 @@
// ----------
QUnit.test('reset', function(assert) {
var done = assert.async();
viewer.addHandler('tile-drawn', function updateHandler() {
viewer.removeHandler('tile-drawn', updateHandler);
assert.ok(viewer.tileCache.numTilesLoaded() > 0, 'we have tiles after tile-drawn');
viewer.addHandler('tiled-image-drawn', function updateHandler() {
viewer.removeHandler('tiled-image-drawn', updateHandler);
assert.ok(viewer.tileCache.numTilesLoaded() > 0, 'we have tiles after tiled-image-drawn');
viewer.world.getItemAt(0).reset();
assert.equal(viewer.tileCache.numTilesLoaded(), 0, 'no tiles after reset');
viewer.addHandler('tile-drawn', function updateHandler2() {
viewer.removeHandler('tile-drawn', updateHandler2);
viewer.addHandler('tiled-image-drawn', function updateHandler2() {
viewer.removeHandler('tiled-image-drawn', updateHandler2);
assert.ok(viewer.tileCache.numTilesLoaded() > 0, 'more tiles load');
viewer.world.getItemAt(0).destroy();
assert.equal(viewer.tileCache.numTilesLoaded(), 0, 'no tiles after destroy');
@ -225,7 +232,7 @@
image.setClip(clip);
assert.propEqual(image.getClip(), clip, 'clip is set correctly');
Util.spyOnce(viewer.drawer, 'setClip', function(rect) {
Util.spyOnce(viewer.drawer, '_setClip', function(rect) {
var homeBounds = viewer.viewport.getHomeBounds();
var canvasClip = viewer.drawer
.viewportToDrawerRectangle(homeBounds);

View File

@ -4,6 +4,7 @@
(function() {
var ASSERT = null;
var done = null;
var DYNAMIC_URL = "";
var viewer = null;
var OriginalAjax = OpenSeadragon.makeAjaxRequest;
@ -16,9 +17,10 @@
/**
* Set up shared variables for test
*/
var configure = function(assert, url) {
var configure = function(assert, url, assertAsyncDone) {
ASSERT = assert;
DYNAMIC_URL = url;
done = assertAsyncDone;
firstUrlPromise = new Promise(resolve => {
firstUrlPromiseResolver = () => {
isFirstUrlPromiseResolved = true;
@ -94,6 +96,7 @@
// Otherwise close viewer
if (isFirstUrlPromiseResolved) {
viewer.close();
done();
} else {
firstUrlPromiseResolver();
}
@ -119,7 +122,11 @@
OpenSeadragon.extend( Tile.prototype, OpenSeadragon.Tile.prototype, {
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);
}
});
@ -129,9 +136,10 @@
afterEach: function () {
ASSERT = null;
if (viewer && viewer.close) {
viewer.close();
if (viewer){
viewer.destroy();
}
viewer = null;
OpenSeadragon.makeAjaxRequest = OriginalAjax;
@ -188,11 +196,12 @@
// ----------
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');
firstUrlPromise.then(() => {
// 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];
})
});

View File

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

View File

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

View File

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

View File

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