mirror of
https://github.com/openseadragon/openseadragon.git
synced 2024-11-25 14:46:10 +03:00
afa8c2d1bd
This will flip each individual tile on a per image bases. However the tiles are now drawn in the wrong locations. Clipping etc works. this is implemented for Canvas and HTML renderers.
472 lines
15 KiB
JavaScript
472 lines
15 KiB
JavaScript
/*
|
|
* OpenSeadragon - Tile
|
|
*
|
|
* Copyright (C) 2009 CodePlex Foundation
|
|
* Copyright (C) 2010-2013 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 Tile
|
|
* @memberof OpenSeadragon
|
|
* @param {Number} level The zoom level this tile belongs to.
|
|
* @param {Number} x The vector component 'x'.
|
|
* @param {Number} y The vector component 'y'.
|
|
* @param {OpenSeadragon.Rect} bounds Where this tile fits, in normalized
|
|
* coordinates.
|
|
* @param {Boolean} exists Is this tile a part of a sparse image? ( Also has
|
|
* this tile failed to load? )
|
|
* @param {String} url The URL of this tile's image.
|
|
* @param {CanvasRenderingContext2D} context2D The context2D of this tile if it
|
|
* is provided directly by the tile source.
|
|
* @param {Boolean} loadWithAjax Whether this tile image should be loaded with an AJAX request .
|
|
* @param {Object} ajaxHeaders The headers to send with this tile's AJAX request (if applicable).
|
|
* @param {OpenSeadragon.Rect} sourceBounds The portion of the tile to use as the source of the
|
|
* drawing operation, in pixels. Note that this only works when drawing with canvas; when drawing
|
|
* with HTML the entire tile is always used.
|
|
*/
|
|
$.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, ajaxHeaders, sourceBounds) {
|
|
/**
|
|
* The zoom level this tile belongs to.
|
|
* @member {Number} level
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this.level = level;
|
|
/**
|
|
* The vector component 'x'.
|
|
* @member {Number} x
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this.x = x;
|
|
/**
|
|
* The vector component 'y'.
|
|
* @member {Number} y
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this.y = y;
|
|
/**
|
|
* Where this tile fits, in normalized coordinates
|
|
* @member {OpenSeadragon.Rect} bounds
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this.bounds = bounds;
|
|
/**
|
|
* The portion of the tile to use as the source of the drawing operation, in pixels. Note that
|
|
* this only works when drawing with canvas; when drawing with HTML the entire tile is always used.
|
|
* @member {OpenSeadragon.Rect} sourceBounds
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this.sourceBounds = sourceBounds;
|
|
/**
|
|
* Is this tile a part of a sparse image? Also has this tile failed to load?
|
|
* @member {Boolean} exists
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this.exists = exists;
|
|
/**
|
|
* The URL of this tile's image.
|
|
* @member {String} url
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this.url = url;
|
|
/**
|
|
* The context2D of this tile if it is provided directly by the tile source.
|
|
* @member {CanvasRenderingContext2D} context2D
|
|
* @memberOf OpenSeadragon.Tile#
|
|
*/
|
|
this.context2D = context2D;
|
|
/**
|
|
* Whether to load this tile's image with an AJAX request.
|
|
* @member {Boolean} loadWithAjax
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this.loadWithAjax = loadWithAjax;
|
|
/**
|
|
* The headers to be used in requesting this tile's image.
|
|
* Only used if loadWithAjax is set to true.
|
|
* @member {Object} ajaxHeaders
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this.ajaxHeaders = ajaxHeaders;
|
|
/**
|
|
* The unique cache key for this tile.
|
|
* @member {String} cacheKey
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
if (this.ajaxHeaders) {
|
|
this.cacheKey = this.url + "+" + JSON.stringify(this.ajaxHeaders);
|
|
} else {
|
|
this.cacheKey = this.url;
|
|
}
|
|
/**
|
|
* Is this tile loaded?
|
|
* @member {Boolean} loaded
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this.loaded = false;
|
|
/**
|
|
* Is this tile loading?
|
|
* @member {Boolean} loading
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this.loading = false;
|
|
|
|
/**
|
|
* The HTML div element for this tile
|
|
* @member {Element} element
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this.element = null;
|
|
/**
|
|
* The HTML img element for this tile.
|
|
* @member {Element} imgElement
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this.imgElement = null;
|
|
/**
|
|
* The Image object for this tile.
|
|
* @member {Object} image
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this.image = null;
|
|
|
|
/**
|
|
* The alias of this.element.style.
|
|
* @member {String} style
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this.style = null;
|
|
/**
|
|
* This tile's position on screen, in pixels.
|
|
* @member {OpenSeadragon.Point} position
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this.position = null;
|
|
/**
|
|
* This tile's size on screen, in pixels.
|
|
* @member {OpenSeadragon.Point} size
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this.size = null;
|
|
/**
|
|
* Whether to flip the tile when rendering.
|
|
* @member {Boolean} flipped
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this.flipped = false;
|
|
/**
|
|
* The start time of this tile's blending.
|
|
* @member {Number} blendStart
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this.blendStart = null;
|
|
/**
|
|
* The current opacity this tile should be.
|
|
* @member {Number} opacity
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this.opacity = null;
|
|
/**
|
|
* The squared distance of this tile to the viewport center.
|
|
* Use for comparing tiles.
|
|
* @private
|
|
* @member {Number} squaredDistance
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this.squaredDistance = null;
|
|
/**
|
|
* The visibility score of this tile.
|
|
* @member {Number} visibility
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this.visibility = null;
|
|
|
|
/**
|
|
* Whether this tile is currently being drawn.
|
|
* @member {Boolean} beingDrawn
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this.beingDrawn = false;
|
|
|
|
/**
|
|
* Timestamp the tile was last touched.
|
|
* @member {Number} lastTouchTime
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this.lastTouchTime = 0;
|
|
|
|
/**
|
|
* Whether this tile is in the right-most column for its level.
|
|
* @member {Boolean} isRightMost
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this.isRightMost = false;
|
|
|
|
/**
|
|
* Whether this tile is in the bottom-most row for its level.
|
|
* @member {Boolean} isBottomMost
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this.isBottomMost = false;
|
|
};
|
|
|
|
/** @lends OpenSeadragon.Tile.prototype */
|
|
$.Tile.prototype = {
|
|
|
|
/**
|
|
* Provides a string representation of this tiles level and (x,y)
|
|
* components.
|
|
* @function
|
|
* @returns {String}
|
|
*/
|
|
toString: function() {
|
|
return this.level + "/" + this.x + "_" + this.y;
|
|
},
|
|
|
|
// private
|
|
_hasTransparencyChannel: function() {
|
|
return !!this.context2D || this.url.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 ) {
|
|
this.element = $.makeNeutralElement( "div" );
|
|
this.imgElement = this.cacheImageRecord.getImage().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 );
|
|
},
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
drawCanvas: function( context, drawingHandler, scale, translate ) {
|
|
|
|
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.context2D || this.cacheImageRecord.getRenderedContext();
|
|
|
|
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._hasTransparencyChannel()) {
|
|
//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.
|
|
* @function
|
|
* @return {Float}
|
|
*/
|
|
getScaleForEdgeSmoothing: function() {
|
|
var context;
|
|
if (this.cacheImageRecord) {
|
|
context = this.cacheImageRecord.getRenderedContext();
|
|
} else if (this.context2D) {
|
|
context = this.context2D;
|
|
} else {
|
|
$.console.warn(
|
|
'[Tile.drawCanvas] attempting to get tile scale %s when tile\'s not cached',
|
|
this.toString());
|
|
return 1;
|
|
}
|
|
return context.canvas.width / (this.size.x * $.pixelDensityRatio);
|
|
},
|
|
|
|
/**
|
|
* Get a translation vector that when applied to the tile position produces integer coordinates.
|
|
* Needed to avoid swimming and twitching.
|
|
* @function
|
|
* @param {Number} [scale=1] - Scale to be applied to position.
|
|
* @return {OpenSeadragon.Point}
|
|
*/
|
|
getTranslationForEdgeSmoothing: function(scale, canvasSize, sketchCanvasSize) {
|
|
// The translation vector must have positive values, otherwise the image goes a bit off
|
|
// the sketch canvas to the top and left and we must use negative coordinates to repaint it
|
|
// to the main canvas. In that case, some browsers throw:
|
|
// INDEX_SIZE_ERR: DOM Exception 1: Index or size was negative, or greater than the allowed value.
|
|
var x = Math.max(1, Math.ceil((sketchCanvasSize.x - canvasSize.x) / 2));
|
|
var y = Math.max(1, Math.ceil((sketchCanvasSize.y - canvasSize.y) / 2));
|
|
return new $.Point(x, y).minus(
|
|
this.position
|
|
.times($.pixelDensityRatio)
|
|
.times(scale || 1)
|
|
.apply(function(x) {
|
|
return x % 1;
|
|
})
|
|
);
|
|
},
|
|
|
|
/**
|
|
* Removes tile from its container.
|
|
* @function
|
|
*/
|
|
unload: function() {
|
|
if ( this.imgElement && this.imgElement.parentNode ) {
|
|
this.imgElement.parentNode.removeChild( this.imgElement );
|
|
}
|
|
if ( this.element && this.element.parentNode ) {
|
|
this.element.parentNode.removeChild( this.element );
|
|
}
|
|
|
|
this.element = null;
|
|
this.imgElement = null;
|
|
this.loaded = false;
|
|
this.loading = false;
|
|
}
|
|
};
|
|
|
|
}( OpenSeadragon ));
|