mirror of
https://github.com/openseadragon/openseadragon.git
synced 2024-11-21 20:56:09 +03:00
finished many implementation details and demo
This commit is contained in:
parent
f9ab63944b
commit
5328761877
@ -57,6 +57,7 @@ module.exports = function(grunt) {
|
|||||||
"src/imageloader.js",
|
"src/imageloader.js",
|
||||||
"src/tile.js",
|
"src/tile.js",
|
||||||
"src/overlay.js",
|
"src/overlay.js",
|
||||||
|
"src/drawerbase.js",
|
||||||
"src/drawer.js",
|
"src/drawer.js",
|
||||||
"src/viewport.js",
|
"src/viewport.js",
|
||||||
"src/tiledimage.js",
|
"src/tiledimage.js",
|
||||||
|
709
src/drawer.js
709
src/drawer.js
@ -44,7 +44,9 @@
|
|||||||
* @param {Element} options.element - Parent element.
|
* @param {Element} options.element - Parent element.
|
||||||
* @param {Number} [options.debugGridColor] - See debugGridColor in {@link OpenSeadragon.Options} for details.
|
* @param {Number} [options.debugGridColor] - See debugGridColor in {@link OpenSeadragon.Options} for details.
|
||||||
*/
|
*/
|
||||||
$.Drawer = function( options ) {
|
$.Drawer = function(options) {
|
||||||
|
|
||||||
|
$.DrawerBase.call(this, options);
|
||||||
|
|
||||||
$.console.assert( options.viewer, "[Drawer] options.viewer is required" );
|
$.console.assert( options.viewer, "[Drawer] options.viewer is required" );
|
||||||
|
|
||||||
@ -138,51 +140,27 @@ $.Drawer = function( options ) {
|
|||||||
// Image smoothing for canvas rendering (only if canvas is used).
|
// Image smoothing for canvas rendering (only if canvas is used).
|
||||||
// Canvas default is "true", so this will only be changed if user specified "false".
|
// Canvas default is "true", so this will only be changed if user specified "false".
|
||||||
this._imageSmoothingEnabled = true;
|
this._imageSmoothingEnabled = true;
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @lends OpenSeadragon.Drawer.prototype */
|
$.extend( $.Drawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadragon.Drawer.prototype */ {
|
||||||
$.Drawer.prototype = {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function converts the given point from to the drawer coordinate by
|
* Draws the TiledImage to its Drawer.
|
||||||
* multiplying it with the pixel density.
|
|
||||||
* This function does not take rotation into account, thus assuming provided
|
|
||||||
* point is at 0 degree.
|
|
||||||
* @param {OpenSeadragon.Point} point - the pixel point to convert
|
|
||||||
* @returns {OpenSeadragon.Point} Point in drawer coordinate system.
|
|
||||||
*/
|
*/
|
||||||
viewportCoordToDrawerCoord: function(point) {
|
draw: function(tiledImage) {
|
||||||
var vpPoint = this.viewport.pixelFromPointNoRotate(point, true);
|
if (tiledImage.opacity !== 0 || tiledImage._preload) {
|
||||||
return new $.Point(
|
tiledImage._midDraw = true;
|
||||||
vpPoint.x * $.pixelDensityRatio,
|
this._updateViewport(tiledImage);
|
||||||
vpPoint.y * $.pixelDensityRatio
|
tiledImage._midDraw = false;
|
||||||
);
|
}
|
||||||
},
|
// Images with opacity 0 should not need to be drawn in future. this._needsDraw = false is set in this._updateViewport() for other images.
|
||||||
|
else {
|
||||||
/**
|
tiledImage._needsDraw = false;
|
||||||
* This function will create multiple polygon paths on the drawing context by provided polygons,
|
|
||||||
* then clip the context to the paths.
|
|
||||||
* @param {OpenSeadragon.Point[][]} polygons - an array of polygons. A polygon is an array of OpenSeadragon.Point
|
|
||||||
* @param {Boolean} useSketch - Whether to use the sketch canvas or not.
|
|
||||||
*/
|
|
||||||
clipWithPolygons: function (polygons, useSketch) {
|
|
||||||
if (!this.useCanvas) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
var context = this._getContext(useSketch);
|
|
||||||
context.beginPath();
|
|
||||||
polygons.forEach(function (polygon) {
|
|
||||||
polygon.forEach(function (coord, i) {
|
|
||||||
context[i === 0 ? 'moveTo' : 'lineTo'](coord.x, coord.y);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
context.clip();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Boolean} True if rotation is supported.
|
* @returns {Boolean} True if rotation is supported.
|
||||||
*/
|
*/
|
||||||
@ -237,6 +215,419 @@ $.Drawer.prototype = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/* Methods from TiledImage */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @inner
|
||||||
|
* Pretty much every other line in this needs to be documented so it's clear
|
||||||
|
* how each piece of this routine contributes to the drawing process. That's
|
||||||
|
* why there are so many TODO's inside this function.
|
||||||
|
*/
|
||||||
|
_updateViewport: function(tiledImage) {
|
||||||
|
var _this = this;
|
||||||
|
tiledImage._needsDraw = false;
|
||||||
|
tiledImage._tilesLoading = 0;
|
||||||
|
tiledImage.loadingCoverage = {};
|
||||||
|
|
||||||
|
// Reset tile's internal drawn state
|
||||||
|
while (tiledImage.lastDrawn.length > 0) {
|
||||||
|
var tile = tiledImage.lastDrawn.pop();
|
||||||
|
tile.beingDrawn = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var drawArea = tiledImage.getDrawArea();
|
||||||
|
if(!drawArea){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTile(info){
|
||||||
|
var tile = info.tile;
|
||||||
|
if(tile && tile.loaded){
|
||||||
|
var needsDraw = _this._blendTile(
|
||||||
|
tiledImage,
|
||||||
|
tile,
|
||||||
|
tile.x,
|
||||||
|
tile.y,
|
||||||
|
info.level,
|
||||||
|
info.levelOpacity,
|
||||||
|
info.currentTime
|
||||||
|
);
|
||||||
|
if(needsDraw){
|
||||||
|
tiledImage._needsDraw = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var infoArray = tiledImage.getTileInfoForDrawing();
|
||||||
|
infoArray.forEach(updateTile);
|
||||||
|
|
||||||
|
this._drawTiles(tiledImage, tiledImage.lastDrawn);
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @inner
|
||||||
|
* Updates the opacity of a tile according to the time it has been on screen
|
||||||
|
* to perform a fade-in.
|
||||||
|
* Updates coverage once a tile is fully opaque.
|
||||||
|
* Returns whether the fade-in has completed.
|
||||||
|
*
|
||||||
|
* @param {OpenSeadragon.Tile} tile
|
||||||
|
* @param {Number} x
|
||||||
|
* @param {Number} y
|
||||||
|
* @param {Number} level
|
||||||
|
* @param {Number} levelOpacity
|
||||||
|
* @param {Number} currentTime
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
_blendTile: function( tiledImage, tile, x, y, level, levelOpacity, currentTime ){
|
||||||
|
var blendTimeMillis = 1000 * tiledImage.blendTime,
|
||||||
|
deltaTime,
|
||||||
|
opacity;
|
||||||
|
|
||||||
|
if ( !tile.blendStart ) {
|
||||||
|
tile.blendStart = currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
deltaTime = currentTime - tile.blendStart;
|
||||||
|
opacity = blendTimeMillis ? Math.min( 1, deltaTime / ( blendTimeMillis ) ) : 1;
|
||||||
|
|
||||||
|
if ( tiledImage.alwaysBlend ) {
|
||||||
|
opacity *= levelOpacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
tile.opacity = opacity;
|
||||||
|
|
||||||
|
tiledImage.lastDrawn.push( tile );
|
||||||
|
|
||||||
|
if ( opacity === 1 ) {
|
||||||
|
tiledImage._setCoverage( tiledImage.coverage, level, x, y, true );
|
||||||
|
tiledImage._hasOpaqueTile = true;
|
||||||
|
} else if ( deltaTime < blendTimeMillis ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @inner
|
||||||
|
* Draws a TiledImage.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
_drawTiles: function( tiledImage ) {
|
||||||
|
var lastDrawn = tiledImage.lastDrawn;
|
||||||
|
if (tiledImage.opacity === 0 || (lastDrawn.length === 0 && !tiledImage.placeholderFillStyle)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tile = lastDrawn[0];
|
||||||
|
var useSketch;
|
||||||
|
|
||||||
|
if (tile) {
|
||||||
|
useSketch = tiledImage.opacity < 1 ||
|
||||||
|
(tiledImage.compositeOperation && tiledImage.compositeOperation !== 'source-over') ||
|
||||||
|
(!tiledImage._isBottomItem() &&
|
||||||
|
tiledImage.source.hasTransparency(tile.context2D, tile.getUrl(), tile.ajaxHeaders, tile.postData));
|
||||||
|
}
|
||||||
|
|
||||||
|
var sketchScale;
|
||||||
|
var sketchTranslate;
|
||||||
|
|
||||||
|
var zoom = this.viewport.getZoom(true);
|
||||||
|
var imageZoom = tiledImage.viewportToImageZoom(zoom);
|
||||||
|
|
||||||
|
if (lastDrawn.length > 1 &&
|
||||||
|
imageZoom > tiledImage.smoothTileEdgesMinZoom &&
|
||||||
|
!tiledImage.iOSDevice &&
|
||||||
|
tiledImage.getRotation(true) % 360 === 0 && // TODO: support tile edge smoothing with tiled image rotation.
|
||||||
|
$.supportsCanvas && this.viewer.useCanvas) {
|
||||||
|
// When zoomed in a lot (>100%) the tile edges are visible.
|
||||||
|
// So we have to composite them at ~100% and scale them up together.
|
||||||
|
// Note: Disabled on iOS devices per default as it causes a native crash
|
||||||
|
useSketch = true;
|
||||||
|
sketchScale = tile.getScaleForEdgeSmoothing();
|
||||||
|
sketchTranslate = tile.getTranslationForEdgeSmoothing(sketchScale,
|
||||||
|
this.getCanvasSize(false),
|
||||||
|
this.getCanvasSize(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
var bounds;
|
||||||
|
if (useSketch) {
|
||||||
|
if (!sketchScale) {
|
||||||
|
// Except when edge smoothing, we only clean the part of the
|
||||||
|
// sketch canvas we are going to use for performance reasons.
|
||||||
|
bounds = this.viewport.viewportToViewerElementRectangle(
|
||||||
|
tiledImage.getClippedBounds(true))
|
||||||
|
.getIntegerBoundingBox();
|
||||||
|
|
||||||
|
if(this.viewer.viewport.getFlip()) {
|
||||||
|
if (this.viewport.getRotation(true) % 360 !== 0 ||
|
||||||
|
tiledImage.getRotation(true) % 360 !== 0) {
|
||||||
|
bounds.x = this.viewer.container.clientWidth - (bounds.x + bounds.width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bounds = bounds.times($.pixelDensityRatio);
|
||||||
|
}
|
||||||
|
this._clear(true, bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
// When scaling, we must rotate only when blending the sketch canvas to
|
||||||
|
// avoid interpolation
|
||||||
|
if (!sketchScale) {
|
||||||
|
if (this.viewport.getRotation(true) % 360 !== 0) {
|
||||||
|
this._offsetForRotation({
|
||||||
|
degrees: this.viewport.getRotation(true),
|
||||||
|
useSketch: useSketch
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (tiledImage.getRotation(true) % 360 !== 0) {
|
||||||
|
this._offsetForRotation({
|
||||||
|
degrees: tiledImage.getRotation(true),
|
||||||
|
point: this.viewport.pixelFromPointNoRotate(
|
||||||
|
tiledImage._getRotationPoint(true), true),
|
||||||
|
useSketch: useSketch
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.viewport.getRotation(true) % 360 === 0 &&
|
||||||
|
tiledImage.getRotation(true) % 360 === 0) {
|
||||||
|
if(this.viewer.viewport.getFlip()) {
|
||||||
|
this._flip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var usedClip = false;
|
||||||
|
if ( tiledImage._clip ) {
|
||||||
|
this.saveContext(useSketch);
|
||||||
|
|
||||||
|
var box = tiledImage.imageToViewportRectangle(tiledImage._clip, true);
|
||||||
|
box = box.rotate(-tiledImage.getRotation(true), tiledImage._getRotationPoint(true));
|
||||||
|
var clipRect = this.viewportToDrawerRectangle(box);
|
||||||
|
if (sketchScale) {
|
||||||
|
clipRect = clipRect.times(sketchScale);
|
||||||
|
}
|
||||||
|
if (sketchTranslate) {
|
||||||
|
clipRect = clipRect.translate(sketchTranslate);
|
||||||
|
}
|
||||||
|
this.setClip(clipRect, useSketch);
|
||||||
|
|
||||||
|
usedClip = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tiledImage._croppingPolygons) {
|
||||||
|
var self = this;
|
||||||
|
this.saveContext(useSketch);
|
||||||
|
try {
|
||||||
|
var polygons = tiledImage._croppingPolygons.map(function (polygon) {
|
||||||
|
return polygon.map(function (coord) {
|
||||||
|
var point = tiledImage
|
||||||
|
.imageToViewportCoordinates(coord.x, coord.y, true)
|
||||||
|
.rotate(-tiledImage.getRotation(true), tiledImage._getRotationPoint(true));
|
||||||
|
var clipPoint = self.viewportCoordToDrawerCoord(point);
|
||||||
|
if (sketchScale) {
|
||||||
|
clipPoint = clipPoint.times(sketchScale);
|
||||||
|
}
|
||||||
|
return clipPoint;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.clipWithPolygons(polygons, useSketch);
|
||||||
|
} catch (e) {
|
||||||
|
$.console.error(e);
|
||||||
|
}
|
||||||
|
usedClip = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( tiledImage.placeholderFillStyle && tiledImage._hasOpaqueTile === false ) {
|
||||||
|
var placeholderRect = this.viewportToDrawerRectangle(tiledImage.getBounds(true));
|
||||||
|
if (sketchScale) {
|
||||||
|
placeholderRect = placeholderRect.times(sketchScale);
|
||||||
|
}
|
||||||
|
if (sketchTranslate) {
|
||||||
|
placeholderRect = placeholderRect.translate(sketchTranslate);
|
||||||
|
}
|
||||||
|
|
||||||
|
var fillStyle = null;
|
||||||
|
if ( typeof tiledImage.placeholderFillStyle === "function" ) {
|
||||||
|
fillStyle = tiledImage.placeholderFillStyle(tiledImage, this.context);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fillStyle = tiledImage.placeholderFillStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.drawRectangle(placeholderRect, fillStyle, useSketch);
|
||||||
|
}
|
||||||
|
|
||||||
|
var subPixelRoundingRule = determineSubPixelRoundingRule(tiledImage.subPixelRoundingForTransparency);
|
||||||
|
|
||||||
|
var shouldRoundPositionAndSize = false;
|
||||||
|
|
||||||
|
if (subPixelRoundingRule === $.SUBPIXEL_ROUNDING_OCCURRENCES.ALWAYS) {
|
||||||
|
shouldRoundPositionAndSize = true;
|
||||||
|
} else if (subPixelRoundingRule === $.SUBPIXEL_ROUNDING_OCCURRENCES.ONLY_AT_REST) {
|
||||||
|
var isAnimating = this.viewer && this.viewer.isAnimating();
|
||||||
|
shouldRoundPositionAndSize = !isAnimating;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = lastDrawn.length - 1; i >= 0; i--) {
|
||||||
|
tile = lastDrawn[ i ];
|
||||||
|
this.drawTile( tile, tiledImage._drawingHandler, useSketch, sketchScale,
|
||||||
|
sketchTranslate, shouldRoundPositionAndSize, tiledImage.source );
|
||||||
|
tile.beingDrawn = true;
|
||||||
|
|
||||||
|
if( this.viewer ){
|
||||||
|
/**
|
||||||
|
* <em>- Needs documentation -</em>
|
||||||
|
*
|
||||||
|
* @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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( usedClip ) {
|
||||||
|
this.restoreContext( useSketch );
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sketchScale) {
|
||||||
|
if (tiledImage.getRotation(true) % 360 !== 0) {
|
||||||
|
this._restoreRotationChanges(useSketch);
|
||||||
|
}
|
||||||
|
if (this.viewport.getRotation(true) % 360 !== 0) {
|
||||||
|
this._restoreRotationChanges(useSketch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useSketch) {
|
||||||
|
if (sketchScale) {
|
||||||
|
if (this.viewport.getRotation(true) % 360 !== 0) {
|
||||||
|
this._offsetForRotation({
|
||||||
|
degrees: this.viewport.getRotation(true),
|
||||||
|
useSketch: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (tiledImage.getRotation(true) % 360 !== 0) {
|
||||||
|
this._offsetForRotation({
|
||||||
|
degrees: tiledImage.getRotation(true),
|
||||||
|
point: this.viewport.pixelFromPointNoRotate(
|
||||||
|
tiledImage._getRotationPoint(true), true),
|
||||||
|
useSketch: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.blendSketch({
|
||||||
|
opacity: tiledImage.opacity,
|
||||||
|
scale: sketchScale,
|
||||||
|
translate: sketchTranslate,
|
||||||
|
compositeOperation: tiledImage.compositeOperation,
|
||||||
|
bounds: bounds
|
||||||
|
});
|
||||||
|
if (sketchScale) {
|
||||||
|
if (tiledImage.getRotation(true) % 360 !== 0) {
|
||||||
|
this._restoreRotationChanges(false);
|
||||||
|
}
|
||||||
|
if (this.viewport.getRotation(true) % 360 !== 0) {
|
||||||
|
this._restoreRotationChanges(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sketchScale) {
|
||||||
|
if (this.viewport.getRotation(true) % 360 === 0 &&
|
||||||
|
tiledImage.getRotation(true) % 360 === 0) {
|
||||||
|
if(this.viewer.viewport.getFlip()) {
|
||||||
|
this._flip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._drawDebugInfo( tiledImage, lastDrawn );
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @inner
|
||||||
|
* Draws special debug information for a TiledImage if in debug mode.
|
||||||
|
* @param {OpenSeadragon.Tile[]} lastDrawn - An unordered list of Tiles drawn last frame.
|
||||||
|
*/
|
||||||
|
_drawDebugInfo: function( tiledImage, lastDrawn ) {
|
||||||
|
if( tiledImage.debugMode ) {
|
||||||
|
for ( var i = lastDrawn.length - 1; i >= 0; i-- ) {
|
||||||
|
var tile = lastDrawn[ i ];
|
||||||
|
try {
|
||||||
|
this.drawDebugInfo(tile, lastDrawn.length, i, tiledImage);
|
||||||
|
} catch(e) {
|
||||||
|
$.console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Methods from Tile */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function converts the given point from to the drawer coordinate by
|
||||||
|
* multiplying it with the pixel density.
|
||||||
|
* This function does not take rotation into account, thus assuming provided
|
||||||
|
* point is at 0 degree.
|
||||||
|
* @param {OpenSeadragon.Point} point - the pixel point to convert
|
||||||
|
* @returns {OpenSeadragon.Point} Point in drawer coordinate system.
|
||||||
|
*/
|
||||||
|
viewportCoordToDrawerCoord: function(point) {
|
||||||
|
var vpPoint = this.viewport.pixelFromPointNoRotate(point, true);
|
||||||
|
return new $.Point(
|
||||||
|
vpPoint.x * $.pixelDensityRatio,
|
||||||
|
vpPoint.y * $.pixelDensityRatio
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function will create multiple polygon paths on the drawing context by provided polygons,
|
||||||
|
* then clip the context to the paths.
|
||||||
|
* @param {OpenSeadragon.Point[][]} polygons - an array of polygons. A polygon is an array of OpenSeadragon.Point
|
||||||
|
* @param {Boolean} useSketch - Whether to use the sketch canvas or not.
|
||||||
|
*/
|
||||||
|
clipWithPolygons: function (polygons, useSketch) {
|
||||||
|
if (!this.useCanvas) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var context = this._getContext(useSketch);
|
||||||
|
context.beginPath();
|
||||||
|
polygons.forEach(function (polygon) {
|
||||||
|
polygon.forEach(function (coord, i) {
|
||||||
|
context[i === 0 ? 'moveTo' : 'lineTo'](coord.x, coord.y);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
context.clip();
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scale from OpenSeadragon viewer rectangle to drawer rectangle
|
* Scale from OpenSeadragon viewer rectangle to drawer rectangle
|
||||||
* (ignoring rotation)
|
* (ignoring rotation)
|
||||||
@ -276,12 +667,179 @@ $.Drawer.prototype = {
|
|||||||
if (this.useCanvas) {
|
if (this.useCanvas) {
|
||||||
var context = this._getContext(useSketch);
|
var context = this._getContext(useSketch);
|
||||||
scale = scale || 1;
|
scale = scale || 1;
|
||||||
tile.drawCanvas(context, drawingHandler, scale, translate, shouldRoundPositionAndSize, source);
|
this.drawTileToCanvas(tile, context, drawingHandler, scale, translate, shouldRoundPositionAndSize, source);
|
||||||
} else {
|
} else {
|
||||||
tile.drawHTML( this.canvas );
|
tile.drawTileToHTML( tile, this.canvas );
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the tile in a canvas-based context.
|
||||||
|
* @function
|
||||||
|
* @param {OpenSeadragon.Tile} tile - the tile to draw to the canvas
|
||||||
|
* @param {Canvas} context
|
||||||
|
* @param {Function} drawingHandler - Method for firing the drawing event.
|
||||||
|
* drawingHandler({context, tile, rendered})
|
||||||
|
* where <code>rendered</code> is the context with the pre-drawn image.
|
||||||
|
* @param {Number} [scale=1] - Apply a scale to position and size
|
||||||
|
* @param {OpenSeadragon.Point} [translate] - A translation vector
|
||||||
|
* @param {Boolean} [shouldRoundPositionAndSize] - Tells whether to round
|
||||||
|
* position and size of tiles supporting alpha channel in non-transparency
|
||||||
|
* context.
|
||||||
|
* @param {OpenSeadragon.TileSource} source - The source specification of the tile.
|
||||||
|
*/
|
||||||
|
drawTileToCanvas: function( tile, context, drawingHandler, scale, translate, shouldRoundPositionAndSize, source) {
|
||||||
|
|
||||||
|
var position = tile.position.times($.pixelDensityRatio),
|
||||||
|
size = tile.size.times($.pixelDensityRatio),
|
||||||
|
rendered;
|
||||||
|
|
||||||
|
if (!tile.context2D && !tile.cacheImageRecord) {
|
||||||
|
$.console.warn(
|
||||||
|
'[Drawer.drawTileToCanvas] attempting to draw tile %s when it\'s not cached',
|
||||||
|
tile.toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rendered = tile.getCanvasContext();
|
||||||
|
|
||||||
|
if ( !tile.loaded || !rendered ){
|
||||||
|
$.console.warn(
|
||||||
|
"Attempting to draw tile %s when it's not yet loaded.",
|
||||||
|
tile.toString()
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.save();
|
||||||
|
context.globalAlpha = this.opacity;
|
||||||
|
|
||||||
|
if (typeof scale === 'number' && scale !== 1) {
|
||||||
|
// draw tile at a different scale
|
||||||
|
position = position.times(scale);
|
||||||
|
size = size.times(scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (translate instanceof $.Point) {
|
||||||
|
// shift tile position slightly
|
||||||
|
position = position.plus(translate);
|
||||||
|
}
|
||||||
|
|
||||||
|
//if we are supposed to be rendering fully opaque rectangle,
|
||||||
|
//ie its done fading or fading is turned off, and if we are drawing
|
||||||
|
//an image with an alpha channel, then the only way
|
||||||
|
//to avoid seeing the tile underneath is to clear the rectangle
|
||||||
|
if (context.globalAlpha === 1 && tile.hasTransparency) {
|
||||||
|
if (shouldRoundPositionAndSize) {
|
||||||
|
// Round to the nearest whole pixel so we don't get seams from overlap.
|
||||||
|
position.x = Math.round(position.x);
|
||||||
|
position.y = Math.round(position.y);
|
||||||
|
size.x = Math.round(size.x);
|
||||||
|
size.y = Math.round(size.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
//clearing only the inside of the rectangle occupied
|
||||||
|
//by the png prevents edge flikering
|
||||||
|
context.clearRect(
|
||||||
|
position.x,
|
||||||
|
position.y,
|
||||||
|
size.x,
|
||||||
|
size.y
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This gives the application a chance to make image manipulation
|
||||||
|
// changes as we are rendering the image
|
||||||
|
drawingHandler({context: context, tile: tile, rendered: rendered});
|
||||||
|
|
||||||
|
var sourceWidth, sourceHeight;
|
||||||
|
if (tile.sourceBounds) {
|
||||||
|
sourceWidth = Math.min(tile.sourceBounds.width, rendered.canvas.width);
|
||||||
|
sourceHeight = Math.min(tile.sourceBounds.height, rendered.canvas.height);
|
||||||
|
} else {
|
||||||
|
sourceWidth = rendered.canvas.width;
|
||||||
|
sourceHeight = rendered.canvas.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.translate(position.x + size.x / 2, 0);
|
||||||
|
if (tile.flipped) {
|
||||||
|
context.scale(-1, 1);
|
||||||
|
}
|
||||||
|
context.drawImage(
|
||||||
|
rendered.canvas,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
sourceWidth,
|
||||||
|
sourceHeight,
|
||||||
|
-size.x / 2,
|
||||||
|
position.y,
|
||||||
|
size.x,
|
||||||
|
size.y
|
||||||
|
);
|
||||||
|
|
||||||
|
context.restore();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the tile in an html container.
|
||||||
|
* @function
|
||||||
|
* @param {OpenSeadragon.Tile} tile
|
||||||
|
* @param {Element} container
|
||||||
|
*/
|
||||||
|
drawTileToHTML: function( tile, container ) {
|
||||||
|
if (!tile.cacheImageRecord) {
|
||||||
|
$.console.warn(
|
||||||
|
'[Drawer.drawTileToHTML] attempting to draw tile %s when it\'s not cached',
|
||||||
|
tile.toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !tile.loaded ) {
|
||||||
|
$.console.warn(
|
||||||
|
"Attempting to draw tile %s when it's not yet loaded.",
|
||||||
|
tile.toString()
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//EXPERIMENTAL - trying to figure out how to scale the container
|
||||||
|
// content during animation of the container size.
|
||||||
|
|
||||||
|
if ( !tile.element ) {
|
||||||
|
var image = tile.getImage();
|
||||||
|
if (!image) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tile.element = $.makeNeutralElement( "div" );
|
||||||
|
tile.imgElement = image.cloneNode();
|
||||||
|
tile.imgElement.style.msInterpolationMode = "nearest-neighbor";
|
||||||
|
tile.imgElement.style.width = "100%";
|
||||||
|
tile.imgElement.style.height = "100%";
|
||||||
|
|
||||||
|
tile.style = tile.element.style;
|
||||||
|
tile.style.position = "absolute";
|
||||||
|
}
|
||||||
|
if ( tile.element.parentNode !== container ) {
|
||||||
|
container.appendChild( tile.element );
|
||||||
|
}
|
||||||
|
if ( tile.imgElement.parentNode !== tile.element ) {
|
||||||
|
tile.element.appendChild( tile.imgElement );
|
||||||
|
}
|
||||||
|
|
||||||
|
tile.style.top = tile.position.y + "px";
|
||||||
|
tile.style.left = tile.position.x + "px";
|
||||||
|
tile.style.height = tile.size.y + "px";
|
||||||
|
tile.style.width = tile.size.x + "px";
|
||||||
|
|
||||||
|
if (tile.flipped) {
|
||||||
|
tile.style.transform = "scaleX(-1)";
|
||||||
|
}
|
||||||
|
|
||||||
|
$.setElementOpacity( tile.element, tile.opacity );
|
||||||
|
},
|
||||||
|
|
||||||
_getContext: function( useSketch ) {
|
_getContext: function( useSketch ) {
|
||||||
var context = this.context;
|
var context = this.context;
|
||||||
if ( useSketch ) {
|
if ( useSketch ) {
|
||||||
@ -766,6 +1324,75 @@ $.Drawer.prototype = {
|
|||||||
}
|
}
|
||||||
return maxOpacity;
|
return maxOpacity;
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @inner
|
||||||
|
* Defines the value for subpixel rounding to fallback to in case of missing or
|
||||||
|
* invalid value.
|
||||||
|
*/
|
||||||
|
var DEFAULT_SUBPIXEL_ROUNDING_RULE = $.SUBPIXEL_ROUNDING_OCCURRENCES.NEVER;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @inner
|
||||||
|
* Checks whether the input value is an invalid subpixel rounding enum value.
|
||||||
|
*
|
||||||
|
* @param {SUBPIXEL_ROUNDING_OCCURRENCES} value - The subpixel rounding enum value to check.
|
||||||
|
* @returns {Boolean} Returns true if the input value is none of the expected
|
||||||
|
* {@link SUBPIXEL_ROUNDING_OCCURRENCES.ALWAYS}, {@link SUBPIXEL_ROUNDING_OCCURRENCES.ONLY_AT_REST} or {@link SUBPIXEL_ROUNDING_OCCURRENCES.NEVER} value.
|
||||||
|
*/
|
||||||
|
function isSubPixelRoundingRuleUnknown(value) {
|
||||||
|
return value !== $.SUBPIXEL_ROUNDING_OCCURRENCES.ALWAYS &&
|
||||||
|
value !== $.SUBPIXEL_ROUNDING_OCCURRENCES.ONLY_AT_REST &&
|
||||||
|
value !== $.SUBPIXEL_ROUNDING_OCCURRENCES.NEVER;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @inner
|
||||||
|
* Ensures the returned value is always a valid subpixel rounding enum value,
|
||||||
|
* defaulting to {@link SUBPIXEL_ROUNDING_OCCURRENCES.NEVER} if input is missing or invalid.
|
||||||
|
*
|
||||||
|
* @param {SUBPIXEL_ROUNDING_OCCURRENCES} value - The subpixel rounding enum value to normalize.
|
||||||
|
* @returns {SUBPIXEL_ROUNDING_OCCURRENCES} Returns a valid subpixel rounding enum value.
|
||||||
|
*/
|
||||||
|
function normalizeSubPixelRoundingRule(value) {
|
||||||
|
if (isSubPixelRoundingRuleUnknown(value)) {
|
||||||
|
return DEFAULT_SUBPIXEL_ROUNDING_RULE;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @inner
|
||||||
|
* Ensures the returned value is always a valid subpixel rounding enum value,
|
||||||
|
* defaulting to 'NEVER' if input is missing or invalid.
|
||||||
|
*
|
||||||
|
* @param {Object} subPixelRoundingRules - A subpixel rounding enum values dictionary [{@link BROWSERS}] --> {@link SUBPIXEL_ROUNDING_OCCURRENCES}.
|
||||||
|
* @returns {SUBPIXEL_ROUNDING_OCCURRENCES} Returns the determined subpixel rounding enum value for the
|
||||||
|
* current browser.
|
||||||
|
*/
|
||||||
|
function determineSubPixelRoundingRule(subPixelRoundingRules) {
|
||||||
|
if (typeof subPixelRoundingRules === 'number') {
|
||||||
|
return normalizeSubPixelRoundingRule(subPixelRoundingRules);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!subPixelRoundingRules || !$.Browser) {
|
||||||
|
return DEFAULT_SUBPIXEL_ROUNDING_RULE;
|
||||||
|
}
|
||||||
|
|
||||||
|
var subPixelRoundingRule = subPixelRoundingRules[$.Browser.vendor];
|
||||||
|
|
||||||
|
if (isSubPixelRoundingRuleUnknown(subPixelRoundingRule)) {
|
||||||
|
subPixelRoundingRule = subPixelRoundingRules['*'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizeSubPixelRoundingRule(subPixelRoundingRule);
|
||||||
|
}
|
||||||
|
|
||||||
}( OpenSeadragon ));
|
}( OpenSeadragon ));
|
||||||
|
344
src/drawerbase.js
Normal file
344
src/drawerbase.js
Normal file
@ -0,0 +1,344 @@
|
|||||||
|
/*
|
||||||
|
* OpenSeadragon - DrawerBase
|
||||||
|
*
|
||||||
|
* Copyright (C) 2009 CodePlex Foundation
|
||||||
|
* Copyright (C) 2010-2023 OpenSeadragon contributors
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are
|
||||||
|
* met:
|
||||||
|
*
|
||||||
|
* - Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* - Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* - Neither the name of CodePlex Foundation nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from
|
||||||
|
* this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||||
|
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function( $ ){
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class DrawerBase
|
||||||
|
* @memberof OpenSeadragon
|
||||||
|
* @classdesc Base class for Drawers that handle rendering of tiles for an {@link OpenSeadragon.Viewer}.
|
||||||
|
* @param {Object} options - Options for this Drawer.
|
||||||
|
* @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this Drawer.
|
||||||
|
* @param {OpenSeadragon.Viewport} options.viewport - Reference to Viewer viewport.
|
||||||
|
* @param {Element} options.element - Parent element.
|
||||||
|
*/
|
||||||
|
$.DrawerBase = function( options ) {
|
||||||
|
|
||||||
|
$.console.assert( options.viewer, "[Drawer] options.viewer is required" );
|
||||||
|
|
||||||
|
//backward compatibility for positional args while preferring more
|
||||||
|
//idiomatic javascript options object as the only argument
|
||||||
|
var args = arguments;
|
||||||
|
|
||||||
|
if( !$.isPlainObject( options ) ){
|
||||||
|
options = {
|
||||||
|
source: args[ 0 ], // Reference to Viewer tile source.
|
||||||
|
viewport: args[ 1 ], // Reference to Viewer viewport.
|
||||||
|
element: args[ 2 ] // Parent element.
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
$.console.assert( options.viewport, "[Drawer] options.viewport is required" );
|
||||||
|
$.console.assert( options.element, "[Drawer] options.element is required" );
|
||||||
|
|
||||||
|
if ( options.source ) {
|
||||||
|
$.console.error( "[Drawer] options.source is no longer accepted; use TiledImage instead" );
|
||||||
|
}
|
||||||
|
|
||||||
|
this.viewer = options.viewer;
|
||||||
|
this.viewport = options.viewport;
|
||||||
|
this.debugGridColor = typeof options.debugGridColor === 'string' ? [options.debugGridColor] : options.debugGridColor || $.DEFAULT_SETTINGS.debugGridColor;
|
||||||
|
|
||||||
|
if (options.opacity) {
|
||||||
|
$.console.error( "[Drawer] options.opacity is no longer accepted; set the opacity on the TiledImage instead" );
|
||||||
|
}
|
||||||
|
|
||||||
|
this.useCanvas = $.supportsCanvas && ( this.viewer ? this.viewer.useCanvas : true );
|
||||||
|
/**
|
||||||
|
* The parent element of this Drawer instance, passed in when the Drawer was created.
|
||||||
|
* The parent of {@link OpenSeadragon.DrawerBase#canvas}.
|
||||||
|
* @member {Element} container
|
||||||
|
* @memberof OpenSeadragon.DrawerBase#
|
||||||
|
*/
|
||||||
|
this.container = $.getElement( options.element );
|
||||||
|
/**
|
||||||
|
* A <canvas> element if the browser supports them, otherwise a <div> element.
|
||||||
|
* Child element of {@link OpenSeadragon.DrawerBase#container}.
|
||||||
|
* @member {Element} canvas
|
||||||
|
* @memberof OpenSeadragon.DrawerBase#
|
||||||
|
*/
|
||||||
|
this.canvas = $.makeNeutralElement( this.useCanvas ? "canvas" : "div" );
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @member {Element} element
|
||||||
|
* @memberof OpenSeadragon.DrawerBase#
|
||||||
|
* @deprecated Alias for {@link OpenSeadragon.DrawerBase#container}.
|
||||||
|
*/
|
||||||
|
this.element = this.container;
|
||||||
|
|
||||||
|
// We force our container to ltr because our drawing math doesn't work in rtl.
|
||||||
|
// This issue only affects our canvas renderer, but we do it always for consistency.
|
||||||
|
// Note that this means overlays you want to be rtl need to be explicitly set to rtl.
|
||||||
|
this.container.dir = 'ltr';
|
||||||
|
|
||||||
|
if (this.useCanvas) {
|
||||||
|
var viewportSize = this._calculateCanvasSize();
|
||||||
|
this.canvas.width = viewportSize.x;
|
||||||
|
this.canvas.height = viewportSize.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.canvas.style.width = "100%";
|
||||||
|
this.canvas.style.height = "100%";
|
||||||
|
this.canvas.style.position = "absolute";
|
||||||
|
$.setElementOpacity( this.canvas, this.opacity, true );
|
||||||
|
|
||||||
|
// Allow pointer events to pass through the canvas element so implicit
|
||||||
|
// pointer capture works on touch devices
|
||||||
|
$.setElementPointerEventsNone( this.canvas );
|
||||||
|
$.setElementTouchActionNone( this.canvas );
|
||||||
|
|
||||||
|
// explicit left-align
|
||||||
|
this.container.style.textAlign = "left";
|
||||||
|
this.container.appendChild( this.canvas );
|
||||||
|
|
||||||
|
// Image smoothing for canvas rendering (only if canvas is used).
|
||||||
|
// Canvas default is "true", so this will only be changed if user specified "false".
|
||||||
|
this._imageSmoothingEnabled = true;
|
||||||
|
|
||||||
|
this._checkForAPIOverrides();
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @lends OpenSeadragon.DrawerBaseBase.prototype */
|
||||||
|
$.DrawerBase.prototype = {
|
||||||
|
|
||||||
|
// Drawer implementaions must define the next four methods. These are called
|
||||||
|
// by core OSD, and forcing overrides (even for nullop methods) makes the
|
||||||
|
// behavior of the implementations explicitly clear in the code.
|
||||||
|
// Whether these have been overridden by child classes is checked in the
|
||||||
|
// constructor (via _checkForAPIOverrides). It could make sense to consolidate
|
||||||
|
// these a bit (e.g. by making `draw` take an array of `TiledImage`s and
|
||||||
|
// clearing the view as needed, rather than the existing pattern of
|
||||||
|
// `drawer.clear(); world.draw()` in the calling code), but they have been
|
||||||
|
// left as-is to maintain backwards compatibility.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param tiledImage the TiledImage that is ready to be drawn
|
||||||
|
*/
|
||||||
|
draw: function(tiledImage) {
|
||||||
|
$.console.error('Drawer.draw must be implemented by child class');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Boolean} True if rotation is supported.
|
||||||
|
*/
|
||||||
|
canRotate: function() {
|
||||||
|
$.console.error('Drawer.canRotate must be implemented by child class');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the drawer (unload current loaded tiles)
|
||||||
|
*/
|
||||||
|
destroy: function() {
|
||||||
|
$.console.error('Drawer.destroy must be implemented by child class');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the Drawer so it's ready to draw another frame.
|
||||||
|
*/
|
||||||
|
clear: function() {
|
||||||
|
$.console.error('Drawer.clear must be implemented by child class');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns image smoothing on or off for this viewer. Note: Ignored in some (especially older) browsers that do not support this property.
|
||||||
|
*
|
||||||
|
* @function
|
||||||
|
* @param {Boolean} [imageSmoothingEnabled] - Whether or not the image is
|
||||||
|
* drawn smoothly on the canvas; see imageSmoothingEnabled in
|
||||||
|
* {@link OpenSeadragon.Options} for more explanation.
|
||||||
|
*/
|
||||||
|
setImageSmoothingEnabled: function(imageSmoothingEnabled){
|
||||||
|
$.console.error('Drawer.setImageSmoothingEnabled must be implemented by child class');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures that child classes have provided implementations for API methods
|
||||||
|
* draw, canRotate, destroy, and clear. Throws an exception if the original
|
||||||
|
* placeholder methods are still in place.
|
||||||
|
*/
|
||||||
|
_checkForAPIOverrides: function(){
|
||||||
|
if(this.draw === $.DrawerBase.prototype.draw){
|
||||||
|
throw("[drawer].draw must be implemented by child class");
|
||||||
|
}
|
||||||
|
if(this.canRotate === $.DrawerBase.prototype.canRotate){
|
||||||
|
throw("[drawer].canRotate must be implemented by child class");
|
||||||
|
}
|
||||||
|
if(this.destroy === $.DrawerBase.prototype.destroy){
|
||||||
|
throw("[drawer].destroy must be implemented by child class");
|
||||||
|
}
|
||||||
|
if(this.clear === $.DrawerBase.prototype.clear){
|
||||||
|
throw("[drawer].clear must be implemented by child class");
|
||||||
|
}
|
||||||
|
if(this.setImageSmoothingEnabled === $.DrawerBase.prototype.setImageSmoothingEnabled){
|
||||||
|
throw("[drawer].setImageSmoothingEnabled must be implemented by child class");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scale from OpenSeadragon viewer rectangle to drawer rectangle
|
||||||
|
* (ignoring rotation)
|
||||||
|
* @param {OpenSeadragon.Rect} rectangle - The rectangle in viewport coordinate system.
|
||||||
|
* @returns {OpenSeadragon.Rect} Rectangle in drawer coordinate system.
|
||||||
|
*/
|
||||||
|
viewportToDrawerRectangle: function(rectangle) {
|
||||||
|
var topLeft = this.viewport.pixelFromPointNoRotate(rectangle.getTopLeft(), true);
|
||||||
|
var size = this.viewport.deltaPixelsFromPointsNoRotate(rectangle.getSize(), true);
|
||||||
|
|
||||||
|
return new $.Rect(
|
||||||
|
topLeft.x * $.pixelDensityRatio,
|
||||||
|
topLeft.y * $.pixelDensityRatio,
|
||||||
|
size.x * $.pixelDensityRatio,
|
||||||
|
size.y * $.pixelDensityRatio
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function converts the given point from to the drawer coordinate by
|
||||||
|
* multiplying it with the pixel density.
|
||||||
|
* This function does not take rotation into account, thus assuming provided
|
||||||
|
* point is at 0 degree.
|
||||||
|
* @param {OpenSeadragon.Point} point - the pixel point to convert
|
||||||
|
* @returns {OpenSeadragon.Point} Point in drawer coordinate system.
|
||||||
|
*/
|
||||||
|
viewportCoordToDrawerCoord: function(point) {
|
||||||
|
var vpPoint = this.viewport.pixelFromPointNoRotate(point, true);
|
||||||
|
return new $.Point(
|
||||||
|
vpPoint.x * $.pixelDensityRatio,
|
||||||
|
vpPoint.y * $.pixelDensityRatio
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
// private
|
||||||
|
_calculateCanvasSize: function() {
|
||||||
|
var pixelDensityRatio = $.pixelDensityRatio;
|
||||||
|
var viewportSize = this.viewport.getContainerSize();
|
||||||
|
return {
|
||||||
|
// canvas width and height are integers
|
||||||
|
x: Math.round(viewportSize.x * pixelDensityRatio),
|
||||||
|
y: Math.round(viewportSize.y * pixelDensityRatio)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/* Deprecated Functions */
|
||||||
|
|
||||||
|
// deprecated
|
||||||
|
addOverlay: function( element, location, placement, onDraw ) {
|
||||||
|
$.console.error("drawer.addOverlay is deprecated. Use viewer.addOverlay instead.");
|
||||||
|
this.viewer.addOverlay( element, location, placement, onDraw );
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
// deprecated
|
||||||
|
updateOverlay: function( element, location, placement ) {
|
||||||
|
$.console.error("drawer.updateOverlay is deprecated. Use viewer.updateOverlay instead.");
|
||||||
|
this.viewer.updateOverlay( element, location, placement );
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
// deprecated
|
||||||
|
removeOverlay: function( element ) {
|
||||||
|
$.console.error("drawer.removeOverlay is deprecated. Use viewer.removeOverlay instead.");
|
||||||
|
this.viewer.removeOverlay( element );
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
// deprecated
|
||||||
|
clearOverlays: function() {
|
||||||
|
$.console.error("drawer.clearOverlays is deprecated. Use viewer.clearOverlays instead.");
|
||||||
|
this.viewer.clearOverlays();
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
// deprecated
|
||||||
|
needsUpdate: function() {
|
||||||
|
$.console.error( "[Drawer.needsUpdate] this function is deprecated. Use World.needsDraw instead." );
|
||||||
|
return this.viewer.world.needsDraw();
|
||||||
|
},
|
||||||
|
|
||||||
|
// deprecated
|
||||||
|
numTilesLoaded: function() {
|
||||||
|
$.console.error( "[Drawer.numTilesLoaded] this function is deprecated. Use TileCache.numTilesLoaded instead." );
|
||||||
|
return this.viewer.tileCache.numTilesLoaded();
|
||||||
|
},
|
||||||
|
|
||||||
|
// deprecated
|
||||||
|
reset: function() {
|
||||||
|
$.console.error( "[Drawer.reset] this function is deprecated. Use World.resetItems instead." );
|
||||||
|
this.viewer.world.resetItems();
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
// deprecated
|
||||||
|
update: function() {
|
||||||
|
$.console.error( "[Drawer.update] this function is deprecated. Use Drawer.clear and World.draw instead." );
|
||||||
|
this.clear();
|
||||||
|
this.viewer.world.draw();
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
// deprecated
|
||||||
|
setOpacity: function( opacity ) {
|
||||||
|
$.console.error("drawer.setOpacity is deprecated. Use tiledImage.setOpacity instead.");
|
||||||
|
var world = this.viewer.world;
|
||||||
|
for (var i = 0; i < world.getItemCount(); i++) {
|
||||||
|
world.getItemAt( i ).setOpacity( opacity );
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
// deprecated
|
||||||
|
getOpacity: function() {
|
||||||
|
$.console.error("drawer.getOpacity is deprecated. Use tiledImage.getOpacity instead.");
|
||||||
|
var world = this.viewer.world;
|
||||||
|
var maxOpacity = 0;
|
||||||
|
for (var i = 0; i < world.getItemCount(); i++) {
|
||||||
|
var opacity = world.getItemAt( i ).getOpacity();
|
||||||
|
if ( opacity > maxOpacity ) {
|
||||||
|
maxOpacity = opacity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxOpacity;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.defineProperty($.DrawerBase.prototype, "isOpenSeadragonDrawer", {
|
||||||
|
get: function get() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}( OpenSeadragon ));
|
165
src/tile.js
165
src/tile.js
@ -274,64 +274,6 @@
|
|||||||
return !!this.context2D || this.getUrl().match('.png');
|
return !!this.context2D || this.getUrl().match('.png');
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the tile in an html container.
|
|
||||||
* @function
|
|
||||||
* @param {Element} container
|
|
||||||
*/
|
|
||||||
drawHTML: function( container ) {
|
|
||||||
if (!this.cacheImageRecord) {
|
|
||||||
$.console.warn(
|
|
||||||
'[Tile.drawHTML] attempting to draw tile %s when it\'s not cached',
|
|
||||||
this.toString());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( !this.loaded ) {
|
|
||||||
$.console.warn(
|
|
||||||
"Attempting to draw tile %s when it's not yet loaded.",
|
|
||||||
this.toString()
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//EXPERIMENTAL - trying to figure out how to scale the container
|
|
||||||
// content during animation of the container size.
|
|
||||||
|
|
||||||
if ( !this.element ) {
|
|
||||||
var image = this.getImage();
|
|
||||||
if (!image) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.element = $.makeNeutralElement( "div" );
|
|
||||||
this.imgElement = image.cloneNode();
|
|
||||||
this.imgElement.style.msInterpolationMode = "nearest-neighbor";
|
|
||||||
this.imgElement.style.width = "100%";
|
|
||||||
this.imgElement.style.height = "100%";
|
|
||||||
|
|
||||||
this.style = this.element.style;
|
|
||||||
this.style.position = "absolute";
|
|
||||||
}
|
|
||||||
if ( this.element.parentNode !== container ) {
|
|
||||||
container.appendChild( this.element );
|
|
||||||
}
|
|
||||||
if ( this.imgElement.parentNode !== this.element ) {
|
|
||||||
this.element.appendChild( this.imgElement );
|
|
||||||
}
|
|
||||||
|
|
||||||
this.style.top = this.position.y + "px";
|
|
||||||
this.style.left = this.position.x + "px";
|
|
||||||
this.style.height = this.size.y + "px";
|
|
||||||
this.style.width = this.size.x + "px";
|
|
||||||
|
|
||||||
if (this.flipped) {
|
|
||||||
this.style.transform = "scaleX(-1)";
|
|
||||||
}
|
|
||||||
|
|
||||||
$.setElementOpacity( this.element, this.opacity );
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Image object for this tile.
|
* The Image object for this tile.
|
||||||
* @member {Object} image
|
* @member {Object} image
|
||||||
@ -385,113 +327,6 @@
|
|||||||
return this.context2D || this.cacheImageRecord.getRenderedContext();
|
return this.context2D || this.cacheImageRecord.getRenderedContext();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the tile in a canvas-based context.
|
|
||||||
* @function
|
|
||||||
* @param {Canvas} context
|
|
||||||
* @param {Function} drawingHandler - Method for firing the drawing event.
|
|
||||||
* drawingHandler({context, tile, rendered})
|
|
||||||
* where <code>rendered</code> is the context with the pre-drawn image.
|
|
||||||
* @param {Number} [scale=1] - Apply a scale to position and size
|
|
||||||
* @param {OpenSeadragon.Point} [translate] - A translation vector
|
|
||||||
* @param {Boolean} [shouldRoundPositionAndSize] - Tells whether to round
|
|
||||||
* position and size of tiles supporting alpha channel in non-transparency
|
|
||||||
* context.
|
|
||||||
* @param {OpenSeadragon.TileSource} source - The source specification of the tile.
|
|
||||||
*/
|
|
||||||
drawCanvas: function( context, drawingHandler, scale, translate, shouldRoundPositionAndSize, source) {
|
|
||||||
|
|
||||||
var position = this.position.times($.pixelDensityRatio),
|
|
||||||
size = this.size.times($.pixelDensityRatio),
|
|
||||||
rendered;
|
|
||||||
|
|
||||||
if (!this.context2D && !this.cacheImageRecord) {
|
|
||||||
$.console.warn(
|
|
||||||
'[Tile.drawCanvas] attempting to draw tile %s when it\'s not cached',
|
|
||||||
this.toString());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
rendered = this.getCanvasContext();
|
|
||||||
|
|
||||||
if ( !this.loaded || !rendered ){
|
|
||||||
$.console.warn(
|
|
||||||
"Attempting to draw tile %s when it's not yet loaded.",
|
|
||||||
this.toString()
|
|
||||||
);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
context.save();
|
|
||||||
context.globalAlpha = this.opacity;
|
|
||||||
|
|
||||||
if (typeof scale === 'number' && scale !== 1) {
|
|
||||||
// draw tile at a different scale
|
|
||||||
position = position.times(scale);
|
|
||||||
size = size.times(scale);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (translate instanceof $.Point) {
|
|
||||||
// shift tile position slightly
|
|
||||||
position = position.plus(translate);
|
|
||||||
}
|
|
||||||
|
|
||||||
//if we are supposed to be rendering fully opaque rectangle,
|
|
||||||
//ie its done fading or fading is turned off, and if we are drawing
|
|
||||||
//an image with an alpha channel, then the only way
|
|
||||||
//to avoid seeing the tile underneath is to clear the rectangle
|
|
||||||
if (context.globalAlpha === 1 && this.hasTransparency) {
|
|
||||||
if (shouldRoundPositionAndSize) {
|
|
||||||
// Round to the nearest whole pixel so we don't get seams from overlap.
|
|
||||||
position.x = Math.round(position.x);
|
|
||||||
position.y = Math.round(position.y);
|
|
||||||
size.x = Math.round(size.x);
|
|
||||||
size.y = Math.round(size.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
//clearing only the inside of the rectangle occupied
|
|
||||||
//by the png prevents edge flikering
|
|
||||||
context.clearRect(
|
|
||||||
position.x,
|
|
||||||
position.y,
|
|
||||||
size.x,
|
|
||||||
size.y
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This gives the application a chance to make image manipulation
|
|
||||||
// changes as we are rendering the image
|
|
||||||
drawingHandler({context: context, tile: this, rendered: rendered});
|
|
||||||
|
|
||||||
var sourceWidth, sourceHeight;
|
|
||||||
if (this.sourceBounds) {
|
|
||||||
sourceWidth = Math.min(this.sourceBounds.width, rendered.canvas.width);
|
|
||||||
sourceHeight = Math.min(this.sourceBounds.height, rendered.canvas.height);
|
|
||||||
} else {
|
|
||||||
sourceWidth = rendered.canvas.width;
|
|
||||||
sourceHeight = rendered.canvas.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
context.translate(position.x + size.x / 2, 0);
|
|
||||||
if (this.flipped) {
|
|
||||||
context.scale(-1, 1);
|
|
||||||
}
|
|
||||||
context.drawImage(
|
|
||||||
rendered.canvas,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
sourceWidth,
|
|
||||||
sourceHeight,
|
|
||||||
-size.x / 2,
|
|
||||||
position.y,
|
|
||||||
size.x,
|
|
||||||
size.y
|
|
||||||
);
|
|
||||||
|
|
||||||
context.restore();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the ratio between current and original size.
|
* Get the ratio between current and original size.
|
||||||
* @function
|
* @function
|
||||||
|
@ -163,6 +163,7 @@ $.TiledImage = function( options ) {
|
|||||||
_needsDraw: true, // Does the tiledImage need to update the viewport again?
|
_needsDraw: true, // Does the tiledImage need to update the viewport again?
|
||||||
_hasOpaqueTile: false, // Do we have even one fully opaque tile?
|
_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.
|
||||||
|
_tilesToDraw: [], // info about the tiles currently in the viewport
|
||||||
//configurable settings
|
//configurable settings
|
||||||
springStiffness: $.DEFAULT_SETTINGS.springStiffness,
|
springStiffness: $.DEFAULT_SETTINGS.springStiffness,
|
||||||
animationTime: $.DEFAULT_SETTINGS.animationTime,
|
animationTime: $.DEFAULT_SETTINGS.animationTime,
|
||||||
@ -305,6 +306,8 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
var scaleUpdated = this._scaleSpring.update();
|
var scaleUpdated = this._scaleSpring.update();
|
||||||
var degreesUpdated = this._degreesSpring.update();
|
var degreesUpdated = this._degreesSpring.update();
|
||||||
|
|
||||||
|
this._updateTilesForViewport();
|
||||||
|
|
||||||
if (xUpdated || yUpdated || scaleUpdated || degreesUpdated) {
|
if (xUpdated || yUpdated || scaleUpdated || degreesUpdated) {
|
||||||
this._updateForScale();
|
this._updateForScale();
|
||||||
this._raiseBoundsChange();
|
this._raiseBoundsChange();
|
||||||
@ -315,21 +318,6 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the TiledImage to its Drawer.
|
|
||||||
*/
|
|
||||||
draw: function() {
|
|
||||||
if (this.opacity !== 0 || this._preload) {
|
|
||||||
this._midDraw = true;
|
|
||||||
this._updateViewport();
|
|
||||||
this._midDraw = false;
|
|
||||||
}
|
|
||||||
// Images with opacity 0 should not need to be drawn in future. this._needsDraw = false is set in this._updateViewport() for other images.
|
|
||||||
else {
|
|
||||||
this._needsDraw = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destroy the TiledImage (unload current loaded tiles).
|
* Destroy the TiledImage (unload current loaded tiles).
|
||||||
*/
|
*/
|
||||||
@ -767,7 +755,6 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
* ]
|
* ]
|
||||||
*/
|
*/
|
||||||
setCroppingPolygons: function( polygons ) {
|
setCroppingPolygons: function( polygons ) {
|
||||||
|
|
||||||
var isXYObject = function(obj) {
|
var isXYObject = function(obj) {
|
||||||
return obj instanceof $.Point || (typeof obj.x === 'number' && typeof obj.y === 'number');
|
return obj instanceof $.Point || (typeof obj.x === 'number' && typeof obj.y === 'number');
|
||||||
};
|
};
|
||||||
@ -793,10 +780,11 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
this._croppingPolygons = polygons.map(function(polygon){
|
this._croppingPolygons = polygons.map(function(polygon){
|
||||||
return objectToSimpleXYObject(polygon);
|
return objectToSimpleXYObject(polygon);
|
||||||
});
|
});
|
||||||
|
this._needsDraw = true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
$.console.error('[TiledImage.setCroppingPolygons] Cropping polygon format not supported');
|
$.console.error('[TiledImage.setCroppingPolygons] Cropping polygon format not supported');
|
||||||
$.console.error(e);
|
$.console.error(e);
|
||||||
this._croppingPolygons = null;
|
this.resetCroppingPolygons();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -806,6 +794,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
*/
|
*/
|
||||||
resetCroppingPolygons: function() {
|
resetCroppingPolygons: function() {
|
||||||
this._croppingPolygons = null;
|
this._croppingPolygons = null;
|
||||||
|
this._needsDraw = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1007,6 +996,24 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
this._raiseBoundsChange();
|
this._raiseBoundsChange();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the region of this tiled image that falls within the viewport.
|
||||||
|
* @returns OpenSeadragon.Rect
|
||||||
|
*/
|
||||||
|
getDrawArea: function(){
|
||||||
|
|
||||||
|
var drawArea = this._viewportToTiledImageRectangle(
|
||||||
|
this.viewport.getBoundsWithMargins(true));
|
||||||
|
|
||||||
|
if (!this.wrapHorizontal && !this.wrapVertical) {
|
||||||
|
var tiledImageBounds = this._viewportToTiledImageRectangle(
|
||||||
|
this.getClippedBounds(true));
|
||||||
|
drawArea = drawArea.intersection(tiledImageBounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
return drawArea;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the point around which this tiled image is rotated
|
* Get the point around which this tiled image is rotated
|
||||||
* @private
|
* @private
|
||||||
@ -1029,6 +1036,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
* @fires OpenSeadragon.TiledImage.event:composite-operation-change
|
* @fires OpenSeadragon.TiledImage.event:composite-operation-change
|
||||||
*/
|
*/
|
||||||
setCompositeOperation: function(compositeOperation) {
|
setCompositeOperation: function(compositeOperation) {
|
||||||
|
var _this = this;
|
||||||
if (compositeOperation === this.compositeOperation) {
|
if (compositeOperation === this.compositeOperation) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1048,6 +1056,21 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
this.raiseEvent('composite-operation-change', {
|
this.raiseEvent('composite-operation-change', {
|
||||||
compositeOperation: this.compositeOperation
|
compositeOperation: this.compositeOperation
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raised when a TiledImage's opacity is changed.
|
||||||
|
* @event composite-operation-change
|
||||||
|
* @memberOf OpenSeadragon.TiledImage
|
||||||
|
* @type {object}
|
||||||
|
* @property {String} compositeOperation - The new compositeOperation value.
|
||||||
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the
|
||||||
|
* Viewer which raised the event.
|
||||||
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
||||||
|
*/
|
||||||
|
this.viewer.raiseEvent('composite-operation-change', {
|
||||||
|
compositeOperation: _this.compositeOperation,
|
||||||
|
tiledImage: _this
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1215,50 +1238,34 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
getTileInfoForDrawing: function(){
|
||||||
* @private
|
return this._tilesToDraw;
|
||||||
* @inner
|
},
|
||||||
* Pretty much every other line in this needs to be documented so it's clear
|
|
||||||
* how each piece of this routine contributes to the drawing process. That's
|
|
||||||
* why there are so many TODO's inside this function.
|
|
||||||
*/
|
|
||||||
_updateViewport: function() {
|
|
||||||
this._needsDraw = false;
|
|
||||||
this._tilesLoading = 0;
|
|
||||||
this.loadingCoverage = {};
|
|
||||||
|
|
||||||
// Reset tile's internal drawn state
|
|
||||||
while (this.lastDrawn.length > 0) {
|
|
||||||
var tile = this.lastDrawn.pop();
|
|
||||||
tile.beingDrawn = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var viewport = this.viewport;
|
|
||||||
var drawArea = this._viewportToTiledImageRectangle(
|
|
||||||
viewport.getBoundsWithMargins(true));
|
|
||||||
|
|
||||||
if (!this.wrapHorizontal && !this.wrapVertical) {
|
|
||||||
var tiledImageBounds = this._viewportToTiledImageRectangle(
|
|
||||||
this.getClippedBounds(true));
|
|
||||||
drawArea = drawArea.intersection(tiledImageBounds);
|
|
||||||
if (drawArea === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
_updateTilesForViewport: function(){
|
||||||
var levelsInterval = this._getLevelsInterval();
|
var levelsInterval = this._getLevelsInterval();
|
||||||
var lowestLevel = levelsInterval.lowestLevel;
|
var lowestLevel = levelsInterval.lowestLevel;
|
||||||
var highestLevel = levelsInterval.highestLevel;
|
var highestLevel = levelsInterval.highestLevel;
|
||||||
var bestTile = null;
|
var bestTile = null;
|
||||||
var haveDrawn = false;
|
var haveDrawn = false;
|
||||||
|
var drawArea = this.getDrawArea();
|
||||||
var currentTime = $.now();
|
var currentTime = $.now();
|
||||||
|
|
||||||
|
this._tilesToDraw = [];
|
||||||
|
this._tilesLoading = 0;
|
||||||
|
this.loadingCoverage = {};
|
||||||
|
|
||||||
|
if(!drawArea){
|
||||||
|
this._needsDraw = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Update any level that will be drawn
|
// Update any level that will be drawn
|
||||||
for (var level = highestLevel; level >= lowestLevel; level--) {
|
for (var level = highestLevel; level >= lowestLevel; level--) {
|
||||||
var drawLevel = false;
|
var drawLevel = false;
|
||||||
|
|
||||||
//Avoid calculations for draw if we have already drawn this
|
//Avoid calculations for draw if we have already drawn this
|
||||||
var currentRenderPixelRatio = viewport.deltaPixelsFromPointsNoRotate(
|
var currentRenderPixelRatio = this.viewport.deltaPixelsFromPointsNoRotate(
|
||||||
this.source.getPixelRatio(level),
|
this.source.getPixelRatio(level),
|
||||||
true
|
true
|
||||||
).x * this._scaleSpring.current.value;
|
).x * this._scaleSpring.current.value;
|
||||||
@ -1272,12 +1279,12 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Perform calculations for draw if we haven't drawn this
|
//Perform calculations for draw if we haven't drawn this
|
||||||
var targetRenderPixelRatio = viewport.deltaPixelsFromPointsNoRotate(
|
var targetRenderPixelRatio = this.viewport.deltaPixelsFromPointsNoRotate(
|
||||||
this.source.getPixelRatio(level),
|
this.source.getPixelRatio(level),
|
||||||
false
|
false
|
||||||
).x * this._scaleSpring.current.value;
|
).x * this._scaleSpring.current.value;
|
||||||
|
|
||||||
var targetZeroRatio = viewport.deltaPixelsFromPointsNoRotate(
|
var targetZeroRatio = this.viewport.deltaPixelsFromPointsNoRotate(
|
||||||
this.source.getPixelRatio(
|
this.source.getPixelRatio(
|
||||||
Math.max(
|
Math.max(
|
||||||
this.source.getClosestLevel(),
|
this.source.getClosestLevel(),
|
||||||
@ -1294,7 +1301,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Update the level and keep track of 'best' tile to load
|
// Update the level and keep track of 'best' tile to load
|
||||||
bestTile = this._updateLevel(
|
var result = this._updateLevel(
|
||||||
haveDrawn,
|
haveDrawn,
|
||||||
drawLevel,
|
drawLevel,
|
||||||
level,
|
level,
|
||||||
@ -1305,6 +1312,21 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
bestTile
|
bestTile
|
||||||
);
|
);
|
||||||
|
|
||||||
|
bestTile = result.best;
|
||||||
|
var tiles = result.tiles;
|
||||||
|
var makeTileInfoObject = (function(level, levelOpacity, currentTime){
|
||||||
|
return function(tile){
|
||||||
|
return {
|
||||||
|
tile: tile,
|
||||||
|
level: level,
|
||||||
|
levelOpacity: levelOpacity,
|
||||||
|
currentTime: currentTime
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})(level, levelOpacity, currentTime);
|
||||||
|
|
||||||
|
this._tilesToDraw = this._tilesToDraw.concat(tiles.map(makeTileInfoObject));
|
||||||
|
|
||||||
// Stop the loop if lower-res tiles would all be covered by
|
// Stop the loop if lower-res tiles would all be covered by
|
||||||
// already drawn tiles
|
// already drawn tiles
|
||||||
if (this._providesCoverage(this.coverage, level)) {
|
if (this._providesCoverage(this.coverage, level)) {
|
||||||
@ -1312,11 +1334,6 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform the actual drawing
|
|
||||||
|
|
||||||
this._drawTiles(this.lastDrawn);
|
|
||||||
|
|
||||||
|
|
||||||
// Load the new 'best' tile
|
// Load the new 'best' tile
|
||||||
if (bestTile && !bestTile.context2D) {
|
if (bestTile && !bestTile.context2D) {
|
||||||
this._loadTile(bestTile, currentTime);
|
this._loadTile(bestTile, currentTime);
|
||||||
@ -1325,47 +1342,9 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
} else {
|
} else {
|
||||||
this._setFullyLoaded(this._tilesLoading === 0);
|
this._setFullyLoaded(this._tilesLoading === 0);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
// private
|
|
||||||
_getCornerTiles: function(level, topLeftBound, bottomRightBound) {
|
|
||||||
var leftX;
|
|
||||||
var rightX;
|
|
||||||
if (this.wrapHorizontal) {
|
|
||||||
leftX = $.positiveModulo(topLeftBound.x, 1);
|
|
||||||
rightX = $.positiveModulo(bottomRightBound.x, 1);
|
|
||||||
} else {
|
|
||||||
leftX = Math.max(0, topLeftBound.x);
|
|
||||||
rightX = Math.min(1, bottomRightBound.x);
|
|
||||||
}
|
|
||||||
var topY;
|
|
||||||
var bottomY;
|
|
||||||
var aspectRatio = 1 / this.source.aspectRatio;
|
|
||||||
if (this.wrapVertical) {
|
|
||||||
topY = $.positiveModulo(topLeftBound.y, aspectRatio);
|
|
||||||
bottomY = $.positiveModulo(bottomRightBound.y, aspectRatio);
|
|
||||||
} else {
|
|
||||||
topY = Math.max(0, topLeftBound.y);
|
|
||||||
bottomY = Math.min(aspectRatio, bottomRightBound.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
var topLeftTile = this.source.getTileAtPoint(level, new $.Point(leftX, topY));
|
// return bestTile;
|
||||||
var bottomRightTile = this.source.getTileAtPoint(level, new $.Point(rightX, bottomY));
|
|
||||||
var numTiles = this.source.getNumTiles(level);
|
|
||||||
|
|
||||||
if (this.wrapHorizontal) {
|
|
||||||
topLeftTile.x += numTiles.x * Math.floor(topLeftBound.x);
|
|
||||||
bottomRightTile.x += numTiles.x * Math.floor(bottomRightBound.x);
|
|
||||||
}
|
|
||||||
if (this.wrapVertical) {
|
|
||||||
topLeftTile.y += numTiles.y * Math.floor(topLeftBound.y / aspectRatio);
|
|
||||||
bottomRightTile.y += numTiles.y * Math.floor(bottomRightBound.y / aspectRatio);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
topLeft: topLeftTile,
|
|
||||||
bottomRight: bottomRightTile,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1378,7 +1357,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
* @param {Number} levelVisibility
|
* @param {Number} levelVisibility
|
||||||
* @param {OpenSeadragon.Rect} drawArea
|
* @param {OpenSeadragon.Rect} drawArea
|
||||||
* @param {Number} currentTime
|
* @param {Number} currentTime
|
||||||
* @param {OpenSeadragon.Tile} best - The current "best" tile to draw.
|
* @param {Object} result Dictionary {best: OpenSeadragon.Tile - the current "best" tile to draw, tiles: Array(OpenSeadragon.Tile) - the updated tiles}.
|
||||||
*/
|
*/
|
||||||
_updateLevel: function(haveDrawn, drawLevel, level, levelOpacity,
|
_updateLevel: function(haveDrawn, drawLevel, level, levelOpacity,
|
||||||
levelVisibility, drawArea, currentTime, best) {
|
levelVisibility, drawArea, currentTime, best) {
|
||||||
@ -1443,7 +1422,9 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
bottomRightTile.x = Math.min(bottomRightTile.x, numberOfTiles.x - 1);
|
bottomRightTile.x = Math.min(bottomRightTile.x, numberOfTiles.x - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var numTiles = Math.max(0, (bottomRightTile.x - topLeftTile.x) * (bottomRightTile.y - topLeftTile.y));
|
||||||
|
var tiles = new Array(numTiles);
|
||||||
|
var tileIndex = 0;
|
||||||
for (var x = topLeftTile.x; x <= bottomRightTile.x; x++) {
|
for (var x = topLeftTile.x; x <= bottomRightTile.x; x++) {
|
||||||
for (var y = topLeftTile.y; y <= bottomRightTile.y; y++) {
|
for (var y = topLeftTile.y; y <= bottomRightTile.y; y++) {
|
||||||
|
|
||||||
@ -1460,22 +1441,74 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
best = this._updateTile(
|
var result = this._updateTile(
|
||||||
drawLevel,
|
drawLevel,
|
||||||
haveDrawn,
|
haveDrawn,
|
||||||
flippedX, y,
|
flippedX, y,
|
||||||
level,
|
level,
|
||||||
levelOpacity,
|
|
||||||
levelVisibility,
|
levelVisibility,
|
||||||
viewportCenter,
|
viewportCenter,
|
||||||
numberOfTiles,
|
numberOfTiles,
|
||||||
currentTime,
|
currentTime,
|
||||||
best
|
best
|
||||||
);
|
);
|
||||||
|
best = result.best;
|
||||||
|
tiles[tileIndex] = result.tile;
|
||||||
|
tileIndex += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return best;
|
return {
|
||||||
|
best: best,
|
||||||
|
tiles: tiles
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @inner
|
||||||
|
* @param {OpenSeadragon.Tile} tile
|
||||||
|
* @param {Boolean} overlap
|
||||||
|
* @param {OpenSeadragon.Viewport} viewport
|
||||||
|
* @param {OpenSeadragon.Point} viewportCenter
|
||||||
|
* @param {Number} levelVisibility
|
||||||
|
*/
|
||||||
|
_positionTile: function( tile, overlap, viewport, viewportCenter, levelVisibility ){
|
||||||
|
var boundsTL = tile.bounds.getTopLeft();
|
||||||
|
|
||||||
|
boundsTL.x *= this._scaleSpring.current.value;
|
||||||
|
boundsTL.y *= this._scaleSpring.current.value;
|
||||||
|
boundsTL.x += this._xSpring.current.value;
|
||||||
|
boundsTL.y += this._ySpring.current.value;
|
||||||
|
|
||||||
|
var boundsSize = tile.bounds.getSize();
|
||||||
|
|
||||||
|
boundsSize.x *= this._scaleSpring.current.value;
|
||||||
|
boundsSize.y *= this._scaleSpring.current.value;
|
||||||
|
|
||||||
|
var positionC = viewport.pixelFromPointNoRotate(boundsTL, true),
|
||||||
|
positionT = viewport.pixelFromPointNoRotate(boundsTL, false),
|
||||||
|
sizeC = viewport.deltaPixelsFromPointsNoRotate(boundsSize, true),
|
||||||
|
sizeT = viewport.deltaPixelsFromPointsNoRotate(boundsSize, false),
|
||||||
|
tileCenter = positionT.plus( sizeT.divide( 2 ) ),
|
||||||
|
tileSquaredDistance = viewportCenter.squaredDistanceTo( tileCenter );
|
||||||
|
|
||||||
|
if ( !overlap ) {
|
||||||
|
sizeC = sizeC.plus( new $.Point( 1, 1 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tile.isRightMost && this.wrapHorizontal) {
|
||||||
|
sizeC.x += 0.75; // Otherwise Firefox and Safari show seams
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tile.isBottomMost && this.wrapVertical) {
|
||||||
|
sizeC.y += 0.75; // Otherwise Firefox and Safari show seams
|
||||||
|
}
|
||||||
|
|
||||||
|
tile.position = positionC;
|
||||||
|
tile.size = sizeC;
|
||||||
|
tile.squaredDistance = tileSquaredDistance;
|
||||||
|
tile.visibility = levelVisibility;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1487,14 +1520,13 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
* @param {Number} x
|
* @param {Number} x
|
||||||
* @param {Number} y
|
* @param {Number} y
|
||||||
* @param {Number} level
|
* @param {Number} level
|
||||||
* @param {Number} levelOpacity
|
|
||||||
* @param {Number} levelVisibility
|
* @param {Number} levelVisibility
|
||||||
* @param {OpenSeadragon.Point} viewportCenter
|
* @param {OpenSeadragon.Point} viewportCenter
|
||||||
* @param {Number} numberOfTiles
|
* @param {Number} numberOfTiles
|
||||||
* @param {Number} currentTime
|
* @param {Number} currentTime
|
||||||
* @param {OpenSeadragon.Tile} best - The current "best" tile to draw.
|
* @param {OpenSeadragon.Tile} best - The current "best" tile to draw.
|
||||||
*/
|
*/
|
||||||
_updateTile: function( haveDrawn, drawLevel, x, y, level, levelOpacity,
|
_updateTile: function( haveDrawn, drawLevel, x, y, level,
|
||||||
levelVisibility, viewportCenter, numberOfTiles, currentTime, best){
|
levelVisibility, viewportCenter, numberOfTiles, currentTime, best){
|
||||||
|
|
||||||
var tile = this._getTile(
|
var tile = this._getTile(
|
||||||
@ -1531,7 +1563,9 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
this._setCoverage(this.loadingCoverage, level, x, y, loadingCoverage);
|
this._setCoverage(this.loadingCoverage, level, x, y, loadingCoverage);
|
||||||
|
|
||||||
if ( !tile.exists ) {
|
if ( !tile.exists ) {
|
||||||
return best;
|
return {
|
||||||
|
best: best
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( haveDrawn && !drawTile ) {
|
if ( haveDrawn && !drawTile ) {
|
||||||
@ -1543,7 +1577,9 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ( !drawTile ) {
|
if ( !drawTile ) {
|
||||||
return best;
|
return {
|
||||||
|
best: best
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
this._positionTile(
|
this._positionTile(
|
||||||
@ -1565,26 +1601,58 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( tile.loaded ) {
|
if ( tile.loading ) {
|
||||||
var needsDraw = this._blendTile(
|
|
||||||
tile,
|
|
||||||
x, y,
|
|
||||||
level,
|
|
||||||
levelOpacity,
|
|
||||||
currentTime
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( needsDraw ) {
|
|
||||||
this._needsDraw = true;
|
|
||||||
}
|
|
||||||
} else if ( tile.loading ) {
|
|
||||||
// the tile is already in the download queue
|
// the tile is already in the download queue
|
||||||
this._tilesLoading++;
|
this._tilesLoading++;
|
||||||
} else if (!loadingCoverage) {
|
} else if (!loadingCoverage) {
|
||||||
best = this._compareTiles( best, tile );
|
best = this._compareTiles( best, tile );
|
||||||
}
|
}
|
||||||
|
|
||||||
return best;
|
return {
|
||||||
|
best: best,
|
||||||
|
tile: tile
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
// private
|
||||||
|
_getCornerTiles: function(level, topLeftBound, bottomRightBound) {
|
||||||
|
var leftX;
|
||||||
|
var rightX;
|
||||||
|
if (this.wrapHorizontal) {
|
||||||
|
leftX = $.positiveModulo(topLeftBound.x, 1);
|
||||||
|
rightX = $.positiveModulo(bottomRightBound.x, 1);
|
||||||
|
} else {
|
||||||
|
leftX = Math.max(0, topLeftBound.x);
|
||||||
|
rightX = Math.min(1, bottomRightBound.x);
|
||||||
|
}
|
||||||
|
var topY;
|
||||||
|
var bottomY;
|
||||||
|
var aspectRatio = 1 / this.source.aspectRatio;
|
||||||
|
if (this.wrapVertical) {
|
||||||
|
topY = $.positiveModulo(topLeftBound.y, aspectRatio);
|
||||||
|
bottomY = $.positiveModulo(bottomRightBound.y, aspectRatio);
|
||||||
|
} else {
|
||||||
|
topY = Math.max(0, topLeftBound.y);
|
||||||
|
bottomY = Math.min(aspectRatio, bottomRightBound.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
var topLeftTile = this.source.getTileAtPoint(level, new $.Point(leftX, topY));
|
||||||
|
var bottomRightTile = this.source.getTileAtPoint(level, new $.Point(rightX, bottomY));
|
||||||
|
var numTiles = this.source.getNumTiles(level);
|
||||||
|
|
||||||
|
if (this.wrapHorizontal) {
|
||||||
|
topLeftTile.x += numTiles.x * Math.floor(topLeftBound.x);
|
||||||
|
bottomRightTile.x += numTiles.x * Math.floor(bottomRightBound.x);
|
||||||
|
}
|
||||||
|
if (this.wrapVertical) {
|
||||||
|
topLeftTile.y += numTiles.y * Math.floor(topLeftBound.y / aspectRatio);
|
||||||
|
bottomRightTile.y += numTiles.y * Math.floor(bottomRightBound.y / aspectRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
topLeft: topLeftTile,
|
||||||
|
bottomRight: bottomRightTile,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1876,99 +1944,6 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
fallbackCompletion();
|
fallbackCompletion();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @inner
|
|
||||||
* @param {OpenSeadragon.Tile} tile
|
|
||||||
* @param {Boolean} overlap
|
|
||||||
* @param {OpenSeadragon.Viewport} viewport
|
|
||||||
* @param {OpenSeadragon.Point} viewportCenter
|
|
||||||
* @param {Number} levelVisibility
|
|
||||||
*/
|
|
||||||
_positionTile: function( tile, overlap, viewport, viewportCenter, levelVisibility ){
|
|
||||||
var boundsTL = tile.bounds.getTopLeft();
|
|
||||||
|
|
||||||
boundsTL.x *= this._scaleSpring.current.value;
|
|
||||||
boundsTL.y *= this._scaleSpring.current.value;
|
|
||||||
boundsTL.x += this._xSpring.current.value;
|
|
||||||
boundsTL.y += this._ySpring.current.value;
|
|
||||||
|
|
||||||
var boundsSize = tile.bounds.getSize();
|
|
||||||
|
|
||||||
boundsSize.x *= this._scaleSpring.current.value;
|
|
||||||
boundsSize.y *= this._scaleSpring.current.value;
|
|
||||||
|
|
||||||
var positionC = viewport.pixelFromPointNoRotate(boundsTL, true),
|
|
||||||
positionT = viewport.pixelFromPointNoRotate(boundsTL, false),
|
|
||||||
sizeC = viewport.deltaPixelsFromPointsNoRotate(boundsSize, true),
|
|
||||||
sizeT = viewport.deltaPixelsFromPointsNoRotate(boundsSize, false),
|
|
||||||
tileCenter = positionT.plus( sizeT.divide( 2 ) ),
|
|
||||||
tileSquaredDistance = viewportCenter.squaredDistanceTo( tileCenter );
|
|
||||||
|
|
||||||
if ( !overlap ) {
|
|
||||||
sizeC = sizeC.plus( new $.Point( 1, 1 ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tile.isRightMost && this.wrapHorizontal) {
|
|
||||||
sizeC.x += 0.75; // Otherwise Firefox and Safari show seams
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tile.isBottomMost && this.wrapVertical) {
|
|
||||||
sizeC.y += 0.75; // Otherwise Firefox and Safari show seams
|
|
||||||
}
|
|
||||||
|
|
||||||
tile.position = positionC;
|
|
||||||
tile.size = sizeC;
|
|
||||||
tile.squaredDistance = tileSquaredDistance;
|
|
||||||
tile.visibility = levelVisibility;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @inner
|
|
||||||
* Updates the opacity of a tile according to the time it has been on screen
|
|
||||||
* to perform a fade-in.
|
|
||||||
* Updates coverage once a tile is fully opaque.
|
|
||||||
* Returns whether the fade-in has completed.
|
|
||||||
*
|
|
||||||
* @param {OpenSeadragon.Tile} tile
|
|
||||||
* @param {Number} x
|
|
||||||
* @param {Number} y
|
|
||||||
* @param {Number} level
|
|
||||||
* @param {Number} levelOpacity
|
|
||||||
* @param {Number} currentTime
|
|
||||||
* @returns {Boolean}
|
|
||||||
*/
|
|
||||||
_blendTile: function( tile, x, y, level, levelOpacity, currentTime ){
|
|
||||||
var blendTimeMillis = 1000 * this.blendTime,
|
|
||||||
deltaTime,
|
|
||||||
opacity;
|
|
||||||
|
|
||||||
if ( !tile.blendStart ) {
|
|
||||||
tile.blendStart = currentTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
deltaTime = currentTime - tile.blendStart;
|
|
||||||
opacity = blendTimeMillis ? Math.min( 1, deltaTime / ( blendTimeMillis ) ) : 1;
|
|
||||||
|
|
||||||
if ( this.alwaysBlend ) {
|
|
||||||
opacity *= levelOpacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
tile.opacity = opacity;
|
|
||||||
|
|
||||||
this.lastDrawn.push( tile );
|
|
||||||
|
|
||||||
if ( opacity === 1 ) {
|
|
||||||
this._setCoverage( this.coverage, level, x, y, true );
|
|
||||||
this._hasOpaqueTile = true;
|
|
||||||
} else if ( deltaTime < blendTimeMillis ) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
@ -1995,274 +1970,6 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
return previousBest;
|
return previousBest;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @inner
|
|
||||||
* Draws a TiledImage.
|
|
||||||
* @param {OpenSeadragon.Tile[]} lastDrawn - An unordered list of Tiles drawn last frame.
|
|
||||||
*/
|
|
||||||
_drawTiles: function( lastDrawn ) {
|
|
||||||
if (this.opacity === 0 || (lastDrawn.length === 0 && !this.placeholderFillStyle)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var tile = lastDrawn[0];
|
|
||||||
var useSketch;
|
|
||||||
|
|
||||||
if (tile) {
|
|
||||||
useSketch = this.opacity < 1 ||
|
|
||||||
(this.compositeOperation && this.compositeOperation !== 'source-over') ||
|
|
||||||
(!this._isBottomItem() &&
|
|
||||||
this.source.hasTransparency(tile.context2D, tile.getUrl(), tile.ajaxHeaders, tile.postData));
|
|
||||||
}
|
|
||||||
|
|
||||||
var sketchScale;
|
|
||||||
var sketchTranslate;
|
|
||||||
|
|
||||||
var zoom = this.viewport.getZoom(true);
|
|
||||||
var imageZoom = this.viewportToImageZoom(zoom);
|
|
||||||
|
|
||||||
if (lastDrawn.length > 1 &&
|
|
||||||
imageZoom > this.smoothTileEdgesMinZoom &&
|
|
||||||
!this.iOSDevice &&
|
|
||||||
this.getRotation(true) % 360 === 0 && // TODO: support tile edge smoothing with tiled image rotation (viewport rotation is not a problem).
|
|
||||||
this._drawer.viewer.viewport.getFlip() === false && // TODO: support tile edge smoothing with viewport flip (tiled image flip is not a problem).
|
|
||||||
$.supportsCanvas && this.viewer.useCanvas) {
|
|
||||||
// When zoomed in a lot (>100%) the tile edges are visible.
|
|
||||||
// So we have to composite them at ~100% and scale them up together.
|
|
||||||
// Note: Disabled on iOS devices per default as it causes a native crash
|
|
||||||
useSketch = true;
|
|
||||||
sketchScale = tile.getScaleForEdgeSmoothing();
|
|
||||||
sketchTranslate = tile.getTranslationForEdgeSmoothing(sketchScale,
|
|
||||||
this._drawer.getCanvasSize(false),
|
|
||||||
this._drawer.getCanvasSize(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
var bounds;
|
|
||||||
if (useSketch) {
|
|
||||||
if (!sketchScale) {
|
|
||||||
// Except when edge smoothing, we only clean the part of the
|
|
||||||
// sketch canvas we are going to use for performance reasons.
|
|
||||||
bounds = this.viewport.viewportToViewerElementRectangle(
|
|
||||||
this.getClippedBounds(true))
|
|
||||||
.getIntegerBoundingBox();
|
|
||||||
|
|
||||||
if(this._drawer.viewer.viewport.getFlip()) {
|
|
||||||
if (this.viewport.getRotation(true) % 360 !== 0 ||
|
|
||||||
this.getRotation(true) % 360 !== 0) {
|
|
||||||
bounds.x = this._drawer.viewer.container.clientWidth - (bounds.x + bounds.width);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bounds = bounds.times($.pixelDensityRatio);
|
|
||||||
}
|
|
||||||
this._drawer._clear(true, bounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
// When scaling, we must rotate only when blending the sketch canvas to
|
|
||||||
// avoid interpolation
|
|
||||||
if (!sketchScale) {
|
|
||||||
if (this.viewport.getRotation(true) % 360 !== 0) {
|
|
||||||
this._drawer._offsetForRotation({
|
|
||||||
degrees: this.viewport.getRotation(true),
|
|
||||||
useSketch: useSketch
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (this.getRotation(true) % 360 !== 0) {
|
|
||||||
this._drawer._offsetForRotation({
|
|
||||||
degrees: this.getRotation(true),
|
|
||||||
point: this.viewport.pixelFromPointNoRotate(
|
|
||||||
this._getRotationPoint(true), true),
|
|
||||||
useSketch: useSketch
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.viewport.getRotation(true) % 360 === 0 &&
|
|
||||||
this.getRotation(true) % 360 === 0) {
|
|
||||||
if(this._drawer.viewer.viewport.getFlip()) {
|
|
||||||
this._drawer._flip();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var usedClip = false;
|
|
||||||
if ( this._clip ) {
|
|
||||||
this._drawer.saveContext(useSketch);
|
|
||||||
|
|
||||||
var box = this.imageToViewportRectangle(this._clip, true);
|
|
||||||
box = box.rotate(-this.getRotation(true), this._getRotationPoint(true));
|
|
||||||
var clipRect = this._drawer.viewportToDrawerRectangle(box);
|
|
||||||
if (sketchScale) {
|
|
||||||
clipRect = clipRect.times(sketchScale);
|
|
||||||
}
|
|
||||||
if (sketchTranslate) {
|
|
||||||
clipRect = clipRect.translate(sketchTranslate);
|
|
||||||
}
|
|
||||||
this._drawer.setClip(clipRect, useSketch);
|
|
||||||
|
|
||||||
usedClip = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._croppingPolygons) {
|
|
||||||
var self = this;
|
|
||||||
this._drawer.saveContext(useSketch);
|
|
||||||
try {
|
|
||||||
var polygons = this._croppingPolygons.map(function (polygon) {
|
|
||||||
return polygon.map(function (coord) {
|
|
||||||
var point = self
|
|
||||||
.imageToViewportCoordinates(coord.x, coord.y, true)
|
|
||||||
.rotate(-self.getRotation(true), self._getRotationPoint(true));
|
|
||||||
var clipPoint = self._drawer.viewportCoordToDrawerCoord(point);
|
|
||||||
if (sketchScale) {
|
|
||||||
clipPoint = clipPoint.times(sketchScale);
|
|
||||||
}
|
|
||||||
if (sketchTranslate) {
|
|
||||||
clipPoint = clipPoint.plus(sketchTranslate);
|
|
||||||
}
|
|
||||||
return clipPoint;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
this._drawer.clipWithPolygons(polygons, useSketch);
|
|
||||||
} catch (e) {
|
|
||||||
$.console.error(e);
|
|
||||||
}
|
|
||||||
usedClip = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( this.placeholderFillStyle && this._hasOpaqueTile === false ) {
|
|
||||||
var placeholderRect = this._drawer.viewportToDrawerRectangle(this.getBounds(true));
|
|
||||||
if (sketchScale) {
|
|
||||||
placeholderRect = placeholderRect.times(sketchScale);
|
|
||||||
}
|
|
||||||
if (sketchTranslate) {
|
|
||||||
placeholderRect = placeholderRect.translate(sketchTranslate);
|
|
||||||
}
|
|
||||||
|
|
||||||
var fillStyle = null;
|
|
||||||
if ( typeof this.placeholderFillStyle === "function" ) {
|
|
||||||
fillStyle = this.placeholderFillStyle(this, this._drawer.context);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
fillStyle = this.placeholderFillStyle;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._drawer.drawRectangle(placeholderRect, fillStyle, useSketch);
|
|
||||||
}
|
|
||||||
|
|
||||||
var subPixelRoundingRule = determineSubPixelRoundingRule(this.subPixelRoundingForTransparency);
|
|
||||||
|
|
||||||
var shouldRoundPositionAndSize = false;
|
|
||||||
|
|
||||||
if (subPixelRoundingRule === $.SUBPIXEL_ROUNDING_OCCURRENCES.ALWAYS) {
|
|
||||||
shouldRoundPositionAndSize = true;
|
|
||||||
} else if (subPixelRoundingRule === $.SUBPIXEL_ROUNDING_OCCURRENCES.ONLY_AT_REST) {
|
|
||||||
var isAnimating = this.viewer && this.viewer.isAnimating();
|
|
||||||
shouldRoundPositionAndSize = !isAnimating;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = lastDrawn.length - 1; i >= 0; i--) {
|
|
||||||
tile = lastDrawn[ i ];
|
|
||||||
this._drawer.drawTile( tile, this._drawingHandler, useSketch, sketchScale,
|
|
||||||
sketchTranslate, shouldRoundPositionAndSize, this.source );
|
|
||||||
tile.beingDrawn = true;
|
|
||||||
|
|
||||||
if( this.viewer ){
|
|
||||||
/**
|
|
||||||
* <em>- Needs documentation -</em>
|
|
||||||
*
|
|
||||||
* @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: this,
|
|
||||||
tile: tile
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( usedClip ) {
|
|
||||||
this._drawer.restoreContext( useSketch );
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sketchScale) {
|
|
||||||
if (this.getRotation(true) % 360 !== 0) {
|
|
||||||
this._drawer._restoreRotationChanges(useSketch);
|
|
||||||
}
|
|
||||||
if (this.viewport.getRotation(true) % 360 !== 0) {
|
|
||||||
this._drawer._restoreRotationChanges(useSketch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (useSketch) {
|
|
||||||
if (sketchScale) {
|
|
||||||
if (this.viewport.getRotation(true) % 360 !== 0) {
|
|
||||||
this._drawer._offsetForRotation({
|
|
||||||
degrees: this.viewport.getRotation(true),
|
|
||||||
useSketch: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (this.getRotation(true) % 360 !== 0) {
|
|
||||||
this._drawer._offsetForRotation({
|
|
||||||
degrees: this.getRotation(true),
|
|
||||||
point: this.viewport.pixelFromPointNoRotate(
|
|
||||||
this._getRotationPoint(true), true),
|
|
||||||
useSketch: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._drawer.blendSketch({
|
|
||||||
opacity: this.opacity,
|
|
||||||
scale: sketchScale,
|
|
||||||
translate: sketchTranslate,
|
|
||||||
compositeOperation: this.compositeOperation,
|
|
||||||
bounds: bounds
|
|
||||||
});
|
|
||||||
if (sketchScale) {
|
|
||||||
if (this.getRotation(true) % 360 !== 0) {
|
|
||||||
this._drawer._restoreRotationChanges(false);
|
|
||||||
}
|
|
||||||
if (this.viewport.getRotation(true) % 360 !== 0) {
|
|
||||||
this._drawer._restoreRotationChanges(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sketchScale) {
|
|
||||||
if (this.viewport.getRotation(true) % 360 === 0 &&
|
|
||||||
this.getRotation(true) % 360 === 0) {
|
|
||||||
if(this._drawer.viewer.viewport.getFlip()) {
|
|
||||||
this._drawer._flip();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this._drawDebugInfo( lastDrawn );
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @inner
|
|
||||||
* Draws special debug information for a TiledImage if in debug mode.
|
|
||||||
* @param {OpenSeadragon.Tile[]} lastDrawn - An unordered list of Tiles drawn last frame.
|
|
||||||
*/
|
|
||||||
_drawDebugInfo: function( lastDrawn ) {
|
|
||||||
if( this.debugMode ) {
|
|
||||||
for ( var i = lastDrawn.length - 1; i >= 0; i-- ) {
|
|
||||||
var tile = lastDrawn[ i ];
|
|
||||||
try {
|
|
||||||
this._drawer.drawDebugInfo(tile, lastDrawn.length, i, this);
|
|
||||||
} catch(e) {
|
|
||||||
$.console.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @inner
|
* @inner
|
||||||
@ -2381,71 +2088,5 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @inner
|
|
||||||
* Defines the value for subpixel rounding to fallback to in case of missing or
|
|
||||||
* invalid value.
|
|
||||||
*/
|
|
||||||
var DEFAULT_SUBPIXEL_ROUNDING_RULE = $.SUBPIXEL_ROUNDING_OCCURRENCES.NEVER;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @inner
|
|
||||||
* Checks whether the input value is an invalid subpixel rounding enum value.
|
|
||||||
*
|
|
||||||
* @param {SUBPIXEL_ROUNDING_OCCURRENCES} value - The subpixel rounding enum value to check.
|
|
||||||
* @returns {Boolean} Returns true if the input value is none of the expected
|
|
||||||
* {@link SUBPIXEL_ROUNDING_OCCURRENCES.ALWAYS}, {@link SUBPIXEL_ROUNDING_OCCURRENCES.ONLY_AT_REST} or {@link SUBPIXEL_ROUNDING_OCCURRENCES.NEVER} value.
|
|
||||||
*/
|
|
||||||
function isSubPixelRoundingRuleUnknown(value) {
|
|
||||||
return value !== $.SUBPIXEL_ROUNDING_OCCURRENCES.ALWAYS &&
|
|
||||||
value !== $.SUBPIXEL_ROUNDING_OCCURRENCES.ONLY_AT_REST &&
|
|
||||||
value !== $.SUBPIXEL_ROUNDING_OCCURRENCES.NEVER;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @inner
|
|
||||||
* Ensures the returned value is always a valid subpixel rounding enum value,
|
|
||||||
* defaulting to {@link SUBPIXEL_ROUNDING_OCCURRENCES.NEVER} if input is missing or invalid.
|
|
||||||
*
|
|
||||||
* @param {SUBPIXEL_ROUNDING_OCCURRENCES} value - The subpixel rounding enum value to normalize.
|
|
||||||
* @returns {SUBPIXEL_ROUNDING_OCCURRENCES} Returns a valid subpixel rounding enum value.
|
|
||||||
*/
|
|
||||||
function normalizeSubPixelRoundingRule(value) {
|
|
||||||
if (isSubPixelRoundingRuleUnknown(value)) {
|
|
||||||
return DEFAULT_SUBPIXEL_ROUNDING_RULE;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @inner
|
|
||||||
* Ensures the returned value is always a valid subpixel rounding enum value,
|
|
||||||
* defaulting to 'NEVER' if input is missing or invalid.
|
|
||||||
*
|
|
||||||
* @param {Object} subPixelRoundingRules - A subpixel rounding enum values dictionary [{@link BROWSERS}] --> {@link SUBPIXEL_ROUNDING_OCCURRENCES}.
|
|
||||||
* @returns {SUBPIXEL_ROUNDING_OCCURRENCES} Returns the determined subpixel rounding enum value for the
|
|
||||||
* current browser.
|
|
||||||
*/
|
|
||||||
function determineSubPixelRoundingRule(subPixelRoundingRules) {
|
|
||||||
if (typeof subPixelRoundingRules === 'number') {
|
|
||||||
return normalizeSubPixelRoundingRule(subPixelRoundingRules);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!subPixelRoundingRules || !$.Browser) {
|
|
||||||
return DEFAULT_SUBPIXEL_ROUNDING_RULE;
|
|
||||||
}
|
|
||||||
|
|
||||||
var subPixelRoundingRule = subPixelRoundingRules[$.Browser.vendor];
|
|
||||||
|
|
||||||
if (isSubPixelRoundingRuleUnknown(subPixelRoundingRule)) {
|
|
||||||
subPixelRoundingRule = subPixelRoundingRules['*'];
|
|
||||||
}
|
|
||||||
|
|
||||||
return normalizeSubPixelRoundingRule(subPixelRoundingRule);
|
|
||||||
}
|
|
||||||
|
|
||||||
}( OpenSeadragon ));
|
}( OpenSeadragon ));
|
||||||
|
@ -368,7 +368,10 @@ $.TileSource.prototype = {
|
|||||||
getTileAtPoint: function(level, point) {
|
getTileAtPoint: function(level, point) {
|
||||||
var validPoint = point.x >= 0 && point.x <= 1 &&
|
var validPoint = point.x >= 0 && point.x <= 1 &&
|
||||||
point.y >= 0 && point.y <= 1 / this.aspectRatio;
|
point.y >= 0 && point.y <= 1 / this.aspectRatio;
|
||||||
$.console.assert(validPoint, "[TileSource.getTileAtPoint] must be called with a valid point.");
|
// $.console.assert(validPoint, "[TileSource.getTileAtPoint] must be called with a valid point.");
|
||||||
|
if(!validPoint){
|
||||||
|
$.console.warn("[TileSource.getTileAtPoint] called with an invalid point.");
|
||||||
|
}
|
||||||
|
|
||||||
var widthScaled = this.dimensions.x * this.getLevelScale(level);
|
var widthScaled = this.dimensions.x * this.getLevelScale(level);
|
||||||
var pixelX = point.x * widthScaled;
|
var pixelX = point.x * widthScaled;
|
||||||
|
@ -419,12 +419,29 @@ $.Viewer = function( options ) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Create the drawer
|
// Create the drawer
|
||||||
this.drawer = new $.Drawer({
|
if(this.customDrawer){
|
||||||
viewer: this,
|
if(this.customDrawer.prototype.isOpenSeadragonDrawer){
|
||||||
viewport: this.viewport,
|
var Drawer = this.customDrawer;
|
||||||
element: this.canvas,
|
this.drawer = new Drawer({
|
||||||
debugGridColor: this.debugGridColor
|
viewer: this,
|
||||||
});
|
viewport: this.viewport,
|
||||||
|
element: this.canvas,
|
||||||
|
debugGridColor: this.debugGridColor
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// $.console.error('Viewer option customDrawer must derive from OpenSeadragon.DrawerBase');
|
||||||
|
throw('Viewer option customDrawer must derive from OpenSeadragon.DrawerBase');
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.drawer = new $.Drawer({
|
||||||
|
viewer: this,
|
||||||
|
viewport: this.viewport,
|
||||||
|
element: this.canvas,
|
||||||
|
debugGridColor: this.debugGridColor
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Overlay container
|
// Overlay container
|
||||||
this.overlaysContainer = $.makeNeutralElement( "div" );
|
this.overlaysContainer = $.makeNeutralElement( "div" );
|
||||||
|
@ -257,7 +257,7 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
|
|||||||
*/
|
*/
|
||||||
draw: function() {
|
draw: function() {
|
||||||
for ( var i = 0; i < this._items.length; i++ ) {
|
for ( var i = 0; i < this._items.length; i++ ) {
|
||||||
this._items[i].draw();
|
this.viewer.drawer.draw(this._items[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._needsDraw = false;
|
this._needsDraw = false;
|
||||||
|
796
test/demo/threejsdrawer.js
Normal file
796
test/demo/threejsdrawer.js
Normal file
@ -0,0 +1,796 @@
|
|||||||
|
// import 'https://cdnjs.cloudflare.com/ajax/libs/three.js/0.149.0/three.min.js';
|
||||||
|
import '../lib/three.js';
|
||||||
|
const THREE = window.THREE;
|
||||||
|
|
||||||
|
export class ThreeJSDrawer extends OpenSeadragon.DrawerBase{
|
||||||
|
constructor(options){
|
||||||
|
super(options);
|
||||||
|
let _this = this;
|
||||||
|
|
||||||
|
// this.viewer set by parent constructor
|
||||||
|
// this.canvas set by parent constructor, created and appended to the viewer container element
|
||||||
|
this._camera = null;
|
||||||
|
this._currentImages = [];
|
||||||
|
this._renderer = null;
|
||||||
|
|
||||||
|
this._tileMap = {};
|
||||||
|
this._tiledImageMap = {};
|
||||||
|
this._uuid = generateUUID(); // to use for reference mapping
|
||||||
|
|
||||||
|
this._renderingContinously = false;
|
||||||
|
this._animationFrame = null;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
//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._renderer.setViewport(0, 0, _this._outputCanvas.width, _this._outputCanvas.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
_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.render();
|
||||||
|
})
|
||||||
|
this._setupRenderer();
|
||||||
|
}
|
||||||
|
renderFrame(){
|
||||||
|
if(this._animationFrame) {
|
||||||
|
cancelAnimationFrame(this._animationFrame);
|
||||||
|
}
|
||||||
|
this._animationFrame = requestAnimationFrame(()=>this.render());
|
||||||
|
}
|
||||||
|
render(){
|
||||||
|
let numItems = this.viewer.world.getItemCount();
|
||||||
|
this._outputContext.clearRect(0, 0, this._outputCanvas.width, this._outputCanvas.height);
|
||||||
|
//iterate over items to draw
|
||||||
|
for(let i = 0; i < numItems; i++){
|
||||||
|
let item = this.viewer.world.getItemAt(i);
|
||||||
|
let scene = this._tiledImageMap[item[this._uuid]];
|
||||||
|
|
||||||
|
if(item.wrapHorizontal || item.wrapVertical){
|
||||||
|
createWrappingGrid(scene, item);
|
||||||
|
} else {
|
||||||
|
scene.userData.wrappedCopies.clear();
|
||||||
|
}
|
||||||
|
this._renderer.render(scene, this._camera); //renders to this._renderingCanvas
|
||||||
|
|
||||||
|
this._outputContext.save();
|
||||||
|
// set composite operation; ignore for first image drawn
|
||||||
|
this._outputContext.globalCompositeOperation = i===0 ? null : item.compositeOperation || this.viewer.compositeOperation;
|
||||||
|
if(item._croppingPolygons || item._clip){
|
||||||
|
this._renderToClippingCanvas(item);
|
||||||
|
this._outputContext.drawImage(this._clippingCanvas, 0, 0);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this._outputContext.drawImage(this._renderingCanvas, 0, 0);
|
||||||
|
}
|
||||||
|
this._outputContext.restore();
|
||||||
|
if(item.debugMode){
|
||||||
|
this._drawDebugInfo(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._animationFrame = null;
|
||||||
|
if(this._renderingContinuously){
|
||||||
|
this.renderFrame();
|
||||||
|
}
|
||||||
|
// console.log(this._renderer.info.memory, this._renderer.info.render.triangles);
|
||||||
|
}
|
||||||
|
renderContinuously(continuously){
|
||||||
|
if(continuously){
|
||||||
|
this._renderingContinuously = true;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this._renderingContinuously = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public API required by all Drawer implementations
|
||||||
|
|
||||||
|
destroy(){
|
||||||
|
// clear all resources used by the renderer, geometries, textures etc
|
||||||
|
|
||||||
|
// to do: remove handlers from viewer
|
||||||
|
|
||||||
|
// dispose of any remaining textures/materials
|
||||||
|
Object.values(this._tileMap).forEach(material=>material.dispose());
|
||||||
|
//to do: check whether tiled images are all removed (for clean up) before viewer destroy event
|
||||||
|
|
||||||
|
// clean up renderer and camera objects
|
||||||
|
cleanupObject(this._renderer);
|
||||||
|
cleanupObject(this._camera);
|
||||||
|
this._renderer = null;
|
||||||
|
this._camera = null;
|
||||||
|
|
||||||
|
}
|
||||||
|
clear(){
|
||||||
|
//not needed by this implementation
|
||||||
|
}
|
||||||
|
canRotate(){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
draw(tiledImage){
|
||||||
|
// actual drawing is handled by event listeneners
|
||||||
|
// just mark this tiledImage as having been drawn (possibly unnecessary)
|
||||||
|
tiledImage._needsDraw = false;
|
||||||
|
}
|
||||||
|
setImageSmoothingEnabled(enabled){
|
||||||
|
this._clippingContext.imageSmoothingEnabled = enabled;
|
||||||
|
this._outputContext.imageSmoothingEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private methods
|
||||||
|
|
||||||
|
_setupRenderer(){
|
||||||
|
//to do: test support for pages of sequence mode
|
||||||
|
|
||||||
|
let viewerBounds = this.viewer.viewport.getBoundsNoRotate();
|
||||||
|
this._camera = new THREE.OrthographicCamera(
|
||||||
|
viewerBounds.width / -2,
|
||||||
|
viewerBounds.width / 2,
|
||||||
|
viewerBounds.height / 2,
|
||||||
|
viewerBounds.height / -2,
|
||||||
|
0,
|
||||||
|
10000
|
||||||
|
);
|
||||||
|
this._camera.position.x = viewerBounds.x + viewerBounds.width/2;
|
||||||
|
this._camera.position.y = -(viewerBounds.y + viewerBounds.height/2);
|
||||||
|
this._camera.position.z = 100;
|
||||||
|
this._camera.lookAt(this._camera.position.x, this._camera.position.y, 0);
|
||||||
|
this._camera.updateProjectionMatrix();
|
||||||
|
|
||||||
|
|
||||||
|
this._renderer = new THREE.WebGLRenderer({canvas: this._renderingCanvas, alpha:true});
|
||||||
|
|
||||||
|
// Add listeners for events that require modifying the scene or camera
|
||||||
|
this.viewer.addHandler("destroy", ()=>this.destroy());
|
||||||
|
this.viewer.world.addHandler("add-item", ev => this._addTiledImage(ev));
|
||||||
|
this.viewer.world.addHandler("remove-item", ev => this._removeTiledImage(ev));
|
||||||
|
this.viewer.addHandler("tile-ready", ev => this._tileReadyHandler(ev));
|
||||||
|
this.viewer.addHandler("tile-unloaded", ev => this._tileUnloadedHandler(ev));
|
||||||
|
this.viewer.addHandler("viewport-change", () => this._viewportChangeHandler());
|
||||||
|
this.viewer.addHandler("home", () => this._viewportChangeHandler());
|
||||||
|
|
||||||
|
// this.viewer.world.addHandler("item-index-change", () => this.renderFrame());
|
||||||
|
// this.viewer.addHandler("crop-change", () => this.renderFrame());
|
||||||
|
// this.viewer.addHandler("clip-change", () => this.renderFrame());
|
||||||
|
// this.viewer.addHandler("opacity-change", () => this.renderFrame());
|
||||||
|
// this.viewer.addHandler("composite-operation-change", () => this.renderFrame());
|
||||||
|
|
||||||
|
|
||||||
|
this.viewer.addHandler("update-viewport", () => this.renderFrame());
|
||||||
|
|
||||||
|
this._viewportChangeHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
_addTiledImage(event){
|
||||||
|
let tiledImage = event.item;
|
||||||
|
|
||||||
|
// create a Group for the tiles of this tiled image.
|
||||||
|
if(!tiledImage[this._uuid]){
|
||||||
|
let tileContainer = new THREE.Group();
|
||||||
|
let rotationAxis = new THREE.Group();
|
||||||
|
let positioningGroup = new THREE.Group();
|
||||||
|
|
||||||
|
rotationAxis.add(tileContainer);
|
||||||
|
positioningGroup.add(rotationAxis);
|
||||||
|
|
||||||
|
let wrappedCopies = new THREE.Group();
|
||||||
|
rotationAxis.add(wrappedCopies);
|
||||||
|
|
||||||
|
// save mutual references between OpenSceneGraph and ThreeJSRenderer versions of tiledImages
|
||||||
|
// add unique ID to the tiledImage to look it up in our map in event handlers
|
||||||
|
let scene = new THREE.Scene()
|
||||||
|
let light = new THREE.AmbientLight();
|
||||||
|
scene.add(light);
|
||||||
|
scene.add(positioningGroup);
|
||||||
|
scene.userData.tileContainer = tileContainer;
|
||||||
|
scene.userData.wrappedCopies = wrappedCopies;
|
||||||
|
|
||||||
|
let tiledImageID = generateUUID();
|
||||||
|
tiledImage[this._uuid] = tiledImageID;
|
||||||
|
this._tiledImageMap[tiledImageID] = scene;
|
||||||
|
|
||||||
|
|
||||||
|
// keep a direct reference to the TiledImage on the Group
|
||||||
|
positioningGroup._tiledImage = tiledImage;
|
||||||
|
|
||||||
|
//offset the tileContainer so the center of the image is at the origin of the parent group
|
||||||
|
tileContainer.position.x = -0.5;
|
||||||
|
tileContainer.position.y = -0.5 / tiledImage.source.aspectRatio;
|
||||||
|
|
||||||
|
//undo the offset of the tileContainer, moving this back into original viewport coordinate space
|
||||||
|
rotationAxis.position.x = tileContainer.position.x * -1;
|
||||||
|
rotationAxis.position.y = tileContainer.position.y * -1;
|
||||||
|
|
||||||
|
|
||||||
|
this._updateTiledImageParameters(tiledImage, positioningGroup, rotationAxis);
|
||||||
|
tiledImage.addHandler('bounds-change',()=>this._updateTiledImageParameters(tiledImage, positioningGroup, rotationAxis, true));
|
||||||
|
tiledImage.addHandler('opacity-change',()=>this._updateTiledImageParameters(tiledImage, positioningGroup, rotationAxis, true));
|
||||||
|
|
||||||
|
}
|
||||||
|
this._updateMeshIfNeeded(tiledImage);
|
||||||
|
this.renderFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
_removeTiledImage(event){
|
||||||
|
let tiledImage = event.item;
|
||||||
|
|
||||||
|
cleanupObject(this._tiledImageMap[tiledImage[this._uuid]]);
|
||||||
|
delete this._tiledImageMap[tiledImage[this._uuid]];
|
||||||
|
this.renderFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
_tileReadyHandler(event){
|
||||||
|
let tile = event.tile;
|
||||||
|
let tiledImage = event.tiledImage;
|
||||||
|
|
||||||
|
if(this._tileMap[tile.cacheKey]){
|
||||||
|
// this tile has already been handled; ignore repeat ready request (happens when image is wrapped)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//create a THREE.Material with the image data for this tile
|
||||||
|
let texture = new THREE.CanvasTexture(event.tile.getCanvasContext().canvas);
|
||||||
|
texture.flipY = false; // To match OSD reference frame
|
||||||
|
let material = new THREE.MeshLambertMaterial({
|
||||||
|
map: texture,
|
||||||
|
transparent: !!tile.hasTransparency || tiledImage.opacity < 1,
|
||||||
|
opacity: tiledImage.opacity
|
||||||
|
});
|
||||||
|
|
||||||
|
// cache the material using the tile's cacheKey so that when a tile is located by OpenSeadragon methods, the associate material can be retrieved
|
||||||
|
this._tileMap[tile.cacheKey] = material;
|
||||||
|
|
||||||
|
let numTiles = tiledImage.source.getNumTiles(tile.level);
|
||||||
|
let tx = OpenSeadragon.positiveModulo(tile.x, numTiles.x);
|
||||||
|
let ty = OpenSeadragon.positiveModulo(tile.y, numTiles.y);
|
||||||
|
|
||||||
|
//cache the bounds for this material so it doesn't have to be recomputed every time it is used to update the scene
|
||||||
|
material.userData._tileBounds = tiledImage.source.getTileBounds(tile.level, tx, ty);
|
||||||
|
material.userData.hasTransparency = !!tile.hasTransparency;
|
||||||
|
material.userData.tile = tile;
|
||||||
|
material.userData.tiledImage = tiledImage;
|
||||||
|
|
||||||
|
//since a new tile is available, update the image (if needed)
|
||||||
|
this._updateTiledImageRendering(tiledImage, tile);
|
||||||
|
}
|
||||||
|
|
||||||
|
_tileUnloadedHandler(event){
|
||||||
|
let tile = event.tile;
|
||||||
|
if(!this._tileMap[tile.cacheKey]){
|
||||||
|
//already cleaned up
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._updateTiledImageRendering(event.tiledImage, tile);
|
||||||
|
cleanupObject(this._tileMap[tile.cacheKey]);
|
||||||
|
delete this._tileMap[tile.cacheKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateTiledImageParameters(tiledImage, positioningGroup, rotationAxis, requestRender){
|
||||||
|
let bounds = tiledImage.getBoundsNoRotate(true);
|
||||||
|
let rotation = tiledImage.getRotation(true);
|
||||||
|
|
||||||
|
//set size and location
|
||||||
|
positioningGroup.scale.x = bounds.width; //scale the normalized image coordinates to match the size within the world
|
||||||
|
positioningGroup.scale.y = -bounds.width; //flip Y
|
||||||
|
positioningGroup.position.x = bounds.x;
|
||||||
|
positioningGroup.position.y = bounds.y * -1;//flip Y
|
||||||
|
|
||||||
|
// rotate about the rotation axis
|
||||||
|
rotationAxis.rotation.z = rotation * Math.PI / 180;
|
||||||
|
rotationAxis.scale.x = tiledImage.getFlip() ? -1 : 1;
|
||||||
|
|
||||||
|
updateOpacity(this._tiledImageMap[tiledImage[this._uuid]].userData.tileContainer, tiledImage.opacity);
|
||||||
|
|
||||||
|
if(requestRender){
|
||||||
|
this.renderFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateTiledImageRendering(tiledImage, tile){
|
||||||
|
|
||||||
|
let scene = this._tiledImageMap[tiledImage[this._uuid]];
|
||||||
|
|
||||||
|
let bounds = this._tileMap[tile.cacheKey].userData._tileBounds
|
||||||
|
|
||||||
|
if(bounds.x < 0 || bounds.y < 0 || bounds.x >= 1 || bounds.y >= 1){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._updateMeshIfNeeded(tiledImage);
|
||||||
|
|
||||||
|
let level = scene.userData.currentLevel;
|
||||||
|
|
||||||
|
//whether the tile was just loaded or unloaded, update any tiles that it overlaps in the current tileGrid (as needed)
|
||||||
|
|
||||||
|
let topLeft = tiledImage.source.getTileAtPoint(level, {x: bounds.x, y: bounds.y});
|
||||||
|
let bottomRight = tiledImage.source.getTileAtPoint(level, {x: bounds.x + bounds.width, y: bounds.y + bounds.height});
|
||||||
|
|
||||||
|
//iterate over the tiles overlapped by this one
|
||||||
|
let x, y;
|
||||||
|
for(x = topLeft.x; x<= bottomRight.x; x++){
|
||||||
|
for(y = topLeft.y; y <= bottomRight.y; y++){
|
||||||
|
let mesh = scene.userData._tileMatrix[x][y];
|
||||||
|
this._loadBestImage(mesh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.renderFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateMeshIfNeeded(tiledImage){
|
||||||
|
let tileContainer = this._tiledImageMap[tiledImage[this._uuid]].userData.tileContainer;
|
||||||
|
let scene = this._tiledImageMap[tiledImage[this._uuid]]
|
||||||
|
let levelsInterval = tiledImage._getLevelsInterval();
|
||||||
|
let level = levelsInterval.highestLevel;
|
||||||
|
|
||||||
|
if(scene.userData.currentLevel === level){
|
||||||
|
//we are already drawing the highest-resolution tiles, just return
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// console.log('new level', level);
|
||||||
|
scene.userData.currentLevel = level;
|
||||||
|
//we need to update the grid.
|
||||||
|
//clear the old matrix
|
||||||
|
scene.userData._tileMatrix = [];
|
||||||
|
scene.userData.tiledImage = tiledImage;
|
||||||
|
//remove existing tiles
|
||||||
|
|
||||||
|
tileContainer.children.forEach(cleanupObject);
|
||||||
|
tileContainer.clear();
|
||||||
|
|
||||||
|
//create new set of tiles and add to the tileContainer
|
||||||
|
let gridInfo = tiledImage.getGridDefinition(level);
|
||||||
|
let col, row;
|
||||||
|
for(col = 0; col < gridInfo.numColumns; col += 1){
|
||||||
|
scene.userData._tileMatrix[col] = [];
|
||||||
|
for(row = 0; row < gridInfo.numRows; row += 1){
|
||||||
|
let colInfo = gridInfo.columnInfo[col];
|
||||||
|
let rowInfo = gridInfo.rowInfo[row];
|
||||||
|
|
||||||
|
let left = colInfo.x;
|
||||||
|
let top = rowInfo.y;
|
||||||
|
let x = left + colInfo.width / 2;
|
||||||
|
let y = top + rowInfo.height / 2;
|
||||||
|
let z = 0;
|
||||||
|
|
||||||
|
let tileGeometry = new THREE.PlaneGeometry(colInfo.width, rowInfo.height);
|
||||||
|
let mesh = new THREE.Mesh(tileGeometry);
|
||||||
|
|
||||||
|
|
||||||
|
mesh.position.set(x, y, z);
|
||||||
|
|
||||||
|
mesh.userData._tileInfo = {
|
||||||
|
row: row,
|
||||||
|
col: col,
|
||||||
|
level: level,
|
||||||
|
...rowInfo,
|
||||||
|
...colInfo,
|
||||||
|
tiledImageID: tiledImage[this._uuid],
|
||||||
|
center: new OpenSeadragon.Point(x, y),
|
||||||
|
uvMapOriginal: [],
|
||||||
|
// uvMap
|
||||||
|
}
|
||||||
|
|
||||||
|
let i;
|
||||||
|
let uvAttribute = tileGeometry.attributes.uv;
|
||||||
|
|
||||||
|
for(i =0 ; i<uvAttribute.count; ++i){
|
||||||
|
let x = uvAttribute.getX(i);
|
||||||
|
let y = uvAttribute.getY(i);
|
||||||
|
mesh.userData._tileInfo.uvMapOriginal[i] = [x, y];
|
||||||
|
}
|
||||||
|
|
||||||
|
tileContainer.add(mesh);
|
||||||
|
|
||||||
|
scene.userData._tileMatrix[col][row] = mesh;
|
||||||
|
|
||||||
|
//get (current) best image data for the tile
|
||||||
|
this._loadBestImage(mesh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_loadBestImage(mesh){
|
||||||
|
let tileInfo = mesh.userData._tileInfo;
|
||||||
|
let tiledImage = this._tiledImageMap[tileInfo.tiledImageID].userData.tiledImage;
|
||||||
|
let tileSource = tiledImage.source;
|
||||||
|
let tilesMatrix = tiledImage.tilesMatrix;
|
||||||
|
|
||||||
|
//if we have our own texture, use it
|
||||||
|
// let tile = tilesMatrix[tileInfo.level][tileInfo.col][tileInfo.row];
|
||||||
|
let tile = getTile(tilesMatrix, tileInfo.level, tileInfo.col, tileInfo.row);
|
||||||
|
let material = tile && this._tileMap[tile.cacheKey];
|
||||||
|
if(material){
|
||||||
|
addMaterialToMesh(mesh, material, tiledImage.opacity);
|
||||||
|
} else {
|
||||||
|
//start at next highest level and work downward
|
||||||
|
let queryLevel = tileInfo.level - 1;
|
||||||
|
while(queryLevel >= 0){
|
||||||
|
let tileIndex = tileSource.getTileAtPoint(queryLevel, tileInfo.center);
|
||||||
|
let tile = getTile(tilesMatrix, queryLevel, tileIndex.x, tileIndex.y);
|
||||||
|
let material = tile && this._tileMap[tile.cacheKey];
|
||||||
|
if(material){
|
||||||
|
addMaterialToMesh(mesh, material, tiledImage.opacity);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
queryLevel--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_viewportChangeHandler(){
|
||||||
|
let viewer = this.viewer;
|
||||||
|
|
||||||
|
let viewerBounds = viewer.viewport.getBoundsNoRotate(true);
|
||||||
|
this._camera.left = viewerBounds.width / -2;
|
||||||
|
this._camera.right = viewerBounds.width / 2;
|
||||||
|
this._camera.top = viewerBounds.height / 2;
|
||||||
|
this._camera.bottom = viewerBounds.height / -2;
|
||||||
|
|
||||||
|
let center = viewer.viewport.getCenter(true);
|
||||||
|
this._camera.position.x = center.x;
|
||||||
|
this._camera.position.y = -center.y;
|
||||||
|
this._camera.rotation.z = viewer.viewport.getRotation(true) * Math.PI / 180;
|
||||||
|
|
||||||
|
this._camera.updateProjectionMatrix();
|
||||||
|
|
||||||
|
let numItems = viewer.world.getItemCount();
|
||||||
|
let i;
|
||||||
|
for(i = 0; i < numItems; i++){
|
||||||
|
let tiledImage = viewer.world.getItemAt(i);
|
||||||
|
this._updateMeshIfNeeded(tiledImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.renderFrame();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
_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._clippingContext.beginPath();
|
||||||
|
this._clippingContext.rect(rect.x, rect.y, rect.width, rect.height);
|
||||||
|
this._clippingContext.clip();
|
||||||
|
}
|
||||||
|
if(item._croppingPolygons){
|
||||||
|
let polygons = item._croppingPolygons.map(function (polygon) {
|
||||||
|
return polygon.map(function (coord) {
|
||||||
|
let point = item.imageToViewportCoordinates(coord.x, coord.y, 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(OpenSeadragon.pixelDensityRatio) :
|
||||||
|
new OpenSeadragon.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
|
||||||
|
_drawDebugInfoOnTile(tile, count, i, tiledImage) {
|
||||||
|
|
||||||
|
var colorIndex = this.viewer.world.getIndexOfItem(tiledImage) % this.debugGridColor.length;
|
||||||
|
var context = this._outputContext;
|
||||||
|
context.save();
|
||||||
|
context.lineWidth = 2 * OpenSeadragon.pixelDensityRatio;
|
||||||
|
context.font = 'small-caps bold ' + (13 * OpenSeadragon.pixelDensityRatio) + 'px arial';
|
||||||
|
context.strokeStyle = this.debugGridColor[colorIndex];
|
||||||
|
context.fillStyle = this.debugGridColor[colorIndex];
|
||||||
|
|
||||||
|
if (this.viewport.getRotation(true) % 360 !== 0 ) {
|
||||||
|
this._offsetForRotation({degrees: this.viewport.getRotation(true)});
|
||||||
|
}
|
||||||
|
if (tiledImage.getRotation(true) % 360 !== 0) {
|
||||||
|
this._offsetForRotation({
|
||||||
|
degrees: tiledImage.getRotation(true),
|
||||||
|
point: tiledImage.viewport.pixelFromPointNoRotate(
|
||||||
|
tiledImage._getRotationPoint(true), true)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (tiledImage.viewport.getRotation(true) % 360 === 0 &&
|
||||||
|
tiledImage.getRotation(true) % 360 === 0) {
|
||||||
|
if(tiledImage._drawer.viewer.viewport.getFlip()) {
|
||||||
|
tiledImage._drawer._flip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.strokeRect(
|
||||||
|
tile.position.x * OpenSeadragon.pixelDensityRatio,
|
||||||
|
tile.position.y * OpenSeadragon.pixelDensityRatio,
|
||||||
|
tile.size.x * OpenSeadragon.pixelDensityRatio,
|
||||||
|
tile.size.y * OpenSeadragon.pixelDensityRatio
|
||||||
|
);
|
||||||
|
|
||||||
|
var tileCenterX = (tile.position.x + (tile.size.x / 2)) * OpenSeadragon.pixelDensityRatio;
|
||||||
|
var tileCenterY = (tile.position.y + (tile.size.y / 2)) * OpenSeadragon.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 * OpenSeadragon.pixelDensityRatio,
|
||||||
|
(tile.position.y - 30) * OpenSeadragon.pixelDensityRatio
|
||||||
|
);
|
||||||
|
context.fillText(
|
||||||
|
"Pan: " + this.viewport.getBounds().toString(),
|
||||||
|
tile.position.x * OpenSeadragon.pixelDensityRatio,
|
||||||
|
(tile.position.y - 20) * OpenSeadragon.pixelDensityRatio
|
||||||
|
);
|
||||||
|
}
|
||||||
|
context.fillText(
|
||||||
|
"Level: " + tile.level,
|
||||||
|
(tile.position.x + 10) * OpenSeadragon.pixelDensityRatio,
|
||||||
|
(tile.position.y + 20) * OpenSeadragon.pixelDensityRatio
|
||||||
|
);
|
||||||
|
context.fillText(
|
||||||
|
"Column: " + tile.x,
|
||||||
|
(tile.position.x + 10) * OpenSeadragon.pixelDensityRatio,
|
||||||
|
(tile.position.y + 30) * OpenSeadragon.pixelDensityRatio
|
||||||
|
);
|
||||||
|
context.fillText(
|
||||||
|
"Row: " + tile.y,
|
||||||
|
(tile.position.x + 10) * OpenSeadragon.pixelDensityRatio,
|
||||||
|
(tile.position.y + 40) * OpenSeadragon.pixelDensityRatio
|
||||||
|
);
|
||||||
|
context.fillText(
|
||||||
|
"Order: " + i + " of " + count,
|
||||||
|
(tile.position.x + 10) * OpenSeadragon.pixelDensityRatio,
|
||||||
|
(tile.position.y + 50) * OpenSeadragon.pixelDensityRatio
|
||||||
|
);
|
||||||
|
context.fillText(
|
||||||
|
"Size: " + tile.size.toString(),
|
||||||
|
(tile.position.x + 10) * OpenSeadragon.pixelDensityRatio,
|
||||||
|
(tile.position.y + 60) * OpenSeadragon.pixelDensityRatio
|
||||||
|
);
|
||||||
|
context.fillText(
|
||||||
|
"Position: " + tile.position.toString(),
|
||||||
|
(tile.position.x + 10) * OpenSeadragon.pixelDensityRatio,
|
||||||
|
(tile.position.y + 70) * OpenSeadragon.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();
|
||||||
|
}
|
||||||
|
|
||||||
|
_drawDebugInfo( tiledImage ) {
|
||||||
|
let scene = this._tiledImageMap[tiledImage[this._uuid]];
|
||||||
|
let level = scene.userData.currentLevel;
|
||||||
|
let tiles = tiledImage.getTileInfoForDrawing().filter(tile=>tile.level === level);
|
||||||
|
|
||||||
|
// only draw on the highest level tiles
|
||||||
|
for ( var i = tiles.length - 1; i >= 0; i-- ) {
|
||||||
|
var tile = tiles[ i ].tile;
|
||||||
|
try {
|
||||||
|
this._drawDebugInfoOnTile(tile, tiles.length, i, tiledImage);
|
||||||
|
} catch(e) {
|
||||||
|
OpenSeadragon.console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// private
|
||||||
|
_debugRect(rect) {
|
||||||
|
if ( this.useCanvas ) {
|
||||||
|
var context = this._outputContext;
|
||||||
|
context.save();
|
||||||
|
context.lineWidth = 2 * OpenSeadragon.pixelDensityRatio;
|
||||||
|
context.strokeStyle = this.debugGridColor[0];
|
||||||
|
context.fillStyle = this.debugGridColor[0];
|
||||||
|
|
||||||
|
context.strokeRect(
|
||||||
|
rect.x * OpenSeadragon.pixelDensityRatio,
|
||||||
|
rect.y * OpenSeadragon.pixelDensityRatio,
|
||||||
|
rect.width * OpenSeadragon.pixelDensityRatio,
|
||||||
|
rect.height * OpenSeadragon.pixelDensityRatio
|
||||||
|
);
|
||||||
|
|
||||||
|
context.restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// private
|
||||||
|
_restoreRotationChanges() {
|
||||||
|
var context = this._outputContext;
|
||||||
|
context.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Functions below do not depend on an instance of the Drawer, and can be defined outside of the class definition
|
||||||
|
|
||||||
|
function updateOpacity(meshGroup, opacity){
|
||||||
|
meshGroup.children.forEach(mesh=>{
|
||||||
|
mesh.material.opacity = opacity;
|
||||||
|
if(opacity < 1 || mesh.material.userData.hasTransparency){
|
||||||
|
mesh.material.transparent = true;
|
||||||
|
} else {
|
||||||
|
mesh.material.transparent = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTile(tileMatrix, level, x, y){
|
||||||
|
return tileMatrix[level] && tileMatrix[level][x] && tileMatrix[level][x][y];
|
||||||
|
}
|
||||||
|
|
||||||
|
function addMaterialToMesh(mesh, material, opacity){
|
||||||
|
let regionInfo = mesh.userData._tileInfo;
|
||||||
|
let materialBounds = material.userData._tileBounds;
|
||||||
|
|
||||||
|
//update transparent and opacity properties to reflect current state
|
||||||
|
let transparent = opacity < 1 || material.userData.hasTransparency;
|
||||||
|
material.transparent = transparent;
|
||||||
|
material.opacity = opacity;
|
||||||
|
|
||||||
|
mesh.material = material;
|
||||||
|
let uvMap = mesh.userData._tileInfo.uvMapOriginal;
|
||||||
|
let uvAttribute = mesh.geometry.attributes.uv;
|
||||||
|
|
||||||
|
// iterate over UV map for each vertex and calculate position within material/texture
|
||||||
|
let xNew, yNew;
|
||||||
|
let regionLeft = regionInfo.x;
|
||||||
|
let regionTop = regionInfo.y;
|
||||||
|
let regionRight = regionLeft + regionInfo.width;
|
||||||
|
let regionBottom = regionTop + regionInfo.height;
|
||||||
|
|
||||||
|
//what is needed to calculate the right uv index for each vertex?
|
||||||
|
// 1) position of the vertex in normalized coordinates
|
||||||
|
// 2) position of the entire texture area (not just non-overlapped area) in normalized coordinates
|
||||||
|
|
||||||
|
uvMap.forEach(([x,y],i)=>{
|
||||||
|
// x, y describe which corner of the original texture to use
|
||||||
|
if(x==0){
|
||||||
|
xNew = (regionLeft - materialBounds.x) / materialBounds.width;
|
||||||
|
} else {
|
||||||
|
xNew = (regionRight - materialBounds.x) / materialBounds.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(y == 0){
|
||||||
|
yNew = (regionTop - materialBounds.y) / materialBounds.height;
|
||||||
|
} else {
|
||||||
|
yNew = (regionBottom - materialBounds.y) / materialBounds.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
uvAttribute.setXY(i, xNew, yNew);
|
||||||
|
});
|
||||||
|
uvAttribute.needsUpdate = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function createWrappingGrid(scene, tiledImage){
|
||||||
|
let container = scene.userData.wrappedCopies;
|
||||||
|
container.clear();
|
||||||
|
|
||||||
|
//calculate how to tile the space
|
||||||
|
|
||||||
|
let tiledImageBounds = tiledImage.getBounds();
|
||||||
|
let imgBounds = {x: 0, y: 0, width: tiledImageBounds.width, height: tiledImageBounds.height };
|
||||||
|
let drawArea = tiledImage.viewer.viewport.getBounds(true);
|
||||||
|
let center = drawArea.getCenter();
|
||||||
|
let halfDiag = Math.sqrt(drawArea.width * drawArea.width + drawArea.height * drawArea.height);
|
||||||
|
let extraBuffer = imgBounds.width + imgBounds.height;
|
||||||
|
let left = center.x - halfDiag - extraBuffer;
|
||||||
|
let right = center.x + halfDiag + extraBuffer;
|
||||||
|
let top = center.y - halfDiag - extraBuffer;
|
||||||
|
let bottom = center.y + halfDiag + extraBuffer;
|
||||||
|
|
||||||
|
let xMin = tiledImage.wrapHorizontal ? Math.floor(left / imgBounds.width) : imgBounds.x;
|
||||||
|
let yMin = tiledImage.wrapVertical ? Math.floor(top / imgBounds.height) : imgBounds.y;
|
||||||
|
let xMax = tiledImage.wrapHorizontal ? Math.floor(right / imgBounds.width) : imgBounds.x;
|
||||||
|
let yMax = tiledImage.wrapVertical ? Math.floor(bottom / imgBounds.height) : imgBounds.y;
|
||||||
|
|
||||||
|
for(let x = xMin; x <= xMax; x += 1){
|
||||||
|
for(let y = yMin; y <= yMax; y += 1){
|
||||||
|
if(x == 0 && y == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let clone = scene.userData.tileContainer.clone();
|
||||||
|
clone.position.x += x;
|
||||||
|
clone.position.y += y;
|
||||||
|
container.add(clone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function cleanupObject(object){
|
||||||
|
if(object.children && object.children.forEach){
|
||||||
|
object.children.forEach(cleanupObject);
|
||||||
|
}
|
||||||
|
if(object.dispose){
|
||||||
|
object.dispose();
|
||||||
|
}
|
||||||
|
if(object.geometry){
|
||||||
|
object.geometry.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136
|
||||||
|
const _lut = [ '00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '0a', '0b', '0c', '0d', '0e', '0f', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '1a', '1b', '1c', '1d', '1e', '1f', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '2a', '2b', '2c', '2d', '2e', '2f', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '3a', '3b', '3c', '3d', '3e', '3f', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '4a', '4b', '4c', '4d', '4e', '4f', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '5a', '5b', '5c', '5d', '5e', '5f', '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '6a', '6b', '6c', '6d', '6e', '6f', '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '7a', '7b', '7c', '7d', '7e', '7f', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '8a', '8b', '8c', '8d', '8e', '8f', '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', '9a', '9b', '9c', '9d', '9e', '9f', 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9', 'aa', 'ab', 'ac', 'ad', 'ae', 'af', 'b0', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8', 'b9', 'ba', 'bb', 'bc', 'bd', 'be', 'bf', 'c0', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9', 'ca', 'cb', 'cc', 'cd', 'ce', 'cf', 'd0', 'd1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8', 'd9', 'da', 'db', 'dc', 'dd', 'de', 'df', 'e0', 'e1', 'e2', 'e3', 'e4', 'e5', 'e6', 'e7', 'e8', 'e9', 'ea', 'eb', 'ec', 'ed', 'ee', 'ef', 'f0', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'fa', 'fb', 'fc', 'fd', 'fe', 'ff' ];
|
||||||
|
function generateUUID() {
|
||||||
|
|
||||||
|
const d0 = Math.random() * 0xffffffff | 0;
|
||||||
|
const d1 = Math.random() * 0xffffffff | 0;
|
||||||
|
const d2 = Math.random() * 0xffffffff | 0;
|
||||||
|
const d3 = Math.random() * 0xffffffff | 0;
|
||||||
|
const uuid = _lut[ d0 & 0xff ] + _lut[ d0 >> 8 & 0xff ] + _lut[ d0 >> 16 & 0xff ] + _lut[ d0 >> 24 & 0xff ] + '-' +
|
||||||
|
_lut[ d1 & 0xff ] + _lut[ d1 >> 8 & 0xff ] + '-' + _lut[ d1 >> 16 & 0x0f | 0x40 ] + _lut[ d1 >> 24 & 0xff ] + '-' +
|
||||||
|
_lut[ d2 & 0x3f | 0x80 ] + _lut[ d2 >> 8 & 0xff ] + '-' + _lut[ d2 >> 16 & 0xff ] + _lut[ d2 >> 24 & 0xff ] +
|
||||||
|
_lut[ d3 & 0xff ] + _lut[ d3 >> 8 & 0xff ] + _lut[ d3 >> 16 & 0xff ] + _lut[ d3 >> 24 & 0xff ];
|
||||||
|
|
||||||
|
// .toLowerCase() here flattens concatenated strings to save heap memory space.
|
||||||
|
return uuid.toLowerCase();
|
||||||
|
|
||||||
|
}
|
@ -1,841 +0,0 @@
|
|||||||
// import 'https://cdnjs.cloudflare.com/ajax/libs/three.js/0.149.0/three.min.js';
|
|
||||||
import '../lib/three.js';
|
|
||||||
const THREE = window.THREE;
|
|
||||||
const DEPTH_MULTIPLIER = 0.1;
|
|
||||||
|
|
||||||
export class ThreeJSRenderer extends OpenSeadragon.Drawer{
|
|
||||||
constructor(openSeadragonViewer, canvas){
|
|
||||||
this._viewer = openSeadragonViewer;
|
|
||||||
this._camera = null;
|
|
||||||
this._scene = null;
|
|
||||||
this._imageContainer = null;
|
|
||||||
this._renderer = null;
|
|
||||||
|
|
||||||
this._renderingContinously = false;
|
|
||||||
this._animationFrame = null;
|
|
||||||
|
|
||||||
if(canvas){
|
|
||||||
this._canvas = canvas;
|
|
||||||
} else {
|
|
||||||
let viewerCanvas = viewer.drawer.canvas;
|
|
||||||
this._canvas = viewer.drawer.canvas;
|
|
||||||
// let canvas = this._canvas = document.createElement('canvas');
|
|
||||||
// canvas.insertBefore(viewerCanvas);
|
|
||||||
|
|
||||||
// canvas.style.width = viewerCanvas.clientWidth+'px';
|
|
||||||
// canvas.style.height = viewerCanvas.clientHeight+'px';
|
|
||||||
// canvas.width = viewerCanvas.width;
|
|
||||||
// canvas.height = viewerCanvas.height;
|
|
||||||
|
|
||||||
// //make the test canvas mirror all changes to the viewer canvas
|
|
||||||
// viewer.addHandler("resize", function(){
|
|
||||||
// canvas.style.width = viewerCanvas.clientWidth+'px';
|
|
||||||
// canvas.style.height = viewerCanvas.clientHeight+'px';
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
createThreeViewer(this);
|
|
||||||
}
|
|
||||||
renderFrame(){
|
|
||||||
if(this._animationFrame) {
|
|
||||||
cancelAnimationFrame(this._animationFrame);
|
|
||||||
}
|
|
||||||
this._animationFrame = requestAnimationFrame(()=>this.render());
|
|
||||||
}
|
|
||||||
render(){
|
|
||||||
// this.camera.updateProjectionMatrix();
|
|
||||||
this._renderer.render(this._scene, this._camera);
|
|
||||||
this._animationFrame = null;
|
|
||||||
if(this._renderingContinuously){
|
|
||||||
this.renderFrame();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
renderContinuously(continuously){
|
|
||||||
if(continuously){
|
|
||||||
this._renderingContinuously = true;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
this._renderingContinuously = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
destroy(){
|
|
||||||
//clear all resources used by the renderer, geometries, textures etc
|
|
||||||
//to do: remove handlers from viewer and dispose of any remaining textures/materials
|
|
||||||
cleanupObject(this._scene);
|
|
||||||
cleanupObject(this._renderer);
|
|
||||||
cleanupObject(this._camera);
|
|
||||||
this._scene = null;
|
|
||||||
this._renderer = null;
|
|
||||||
this._camera = null;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//// Override API from OpenSeadragon.Drawer
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function will create multiple polygon paths on the drawing context by provided polygons,
|
|
||||||
* then clip the context to the paths.
|
|
||||||
* @param {OpenSeadragon.Point[][]} polygons - an array of polygons. A polygon is an array of OpenSeadragon.Point
|
|
||||||
* @param {Boolean} useSketch - Whether to use the sketch canvas or not.
|
|
||||||
*/
|
|
||||||
clipWithPolygons(polygons, useSketch) {
|
|
||||||
if (!this.useCanvas) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var context = this._getContext(useSketch);
|
|
||||||
context.beginPath();
|
|
||||||
polygons.forEach(function (polygon) {
|
|
||||||
polygon.forEach(function (coord, i) {
|
|
||||||
context[i === 0 ? 'moveTo' : 'lineTo'](coord.x, coord.y);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
context.clip();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroy the drawer (unload current loaded tiles)
|
|
||||||
*/
|
|
||||||
destroy() {
|
|
||||||
//force unloading of current canvas (1x1 will be gc later, trick not necessarily needed)
|
|
||||||
this.canvas.width = 1;
|
|
||||||
this.canvas.height = 1;
|
|
||||||
this.sketchCanvas = null;
|
|
||||||
this.sketchContext = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears the Drawer so it's ready to draw another frame.
|
|
||||||
*/
|
|
||||||
clear(){
|
|
||||||
this.canvas.innerHTML = "";
|
|
||||||
if ( this.useCanvas ) {
|
|
||||||
var viewportSize = this._calculateCanvasSize();
|
|
||||||
if( this.canvas.width !== viewportSize.x ||
|
|
||||||
this.canvas.height !== viewportSize.y ) {
|
|
||||||
this.canvas.width = viewportSize.x;
|
|
||||||
this.canvas.height = viewportSize.y;
|
|
||||||
this._updateImageSmoothingEnabled(this.context);
|
|
||||||
if ( this.sketchCanvas !== null ) {
|
|
||||||
var sketchCanvasSize = this._calculateSketchCanvasSize();
|
|
||||||
this.sketchCanvas.width = sketchCanvasSize.x;
|
|
||||||
this.sketchCanvas.height = sketchCanvasSize.y;
|
|
||||||
this._updateImageSmoothingEnabled(this.sketchContext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_clear(useSketch, bounds) {
|
|
||||||
if (!this.useCanvas) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var context = this._getContext(useSketch);
|
|
||||||
if (bounds) {
|
|
||||||
context.clearRect(bounds.x, bounds.y, bounds.width, bounds.height);
|
|
||||||
} else {
|
|
||||||
var canvas = context.canvas;
|
|
||||||
context.clearRect(0, 0, canvas.width, canvas.height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the given tile.
|
|
||||||
* @param {OpenSeadragon.Tile} tile - The tile to draw.
|
|
||||||
* @param {Function} drawingHandler - Method for firing the drawing event if using canvas.
|
|
||||||
* drawingHandler({context, tile, rendered})
|
|
||||||
* @param {Boolean} useSketch - Whether to use the sketch canvas or not.
|
|
||||||
* where <code>rendered</code> is the context with the pre-drawn image.
|
|
||||||
* @param {Float} [scale=1] - Apply a scale to tile position and size. Defaults to 1.
|
|
||||||
* @param {OpenSeadragon.Point} [translate] A translation vector to offset tile position
|
|
||||||
* @param {Boolean} [shouldRoundPositionAndSize] - Tells whether to round
|
|
||||||
* position and size of tiles supporting alpha channel in non-transparency
|
|
||||||
* context.
|
|
||||||
* @param {OpenSeadragon.TileSource} source - The source specification of the tile.
|
|
||||||
*/
|
|
||||||
drawTile( tile, drawingHandler, useSketch, scale, translate, shouldRoundPositionAndSize, source) {
|
|
||||||
$.console.assert(tile, '[Drawer.drawTile] tile is required');
|
|
||||||
$.console.assert(drawingHandler, '[Drawer.drawTile] drawingHandler is required');
|
|
||||||
|
|
||||||
if (this.useCanvas) {
|
|
||||||
var context = this._getContext(useSketch);
|
|
||||||
scale = scale || 1;
|
|
||||||
tile.drawCanvas(context, drawingHandler, scale, translate, shouldRoundPositionAndSize, source);
|
|
||||||
} else {
|
|
||||||
tile.drawHTML( this.canvas );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_getContext( useSketch ) {
|
|
||||||
var context = this.context;
|
|
||||||
if ( useSketch ) {
|
|
||||||
if (this.sketchCanvas === null) {
|
|
||||||
this.sketchCanvas = document.createElement( "canvas" );
|
|
||||||
var sketchCanvasSize = this._calculateSketchCanvasSize();
|
|
||||||
this.sketchCanvas.width = sketchCanvasSize.x;
|
|
||||||
this.sketchCanvas.height = sketchCanvasSize.y;
|
|
||||||
this.sketchContext = this.sketchCanvas.getContext( "2d" );
|
|
||||||
|
|
||||||
// If the viewport is not currently rotated, the sketchCanvas
|
|
||||||
// will have the same size as the main canvas. However, if
|
|
||||||
// the viewport get rotated later on, we will need to resize it.
|
|
||||||
if (this.viewport.getRotation() === 0) {
|
|
||||||
var self = this;
|
|
||||||
this.viewer.addHandler('rotate', function resizeSketchCanvas() {
|
|
||||||
if (self.viewport.getRotation() === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.viewer.removeHandler('rotate', resizeSketchCanvas);
|
|
||||||
var sketchCanvasSize = self._calculateSketchCanvasSize();
|
|
||||||
self.sketchCanvas.width = sketchCanvasSize.x;
|
|
||||||
self.sketchCanvas.height = sketchCanvasSize.y;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this._updateImageSmoothingEnabled(this.sketchContext);
|
|
||||||
}
|
|
||||||
context = this.sketchContext;
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
// private
|
|
||||||
saveContext() {
|
|
||||||
if (!this.useCanvas) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._getContext( useSketch ).save();
|
|
||||||
}
|
|
||||||
|
|
||||||
// private
|
|
||||||
restoreContext( useSketch ) {
|
|
||||||
if (!this.useCanvas) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._getContext( useSketch ).restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
// private
|
|
||||||
setClip(rect, useSketch) {
|
|
||||||
if (!this.useCanvas) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var context = this._getContext( useSketch );
|
|
||||||
context.beginPath();
|
|
||||||
context.rect(rect.x, rect.y, rect.width, rect.height);
|
|
||||||
context.clip();
|
|
||||||
}
|
|
||||||
|
|
||||||
// private
|
|
||||||
drawRectangle(rect, fillStyle, useSketch) {
|
|
||||||
if (!this.useCanvas) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var context = this._getContext( useSketch );
|
|
||||||
context.save();
|
|
||||||
context.fillStyle = fillStyle;
|
|
||||||
context.fillRect(rect.x, rect.y, rect.width, rect.height);
|
|
||||||
context.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Blends the sketch canvas in the main canvas.
|
|
||||||
* @param {Object} options The options
|
|
||||||
* @param {Float} options.opacity The opacity of the blending.
|
|
||||||
* @param {Float} [options.scale=1] The scale at which tiles were drawn on
|
|
||||||
* the sketch. Default is 1.
|
|
||||||
* Use scale to draw at a lower scale and then enlarge onto the main canvas.
|
|
||||||
* @param {OpenSeadragon.Point} [options.translate] A translation vector
|
|
||||||
* that was used to draw the tiles
|
|
||||||
* @param {String} [options.compositeOperation] - How the image is
|
|
||||||
* composited onto other images; see compositeOperation in
|
|
||||||
* {@link OpenSeadragon.Options} for possible values.
|
|
||||||
* @param {OpenSeadragon.Rect} [options.bounds] The part of the sketch
|
|
||||||
* canvas to blend in the main canvas. If specified, options.scale and
|
|
||||||
* options.translate get ignored.
|
|
||||||
*/
|
|
||||||
blendSketch(opacity, scale, translate, compositeOperation) {
|
|
||||||
var options = opacity;
|
|
||||||
if (!$.isPlainObject(options)) {
|
|
||||||
options = {
|
|
||||||
opacity: opacity,
|
|
||||||
scale: scale,
|
|
||||||
translate: translate,
|
|
||||||
compositeOperation: compositeOperation
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (!this.useCanvas || !this.sketchCanvas) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
opacity = options.opacity;
|
|
||||||
compositeOperation = options.compositeOperation;
|
|
||||||
var bounds = options.bounds;
|
|
||||||
|
|
||||||
this.context.save();
|
|
||||||
this.context.globalAlpha = opacity;
|
|
||||||
if (compositeOperation) {
|
|
||||||
this.context.globalCompositeOperation = compositeOperation;
|
|
||||||
}
|
|
||||||
if (bounds) {
|
|
||||||
// Internet Explorer, Microsoft Edge, and Safari have problems
|
|
||||||
// when you call context.drawImage with negative x or y
|
|
||||||
// or x + width or y + height greater than the canvas width or height respectively.
|
|
||||||
if (bounds.x < 0) {
|
|
||||||
bounds.width += bounds.x;
|
|
||||||
bounds.x = 0;
|
|
||||||
}
|
|
||||||
if (bounds.x + bounds.width > this.canvas.width) {
|
|
||||||
bounds.width = this.canvas.width - bounds.x;
|
|
||||||
}
|
|
||||||
if (bounds.y < 0) {
|
|
||||||
bounds.height += bounds.y;
|
|
||||||
bounds.y = 0;
|
|
||||||
}
|
|
||||||
if (bounds.y + bounds.height > this.canvas.height) {
|
|
||||||
bounds.height = this.canvas.height - bounds.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.context.drawImage(
|
|
||||||
this.sketchCanvas,
|
|
||||||
bounds.x,
|
|
||||||
bounds.y,
|
|
||||||
bounds.width,
|
|
||||||
bounds.height,
|
|
||||||
bounds.x,
|
|
||||||
bounds.y,
|
|
||||||
bounds.width,
|
|
||||||
bounds.height
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
scale = options.scale || 1;
|
|
||||||
translate = options.translate;
|
|
||||||
var position = translate instanceof $.Point ?
|
|
||||||
translate : new $.Point(0, 0);
|
|
||||||
|
|
||||||
var widthExt = 0;
|
|
||||||
var heightExt = 0;
|
|
||||||
if (translate) {
|
|
||||||
var widthDiff = this.sketchCanvas.width - this.canvas.width;
|
|
||||||
var heightDiff = this.sketchCanvas.height - this.canvas.height;
|
|
||||||
widthExt = Math.round(widthDiff / 2);
|
|
||||||
heightExt = Math.round(heightDiff / 2);
|
|
||||||
}
|
|
||||||
this.context.drawImage(
|
|
||||||
this.sketchCanvas,
|
|
||||||
position.x - widthExt * scale,
|
|
||||||
position.y - heightExt * scale,
|
|
||||||
(this.canvas.width + 2 * widthExt) * scale,
|
|
||||||
(this.canvas.height + 2 * heightExt) * scale,
|
|
||||||
-widthExt,
|
|
||||||
-heightExt,
|
|
||||||
this.canvas.width + 2 * widthExt,
|
|
||||||
this.canvas.height + 2 * heightExt
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this.context.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
// private
|
|
||||||
drawDebugInfo(tile, count, i, tiledImage) {
|
|
||||||
if ( !this.useCanvas ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var colorIndex = this.viewer.world.getIndexOfItem(tiledImage) % this.debugGridColor.length;
|
|
||||||
var context = this.context;
|
|
||||||
context.save();
|
|
||||||
context.lineWidth = 2 * $.pixelDensityRatio;
|
|
||||||
context.font = 'small-caps bold ' + (13 * $.pixelDensityRatio) + 'px arial';
|
|
||||||
context.strokeStyle = this.debugGridColor[colorIndex];
|
|
||||||
context.fillStyle = this.debugGridColor[colorIndex];
|
|
||||||
|
|
||||||
if (this.viewport.getRotation(true) % 360 !== 0 ) {
|
|
||||||
this._offsetForRotation({degrees: this.viewport.getRotation(true)});
|
|
||||||
}
|
|
||||||
if (tiledImage.getRotation(true) % 360 !== 0) {
|
|
||||||
this._offsetForRotation({
|
|
||||||
degrees: tiledImage.getRotation(true),
|
|
||||||
point: tiledImage.viewport.pixelFromPointNoRotate(
|
|
||||||
tiledImage._getRotationPoint(true), true)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (tiledImage.viewport.getRotation(true) % 360 === 0 &&
|
|
||||||
tiledImage.getRotation(true) % 360 === 0) {
|
|
||||||
if(tiledImage._drawer.viewer.viewport.getFlip()) {
|
|
||||||
tiledImage._drawer._flip();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context.strokeRect(
|
|
||||||
tile.position.x * $.pixelDensityRatio,
|
|
||||||
tile.position.y * $.pixelDensityRatio,
|
|
||||||
tile.size.x * $.pixelDensityRatio,
|
|
||||||
tile.size.y * $.pixelDensityRatio
|
|
||||||
);
|
|
||||||
|
|
||||||
var tileCenterX = (tile.position.x + (tile.size.x / 2)) * $.pixelDensityRatio;
|
|
||||||
var tileCenterY = (tile.position.y + (tile.size.y / 2)) * $.pixelDensityRatio;
|
|
||||||
|
|
||||||
// Rotate the text the right way around.
|
|
||||||
context.translate( tileCenterX, tileCenterY );
|
|
||||||
context.rotate( Math.PI / 180 * -this.viewport.getRotation(true) );
|
|
||||||
context.translate( -tileCenterX, -tileCenterY );
|
|
||||||
|
|
||||||
if( tile.x === 0 && tile.y === 0 ){
|
|
||||||
context.fillText(
|
|
||||||
"Zoom: " + this.viewport.getZoom(),
|
|
||||||
tile.position.x * $.pixelDensityRatio,
|
|
||||||
(tile.position.y - 30) * $.pixelDensityRatio
|
|
||||||
);
|
|
||||||
context.fillText(
|
|
||||||
"Pan: " + this.viewport.getBounds().toString(),
|
|
||||||
tile.position.x * $.pixelDensityRatio,
|
|
||||||
(tile.position.y - 20) * $.pixelDensityRatio
|
|
||||||
);
|
|
||||||
}
|
|
||||||
context.fillText(
|
|
||||||
"Level: " + tile.level,
|
|
||||||
(tile.position.x + 10) * $.pixelDensityRatio,
|
|
||||||
(tile.position.y + 20) * $.pixelDensityRatio
|
|
||||||
);
|
|
||||||
context.fillText(
|
|
||||||
"Column: " + tile.x,
|
|
||||||
(tile.position.x + 10) * $.pixelDensityRatio,
|
|
||||||
(tile.position.y + 30) * $.pixelDensityRatio
|
|
||||||
);
|
|
||||||
context.fillText(
|
|
||||||
"Row: " + tile.y,
|
|
||||||
(tile.position.x + 10) * $.pixelDensityRatio,
|
|
||||||
(tile.position.y + 40) * $.pixelDensityRatio
|
|
||||||
);
|
|
||||||
context.fillText(
|
|
||||||
"Order: " + i + " of " + count,
|
|
||||||
(tile.position.x + 10) * $.pixelDensityRatio,
|
|
||||||
(tile.position.y + 50) * $.pixelDensityRatio
|
|
||||||
);
|
|
||||||
context.fillText(
|
|
||||||
"Size: " + tile.size.toString(),
|
|
||||||
(tile.position.x + 10) * $.pixelDensityRatio,
|
|
||||||
(tile.position.y + 60) * $.pixelDensityRatio
|
|
||||||
);
|
|
||||||
context.fillText(
|
|
||||||
"Position: " + tile.position.toString(),
|
|
||||||
(tile.position.x + 10) * $.pixelDensityRatio,
|
|
||||||
(tile.position.y + 70) * $.pixelDensityRatio
|
|
||||||
);
|
|
||||||
|
|
||||||
if (this.viewport.getRotation(true) % 360 !== 0 ) {
|
|
||||||
this._restoreRotationChanges();
|
|
||||||
}
|
|
||||||
if (tiledImage.getRotation(true) % 360 !== 0) {
|
|
||||||
this._restoreRotationChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tiledImage.viewport.getRotation(true) % 360 === 0 &&
|
|
||||||
tiledImage.getRotation(true) % 360 === 0) {
|
|
||||||
if(tiledImage._drawer.viewer.viewport.getFlip()) {
|
|
||||||
tiledImage._drawer._flip();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
// private
|
|
||||||
debugRect(rect) {
|
|
||||||
if ( this.useCanvas ) {
|
|
||||||
var context = this.context;
|
|
||||||
context.save();
|
|
||||||
context.lineWidth = 2 * $.pixelDensityRatio;
|
|
||||||
context.strokeStyle = this.debugGridColor[0];
|
|
||||||
context.fillStyle = this.debugGridColor[0];
|
|
||||||
|
|
||||||
context.strokeRect(
|
|
||||||
rect.x * $.pixelDensityRatio,
|
|
||||||
rect.y * $.pixelDensityRatio,
|
|
||||||
rect.width * $.pixelDensityRatio,
|
|
||||||
rect.height * $.pixelDensityRatio
|
|
||||||
);
|
|
||||||
|
|
||||||
context.restore();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/*** Create THREE.js version of rendering tiled images using WebGL ****/
|
|
||||||
function createThreeViewer(instance){
|
|
||||||
// Add listeners for events that require modifying the scene or camera
|
|
||||||
instance._viewer.addHandler("close", ()=>instance.destroy());
|
|
||||||
instance._viewer.world.addHandler("add-item", ev => addTiledImage(ev, instance));
|
|
||||||
instance._viewer.world.addHandler("remove-item", ev => removeTiledImage(ev, instance));
|
|
||||||
instance._viewer.world.addHandler("item-index-change", ev => setItemOrder(ev, instance));
|
|
||||||
instance._viewer.addHandler("tile-ready", ev => tileReady(ev, instance));
|
|
||||||
instance._viewer.addHandler("tile-unloaded", ev => tileUnloaded(ev, instance));
|
|
||||||
instance._viewer.addHandler("viewport-change", ev => viewportChange(ev, instance));
|
|
||||||
// instance._viewer.addHandler("update-viewport", ev => instance.renderFrame());
|
|
||||||
|
|
||||||
//to do: support pages of sequence mode
|
|
||||||
|
|
||||||
//to do: add handler for resize event to auto-sync
|
|
||||||
|
|
||||||
let viewerBounds = instance._viewer.viewport.getBoundsNoRotate();
|
|
||||||
instance._scene = new THREE.Scene();
|
|
||||||
instance._imageContainer = new THREE.Group();
|
|
||||||
instance._camera = new THREE.OrthographicCamera(
|
|
||||||
viewerBounds.width / -2,
|
|
||||||
viewerBounds.width / 2,
|
|
||||||
viewerBounds.height / 2,
|
|
||||||
viewerBounds.height / -2,
|
|
||||||
0,
|
|
||||||
10000
|
|
||||||
);
|
|
||||||
instance._camera.position.x = viewerBounds.x + viewerBounds.width/2;
|
|
||||||
instance._camera.position.y = -(viewerBounds.y + viewerBounds.height/2);
|
|
||||||
instance._camera.position.z = 100;
|
|
||||||
instance._camera.lookAt(instance._camera.position.x, instance._camera.position.y, 0);
|
|
||||||
instance._camera.updateProjectionMatrix();
|
|
||||||
|
|
||||||
var light = new THREE.AmbientLight();
|
|
||||||
instance._scene.add(light);
|
|
||||||
instance._scene.add(instance._imageContainer);
|
|
||||||
|
|
||||||
instance._scene.background = new THREE.Color(0.25, 0.25, 0.25);
|
|
||||||
|
|
||||||
instance._renderer = new THREE.WebGLRenderer({canvas: instance._canvas});
|
|
||||||
}
|
|
||||||
|
|
||||||
//private
|
|
||||||
function tileReady(event, instance){
|
|
||||||
let tile = event.tile;
|
|
||||||
let tiledImage = event.tiledImage;
|
|
||||||
|
|
||||||
//create a THREE.Material with the image data for this tile
|
|
||||||
let texture = new THREE.CanvasTexture(event.tile.getCanvasContext().canvas);
|
|
||||||
texture.flipY = false; // To match OSD reference frame
|
|
||||||
let material = new THREE.MeshLambertMaterial({
|
|
||||||
map: texture,
|
|
||||||
transparent: !!tile.hasTransparency || tiledImage.opacity < 1,
|
|
||||||
opacity: tiledImage.opacity
|
|
||||||
});
|
|
||||||
|
|
||||||
//attach the material to the tile so it can be queried by location using OpenSeadragon methods
|
|
||||||
tile._three = material;
|
|
||||||
|
|
||||||
//cache the bounds for this material so it doesn't have to be recomputed every time it is used to update the scene
|
|
||||||
material.userData._tileBounds = tiledImage.source.getTileBounds(tile.level, tile.x, tile.y);
|
|
||||||
material.userData.hasTransparency = !!tile.hasTransparency;
|
|
||||||
material.userData.tile = tile;
|
|
||||||
material.userData.tiledImage = tiledImage;
|
|
||||||
|
|
||||||
//since a new tile is available, update the image (if needed)
|
|
||||||
updateTiledImageRendering(tiledImage, tile, instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
function tileUnloaded(event, instance){
|
|
||||||
let tile = event.tile;
|
|
||||||
cleanupObject(tile._three);
|
|
||||||
delete tile._three;
|
|
||||||
|
|
||||||
updateTiledImageRendering(event.tiledImage, tile, instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
function viewportChange(event, instance){
|
|
||||||
let viewer = event.eventSource;
|
|
||||||
|
|
||||||
let viewerBounds = viewer.viewport.getBoundsNoRotate(true);
|
|
||||||
instance._camera.left = viewerBounds.width / -2;
|
|
||||||
instance._camera.right = viewerBounds.width / 2;
|
|
||||||
instance._camera.top = viewerBounds.height / 2;
|
|
||||||
instance._camera.bottom = viewerBounds.height / -2;
|
|
||||||
|
|
||||||
let center = viewer.viewport.getCenter(true);
|
|
||||||
instance._camera.position.x = center.x;
|
|
||||||
instance._camera.position.y = -center.y;
|
|
||||||
instance._camera.rotation.z = viewer.viewport.getRotation(true) * Math.PI / 180;
|
|
||||||
|
|
||||||
instance._camera.updateProjectionMatrix();
|
|
||||||
|
|
||||||
let numItems = viewer.world.getItemCount();
|
|
||||||
let i;
|
|
||||||
for(i = 0; i < numItems; i++){
|
|
||||||
let tiledImage = viewer.world.getItemAt(i);
|
|
||||||
updateMeshIfNeeded(tiledImage);
|
|
||||||
}
|
|
||||||
|
|
||||||
instance.renderFrame();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function addTiledImage(event, instance){
|
|
||||||
let tiledImage = event.item;
|
|
||||||
|
|
||||||
//create a Group for the tiles of this tiled image.
|
|
||||||
if(!tiledImage._three){
|
|
||||||
let tileContainer = new THREE.Group();
|
|
||||||
let rotationAxis = new THREE.Group();
|
|
||||||
let positioningGroup = new THREE.Group();
|
|
||||||
rotationAxis.add(tileContainer);
|
|
||||||
positioningGroup.add(rotationAxis);
|
|
||||||
positioningGroup.userData._tileContainer = tileContainer;
|
|
||||||
|
|
||||||
//add the object to the group of images (i.e. add to the scene)
|
|
||||||
instance._imageContainer.add(positioningGroup);
|
|
||||||
|
|
||||||
//save mutual references between OpenSceneGraph and ThreeJSRenderer versions of tiledImages
|
|
||||||
tiledImage._three = positioningGroup;
|
|
||||||
positioningGroup._tiledImage = tiledImage;
|
|
||||||
|
|
||||||
//offset the tileContainer so the center of the image is at the origin of the parent group
|
|
||||||
tileContainer.position.x = -0.5;
|
|
||||||
tileContainer.position.y = -0.5 / tiledImage.source.aspectRatio;
|
|
||||||
|
|
||||||
//undo the offset of the tileContainer, moving this back into original viewport coordinate space
|
|
||||||
rotationAxis.position.x = tileContainer.position.x * -1;
|
|
||||||
rotationAxis.position.y = tileContainer.position.y * -1;
|
|
||||||
|
|
||||||
|
|
||||||
updateTiledImageParameters(instance, tiledImage, positioningGroup, rotationAxis);
|
|
||||||
tiledImage.addHandler('bounds-change',()=>updateTiledImageParameters(instance, tiledImage, positioningGroup, rotationAxis, true));
|
|
||||||
tiledImage.addHandler('opacity-change',()=>updateTiledImageParameters(instance, tiledImage, positioningGroup, rotationAxis, true));
|
|
||||||
|
|
||||||
}
|
|
||||||
setItemOrder(null, instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeTiledImage(event){
|
|
||||||
let tiledImage = event.item;
|
|
||||||
//to do: make sure all resources for all tiles are unloaded (even if not actively in the tile group)
|
|
||||||
tiledImage._three.removeFromParent();
|
|
||||||
cleanupObject(tiledImage._three);
|
|
||||||
delete tiledImage._three;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateTiledImageParameters(instance, tiledImage, positioningGroup, rotationAxis, requestRender){
|
|
||||||
let bounds = tiledImage.getBoundsNoRotate(true);
|
|
||||||
let rotation = tiledImage.getRotation(true);
|
|
||||||
|
|
||||||
//set size and location
|
|
||||||
positioningGroup.scale.x = bounds.width; //scale the normalized image coordinates to match the size within the world
|
|
||||||
positioningGroup.scale.y = -bounds.width; //flip Y
|
|
||||||
positioningGroup.position.x = bounds.x;
|
|
||||||
positioningGroup.position.y = bounds.y * -1;//flip Y
|
|
||||||
|
|
||||||
// rotate about the rotation axis
|
|
||||||
rotationAxis.rotation.z = rotation * Math.PI / 180;
|
|
||||||
rotationAxis.scale.x = tiledImage.getFlip() ? -1 : 1;
|
|
||||||
|
|
||||||
updateOpacity(tiledImage._three.userData._tileContainer, tiledImage.opacity);
|
|
||||||
|
|
||||||
if(requestRender){
|
|
||||||
instance.renderFrame();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateOpacity(meshGroup, opacity){
|
|
||||||
meshGroup.children.forEach(mesh=>{
|
|
||||||
mesh.material.opacity = opacity;
|
|
||||||
if(opacity < 1 || mesh.material.userData.hasTransparency){
|
|
||||||
mesh.material.transparent = true;
|
|
||||||
} else {
|
|
||||||
mesh.material.transparent = false;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateTiledImageRendering(tiledImage, tile, instance){
|
|
||||||
updateMeshIfNeeded(tiledImage);
|
|
||||||
|
|
||||||
let tileContainer = tiledImage._three.userData._tileContainer;
|
|
||||||
let level = tileContainer.userData._tiledImageLevel;
|
|
||||||
|
|
||||||
//whether the tile was just loaded or unloaded, update any tiles that it overlaps in the current tileGrid (as needed)
|
|
||||||
let topLeft = tiledImage.source.getTileAtPoint(level, {x: tile.bounds.x, y: tile.bounds.y});
|
|
||||||
let bottomRight = tiledImage.source.getTileAtPoint(level, {x: tile.bounds.x + tile.bounds.width, y: tile.bounds.y + tile.bounds.height});
|
|
||||||
|
|
||||||
//iterate over the tiles overlapped by this one
|
|
||||||
let x, y;
|
|
||||||
for(x = topLeft.x; x<= bottomRight.x; x++){
|
|
||||||
for(y = topLeft.y; y <= bottomRight.y; y++){
|
|
||||||
let mesh = tileContainer.userData._tileMatrix[x][y];
|
|
||||||
loadBestImage(mesh);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
instance.renderFrame();
|
|
||||||
}
|
|
||||||
|
|
||||||
function setItemOrder(event, instance){
|
|
||||||
instance._imageContainer.children.forEach(child=>{
|
|
||||||
child.position.z = DEPTH_MULTIPLIER * instance._viewer.world.getIndexOfItem(child._tiledImage);
|
|
||||||
});
|
|
||||||
instance.renderFrame();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateMeshIfNeeded(tiledImage){
|
|
||||||
let tileContainer = tiledImage._three.userData._tileContainer;
|
|
||||||
let levelsInterval = tiledImage._getLevelsInterval();
|
|
||||||
let level = levelsInterval.highestLevel;
|
|
||||||
|
|
||||||
if(tileContainer.userData._tiledImageLevel === level){
|
|
||||||
//we are already drawing the highest-resolution tiles, just return
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log('new level', level);
|
|
||||||
tileContainer.userData._tiledImageLevel = level;
|
|
||||||
//we need to update the grid.
|
|
||||||
//clear the old matrix
|
|
||||||
tileContainer.userData._tileMatrix = [];
|
|
||||||
//remove existing tiles
|
|
||||||
|
|
||||||
tileContainer.children.forEach(cleanupObject);
|
|
||||||
tileContainer.clear();
|
|
||||||
|
|
||||||
//create new set of tiles and add to the tileContainer
|
|
||||||
let gridInfo = tiledImage.getGridDefinition(level);
|
|
||||||
let col, row;
|
|
||||||
for(col = 0; col < gridInfo.numColumns; col += 1){
|
|
||||||
tileContainer.userData._tileMatrix[col] = [];
|
|
||||||
for(row = 0; row < gridInfo.numRows; row += 1){
|
|
||||||
let colInfo = gridInfo.columnInfo[col];
|
|
||||||
let rowInfo = gridInfo.rowInfo[row];
|
|
||||||
|
|
||||||
let left = colInfo.x;
|
|
||||||
let top = rowInfo.y;
|
|
||||||
let x = left + colInfo.width / 2;
|
|
||||||
let y = top + rowInfo.height / 2;
|
|
||||||
let z = 0;
|
|
||||||
|
|
||||||
let tileGeometry = new THREE.PlaneGeometry(colInfo.width, rowInfo.height);
|
|
||||||
let mesh = new THREE.Mesh(tileGeometry);
|
|
||||||
|
|
||||||
|
|
||||||
mesh.position.set(x, y, z);
|
|
||||||
|
|
||||||
mesh._tileInfo = {
|
|
||||||
row: row,
|
|
||||||
col: col,
|
|
||||||
level: level,
|
|
||||||
...rowInfo,
|
|
||||||
...colInfo,
|
|
||||||
tiledImage: tiledImage,
|
|
||||||
center: new OpenSeadragon.Point(x, y),
|
|
||||||
uvMapOriginal: [],
|
|
||||||
// uvMap
|
|
||||||
}
|
|
||||||
|
|
||||||
let i;
|
|
||||||
let uvAttribute = tileGeometry.attributes.uv;
|
|
||||||
|
|
||||||
for(i =0 ; i<uvAttribute.count; ++i){
|
|
||||||
let x = uvAttribute.getX(i);
|
|
||||||
let y = uvAttribute.getY(i);
|
|
||||||
mesh._tileInfo.uvMapOriginal[i] = [x, y];
|
|
||||||
}
|
|
||||||
|
|
||||||
tileContainer.add(mesh);
|
|
||||||
|
|
||||||
tileContainer.userData._tileMatrix[col][row] = mesh;
|
|
||||||
|
|
||||||
//get (current) best image data for the tile
|
|
||||||
loadBestImage(mesh);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadBestImage(mesh){
|
|
||||||
let tileInfo = mesh._tileInfo;
|
|
||||||
let tiledImage = tileInfo.tiledImage;
|
|
||||||
let tileSource = tiledImage.source;
|
|
||||||
let tilesMatrix = tiledImage.tilesMatrix;
|
|
||||||
|
|
||||||
//if we have our own texture, use it
|
|
||||||
// let tile = tilesMatrix[tileInfo.level][tileInfo.col][tileInfo.row];
|
|
||||||
let tile = hasMaterial(tilesMatrix, tileInfo.level, tileInfo.col, tileInfo.row);
|
|
||||||
if(tile){
|
|
||||||
addMaterialToMesh(mesh, tile, tiledImage);
|
|
||||||
} else {
|
|
||||||
//start at next highest level and work downward
|
|
||||||
let queryLevel = tileInfo.level - 1;
|
|
||||||
while(queryLevel >= 0){
|
|
||||||
let tileIndex = tileSource.getTileAtPoint(queryLevel, tileInfo.center);
|
|
||||||
let tile = hasMaterial(tilesMatrix, queryLevel, tileIndex.x, tileIndex.y);
|
|
||||||
if(tile){
|
|
||||||
addMaterialToMesh(mesh, tile, tiledImage);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
queryLevel--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasMaterial(tileMatrix, level, x, y){
|
|
||||||
let tile = tileMatrix[level] && tileMatrix[level][x] && tileMatrix[level][x][y];
|
|
||||||
return tile && tile._three ? tile : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addMaterialToMesh(mesh, tile, tiledImage){
|
|
||||||
let regionInfo = mesh._tileInfo;
|
|
||||||
let material = tile._three;
|
|
||||||
let materialBounds = material.userData._tileBounds;
|
|
||||||
|
|
||||||
//update transparent and opacity properties to reflect current state
|
|
||||||
let opacity = tiledImage.opacity;
|
|
||||||
let transparent = opacity < 1 || material.userData.hasTransparency;
|
|
||||||
material.transparent = transparent;
|
|
||||||
material.opacity = opacity;
|
|
||||||
|
|
||||||
mesh.material = material;
|
|
||||||
let uvMap = mesh._tileInfo.uvMapOriginal;
|
|
||||||
let uvAttribute = mesh.geometry.attributes.uv;
|
|
||||||
|
|
||||||
// iterate over UV map for each vertex and calculate position within material/texture
|
|
||||||
let xNew, yNew;
|
|
||||||
let regionLeft = regionInfo.x;
|
|
||||||
let regionTop = regionInfo.y;
|
|
||||||
let regionRight = regionLeft + regionInfo.width;
|
|
||||||
let regionBottom = regionTop + regionInfo.height;
|
|
||||||
|
|
||||||
//what is needed to calculate the right uv index for each vertex?
|
|
||||||
// 1) position of the vertex in normalized coordinates
|
|
||||||
// 2) position of the entire texture area (not just non-overlapped area) in normalized coordinates
|
|
||||||
|
|
||||||
uvMap.forEach(([x,y],i)=>{
|
|
||||||
// x, y describe which corner of the original texture to use
|
|
||||||
if(x==0){
|
|
||||||
xNew = (regionLeft - materialBounds.x) / materialBounds.width;
|
|
||||||
} else {
|
|
||||||
xNew = (regionRight - materialBounds.x) / materialBounds.width;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(y == 0){
|
|
||||||
yNew = (regionTop - materialBounds.y) / materialBounds.height;
|
|
||||||
} else {
|
|
||||||
yNew = (regionBottom - materialBounds.y) / materialBounds.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
uvAttribute.setXY(i, xNew, yNew);
|
|
||||||
});
|
|
||||||
uvAttribute.needsUpdate = true;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function cleanupObject(object){
|
|
||||||
if(object.children && object.children.forEach){
|
|
||||||
object.children.forEach(cleanupObject);
|
|
||||||
}
|
|
||||||
if(object.dispose){
|
|
||||||
object.dispose();
|
|
||||||
}
|
|
||||||
if(object.geometry){
|
|
||||||
object.geometry.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
@ -16,77 +16,156 @@
|
|||||||
</script>
|
</script>
|
||||||
<script type="module" src="./webgl.js"></script>
|
<script type="module" src="./webgl.js"></script>
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
|
.content{
|
||||||
.openseadragon1 {
|
max-width:960px;
|
||||||
width: 600px;
|
margin: 0 auto;
|
||||||
height: 400px;
|
|
||||||
}
|
}
|
||||||
|
.mirrored{
|
||||||
#three-canvas{
|
display:grid;
|
||||||
position:fixed;
|
grid-template-columns:50% 50%;
|
||||||
top:5px;
|
gap: 2em;
|
||||||
right:5px;
|
}
|
||||||
|
.viewer-container {
|
||||||
|
/* width: 600px;
|
||||||
|
height: 400px; */
|
||||||
|
aspect-ratio: 4 / 3;
|
||||||
border: thin gray solid;
|
border: thin gray solid;
|
||||||
|
position:relative;
|
||||||
}
|
}
|
||||||
|
.example-code{
|
||||||
|
background-color:tan;
|
||||||
|
border: thin black solid;
|
||||||
|
padding:10px;
|
||||||
|
display:inline-block;
|
||||||
|
}
|
||||||
|
.description pre{
|
||||||
|
display:inline-block;
|
||||||
|
background-color:gainsboro;
|
||||||
|
padding:0;
|
||||||
|
margin:0;
|
||||||
|
}
|
||||||
|
|
||||||
.image-options{
|
.image-options{
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 2em repeat(7, 10em);
|
grid-template-columns: 2em 9em 1fr;
|
||||||
|
padding:3px;
|
||||||
|
border: thin gray solid;
|
||||||
|
}
|
||||||
|
.option-grid{
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 7em 7em 10em 10em 10em;
|
||||||
|
/* grid-template-columns: repeat(5, auto); */
|
||||||
}
|
}
|
||||||
.image-options input[type=number]{
|
.image-options input[type=number]{
|
||||||
width: 5em;
|
width: 5em;
|
||||||
}
|
}
|
||||||
|
.image-options select{
|
||||||
|
width: 5em;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div>
|
<div class="content">
|
||||||
Simple demo page to show three.js based rendering.
|
<h2>Use a WebGL drawer implementation (using three.js) instead of the default context2d drawer</h2>
|
||||||
</div>
|
<div class="mirrored">
|
||||||
<div id="contentDiv" class="openseadragon1"></div>
|
<div>
|
||||||
|
<div class="description">
|
||||||
|
The context2d drawing operations in core OpenSeadragon have been
|
||||||
|
consolidated from the <pre>TiledImage</pre> and <pre>Tile</pre> classes into the
|
||||||
|
<pre>Drawer</pre> class, which inherits from a new <pre>DrawerBase</pre> base class.
|
||||||
|
The <pre>TiledImage</pre> and <pre>Tile</pre> classes now handle the logic of managing
|
||||||
|
tile positioning and image data, with cleaner separation from details of the rendering
|
||||||
|
process. <pre>DrawerBase</pre> defines a public API that core OpenSeadragon code uses to
|
||||||
|
interact with the drawer implementation. To use a custom drawer/render, define
|
||||||
|
a new class that inherits from <pre>DrawerBase</pre> and implements the public API.
|
||||||
|
The constructor of this class can be passed in during construction of the viewer using the
|
||||||
|
new <pre>customDrawer</pre> option.
|
||||||
|
</div>
|
||||||
|
<pre class="example-code">
|
||||||
|
import { ThreeJSDrawer } from './threejsdrawer.js';
|
||||||
|
|
||||||
<div id="image-picker">
|
let viewer = OpenSeadragon({
|
||||||
<div class="image-options">
|
...
|
||||||
<span class="ui-icon ui-icon-arrowthick-2-n-s"></span>
|
customDrawer: ThreeJSDrawer,
|
||||||
<label><input type="checkbox" checked data-image="rainbow" class="toggle"> Rainbow Grid</label>
|
...
|
||||||
<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>
|
</pre>
|
||||||
<label>Width: <input type="number" value="1" data-image="rainbow" data-field="width" min="0"> </label>
|
</div>
|
||||||
<label>Degrees: <input type="number" value="0" data-image="rainbow" data-field="degrees"> </label>
|
<div id="three-viewer" class="viewer-container"></div>
|
||||||
<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>
|
|
||||||
</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>
|
<h2>Compare behavior of <strong>Context2d</strong> and <strong>WebGL</strong> (via three.js) drawers</h2>
|
||||||
<label>X: <input type="number" value="0.5" data-image="leaves" data-field="x"> </label>
|
<div class="mirrored">
|
||||||
<label>Y: <input type="number" value="0.5" data-image="leaves" data-field="y"> </label>
|
<div>
|
||||||
<label>Width: <input type="number" value="1.5" data-image="leaves" data-field="width" min="0"> </label>
|
<h3>Use default OpenSeadragon viewer to pan/zoom</h3>
|
||||||
<label>Degrees: <input type="number" value="0" data-image="leaves" data-field="degrees"> </label>
|
<div id="contentDiv" class="viewer-container"></div>
|
||||||
<label>Opacity: <input type="number" value="0.8" data-image="leaves" data-field="opacity" min="0" max="1" step="0.2"> </label>
|
</div>
|
||||||
<label>Flipped: <input type="checkbox" data-image="leaves" data-field="flipped"></label>
|
|
||||||
|
<div>
|
||||||
|
<h3>WebGL drawer linked using event listeners </h3>
|
||||||
|
<div id="three-canvas-container" class="viewer-container"></div>
|
||||||
|
</div>
|
||||||
</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 id="image-picker">
|
||||||
<label>X: <input type="number" value="0" data-image="bblue" data-field="x"> </label>
|
<h3>Image options (drag and drop to re-order images)</h3>
|
||||||
<label>Y: <input type="number" value="0" data-image="bblue" data-field="y"> </label>
|
<div class="image-options">
|
||||||
<label>Width: <input type="number" value="0.5" data-image="bblue" data-field="width" min="0"> </label>
|
<span class="ui-icon ui-icon-arrowthick-2-n-s"></span>
|
||||||
<label>Degrees: <input type="number" value="0" data-image="bblue" data-field="degrees"> </label>
|
<label><input type="checkbox" checked data-image="rainbow" class="toggle"> Rainbow Grid</label>
|
||||||
<label>Opacity: <input type="number" value="1" data-image="bblue" data-field="opacity" min="0" max="1" step="0.2"> </label>
|
<div class="option-grid">
|
||||||
<label>Flipped: <input type="checkbox" data-image="bblue" data-field="flipped"></label>
|
<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>
|
</div>
|
||||||
<!-- <div class="image-options">
|
|
||||||
<span class="ui-icon ui-icon-arrowthick-2-n-s"></span>
|
|
||||||
<label><input type="checkbox" data-image="duomo" class="toggle"> Duomo</label>
|
|
||||||
<label>X: <input type="number" value="0" data-image="duomo" data-field="x"> </label>
|
|
||||||
<label>Y: <input type="number" value="0" data-image="duomo" data-field="y"> </label>
|
|
||||||
<label>Width: <input type="number" value="0.5" data-image="duomo" data-field="width"> </label>
|
|
||||||
<label>Degrees: <input type="number" value="0" data-image="duomo" data-field="degrees"> </label>
|
|
||||||
<label>Opacity: <input type="number" value="1" data-image="duomo" data-field="opacity"> </label>
|
|
||||||
<label>Flipped: <input type="checkbox" data-image="duomo" data-field="flipped"></label>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<canvas id="three-canvas"></canvas>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
//imports
|
//imports
|
||||||
import { ThreeJSRenderer } from './webgl-renderer.js';
|
import { ThreeJSDrawer } from './threejsdrawer.js';
|
||||||
|
|
||||||
//globals
|
//globals
|
||||||
const canvas = document.querySelector('#three-canvas');
|
// const canvas = document.querySelector('#three-canvas');
|
||||||
const sources = {
|
const sources = {
|
||||||
"rainbow":"../data/testpattern.dzi",
|
"rainbow":"../data/testpattern.dzi",
|
||||||
"leaves":"../data/iiif_2_0_sizes/info.json",
|
"leaves":"../data/iiif_2_0_sizes/info.json",
|
||||||
@ -10,37 +10,38 @@ const sources = {
|
|||||||
type:'image',
|
type:'image',
|
||||||
url: "../data/BBlue.png",
|
url: "../data/BBlue.png",
|
||||||
},
|
},
|
||||||
// "duomo":"https://openseadragon.github.io/example-images/highsmith/highsmith.dzi"
|
|
||||||
}
|
}
|
||||||
var viewer = window.viewer = OpenSeadragon({
|
var viewer = window.viewer = OpenSeadragon({
|
||||||
// debugMode: true,
|
|
||||||
id: "contentDiv",
|
id: "contentDiv",
|
||||||
prefixUrl: "../../build/openseadragon/images/",
|
prefixUrl: "../../build/openseadragon/images/",
|
||||||
showNavigator:true,
|
minZoomImageRatio:0.01,
|
||||||
minZoomImageRatio:0.001,
|
|
||||||
customRenderer: true, // set this to true to use a renderer plugin instead of the built-in drawer
|
|
||||||
useCanvas: {contextType: 'webgl2'} //set this to match the context type used by the plugin renderer
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//sync size
|
let threeRenderer = window.threeRenderer = new ThreeJSDrawer({viewer, viewport: viewer.viewport, element:viewer.element});
|
||||||
|
|
||||||
// let viewerCanvas = viewer.drawer.canvas;
|
var viewer2 = window.viewer2 = OpenSeadragon({
|
||||||
// canvas.style.width = viewerCanvas.clientWidth+'px';
|
id: "three-viewer",
|
||||||
// canvas.style.height = viewerCanvas.clientHeight+'px';
|
prefixUrl: "../../build/openseadragon/images/",
|
||||||
// canvas.width = viewerCanvas.width;
|
minZoomImageRatio:0.01,
|
||||||
// canvas.height = viewerCanvas.height;
|
customDrawer: ThreeJSDrawer,
|
||||||
|
tileSources: sources['leaves'],
|
||||||
|
imageSmoothingEnabled: false,
|
||||||
|
});
|
||||||
|
|
||||||
// //make the test canvas mirror all changes to the viewer canvas
|
//make the test canvas mirror all changes to the viewer canvas
|
||||||
// viewer.addHandler("resize", function(){
|
let viewerCanvas = viewer.drawer.canvas;
|
||||||
// canvas.style.width = viewerCanvas.clientWidth+'px';
|
let canvas = threeRenderer.canvas;
|
||||||
// canvas.style.height = viewerCanvas.clientHeight+'px';
|
let canvasContainer = $('#three-canvas-container').append(canvas);
|
||||||
// })
|
viewer.addHandler("resize", function(){
|
||||||
let noCanvas;
|
canvasContainer[0].style.width = viewerCanvas.clientWidth+'px';
|
||||||
|
canvasContainer[0].style.height = viewerCanvas.clientHeight+'px';
|
||||||
|
// canvas.width = viewerCanvas.width;
|
||||||
|
// canvas.height = viewerCanvas.height;
|
||||||
|
})
|
||||||
|
|
||||||
let threeRenderer = window.threeRenderer = new ThreeJSRenderer(viewer, noCanvas);
|
|
||||||
|
|
||||||
// viewer.addHandler("open", () => viewer.world.getItemAt(0).source.hasTransparency = function(){ return true; });
|
// viewer.addHandler("open", () => viewer.world.getItemAt(0).source.hasTransparency = function(){ return true; });
|
||||||
|
$('#three-viewer').resizable(true);
|
||||||
$('#contentDiv').resizable(true);
|
$('#contentDiv').resizable(true);
|
||||||
$('#image-picker').sortable({
|
$('#image-picker').sortable({
|
||||||
update: function(event, ui){
|
update: function(event, ui){
|
||||||
@ -90,9 +91,82 @@ $('#image-picker input:not(.toggle)').on('change',function(){
|
|||||||
tiledImage.setOpacity(Number(value));
|
tiledImage.setOpacity(Number(value));
|
||||||
} else if (field == 'flipped'){
|
} else if (field == 'flipped'){
|
||||||
tiledImage.setFlip($(this).prop('checked'));
|
tiledImage.setFlip($(this).prop('checked'));
|
||||||
|
} else if (field == 'cropped'){
|
||||||
|
if( $(this).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') ){
|
||||||
|
let clipRect = new OpenSeadragon.Rect(2000, 0, 3000, 4000);
|
||||||
|
tiledImage.setClip(clipRect);
|
||||||
|
} else {
|
||||||
|
tiledImage.setClip(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (field == 'debug'){
|
||||||
|
if( $(this).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);
|
||||||
|
}
|
||||||
|
}).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');
|
||||||
|
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.
|
||||||
|
}
|
||||||
|
}).trigger('change');
|
||||||
|
|
||||||
|
function getWrappingOptions(){
|
||||||
|
let opts = ['None', 'Horizontal', 'Vertical', 'Both'];
|
||||||
|
let elements = opts.map((opt, i)=>{
|
||||||
|
let el = $('<option>',{value:opt}).text(opt);
|
||||||
|
if(i===0){
|
||||||
|
el.attr('selected',true);
|
||||||
|
}
|
||||||
|
return el[0];
|
||||||
|
// $('.image-options select').append(el);
|
||||||
|
});
|
||||||
|
return $(elements);
|
||||||
|
}
|
||||||
|
function getCompositeOperationOptions(){
|
||||||
|
let opts = [null,'source-over','source-in','source-out','source-atop',
|
||||||
|
'destination-over','destination-in','destination-out','destination-atop',
|
||||||
|
'lighten','darken','copy','xor','multiply','screen','overlay','color-dodge',
|
||||||
|
'color-burn','hard-light','soft-light','difference','exclusion',
|
||||||
|
'hue','saturation','color','luminosity'];
|
||||||
|
let elements = opts.map((opt, i)=>{
|
||||||
|
let el = $('<option>',{value:opt}).text(opt);
|
||||||
|
if(i===0){
|
||||||
|
el.attr('selected',true);
|
||||||
|
}
|
||||||
|
return el[0];
|
||||||
|
// $('.image-options select').append(el);
|
||||||
|
});
|
||||||
|
return $(elements);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
function addTileSource(image, checkbox){
|
function addTileSource(image, checkbox){
|
||||||
let options = $(`#image-picker input[data-image=${image}][type=number]`).toArray().reduce((acc, input)=>{
|
let options = $(`#image-picker input[data-image=${image}][type=number]`).toArray().reduce((acc, input)=>{
|
||||||
|
Loading…
Reference in New Issue
Block a user