mirror of
https://github.com/openseadragon/openseadragon.git
synced 2025-02-20 16:53:14 +03:00
Merge webgl PR - problems with tained canvas and texture upload.
This commit is contained in:
commit
135fa76fde
@ -5,7 +5,7 @@ OPENSEADRAGON CHANGELOG
|
|||||||
|
|
||||||
* BREAKING CHANGE: Dropped support for IE11 (#2300, #2361 @AndrewADev)
|
* BREAKING CHANGE: Dropped support for IE11 (#2300, #2361 @AndrewADev)
|
||||||
* DEPRECATION: The OpenSeadragon.createCallback function is no longer recommended (#2367 @akansjain)
|
* DEPRECATION: The OpenSeadragon.createCallback function is no longer recommended (#2367 @akansjain)
|
||||||
* The viewer now uses WebGL when available (#2310, #2462, #2466 @pearcetm, @Aiosa)
|
* The viewer now uses WebGL when available (#2310, #2462, #2466, #2468, #2469, #2472, #2478 @pearcetm, @Aiosa, @thec0keman)
|
||||||
* Added webp to supported image formats (#2455 @BeebBenjamin)
|
* Added webp to supported image formats (#2455 @BeebBenjamin)
|
||||||
* Introduced maxTilesPerFrame option to allow loading more tiles simultaneously (#2387 @jetic83)
|
* Introduced maxTilesPerFrame option to allow loading more tiles simultaneously (#2387 @jetic83)
|
||||||
* Now when creating a viewer or navigator, we leave its position style alone if possible (#2393 @VIRAT9358)
|
* Now when creating a viewer or navigator, we leave its position style alone if possible (#2393 @VIRAT9358)
|
||||||
@ -15,6 +15,8 @@ OPENSEADRAGON CHANGELOG
|
|||||||
* Fixed: Strange behavior if IIIF sizes were not in ascending order (#2416 @lutzhelm)
|
* Fixed: Strange behavior if IIIF sizes were not in ascending order (#2416 @lutzhelm)
|
||||||
* Fixed: Two-finger tap on a Mac trackpad would zoom you out (#2431 @cavenel)
|
* Fixed: Two-finger tap on a Mac trackpad would zoom you out (#2431 @cavenel)
|
||||||
* Fixed: dragToPan gesture could not be disabled when flickEnabled was activated (#2464 @jonasengelmann)
|
* Fixed: dragToPan gesture could not be disabled when flickEnabled was activated (#2464 @jonasengelmann)
|
||||||
|
* Fixed: placeholderFillStyle didn't work properly when the image was rotated (#2469 @pearcetm)
|
||||||
|
* Fixed: Sometimes exponential springs wouldn't ever settle (#2469 @pearcetm)
|
||||||
|
|
||||||
4.1.0:
|
4.1.0:
|
||||||
|
|
||||||
|
@ -143,6 +143,7 @@ class CanvasDrawer extends OpenSeadragon.DrawerBase{
|
|||||||
this.canvas.height = 1;
|
this.canvas.height = 1;
|
||||||
this.sketchCanvas = null;
|
this.sketchCanvas = null;
|
||||||
this.sketchContext = null;
|
this.sketchContext = null;
|
||||||
|
this.container.removeChild(this.canvas);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -392,9 +393,9 @@ class CanvasDrawer extends OpenSeadragon.DrawerBase{
|
|||||||
}
|
}
|
||||||
usedClip = true;
|
usedClip = true;
|
||||||
}
|
}
|
||||||
|
tiledImage._hasOpaqueTile = false;
|
||||||
if ( tiledImage.placeholderFillStyle && tiledImage._hasOpaqueTile === false ) {
|
if ( tiledImage.placeholderFillStyle && tiledImage._hasOpaqueTile === false ) {
|
||||||
let placeholderRect = this.viewportToDrawerRectangle(tiledImage.getBounds(true));
|
let placeholderRect = this.viewportToDrawerRectangle(tiledImage.getBoundsNoRotate(true));
|
||||||
if (sketchScale) {
|
if (sketchScale) {
|
||||||
placeholderRect = placeholderRect.times(sketchScale);
|
placeholderRect = placeholderRect.times(sketchScale);
|
||||||
}
|
}
|
||||||
@ -402,7 +403,7 @@ class CanvasDrawer extends OpenSeadragon.DrawerBase{
|
|||||||
placeholderRect = placeholderRect.translate(sketchTranslate);
|
placeholderRect = placeholderRect.translate(sketchTranslate);
|
||||||
}
|
}
|
||||||
|
|
||||||
let fillStyle;
|
let fillStyle = null;
|
||||||
if ( typeof tiledImage.placeholderFillStyle === "function" ) {
|
if ( typeof tiledImage.placeholderFillStyle === "function" ) {
|
||||||
fillStyle = tiledImage.placeholderFillStyle(tiledImage, this.context);
|
fillStyle = tiledImage.placeholderFillStyle(tiledImage, this.context);
|
||||||
}
|
}
|
||||||
|
@ -327,6 +327,37 @@ OpenSeadragon.DrawerBase = class DrawerBase{
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by implementations to fire the drawer-error event
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_raiseDrawerErrorEvent(tiledImage, errorMessage){
|
||||||
|
if(!this.viewer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raised when a tiled image is drawn to the canvas. Used internally for testing.
|
||||||
|
* The update-viewport event is preferred if you want to know when a frame has been drawn.
|
||||||
|
*
|
||||||
|
* @event drawer-error
|
||||||
|
* @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.DrawerBase} drawer - The drawer that raised the error.
|
||||||
|
* @property {String} error - A message describing the error.
|
||||||
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.viewer.raiseEvent( 'drawer-error', {
|
||||||
|
tiledImage: tiledImage,
|
||||||
|
drawer: this,
|
||||||
|
error: errorMessage,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}( OpenSeadragon ));
|
}( OpenSeadragon ));
|
||||||
|
@ -130,7 +130,7 @@ class HTMLDrawer extends OpenSeadragon.DrawerBase{
|
|||||||
* Destroy the drawer (unload current loaded tiles)
|
* Destroy the drawer (unload current loaded tiles)
|
||||||
*/
|
*/
|
||||||
destroy() {
|
destroy() {
|
||||||
this.canvas.innerHTML = "";
|
this.container.removeChild(this.canvas);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -212,7 +212,7 @@ $.Spring.prototype = {
|
|||||||
update: function() {
|
update: function() {
|
||||||
this.current.time = $.now();
|
this.current.time = $.now();
|
||||||
|
|
||||||
var startValue, targetValue;
|
let startValue, targetValue;
|
||||||
if (this._exponential) {
|
if (this._exponential) {
|
||||||
startValue = this.start._logValue;
|
startValue = this.start._logValue;
|
||||||
targetValue = this.target._logValue;
|
targetValue = this.target._logValue;
|
||||||
@ -221,23 +221,25 @@ $.Spring.prototype = {
|
|||||||
targetValue = this.target.value;
|
targetValue = this.target.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentValue = (this.current.time >= this.target.time) ?
|
if(this.current.time >= this.target.time){
|
||||||
targetValue :
|
this.current.value = this.target.value;
|
||||||
startValue +
|
|
||||||
( targetValue - startValue ) *
|
|
||||||
transform(
|
|
||||||
this.springStiffness,
|
|
||||||
( this.current.time - this.start.time ) /
|
|
||||||
( this.target.time - this.start.time )
|
|
||||||
);
|
|
||||||
|
|
||||||
if (this._exponential) {
|
|
||||||
this.current.value = Math.exp(currentValue);
|
|
||||||
} else {
|
} else {
|
||||||
this.current.value = currentValue;
|
let currentValue = startValue +
|
||||||
|
( targetValue - startValue ) *
|
||||||
|
transform(
|
||||||
|
this.springStiffness,
|
||||||
|
( this.current.time - this.start.time ) /
|
||||||
|
( this.target.time - this.start.time )
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this._exponential) {
|
||||||
|
this.current.value = Math.exp(currentValue);
|
||||||
|
} else {
|
||||||
|
this.current.value = currentValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return currentValue !== targetValue;
|
return this.current.value !== this.target.value;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -161,14 +161,16 @@ $.TiledImage = function( options ) {
|
|||||||
loadingCoverage: {}, // A '3d' dictionary [level][x][y] --> Boolean; shows what areas are loaded or are being loaded/blended.
|
loadingCoverage: {}, // A '3d' dictionary [level][x][y] --> Boolean; shows what areas are loaded or are being loaded/blended.
|
||||||
lastDrawn: [], // An unordered list of Tiles drawn last frame.
|
lastDrawn: [], // An unordered list of Tiles drawn last frame.
|
||||||
lastResetTime: 0, // Last time for which the tiledImage was reset.
|
lastResetTime: 0, // Last time for which the tiledImage was reset.
|
||||||
_needsDraw: true, // Does the tiledImage need to update the viewport again?
|
_needsDraw: true, // Does the tiledImage need to be drawn again?
|
||||||
_hasOpaqueTile: false, // Do we have even one fully opaque tile?
|
_needsUpdate: 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.
|
_tilesLoading: 0, // The number of pending tile requests.
|
||||||
_zombieCache: false, // Allow cache to stay in memory upon deletion.
|
_zombieCache: false, // Allow cache to stay in memory upon deletion.
|
||||||
_tilesToDraw: [], // info about the tiles currently in the viewport, two deep: array[level][tile]
|
_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
|
_lastDrawn: [], // array of tiles that were last fetched by the drawer
|
||||||
_isBlending: false, // Are any tiles still being blended?
|
_isBlending: false, // Are any tiles still being blended?
|
||||||
_wasBlending: false, // Were any tiles blending before the last draw?
|
_wasBlending: false, // Were any tiles blending before the last draw?
|
||||||
|
_isTainted: false, // Has a Tile been found with tainted data?
|
||||||
//configurable settings
|
//configurable settings
|
||||||
springStiffness: $.DEFAULT_SETTINGS.springStiffness,
|
springStiffness: $.DEFAULT_SETTINGS.springStiffness,
|
||||||
animationTime: $.DEFAULT_SETTINGS.animationTime,
|
animationTime: $.DEFAULT_SETTINGS.animationTime,
|
||||||
@ -327,13 +329,14 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
let scaleUpdated = this._scaleSpring.update();
|
let scaleUpdated = this._scaleSpring.update();
|
||||||
let degreesUpdated = this._degreesSpring.update();
|
let degreesUpdated = this._degreesSpring.update();
|
||||||
|
|
||||||
let updated = (xUpdated || yUpdated || scaleUpdated || degreesUpdated);
|
let updated = (xUpdated || yUpdated || scaleUpdated || degreesUpdated || this._needsUpdate);
|
||||||
|
|
||||||
if (updated || viewportChanged || !this._fullyLoaded){
|
if (updated || viewportChanged || !this._fullyLoaded){
|
||||||
let fullyLoadedFlag = this._updateLevelsForViewport();
|
let fullyLoadedFlag = this._updateLevelsForViewport();
|
||||||
this._setFullyLoaded(fullyLoadedFlag);
|
this._setFullyLoaded(fullyLoadedFlag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._needsUpdate = false;
|
||||||
|
|
||||||
if (updated) {
|
if (updated) {
|
||||||
this._updateForScale();
|
this._updateForScale();
|
||||||
@ -357,6 +360,25 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
return this._needsDraw;
|
return this._needsDraw;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the internal _isTainted flag for this TiledImage. Lazy loaded - not
|
||||||
|
* checked each time a Tile is loaded, but can be set if a consumer of the
|
||||||
|
* tiles (e.g. a Drawer) discovers a Tile to have tainted data so that further
|
||||||
|
* checks are not needed and alternative rendering strategies can be used.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
setTainted(isTainted){
|
||||||
|
this._isTainted = isTainted;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @returns {Boolean} whether the TiledImage has been marked as tainted
|
||||||
|
*/
|
||||||
|
isTainted(){
|
||||||
|
return this._isTainted;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destroy the TiledImage (unload current loaded tiles).
|
* Destroy the TiledImage (unload current loaded tiles).
|
||||||
*/
|
*/
|
||||||
@ -705,6 +727,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
this._xSpring.resetTo(position.x);
|
this._xSpring.resetTo(position.x);
|
||||||
this._ySpring.resetTo(position.y);
|
this._ySpring.resetTo(position.y);
|
||||||
this._needsDraw = true;
|
this._needsDraw = true;
|
||||||
|
this._needsUpdate = true;
|
||||||
} else {
|
} else {
|
||||||
if (sameTarget) {
|
if (sameTarget) {
|
||||||
return;
|
return;
|
||||||
@ -713,6 +736,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
this._xSpring.springTo(position.x);
|
this._xSpring.springTo(position.x);
|
||||||
this._ySpring.springTo(position.y);
|
this._ySpring.springTo(position.y);
|
||||||
this._needsDraw = true;
|
this._needsDraw = true;
|
||||||
|
this._needsUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sameTarget) {
|
if (!sameTarget) {
|
||||||
@ -1042,6 +1066,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
this._degreesSpring.springTo(degrees);
|
this._degreesSpring.springTo(degrees);
|
||||||
}
|
}
|
||||||
this._needsDraw = true;
|
this._needsDraw = true;
|
||||||
|
this._needsUpdate = true;
|
||||||
this._raiseBoundsChange();
|
this._raiseBoundsChange();
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1252,6 +1277,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
this._scaleSpring.resetTo(scale);
|
this._scaleSpring.resetTo(scale);
|
||||||
this._updateForScale();
|
this._updateForScale();
|
||||||
this._needsDraw = true;
|
this._needsDraw = true;
|
||||||
|
this._needsUpdate = true;
|
||||||
} else {
|
} else {
|
||||||
if (sameTarget) {
|
if (sameTarget) {
|
||||||
return;
|
return;
|
||||||
@ -1260,6 +1286,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
this._scaleSpring.springTo(scale);
|
this._scaleSpring.springTo(scale);
|
||||||
this._updateForScale();
|
this._updateForScale();
|
||||||
this._needsDraw = true;
|
this._needsDraw = true;
|
||||||
|
this._needsUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sameTarget) {
|
if (!sameTarget) {
|
||||||
@ -1492,7 +1519,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
lowestLevel
|
lowestLevel
|
||||||
);
|
);
|
||||||
_this._isBlending = _this._isBlending || tileIsBlending;
|
_this._isBlending = _this._isBlending || tileIsBlending;
|
||||||
_this._needsDraw = _this._needsDraw || tileIsBlending || this._wasBlending;
|
_this._needsDraw = _this._needsDraw || tileIsBlending || _this._wasBlending;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -473,35 +473,13 @@ $.Viewer = function( options ) {
|
|||||||
|
|
||||||
|
|
||||||
this.drawer = null;
|
this.drawer = null;
|
||||||
for (let i = 0; i < drawerCandidates.length; i++) {
|
for (const drawerCandidate of drawerCandidates){
|
||||||
|
let success = this.requestDrawer(drawerCandidate, {mainDrawer: true, redrawImmediately: false});
|
||||||
let drawerCandidate = drawerCandidates[i];
|
if(success){
|
||||||
let Drawer = null;
|
|
||||||
|
|
||||||
//if inherits from a drawer base, use it
|
|
||||||
if (drawerCandidate && drawerCandidate.prototype instanceof $.DrawerBase) {
|
|
||||||
Drawer = drawerCandidate;
|
|
||||||
drawerCandidate = 'custom';
|
|
||||||
} else if (typeof drawerCandidate === "string") {
|
|
||||||
Drawer = $.determineDrawer(drawerCandidate);
|
|
||||||
} else {
|
|
||||||
$.console.warn('Unsupported drawer! Drawer must be an existing string type, or a class that extends OpenSeadragon.DrawerBase.');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the drawer is supported, create it and break the loop
|
|
||||||
if (Drawer && Drawer.isSupported()) {
|
|
||||||
this.drawer = new Drawer({
|
|
||||||
viewer: this,
|
|
||||||
viewport: this.viewport,
|
|
||||||
element: this.canvas,
|
|
||||||
debugGridColor: this.debugGridColor,
|
|
||||||
options: this.drawerOptions[drawerCandidate],
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.drawer){
|
if (!this.drawer){
|
||||||
$.console.error('No drawer could be created!');
|
$.console.error('No drawer could be created!');
|
||||||
throw('Error with creating the selected drawer(s)');
|
throw('Error with creating the selected drawer(s)');
|
||||||
@ -968,6 +946,73 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
|
|||||||
this.removeAllHandlers();
|
this.removeAllHandlers();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request a drawer for this viewer, as a supported string or drawer constructor.
|
||||||
|
* @param {String | OpenSeadragon.DrawerBase} drawerCandidate The type of drawer to try to construct.
|
||||||
|
* @param { Object } options
|
||||||
|
* @param { Boolean } [options.mainDrawer] Whether to use this as the viewer's main drawer. Default = true.
|
||||||
|
* @param { Boolean } [options.redrawImmediately] Whether to immediately draw a new frame. Only used if options.mainDrawer = true. Default = true.
|
||||||
|
* @param { Object } [options.drawerOptions] Options for this drawer. Defaults to viewer.drawerOptions.
|
||||||
|
* for this viewer type. See {@link OpenSeadragon.Options}.
|
||||||
|
* @returns {Object | Boolean} The drawer that was created, or false if the requested drawer is not supported
|
||||||
|
*/
|
||||||
|
requestDrawer(drawerCandidate, options){
|
||||||
|
const defaultOpts = {
|
||||||
|
mainDrawer: true,
|
||||||
|
redrawImmediately: true,
|
||||||
|
drawerOptions: null
|
||||||
|
};
|
||||||
|
options = $.extend(true, defaultOpts, options);
|
||||||
|
const mainDrawer = options.mainDrawer;
|
||||||
|
const redrawImmediately = options.redrawImmediately;
|
||||||
|
const drawerOptions = options.drawerOptions;
|
||||||
|
|
||||||
|
const oldDrawer = this.drawer;
|
||||||
|
|
||||||
|
let Drawer = null;
|
||||||
|
|
||||||
|
//if the candidate inherits from a drawer base, use it
|
||||||
|
if (drawerCandidate && drawerCandidate.prototype instanceof $.DrawerBase) {
|
||||||
|
Drawer = drawerCandidate;
|
||||||
|
drawerCandidate = 'custom';
|
||||||
|
} else if (typeof drawerCandidate === "string") {
|
||||||
|
Drawer = $.determineDrawer(drawerCandidate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!Drawer){
|
||||||
|
$.console.warn('Unsupported drawer! Drawer must be an existing string type, or a class that extends OpenSeadragon.DrawerBase.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the drawer is supported, create it and return true
|
||||||
|
if (Drawer && Drawer.isSupported()) {
|
||||||
|
|
||||||
|
// first destroy the previous drawer
|
||||||
|
if(oldDrawer && mainDrawer){
|
||||||
|
oldDrawer.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the new drawer
|
||||||
|
const newDrawer = new Drawer({
|
||||||
|
viewer: this,
|
||||||
|
viewport: this.viewport,
|
||||||
|
element: this.canvas,
|
||||||
|
debugGridColor: this.debugGridColor,
|
||||||
|
options: drawerOptions || this.drawerOptions[drawerCandidate],
|
||||||
|
});
|
||||||
|
|
||||||
|
if(mainDrawer){
|
||||||
|
this.drawer = newDrawer;
|
||||||
|
if(redrawImmediately){
|
||||||
|
this.forceRedraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newDrawer;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @function
|
* @function
|
||||||
* @returns {Boolean}
|
* @returns {Boolean}
|
||||||
|
@ -86,6 +86,7 @@
|
|||||||
this._clippingCanvas = null;
|
this._clippingCanvas = null;
|
||||||
this._clippingContext = null;
|
this._clippingContext = null;
|
||||||
this._renderingCanvas = null;
|
this._renderingCanvas = null;
|
||||||
|
this._backupCanvasDrawer = null;
|
||||||
|
|
||||||
// Reject listening for the tile-drawing and tile-drawn events, which this drawer does not fire
|
// Reject listening for the tile-drawing and tile-drawn events, which this drawer does not fire
|
||||||
this.viewer.rejectEventHandler("tile-drawn", "The WebGLDrawer does not raise the tile-drawn event");
|
this.viewer.rejectEventHandler("tile-drawn", "The WebGLDrawer does not raise the tile-drawn event");
|
||||||
@ -159,6 +160,16 @@
|
|||||||
// set our webgl context reference to null to enable garbage collection
|
// set our webgl context reference to null to enable garbage collection
|
||||||
this._gl = null;
|
this._gl = null;
|
||||||
|
|
||||||
|
if(this._backupCanvasDrawer){
|
||||||
|
this._backupCanvasDrawer.destroy();
|
||||||
|
this._backupCanvasDrawer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.container.removeChild(this.canvas);
|
||||||
|
if(this.viewer.drawer === this){
|
||||||
|
this.viewer.drawer = null;
|
||||||
|
}
|
||||||
|
|
||||||
// set our destroyed flag to true
|
// set our destroyed flag to true
|
||||||
this._destroyed = true;
|
this._destroyed = true;
|
||||||
}
|
}
|
||||||
@ -208,6 +219,21 @@
|
|||||||
return canvas;
|
return canvas;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the backup renderer (CanvasDrawer) to use if data cannot be used by webgl
|
||||||
|
* Lazy loaded
|
||||||
|
* @private
|
||||||
|
* @returns {CanvasDrawer}
|
||||||
|
*/
|
||||||
|
_getBackupCanvasDrawer(){
|
||||||
|
if(!this._backupCanvasDrawer){
|
||||||
|
this._backupCanvasDrawer = this.viewer.requestDrawer('canvas', {mainDrawer: false});
|
||||||
|
this._backupCanvasDrawer.canvas.style.setProperty('visibility', 'hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._backupCanvasDrawer;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {Array} tiledImages Array of TiledImage objects to draw
|
* @param {Array} tiledImages Array of TiledImage objects to draw
|
||||||
@ -239,173 +265,187 @@
|
|||||||
//iterate over tiled images and draw each one using a two-pass rendering pipeline if needed
|
//iterate over tiled images and draw each one using a two-pass rendering pipeline if needed
|
||||||
tiledImages.forEach( (tiledImage, tiledImageIndex) => {
|
tiledImages.forEach( (tiledImage, tiledImageIndex) => {
|
||||||
|
|
||||||
let tilesToDraw = tiledImage.getTilesToDraw();
|
if(tiledImage.isTainted()){
|
||||||
|
// first, draw any data left in the rendering buffer onto the output canvas
|
||||||
if(tilesToDraw.length === 0 || tiledImage.getOpacity() === 0){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let firstTile = tilesToDraw[0];
|
|
||||||
|
|
||||||
let useContext2dPipeline = ( tiledImage.compositeOperation ||
|
|
||||||
this.viewer.compositeOperation ||
|
|
||||||
tiledImage._clip ||
|
|
||||||
tiledImage._croppingPolygons ||
|
|
||||||
tiledImage.debugMode
|
|
||||||
);
|
|
||||||
|
|
||||||
let useTwoPassRendering = useContext2dPipeline || (tiledImage.opacity < 1) || firstTile.hasTransparency;
|
|
||||||
|
|
||||||
// using the context2d pipeline requires a clean rendering (back) buffer to start
|
|
||||||
if(useContext2dPipeline){
|
|
||||||
// if the rendering buffer has image data currently, write it to the output canvas now and clear it
|
|
||||||
|
|
||||||
if(renderingBufferHasImageData){
|
if(renderingBufferHasImageData){
|
||||||
this._outputContext.drawImage(this._renderingCanvas, 0, 0);
|
this._outputContext.drawImage(this._renderingCanvas, 0, 0);
|
||||||
|
// clear the buffer
|
||||||
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
||||||
|
gl.clear(gl.COLOR_BUFFER_BIT); // clear the back buffer
|
||||||
|
renderingBufferHasImageData = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// clear the buffer
|
// next, use the backup canvas drawer to draw this tainted image
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
const canvasDrawer = this._getBackupCanvasDrawer();
|
||||||
gl.clear(gl.COLOR_BUFFER_BIT); // clear the back buffer
|
canvasDrawer.draw([tiledImage]);
|
||||||
}
|
this._outputContext.drawImage(canvasDrawer.canvas, 0, 0);
|
||||||
|
|
||||||
// First rendering pass: compose tiles that make up this tiledImage
|
|
||||||
gl.useProgram(this._firstPass.shaderProgram);
|
|
||||||
|
|
||||||
// bind to the framebuffer for render-to-texture if using two-pass rendering, otherwise back buffer (null)
|
|
||||||
if(useTwoPassRendering){
|
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, this._glFrameBuffer);
|
|
||||||
// clear the buffer to draw a new image
|
|
||||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
||||||
} else {
|
} else {
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
let tilesToDraw = tiledImage.getTilesToDraw();
|
||||||
// no need to clear, just draw on top of the existing pixels
|
|
||||||
}
|
|
||||||
|
|
||||||
let overallMatrix = viewMatrix;
|
if ( tiledImage.placeholderFillStyle && tiledImage._hasOpaqueTile === false ) {
|
||||||
|
this._drawPlaceholder(tiledImage);
|
||||||
|
}
|
||||||
|
|
||||||
let imageRotation = tiledImage.getRotation(true);
|
if(tilesToDraw.length === 0 || tiledImage.getOpacity() === 0){
|
||||||
// if needed, handle the tiledImage being rotated
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
let maxTextures = this._gl.getParameter(this._gl.MAX_TEXTURE_IMAGE_UNITS);
|
|
||||||
if(maxTextures <= 0){
|
|
||||||
// This can apparently happen on some systems if too many WebGL contexts have been created
|
|
||||||
// in which case maxTextures can be null, leading to out of bounds errors with the array.
|
|
||||||
// For example, when viewers were created and not destroyed in the test suite, this error
|
|
||||||
// occured in the TravisCI tests, though it did not happen when testing locally either in
|
|
||||||
// a browser or on the command line via grunt test.
|
|
||||||
|
|
||||||
throw(new Error(`WegGL error: bad value for gl parameter MAX_TEXTURE_IMAGE_UNITS (${maxTextures}). This could happen
|
|
||||||
if too many contexts have been created and not released, or there is another problem with the graphics card.`));
|
|
||||||
}
|
|
||||||
|
|
||||||
let texturePositionArray = new Float32Array(maxTextures * 12); // 6 vertices (2 triangles) x 2 coordinates per vertex
|
|
||||||
let textureDataArray = new Array(maxTextures);
|
|
||||||
let matrixArray = new Array(maxTextures);
|
|
||||||
let opacityArray = new Array(maxTextures);
|
|
||||||
|
|
||||||
// iterate over tiles and add data for each one to the buffers
|
|
||||||
for(let tileIndex = 0; tileIndex < tilesToDraw.length; tileIndex++){
|
|
||||||
let tile = tilesToDraw[tileIndex].tile;
|
|
||||||
let indexInDrawArray = tileIndex % maxTextures;
|
|
||||||
let numTilesToDraw = indexInDrawArray + 1;
|
|
||||||
|
|
||||||
if ( !tile.loaded ) {
|
|
||||||
$.console.warn(
|
|
||||||
"Attempting to draw tile %s when it's not yet loaded.",
|
|
||||||
tile.toString()
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const textureInfo = this.getDataToDraw(tile, true);
|
let firstTile = tilesToDraw[0];
|
||||||
if (!textureInfo) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._getTileData(tile, tiledImage, textureInfo, overallMatrix, indexInDrawArray, texturePositionArray, textureDataArray, matrixArray, opacityArray);
|
|
||||||
|
|
||||||
if ((numTilesToDraw === maxTextures) || (tileIndex === tilesToDraw.length - 1)){
|
let useContext2dPipeline = ( tiledImage.compositeOperation ||
|
||||||
// We've filled up the buffers: time to draw this set of tiles
|
this.viewer.compositeOperation ||
|
||||||
|
tiledImage._clip ||
|
||||||
|
tiledImage._croppingPolygons ||
|
||||||
|
tiledImage.debugMode
|
||||||
|
);
|
||||||
|
|
||||||
// bind each tile's texture to the appropriate gl.TEXTURE#
|
let useTwoPassRendering = useContext2dPipeline || (tiledImage.opacity < 1) || firstTile.hasTransparency;
|
||||||
for(let i = 0; i <= numTilesToDraw; i++){
|
|
||||||
gl.activeTexture(gl.TEXTURE0 + i);
|
// using the context2d pipeline requires a clean rendering (back) buffer to start
|
||||||
gl.bindTexture(gl.TEXTURE_2D, textureDataArray[i]);
|
if(useContext2dPipeline){
|
||||||
|
// if the rendering buffer has image data currently, write it to the output canvas now and clear it
|
||||||
|
|
||||||
|
if(renderingBufferHasImageData){
|
||||||
|
this._outputContext.drawImage(this._renderingCanvas, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the buffer data for the texture coordinates to use for each tile
|
// clear the buffer
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, this._firstPass.bufferTexturePosition);
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
||||||
gl.bufferData(gl.ARRAY_BUFFER, texturePositionArray, gl.DYNAMIC_DRAW);
|
gl.clear(gl.COLOR_BUFFER_BIT); // clear the back buffer
|
||||||
|
}
|
||||||
|
|
||||||
// set the transform matrix uniform for each tile
|
// First rendering pass: compose tiles that make up this tiledImage
|
||||||
matrixArray.forEach( (matrix, index) => {
|
gl.useProgram(this._firstPass.shaderProgram);
|
||||||
gl.uniformMatrix3fv(this._firstPass.uTransformMatrices[index], false, matrix);
|
|
||||||
});
|
|
||||||
// set the opacity uniform for each tile
|
|
||||||
gl.uniform1fv(this._firstPass.uOpacities, new Float32Array(opacityArray));
|
|
||||||
|
|
||||||
// bind vertex buffers and (re)set attributes before calling gl.drawArrays()
|
// bind to the framebuffer for render-to-texture if using two-pass rendering, otherwise back buffer (null)
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, this._firstPass.bufferOutputPosition);
|
if(useTwoPassRendering){
|
||||||
|
gl.bindFramebuffer(gl.FRAMEBUFFER, this._glFrameBuffer);
|
||||||
|
// clear the buffer to draw a new image
|
||||||
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||||
|
} else {
|
||||||
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
||||||
|
// no need to clear, just draw on top of the existing pixels
|
||||||
|
}
|
||||||
|
|
||||||
|
let overallMatrix = viewMatrix;
|
||||||
|
|
||||||
|
let imageRotation = tiledImage.getRotation(true);
|
||||||
|
// if needed, handle the tiledImage being rotated
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
let maxTextures = this._gl.getParameter(this._gl.MAX_TEXTURE_IMAGE_UNITS);
|
||||||
|
if(maxTextures <= 0){
|
||||||
|
// This can apparently happen on some systems if too many WebGL contexts have been created
|
||||||
|
// in which case maxTextures can be null, leading to out of bounds errors with the array.
|
||||||
|
// For example, when viewers were created and not destroyed in the test suite, this error
|
||||||
|
// occured in the TravisCI tests, though it did not happen when testing locally either in
|
||||||
|
// a browser or on the command line via grunt test.
|
||||||
|
|
||||||
|
throw(new Error(`WegGL error: bad value for gl parameter MAX_TEXTURE_IMAGE_UNITS (${maxTextures}). This could happen
|
||||||
|
if too many contexts have been created and not released, or there is another problem with the graphics card.`));
|
||||||
|
}
|
||||||
|
|
||||||
|
let texturePositionArray = new Float32Array(maxTextures * 12); // 6 vertices (2 triangles) x 2 coordinates per vertex
|
||||||
|
let textureDataArray = new Array(maxTextures);
|
||||||
|
let matrixArray = new Array(maxTextures);
|
||||||
|
let opacityArray = new Array(maxTextures);
|
||||||
|
|
||||||
|
// iterate over tiles and add data for each one to the buffers
|
||||||
|
for(let tileIndex = 0; tileIndex < tilesToDraw.length; tileIndex++){
|
||||||
|
let tile = tilesToDraw[tileIndex].tile;
|
||||||
|
let indexInDrawArray = tileIndex % maxTextures;
|
||||||
|
let numTilesToDraw = indexInDrawArray + 1;
|
||||||
|
|
||||||
|
const textureInfo = this.getDataToDraw(tile);
|
||||||
|
if (!textureInfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._getTileData(tile, tiledImage, textureInfo, overallMatrix, indexInDrawArray, texturePositionArray, textureDataArray, matrixArray, opacityArray);
|
||||||
|
|
||||||
|
if( (numTilesToDraw === maxTextures) || (tileIndex === tilesToDraw.length - 1)){
|
||||||
|
// We've filled up the buffers: time to draw this set of tiles
|
||||||
|
|
||||||
|
// bind each tile's texture to the appropriate gl.TEXTURE#
|
||||||
|
for(let i = 0; i <= numTilesToDraw; i++){
|
||||||
|
gl.activeTexture(gl.TEXTURE0 + i);
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, textureDataArray[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the buffer data for the texture coordinates to use for each tile
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this._firstPass.bufferTexturePosition);
|
||||||
|
gl.bufferData(gl.ARRAY_BUFFER, texturePositionArray, gl.DYNAMIC_DRAW);
|
||||||
|
|
||||||
|
// set the transform matrix uniform for each tile
|
||||||
|
matrixArray.forEach( (matrix, index) => {
|
||||||
|
gl.uniformMatrix3fv(this._firstPass.uTransformMatrices[index], false, matrix);
|
||||||
|
});
|
||||||
|
// set the opacity uniform for each tile
|
||||||
|
gl.uniform1fv(this._firstPass.uOpacities, new Float32Array(opacityArray));
|
||||||
|
|
||||||
|
// bind vertex buffers and (re)set attributes before calling gl.drawArrays()
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this._firstPass.bufferOutputPosition);
|
||||||
|
gl.vertexAttribPointer(this._firstPass.aOutputPosition, 2, gl.FLOAT, false, 0, 0);
|
||||||
|
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this._firstPass.bufferTexturePosition);
|
||||||
|
gl.vertexAttribPointer(this._firstPass.aTexturePosition, 2, gl.FLOAT, false, 0, 0);
|
||||||
|
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this._firstPass.bufferIndex);
|
||||||
|
gl.vertexAttribPointer(this._firstPass.aIndex, 1, gl.FLOAT, false, 0, 0);
|
||||||
|
|
||||||
|
// Draw! 6 vertices per tile (2 triangles per rectangle)
|
||||||
|
gl.drawArrays(gl.TRIANGLES, 0, 6 * numTilesToDraw );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(useTwoPassRendering){
|
||||||
|
// Second rendering pass: Render the tiled image from the framebuffer into the back buffer
|
||||||
|
gl.useProgram(this._secondPass.shaderProgram);
|
||||||
|
|
||||||
|
// set the rendering target to the back buffer (null)
|
||||||
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
||||||
|
|
||||||
|
// bind the rendered texture from the first pass to use during this second pass
|
||||||
|
gl.activeTexture(gl.TEXTURE0);
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, this._renderToTexture);
|
||||||
|
|
||||||
|
// set opacity to the value for the current tiledImage
|
||||||
|
this._gl.uniform1f(this._secondPass.uOpacityMultiplier, tiledImage.opacity);
|
||||||
|
|
||||||
|
// bind buffers and set attributes before calling gl.drawArrays
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this._secondPass.bufferTexturePosition);
|
||||||
|
gl.vertexAttribPointer(this._secondPass.aTexturePosition, 2, gl.FLOAT, false, 0, 0);
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this._secondPass.bufferOutputPosition);
|
||||||
gl.vertexAttribPointer(this._firstPass.aOutputPosition, 2, gl.FLOAT, false, 0, 0);
|
gl.vertexAttribPointer(this._firstPass.aOutputPosition, 2, gl.FLOAT, false, 0, 0);
|
||||||
|
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, this._firstPass.bufferTexturePosition);
|
// Draw the quad (two triangles)
|
||||||
gl.vertexAttribPointer(this._firstPass.aTexturePosition, 2, gl.FLOAT, false, 0, 0);
|
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||||
|
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, this._firstPass.bufferIndex);
|
|
||||||
gl.vertexAttribPointer(this._firstPass.aIndex, 1, gl.FLOAT, false, 0, 0);
|
|
||||||
|
|
||||||
// Draw! 6 vertices per tile (2 triangles per rectangle)
|
|
||||||
gl.drawArrays(gl.TRIANGLES, 0, 6 * numTilesToDraw );
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if(useTwoPassRendering){
|
renderingBufferHasImageData = true;
|
||||||
// Second rendering pass: Render the tiled image from the framebuffer into the back buffer
|
|
||||||
gl.useProgram(this._secondPass.shaderProgram);
|
|
||||||
|
|
||||||
// set the rendering target to the back buffer (null)
|
if(useContext2dPipeline){
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
// draw from the rendering canvas onto the output canvas, clipping/cropping if needed.
|
||||||
|
this._applyContext2dPipeline(tiledImage, tilesToDraw, tiledImageIndex);
|
||||||
|
renderingBufferHasImageData = false;
|
||||||
|
// clear the buffer
|
||||||
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
||||||
|
gl.clear(gl.COLOR_BUFFER_BIT); // clear the back buffer
|
||||||
|
}
|
||||||
|
|
||||||
// bind the rendered texture from the first pass to use during this second pass
|
// after drawing the first TiledImage, fire the tiled-image-drawn event (for testing)
|
||||||
gl.activeTexture(gl.TEXTURE0);
|
if(tiledImageIndex === 0){
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this._renderToTexture);
|
this._raiseTiledImageDrawnEvent(tiledImage, tilesToDraw.map(info=>info.tile));
|
||||||
|
}
|
||||||
// set opacity to the value for the current tiledImage
|
|
||||||
this._gl.uniform1f(this._secondPass.uOpacityMultiplier, tiledImage.opacity);
|
|
||||||
|
|
||||||
// bind buffers and set attributes before calling gl.drawArrays
|
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, this._secondPass.bufferTexturePosition);
|
|
||||||
gl.vertexAttribPointer(this._secondPass.aTexturePosition, 2, gl.FLOAT, false, 0, 0);
|
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, this._secondPass.bufferOutputPosition);
|
|
||||||
gl.vertexAttribPointer(this._firstPass.aOutputPosition, 2, gl.FLOAT, false, 0, 0);
|
|
||||||
|
|
||||||
// Draw the quad (two triangles)
|
|
||||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
renderingBufferHasImageData = true;
|
|
||||||
|
|
||||||
if(useContext2dPipeline){
|
|
||||||
// draw from the rendering canvas onto the output canvas, clipping/cropping if needed.
|
|
||||||
this._applyContext2dPipeline(tiledImage, tilesToDraw, tiledImageIndex);
|
|
||||||
renderingBufferHasImageData = false;
|
|
||||||
// clear the buffer
|
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
||||||
gl.clear(gl.COLOR_BUFFER_BIT); // clear the back buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
// after drawing the first TiledImage, fire the tiled-image-drawn event (for testing)
|
|
||||||
if(tiledImageIndex === 0){
|
|
||||||
this._raiseTiledImageDrawnEvent(tiledImage, tilesToDraw.map(info=>info.tile));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -891,10 +931,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// private
|
// private
|
||||||
_setClip(rect){
|
_setClip(){
|
||||||
this._clippingContext.beginPath();
|
// no-op: called by _renderToClippingCanvas when tiledImage._clip is truthy
|
||||||
this._clippingContext.rect(rect.x, rect.y, rect.width, rect.height);
|
// so that tests will pass.
|
||||||
this._clippingContext.clip();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// private
|
// private
|
||||||
@ -902,11 +941,32 @@
|
|||||||
|
|
||||||
this._clippingContext.clearRect(0, 0, this._clippingCanvas.width, this._clippingCanvas.height);
|
this._clippingContext.clearRect(0, 0, this._clippingCanvas.width, this._clippingCanvas.height);
|
||||||
this._clippingContext.save();
|
this._clippingContext.save();
|
||||||
|
if(this.viewer.viewport.getFlip()){
|
||||||
|
const point = new $.Point(this.canvas.width / 2, this.canvas.height / 2);
|
||||||
|
this._clippingContext.translate(point.x, 0);
|
||||||
|
this._clippingContext.scale(-1, 1);
|
||||||
|
this._clippingContext.translate(-point.x, 0);
|
||||||
|
}
|
||||||
|
|
||||||
if(item._clip){
|
if(item._clip){
|
||||||
var box = item.imageToViewportRectangle(item._clip, true);
|
const polygon = [
|
||||||
var rect = this.viewportToDrawerRectangle(box);
|
{x: item._clip.x, y: item._clip.y},
|
||||||
this._setClip(rect);
|
{x: item._clip.x + item._clip.width, y: item._clip.y},
|
||||||
|
{x: item._clip.x + item._clip.width, y: item._clip.y + item._clip.height},
|
||||||
|
{x: item._clip.x, y: item._clip.y + item._clip.height},
|
||||||
|
];
|
||||||
|
let clipPoints = polygon.map(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();
|
||||||
|
clipPoints.forEach( (coord, i) => {
|
||||||
|
this._clippingContext[i === 0 ? 'moveTo' : 'lineTo'](coord.x, coord.y);
|
||||||
|
});
|
||||||
|
this._clippingContext.clip();
|
||||||
|
this._setClip();
|
||||||
}
|
}
|
||||||
if(item._croppingPolygons){
|
if(item._croppingPolygons){
|
||||||
let polygons = item._croppingPolygons.map(polygon => {
|
let polygons = item._croppingPolygons.map(polygon => {
|
||||||
@ -918,7 +978,7 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
this._clippingContext.beginPath();
|
this._clippingContext.beginPath();
|
||||||
polygons.forEach(polygon => {
|
polygons.forEach((polygon) => {
|
||||||
polygon.forEach( (coord, i) => {
|
polygon.forEach( (coord, i) => {
|
||||||
this._clippingContext[i === 0 ? 'moveTo' : 'lineTo'](coord.x, coord.y);
|
this._clippingContext[i === 0 ? 'moveTo' : 'lineTo'](coord.x, coord.y);
|
||||||
});
|
});
|
||||||
@ -926,6 +986,13 @@
|
|||||||
this._clippingContext.clip();
|
this._clippingContext.clip();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(this.viewer.viewport.getFlip()){
|
||||||
|
const point = new $.Point(this.canvas.width / 2, this.canvas.height / 2);
|
||||||
|
this._clippingContext.translate(point.x, 0);
|
||||||
|
this._clippingContext.scale(-1, 1);
|
||||||
|
this._clippingContext.translate(-point.x, 0);
|
||||||
|
}
|
||||||
|
|
||||||
this._clippingContext.drawImage(this._renderingCanvas, 0, 0);
|
this._clippingContext.drawImage(this._renderingCanvas, 0, 0);
|
||||||
|
|
||||||
this._clippingContext.restore();
|
this._clippingContext.restore();
|
||||||
@ -1065,6 +1132,30 @@
|
|||||||
context.restore();
|
context.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_drawPlaceholder(tiledImage){
|
||||||
|
|
||||||
|
const bounds = tiledImage.getBounds(true);
|
||||||
|
const rect = this.viewportToDrawerRectangle(tiledImage.getBounds(true));
|
||||||
|
const context = this._outputContext;
|
||||||
|
|
||||||
|
let fillStyle;
|
||||||
|
if ( typeof tiledImage.placeholderFillStyle === "function" ) {
|
||||||
|
fillStyle = tiledImage.placeholderFillStyle(tiledImage, context);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fillStyle = tiledImage.placeholderFillStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._offsetForRotation({degrees: this.viewer.viewport.getRotation(true)});
|
||||||
|
context.fillStyle = fillStyle;
|
||||||
|
context.translate(rect.x, rect.y);
|
||||||
|
context.rotate(Math.PI / 180 * bounds.degrees);
|
||||||
|
context.translate(-rect.x, -rect.y);
|
||||||
|
context.fillRect(rect.x, rect.y, rect.width, rect.height);
|
||||||
|
this._restoreRotationChanges();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// private
|
// private
|
||||||
_restoreRotationChanges() {
|
_restoreRotationChanges() {
|
||||||
var context = this._outputContext;
|
var context = this._outputContext;
|
||||||
|
@ -32,17 +32,18 @@
|
|||||||
type: 'legacy-image-pyramid',
|
type: 'legacy-image-pyramid',
|
||||||
levels: [
|
levels: [
|
||||||
{
|
{
|
||||||
url: 'http://openseadragon.github.io/example-images/duomo/duomo_files/8/0_0.jpg',
|
url: 'https://openseadragon.github.io/example-images/duomo/duomo_files/8/0_0.jpg',
|
||||||
width: 218,
|
width: 218,
|
||||||
height: 160
|
height: 160
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
degrees: 30,
|
||||||
};
|
};
|
||||||
|
|
||||||
var duomo = {
|
var duomo = {
|
||||||
Image: {
|
Image: {
|
||||||
xmlns: 'http://schemas.microsoft.com/deepzoom/2008',
|
xmlns: 'http://schemas.microsoft.com/deepzoom/2008',
|
||||||
Url: 'http://openseadragon.github.io/example-images/duomo/duomo_files/',
|
Url: 'https://openseadragon.github.io/example-images/duomo/duomo_files/',
|
||||||
Format: 'jpg',
|
Format: 'jpg',
|
||||||
Overlap: '2',
|
Overlap: '2',
|
||||||
TileSize: '256',
|
TileSize: '256',
|
||||||
@ -59,11 +60,14 @@
|
|||||||
tileSources: duomoStandin,
|
tileSources: duomoStandin,
|
||||||
minZoomImageRatio: 0.1,
|
minZoomImageRatio: 0.1,
|
||||||
defaultZoomLevel: 0.1,
|
defaultZoomLevel: 0.1,
|
||||||
zoomPerClick: 1
|
zoomPerClick: 1,
|
||||||
|
crossOriginPolicy: 'Anonymous'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let swapped = false;
|
||||||
viewer.addHandler('canvas-click', function(event) {
|
viewer.addHandler('canvas-click', function(event) {
|
||||||
if (event.quick) {
|
if (event.quick && !swapped) {
|
||||||
|
swapped = true;
|
||||||
var standin = viewer.world.getItemAt(0);
|
var standin = viewer.world.getItemAt(0);
|
||||||
var standinBounds = standin.getBounds();
|
var standinBounds = standin.getBounds();
|
||||||
viewer.viewport.fitBounds(standinBounds);
|
viewer.viewport.fitBounds(standinBounds);
|
||||||
@ -76,13 +80,10 @@
|
|||||||
index: 0, // Add the new image below the stand-in.
|
index: 0, // Add the new image below the stand-in.
|
||||||
success: function(event) {
|
success: function(event) {
|
||||||
var fullImage = event.item;
|
var fullImage = event.item;
|
||||||
|
// The changeover will look better if we wait for the first draw after the changeover.
|
||||||
// The changeover will look better if we wait for the first tile to be drawn.
|
|
||||||
var tileDrawnHandler = function(event) {
|
var tileDrawnHandler = function(event) {
|
||||||
if (event.tiledImage === fullImage) {
|
|
||||||
viewer.removeHandler('update-viewport', tileDrawnHandler);
|
viewer.removeHandler('update-viewport', tileDrawnHandler);
|
||||||
viewer.world.removeItem(standin);
|
viewer.world.removeItem(standin);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
viewer.addHandler('update-viewport', tileDrawnHandler);
|
viewer.addHandler('update-viewport', tileDrawnHandler);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user