mirror of
https://github.com/openseadragon/openseadragon.git
synced 2025-02-27 12:13:14 +03:00
893 lines
33 KiB
JavaScript
893 lines
33 KiB
JavaScript
/*
|
|
* OpenSeadragon - Tile
|
|
*
|
|
* Copyright (C) 2009 CodePlex Foundation
|
|
* Copyright (C) 2010-2024 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( $ ){
|
|
let _workingCacheIdDealer = 0;
|
|
|
|
/**
|
|
* @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|Function} url The URL of this tile's image or a function that returns a url.
|
|
* @param {CanvasRenderingContext2D} [context2D=undefined] The context2D of this tile if it
|
|
* * is provided directly by the tile source. Deprecated: use Tile::addCache(...) instead.
|
|
* @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.
|
|
* @param {String} postData HTTP POST data (usually but not necessarily in k=v&k2=v2... form,
|
|
* see TileSource::getTilePostData) or null
|
|
* @param {String} cacheKey key to act as a tile cache, must be unique for tiles with unique image data
|
|
*/
|
|
$.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, ajaxHeaders, sourceBounds, postData, cacheKey) {
|
|
/**
|
|
* 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;
|
|
/**
|
|
* Where this tile fits, in normalized coordinates, after positioning
|
|
* @member {OpenSeadragon.Rect} positionedBounds
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this.positionedBounds = new OpenSeadragon.Rect(bounds.x, bounds.y, bounds.width, bounds.height);
|
|
/**
|
|
* The portion of the tile to use as the source of the drawing operation, in pixels. Note that
|
|
* this only works when drawing with canvas; when drawing with HTML the entire tile is always used.
|
|
* @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;
|
|
/**
|
|
* Private property to hold string url or url retriever function.
|
|
* Consumers should access via Tile.getUrl()
|
|
* @private
|
|
* @member {String|Function} url
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this._url = url;
|
|
/**
|
|
* Post parameters for this tile. For example, it can be an URL-encoded string
|
|
* in k1=v1&k2=v2... format, or a JSON, or a FormData instance... or null if no POST request used
|
|
* @member {String} postData HTTP POST data (usually but not necessarily in k=v&k2=v2... form,
|
|
* see TileSource::getTilePostData) or null
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this.postData = postData;
|
|
/**
|
|
* The context2D of this tile if it is provided directly by the tile source.
|
|
* @member {CanvasRenderingContext2D} context2D
|
|
* @memberOf OpenSeadragon.Tile#
|
|
*/
|
|
if (context2D) {
|
|
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;
|
|
|
|
if (cacheKey === undefined) {
|
|
$.console.warn("Tile constructor needs 'cacheKey' variable: creation tile cache" +
|
|
" in Tile class is deprecated. TileSource.prototype.getTileHashKey will be used.");
|
|
cacheKey = $.TileSource.prototype.getTileHashKey(level, x, y, url, ajaxHeaders, postData);
|
|
}
|
|
|
|
this._cKey = cacheKey || "";
|
|
this._ocKey = cacheKey || "";
|
|
|
|
/**
|
|
* 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;
|
|
/**
|
|
* 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;
|
|
|
|
/**
|
|
* The transparency indicator of this tile.
|
|
* @member {Boolean} hasTransparency true if tile contains transparency for correct rendering
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this.hasTransparency = false;
|
|
|
|
/**
|
|
* 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;
|
|
|
|
/**
|
|
* Owner of this tile. Do not change this property manually.
|
|
* @member {OpenSeadragon.TiledImage}
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
this.tiledImage = null;
|
|
/**
|
|
* Array of cached tile data associated with the tile.
|
|
* @member {Object}
|
|
* @private
|
|
*/
|
|
this._caches = {};
|
|
/**
|
|
* Static Working Cache key to keep cached object (for swapping) when executing modifications.
|
|
* Uses unique ID to prevent sharing between other tiles:
|
|
* - if some tile initiates processing, all other tiles usually are skipped if they share the data
|
|
* - if someone tries to bypass sharing and process all tiles that share data, working caches would collide
|
|
* Note that $.now() is not sufficient, there might be tile created in the same millisecond.
|
|
* @member {String}
|
|
* @private
|
|
*/
|
|
this._wcKey = `w${_workingCacheIdDealer++}://` + this.originalCacheKey;
|
|
/**
|
|
* Processing flag, exempt the tile from removal when there are ongoing updates
|
|
* @member {Boolean|Number}
|
|
* @private
|
|
*/
|
|
this.processing = false;
|
|
/**
|
|
* Remembers last processing time of the tile, 1 if the tile has just been loaded.
|
|
* @private
|
|
*/
|
|
this.lastProcess = 0;
|
|
/**
|
|
* Transforming flag, exempt the tile from any processing since it is being transformed to a drawer-compatible
|
|
* format. This process cannot be paused and the tile cannot be touched during the process. Used externally.
|
|
* @member {Boolean|Number}
|
|
* @private
|
|
*/
|
|
this.transforming = 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;
|
|
},
|
|
|
|
/**
|
|
* The unique main cache key for this tile. Created automatically
|
|
* from the given tiledImage.source.getTileHashKey(...) implementation.
|
|
* @member {String} cacheKey
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
get cacheKey() {
|
|
return this._cKey;
|
|
},
|
|
set cacheKey(value) {
|
|
if (value === this.cacheKey) {
|
|
return;
|
|
}
|
|
const cache = this.getCache(value);
|
|
if (!cache) {
|
|
// It's better to first set cache, then change the key to existing one. Warn if otherwise.
|
|
$.console.warn("[Tile.cacheKey] should not be set manually. Use addCache() with setAsMain=true.");
|
|
}
|
|
this._updateMainCacheKey(value);
|
|
},
|
|
|
|
/**
|
|
* By default equal to tile.cacheKey, marks a cache associated with this tile
|
|
* that holds the cache original data (it was loaded with). In case you
|
|
* change the tile data, the tile original data should be left with the cache
|
|
* 'originalCacheKey' and the new, modified data should be stored in cache 'cacheKey'.
|
|
* This key is used in cache resolution: in case new tile data is requested, if
|
|
* this cache key exists in the cache it is loaded.
|
|
* @member {String} originalCacheKey
|
|
* @memberof OpenSeadragon.Tile#
|
|
*/
|
|
set originalCacheKey(value) {
|
|
throw "Original Cache Key cannot be managed manually!";
|
|
},
|
|
get originalCacheKey() {
|
|
return this._ocKey;
|
|
},
|
|
|
|
/**
|
|
* The Image object for this tile.
|
|
* @member {Object} image
|
|
* @memberof OpenSeadragon.Tile#
|
|
* @deprecated
|
|
* @returns {Image}
|
|
*/
|
|
get image() {
|
|
$.console.error("[Tile.image] property has been deprecated. Use [Tile.getData] instead.");
|
|
return this.getImage();
|
|
},
|
|
|
|
/**
|
|
* The URL of this tile's image.
|
|
* @member {String} url
|
|
* @memberof OpenSeadragon.Tile#
|
|
* @deprecated
|
|
* @returns {String}
|
|
*/
|
|
get url() {
|
|
$.console.error("[Tile.url] property has been deprecated. Use [Tile.getUrl] instead.");
|
|
return this.getUrl();
|
|
},
|
|
|
|
/**
|
|
* The HTML div element for this tile
|
|
* @member {Element} element
|
|
* @memberof OpenSeadragon.Tile#
|
|
* @deprecated
|
|
*/
|
|
get element() {
|
|
$.console.error("Tile::element property is deprecated. Use cache API instead. Moreover, this property might be unstable.");
|
|
const cache = this.getCache();
|
|
if (!cache || !cache.loaded) {
|
|
return null;
|
|
}
|
|
if (cache.type !== OpenSeadragon.HTMLDrawer.canvasCacheType || cache.type !== OpenSeadragon.HTMLDrawer.imageCacheType) {
|
|
$.console.error("Access to HtmlDrawer property via Tile instance: HTMLDrawer must be used!");
|
|
return null;
|
|
}
|
|
return cache.data.element;
|
|
},
|
|
|
|
/**
|
|
* The HTML img element for this tile.
|
|
* @member {Element} imgElement
|
|
* @memberof OpenSeadragon.Tile#
|
|
* @deprecated
|
|
*/
|
|
get imgElement() {
|
|
$.console.error("Tile::imgElement property is deprecated. Use cache API instead. Moreover, this property might be unstable.");
|
|
const cache = this.getCache();
|
|
if (!cache || !cache.loaded) {
|
|
return null;
|
|
}
|
|
if (cache.type !== OpenSeadragon.HTMLDrawer.canvasCacheType || cache.type !== OpenSeadragon.HTMLDrawer.imageCacheType) {
|
|
$.console.error("Access to HtmlDrawer property via Tile instance: HTMLDrawer must be used!");
|
|
return null;
|
|
}
|
|
return cache.data.imgElement;
|
|
},
|
|
|
|
/**
|
|
* The alias of this.element.style.
|
|
* @member {String} style
|
|
* @memberof OpenSeadragon.Tile#
|
|
* @deprecated
|
|
*/
|
|
get style() {
|
|
$.console.error("Tile::style property is deprecated. Use cache API instead. Moreover, this property might be unstable.");
|
|
const cache = this.getCache();
|
|
if (!cache || !cache.loaded) {
|
|
return null;
|
|
}
|
|
if (cache.type !== OpenSeadragon.HTMLDrawer.canvasCacheType || cache.type !== OpenSeadragon.HTMLDrawer.imageCacheType) {
|
|
$.console.error("Access to HtmlDrawer property via Tile instance: HTMLDrawer must be used!");
|
|
return null;
|
|
}
|
|
return cache.data.style;
|
|
},
|
|
|
|
/**
|
|
* Get the Image object for this tile.
|
|
* @returns {?Image}
|
|
*/
|
|
getImage: function() {
|
|
$.console.error("[Tile.getImage] property has been deprecated. Use [Tile.getData] instead.");
|
|
//this method used to ensure the underlying data model conformed to given type - convert instead of getData()
|
|
const cache = this.getCache(this.cacheKey);
|
|
if (!cache) {
|
|
return undefined;
|
|
}
|
|
cache.transformTo("image");
|
|
return cache.data;
|
|
},
|
|
|
|
/**
|
|
* Get the url string for this tile.
|
|
* @returns {String}
|
|
*/
|
|
getUrl: function() {
|
|
if (typeof this._url === 'function') {
|
|
return this._url();
|
|
}
|
|
|
|
return this._url;
|
|
},
|
|
|
|
/**
|
|
* Get the CanvasRenderingContext2D instance for tile image data drawn
|
|
* onto Canvas if enabled and available
|
|
* @returns {?CanvasRenderingContext2D}
|
|
*/
|
|
getCanvasContext: function() {
|
|
$.console.error("[Tile.getCanvasContext] property has been deprecated. Use [Tile.getData] instead.");
|
|
//this method used to ensure the underlying data model conformed to given type - convert instead of getData()
|
|
const cache = this.getCache(this.cacheKey);
|
|
if (!cache) {
|
|
return undefined;
|
|
}
|
|
cache.transformTo("context2d");
|
|
return cache.data;
|
|
},
|
|
|
|
/**
|
|
* The context2D of this tile if it is provided directly by the tile source.
|
|
* @deprecated
|
|
* @type {CanvasRenderingContext2D} context2D
|
|
*/
|
|
get context2D() {
|
|
$.console.error("[Tile.context2D] property has been deprecated. Use [Tile.getData] instead.");
|
|
return this.getCanvasContext();
|
|
},
|
|
|
|
/**
|
|
* The context2D of this tile if it is provided directly by the tile source.
|
|
* @deprecated
|
|
*/
|
|
set context2D(value) {
|
|
$.console.error("[Tile.context2D] property has been deprecated. Use [Tile.setData] within dedicated update event instead.");
|
|
this.setData(value, "context2d");
|
|
this.updateRenderTarget();
|
|
},
|
|
|
|
/**
|
|
* The default cache for this tile.
|
|
* @deprecated
|
|
* @type OpenSeadragon.CacheRecord
|
|
*/
|
|
get cacheImageRecord() {
|
|
$.console.error("[Tile.cacheImageRecord] property has been deprecated. Use Tile::getCache.");
|
|
return this.getCache(this.cacheKey);
|
|
},
|
|
|
|
/**
|
|
* The default cache for this tile.
|
|
* @deprecated
|
|
*/
|
|
set cacheImageRecord(value) {
|
|
$.console.error("[Tile.cacheImageRecord] property has been deprecated. Use Tile::addCache.");
|
|
const cache = this._caches[this.cacheKey];
|
|
|
|
if (cache) {
|
|
this.removeCache(this.cacheKey);
|
|
}
|
|
|
|
if (value) {
|
|
if (value.loaded) {
|
|
this.addCache(this.cacheKey, value.data, value.type, true, false);
|
|
} else {
|
|
value.await().then(x => this.addCache(this.cacheKey, x, value.type, true, false));
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Get the data to render for this tile. If no conversion is necessary, get a reference. Else, get a copy
|
|
* of the data as desired type. This means that data modification _might_ be reflected on the tile, but
|
|
* it is not guaranteed. Use tile.setData() to ensure changes are reflected.
|
|
* @param {string} type data type to require
|
|
* @return {OpenSeadragon.Promise<*>} data in the desired type, or resolved promise with udnefined if the
|
|
* associated cache object is out of its lifespan
|
|
*/
|
|
getData: function(type) {
|
|
if (!this.tiledImage) {
|
|
return $.Promise.resolve(); //async can access outside its lifetime
|
|
}
|
|
return this._getOrCreateWorkingCacheData(type);
|
|
},
|
|
|
|
/**
|
|
* Restore the original data data for this tile
|
|
* @param {boolean} freeIfUnused if true, restoration frees cache along the way of the tile lifecycle
|
|
*/
|
|
restore: function(freeIfUnused = true) {
|
|
if (!this.tiledImage) {
|
|
return; //async context can access the tile outside its lifetime
|
|
}
|
|
|
|
this.__restoreRequestedFree = freeIfUnused;
|
|
if (this.originalCacheKey !== this.cacheKey) {
|
|
this.__restore = true;
|
|
}
|
|
// Somebody has called restore on this tile, make sure we delete working cache in case there was some
|
|
this.removeCache(this._wcKey, true);
|
|
},
|
|
|
|
/**
|
|
* Set main cache data
|
|
* @param {*} value
|
|
* @param {?string} type data type to require
|
|
* @return {OpenSeadragon.Promise<*>}
|
|
*/
|
|
setData: function(value, type) {
|
|
if (!this.tiledImage) {
|
|
return Promise.resolve(); //async context can access the tile outside its lifetime
|
|
}
|
|
|
|
let cache = this.getCache(this._wcKey);
|
|
if (!cache) {
|
|
this._getOrCreateWorkingCacheData(undefined);
|
|
cache = this.getCache(this._wcKey);
|
|
}
|
|
return cache.setDataAs(value, type);
|
|
},
|
|
|
|
|
|
/**
|
|
* Optimizazion: prepare target cache for subsequent use in rendering, and perform updateRenderTarget()
|
|
* The main idea of this function is that it must be ASYNCHRONOUS since there might be additional processing
|
|
* happening due to underlying drawer requirements.
|
|
* @private
|
|
* @return {OpenSeadragon.Promise<?>}
|
|
*/
|
|
updateRenderTargetWithDataTransform: function (drawerId, supportedFormats, usePrivateCache, processTimestamp) {
|
|
// Now, if working cache exists, we set main cache to the working cache --> prepare
|
|
let cache = this.getCache(this._wcKey);
|
|
if (cache) {
|
|
return cache.prepareForRendering(drawerId, supportedFormats, usePrivateCache).then(c => {
|
|
if (c && processTimestamp && this.processing === processTimestamp) {
|
|
this.updateRenderTarget();
|
|
}
|
|
});
|
|
}
|
|
|
|
// If we requested restore, perform now
|
|
if (this.__restore) {
|
|
cache = this.getCache(this.originalCacheKey);
|
|
|
|
this.tiledImage._tileCache.restoreTilesThatShareOriginalCache(
|
|
this, cache, this.__restoreRequestedFree
|
|
);
|
|
this.__restore = false;
|
|
return cache.prepareForRendering(drawerId, supportedFormats, usePrivateCache).then((c) => {
|
|
if (c && processTimestamp && this.processing === processTimestamp) {
|
|
this.updateRenderTarget();
|
|
}
|
|
});
|
|
}
|
|
|
|
cache = this.getCache();
|
|
return cache.prepareForRendering(drawerId, supportedFormats, usePrivateCache);
|
|
},
|
|
|
|
/**
|
|
* Resolves render target: changes might've been made to the rendering pipeline:
|
|
* - working cache is set: make sure main cache will be replaced
|
|
* - working cache is unset: make sure main cache either gets updated to original data or stays (based on this.__restore)
|
|
*
|
|
* The main idea of this function is that it is SYNCHRONOUS, e.g. can perform in-place cache swap to update
|
|
* before any rendering occurs.
|
|
* @private
|
|
* @return
|
|
*/
|
|
updateRenderTarget: function (_allowTileNotLoaded = false) {
|
|
// Check if we asked for restore, and make sure we set it to false since we update the whole cache state
|
|
const requestedRestore = this.__restore;
|
|
this.__restore = false;
|
|
|
|
// Now, if working cache exists, we set main cache to the working cache, since it has been updated
|
|
// if restore() was called last, then working cache was deleted (does not exist)
|
|
const cache = this.getCache(this._wcKey);
|
|
if (cache) {
|
|
let newCacheKey = this.cacheKey === this.originalCacheKey ? "mod://" + this.originalCacheKey : this.cacheKey;
|
|
this.tiledImage._tileCache.consumeCache({
|
|
tile: this,
|
|
victimKey: this._wcKey,
|
|
consumerKey: newCacheKey,
|
|
tileAllowNotLoaded: _allowTileNotLoaded
|
|
});
|
|
this.cacheKey = newCacheKey;
|
|
} else if (requestedRestore) {
|
|
// If we requested restore, perform now
|
|
this.tiledImage._tileCache.restoreTilesThatShareOriginalCache(
|
|
this, this.getCache(this.originalCacheKey), this.__restoreRequestedFree
|
|
);
|
|
}
|
|
// If transforming was set, we finished: drawer transform always finishes with updateRenderTarget()
|
|
this.transforming = false;
|
|
},
|
|
|
|
/**
|
|
* Read tile cache data object (CacheRecord)
|
|
* @param {string} [key=this.cacheKey] cache key to read that belongs to this tile
|
|
* @return {OpenSeadragon.CacheRecord}
|
|
*/
|
|
getCache: function(key = this._cKey) {
|
|
const cache = this._caches[key];
|
|
if (cache) {
|
|
cache.withTileReference(this);
|
|
}
|
|
return cache;
|
|
},
|
|
|
|
/**
|
|
* Set tile cache, possibly multiple with custom key
|
|
* @param {string} key cache key, must be unique (we recommend re-using this.cacheTile
|
|
* value and extend it with some another unique content, by default overrides the existing
|
|
* main cache used for drawing, if not existing.
|
|
* @param {*} data this data will be IGNORED if cache already exists; therefore if
|
|
* `typeof data === 'function'` holds (both async and normal functions), the data is called to obtain
|
|
* the data item: this is an optimization to load data only when necessary.
|
|
* @param {string} [type=undefined] data type, will be guessed if not provided (not recommended),
|
|
* if data is a callback the type is a mandatory field, not setting it results in undefined behaviour
|
|
* @param {boolean} [setAsMain=false] if true, the key will be set as the tile.cacheKey,
|
|
* no effect if key === this.cacheKey
|
|
* @param [_safely=true] private
|
|
* @returns {OpenSeadragon.CacheRecord|null} - The cache record the tile was attached to.
|
|
*/
|
|
addCache: function(key, data, type = undefined, setAsMain = false, _safely = true) {
|
|
if (!this.tiledImage) {
|
|
return null; //async can access outside its lifetime
|
|
}
|
|
|
|
if (!type) {
|
|
if (!this.__typeWarningReported) {
|
|
$.console.warn(this, "[Tile.addCache] called without type specification. " +
|
|
"Automated deduction is potentially unsafe: prefer specification of data type explicitly.");
|
|
this.__typeWarningReported = true;
|
|
}
|
|
if (typeof data === 'function') {
|
|
$.console.error("[TileCache.cacheTile] options.data as a callback requires type argument! Current is " + type);
|
|
}
|
|
type = $.convertor.guessType(data);
|
|
}
|
|
|
|
const writesToRenderingCache = key === this.cacheKey;
|
|
if (writesToRenderingCache && _safely) {
|
|
// Need to get the supported type for rendering out of the active drawer.
|
|
const supportedTypes = this.tiledImage.viewer.drawer.getSupportedDataFormats();
|
|
const conversion = $.convertor.getConversionPath(type, supportedTypes);
|
|
$.console.assert(conversion, "[Tile.addCache] data was set for the default tile cache we are unable" +
|
|
"to render. Make sure OpenSeadragon.convertor was taught to convert to (one of): " + type);
|
|
}
|
|
|
|
const cachedItem = this.tiledImage._tileCache.cacheTile({
|
|
data: data,
|
|
dataType: type,
|
|
tile: this,
|
|
cacheKey: key,
|
|
//todo consider caching this on a tiled image level
|
|
cutoff: this.__cutoff || this.tiledImage.source.getClosestLevel(),
|
|
});
|
|
const havingRecord = this._caches[key];
|
|
if (havingRecord !== cachedItem) {
|
|
this._caches[key] = cachedItem;
|
|
}
|
|
|
|
// Update cache key if differs and main requested
|
|
if (!writesToRenderingCache && setAsMain) {
|
|
this._updateMainCacheKey(key);
|
|
}
|
|
return cachedItem;
|
|
},
|
|
|
|
/**
|
|
* Sets the main cache key for this tile and
|
|
* performs necessary updates
|
|
* @param value
|
|
* @private
|
|
*/
|
|
_updateMainCacheKey: function(value) {
|
|
let ref = this._caches[this._cKey];
|
|
if (ref) {
|
|
// make sure we free drawer internal cache
|
|
ref.destroyInternalCache();
|
|
}
|
|
this._cKey = value;
|
|
// we do not trigger redraw, this is handled within cache
|
|
// as drawers request data for drawing
|
|
},
|
|
|
|
/**
|
|
* Initializes working cache if it does not exist.
|
|
* @param {string|undefined} type initial cache type to create
|
|
* @return {OpenSeadragon.Promise<?>} data-awaiting promise with the cache data
|
|
* @private
|
|
*/
|
|
_getOrCreateWorkingCacheData: function (type) {
|
|
const cache = this.getCache(this._wcKey);
|
|
if (!cache) {
|
|
const targetCopyKey = this.__restore ? this.originalCacheKey : this.cacheKey;
|
|
const origCache = this.getCache(targetCopyKey);
|
|
if (!origCache) {
|
|
$.console.error("[Tile::getData] There is no cache available for tile with key %s", targetCopyKey);
|
|
}
|
|
// Here ensure type is defined, rquired by data callbacks
|
|
type = type || origCache.type;
|
|
|
|
// Here we use extensively ability to call addCache with callback: working cache is created only if not
|
|
// already in memory (=> shared).
|
|
return this.addCache(this._wcKey, () => origCache.getDataAs(type, true), type, false, false).await();
|
|
}
|
|
return cache.getDataAs(type, false);
|
|
},
|
|
|
|
/**
|
|
* Get the number of caches available to this tile
|
|
* @returns {number} number of caches
|
|
*/
|
|
getCacheSize: function() {
|
|
return Object.values(this._caches).length;
|
|
},
|
|
|
|
/**
|
|
* Free tile cache. Removes by default the cache record if no other tile uses it.
|
|
* @param {string} key cache key, required
|
|
* @param {boolean} [freeIfUnused=true] set to false if zombie should be created
|
|
*/
|
|
removeCache: function(key, freeIfUnused = true) {
|
|
if (!this._caches[key]) {
|
|
// try to erase anyway in case the cache got stuck in memory
|
|
this.tiledImage._tileCache.unloadCacheForTile(this, key, freeIfUnused, true);
|
|
return;
|
|
}
|
|
|
|
const currentMainKey = this.cacheKey,
|
|
originalDataKey = this.originalCacheKey,
|
|
sameBuiltinKeys = currentMainKey === originalDataKey;
|
|
|
|
if (!sameBuiltinKeys && originalDataKey === key) {
|
|
$.console.warn("[Tile.removeCache] original data must not be manually deleted: other parts of the code might rely on it!",
|
|
"If you want the tile not to preserve the original data, toggle of data perseverance in tile.setData().");
|
|
return;
|
|
}
|
|
|
|
if (currentMainKey === key) {
|
|
if (!sameBuiltinKeys && this._caches[originalDataKey]) {
|
|
// if we have original data let's revert back
|
|
this._updateMainCacheKey(originalDataKey);
|
|
} else {
|
|
$.console.warn("[Tile.removeCache] trying to remove the only cache that can be used to draw the tile!",
|
|
"If you want to remove the main cache, first set different cache as main with tile.addCache()");
|
|
return;
|
|
}
|
|
}
|
|
if (this.tiledImage._tileCache.unloadCacheForTile(this, key, freeIfUnused, false)) {
|
|
//if we managed to free tile from record, we are sure we decreased cache count
|
|
delete this._caches[key];
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Get the ratio between current and original size.
|
|
* @function
|
|
* @deprecated
|
|
* @returns {number}
|
|
*/
|
|
getScaleForEdgeSmoothing: function() {
|
|
// getCanvasContext is deprecated and so should be this method.
|
|
$.console.warn("[Tile.getScaleForEdgeSmoothing] is deprecated, the following error is the consequence:");
|
|
const context = this.getCanvasContext();
|
|
if (!context) {
|
|
$.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.
|
|
* @returns {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;
|
|
})
|
|
);
|
|
},
|
|
|
|
/**
|
|
* Reflect that a cache object was renamed. Called internally from TileCache.
|
|
* Do NOT call manually.
|
|
* @function
|
|
* @private
|
|
*/
|
|
reflectCacheRenamed: function (oldKey, newKey) {
|
|
let cache = this._caches[oldKey];
|
|
if (!cache) {
|
|
return; // nothing to fix
|
|
}
|
|
// Do update via private refs, old key no longer exists in cache
|
|
if (oldKey === this._ocKey) {
|
|
this._ocKey = newKey;
|
|
}
|
|
if (oldKey === this._cKey) {
|
|
this._cKey = newKey;
|
|
}
|
|
// Working key is never updated, it will be invalidated (but do not dereference cache, just fix the pointers)
|
|
this._caches[newKey] = cache;
|
|
delete this._caches[oldKey];
|
|
},
|
|
|
|
/**
|
|
* Removes tile from the system: it will still be present in the
|
|
* OSD memory, but marked as loaded=false, and its data will be erased.
|
|
* @param {boolean} [erase=false]
|
|
*/
|
|
unload: function(erase = false) {
|
|
if (!this.loaded) {
|
|
return;
|
|
}
|
|
this.tiledImage._tileCache.unloadTile(this, erase);
|
|
},
|
|
|
|
/**
|
|
* this method shall be called only by cache system when the tile is already empty of data
|
|
* @private
|
|
*/
|
|
_unload: function () {
|
|
this.tiledImage = null;
|
|
this._caches = {};
|
|
this._cacheSize = 0;
|
|
this.element = null;
|
|
this.imgElement = null;
|
|
this.loaded = false;
|
|
this.loading = false;
|
|
this._cKey = this._ocKey;
|
|
}
|
|
};
|
|
|
|
}( OpenSeadragon ));
|