mirror of
https://github.com/openseadragon/openseadragon.git
synced 2024-11-24 22:26:10 +03:00
implement native webgl renderer, and many associated changes related to drawing pipeline and testing
This commit is contained in:
parent
128975ea0f
commit
386ca85db8
@ -60,6 +60,7 @@ module.exports = function(grunt) {
|
||||
"src/drawerbase.js",
|
||||
"src/htmldrawer.js",
|
||||
"src/context2ddrawer.js",
|
||||
"src/webgldrawer.js",
|
||||
"src/viewport.js",
|
||||
"src/tiledimage.js",
|
||||
"src/tilecache.js",
|
||||
|
@ -108,9 +108,6 @@ class Context2dDrawer extends $.DrawerBase{
|
||||
// _this._updateViewportWithTiledImage(tiledImage);
|
||||
_this._drawTiles(tiledImage);
|
||||
}
|
||||
else {
|
||||
tiledImage._needsDraw = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -371,11 +368,10 @@ class Context2dDrawer extends $.DrawerBase{
|
||||
}
|
||||
|
||||
// Iterate over the tiles to draw, and draw them
|
||||
for (var i = lastDrawn.length - 1; i >= 0; i--) {
|
||||
for (var i = 0; i < lastDrawn.length; i++) {
|
||||
tile = lastDrawn[ i ];
|
||||
this._drawTile( tile, tiledImage._drawingHandler, useSketch, sketchScale,
|
||||
this._drawTile( tile, tiledImage, useSketch, sketchScale,
|
||||
sketchTranslate, shouldRoundPositionAndSize, tiledImage.source );
|
||||
tile.beingDrawn = true;
|
||||
|
||||
if( this.viewer ){
|
||||
/**
|
||||
@ -455,6 +451,24 @@ class Context2dDrawer extends $.DrawerBase{
|
||||
this._drawDebugInfo( tiledImage, lastDrawn );
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
* 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
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @inner
|
||||
@ -466,7 +480,7 @@ class Context2dDrawer extends $.DrawerBase{
|
||||
for ( var i = lastDrawn.length - 1; i >= 0; i-- ) {
|
||||
var tile = lastDrawn[ i ];
|
||||
try {
|
||||
this.drawDebugInfo(tile, lastDrawn.length, i, tiledImage);
|
||||
this._drawDebugInfoOnTile(tile, lastDrawn.length, i, tiledImage);
|
||||
} catch(e) {
|
||||
$.console.error(e);
|
||||
}
|
||||
@ -500,8 +514,7 @@ class Context2dDrawer extends $.DrawerBase{
|
||||
* @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.
|
||||
* drawingHandler({context, tile, rendered})
|
||||
* @param {OpenSeadragon.TiledImage} tiledImage - The tiled image being drawn.
|
||||
* @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.
|
||||
@ -511,13 +524,13 @@ class Context2dDrawer extends $.DrawerBase{
|
||||
* context.
|
||||
* @param {OpenSeadragon.TileSource} source - The source specification of the tile.
|
||||
*/
|
||||
_drawTile( tile, drawingHandler, useSketch, scale, translate, shouldRoundPositionAndSize, source) {
|
||||
_drawTile( tile, tiledImage, useSketch, scale, translate, shouldRoundPositionAndSize, source) {
|
||||
$.console.assert(tile, '[Drawer._drawTile] tile is required');
|
||||
$.console.assert(drawingHandler, '[Drawer._drawTile] drawingHandler is required');
|
||||
$.console.assert(tiledImage, '[Drawer._drawTile] drawingHandler is required');
|
||||
|
||||
var context = this._getContext(useSketch);
|
||||
scale = scale || 1;
|
||||
this._drawTileToCanvas(tile, context, drawingHandler, scale, translate, shouldRoundPositionAndSize, source);
|
||||
this._drawTileToCanvas(tile, context, tiledImage, scale, translate, shouldRoundPositionAndSize, source);
|
||||
|
||||
}
|
||||
|
||||
@ -528,7 +541,7 @@ class Context2dDrawer extends $.DrawerBase{
|
||||
* @function
|
||||
* @param {OpenSeadragon.Tile} tile - the tile to draw to the canvas
|
||||
* @param {Canvas} context
|
||||
* @param {Function} drawingHandler - Method for firing the drawing event.
|
||||
* @param {OpenSeadragon.TiledImage} tiledImage - 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
|
||||
@ -538,7 +551,7 @@ class Context2dDrawer extends $.DrawerBase{
|
||||
* context.
|
||||
* @param {OpenSeadragon.TileSource} source - The source specification of the tile.
|
||||
*/
|
||||
_drawTileToCanvas( tile, context, drawingHandler, scale, translate, shouldRoundPositionAndSize, source) {
|
||||
_drawTileToCanvas( tile, context, tiledImage, scale, translate, shouldRoundPositionAndSize, source) {
|
||||
|
||||
var position = tile.position.times($.pixelDensityRatio),
|
||||
size = tile.size.times($.pixelDensityRatio),
|
||||
@ -599,9 +612,7 @@ class Context2dDrawer extends $.DrawerBase{
|
||||
);
|
||||
}
|
||||
|
||||
// This gives the application a chance to make image manipulation
|
||||
// changes as we are rendering the image
|
||||
drawingHandler({context: context, tile: tile, rendered: rendered});
|
||||
this._raiseTileDrawingEvent(tiledImage, context, tile, rendered);
|
||||
|
||||
var sourceWidth, sourceHeight;
|
||||
if (tile.sourceBounds) {
|
||||
@ -805,7 +816,7 @@ class Context2dDrawer extends $.DrawerBase{
|
||||
}
|
||||
|
||||
// private
|
||||
drawDebugInfo(tile, count, i, tiledImage) {
|
||||
_drawDebugInfoOnTile(tile, count, i, tiledImage) {
|
||||
|
||||
var colorIndex = this.viewer.world.getIndexOfItem(tiledImage) % this.debugGridColor.length;
|
||||
var context = this.context;
|
||||
|
@ -90,6 +90,7 @@ $.DrawerBase = class DrawerBase{
|
||||
this.container = $.getElement( options.element );
|
||||
|
||||
// TO DO: Does this need to be in DrawerBase, or only in Drawer implementations?
|
||||
// Original commment:
|
||||
// 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.
|
||||
@ -199,6 +200,9 @@ $.DrawerBase = class DrawerBase{
|
||||
* placeholder methods are still in place.
|
||||
*/
|
||||
_checkForAPIOverrides(){
|
||||
if(this.createDrawingElement === $.DrawerBase.prototype.createDrawingElement){
|
||||
throw("[drawer].createDrawingElement must be implemented by child class");
|
||||
}
|
||||
if(this.draw === $.DrawerBase.prototype.draw){
|
||||
throw("[drawer].draw must be implemented by child class");
|
||||
}
|
||||
@ -208,12 +212,38 @@ $.DrawerBase = class DrawerBase{
|
||||
if(this.destroy === $.DrawerBase.prototype.destroy){
|
||||
throw("[drawer].destroy must be implemented by child class");
|
||||
}
|
||||
|
||||
if(this.setImageSmoothingEnabled === $.DrawerBase.prototype.setImageSmoothingEnabled){
|
||||
throw("[drawer].setImageSmoothingEnabled must be implemented by child class");
|
||||
}
|
||||
}
|
||||
|
||||
_raiseTileDrawingEvent(tiledImage, context, tile, rendered){
|
||||
/**
|
||||
* This event is fired just before the tile is drawn giving the application a chance to alter the image.
|
||||
*
|
||||
* NOTE: This event is only fired in certain drawing contexts: either the 'context2d' drawer is
|
||||
* being used, or the 'webgl' drawer with 'drawerOptions.webgl.continuousTileRefresh'.
|
||||
*
|
||||
* @event tile-drawing
|
||||
* @memberof OpenSeadragon.Viewer
|
||||
* @type {object}
|
||||
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
||||
* @property {OpenSeadragon.Tile} tile - The Tile being drawn.
|
||||
* @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.
|
||||
* @property {CanvasRenderingContext2D} context - The HTML canvas context being drawn into.
|
||||
* @property {CanvasRenderingContext2D} rendered - The HTML canvas context containing the tile imagery.
|
||||
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
||||
*/
|
||||
this.viewer.raiseEvent('tile-drawing', {
|
||||
tiledImage: tiledImage,
|
||||
context: context,
|
||||
tile: tile,
|
||||
rendered: rendered
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Utility functions
|
||||
|
||||
|
@ -84,9 +84,6 @@ class HTMLDrawer extends $.DrawerBase{
|
||||
if (tiledImage.opacity !== 0 || tiledImage._preload) {
|
||||
_this._drawTiles(tiledImage);
|
||||
}
|
||||
else {
|
||||
tiledImage._needsDraw = false;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
@ -145,7 +142,6 @@ class HTMLDrawer extends $.DrawerBase{
|
||||
for (var i = lastDrawn.length - 1; i >= 0; i--) {
|
||||
var tile = lastDrawn[ i ];
|
||||
this._drawTile( tile );
|
||||
tile.beingDrawn = true;
|
||||
|
||||
if( this.viewer ){
|
||||
/**
|
||||
|
@ -196,8 +196,8 @@
|
||||
* Destroys ImageTileSource
|
||||
* @function
|
||||
*/
|
||||
destroy: function () {
|
||||
this._freeupCanvasMemory();
|
||||
destroy: function (viewer) {
|
||||
this._freeupCanvasMemory(viewer);
|
||||
},
|
||||
|
||||
// private
|
||||
@ -267,11 +267,23 @@
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -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.
|
||||
|
@ -190,15 +190,15 @@
|
||||
* 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 = ['context2d', 'html']]
|
||||
* @property {String|DrawerImplementation|Array} [drawer = ['webgl', 'context2d', 'html']]
|
||||
* Which drawer to use. Valid strings are 'context2d' 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. See documentation
|
||||
* for Drawer classes that extend DrawerBase for further information.
|
||||
* @property {Object} drawerOptions
|
||||
* Options to pass to the selected drawer implementation. For details
|
||||
* please @see {@link drawerOptions}.
|
||||
*
|
||||
* @property {Number} [opacity=1]
|
||||
* Default proportional opacity of the tiled images (1=opaque, 0=hidden)
|
||||
@ -1346,9 +1346,32 @@ function OpenSeadragon( options ){
|
||||
compositeOperation: null, // to be passed into each TiledImage
|
||||
|
||||
// DRAWER SETTINGS
|
||||
drawer: ['context2d', 'html'], // prefer using canvas, fallback to html
|
||||
drawerOptions: {},
|
||||
drawer: ['webgl', 'context2d', 'html'], // prefer using webgl, context2d, fallback to html
|
||||
useCanvas: true, // deprecated - set drawer and drawerOptions
|
||||
/**
|
||||
* drawerOptions dictionary.
|
||||
* @type {Object} drawerOptions
|
||||
* @property {Object} webgl - options if the WebGLDrawer is used.
|
||||
* Set 'continuousTileFresh: true' if tile data is modified programmatically
|
||||
* by filtering plugins or similar.
|
||||
* @property {Object} context2d - options if the Context2dDrawer is used
|
||||
* @property {Object} html - options if the HTMLDrawer is used
|
||||
* @property {Object} custom - options if a custom drawer is used
|
||||
*/
|
||||
drawerOptions: {
|
||||
webgl: {
|
||||
continuousTileRefresh: false,
|
||||
},
|
||||
context2d: {
|
||||
|
||||
},
|
||||
html: {
|
||||
|
||||
},
|
||||
custom: {
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
// TILED IMAGE SETTINGS
|
||||
preload: false, // to be passed into each TiledImage
|
||||
|
@ -81,6 +81,12 @@
|
||||
* @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.
|
||||
@ -324,7 +330,7 @@
|
||||
* @returns {CanvasRenderingContext2D}
|
||||
*/
|
||||
getCanvasContext: function() {
|
||||
return this.context2D || this.cacheImageRecord.getRenderedContext();
|
||||
return this.context2D || (this.cacheImageRecord && this.cacheImageRecord.getRenderedContext());
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -236,19 +236,50 @@ $.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
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +291,7 @@ $.TileCache.prototype = {
|
||||
tile: tile,
|
||||
tiledImage: tiledImage
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -85,7 +85,7 @@
|
||||
* A set of headers to include when making tile AJAX requests.
|
||||
*/
|
||||
$.TiledImage = function( options ) {
|
||||
var _this = this;
|
||||
this._initialized = false;
|
||||
/**
|
||||
* The {@link OpenSeadragon.TileSource} that defines this TiledImage.
|
||||
* @member {OpenSeadragon.TileSource} source
|
||||
@ -162,7 +162,10 @@ $.TiledImage = function( options ) {
|
||||
_needsDraw: true, // Does the tiledImage need to update the viewport again?
|
||||
_hasOpaqueTile: false, // Do we have even one fully opaque tile?
|
||||
_tilesLoading: 0, // The number of pending tile requests.
|
||||
_tilesToDraw: [], // info about the tiles currently in the viewport
|
||||
_tilesToDraw: [], // info about the tiles currently in the viewport, two deep: array[level][tile]
|
||||
_lastDrawn: [], // array of tiles that were last fetched by the drawer
|
||||
_isBlending: false, // Are any tiles still being blended?
|
||||
_wasBlending: false, // Were any tiles blending before the last draw?
|
||||
//configurable settings
|
||||
springStiffness: $.DEFAULT_SETTINGS.springStiffness,
|
||||
animationTime: $.DEFAULT_SETTINGS.animationTime,
|
||||
@ -220,30 +223,9 @@ $.TiledImage = function( options ) {
|
||||
this.fitBounds(fitBounds, fitBoundsPlacement, true);
|
||||
}
|
||||
|
||||
// We need a callback to give image manipulation a chance to happen
|
||||
this._drawingHandler = function(args) {
|
||||
/**
|
||||
* This event is fired just before the tile is drawn giving the application a chance to alter the image.
|
||||
*
|
||||
* NOTE: This event is only fired when the drawer is using a <canvas>.
|
||||
*
|
||||
* @event tile-drawing
|
||||
* @memberof OpenSeadragon.Viewer
|
||||
* @type {object}
|
||||
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
||||
* @property {OpenSeadragon.Tile} tile - The Tile being drawn.
|
||||
* @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.
|
||||
* @property {OpenSeadragon.Tile} context - The HTML canvas context being drawn into.
|
||||
* @property {OpenSeadragon.Tile} rendered - The HTML canvas context containing the tile imagery.
|
||||
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
||||
*/
|
||||
_this.viewer.raiseEvent('tile-drawing', $.extend({
|
||||
tiledImage: _this
|
||||
}, args));
|
||||
};
|
||||
|
||||
this._ownAjaxHeaders = {};
|
||||
this.setAjaxHeaders(ajaxHeaders, false);
|
||||
this._initialized = true;
|
||||
};
|
||||
|
||||
$.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.TiledImage.prototype */{
|
||||
@ -311,7 +293,6 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
||||
|
||||
if (updated || viewportChanged || !this._fullyLoaded){
|
||||
let fullyLoadedFlag = this._updateLevelsForViewport();
|
||||
this._updateTilesInViewport();
|
||||
this._setFullyLoaded(fullyLoadedFlag);
|
||||
}
|
||||
|
||||
@ -328,10 +309,13 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
||||
|
||||
/**
|
||||
* Mark this TiledImage as having been drawn, so that it will only be drawn
|
||||
* again if something changes about the image
|
||||
* again if something changes about the image. If the image is still blending,
|
||||
* this will have no effect.
|
||||
* @returns {Boolean} whether the item still needs to be drawn due to blending
|
||||
*/
|
||||
setDrawn: function(){
|
||||
this._needsDraw = false;
|
||||
this._needsDraw = this._isBlending || this._wasBlending;
|
||||
return this._needsDraw;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -341,7 +325,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
||||
this.reset();
|
||||
|
||||
if (this.source.destroy) {
|
||||
this.source.destroy();
|
||||
this.source.destroy(this.viewer);
|
||||
}
|
||||
},
|
||||
|
||||
@ -539,7 +523,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
||||
imageX = imageX.x;
|
||||
}
|
||||
|
||||
var point = this._imageToViewportDelta(imageX, imageY);
|
||||
var point = this._imageToViewportDelta(imageX, imageY, current);
|
||||
if (current) {
|
||||
point.x += this._xSpring.current.value;
|
||||
point.y += this._ySpring.current.value;
|
||||
@ -934,9 +918,39 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
||||
return this._flipped;
|
||||
},
|
||||
set flipped(flipped){
|
||||
let changed = this._flipped !== !!flipped;
|
||||
this._flipped = !!flipped;
|
||||
this._needsDraw = true;
|
||||
this._raiseBoundsChange();
|
||||
if(changed){
|
||||
this.update(true);
|
||||
this._needsDraw = true;
|
||||
this._raiseBoundsChange();
|
||||
}
|
||||
},
|
||||
|
||||
get wrapHorizontal(){
|
||||
return this._wrapHorizontal;
|
||||
},
|
||||
set wrapHorizontal(wrap){
|
||||
let changed = this._wrapHorizontal !== !!wrap;
|
||||
this._wrapHorizontal = !!wrap;
|
||||
if(this._initialized && changed){
|
||||
this.update(true);
|
||||
this._needsDraw = true;
|
||||
// this._raiseBoundsChange();
|
||||
}
|
||||
},
|
||||
|
||||
get wrapVertical(){
|
||||
return this._wrapVertical;
|
||||
},
|
||||
set wrapVertical(wrap){
|
||||
let changed = this._wrapVertical !== !!wrap;
|
||||
this._wrapVertical = !!wrap;
|
||||
if(this._initialized && changed){
|
||||
this.update(true);
|
||||
this._needsDraw = true;
|
||||
// this._raiseBoundsChange();
|
||||
}
|
||||
},
|
||||
|
||||
get debugMode(){
|
||||
@ -1058,7 +1072,20 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
||||
* @returns {Array} Array of Tiles that make up the current view
|
||||
*/
|
||||
getTilesToDraw: function(){
|
||||
return this._tilesToDraw;
|
||||
|
||||
let tileArray = this._tilesToDraw.flat();
|
||||
// update all tiles (so blending can happen right at the time of drawing)
|
||||
this._updateTilesInViewport(tileArray);
|
||||
// _tilesToDraw might have been updated by the update; refresh it
|
||||
tileArray = this._tilesToDraw.flat();
|
||||
|
||||
// mark the tiles as being drawn, so that they won't be discarded from
|
||||
// the tileCache
|
||||
tileArray.forEach(tileInfo => {
|
||||
tileInfo.tile.beingDrawn = true;
|
||||
});
|
||||
this._lastDrawn = tileArray;
|
||||
return tileArray;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1289,7 +1316,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
||||
var currentTime = $.now();
|
||||
|
||||
// reset each tile's beingDrawn flag
|
||||
this._tilesToDraw.forEach(tileinfo => {
|
||||
this._lastDrawn.forEach(tileinfo => {
|
||||
tileinfo.tile.beingDrawn = false;
|
||||
});
|
||||
// clear the list of tiles to draw
|
||||
@ -1391,7 +1418,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
||||
};
|
||||
})(level, levelOpacity, currentTime);
|
||||
|
||||
this._tilesToDraw = this._tilesToDraw.concat(tiles.map(makeTileInfoObject));
|
||||
this._tilesToDraw[level] = tiles.map(makeTileInfoObject);
|
||||
|
||||
// Stop the loop if lower-res tiles would all be covered by
|
||||
// already drawn tiles
|
||||
@ -1419,47 +1446,54 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
||||
* Update all tiles that contribute to the current view
|
||||
*
|
||||
*/
|
||||
_updateTilesInViewport: function() {
|
||||
var _this = this;
|
||||
_updateTilesInViewport: function(tiles) {
|
||||
let currentTime = $.now();
|
||||
let _this = this;
|
||||
this._tilesLoading = 0;
|
||||
this._wasBlending = this._isBlending;
|
||||
this._isBlending = false;
|
||||
this.loadingCoverage = {};
|
||||
let lowestLevel = tiles.length ? tiles[0].level : 0;
|
||||
|
||||
var drawArea = this.getDrawArea();
|
||||
let drawArea = this.getDrawArea();
|
||||
if(!drawArea){
|
||||
return;
|
||||
}
|
||||
|
||||
function updateTile(info){
|
||||
var tile = info.tile;
|
||||
let tile = info.tile;
|
||||
if(tile && tile.loaded){
|
||||
var needsDraw = _this._blendTile(
|
||||
let tileIsBlending = _this._blendTile(
|
||||
tile,
|
||||
tile.x,
|
||||
tile.y,
|
||||
info.level,
|
||||
info.levelOpacity,
|
||||
info.currentTime
|
||||
currentTime,
|
||||
lowestLevel
|
||||
);
|
||||
if(needsDraw){
|
||||
_this._needsDraw = true;
|
||||
}
|
||||
_this._isBlending = _this._isBlending || tileIsBlending;
|
||||
_this._needsDraw = _this._needsDraw || tileIsBlending || this._wasBlending;
|
||||
}
|
||||
}
|
||||
|
||||
// Update each tile in the _tilesToDraw list. As the tiles are updated,
|
||||
// Update each tile in the _lastDrawn list. As the tiles are updated,
|
||||
// the coverage provided is also updated. If a level provides coverage
|
||||
// as part of this process, discard tiles from lower levels
|
||||
let level = 0;
|
||||
for(let i = 0; i < this._tilesToDraw.length; i++){
|
||||
let tile = this._tilesToDraw[i];
|
||||
for(let i = 0; i < tiles.length; i++){
|
||||
let tile = tiles[i];
|
||||
updateTile(tile);
|
||||
if(this._providesCoverage(this.coverage, tile.level)){
|
||||
level = Math.max(level, tile.level);
|
||||
// break;
|
||||
}
|
||||
}
|
||||
if(level > 0){
|
||||
this._tilesToDraw = this._tilesToDraw.filter(tile => tile.level >= level);
|
||||
for( let levelKey in this._tilesToDraw ){
|
||||
if( levelKey < level ){
|
||||
delete this._tilesToDraw[levelKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
@ -1478,10 +1512,11 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
||||
* @param {Number} level
|
||||
* @param {Number} levelOpacity
|
||||
* @param {Number} currentTime
|
||||
* @returns {Boolean}
|
||||
* @param {Boolean} lowestLevel
|
||||
* @returns {Boolean} whether the opacity of this tile has changed
|
||||
*/
|
||||
_blendTile: function(tile, x, y, level, levelOpacity, currentTime ){
|
||||
var blendTimeMillis = 1000 * this.blendTime,
|
||||
_blendTile: function(tile, x, y, level, levelOpacity, currentTime, lowestLevel ){
|
||||
let blendTimeMillis = 1000 * this.blendTime,
|
||||
deltaTime,
|
||||
opacity;
|
||||
|
||||
@ -1492,20 +1527,23 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
||||
deltaTime = currentTime - tile.blendStart;
|
||||
opacity = blendTimeMillis ? Math.min( 1, deltaTime / ( blendTimeMillis ) ) : 1;
|
||||
|
||||
// if this tile is at the lowest level being drawn, render at opacity=1
|
||||
if(level === lowestLevel){
|
||||
opacity = 1;
|
||||
deltaTime = blendTimeMillis;
|
||||
}
|
||||
|
||||
if ( this.alwaysBlend ) {
|
||||
opacity *= levelOpacity;
|
||||
}
|
||||
|
||||
tile.opacity = opacity;
|
||||
|
||||
if ( opacity === 1 ) {
|
||||
this._setCoverage( this.coverage, level, x, y, true );
|
||||
this._hasOpaqueTile = true;
|
||||
} else if ( deltaTime < blendTimeMillis ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
// return true if the tile is still blending
|
||||
return deltaTime < blendTimeMillis;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1647,6 +1685,11 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
||||
boundsSize.x *= this._scaleSpring.current.value;
|
||||
boundsSize.y *= this._scaleSpring.current.value;
|
||||
|
||||
tile.positionedBounds.x = boundsTL.x;
|
||||
tile.positionedBounds.y = boundsTL.y;
|
||||
tile.positionedBounds.width = boundsSize.x;
|
||||
tile.positionedBounds.height = boundsSize.y;
|
||||
|
||||
var positionC = viewport.pixelFromPointNoRotate(boundsTL, true),
|
||||
positionT = viewport.pixelFromPointNoRotate(boundsTL, false),
|
||||
sizeC = viewport.deltaPixelsFromPointsNoRotate(boundsSize, true),
|
||||
|
@ -446,7 +446,7 @@ $.Viewer = function( options ) {
|
||||
delete this.drawerOptions.useCanvas;
|
||||
}
|
||||
let drawerPriority = Array.isArray(this.drawer) ? this.drawer : [this.drawer];
|
||||
let drawersToTry = drawerPriority.filter(d => ['context2d', 'html'].includes(d) || (d.prototype && d.prototype.isOpenSeadragonDrawer) );
|
||||
let drawersToTry = drawerPriority.filter(d => ['webgl', 'context2d', 'html'].includes(d) || (d.prototype && d.prototype.isOpenSeadragonDrawer) );
|
||||
if(drawerPriority.length !== drawersToTry.length){
|
||||
$.console.error('An invalid drawer was requested.');
|
||||
}
|
||||
@ -458,11 +458,19 @@ $.Viewer = function( options ) {
|
||||
this.drawer = null; // TO DO: how to deal with the possibility that none of the requested drawers are supported?
|
||||
for(let i = 0; i < drawersToTry.length; i++){
|
||||
let Drawer = drawersToTry[i];
|
||||
let optsKey = null;
|
||||
// replace text-based option with appropriate constructor
|
||||
if (Drawer === 'context2d'){
|
||||
Drawer = $.Context2dDrawer;
|
||||
optsKey = 'context2d';
|
||||
} else if (Drawer === 'html'){
|
||||
Drawer = $.HTMLDrawer;
|
||||
optsKey = 'html';
|
||||
} else if (Drawer === 'webgl'){
|
||||
Drawer = $.WebGLDrawer;
|
||||
optsKey = 'webgl';
|
||||
} else {
|
||||
optsKey = 'custom';
|
||||
}
|
||||
// if the drawer is supported, create it and break the loop
|
||||
if (Drawer.prototype.isSupported()){
|
||||
@ -471,7 +479,7 @@ $.Viewer = function( options ) {
|
||||
viewport: this.viewport,
|
||||
element: this.canvas,
|
||||
debugGridColor: this.debugGridColor,
|
||||
options: this.drawerOptions,
|
||||
options: this.drawerOptions[optsKey],
|
||||
});
|
||||
this.drawerOptions.constructor = Drawer;
|
||||
// TO DO: add an event that indicates which drawer was instantiated?
|
||||
@ -479,6 +487,10 @@ $.Viewer = function( options ) {
|
||||
}
|
||||
// TO DO: add an event that indicates that the selected drawer could not be created?
|
||||
}
|
||||
if(this.drawer === null){
|
||||
$.console.error('No drawer could be created!');
|
||||
throw('Error with creating the selected drawer(s)');
|
||||
}
|
||||
|
||||
|
||||
// Overlay container
|
||||
@ -1090,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;
|
||||
},
|
||||
|
||||
|
||||
@ -1137,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();
|
||||
|
872
src/webgldrawer.js
Normal file
872
src/webgldrawer.js
Normal file
@ -0,0 +1,872 @@
|
||||
/*
|
||||
* OpenSeadragon - WebGLDrawer
|
||||
*
|
||||
* 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( $ ){
|
||||
|
||||
// internal class Mat3: implements matrix operations
|
||||
// Modified from https://webglfundamentals.org/webgl/lessons/webgl-2d-matrices.html
|
||||
class Mat3{
|
||||
constructor(values){
|
||||
if(!values) {
|
||||
values = [
|
||||
0, 0, 0,
|
||||
0, 0, 0,
|
||||
0, 0, 0
|
||||
];
|
||||
}
|
||||
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
static makeIdentity(){
|
||||
return new Mat3([
|
||||
1, 0, 0,
|
||||
0, 1, 0,
|
||||
0, 0, 1
|
||||
]);
|
||||
}
|
||||
|
||||
static makeTranslation(tx, ty) {
|
||||
return new Mat3([
|
||||
1, 0, 0,
|
||||
0, 1, 0,
|
||||
tx, ty, 1,
|
||||
]);
|
||||
}
|
||||
|
||||
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,
|
||||
]);
|
||||
}
|
||||
|
||||
static makeScaling(sx, sy) {
|
||||
return new Mat3([
|
||||
sx, 0, 0,
|
||||
0, sy, 0,
|
||||
0, 0, 1,
|
||||
]);
|
||||
}
|
||||
|
||||
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,
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @class WebGLDrawer
|
||||
* @memberof OpenSeadragon
|
||||
* @classdesc Default implementation of WebGLDrawer 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.
|
||||
*/
|
||||
|
||||
$.WebGLDrawer = class WebGLDrawer extends OpenSeadragon.DrawerBase{
|
||||
constructor(options){
|
||||
super(options);
|
||||
|
||||
this.destroyed = false;
|
||||
// private members
|
||||
|
||||
this._TextureMap = new Map();
|
||||
this._TileMap = new Map();
|
||||
|
||||
this._gl = null;
|
||||
this._glLocs = null;
|
||||
this._glProgram = null;
|
||||
this._glPositionBuffer = null;
|
||||
this._outputCanvas = null;
|
||||
this._outputContext = null;
|
||||
this._clippingCanvas = null;
|
||||
this._clippingContext = null;
|
||||
this._renderingCanvas = null;
|
||||
|
||||
// Add listeners for events that require modifying the scene or camera
|
||||
this.viewer.addHandler("tile-ready", ev => this._tileReadyHandler(ev));
|
||||
this.viewer.addHandler("image-unloaded", ev => this._imageUnloadedHandler(ev));
|
||||
|
||||
// this.viewer is set by parent constructor
|
||||
// this.canvas is set by parent constructor, created and appended to the viewer container element
|
||||
this._setupCanvases();
|
||||
|
||||
this._setupRenderer();
|
||||
|
||||
this.context = this._outputContext; // API required by tests
|
||||
}
|
||||
|
||||
// Public API required by all Drawer implementations
|
||||
/**
|
||||
* Clean up the renderer, removing all resources
|
||||
*/
|
||||
destroy(){
|
||||
if(this.destroyed){
|
||||
return;
|
||||
}
|
||||
// clear all resources used by the renderer, geometries, textures etc
|
||||
let gl = this._gl;
|
||||
|
||||
// adapted from https://stackoverflow.com/a/23606581/1214731
|
||||
var numTextureUnits = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
|
||||
for (let unit = 0; unit < numTextureUnits; ++unit) {
|
||||
gl.activeTexture(gl.TEXTURE0 + unit);
|
||||
gl.bindTexture(gl.TEXTURE_2D, null);
|
||||
gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
|
||||
}
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
|
||||
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
|
||||
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
||||
|
||||
let canvases = Array.from(this._TextureMap.keys());
|
||||
canvases.forEach(canvas => {
|
||||
this._cleanupImageData(canvas); // deletes texture, removes from _TextureMap
|
||||
});
|
||||
|
||||
// Delete all our created resources
|
||||
gl.deleteBuffer(this._glPositionBuffer);
|
||||
|
||||
// TO DO: if/when render buffers or frame buffers are used, release them:
|
||||
// gl.deleteRenderbuffer(someRenderbuffer);
|
||||
// gl.deleteFramebuffer(someFramebuffer);
|
||||
|
||||
// make canvases 1 x 1 px and delete references
|
||||
this._renderingCanvas.width = this._renderingCanvas.height = 1;
|
||||
this._clippingCanvas.width = this._clippingCanvas.height = 1;
|
||||
this._outputCanvas.width = this._outputCanvas.height = 1;
|
||||
this._renderingCanvas = null;
|
||||
this._clippingCanvas = this._clippingContext = null;
|
||||
this._outputCanvas = this._outputContext = null;
|
||||
|
||||
let ext = gl.getExtension('WEBGL_lose_context');
|
||||
if(ext){
|
||||
ext.loseContext();
|
||||
}
|
||||
|
||||
// set our webgl context reference to null to enable garbage collection
|
||||
this._gl = null;
|
||||
|
||||
// set our destroyed flag to true
|
||||
this.destroyed = true;
|
||||
}
|
||||
|
||||
// Public API required by all Drawer implementations
|
||||
/**
|
||||
*
|
||||
* @returns true if the drawer supports rotation
|
||||
*/
|
||||
canRotate(){
|
||||
return true;
|
||||
}
|
||||
|
||||
// Public API required by all Drawer implementations
|
||||
|
||||
/**
|
||||
* @returns {Boolean} returns true if canvas and webgl are supported and
|
||||
* three.js has been exposed as a global variable named THREE
|
||||
*/
|
||||
isSupported(){
|
||||
let canvasElement = document.createElement( 'canvas' );
|
||||
let webglContext = $.isFunction( canvasElement.getContext ) &&
|
||||
canvasElement.getContext( 'webgl' );
|
||||
let ext = webglContext.getExtension('WEBGL_lose_context');
|
||||
if(ext){
|
||||
ext.loseContext();
|
||||
}
|
||||
return !!( webglContext );
|
||||
}
|
||||
|
||||
/**
|
||||
* create the HTML element (canvas in this case) that the image will be drawn into
|
||||
* @returns {Element} the canvas to draw into
|
||||
*/
|
||||
createDrawingElement(){
|
||||
let canvas = $.makeNeutralElement("canvas");
|
||||
let viewportSize = this._calculateCanvasSize();
|
||||
canvas.width = viewportSize.x;
|
||||
canvas.height = viewportSize.y;
|
||||
return canvas;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Array} tiledImages Array of TiledImage objects to draw
|
||||
*/
|
||||
draw(tiledImages){
|
||||
let viewport = {
|
||||
bounds: this.viewport.getBoundsNoRotate(true),
|
||||
center: this.viewport.getCenter(true),
|
||||
rotation: this.viewport.getRotation(true) * Math.PI / 180
|
||||
};
|
||||
|
||||
let flipMultiplier = this.viewport.flipped ? -1 : 1;
|
||||
// calculate view matrix for viewer
|
||||
let posMatrix = Mat3.makeTranslation(-viewport.center.x, -viewport.center.y);
|
||||
let scaleMatrix = Mat3.makeScaling(2 / viewport.bounds.width * flipMultiplier, -2 / viewport.bounds.height);
|
||||
let rotMatrix = Mat3.makeRotation(-viewport.rotation);
|
||||
let viewMatrix = scaleMatrix.multiply(rotMatrix).multiply(posMatrix);
|
||||
|
||||
//iterate over tiled imagesget the list of tiles to draw
|
||||
this._outputContext.clearRect(0, 0, this._outputCanvas.width, this._outputCanvas.height);
|
||||
|
||||
// TO DO: further optimization is possible.
|
||||
// If no clipping and no composite operation, the tiled images
|
||||
// can all be drawn onto the rendering canvas at the same time, avoiding
|
||||
// unnecessary clearing and copying of the pixel data.
|
||||
// For now, I'm doing it this way to replicate full functionality
|
||||
// of the context2d drawer
|
||||
tiledImages.forEach( (tiledImage, i) => {
|
||||
// clear the rendering canvas
|
||||
this._gl.clear(this._gl.COLOR_BUFFER_BIT);
|
||||
|
||||
// set opacity for this image
|
||||
this._gl.uniform1f(this._glLocs.uOpacityMultiplier, tiledImage.opacity);
|
||||
|
||||
//get the list of tiles to draw
|
||||
let tilesToDraw = tiledImage.getTilesToDraw();
|
||||
|
||||
if(tilesToDraw.length === 0){
|
||||
return;
|
||||
}
|
||||
|
||||
let overallMatrix = viewMatrix;
|
||||
|
||||
let imageRotation = tiledImage.getRotation(true);
|
||||
if( imageRotation % 360 !== 0){
|
||||
let imageRotationMatrix = Mat3.makeRotation(-imageRotation * Math.PI / 180);
|
||||
let imageCenter = tiledImage.getBoundsNoRotate(true).getCenter();
|
||||
let t1 = Mat3.makeTranslation(imageCenter.x, imageCenter.y);
|
||||
let t2 = Mat3.makeTranslation(-imageCenter.x, -imageCenter.y);
|
||||
|
||||
// update the view matrix to account for this image's rotation
|
||||
let localMatrix = t1.multiply(imageRotationMatrix).multiply(t2);
|
||||
overallMatrix = viewMatrix.multiply(localMatrix);
|
||||
}
|
||||
|
||||
for(let i = 0; i < tilesToDraw.length; i++){
|
||||
let tile = tilesToDraw[i].tile;
|
||||
let texture = this._TextureMap.get(tile.getCanvasContext().canvas);
|
||||
if(texture){
|
||||
this._drawTile(tile, tiledImage, texture, overallMatrix, tiledImage.opacity);
|
||||
} else {
|
||||
// console.log('No tile info', tile);
|
||||
}
|
||||
}
|
||||
|
||||
// composite onto the output canvas, clipping if necessary
|
||||
this._outputContext.save();
|
||||
|
||||
// set composite operation; ignore for first image drawn
|
||||
this._outputContext.globalCompositeOperation = i === 0 ? null : tiledImage.compositeOperation || this.viewer.compositeOperation;
|
||||
if(tiledImage._croppingPolygons || tiledImage._clip){
|
||||
this._renderToClippingCanvas(tiledImage);
|
||||
this._outputContext.drawImage(this._clippingCanvas, 0, 0);
|
||||
|
||||
} else {
|
||||
this._outputContext.drawImage(this._renderingCanvas, 0, 0);
|
||||
}
|
||||
this._outputContext.restore();
|
||||
if(tiledImage.debugMode){
|
||||
let colorIndex = this.viewer.world.getIndexOfItem(tiledImage) % this.debugGridColor.length;
|
||||
let strokeStyle = this.debugGridColor[colorIndex];
|
||||
let fillStyle = this.debugGridColor[colorIndex];
|
||||
this._drawDebugInfo(tilesToDraw, tiledImage, strokeStyle, fillStyle);
|
||||
}
|
||||
|
||||
// TO DO: this is necessary for the tests to pass, but doesn't totally make sense for the webgl drawer.
|
||||
// Iterate over the tiles that were just drawn and fire the tile-drawn event
|
||||
for(let i = 0; i < tilesToDraw.length; i++){
|
||||
let tile = tilesToDraw[i].tile;
|
||||
|
||||
if( this.viewer ){
|
||||
/**
|
||||
* Raised when a tile is drawn to the canvas
|
||||
*
|
||||
* @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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// 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 * $.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();
|
||||
}
|
||||
_getTextureDataFromTile(tile){
|
||||
return tile.getCanvasContext().canvas;
|
||||
}
|
||||
|
||||
// Private methods
|
||||
_drawTile(tile, tiledImage, texture, viewMatrix, imageOpacity){
|
||||
|
||||
let gl = this._gl;
|
||||
|
||||
// x, y, w, h in viewport coords
|
||||
let x = tile.positionedBounds.x;
|
||||
let y = tile.positionedBounds.y;
|
||||
let w = tile.positionedBounds.width;
|
||||
let h = tile.positionedBounds.height;
|
||||
|
||||
let matrix = new Mat3([
|
||||
w, 0, 0,
|
||||
0, h, 0,
|
||||
x, y, 1,
|
||||
]);
|
||||
|
||||
|
||||
if(tile.flipped){
|
||||
// flip the tile around the center of the unit quad
|
||||
let t1 = Mat3.makeTranslation(0.5, 0);
|
||||
let t2 = Mat3.makeTranslation(-0.5, 0);
|
||||
|
||||
// update the view matrix to account for this image's rotation
|
||||
let localMatrix = t1.multiply(Mat3.makeScaling(-1, 1)).multiply(t2);
|
||||
matrix = matrix.multiply(localMatrix);
|
||||
}
|
||||
|
||||
let overallMatrix = viewMatrix.multiply(matrix);
|
||||
|
||||
if(tile.opacity !== 1 && tile.x === 0 && tile.y === 0){
|
||||
// set opacity for this image
|
||||
this._gl.uniform1f(this._glLocs.uOpacityMultiplier, imageOpacity * tile.opacity);
|
||||
}
|
||||
|
||||
gl.uniformMatrix3fv(this._glLocs.uMatrix, false, overallMatrix.values);
|
||||
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||||
|
||||
if(this.continuousTileRefresh){
|
||||
// Upload the image into the texture.
|
||||
let tileContext = tile.getCanvasContext();
|
||||
this._raiseTileDrawingEvent(tiledImage, this._outputContext, tile, tileContext);
|
||||
this._uploadImageData(tileContext);
|
||||
}
|
||||
|
||||
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||
}
|
||||
|
||||
_setupRenderer(){
|
||||
|
||||
if(!this._gl){
|
||||
$.console.error('_setupCanvases must be called before _setupRenderer');
|
||||
}
|
||||
|
||||
const vertexShaderProgram = `
|
||||
attribute vec2 a_position;
|
||||
|
||||
uniform mat3 u_matrix;
|
||||
|
||||
varying vec2 v_texCoord;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(u_matrix * vec3(a_position, 1), 1);
|
||||
|
||||
// because we're using a unit quad we can just use
|
||||
// the same data for our texcoords.
|
||||
v_texCoord = a_position;
|
||||
}
|
||||
`;
|
||||
|
||||
const fragmentShaderProgram = `
|
||||
precision mediump float;
|
||||
|
||||
// our texture
|
||||
uniform sampler2D u_image;
|
||||
|
||||
// the texCoords passed in from the vertex shader.
|
||||
varying vec2 v_texCoord;
|
||||
|
||||
// the opacity multiplier for the image
|
||||
uniform float u_opacity_multiplier;
|
||||
|
||||
void main() {
|
||||
gl_FragColor = texture2D(u_image, v_texCoord);
|
||||
// gl_FragColor *= u_opacity_multiplier;
|
||||
}
|
||||
`;
|
||||
let gl = this._gl;
|
||||
this._glProgram = this.constructor.initShaderProgram(gl, vertexShaderProgram, fragmentShaderProgram);
|
||||
gl.useProgram(this._glProgram);
|
||||
gl.enable(gl.BLEND);
|
||||
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
this._glLocs = {
|
||||
aPosition: gl.getAttribLocation(this._glProgram, 'a_position'),
|
||||
uMatrix: gl.getUniformLocation(this._glProgram, 'u_matrix'),
|
||||
uImage: gl.getUniformLocation(this._glProgram, 'u_image'),
|
||||
uOpacityMultiplier: gl.getUniformLocation(this._glProgram, 'u_opacity_multiplier')
|
||||
};
|
||||
|
||||
// provide texture coordinates for the rectangle.
|
||||
this._glPositionBuffer = gl.createBuffer(); //keep reference to clear it later
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this._glPositionBuffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
|
||||
0.0, 0.0,
|
||||
1.0, 0.0,
|
||||
0., 1.0,
|
||||
0.0, 1.0,
|
||||
1.0, 0.0,
|
||||
1.0, 1.0]), gl.STATIC_DRAW);
|
||||
gl.enableVertexAttribArray(this._glLocs.aPosition);
|
||||
gl.vertexAttribPointer(this._glLocs.aPosition, 2, gl.FLOAT, false, 0, 0);
|
||||
|
||||
}
|
||||
|
||||
_setupCanvases(){
|
||||
let _this = this;
|
||||
|
||||
this._outputCanvas = this.canvas; //output canvas
|
||||
this._outputContext = this._outputCanvas.getContext('2d');
|
||||
|
||||
this._renderingCanvas = document.createElement('canvas');
|
||||
|
||||
this._clippingCanvas = document.createElement('canvas');
|
||||
this._clippingContext = this._clippingCanvas.getContext('2d');
|
||||
this._renderingCanvas.width = this._clippingCanvas.width = this._outputCanvas.width;
|
||||
this._renderingCanvas.height = this._clippingCanvas.height = this._outputCanvas.height;
|
||||
|
||||
this._gl = this._renderingCanvas.getContext('webgl');
|
||||
|
||||
//make the additional canvas elements mirror size changes to the output canvas
|
||||
this.viewer.addHandler("resize", function(){
|
||||
|
||||
if(_this._outputCanvas !== _this.viewer.drawer.canvas){
|
||||
_this._outputCanvas.style.width = _this.viewer.drawer.canvas.clientWidth + 'px';
|
||||
_this._outputCanvas.style.height = _this.viewer.drawer.canvas.clientHeight + 'px';
|
||||
}
|
||||
|
||||
let viewportSize = _this._calculateCanvasSize();
|
||||
if( _this._outputCanvas.width !== viewportSize.x ||
|
||||
_this._outputCanvas.height !== viewportSize.y ) {
|
||||
_this._outputCanvas.width = viewportSize.x;
|
||||
_this._outputCanvas.height = viewportSize.y;
|
||||
}
|
||||
|
||||
_this._renderingCanvas.style.width = _this._outputCanvas.clientWidth + 'px';
|
||||
_this._renderingCanvas.style.height = _this._outputCanvas.clientHeight + 'px';
|
||||
_this._renderingCanvas.width = _this._clippingCanvas.width = _this._outputCanvas.width;
|
||||
_this._renderingCanvas.height = _this._clippingCanvas.height = _this._outputCanvas.height;
|
||||
_this._gl.viewport(0, 0, _this._renderingCanvas.width, _this._renderingCanvas.height);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
_tileReadyHandler(event){
|
||||
let tile = event.tile;
|
||||
let tileContext = tile.getCanvasContext();
|
||||
let canvas = tileContext.canvas;
|
||||
let texture = this._TextureMap.get(canvas);
|
||||
|
||||
// if this is a new image for us, create a texture
|
||||
if(!texture){
|
||||
let gl = this._gl;
|
||||
|
||||
// create a gl Texture for this tile and bind the canvas with the image data
|
||||
texture = gl.createTexture();
|
||||
// add it to our _TextureMap
|
||||
this._TextureMap.set(canvas, texture);
|
||||
|
||||
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||||
// Set the parameters so we can render any size image.
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
||||
|
||||
// Upload the image into the texture.
|
||||
this._uploadImageData(tileContext);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_uploadImageData(tileContext){
|
||||
let gl = this._gl;
|
||||
try{
|
||||
let canvas = tileContext.canvas;
|
||||
if(!canvas){
|
||||
throw('Tile context does not have a canvas', tileContext);
|
||||
}
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas);
|
||||
} catch(e) {
|
||||
$.console.error('Error uploading canvas data to webgl', e);
|
||||
}
|
||||
}
|
||||
|
||||
_imageUnloadedHandler(event){
|
||||
let canvas = event.context2D.canvas;
|
||||
this._cleanupImageData(canvas);
|
||||
}
|
||||
|
||||
_cleanupImageData(tileCanvas){
|
||||
let texture = this._TextureMap.get(tileCanvas);
|
||||
//remove from the map
|
||||
this._TextureMap.delete(tileCanvas);
|
||||
|
||||
//release the texture from the GPU
|
||||
this._gl.deleteTexture(texture);
|
||||
}
|
||||
// private
|
||||
// necessary for clip testing to pass (test uses spyOnce(drawer._setClip))
|
||||
_setClip(rect){
|
||||
this._clippingContext.beginPath();
|
||||
this._clippingContext.rect(rect.x, rect.y, rect.width, rect.height);
|
||||
this._clippingContext.clip();
|
||||
}
|
||||
_renderToClippingCanvas(item){
|
||||
let _this = this;
|
||||
|
||||
this._clippingContext.clearRect(0, 0, this._clippingCanvas.width, this._clippingCanvas.height);
|
||||
this._clippingContext.save();
|
||||
|
||||
if(item._clip){
|
||||
var box = item.imageToViewportRectangle(item._clip, true);
|
||||
var rect = this.viewportToDrawerRectangle(box);
|
||||
this._setClip(rect);
|
||||
}
|
||||
if(item._croppingPolygons){
|
||||
let polygons = item._croppingPolygons.map(function (polygon) {
|
||||
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);
|
||||
return clipPoint;
|
||||
});
|
||||
});
|
||||
this._clippingContext.beginPath();
|
||||
polygons.forEach(function (polygon) {
|
||||
polygon.forEach(function (coord, i) {
|
||||
_this._clippingContext[i === 0 ? 'moveTo' : 'lineTo'](coord.x, coord.y);
|
||||
});
|
||||
});
|
||||
this._clippingContext.clip();
|
||||
}
|
||||
|
||||
this._clippingContext.drawImage(this._renderingCanvas, 0, 0);
|
||||
|
||||
this._clippingContext.restore();
|
||||
}
|
||||
|
||||
// private
|
||||
_offsetForRotation(options) {
|
||||
var point = options.point ?
|
||||
options.point.times($.pixelDensityRatio) :
|
||||
new $.Point(this._outputCanvas.width / 2, this._outputCanvas.height / 2);
|
||||
|
||||
var context = this._outputContext;
|
||||
context.save();
|
||||
|
||||
context.translate(point.x, point.y);
|
||||
if(this.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
|
||||
* @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
|
||||
* 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
|
||||
);
|
||||
}
|
||||
|
||||
// private
|
||||
_drawDebugInfo( tilesToDraw, tiledImage, stroke, fill ) {
|
||||
|
||||
for ( var i = tilesToDraw.length - 1; i >= 0; i-- ) {
|
||||
var tile = tilesToDraw[ i ].tile;
|
||||
try {
|
||||
this._drawDebugInfoOnTile(tile, tilesToDraw.length, i, tiledImage, stroke, fill);
|
||||
} catch(e) {
|
||||
$.console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
// private
|
||||
_drawDebugInfoOnTile(tile, count, i, tiledImage, stroke, fill) {
|
||||
|
||||
var context = this._outputContext;
|
||||
context.save();
|
||||
context.lineWidth = 2 * $.pixelDensityRatio;
|
||||
context.font = 'small-caps bold ' + (13 * $.pixelDensityRatio) + 'px arial';
|
||||
context.strokeStyle = stroke;
|
||||
context.fillStyle = fill;
|
||||
|
||||
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
|
||||
_restoreRotationChanges() {
|
||||
var context = this._outputContext;
|
||||
context.restore();
|
||||
}
|
||||
|
||||
// modified from https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Adding_2D_content_to_a_WebGL_context
|
||||
static initShaderProgram(gl, vsSource, fsSource) {
|
||||
const vertexShader = this.loadShader(gl, gl.VERTEX_SHADER, vsSource);
|
||||
const fragmentShader = this.loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
|
||||
|
||||
// Create the shader program
|
||||
|
||||
const shaderProgram = gl.createProgram();
|
||||
gl.attachShader(shaderProgram, vertexShader);
|
||||
gl.attachShader(shaderProgram, fragmentShader);
|
||||
gl.linkProgram(shaderProgram);
|
||||
|
||||
// If creating the shader program failed, alert
|
||||
|
||||
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
|
||||
alert(
|
||||
`Unable to initialize the shader program: ${gl.getProgramInfoLog(
|
||||
shaderProgram
|
||||
)}`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return shaderProgram;
|
||||
}
|
||||
|
||||
//
|
||||
// creates a shader of the given type, uploads the source and
|
||||
// compiles it.
|
||||
//
|
||||
static loadShader(gl, type, source) {
|
||||
const shader = gl.createShader(type);
|
||||
|
||||
// Send the source to the shader object
|
||||
|
||||
gl.shaderSource(shader, source);
|
||||
|
||||
// Compile the shader program
|
||||
|
||||
gl.compileShader(shader);
|
||||
|
||||
// See if it compiled successfully
|
||||
|
||||
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
||||
alert(
|
||||
`An error occurred compiling the shaders: ${gl.getShaderInfoLog(shader)}`
|
||||
);
|
||||
gl.deleteShader(shader);
|
||||
return null;
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}( OpenSeadragon ));
|
@ -257,10 +257,10 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
|
||||
*/
|
||||
draw: function() {
|
||||
this.viewer.drawer.draw(this._items);
|
||||
this._items.forEach(function(item){
|
||||
item.setDrawn();
|
||||
});
|
||||
this._needsDraw = false;
|
||||
this._items.forEach(function(item){
|
||||
this._needsDraw = item.setDrawn() || this._needsDraw || true;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1,4 +1,10 @@
|
||||
// import 'https://cdnjs.cloudflare.com/ajax/libs/three.js/0.149.0/three.min.js';
|
||||
|
||||
// TO DO LIST:
|
||||
// TO DO: Viewport flip does not work right
|
||||
// TO DO: wrapHorizontal and wrapVertical do not work right with scaled TiledImages
|
||||
// TO DO: wrapping doesn't work right with resolution of wrapped part when zoomed in
|
||||
|
||||
import '../lib/three.js';
|
||||
const THREE = window.THREE;
|
||||
|
||||
@ -182,7 +188,7 @@ export class ThreeJSDrawer extends OpenSeadragon.DrawerBase{
|
||||
|
||||
}
|
||||
|
||||
tiledImages.forEach(tiledImage => tiledImage._needsDraw = false);
|
||||
//tiledImages.forEach(tiledImage => tiledImage.setDrawn());
|
||||
}
|
||||
|
||||
// Public API required by all Drawer implementations
|
||||
@ -349,7 +355,7 @@ export class ThreeJSDrawer extends OpenSeadragon.DrawerBase{
|
||||
}
|
||||
|
||||
_tileUnloadedHandler(event){
|
||||
console.log('Tile unloaded',event);
|
||||
// console.log('Tile unloaded',event);
|
||||
let tile = event.tile;
|
||||
if(!this._tileMap[tile.cacheKey]){
|
||||
//already cleaned up
|
||||
@ -581,6 +587,26 @@ export class ThreeJSDrawer extends OpenSeadragon.DrawerBase{
|
||||
}
|
||||
|
||||
// private
|
||||
|
||||
/**
|
||||
* @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
|
||||
* point is at 0 degree.
|
||||
* @param {OpenSeadragon.Point} point - the pixel point to convert
|
||||
* @returns {OpenSeadragon.Point} Point in drawer coordinate system.
|
||||
*/
|
||||
_viewportCoordToDrawerCoord(point) {
|
||||
let $ = OpenSeadragon;
|
||||
var vpPoint = this.viewport.pixelFromPointNoRotate(point, true);
|
||||
return new $.Point(
|
||||
vpPoint.x * $.pixelDensityRatio,
|
||||
vpPoint.y * $.pixelDensityRatio
|
||||
);
|
||||
}
|
||||
|
||||
_offsetForRotation(options) {
|
||||
var point = options.point ?
|
||||
options.point.times(OpenSeadragon.pixelDensityRatio) :
|
||||
|
@ -68,7 +68,28 @@
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<h2>Use a WebGL drawer implementation (using three.js) instead of the default context2d drawer</h2>
|
||||
|
||||
<h2>Compare behavior of <strong>Context2d</strong> and <strong>WebGL</strong> (via three.js) drawers</h2>
|
||||
<div class="mirrored">
|
||||
<div>
|
||||
<h3>Context2d drawer (default in OSD <= 4.1.0)</h3>
|
||||
<div id="context2d" class="viewer-container"></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>New WebGL drawer</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>Use a custom plugin drawer - example using three.js</h2>
|
||||
<div class="mirrored">
|
||||
<div>
|
||||
<div class="description">
|
||||
@ -97,74 +118,6 @@
|
||||
</div>
|
||||
|
||||
|
||||
<h2>Compare behavior of <strong>Context2d</strong> and <strong>WebGL</strong> (via three.js) drawers</h2>
|
||||
<div class="mirrored">
|
||||
<div>
|
||||
<h3>Use default OpenSeadragon viewer to pan/zoom</h3>
|
||||
<div id="contentDiv" class="viewer-container"></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>WebGL drawer linked using event listeners </h3>
|
||||
<div id="three-canvas-container" class="viewer-container"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="image-picker">
|
||||
<h3>Image options (drag and drop to re-order images)</h3>
|
||||
<!-- <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">
|
||||
<label>X: <input type="number" value="0" data-image="rainbow" data-field="x"> </label>
|
||||
<label>Y: <input type="number" value="0" data-image="rainbow" data-field="y"> </label>
|
||||
<label>Width: <input type="number" value="1" data-image="rainbow" data-field="width" min="0"> </label>
|
||||
<label>Degrees: <input type="number" value="0" data-image="rainbow" data-field="degrees"> </label>
|
||||
<label>Opacity: <input type="number" value="1" data-image="rainbow" data-field="opacity" min="0" max="1" step="0.2"> </label>
|
||||
<label>Flipped: <input type="checkbox" data-image="rainbow" data-field="flipped"></label>
|
||||
<label>Cropped: <input type="checkbox" data-image="rainbow" data-field="cropped"></label>
|
||||
<label>Debug: <input type="checkbox" data-image="rainbow" data-field="debug"></label>
|
||||
<label>Composite: <select data-image="rainbow" data-field="composite"></select></label>
|
||||
<label>Wrapping: <select data-image="rainbow" data-field="wrapping"></select></label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="image-options">
|
||||
<span class="ui-icon ui-icon-arrowthick-2-n-s"></span>
|
||||
<label><input type="checkbox" data-image="leaves" class="toggle"> Leaves</label>
|
||||
<div class="option-grid">
|
||||
<label>X: <input type="number" value="0" data-image="leaves" data-field="x"> </label>
|
||||
<label>Y: <input type="number" value="0" data-image="leaves" data-field="y"> </label>
|
||||
<label>Width: <input type="number" value="1" data-image="leaves" data-field="width" min="0"> </label>
|
||||
<label>Degrees: <input type="number" value="0" data-image="leaves" data-field="degrees"> </label>
|
||||
<label>Opacity: <input type="number" value="1" data-image="leaves" data-field="opacity" min="0" max="1" step="0.2"> </label>
|
||||
<label>Flipped: <input type="checkbox" data-image="leaves" data-field="flipped"></label>
|
||||
<label>Cropped: <input type="checkbox" data-image="leaves" data-field="cropped"></label>
|
||||
<label>Debug: <input type="checkbox" data-image="leaves" data-field="debug"></label>
|
||||
<label>Composite: <select data-image="leaves" data-field="composite" ></select></label>
|
||||
<label>Wrapping: <select data-image="leaves" data-field="wrapping"></select></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="image-options">
|
||||
<span class="ui-icon ui-icon-arrowthick-2-n-s"></span>
|
||||
<label><input type="checkbox" data-image="bblue" class="toggle"> BBlue PNG</label>
|
||||
<div class="option-grid">
|
||||
<label>X: <input type="number" value="0" data-image="bblue" data-field="x"> </label>
|
||||
<label>Y: <input type="number" value="0" data-image="bblue" data-field="y"> </label>
|
||||
<label>Width: <input type="number" value="1" data-image="bblue" data-field="width" min="0"> </label>
|
||||
<label>Degrees: <input type="number" value="0" data-image="bblue" data-field="degrees"> </label>
|
||||
<label>Opacity: <input type="number" value="1" data-image="bblue" data-field="opacity" min="0" max="1" step="0.2"> </label>
|
||||
<label>Flipped: <input type="checkbox" data-image="bblue" data-field="flipped"></label>
|
||||
<label>Cropped: <input type="checkbox" data-image="bblue" data-field="cropped"></label>
|
||||
<label>Debug: <input type="checkbox" data-image="bblue" data-field="debug"></label>
|
||||
<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>
|
||||
|
||||
|
||||
<h2>HTMLDrawer: legacy pre-HTML5 drawer that uses <img> elements for tiles</h2>
|
||||
<div class="mirrored">
|
||||
|
@ -1,8 +1,3 @@
|
||||
//imports
|
||||
import { ThreeJSDrawer } from './threejsdrawer.js';
|
||||
// import { default as Stats } from "https://cdnjs.cloudflare.com/ajax/libs/stats.js/17/Stats.js";
|
||||
//globals
|
||||
// const canvas = document.querySelector('#three-canvas');
|
||||
const sources = {
|
||||
"rainbow":"../data/testpattern.dzi",
|
||||
"leaves":"../data/iiif_2_0_sizes/info.json",
|
||||
@ -25,63 +20,102 @@ var stats = null;
|
||||
// document.body.appendChild( stats.dom );
|
||||
|
||||
|
||||
//Double viewer setup for comparison - Context2dDrawer and ThreeJSDrawer
|
||||
//Double viewer setup for comparison - Context2dDrawer and WebGLDrawer
|
||||
|
||||
var viewer = window.viewer = OpenSeadragon({
|
||||
id: "contentDiv",
|
||||
let viewer1 = window.viewer1 = OpenSeadragon({
|
||||
id: "context2d",
|
||||
prefixUrl: "../../build/openseadragon/images/",
|
||||
// minZoomImageRatio:0.8,
|
||||
// maxZoomPixelRatio:0.5,
|
||||
minZoomImageRatio:0.01,
|
||||
maxZoomPixelRatio:100,
|
||||
smoothTileEdgesMinZoom:1.1,
|
||||
crossOriginPolicy: 'Anonymous',
|
||||
ajaxWithCredentials: false,
|
||||
drawer:'context2d',
|
||||
// maxImageCacheCount: 30,
|
||||
drawer:'webgl',
|
||||
blendTime:0
|
||||
});
|
||||
|
||||
|
||||
// Mirror the interactive viewer with Context2dDrawer onto a separate canvas using ThreeJSDrawer
|
||||
let threeRenderer = window.threeRenderer = new ThreeJSDrawer({viewer, viewport: viewer.viewport, element:viewer.element, stats: stats});
|
||||
//make the test canvas mirror all changes to the viewer canvas
|
||||
let viewerCanvas = viewer.drawer.canvas;
|
||||
let canvas = threeRenderer.canvas;
|
||||
let canvasContainer = $('#three-canvas-container').append(canvas);
|
||||
viewer.addHandler("resize", function(){
|
||||
canvasContainer[0].style.width = viewerCanvas.clientWidth+'px';
|
||||
canvasContainer[0].style.height = viewerCanvas.clientHeight+'px';
|
||||
// canvas.width = viewerCanvas.width;
|
||||
// canvas.height = viewerCanvas.height;
|
||||
});
|
||||
|
||||
|
||||
// Single viewer showing how to use plugin Drawer via configuration
|
||||
// Also shows sequence mode
|
||||
var viewer2 = window.viewer2 = OpenSeadragon({
|
||||
id: "three-viewer",
|
||||
let viewer2 = window.viewer2 = OpenSeadragon({
|
||||
id: "webgl",
|
||||
prefixUrl: "../../build/openseadragon/images/",
|
||||
minZoomImageRatio:0.01,
|
||||
drawer: ThreeJSDrawer,
|
||||
tileSources: [sources['leaves'], sources['rainbow'], sources['duomo']],
|
||||
sequenceMode: true,
|
||||
imageSmoothingEnabled: false,
|
||||
maxZoomPixelRatio:100,
|
||||
smoothTileEdgesMinZoom:1.1,
|
||||
crossOriginPolicy: 'Anonymous',
|
||||
ajaxWithCredentials: false
|
||||
ajaxWithCredentials: false,
|
||||
// maxImageCacheCount: 30,
|
||||
drawer:'webgl',
|
||||
blendTime:0.0
|
||||
});
|
||||
|
||||
// Single viewer showing how to use plugin Drawer via configuration
|
||||
// Also shows sequence mode
|
||||
var viewer3 = window.viewer3 = OpenSeadragon({
|
||||
id: "htmldrawer",
|
||||
drawer:'html',
|
||||
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);
|
||||
|
||||
|
||||
// // Single viewer showing how to use plugin Drawer via configuration
|
||||
// // Also shows sequence mode
|
||||
// var viewer3 = window.viewer3 = OpenSeadragon({
|
||||
// id: "three-viewer",
|
||||
// prefixUrl: "../../build/openseadragon/images/",
|
||||
// minZoomImageRatio:0.01,
|
||||
// drawer: ThreeJSDrawer,
|
||||
// tileSources: [sources['leaves'], sources['rainbow'], sources['duomo']],
|
||||
// sequenceMode: true,
|
||||
// imageSmoothingEnabled: false,
|
||||
// crossOriginPolicy: 'Anonymous',
|
||||
// ajaxWithCredentials: false
|
||||
// });
|
||||
|
||||
// // Single viewer showing how to use plugin Drawer via configuration
|
||||
// // Also shows sequence mode
|
||||
// var viewer4 = window.viewer4 = 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
|
||||
// });
|
||||
|
||||
|
||||
|
||||
@ -90,11 +124,18 @@ $('#three-viewer').resizable(true);
|
||||
$('#contentDiv').resizable(true);
|
||||
$('#image-picker').sortable({
|
||||
update: function(event, ui){
|
||||
let thisItem = ui.item.find('.toggle').data('item');
|
||||
let items = $('#image-picker input.toggle:checked').toArray().map(item=>$(item).data('item'));
|
||||
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){
|
||||
viewer.world.setItemIndex(thisItem, newIndex);
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -110,12 +151,13 @@ Object.keys(sources).forEach((key, index)=>{
|
||||
$('#image-picker input.toggle').on('change',function(){
|
||||
let data = $(this).data();
|
||||
if(this.checked){
|
||||
addTileSource(data.image, this);
|
||||
|
||||
addTileSource(viewer1, data.image, this);
|
||||
// addTileSource(viewer2, data.image, this);
|
||||
} else {
|
||||
if(data.item){
|
||||
viewer.world.removeItem(data.item);
|
||||
$(this).data('item',null);
|
||||
if(data.item1){
|
||||
viewer1.world.removeItem(data.item1);
|
||||
// viewer2.world.removeItem(data.item2);
|
||||
$(this).data({item1: null, item2: null});
|
||||
}
|
||||
}
|
||||
}).trigger('change');
|
||||
@ -123,7 +165,13 @@ $('#image-picker input.toggle').on('change',function(){
|
||||
$('#image-picker input:not(.toggle)').on('change',function(){
|
||||
let data = $(this).data();
|
||||
let value = $(this).val();
|
||||
let tiledImage = $(`#image-picker input.toggle[data-image=${data.image}]`).data('item');
|
||||
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){
|
||||
if(tiledImage){
|
||||
//item = tiledImage
|
||||
let field = data.field;
|
||||
@ -142,16 +190,16 @@ $('#image-picker input:not(.toggle)').on('change',function(){
|
||||
} else if (field == 'opacity'){
|
||||
tiledImage.setOpacity(Number(value));
|
||||
} else if (field == 'flipped'){
|
||||
tiledImage.setFlip($(this).prop('checked'));
|
||||
tiledImage.setFlip($(item).prop('checked'));
|
||||
} else if (field == 'cropped'){
|
||||
if( $(this).prop('checked') ){
|
||||
if( $(item).prop('checked') ){
|
||||
let croppingPolygons = [ [{x:200, y:200}, {x:800, y:200}, {x:500, y:800}] ];
|
||||
tiledImage.setCroppingPolygons(croppingPolygons);
|
||||
} else {
|
||||
tiledImage.resetCroppingPolygons();
|
||||
}
|
||||
} else if (field == 'clipped'){
|
||||
if( $(this).prop('checked') ){
|
||||
if( $(item).prop('checked') ){
|
||||
let clipRect = new OpenSeadragon.Rect(2000, 0, 3000, 4000);
|
||||
tiledImage.setClip(clipRect);
|
||||
} else {
|
||||
@ -159,26 +207,40 @@ $('#image-picker input:not(.toggle)').on('change',function(){
|
||||
}
|
||||
}
|
||||
else if (field == 'debug'){
|
||||
if( $(this).prop('checked') ){
|
||||
if( $(item).prop('checked') ){
|
||||
tiledImage.debugMode = true;
|
||||
} else {
|
||||
tiledImage.debugMode = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$('.image-options select[data-field=composite]').append(getCompositeOperationOptions()).on('change',function(){
|
||||
let data = $(this).data();
|
||||
let tiledImage = $(`#image-picker input.toggle[data-image=${data.image}]`).data('item');
|
||||
if(tiledImage){
|
||||
tiledImage.setCompositeOperation(this.value == 'null' ? null : this.value);
|
||||
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('item');
|
||||
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.viewer.raiseEvent('opacity-change');//trigger a redraw for the webgl renderer. TODO: fix this hack.
|
||||
}
|
||||
tiledImage = $(`#image-picker input.toggle[data-image=${data.image}]`).data('item2');
|
||||
if(tiledImage){
|
||||
switch(this.value){
|
||||
case "None": tiledImage.wrapHorizontal = tiledImage.wrapVertical = false; break;
|
||||
@ -220,7 +282,7 @@ function getCompositeOperationOptions(){
|
||||
|
||||
}
|
||||
|
||||
function addTileSource(image, checkbox){
|
||||
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){
|
||||
@ -236,10 +298,11 @@ function addTileSource(image, checkbox){
|
||||
|
||||
let tileSource = sources[image];
|
||||
if(tileSource){
|
||||
viewer.addTiledImage({tileSource: tileSource, ...options, index: insertionIndex});
|
||||
viewer.world.addOnceHandler('add-item',function(ev){
|
||||
viewer&&viewer.addTiledImage({tileSource: tileSource, ...options, index: insertionIndex});
|
||||
viewer&&viewer.world.addOnceHandler('add-item',function(ev){
|
||||
let item = ev.item;
|
||||
$(checkbox).data('item',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
|
||||
});
|
||||
}
|
||||
|
@ -56,6 +56,9 @@
|
||||
if (viewer && viewer.close) {
|
||||
viewer.close();
|
||||
}
|
||||
if (viewer && viewer.destroy){
|
||||
viewer.destroy();
|
||||
}
|
||||
|
||||
viewer = null;
|
||||
}
|
||||
|
@ -19,6 +19,9 @@
|
||||
if (viewer && viewer.close) {
|
||||
viewer.close();
|
||||
}
|
||||
if (viewer && viewer.destroy){
|
||||
viewer.destroy();
|
||||
}
|
||||
|
||||
viewer = null;
|
||||
}
|
||||
@ -319,8 +322,8 @@
|
||||
height: 155
|
||||
} ]
|
||||
} );
|
||||
viewer.addOnceHandler('tile-drawn', function() {
|
||||
assert.ok(OpenSeadragon.isCanvasTainted(viewer.drawer.context.canvas),
|
||||
viewer.addOnceHandler('tile-drawn', function(event) {
|
||||
assert.ok(OpenSeadragon.isCanvasTainted(event.tile.getCanvasContext().canvas),
|
||||
"Canvas should be tainted.");
|
||||
done();
|
||||
});
|
||||
@ -339,8 +342,8 @@
|
||||
height: 155
|
||||
} ]
|
||||
} );
|
||||
viewer.addOnceHandler('tile-drawn', function() {
|
||||
assert.ok(!OpenSeadragon.isCanvasTainted(viewer.drawer.context.canvas),
|
||||
viewer.addOnceHandler('tile-drawn', function(event) {
|
||||
assert.ok(!OpenSeadragon.isCanvasTainted(event.tile.getCanvasContext().canvas),
|
||||
"Canvas should not be tainted.");
|
||||
done();
|
||||
});
|
||||
@ -363,8 +366,8 @@
|
||||
},
|
||||
crossOriginPolicy : false
|
||||
} );
|
||||
viewer.addOnceHandler('tile-drawn', function() {
|
||||
assert.ok(OpenSeadragon.isCanvasTainted(viewer.drawer.context.canvas),
|
||||
viewer.addOnceHandler('tile-drawn', function(event) {
|
||||
assert.ok(OpenSeadragon.isCanvasTainted(event.tile.getCanvasContext().canvas),
|
||||
"Canvas should be tainted.");
|
||||
done();
|
||||
});
|
||||
@ -387,8 +390,8 @@
|
||||
crossOriginPolicy : "Anonymous"
|
||||
}
|
||||
} );
|
||||
viewer.addOnceHandler('tile-drawn', function() {
|
||||
assert.ok(!OpenSeadragon.isCanvasTainted(viewer.drawer.context.canvas),
|
||||
viewer.addOnceHandler('tile-drawn', function(event) {
|
||||
assert.ok(!OpenSeadragon.isCanvasTainted(event.tile.getCanvasContext().canvas),
|
||||
"Canvas should not be tainted.");
|
||||
done();
|
||||
});
|
||||
|
3
test/modules/controls.js
vendored
3
test/modules/controls.js
vendored
@ -19,6 +19,9 @@
|
||||
if (viewer && viewer.close) {
|
||||
viewer.close();
|
||||
}
|
||||
if (viewer && viewer.destroy){
|
||||
viewer.destroy();
|
||||
}
|
||||
|
||||
viewer = null;
|
||||
}
|
||||
|
@ -13,7 +13,9 @@
|
||||
if (viewer && viewer.close) {
|
||||
viewer.close();
|
||||
}
|
||||
|
||||
if (viewer && viewer.destroy){
|
||||
viewer.destroy();
|
||||
}
|
||||
viewer = null;
|
||||
}
|
||||
});
|
||||
@ -42,7 +44,8 @@
|
||||
QUnit.test('rotation', function(assert) {
|
||||
var done = assert.async();
|
||||
createViewer({
|
||||
tileSources: '/test/data/testpattern.dzi'
|
||||
tileSources: '/test/data/testpattern.dzi',
|
||||
drawer: 'context2d', // this test only makes sense for certain drawers
|
||||
});
|
||||
|
||||
viewer.addHandler('open', function handler(event) {
|
||||
@ -62,8 +65,8 @@
|
||||
debugMode: true
|
||||
});
|
||||
|
||||
Util.spyOnce(viewer.drawer, 'drawDebugInfo', function() {
|
||||
assert.ok(true, 'drawDebugInfo is called');
|
||||
Util.spyOnce(viewer.drawer, '_drawDebugInfo', function() {
|
||||
assert.ok(true, '_drawDebugInfo is called');
|
||||
done();
|
||||
});
|
||||
});
|
||||
@ -72,7 +75,8 @@
|
||||
QUnit.test('sketchCanvas', function(assert) {
|
||||
var done = assert.async();
|
||||
createViewer({
|
||||
tileSources: '/test/data/testpattern.dzi'
|
||||
tileSources: '/test/data/testpattern.dzi',
|
||||
drawer: 'context2d' // test only makes sense for this drawer
|
||||
});
|
||||
var drawer = viewer.drawer;
|
||||
|
||||
|
@ -20,6 +20,9 @@
|
||||
if ( viewer && viewer.close ) {
|
||||
viewer.close();
|
||||
}
|
||||
if (viewer && viewer.destroy){
|
||||
viewer.destroy();
|
||||
}
|
||||
|
||||
viewer = null;
|
||||
}
|
||||
@ -1155,6 +1158,7 @@
|
||||
// ----------
|
||||
QUnit.test( 'Viewer: event count test with \'tile-drawing\'', function (assert) {
|
||||
var done = assert.async();
|
||||
var previousValue = viewer.drawer.continuousTileRefresh;
|
||||
assert.ok(viewer.numberOfHandlers('tile-drawing') === 0,
|
||||
"'tile-drawing' event is empty by default.");
|
||||
|
||||
@ -1162,6 +1166,7 @@
|
||||
viewer.removeHandler( 'tile-drawing', tileDrawing );
|
||||
assert.ok(viewer.numberOfHandlers('tile-drawing') === 0,
|
||||
"'tile-drawing' deleted: count is 0.");
|
||||
viewer.drawer.continuousTileRefresh = previousValue; // reset property
|
||||
viewer.close();
|
||||
done();
|
||||
};
|
||||
@ -1180,11 +1185,14 @@
|
||||
assert.ok(viewer.numberOfHandlers('tile-drawing') === 1,
|
||||
"'tile-drawing' deleted once: count is 1.");
|
||||
|
||||
viewer.drawer.continuousTileRefresh = true; // set to true so the tile-drawing event fires
|
||||
viewer.open( '/test/data/testpattern.dzi' );
|
||||
} );
|
||||
|
||||
QUnit.test( 'Viewer: tile-drawing event', function (assert) {
|
||||
var done = assert.async();
|
||||
var previousValue = viewer.drawer.continuousTileRefresh;
|
||||
|
||||
var tileDrawing = function ( event ) {
|
||||
viewer.removeHandler( 'tile-drawing', tileDrawing );
|
||||
assert.ok( event, 'Event handler should be invoked' );
|
||||
@ -1194,10 +1202,12 @@
|
||||
assert.ok(event.tile, "Tile should be set");
|
||||
assert.ok(event.rendered, "Rendered should be set");
|
||||
}
|
||||
viewer.drawer.continuousTileRefresh = previousValue; // reset property
|
||||
viewer.close();
|
||||
done();
|
||||
};
|
||||
|
||||
viewer.drawer.continuousTileRefresh = true; // set to true so the tile-drawing event fires
|
||||
viewer.addHandler( 'tile-drawing', tileDrawing );
|
||||
viewer.open( '/test/data/testpattern.dzi' );
|
||||
} );
|
||||
|
@ -5,15 +5,26 @@
|
||||
// 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.close ) {
|
||||
viewer.close();
|
||||
}
|
||||
if (viewer && viewer.destroy){
|
||||
viewer.destroy();
|
||||
}
|
||||
|
||||
viewer = null;
|
||||
}
|
||||
});
|
||||
|
||||
var viewer = null;
|
||||
|
||||
// ----------
|
||||
var testOpenUrl = function(relativeUrl, assert) {
|
||||
|
@ -18,6 +18,9 @@
|
||||
if (viewer && viewer.close) {
|
||||
viewer.close();
|
||||
}
|
||||
if (viewer && viewer.destroy){
|
||||
viewer.destroy();
|
||||
}
|
||||
|
||||
viewer = null;
|
||||
}
|
||||
|
@ -19,6 +19,9 @@
|
||||
if ( viewer && viewer.close ) {
|
||||
viewer.close();
|
||||
}
|
||||
if (viewer && viewer.destroy){
|
||||
viewer.destroy();
|
||||
}
|
||||
|
||||
viewer = null;
|
||||
$("#example").remove();
|
||||
|
@ -41,6 +41,15 @@
|
||||
}
|
||||
|
||||
resetTestVariables();
|
||||
|
||||
if ( viewer && viewer.close ) {
|
||||
viewer.close();
|
||||
}
|
||||
if (viewer && viewer.destroy){
|
||||
viewer.destroy();
|
||||
}
|
||||
|
||||
viewer = null;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -16,6 +16,14 @@
|
||||
},
|
||||
afterEach: function() {
|
||||
resetTestVariables();
|
||||
if ( viewer && viewer.close ) {
|
||||
viewer.close();
|
||||
}
|
||||
if (viewer && viewer.destroy){
|
||||
viewer.destroy();
|
||||
}
|
||||
|
||||
viewer = null;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -13,6 +13,9 @@
|
||||
if (viewer && viewer.close) {
|
||||
viewer.close();
|
||||
}
|
||||
if (viewer && viewer.destroy){
|
||||
viewer.destroy();
|
||||
}
|
||||
|
||||
viewer = null;
|
||||
}
|
||||
|
@ -20,6 +20,9 @@
|
||||
if (viewer && viewer.close) {
|
||||
viewer.close();
|
||||
}
|
||||
if (viewer && viewer.destroy){
|
||||
viewer.destroy();
|
||||
}
|
||||
|
||||
viewer = null;
|
||||
}
|
||||
@ -132,6 +135,8 @@
|
||||
QUnit.test('update', function(assert) {
|
||||
var done = assert.async();
|
||||
var handlerCount = 0;
|
||||
var testTileDrawingEvent = viewer.drawerOptions.type === 'context2d';
|
||||
let expectedHandlers = testTileDrawingEvent ? 4 : 3;
|
||||
|
||||
viewer.addHandler('open', function(event) {
|
||||
var image = viewer.world.getItemAt(0);
|
||||
@ -160,15 +165,18 @@
|
||||
assert.ok(event.tile, 'update-tile event includes tile');
|
||||
});
|
||||
|
||||
viewer.addHandler('tile-drawing', function tileDrawingHandler(event) {
|
||||
viewer.removeHandler('tile-drawing', tileDrawingHandler);
|
||||
handlerCount++;
|
||||
assert.equal(event.eventSource, viewer, 'sender of tile-drawing event was viewer');
|
||||
assert.equal(event.tiledImage, image, 'tiledImage of update-level event is correct');
|
||||
assert.ok(event.tile, 'tile-drawing event includes a tile');
|
||||
assert.ok(event.context, 'tile-drawing event includes a context');
|
||||
assert.ok(event.rendered, 'tile-drawing event includes a rendered');
|
||||
});
|
||||
if(testTileDrawingEvent){
|
||||
viewer.addHandler('tile-drawing', function tileDrawingHandler(event) {
|
||||
viewer.removeHandler('tile-drawing', tileDrawingHandler);
|
||||
handlerCount++;
|
||||
assert.equal(event.eventSource, viewer, 'sender of tile-drawing event was viewer');
|
||||
assert.equal(event.tiledImage, image, 'tiledImage of update-level event is correct');
|
||||
assert.ok(event.tile, 'tile-drawing event includes a tile');
|
||||
assert.ok(event.context, 'tile-drawing event includes a context');
|
||||
assert.ok(event.rendered, 'tile-drawing event includes a rendered');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
viewer.addHandler('tile-drawn', function tileDrawnHandler(event) {
|
||||
viewer.removeHandler('tile-drawn', tileDrawnHandler);
|
||||
@ -177,11 +185,10 @@
|
||||
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');
|
||||
assert.equal(handlerCount, expectedHandlers, 'correct number of handlers called');
|
||||
done();
|
||||
});
|
||||
|
||||
//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 ] );
|
||||
});
|
||||
|
||||
|
@ -132,6 +132,10 @@
|
||||
if (viewer && viewer.close) {
|
||||
viewer.close();
|
||||
}
|
||||
if (viewer && viewer.destroy){
|
||||
viewer.destroy();
|
||||
}
|
||||
|
||||
viewer = null;
|
||||
|
||||
OpenSeadragon.makeAjaxRequest = OriginalAjax;
|
||||
|
@ -22,6 +22,9 @@
|
||||
if (viewer && viewer.close) {
|
||||
viewer.close();
|
||||
}
|
||||
if (viewer && viewer.destroy){
|
||||
viewer.destroy();
|
||||
}
|
||||
|
||||
viewer = null;
|
||||
}
|
||||
|
@ -24,6 +24,9 @@
|
||||
if (viewer && viewer.close) {
|
||||
viewer.close();
|
||||
}
|
||||
if (viewer && viewer.destroy){
|
||||
viewer.destroy();
|
||||
}
|
||||
|
||||
viewer = null;
|
||||
}
|
||||
|
@ -19,6 +19,9 @@
|
||||
if (viewer && viewer.close) {
|
||||
viewer.close();
|
||||
}
|
||||
if (viewer && viewer.destroy){
|
||||
viewer.destroy();
|
||||
}
|
||||
|
||||
viewer = null;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user