/* * OpenSeadragon - ImageTileSource * * 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 ($) { /** * @class ImageTileSource * @classdesc The ImageTileSource allows a simple image to be loaded * into an OpenSeadragon Viewer. * There are 2 ways to open an ImageTileSource: * 1. viewer.open({type: 'image', url: fooUrl}); * 2. viewer.open(new OpenSeadragon.ImageTileSource({url: fooUrl})); * * With the first syntax, the crossOriginPolicy, ajaxWithCredentials and * useCanvas options are inherited from the viewer if they are not * specified directly in the options object. * * @memberof OpenSeadragon * @extends OpenSeadragon.TileSource * @param {Object} options Options object. * @param {String} options.url URL of the image * @param {Boolean} [options.buildPyramid=true] If set to true (default), a * pyramid will be built internally to provide a better downsampling. * @param {String|Boolean} [options.crossOriginPolicy=false] Valid values are * 'Anonymous', 'use-credentials', and false. If false, image requests will * not use CORS preventing internal pyramid building for images from other * domains. * @param {String|Boolean} [options.ajaxWithCredentials=false] Whether to set * the withCredentials XHR flag for AJAX requests (when loading tile sources). * @param {Boolean} [options.useCanvas=true] Set to false to prevent any use * of the canvas API. */ $.ImageTileSource = class extends $.TileSource { constructor(props) { super($.extend({ buildPyramid: true, crossOriginPolicy: false, ajaxWithCredentials: false, }, props)); } /** * Determine if the data and/or url imply the image service is supported by * this tile source. * @function * @param {Object|Array} data * @param {String} url - optional */ supports(data, url) { return data.type && data.type === "image"; } /** * * @function * @param {Object} options - the options * @param {String} dataUrl - the url the image was retrieved from, if any. * @param {String} postData - HTTP POST data in k=v&k2=v2... form or null * @returns {Object} options - A dictionary of keyword arguments sufficient * to configure this tile sources constructor. */ configure(options, dataUrl, postData) { return options; } /** * Responsible for retrieving, and caching the * image metadata pertinent to this TileSources implementation. * @function * @param {String} url * @throws {Error} */ getImageInfo(url) { const image = new Image(), _this = this; if (this.crossOriginPolicy) { image.crossOrigin = this.crossOriginPolicy; } if (this.ajaxWithCredentials) { image.useCredentials = this.ajaxWithCredentials; } $.addEvent(image, 'load', function () { _this.width = image.naturalWidth; _this.height = image.naturalHeight; _this.aspectRatio = _this.width / _this.height; _this.dimensions = new $.Point(_this.width, _this.height); _this._tileWidth = _this.width; _this._tileHeight = _this.height; _this.tileOverlap = 0; _this.minLevel = 0; _this.image = image; _this.levels = _this._buildLevels(image); _this.maxLevel = _this.levels.length - 1; _this.ready = true; // Note: this event is documented elsewhere, in TileSource _this.raiseEvent('ready', {tileSource: _this}); }); $.addEvent(image, 'error', function () { _this.image = null; // Note: this event is documented elsewhere, in TileSource _this.raiseEvent('open-failed', { message: "Error loading image at " + url, source: url }); }); image.src = url; } /** * @function * @param {Number} level */ getLevelScale(level) { let levelScale = NaN; if (level >= this.minLevel && level <= this.maxLevel) { levelScale = this.levels[level].width / this.levels[this.maxLevel].width; } return levelScale; } /** * @function * @param {Number} level */ getNumTiles(level) { if (this.getLevelScale(level)) { return new $.Point(1, 1); } return new $.Point(0, 0); } /** * Retrieves a tile url * @function * @param {Number} level Level of the tile * @param {Number} x x coordinate of the tile * @param {Number} y y coordinate of the tile */ getTileUrl(level, x, y) { if (level === this.maxLevel) { return this.url; //for original image, preserve url } //make up url by positional args return `${this.url}?l=${level}&x=${x}&y=${y}`; } /** * Equality comparator */ equals(otherSource) { return this.url === otherSource.url; } getTilePostData(level, x, y) { return {level: level, x: x, y: y}; } /** * Retrieves a tile context 2D * @deprecated */ getContext2D(level, x, y) { $.console.error('Using [TiledImage.getContext2D] (for plain images only) is deprecated. ' + 'Use overridden downloadTileStart (https://openseadragon.github.io/examples/advanced-data-model/) instead.'); return this._createContext2D(); } downloadTileStart(job) { const tileData = job.postData; if (tileData.level === this.maxLevel) { job.finish(this.image, null, "image"); return; } if (tileData.level >= this.minLevel && tileData.level <= this.maxLevel) { const levelData = this.levels[tileData.level]; const context = this._createContext2D(this.image, levelData.width, levelData.height); job.finish(context, null, "context2d"); return; } job.fail(`Invalid level ${tileData.level} for plain image source. Did you forget to set buildPyramid=true?`); } downloadTileAbort(job) { //no-op } // private // // Builds the different levels of the pyramid if possible // (i.e. if canvas API enabled and no canvas tainting issue). _buildLevels(image) { const levels = [{ url: image.src, width: image.naturalWidth, height: image.naturalHeight }]; if (!this.buildPyramid || !$.supportsCanvas || !this.useCanvas) { return levels; } let currentWidth = image.naturalWidth, currentHeight = image.naturalHeight; // We build smaller levels until either width or height becomes // 2 pixel wide. while (currentWidth >= 2 && currentHeight >= 2) { currentWidth = Math.floor(currentWidth / 2); currentHeight = Math.floor(currentHeight / 2); levels.push({ width: currentWidth, height: currentHeight, }); } return levels.reverse(); } _createContext2D(data, w, h) { const canvas = document.createElement("canvas"), context = canvas.getContext("2d"); canvas.width = w; canvas.height = h; context.drawImage(data, 0, 0, w, h); return context; } }; }(OpenSeadragon));