cleaning up drawer APIs

This commit is contained in:
Tom 2023-03-11 11:38:21 -05:00
parent 5eece7d73a
commit 467597e2c2
11 changed files with 315 additions and 475 deletions

View File

@ -37,7 +37,7 @@
/**
* @class Drawer
* @memberof OpenSeadragon
* @classdesc Handles rendering of tiles for an {@link OpenSeadragon.Viewer}.
* @classdesc Default implementation of Drawer for an {@link OpenSeadragon.Viewer}.
* @param {Object} options - Options for this Drawer.
* @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this Drawer.
* @param {OpenSeadragon.Viewport} options.viewport - Reference to Viewer viewport.
@ -48,55 +48,12 @@ $.Drawer = function(options) {
$.DrawerBase.call(this, options);
$.console.assert( options.viewer, "[Drawer] options.viewer is required" );
//backward compatibility for positional args while preferring more
//idiomatic javascript options object as the only argument
var args = arguments;
if( !$.isPlainObject( options ) ){
options = {
source: args[ 0 ], // Reference to Viewer tile source.
viewport: args[ 1 ], // Reference to Viewer viewport.
element: args[ 2 ] // Parent element.
};
}
$.console.assert( options.viewport, "[Drawer] options.viewport is required" );
$.console.assert( options.element, "[Drawer] options.element is required" );
if ( options.source ) {
$.console.error( "[Drawer] options.source is no longer accepted; use TiledImage instead" );
}
this.viewer = options.viewer;
this.viewport = options.viewport;
this.debugGridColor = typeof options.debugGridColor === 'string' ? [options.debugGridColor] : options.debugGridColor || $.DEFAULT_SETTINGS.debugGridColor;
if (options.opacity) {
$.console.error( "[Drawer] options.opacity is no longer accepted; set the opacity on the TiledImage instead" );
}
this.useCanvas = $.supportsCanvas && ( this.viewer ? this.viewer.useCanvas : true );
/**
* The parent element of this Drawer instance, passed in when the Drawer was created.
* The parent of {@link OpenSeadragon.Drawer#canvas}.
* @member {Element} container
* @memberof OpenSeadragon.Drawer#
*/
this.container = $.getElement( options.element );
/**
* A <canvas> element if the browser supports them, otherwise a <div> element.
* Child element of {@link OpenSeadragon.Drawer#container}.
* @member {Element} canvas
* @memberof OpenSeadragon.Drawer#
*/
this.canvas = $.makeNeutralElement( this.useCanvas ? "canvas" : "div" );
/**
* 2d drawing context for {@link OpenSeadragon.Drawer#canvas} if it's a <canvas> element, otherwise null.
* @member {Object} context
* @memberof OpenSeadragon.Drawer#
*/
this.context = this.useCanvas ? this.canvas.getContext( this.useCanvas.contextType || "2d" ) : null;
this.context = this.useCanvas ? this.canvas.getContext( '2d' ) : null;
/**
* Sketch canvas used to temporarily draw tiles which cannot be drawn directly
@ -105,60 +62,35 @@ $.Drawer = function(options) {
this.sketchCanvas = null;
this.sketchContext = null;
/**
* @member {Element} element
* @memberof OpenSeadragon.Drawer#
* @deprecated Alias for {@link OpenSeadragon.Drawer#container}.
*/
this.element = this.container;
// We force our container to ltr because our drawing math doesn't work in rtl.
// This issue only affects our canvas renderer, but we do it always for consistency.
// Note that this means overlays you want to be rtl need to be explicitly set to rtl.
this.container.dir = 'ltr';
// check canvas available width and height, set canvas width and height such that the canvas backing store is set to the proper pixel density
if (this.useCanvas) {
var viewportSize = this._calculateCanvasSize();
this.canvas.width = viewportSize.x;
this.canvas.height = viewportSize.y;
}
this.canvas.style.width = "100%";
this.canvas.style.height = "100%";
this.canvas.style.position = "absolute";
$.setElementOpacity( this.canvas, this.opacity, true );
// Allow pointer events to pass through the canvas element so implicit
// pointer capture works on touch devices
$.setElementPointerEventsNone( this.canvas );
$.setElementTouchActionNone( this.canvas );
// explicit left-align
this.container.style.textAlign = "left";
this.container.appendChild( this.canvas );
// Image smoothing for canvas rendering (only if canvas is used).
// Canvas default is "true", so this will only be changed if user specified "false".
this._imageSmoothingEnabled = true;
};
$.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.Drawer.prototype */ {
/**
* Draws the TiledImage to its Drawer.
* Draws the TiledImages
*/
draw: function(tiledImage) {
if (tiledImage.opacity !== 0 || tiledImage._preload) {
tiledImage._midDraw = true;
this._updateViewport(tiledImage);
tiledImage._midDraw = false;
}
// Images with opacity 0 should not need to be drawn in future. this._needsDraw = false is set in this._updateViewport() for other images.
else {
tiledImage._needsDraw = false;
}
draw: function(tiledImages) {
var _this = this;
this._prepareNewFrame(); // prepare to draw a new frame
tiledImages.forEach(function(tiledImage){
if (tiledImage.opacity !== 0 || tiledImage._preload) {
tiledImage._midDraw = true;
_this._updateViewportWithTiledImage(tiledImage);
tiledImage._midDraw = false;
}
else {
tiledImage._needsDraw = false;
}
});
},
/**
@ -180,9 +112,51 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D
},
/**
* Clears the Drawer so it's ready to draw another frame.
* Turns image smoothing on or off for this viewer. Note: Ignored in some (especially older) browsers that do not support this property.
*
* @function
* @param {Boolean} [imageSmoothingEnabled] - Whether or not the image is
* drawn smoothly on the canvas; see imageSmoothingEnabled in
* {@link OpenSeadragon.Options} for more explanation.
*/
clear: function() {
setImageSmoothingEnabled: function(imageSmoothingEnabled){
if ( this.useCanvas ) {
this._imageSmoothingEnabled = imageSmoothingEnabled;
this._updateImageSmoothingEnabled(this.context);
this.viewer.forceRedraw();
}
},
/**
* Draw a rectangle onto the canvas
* @param {OpenSeadragon.Rect} rect
*/
drawDebuggingRect: function(rect) {
if ( this.useCanvas ) {
var context = this.context;
context.save();
context.lineWidth = 2 * $.pixelDensityRatio;
context.strokeStyle = this.debugGridColor[0];
context.fillStyle = this.debugGridColor[0];
context.strokeRect(
rect.x * $.pixelDensityRatio,
rect.y * $.pixelDensityRatio,
rect.width * $.pixelDensityRatio,
rect.height * $.pixelDensityRatio
);
context.restore();
}
},
/**
* @private
* @inner
* Clears the Drawer so it's ready to draw another frame.
*
*/
_prepareNewFrame: function() {
this.canvas.innerHTML = "";
if ( this.useCanvas ) {
var viewportSize = this._calculateCanvasSize();
@ -202,31 +176,33 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D
}
},
_clear: function (useSketch, bounds) {
if (!this.useCanvas) {
return;
}
var context = this._getContext(useSketch);
if (bounds) {
context.clearRect(bounds.x, bounds.y, bounds.width, bounds.height);
} else {
var canvas = context.canvas;
context.clearRect(0, 0, canvas.width, canvas.height);
/**
* @private
* @inner
* @param {Boolean} useSketch Whether to clear sketch canvas or main canvas
* @param {OpenSeadragon.Rect} [bounds] The rectangle to clear
*/
_clear: function(useSketch, bounds){
if( this.useCanvas ){
var context = this._getContext(useSketch);
if (bounds) {
context.clearRect(bounds.x, bounds.y, bounds.width, bounds.height);
} else {
var canvas = context.canvas;
context.clearRect(0, 0, canvas.width, canvas.height);
}
}
},
/* Methods from TiledImage */
/**
* @private
* @inner
* Pretty much every other line in this needs to be documented so it's clear
* how each piece of this routine contributes to the drawing process. That's
* why there are so many TODO's inside this function.
* Handles drawing a single TiledImage to the canvas
*
*/
_updateViewport: function(tiledImage) {
_updateViewportWithTiledImage: function(tiledImage) {
var _this = this;
tiledImage._needsDraw = false;
tiledImage._tilesLoading = 0;
@ -265,7 +241,7 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D
var infoArray = tiledImage.getTileInfoForDrawing();
infoArray.forEach(updateTile);
this._drawTiles(tiledImage, tiledImage.lastDrawn);
this._drawTiles(tiledImage);
},
@ -348,7 +324,7 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D
if (lastDrawn.length > 1 &&
imageZoom > tiledImage.smoothTileEdgesMinZoom &&
!tiledImage.iOSDevice &&
tiledImage.getRotation(true) % 360 === 0 && // TODO: support tile edge smoothing with tiled image rotation.
tiledImage.getRotation(true) % 360 === 0 && // TO DO: support tile edge smoothing with tiled image rotation.
$.supportsCanvas && this.viewer.useCanvas) {
// When zoomed in a lot (>100%) the tile edges are visible.
// So we have to composite them at ~100% and scale them up together.
@ -356,8 +332,8 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D
useSketch = true;
sketchScale = tile.getScaleForEdgeSmoothing();
sketchTranslate = tile.getTranslationForEdgeSmoothing(sketchScale,
this.getCanvasSize(false),
this.getCanvasSize(true));
this._getCanvasSize(false),
this._getCanvasSize(true));
}
var bounds;
@ -409,39 +385,42 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D
var usedClip = false;
if ( tiledImage._clip ) {
this.saveContext(useSketch);
this._saveContext(useSketch);
var box = tiledImage.imageToViewportRectangle(tiledImage._clip, true);
box = box.rotate(-tiledImage.getRotation(true), tiledImage._getRotationPoint(true));
var clipRect = this.viewportToDrawerRectangle(box);
var clipRect = this._viewportToDrawerRectangle(box);
if (sketchScale) {
clipRect = clipRect.times(sketchScale);
}
if (sketchTranslate) {
clipRect = clipRect.translate(sketchTranslate);
}
this.setClip(clipRect, useSketch);
this._setClip(clipRect, useSketch);
usedClip = true;
}
if (tiledImage._croppingPolygons) {
var self = this;
this.saveContext(useSketch);
this._saveContext(useSketch);
try {
var polygons = tiledImage._croppingPolygons.map(function (polygon) {
return polygon.map(function (coord) {
var point = tiledImage
.imageToViewportCoordinates(coord.x, coord.y, true)
.rotate(-tiledImage.getRotation(true), tiledImage._getRotationPoint(true));
var clipPoint = self.viewportCoordToDrawerCoord(point);
var clipPoint = self._viewportCoordToDrawerCoord(point);
if (sketchScale) {
clipPoint = clipPoint.times(sketchScale);
}
if (sketchTranslate) { // mostly fixes #2312
clipPoint = clipPoint.plus(sketchTranslate);
}
return clipPoint;
});
});
this.clipWithPolygons(polygons, useSketch);
this._clipWithPolygons(polygons, useSketch);
} catch (e) {
$.console.error(e);
}
@ -449,7 +428,7 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D
}
if ( tiledImage.placeholderFillStyle && tiledImage._hasOpaqueTile === false ) {
var placeholderRect = this.viewportToDrawerRectangle(tiledImage.getBounds(true));
var placeholderRect = this._viewportToDrawerRectangle(tiledImage.getBounds(true));
if (sketchScale) {
placeholderRect = placeholderRect.times(sketchScale);
}
@ -479,15 +458,16 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D
shouldRoundPositionAndSize = !isAnimating;
}
// Iterate over the tiles to draw, and draw them
for (var i = lastDrawn.length - 1; i >= 0; i--) {
tile = lastDrawn[ i ];
this.drawTile( tile, tiledImage._drawingHandler, useSketch, sketchScale,
this._drawTile( tile, tiledImage._drawingHandler, useSketch, sketchScale,
sketchTranslate, shouldRoundPositionAndSize, tiledImage.source );
tile.beingDrawn = true;
if( this.viewer ){
/**
* <em>- Needs documentation -</em>
* Raised when a tile is drawn to the canvas
*
* @event tile-drawn
* @memberof OpenSeadragon.Viewer
@ -505,7 +485,7 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D
}
if ( usedClip ) {
this.restoreContext( useSketch );
this._restoreContext( useSketch );
}
if (!sketchScale) {
@ -584,31 +564,15 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D
/* Methods from Tile */
/**
* This function converts the given point from to the drawer coordinate by
* multiplying it with the pixel density.
* This function does not take rotation into account, thus assuming provided
* point is at 0 degree.
* @param {OpenSeadragon.Point} point - the pixel point to convert
* @returns {OpenSeadragon.Point} Point in drawer coordinate system.
*/
viewportCoordToDrawerCoord: function(point) {
var vpPoint = this.viewport.pixelFromPointNoRotate(point, true);
return new $.Point(
vpPoint.x * $.pixelDensityRatio,
vpPoint.y * $.pixelDensityRatio
);
},
/**
* @private
* @inner
* This function will create multiple polygon paths on the drawing context by provided polygons,
* then clip the context to the paths.
* @param {OpenSeadragon.Point[][]} polygons - an array of polygons. A polygon is an array of OpenSeadragon.Point
* @param {Boolean} useSketch - Whether to use the sketch canvas or not.
*/
clipWithPolygons: function (polygons, useSketch) {
_clipWithPolygons: function (polygons, useSketch) {
if (!this.useCanvas) {
return;
}
@ -622,31 +586,9 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D
context.clip();
},
/**
* Scale from OpenSeadragon viewer rectangle to drawer rectangle
* (ignoring rotation)
* @param {OpenSeadragon.Rect} rectangle - The rectangle in viewport coordinate system.
* @returns {OpenSeadragon.Rect} Rectangle in drawer coordinate system.
*/
viewportToDrawerRectangle: function(rectangle) {
var topLeft = this.viewport.pixelFromPointNoRotate(rectangle.getTopLeft(), true);
var size = this.viewport.deltaPixelsFromPointsNoRotate(rectangle.getSize(), true);
return new $.Rect(
topLeft.x * $.pixelDensityRatio,
topLeft.y * $.pixelDensityRatio,
size.x * $.pixelDensityRatio,
size.y * $.pixelDensityRatio
);
},
/**
* @private
* @inner
* Draws the given tile.
* @param {OpenSeadragon.Tile} tile - The tile to draw.
* @param {Function} drawingHandler - Method for firing the drawing event if using canvas.
@ -660,35 +602,37 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D
* context.
* @param {OpenSeadragon.TileSource} source - The source specification of the tile.
*/
drawTile: function( tile, drawingHandler, useSketch, scale, translate, shouldRoundPositionAndSize, source) {
$.console.assert(tile, '[Drawer.drawTile] tile is required');
$.console.assert(drawingHandler, '[Drawer.drawTile] drawingHandler is required');
_drawTile: function( tile, drawingHandler, useSketch, scale, translate, shouldRoundPositionAndSize, source) {
$.console.assert(tile, '[Drawer._drawTile] tile is required');
$.console.assert(drawingHandler, '[Drawer._drawTile] drawingHandler is required');
if (this.useCanvas) {
var context = this._getContext(useSketch);
scale = scale || 1;
this.drawTileToCanvas(tile, context, drawingHandler, scale, translate, shouldRoundPositionAndSize, source);
this._drawTileToCanvas(tile, context, drawingHandler, scale, translate, shouldRoundPositionAndSize, source);
} else {
tile.drawTileToHTML( tile, this.canvas );
tile._drawTileToHTML( tile, this.canvas );
}
},
/**
* Renders the tile in a canvas-based context.
* @function
* @param {OpenSeadragon.Tile} tile - the tile to draw to the canvas
* @param {Canvas} context
* @param {Function} drawingHandler - Method for firing the drawing event.
* drawingHandler({context, tile, rendered})
* where <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.
*/
drawTileToCanvas: function( tile, context, drawingHandler, scale, translate, shouldRoundPositionAndSize, source) {
* @private
* @inner
* Renders the tile in a canvas-based context.
* @function
* @param {OpenSeadragon.Tile} tile - the tile to draw to the canvas
* @param {Canvas} context
* @param {Function} drawingHandler - Method for firing the drawing event.
* drawingHandler({context, tile, rendered})
* where <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.
*/
_drawTileToCanvas: function( tile, context, drawingHandler, scale, translate, shouldRoundPositionAndSize, source) {
var position = tile.position.times($.pixelDensityRatio),
size = tile.size.times($.pixelDensityRatio),
@ -696,7 +640,7 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D
if (!tile.context2D && !tile.cacheImageRecord) {
$.console.warn(
'[Drawer.drawTileToCanvas] attempting to draw tile %s when it\'s not cached',
'[Drawer._drawTileToCanvas] attempting to draw tile %s when it\'s not cached',
tile.toString());
return;
}
@ -782,15 +726,17 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D
},
/**
* Renders the tile in an html container.
* @function
* @param {OpenSeadragon.Tile} tile
* @param {Element} container
*/
drawTileToHTML: function( tile, container ) {
* @private
* @inner
* Renders the tile in an html container.
* @function
* @param {OpenSeadragon.Tile} tile
* @param {Element} container
*/
_drawTileToHTML: function( tile, container ) {
if (!tile.cacheImageRecord) {
$.console.warn(
'[Drawer.drawTileToHTML] attempting to draw tile %s when it\'s not cached',
'[Drawer._drawTileToHTML] attempting to draw tile %s when it\'s not cached',
tile.toString());
return;
}
@ -840,6 +786,13 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D
$.setElementOpacity( tile.element, tile.opacity );
},
/**
* @private
* @inner
* Get the context of the main or sketch canvas
* @param {Boolean} useSketch
* @returns
*/
_getContext: function( useSketch ) {
var context = this.context;
if ( useSketch ) {
@ -872,8 +825,14 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D
return context;
},
// private
saveContext: function( useSketch ) {
/**
* @private
* @inner
* Save the context of the main or sketch canvas
* @param {Boolean} useSketch
* @returns
*/
_saveContext: function( useSketch ) {
if (!this.useCanvas) {
return;
}
@ -881,8 +840,14 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D
this._getContext( useSketch ).save();
},
// private
restoreContext: function( useSketch ) {
/**
* @private
* @inner
* Restore the context of the main or sketch canvas
* @param {Boolean} useSketch
* @returns
*/
_restoreContext: function( useSketch ) {
if (!this.useCanvas) {
return;
}
@ -891,7 +856,7 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D
},
// private
setClip: function(rect, useSketch) {
_setClip: function(rect, useSketch) {
if (!this.useCanvas) {
return;
}
@ -1118,42 +1083,6 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D
context.restore();
},
// private
debugRect: function(rect) {
if ( this.useCanvas ) {
var context = this.context;
context.save();
context.lineWidth = 2 * $.pixelDensityRatio;
context.strokeStyle = this.debugGridColor[0];
context.fillStyle = this.debugGridColor[0];
context.strokeRect(
rect.x * $.pixelDensityRatio,
rect.y * $.pixelDensityRatio,
rect.width * $.pixelDensityRatio,
rect.height * $.pixelDensityRatio
);
context.restore();
}
},
/**
* Turns image smoothing on or off for this viewer. Note: Ignored in some (especially older) browsers that do not support this property.
*
* @function
* @param {Boolean} [imageSmoothingEnabled] - Whether or not the image is
* drawn smoothly on the canvas; see imageSmoothingEnabled in
* {@link OpenSeadragon.Options} for more explanation.
*/
setImageSmoothingEnabled: function(imageSmoothingEnabled){
if ( this.useCanvas ) {
this._imageSmoothingEnabled = imageSmoothingEnabled;
this._updateImageSmoothingEnabled(this.context);
this.viewer.forceRedraw();
}
},
// private
_updateImageSmoothingEnabled: function(context){
context.msImageSmoothingEnabled = this._imageSmoothingEnabled;
@ -1161,16 +1090,25 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D
},
/**
* @private
* @inner
* Get the canvas size
* @param {Boolean} sketch If set to true return the size of the sketch canvas
* @returns {OpenSeadragon.Point} The size of the canvas
*/
getCanvasSize: function(sketch) {
_getCanvasSize: function(sketch) {
var canvas = this._getContext(sketch).canvas;
return new $.Point(canvas.width, canvas.height);
},
getCanvasCenter: function() {
/**
* @private
* @inner
* Get the canvas center
* @param {Boolean} sketch If set to true return the center point of the sketch canvas
* @returns {OpenSeadragon.Point} The center point of the canvas
*/
_getCanvasCenter: function() {
return new $.Point(this.canvas.width / 2, this.canvas.height / 2);
},
@ -1178,7 +1116,7 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D
_offsetForRotation: function(options) {
var point = options.point ?
options.point.times($.pixelDensityRatio) :
this.getCanvasCenter();
this._getCanvasCenter();
var context = this._getContext(options.useSketch);
context.save();
@ -1198,7 +1136,7 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D
options = options || {};
var point = options.point ?
options.point.times($.pixelDensityRatio) :
this.getCanvasCenter();
this._getCanvasCenter();
var context = this._getContext(options.useSketch);
context.translate(point.x, 0);
@ -1240,90 +1178,7 @@ $.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.D
};
},
// deprecated functions
// deprecated
addOverlay: function( element, location, placement, onDraw ) {
$.console.error("drawer.addOverlay is deprecated. Use viewer.addOverlay instead.");
this.viewer.addOverlay( element, location, placement, onDraw );
return this;
},
// deprecated
updateOverlay: function( element, location, placement ) {
$.console.error("drawer.updateOverlay is deprecated. Use viewer.updateOverlay instead.");
this.viewer.updateOverlay( element, location, placement );
return this;
},
// deprecated
removeOverlay: function( element ) {
$.console.error("drawer.removeOverlay is deprecated. Use viewer.removeOverlay instead.");
this.viewer.removeOverlay( element );
return this;
},
// deprecated
clearOverlays: function() {
$.console.error("drawer.clearOverlays is deprecated. Use viewer.clearOverlays instead.");
this.viewer.clearOverlays();
return this;
},
// deprecated
needsUpdate: function() {
$.console.error( "[Drawer.needsUpdate] this function is deprecated. Use World.needsDraw instead." );
return this.viewer.world.needsDraw();
},
// deprecated
numTilesLoaded: function() {
$.console.error( "[Drawer.numTilesLoaded] this function is deprecated. Use TileCache.numTilesLoaded instead." );
return this.viewer.tileCache.numTilesLoaded();
},
// deprecated
reset: function() {
$.console.error( "[Drawer.reset] this function is deprecated. Use World.resetItems instead." );
this.viewer.world.resetItems();
return this;
},
// deprecated
update: function() {
$.console.error( "[Drawer.update] this function is deprecated. Use Drawer.clear and World.draw instead." );
this.clear();
this.viewer.world.draw();
return this;
},
/**
* Set the opacity of the drawer.
* @param {Number} opacity
* @returns {OpenSeadragon.Drawer} Chainable.
*/
setOpacity: function( opacity ) {
$.console.error("drawer.setOpacity is deprecated. Use tiledImage.setOpacity instead.");
var world = this.viewer.world;
for (var i = 0; i < world.getItemCount(); i++) {
world.getItemAt( i ).setOpacity( opacity );
}
return this;
},
/**
* Get the opacity of the drawer.
* @returns {Number}
*/
getOpacity: function() {
$.console.error("drawer.getOpacity is deprecated. Use tiledImage.getOpacity instead.");
var world = this.viewer.world;
var maxOpacity = 0;
for (var i = 0; i < world.getItemCount(); i++) {
var opacity = world.getItemAt( i ).getOpacity();
if ( opacity > maxOpacity ) {
maxOpacity = opacity;
}
}
return maxOpacity;
},
});

View File

@ -98,6 +98,7 @@ $.DrawerBase = function( options ) {
*/
this.element = this.container;
// TO DO: Does this need to be in DrawerBase, or only in Drawer implementations?
// We force our container to ltr because our drawing math doesn't work in rtl.
// This issue only affects our canvas renderer, but we do it always for consistency.
// Note that this means overlays you want to be rtl need to be explicitly set to rtl.
@ -123,10 +124,6 @@ $.DrawerBase = function( options ) {
this.container.style.textAlign = "left";
this.container.appendChild( this.canvas );
// Image smoothing for canvas rendering (only if canvas is used).
// Canvas default is "true", so this will only be changed if user specified "false".
this._imageSmoothingEnabled = true;
this._checkForAPIOverrides();
};
@ -134,14 +131,10 @@ $.DrawerBase = function( options ) {
$.DrawerBase.prototype = {
// Drawer implementaions must define the next four methods. These are called
// by core OSD, and forcing overrides (even for nullop methods) makes the
// by core OSD and/or public APIs, and forcing overrides (even for nullop methods) makes the
// behavior of the implementations explicitly clear in the code.
// Whether these have been overridden by child classes is checked in the
// constructor (via _checkForAPIOverrides). It could make sense to consolidate
// these a bit (e.g. by making `draw` take an array of `TiledImage`s and
// clearing the view as needed, rather than the existing pattern of
// `drawer.clear(); world.draw()` in the calling code), but they have been
// left as-is to maintain backwards compatibility.
// constructor (via _checkForAPIOverrides).
/**
* @param tiledImage the TiledImage that is ready to be drawn
@ -164,13 +157,6 @@ $.DrawerBase.prototype = {
$.console.error('Drawer.destroy must be implemented by child class');
},
/**
* Clears the Drawer so it's ready to draw another frame.
*/
clear: function() {
$.console.error('Drawer.clear must be implemented by child class');
},
/**
* Turns image smoothing on or off for this viewer. Note: Ignored in some (especially older) browsers that do not support this property.
*
@ -184,8 +170,26 @@ $.DrawerBase.prototype = {
},
/**
* Ensures that child classes have provided implementations for API methods
* draw, canRotate, destroy, and clear. Throws an exception if the original
* Optional public API to draw a rectangle (e.g. for debugging purposes)
* Child classes can override this method if they wish to support this
* @param {OpenSeadragon.Rect} rect
*/
drawDebuggingRect: function(rect) {
$.console.warn('[drawer].drawDebuggingRect is not implemented by this drawer');
},
// Deprecated functions
clear: function(){
$.console.warn('[drawer].clear() is deprecated. The drawer is responsible for clearing itself as needed before drawing tiles.');
},
// Private functions
/**
* @private
* @inner
* Ensures that child classes have provided implementations for public API methods
* draw, canRotate, destroy, and setImageSmoothinEnabled. Throws an exception if the original
* placeholder methods are still in place.
*/
_checkForAPIOverrides: function(){
@ -198,21 +202,24 @@ $.DrawerBase.prototype = {
if(this.destroy === $.DrawerBase.prototype.destroy){
throw("[drawer].destroy must be implemented by child class");
}
if(this.clear === $.DrawerBase.prototype.clear){
throw("[drawer].clear must be implemented by child class");
}
if(this.setImageSmoothingEnabled === $.DrawerBase.prototype.setImageSmoothingEnabled){
throw("[drawer].setImageSmoothingEnabled must be implemented by child class");
}
},
// Utility functions internal API
/**
* @private
* @inner
* Scale from OpenSeadragon viewer rectangle to drawer rectangle
* (ignoring rotation)
* @param {OpenSeadragon.Rect} rectangle - The rectangle in viewport coordinate system.
* @returns {OpenSeadragon.Rect} Rectangle in drawer coordinate system.
*/
viewportToDrawerRectangle: function(rectangle) {
_viewportToDrawerRectangle: function(rectangle) {
var topLeft = this.viewport.pixelFromPointNoRotate(rectangle.getTopLeft(), true);
var size = this.viewport.deltaPixelsFromPointsNoRotate(rectangle.getSize(), true);
@ -225,6 +232,8 @@ $.DrawerBase.prototype = {
},
/**
* @private
* @inner
* This function converts the given point from to the drawer coordinate by
* multiplying it with the pixel density.
* This function does not take rotation into account, thus assuming provided
@ -232,7 +241,7 @@ $.DrawerBase.prototype = {
* @param {OpenSeadragon.Point} point - the pixel point to convert
* @returns {OpenSeadragon.Point} Point in drawer coordinate system.
*/
viewportCoordToDrawerCoord: function(point) {
_viewportCoordToDrawerCoord: function(point) {
var vpPoint = this.viewport.pixelFromPointNoRotate(point, true);
return new $.Point(
vpPoint.x * $.pixelDensityRatio,
@ -240,8 +249,13 @@ $.DrawerBase.prototype = {
);
},
// private
/**
* @private
* @inner
* Calculate width and height of the canvas based on viewport dimensions
* and pixelDensityRatio
* @returns {Dictionary} {x, y} size of the canvas
*/
_calculateCanvasSize: function() {
var pixelDensityRatio = $.pixelDensityRatio;
var viewportSize = this.viewport.getContainerSize();
@ -252,86 +266,6 @@ $.DrawerBase.prototype = {
};
},
/* Deprecated Functions */
// deprecated
addOverlay: function( element, location, placement, onDraw ) {
$.console.error("drawer.addOverlay is deprecated. Use viewer.addOverlay instead.");
this.viewer.addOverlay( element, location, placement, onDraw );
return this;
},
// deprecated
updateOverlay: function( element, location, placement ) {
$.console.error("drawer.updateOverlay is deprecated. Use viewer.updateOverlay instead.");
this.viewer.updateOverlay( element, location, placement );
return this;
},
// deprecated
removeOverlay: function( element ) {
$.console.error("drawer.removeOverlay is deprecated. Use viewer.removeOverlay instead.");
this.viewer.removeOverlay( element );
return this;
},
// deprecated
clearOverlays: function() {
$.console.error("drawer.clearOverlays is deprecated. Use viewer.clearOverlays instead.");
this.viewer.clearOverlays();
return this;
},
// deprecated
needsUpdate: function() {
$.console.error( "[Drawer.needsUpdate] this function is deprecated. Use World.needsDraw instead." );
return this.viewer.world.needsDraw();
},
// deprecated
numTilesLoaded: function() {
$.console.error( "[Drawer.numTilesLoaded] this function is deprecated. Use TileCache.numTilesLoaded instead." );
return this.viewer.tileCache.numTilesLoaded();
},
// deprecated
reset: function() {
$.console.error( "[Drawer.reset] this function is deprecated. Use World.resetItems instead." );
this.viewer.world.resetItems();
return this;
},
// deprecated
update: function() {
$.console.error( "[Drawer.update] this function is deprecated. Use Drawer.clear and World.draw instead." );
this.clear();
this.viewer.world.draw();
return this;
},
// deprecated
setOpacity: function( opacity ) {
$.console.error("drawer.setOpacity is deprecated. Use tiledImage.setOpacity instead.");
var world = this.viewer.world;
for (var i = 0; i < world.getItemCount(); i++) {
world.getItemAt( i ).setOpacity( opacity );
}
return this;
},
// deprecated
getOpacity: function() {
$.console.error("drawer.getOpacity is deprecated. Use tiledImage.getOpacity instead.");
var world = this.viewer.world;
var maxOpacity = 0;
for (var i = 0; i < world.getItemCount(); i++) {
var opacity = world.getItemAt( i ).getOpacity();
if ( opacity > maxOpacity ) {
maxOpacity = opacity;
}
}
return maxOpacity;
},
};
Object.defineProperty($.DrawerBase.prototype, "isOpenSeadragonDrawer", {

View File

@ -310,7 +310,6 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
this.viewport.resize( containerSize, true );
this.viewport.goHome(true);
this.oldContainerSize = containerSize;
this.drawer.clear();
this.world.draw();
}
}

View File

@ -1238,6 +1238,10 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
};
},
/**
*
* @returns {Array} Array of Tiles within the viewport which should be drawn
*/
getTileInfoForDrawing: function(){
return this._tilesToDraw;
},
@ -1342,9 +1346,6 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
} else {
this._setFullyLoaded(this._tilesLoading === 0);
}
// return bestTile;
},
/**

View File

@ -3811,7 +3811,6 @@ function updateOnce( viewer ) {
function drawWorld( viewer ) {
viewer.imageLoader.clear();
viewer.drawer.clear();
viewer.world.draw();
/**

View File

@ -256,10 +256,7 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
* Draws all items.
*/
draw: function() {
for ( var i = 0; i < this._items.length; i++ ) {
this.viewer.drawer.draw(this._items[i]);
}
this.viewer.drawer.draw(this._items);
this._needsDraw = false;
},

View File

@ -131,8 +131,8 @@
var box = new OpenSeadragon.Rect(margins.left, margins.top,
$('#contentDiv').width() - (margins.left + margins.right),
$('#contentDiv').height() - (margins.top + margins.bottom));
self.viewer.drawer.debugRect(box);
// if drawDebuggingRect is implemented, use it to show the box
self.viewer.drawer.drawDebuggingRect(box);
});
}

View File

@ -106,7 +106,9 @@ export class ThreeJSDrawer extends OpenSeadragon.DrawerBase{
}
// Public API required by all Drawer implementations
/**
* Clean up the renderer, removing all resources
*/
destroy(){
// clear all resources used by the renderer, geometries, textures etc
@ -123,26 +125,61 @@ export class ThreeJSDrawer extends OpenSeadragon.DrawerBase{
this._camera = null;
}
clear(){
//not needed by this implementation
}
// Public API required by all Drawer implementations
/**
*
* @returns true if the drawer supports rotation
*/
canRotate(){
return true;
}
draw(tiledImage){
// Public API required by all Drawer implementations
/**
*
* @param {Array} tiledImages Array of TiledImage objects to draw
*/
draw(tiledImages){
// actual drawing is handled by event listeneners
// just mark this tiledImage as having been drawn (possibly unnecessary)
tiledImage._needsDraw = false;
// just mark the tiledImages as having been drawn (possibly unnecessary)
tiledImages.forEach(tiledImage => tiledImage._needsDraw = false);
}
// Public API required by all Drawer implementations
/**
* Set the context2d imageSmoothingEnabled parameter
* @param {Boolean} enabled
*/
setImageSmoothingEnabled(enabled){
this._clippingContext.imageSmoothingEnabled = enabled;
this._outputContext.imageSmoothingEnabled = enabled;
}
/**
* Draw a rect onto the output canvas for debugging purposes
* @param {OpenSeadragon.Rect} rect
*/
drawDebuggingRect(rect){
let context = this._outputContext;
context.save();
context.lineWidth = 2 * OpenSeadragon.pixelDensityRatio;
context.strokeStyle = this.debugGridColor[0];
context.fillStyle = this.debugGridColor[0];
context.strokeRect(
rect.x * OpenSeadragon.pixelDensityRatio,
rect.y * OpenSeadragon.pixelDensityRatio,
rect.width * OpenSeadragon.pixelDensityRatio,
rect.height * OpenSeadragon.pixelDensityRatio
);
context.restore();
}
// Private methods
_setupRenderer(){
//to do: test support for pages of sequence mode
let viewerBounds = this.viewer.viewport.getBoundsNoRotate();
this._camera = new THREE.OrthographicCamera(
@ -171,13 +208,6 @@ export class ThreeJSDrawer extends OpenSeadragon.DrawerBase{
this.viewer.addHandler("viewport-change", () => this._viewportChangeHandler());
this.viewer.addHandler("home", () => this._viewportChangeHandler());
// this.viewer.world.addHandler("item-index-change", () => this.renderFrame());
// this.viewer.addHandler("crop-change", () => this.renderFrame());
// this.viewer.addHandler("clip-change", () => this.renderFrame());
// this.viewer.addHandler("opacity-change", () => this.renderFrame());
// this.viewer.addHandler("composite-operation-change", () => this.renderFrame());
this.viewer.addHandler("update-viewport", () => this.renderFrame());
this._viewportChangeHandler();
@ -476,7 +506,7 @@ export class ThreeJSDrawer extends OpenSeadragon.DrawerBase{
if(item._clip){
var box = item.imageToViewportRectangle(item._clip, true);
var rect = this.viewportToDrawerRectangle(box);
var rect = this._viewportToDrawerRectangle(box);
this._clippingContext.beginPath();
this._clippingContext.rect(rect.x, rect.y, rect.width, rect.height);
this._clippingContext.clip();
@ -486,7 +516,7 @@ export class ThreeJSDrawer extends OpenSeadragon.DrawerBase{
return polygon.map(function (coord) {
let point = item.imageToViewportCoordinates(coord.x, coord.y, true)
.rotate(_this.viewer.viewport.getRotation(true), _this.viewer.viewport.getCenter(true));
let clipPoint = _this.viewportCoordToDrawerCoord(point);
let clipPoint = _this._viewportCoordToDrawerCoord(point);
return clipPoint;
});
});
@ -625,6 +655,7 @@ export class ThreeJSDrawer extends OpenSeadragon.DrawerBase{
context.restore();
}
// private
_drawDebugInfo( tiledImage ) {
let scene = this._tiledImageMap[tiledImage[this._uuid]];
let level = scene.userData.currentLevel;
@ -641,25 +672,6 @@ export class ThreeJSDrawer extends OpenSeadragon.DrawerBase{
}
}
// private
_debugRect(rect) {
if ( this.useCanvas ) {
var context = this._outputContext;
context.save();
context.lineWidth = 2 * OpenSeadragon.pixelDensityRatio;
context.strokeStyle = this.debugGridColor[0];
context.fillStyle = this.debugGridColor[0];
context.strokeRect(
rect.x * OpenSeadragon.pixelDensityRatio,
rect.y * OpenSeadragon.pixelDensityRatio,
rect.width * OpenSeadragon.pixelDensityRatio,
rect.height * OpenSeadragon.pixelDensityRatio
);
context.restore();
}
}
// private
_restoreRotationChanges() {

View File

@ -111,7 +111,7 @@
<div id="image-picker">
<h3>Image options (drag and drop to re-order images)</h3>
<div class="image-options">
<!-- <div class="image-options">
<span class="ui-icon ui-icon-arrowthick-2-n-s"></span>
<label><input type="checkbox" checked data-image="rainbow" class="toggle"> Rainbow Grid</label>
<div class="option-grid">
@ -159,7 +159,7 @@
<label>Composite: <select data-image="bblue" data-field="composite"></select></label>
<label>Wrapping: <select data-image="bblue" data-field="wrapping"></select></label>
</div>
</div>
</div> -->
</div>

View File

@ -10,11 +10,24 @@ const sources = {
type:'image',
url: "../data/BBlue.png",
},
"duomo":"https://openseadragon.github.io/example-images/duomo/duomo.dzi",
}
const labels = {
rainbow: 'Rainbow Grid',
leaves: 'Leaves',
bblue: 'Blue B',
duomo: 'Duomo',
}
var viewer = window.viewer = OpenSeadragon({
id: "contentDiv",
prefixUrl: "../../build/openseadragon/images/",
// minZoomImageRatio:0.8,
// maxZoomPixelRatio:0.5,
minZoomImageRatio:0.01,
maxZoomPixelRatio:100,
smoothTileEdgesMinZoom:1.1,
crossOriginPolicy: 'Anonymous',
ajaxWithCredentials: false
});
let threeRenderer = window.threeRenderer = new ThreeJSDrawer({viewer, viewport: viewer.viewport, element:viewer.element});
@ -24,8 +37,11 @@ var viewer2 = window.viewer2 = OpenSeadragon({
prefixUrl: "../../build/openseadragon/images/",
minZoomImageRatio:0.01,
customDrawer: ThreeJSDrawer,
tileSources: sources['leaves'],
tileSources: [sources['leaves'], sources['rainbow'], sources['duomo']],
sequenceMode: true,
imageSmoothingEnabled: false,
crossOriginPolicy: 'Anonymous',
ajaxWithCredentials: false
});
//make the test canvas mirror all changes to the viewer canvas
@ -54,6 +70,13 @@ $('#image-picker').sortable({
}
});
Object.keys(sources).forEach((key, index)=>{
let element = makeImagePickerElement(key, labels[key])
$('#image-picker').append(element);
if(index === 0){
element.find('.toggle').prop('checked',true);
}
})
$('#image-picker input.toggle').on('change',function(){
let data = $(this).data();
@ -193,7 +216,26 @@ function addTileSource(image, checkbox){
}
}
function makeImagePickerElement(key, label){
return $(`<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>
<label>Flipped: <input type="checkbox" data-image="" data-field="flipped"></label>
<label>Cropped: <input type="checkbox" data-image="" data-field="cropped"></label>
<label>Debug: <input type="checkbox" data-image="" data-field="debug"></label>
<label>Composite: <select data-image="" data-field="composite"></select></label>
<label>Wrapping: <select data-image="" data-field="wrapping"></select></label>
</div>
</div>`.replaceAll('data-image=""', `data-image="${key}"`).replace('__title__', label));
}

View File

@ -181,7 +181,8 @@
done();
});
image.draw();
//image.draw(); // TO DO: Is this necessary for the test? It will now fail since tiledImage.draw() is not a thing.
viewer.drawer.draw( [ image ] );
});
viewer.open('/test/data/testpattern.dzi');
@ -225,7 +226,7 @@
image.setClip(clip);
assert.propEqual(image.getClip(), clip, 'clip is set correctly');
Util.spyOnce(viewer.drawer, 'setClip', function(rect) {
Util.spyOnce(viewer.drawer, '_setClip', function(rect) {
var homeBounds = viewer.viewport.getHomeBounds();
var canvasClip = viewer.drawer
.viewportToDrawerRectangle(homeBounds);