diff --git a/Gruntfile.js b/Gruntfile.js index 7bab26d5..29e37cda 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -192,7 +192,12 @@ module.exports = function(grunt) { server: { options: { port: 8000, - base: "." + base: { + path: ".", + options: { + stylesheet: 'style.css' + } + } } } }, diff --git a/src/control.js b/src/control.js index 3cba9943..3428befd 100644 --- a/src/control.js +++ b/src/control.js @@ -194,11 +194,7 @@ $.Control.prototype = { * @param {Number} opactiy - a value between 1 and 0 inclusively. */ setOpacity: function( opacity ) { - if ( this.element[ $.SIGNAL ] && $.Browser.vendor === $.BROWSERS.IE ) { - $.setElementOpacity( this.element, opacity, true ); - } else { - $.setElementOpacity( this.wrapper, opacity, true ); - } + $.setElementOpacity( this.wrapper, opacity, true ); } }; diff --git a/src/imageloader.js b/src/imageloader.js index db5c7440..59ff63c4 100644 --- a/src/imageloader.js +++ b/src/imageloader.js @@ -231,6 +231,13 @@ $.ImageLoader.prototype = { } }, + /** + * @returns {boolean} true if a job can be submitted + */ + canAcceptNewJob() { + return !this.jobLimit || this.jobsInProgress < this.jobLimit; + }, + /** * Clear any unstarted image loading jobs from the queue. * @method @@ -264,14 +271,14 @@ function completeJob(loader, job, callback) { loader.jobsInProgress--; - if ((!loader.jobLimit || loader.jobsInProgress < loader.jobLimit) && loader.jobQueue.length > 0) { + if (loader.canAcceptNewJob() && loader.jobQueue.length > 0) { nextJob = loader.jobQueue.shift(); nextJob.start(); loader.jobsInProgress++; } if (loader.tileRetryMax > 0 && loader.jobQueue.length === 0) { - if ((!loader.jobLimit || loader.jobsInProgress < loader.jobLimit) && loader.failedTiles.length > 0) { + if (loader.canAcceptNewJob() && loader.failedTiles.length > 0) { nextJob = loader.failedTiles.shift(); setTimeout(function () { nextJob.start(); diff --git a/src/openseadragon.js b/src/openseadragon.js index 93a079e5..f24b1c54 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -1450,12 +1450,14 @@ function OpenSeadragon( options ){ /** - * TODO: get rid of this. I can't see how it's required at all. Looks - * like an early legacy code artifact. - * @static + * TODO: remove soon + * @deprecated * @ignore */ - SIGNAL: "----seadragon----", + get SIGNAL() { + $.console.error("OpenSeadragon.SIGNAL is deprecated and should not be used."); + return "----seadragon----"; + }, /** @@ -2269,29 +2271,6 @@ function OpenSeadragon( options ){ event.stopPropagation(); }, - // Deprecated - createCallback: function( object, method ) { - //TODO: This pattern is painful to use and debug. It's much cleaner - // to use pinning plus anonymous functions. Get rid of this - // pattern! - console.error('The createCallback function is deprecated and will be removed in future versions. Please use alternativeFunction instead.'); - var initialArgs = [], - i; - for ( i = 2; i < arguments.length; i++ ) { - initialArgs.push( arguments[ i ] ); - } - - return function() { - var args = initialArgs.concat( [] ), - i; - for ( i = 0; i < arguments.length; i++ ) { - args.push( arguments[ i ] ); - } - - return method.apply( object, args ); - }; - }, - /** * Retrieves the value of a url parameter from the window.location string. @@ -2632,8 +2611,72 @@ function OpenSeadragon( options ){ setImageFormatsSupported: function(formats) { // eslint-disable-next-line no-use-before-define $.extend(FILEFORMATS, formats); - } + }, + + //@private, runs non-invasive update of all tiles given in the list + invalidateTilesLater: function(tileList, tStamp, viewer, batch = 999) { + let i = 0; + let interval = setInterval(() => { + let tile = tileList[i]; + while (tile && !tile.loaded) { + tile = tileList[i++]; + } + + if (i >= tileList.length) { + console.log(":::::::::::::::::::::::::::::end"); + clearInterval(interval); + return; + } + const tiledImage = tile.tiledImage; + if (tiledImage.invalidatedAt > tStamp) { + console.log(":::::::::::::::::::::::::::::end"); + clearInterval(interval); + return; + } + let count = 1; + for (; i < tileList.length; i++) { + const tile = tileList[i]; + if (!tile.loaded) { + console.log("skipping tile: not loaded", tile); + continue; + } + + const tileCache = tile.getCache(); + if (tileCache._updateStamp >= tStamp) { + continue; + } + // prevents other tiles sharing the cache (~the key) from event + //todo works unless the cache key CHANGES by plugins + // - either prevent + // - or ...? + tileCache._updateStamp = tStamp; + $.invalidateTile(tile, tile.tiledImage, tStamp, viewer, i); + if (++count > batch) { + break; + } + } + }, 5); //how to select the delay...?? todo: just try out + }, + + //@private, runs tile update event + invalidateTile: function(tile, image, tStamp, viewer, i = -1) { + console.log(i, "tile: process", tile); + + //todo consider also ability to cut execution of ongoing event if outdated by providing comparison timestamp + viewer.raiseEventAwaiting('tile-needs-update', { + tile: tile, + tiledImage: image, + }).then(() => { + //TODO IF NOT CACHE ERRO + const newCache = tile.getCache(); + if (newCache) { + newCache._updateStamp = tStamp; + } else { + $.console.error("After an update, the tile %s has not cache data! Check handlers on 'tile-needs-update' evemt!", tile); + } + }); + } }); @@ -2903,13 +2946,10 @@ function OpenSeadragon( options ){ } const promise = function () {}; //TODO consider supplying promise API via callbacks/polyfill - promise.prototype.then = function () { - throw "OpenSeadragon needs promises API. Your browser do not support promises. You can add polyfill.js to import promises."; - }; - promise.prototype.catch = function () { - throw "OpenSeadragon needs promises API. Your browser do not support promises. You can add polyfill.js to import promises."; - }; - promise.prototype.finally = function () { + promise.prototype.then = + promise.prototype.catch = + promise.prototype.finally = + promise.all = promise.race = function () { throw "OpenSeadragon needs promises API. Your browser do not support promises. You can add polyfill.js to import promises."; }; return promise; diff --git a/src/tile.js b/src/tile.js index 97eab0c5..6a2cfbdd 100644 --- a/src/tile.js +++ b/src/tile.js @@ -266,18 +266,13 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, aja * @memberof OpenSeadragon.Tile# */ this.isBottomMost = false; + /** - * FIXME: I would like to remove this reference but there is no way - * to remove it since tile-unloaded event requires the tiledImage reference. - * And, unloadTilesFor(tiledImage) in cache uses it too. Storing the - * reference on a tile level rather than cache level is more efficient. - * - * Owner of this tile. + * 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} _caches @@ -385,7 +380,7 @@ $.Tile.prototype = { * @returns {?Image} */ getImage: function() { - //TODO: after merge $.console.error("[Tile.getImage] property has been deprecated. Use [Tile.getData] instead."); + //TODO: after-merge-aiosa $.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) { @@ -413,7 +408,7 @@ $.Tile.prototype = { * @returns {?CanvasRenderingContext2D} */ getCanvasContext: function() { - //TODO: after merge $.console.error("[Tile.getCanvasContext] property has been deprecated. Use [Tile.getData] instead."); + //TODO: after-merge-aiosa $.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) { @@ -471,10 +466,14 @@ $.Tile.prototype = { /** * Get the data to render for this tile * @param {string} type data type to require - * @param {boolean?} [copy=this.loaded] whether to force copy retrieval + * @param {boolean?} [copy=true] whether to force copy retrieval * @return {*|undefined} data in the desired type, or undefined if a conversion is ongoing */ - getData: function(type, copy = this.loaded) { + getData: function(type, copy = true) { + if (!this.tiledImage) { + return null; //async can access outside its lifetime + } + //we return the data synchronously immediatelly (undefined if conversion happens) const cache = this.getCache(this.cacheKey); if (!cache) { @@ -491,6 +490,10 @@ $.Tile.prototype = { * @return {*|undefined} data in the desired type, or undefined if a conversion is ongoing */ getOriginalData: function(type, copy = true) { + if (!this.tiledImage) { + return null; //async can access outside its lifetime + } + //we return the data synchronously immediatelly (undefined if conversion happens) const cache = this.getCache(this.originalCacheKey); if (!cache) { @@ -509,6 +512,10 @@ $.Tile.prototype = { * to a new data. This makes the Tile assigned to two cache objects. */ setData: function(value, type, preserveOriginalData = true) { + if (!this.tiledImage) { + return null; //async can access outside its lifetime + } + if (preserveOriginalData && this.cacheKey === this.originalCacheKey) { //caches equality means we have only one cache: // change current pointer to a new cache and create it: new tiles will @@ -542,10 +549,13 @@ $.Tile.prototype = { * @param {*} data data to cache - this data will be sent to the TileSource API for refinement. * @param {?string} type data type, will be guessed if not provided * @param [_safely=true] private - * @param [_cutoff=0] private - * @returns {OpenSeadragon.CacheRecord} - The cache record the tile was attached to. + * @returns {OpenSeadragon.CacheRecord|null} - The cache record the tile was attached to. */ - setCache: function(key, data, type = undefined, _safely = true, _cutoff = 0) { + setCache: function(key, data, type = undefined, _safely = true) { + if (!this.tiledImage) { + return null; //async can access outside its lifetime + } + if (!type) { if (this.tiledImage && !this.tiledImage.__typeWarningReported) { $.console.warn(this, "[Tile.setCache] called without type specification. " + @@ -557,20 +567,22 @@ $.Tile.prototype = { const writesToRenderingCache = key === this.cacheKey; if (writesToRenderingCache && _safely) { - //todo later, we could have drawers register their supported rendering type - // and OpenSeadragon would check compatibility automatically, now we render - // using two main types so we check their ability + //todo after-merge-aiosa decide dynamically const conversion = $.convertor.getConversionPath(type, "context2d"); $.console.assert(conversion, "[Tile.setCache] data was set for the default tile cache we are unable" + "to render. Make sure OpenSeadragon.convertor was taught to convert type: " + type); } + if (!this.__cutoff) { + //todo consider caching this on a tiled image level.. + this.__cutoff = this.tiledImage.source.getClosestLevel(); + } const cachedItem = this.tiledImage._tileCache.cacheTile({ data: data, dataType: type, tile: this, cacheKey: key, - cutoff: _cutoff + cutoff: this.__cutoff, }); const havingRecord = this._caches[key]; if (havingRecord !== cachedItem) { @@ -631,8 +643,13 @@ $.Tile.prototype = { const _this = this; // This gives the application a chance to make image manipulation // changes as we are rendering the image - drawingHandler({context: context, tile: this, get rendered() { - $.console.warn("[tile-drawing rendered] property is deprecated. Use Tile data API."); + drawingHandler({context: context, get tile() { + $.console.warn("[tile-drawing] event is deprecated. " + + "Use 'tile-drawn' event instead."); + return _this; + }, get rendered() { + $.console.warn("[tile-drawing] rendered property and this event itself are deprecated. " + + "Use Tile data API and `tile-drawn` event instead."); const context = _this.getCanvasContext(); if (!context) { $.console.warn( diff --git a/src/tilecache.js b/src/tilecache.js index d409f08d..a686f1e9 100644 --- a/src/tilecache.js +++ b/src/tilecache.js @@ -581,6 +581,18 @@ $.TileCache = class { } } + /** + * Returns reference to all tiles loaded by a particular + * tiled image item + * @param {OpenSeadragon.TiledImage|Boolean} tiledImage true for all, reference for selection + */ + getLoadedTilesFor(tiledImage) { + if (tiledImage === true) { + return [...this._tilesLoaded]; + } + return this._tilesLoaded.filter(tile => tile.tiledImage === tiledImage); + } + /** * Get cache record (might be a unattached record, i.e. a zombie) * @param cacheKey diff --git a/src/tiledimage.js b/src/tiledimage.js index 411edae1..e757d24b 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -241,6 +241,7 @@ $.TiledImage = function( options ) { * @property {OpenSeadragon.Tile} context - The HTML canvas context being drawn into. * @property {OpenSeadragon.Tile} rendered - The HTML canvas context containing the tile imagery. * @property {?Object} userData - Arbitrary subscriber-defined object. + * @deprecated */ _this.viewer.raiseEvent('tile-drawing', $.extend({ tiledImage: _this @@ -290,6 +291,32 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag }); }, + /** + * Forces the system consider all tiles in this tiled image + * as outdated, and fire tile update event on relevant tiles + * Detailed description is available within the 'tile-needs-update' + * event. TODO: consider re-using update function instead? + * @param {boolean} [viewportOnly=false] optionally invalidate only viewport-visible tiles if true + * @param {number} [tStamp=OpenSeadragon.now()] optionally provide tStamp of the update event + */ + invalidate: function (viewportOnly, tStamp) { + tStamp = tStamp || $.now(); + this.invalidatedAt = tStamp; //todo document, or remove by something nicer + + //always invalidate active tiles + for (let tile of this.lastDrawn) { + $.invalidateTile(tile, this, tStamp, this.viewer); + } + //if not called from world or not desired, avoid update of offscreen data + if (viewportOnly) { + return; + } + //else update all tiles at some point, but by priority of access time + const tiles = this.tileCache.getLoadedTilesFor(this); + tiles.sort((a, b) => a.lastTouchTime - b.lastTouchTime); + $.invalidateTilesLater(tiles, tStamp, this.viewer); + }, + /** * Clears all tiles and triggers an update on the next call to * {@link OpenSeadragon.TiledImage#update}. @@ -1575,14 +1602,13 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag } let record = this._tileCache.getCacheRecord(tile.cacheKey); - const cutoff = this.source.getClosestLevel(); if (record) { //setup without calling tile loaded event! tile cache is ready for usage, tile.loading = true; tile.loaded = false; //set data as null, cache already has data, it does not overwrite - this._setTileLoaded(tile, null, cutoff, null, record.type, + this._setTileLoaded(tile, null, null, null, record.type, this.callTileLoadedWithCachedData); return true; } @@ -1594,7 +1620,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag tile.loading = true; tile.loaded = false; //set data as null, cache already has data, it does not overwrite - this._setTileLoaded(tile, null, cutoff, null, record.type); + this._setTileLoaded(tile, null, null, null, record.type); return true; } } @@ -1673,8 +1699,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag ajaxHeaders, sourceBounds, post, - tileSource.getTileHashKey(level, xMod, yMod, urlOrGetter, ajaxHeaders, post), - this + tileSource.getTileHashKey(level, xMod, yMod, urlOrGetter, ajaxHeaders, post) ); if (this.getFlip()) { @@ -1779,9 +1804,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag var _this = this, finish = function() { - var ccc = _this.source; - var cutoff = ccc.getClosestLevel(); - _this._setTileLoaded(tile, data, cutoff, tileRequest, dataType); + _this._setTileLoaded(tile, data, null, tileRequest, dataType); }; // Check if we're mid-update; this can happen on IE8 because image load events for @@ -1800,7 +1823,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag * @param {OpenSeadragon.Tile} tile * @param {*} data image data, the data sent to ImageJob.prototype.finish(), by default an Image object, * can be null: in that case, cache is assigned to a tile without further processing - * @param {?Number} cutoff + * @param {?Number} cutoff ignored, @deprecated * @param {?XMLHttpRequest} tileRequest * @param {?String} [dataType=undefined] data type, derived automatically if not set * @param {?Boolean} [withEvent=true] do not trigger event if true @@ -1808,7 +1831,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag _setTileLoaded: function(tile, data, cutoff, tileRequest, dataType, withEvent = true) { tile.tiledImage = this; //unloaded with tile.unload(), so we need to set it back // -> reason why it is not in the constructor - tile.setCache(tile.cacheKey, data, dataType, false, cutoff); + tile.setCache(tile.cacheKey, data, dataType, false); let resolver = null, increment = 0, @@ -1829,7 +1852,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag ); //make sure cache data is ready for drawing, if not, request the desired format const cache = tile.getCache(tile.cacheKey), - // TODO: dynamic type declaration from the drawer base class interface from v5.0 onwards + // TODO: after-merge-aiosa dynamic type declaration from the drawer base class interface requiredType = _this._drawer.useCanvas ? "context2d" : "image"; if (!cache) { $.console.warn("Tile %s not cached at the end of tile-loaded event: tile will not be drawn - it has no data!", tile); @@ -1866,9 +1889,9 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag /** * Triggered when a tile has just been loaded in memory. That means that the * image has been downloaded and can be modified before being drawn to the canvas. - * This event awaits its handlers - they can return promises, or be async functions. + * This event is _awaiting_, it supports asynchronous functions or functions that return a promise. * - * @event tile-loaded awaiting event + * @event tile-loaded * @memberof OpenSeadragon.Viewer * @type {object} * @property {Image|*} image - The image (data) of the tile. Deprecated. @@ -2213,15 +2236,26 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag shouldRoundPositionAndSize = !isAnimating; } - for (var i = lastDrawn.length - 1; i >= 0; i--) { + for (let i = lastDrawn.length - 1; i >= 0; i--) { tile = lastDrawn[ i ]; + + if (tile.loaded) { + const cache = tile.getCache(); + if (cache._updateStamp && cache._updateStamp !== $.__updated) { + console.warn("Tile not updated", cache); + } + } + this._drawer.drawTile( tile, this._drawingHandler, useSketch, sketchScale, sketchTranslate, shouldRoundPositionAndSize, this.source ); tile.beingDrawn = true; if( this.viewer ){ + const targetTile = tile; /** - * - Needs documentation - + * This event is fired after a tile has been drawn on the viewport. You can + * use this event to modify the tile data if necessary. + * This event is _awaiting_, it supports asynchronous functions or functions that return a promise. * * @event tile-drawn * @memberof OpenSeadragon.Viewer @@ -2231,9 +2265,19 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag * @property {OpenSeadragon.Tile} tile * @property {?Object} userData - Arbitrary subscriber-defined object. */ - this.viewer.raiseEvent( 'tile-drawn', { + this.viewer.raiseEventAwaiting( 'tile-drawn', { tiledImage: this, - tile: tile + tile: targetTile + }).then(() => { + const cache = targetTile.getCache(targetTile.cacheKey), + // TODO: after-merge-aiosa dynamic type declaration from the drawer base class interface + requiredType = this._drawer.useCanvas ? "context2d" : "image"; + if (!cache) { + $.console.warn("Tile %s not cached at the end of tile-drawn event: tile will not be drawn - it has no data!", targetTile); + } else if (cache.type !== requiredType) { + //initiate conversion as soon as possible if incompatible with the drawer + cache.transformTo(requiredType); + } }); } } diff --git a/src/viewer.js b/src/viewer.js index e5b41abc..5b5af50a 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -354,8 +354,23 @@ $.Viewer = function( options ) { THIS[ _this.hash ].forceRedraw = true; - if (!_this._updateRequestId) { - _this._updateRequestId = scheduleUpdate( _this, updateMulti ); + //if we are not throttling + if (_this.imageLoader.canAcceptNewJob()) { + //todo small hack, we could make this builtin speedup more sophisticated + const item = event.item; + const origOpacity = item.opacity; + const origMaxTiles = item.maxTilesPerFrame; + //update tiles + item.opacity = 0; //prevent draw + item.maxTilesPerFrame = 50; //todo based on image size and also number of images! + item._updateViewport(); + item._needsDraw = true; //we did not draw + item.opacity = origOpacity; + item.maxTilesPerFrame = origMaxTiles; + + if (!_this._updateRequestId) { + _this._updateRequestId = scheduleUpdate( _this, updateMulti ); + } } }); diff --git a/src/world.js b/src/world.js index 4a9712e1..1d70f019 100644 --- a/src/world.js +++ b/src/world.js @@ -232,6 +232,28 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W } }, + /** + * Forces the system consider all tiles across all tiled images + * as outdated, and fire tile update event on relevant tiles + * Detailed description is available within the 'tile-needs-update' + * event. + */ + invalidateItems: function () { + const updatedAt = $.now(); + $.__updated = updatedAt; + for ( let i = 0; i < this._items.length; i++ ) { + console.log("Refreshing ", this._items[i].lastDrawn); + + this._items[i].invalidate(true, updatedAt); + } + + //update all tiles at some point, but by priority of access time + const tiles = this.viewer.tileCache.getLoadedTilesFor(true); + tiles.sort((a, b) => a.lastTouchTime - b.lastTouchTime); + console.log("Refreshing with late update: ", tiles); + $.invalidateTilesLater(tiles, updatedAt, this.viewer); + }, + /** * Clears all tiles and triggers updates for all items. */ diff --git a/style.css b/style.css new file mode 100644 index 00000000..e9c4c8d1 --- /dev/null +++ b/style.css @@ -0,0 +1,300 @@ +* { + margin: 0; + padding: 0; + outline: 0; +} + +body { + padding: 80px 100px; + font: 13px "Helvetica Neue", "Lucida Grande", "Arial"; + background: #ECE9E9 -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fff), to(#ECE9E9)); + background: #ECE9E9 -moz-linear-gradient(top, #fff, #ECE9E9); + background-repeat: no-repeat; + color: #555; + -webkit-font-smoothing: antialiased; +} +h1, h2, h3 { + font-size: 22px; + color: #343434; +} +h1 em, h2 em { + padding: 0 5px; + font-weight: normal; +} +h1 { + font-size: 60px; +} +h2 { + margin-top: 10px; +} +h3 { + margin: 5px 0 10px 0; + padding-bottom: 5px; + border-bottom: 1px solid #eee; + font-size: 18px; +} +ul li { + list-style: none; +} +ul li:hover { + cursor: pointer; + color: #2e2e2e; +} +ul li .path { + padding-left: 5px; + font-weight: bold; +} +ul li .line { + padding-right: 5px; + font-style: italic; +} +ul li:first-child .path { + padding-left: 0; +} +p { + line-height: 1.5; +} +a { + color: #555; + text-decoration: none; +} +a:hover { + color: #303030; +} +#stacktrace { + margin-top: 15px; +} +.directory h1 { + margin-bottom: 15px; + font-size: 18px; +} +ul#files { + width: 100%; + height: 100%; + overflow: hidden; +} +ul#files li { + float: left; + width: 30%; + line-height: 25px; + margin: 1px; +} +ul#files li a { + display: block; + height: 25px; + border: 1px solid transparent; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + overflow: hidden; + white-space: nowrap; +} +ul#files li a:focus, +ul#files li a:hover { + background: rgba(255,255,255,0.65); + border: 1px solid #ececec; +} +ul#files li a.highlight { + -webkit-transition: background .4s ease-in-out; + background: #ffff4f; + border-color: #E9DC51; +} +#search { + display: block; + position: fixed; + top: 20px; + right: 20px; + width: 90px; + -webkit-transition: width ease 0.2s, opacity ease 0.4s; + -moz-transition: width ease 0.2s, opacity ease 0.4s; + -webkit-border-radius: 32px; + -moz-border-radius: 32px; + -webkit-box-shadow: inset 0px 0px 3px rgba(0, 0, 0, 0.25), inset 0px 1px 3px rgba(0, 0, 0, 0.7), 0px 1px 0px rgba(255, 255, 255, 0.03); + -moz-box-shadow: inset 0px 0px 3px rgba(0, 0, 0, 0.25), inset 0px 1px 3px rgba(0, 0, 0, 0.7), 0px 1px 0px rgba(255, 255, 255, 0.03); + -webkit-font-smoothing: antialiased; + text-align: left; + font: 13px "Helvetica Neue", Arial, sans-serif; + padding: 4px 10px; + border: none; + background: transparent; + margin-bottom: 0; + outline: none; + opacity: 0.7; + color: #888; +} +#search:focus { + width: 120px; + opacity: 1.0; +} + +/*views*/ +#files span { + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + text-indent: 10px; +} +#files .name { + background-repeat: no-repeat; +} +#files .icon .name { + text-indent: 28px; +} + +/*tiles*/ +.view-tiles .name { + width: 100%; + background-position: 8px 5px; + margin-left: 30px; +} +.view-tiles .size, +.view-tiles .date { + display: none; +} + +.view-tiles a { + position: relative; +} +/*hack: reuse empty to find folders*/ +#files .size:empty { + width: 20px; + height: 14px; + background-color: #f9d342; /* Folder color */ + position: absolute; + border-radius: 4px; + box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1); /* Optional shadow for effect */ + display: block !important; + float: left; + left: 13px; + top: 5px; +} +#files .size:empty:before { + content: ''; + position: absolute; + top: -2px; + left: 2px; + width: 12px; + height: 4px; + background-color: #f9d342; + border-top-left-radius: 2px; + border-top-right-radius: 2px; +} +#files .size:empty:after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 8px; + height: 4px; + background-color: #e8c233; /* Slightly darker shade for the tab */ + border-top-left-radius: 2px; + border-bottom-right-radius: 2px; +} +/*details*/ +ul#files.view-details li { + float: none; + display: block; + width: 90%; +} +ul#files.view-details li.header { + height: 25px; + background: #000; + color: #fff; + font-weight: bold; +} +.view-details .header { + border-radius: 5px; +} +.view-details .name { + width: 60%; + background-position: 8px 5px; +} +.view-details .size { + width: 10%; +} +.view-details .date { + width: 30%; +} +.view-details .size, +.view-details .date { + text-align: right; + direction: rtl; +} + +/*mobile*/ +@media (max-width: 768px) { + body { + font-size: 13px; + line-height: 16px; + padding: 0; + } + #search { + position: static; + width: 100%; + font-size: 2em; + line-height: 1.8em; + text-indent: 10px; + border: 0; + border-radius: 0; + padding: 10px 0; + margin: 0; + } + #search:focus { + width: 100%; + border: 0; + opacity: 1; + } + .directory h1 { + font-size: 2em; + line-height: 1.5em; + color: #fff; + background: #000; + padding: 15px 10px; + margin: 0; + } + ul#files { + border-top: 1px solid #cacaca; + } + ul#files li { + float: none; + width: auto !important; + display: block; + border-bottom: 1px solid #cacaca; + font-size: 2em; + line-height: 1.2em; + text-indent: 0; + margin: 0; + } + ul#files li:nth-child(odd) { + background: #e0e0e0; + } + ul#files li a { + height: auto; + border: 0; + border-radius: 0; + padding: 15px 10px; + } + ul#files li a:focus, + ul#files li a:hover { + border: 0; + } + #files .header, + #files .size, + #files .date { + display: none !important; + } + #files .name { + float: none; + display: inline-block; + width: 100%; + text-indent: 0; + background-position: 0 50%; + } + #files .icon .name { + text-indent: 41px; + } + #files .size:empty { + top: 23px; + left: 5px; + } +} diff --git a/test/demo/filtering-plugin/demo.js b/test/demo/filtering-plugin/demo.js new file mode 100644 index 00000000..ec49a1cc --- /dev/null +++ b/test/demo/filtering-plugin/demo.js @@ -0,0 +1,750 @@ +/* + * Modified and maintained by the OpenSeadragon Community. + * + * This software was orignally developed at the National Institute of Standards and + * Technology by employees of the Federal Government. NIST assumes + * no responsibility whatsoever for its use by other parties, and makes no + * guarantees, expressed or implied, about its quality, reliability, or + * any other characteristic. + * @author Antoine Vandecreme + */ + +/** + * This class is an improvement over the basic jQuery spinner to support + * 'Enter' to update the value (with validity checks). + * @param {Object} options Options object + * @return {Spinner} A spinner object + */ +class Spinner { + constructor(options) { + options.$element.html(''); + + const self = this, + $spinner = options.$element.find('input'); + this.value = options.init; + $spinner.spinner({ + min: options.min, + max: options.max, + step: options.step, + spin: function(event, ui) { + /*jshint unused:true */ + self.value = ui.value; + options.updateCallback(self.value); + } + }); + $spinner.val(this.value); + $spinner.keyup(function(e) { + if (e.which === 13) { + if (!this.value.match(/^-?\d?\.?\d*$/)) { + this.value = options.init; + } else if (options.min !== undefined && + this.value < options.min) { + this.value = options.min; + } else if (options.max !== undefined && + this.value > options.max) { + this.value = options.max; + } + self.value = this.value; + options.updateCallback(self.value); + } + }); + + } + getValue() { + return this.value; + } +} + +class SpinnerSlider { + + constructor(options) { + let idIncrement = 0; + + this.hash = idIncrement++; + + const spinnerId = 'wdzt-spinner-slider-spinner-' + this.hash; + const sliderId = 'wdzt-spinner-slider-slider-' + this.hash; + + this.value = options.init; + + const self = this; + + options.$element.html(` +
+
+
+ +
+
+
+
+
+
+
+ `); + + const $slider = options.$element.find('#' + sliderId) + .slider({ + min: options.min, + max: options.sliderMax !== undefined ? + options.sliderMax : options.max, + step: options.step, + value: this.value, + slide: function (event, ui) { + /*jshint unused:true */ + self.value = ui.value; + $spinner.spinner('value', self.value); + options.updateCallback(self.value); + } + }); + const $spinner = options.$element.find('#' + spinnerId) + .spinner({ + min: options.min, + max: options.max, + step: options.step, + spin: function (event, ui) { + /*jshint unused:true */ + self.value = ui.value; + $slider.slider('value', self.value); + options.updateCallback(self.value); + } + }); + $spinner.val(this.value); + $spinner.keyup(function (e) { + if (e.which === 13) { + self.value = $spinner.spinner('value'); + $slider.slider('value', self.value); + options.updateCallback(self.value); + } + }); + } + + getValue () { + return this.value; + }; +} + + +const viewer = window.viewer = new OpenSeadragon({ + id: 'openseadragon', + prefixUrl: '/build/openseadragon/images/', + tileSources: 'https://openseadragon.github.io/example-images/highsmith/highsmith.dzi', + crossOriginPolicy: 'Anonymous' +}); + +// Prevent Caman from caching the canvas because without this: +// 1. We have a memory leak +// 2. Non-caman filters in between 2 camans filters get ignored. +Caman.Store.put = function() {}; + +// List of filters with their templates. +const availableFilters = [ + { + name: 'Invert', + generate: function() { + return { + html: '', + getParams: function() { + return ''; + }, + getFilter: function() { + /*eslint new-cap: 0*/ + return OpenSeadragon.Filters.INVERT(); + } + }; + } + }, { + name: 'Colormap', + generate: function(updateCallback) { + const cmaps = { + aCm: [ [0,0,0], [0,4,0], [0,8,0], [0,12,0], [0,16,0], [0,20,0], [0,24,0], [0,28,0], [0,32,0], [0,36,0], [0,40,0], [0,44,0], [0,48,0], [0,52,0], [0,56,0], [0,60,0], [0,64,0], [0,68,0], [0,72,0], [0,76,0], [0,80,0], [0,85,0], [0,89,0], [0,93,0], [0,97,0], [0,101,0], [0,105,0], [0,109,0], [0,113,0], [0,117,0], [0,121,0], [0,125,0], [0,129,2], [0,133,5], [0,137,7], [0,141,10], [0,145,13], [0,149,15], [0,153,18], [0,157,21], [0,161,23], [0,165,26], [0,170,29], [0,174,31], [0,178,34], [0,182,37], [0,186,39], [0,190,42], [0,194,45], [0,198,47], [0,202,50], [0,206,53], [0,210,55], [0,214,58], [0,218,61], [0,222,63], [0,226,66], [0,230,69], [0,234,71], [0,238,74], [0,242,77], [0,246,79], [0,250,82], [0,255,85], [3,251,87], [7,247,90], [11,243,92], [15,239,95], [19,235,98], [23,231,100], [27,227,103], [31,223,106], [35,219,108], [39,215,111], [43,211,114], [47,207,116], [51,203,119], [55,199,122], [59,195,124], [63,191,127], [67,187,130], [71,183,132], [75,179,135], [79,175,138], [83,171,140], [87,167,143], [91,163,146], [95,159,148], [99,155,151], [103,151,154], [107,147,156], [111,143,159], [115,139,162], [119,135,164], [123,131,167], [127,127,170], [131,123,172], [135,119,175], [139,115,177], [143,111,180], [147,107,183], [151,103,185], [155,99,188], [159,95,191], [163,91,193], [167,87,196], [171,83,199], [175,79,201], [179,75,204], [183,71,207], [187,67,209], [191,63,212], [195,59,215], [199,55,217], [203,51,220], [207,47,223], [211,43,225], [215,39,228], [219,35,231], [223,31,233], [227,27,236], [231,23,239], [235,19,241], [239,15,244], [243,11,247], [247,7,249], [251,3,252], [255,0,255], [255,0,251], [255,0,247], [255,0,244], [255,0,240], [255,0,237], [255,0,233], [255,0,230], [255,0,226], [255,0,223], [255,0,219], [255,0,216], [255,0,212], [255,0,208], [255,0,205], [255,0,201], [255,0,198], [255,0,194], [255,0,191], [255,0,187], [255,0,184], [255,0,180], [255,0,177], [255,0,173], [255,0,170], [255,0,166], [255,0,162], [255,0,159], [255,0,155], [255,0,152], [255,0,148], [255,0,145], [255,0,141], [255,0,138], [255,0,134], [255,0,131], [255,0,127], [255,0,123], [255,0,119], [255,0,115], [255,0,112], [255,0,108], [255,0,104], [255,0,100], [255,0,96], [255,0,92], [255,0,88], [255,0,85], [255,0,81], [255,0,77], [255,0,73], [255,0,69], [255,0,65], [255,0,61], [255,0,57], [255,0,54], [255,0,50], [255,0,46], [255,0,42], [255,0,38], [255,0,34], [255,0,30], [255,0,27], [255,0,23], [255,0,19], [255,0,15], [255,0,11], [255,0,7], [255,0,3], [255,0,0], [255,4,0], [255,8,0], [255,12,0], [255,17,0], [255,21,0], [255,25,0], [255,30,0], [255,34,0], [255,38,0], [255,43,0], [255,47,0], [255,51,0], [255,56,0], [255,60,0], [255,64,0], [255,69,0], [255,73,0], [255,77,0], [255,82,0], [255,86,0], [255,90,0], [255,95,0], [255,99,0], [255,103,0], [255,108,0], [255,112,0], [255,116,0], [255,121,0], [255,125,0], [255,129,0], [255,133,0], [255,138,0], [255,142,0], [255,146,0], [255,151,0], [255,155,0], [255,159,0], [255,164,0], [255,168,0], [255,172,0], [255,177,0], [255,181,0], [255,185,0], [255,190,0], [255,194,0], [255,198,0], [255,203,0], [255,207,0], [255,211,0], [255,216,0], [255,220,0], [255,224,0], [255,229,0], [255,233,0], [255,237,0], [255,242,0], [255,246,0], [255,250,0], [255,255,0]], + bCm: [ [0,0,0], [0,0,4], [0,0,8], [0,0,12], [0,0,16], [0,0,20], [0,0,24], [0,0,28], [0,0,32], [0,0,36], [0,0,40], [0,0,44], [0,0,48], [0,0,52], [0,0,56], [0,0,60], [0,0,64], [0,0,68], [0,0,72], [0,0,76], [0,0,80], [0,0,85], [0,0,89], [0,0,93], [0,0,97], [0,0,101], [0,0,105], [0,0,109], [0,0,113], [0,0,117], [0,0,121], [0,0,125], [0,0,129], [0,0,133], [0,0,137], [0,0,141], [0,0,145], [0,0,149], [0,0,153], [0,0,157], [0,0,161], [0,0,165], [0,0,170], [0,0,174], [0,0,178], [0,0,182], [0,0,186], [0,0,190], [0,0,194], [0,0,198], [0,0,202], [0,0,206], [0,0,210], [0,0,214], [0,0,218], [0,0,222], [0,0,226], [0,0,230], [0,0,234], [0,0,238], [0,0,242], [0,0,246], [0,0,250], [0,0,255], [3,0,251], [7,0,247], [11,0,243], [15,0,239], [19,0,235], [23,0,231], [27,0,227], [31,0,223], [35,0,219], [39,0,215], [43,0,211], [47,0,207], [51,0,203], [55,0,199], [59,0,195], [63,0,191], [67,0,187], [71,0,183], [75,0,179], [79,0,175], [83,0,171], [87,0,167], [91,0,163], [95,0,159], [99,0,155], [103,0,151], [107,0,147], [111,0,143], [115,0,139], [119,0,135], [123,0,131], [127,0,127], [131,0,123], [135,0,119], [139,0,115], [143,0,111], [147,0,107], [151,0,103], [155,0,99], [159,0,95], [163,0,91], [167,0,87], [171,0,83], [175,0,79], [179,0,75], [183,0,71], [187,0,67], [191,0,63], [195,0,59], [199,0,55], [203,0,51], [207,0,47], [211,0,43], [215,0,39], [219,0,35], [223,0,31], [227,0,27], [231,0,23], [235,0,19], [239,0,15], [243,0,11], [247,0,7], [251,0,3], [255,0,0], [255,3,0], [255,7,0], [255,11,0], [255,15,0], [255,19,0], [255,23,0], [255,27,0], [255,31,0], [255,35,0], [255,39,0], [255,43,0], [255,47,0], [255,51,0], [255,55,0], [255,59,0], [255,63,0], [255,67,0], [255,71,0], [255,75,0], [255,79,0], [255,83,0], [255,87,0], [255,91,0], [255,95,0], [255,99,0], [255,103,0], [255,107,0], [255,111,0], [255,115,0], [255,119,0], [255,123,0], [255,127,0], [255,131,0], [255,135,0], [255,139,0], [255,143,0], [255,147,0], [255,151,0], [255,155,0], [255,159,0], [255,163,0], [255,167,0], [255,171,0], [255,175,0], [255,179,0], [255,183,0], [255,187,0], [255,191,0], [255,195,0], [255,199,0], [255,203,0], [255,207,0], [255,211,0], [255,215,0], [255,219,0], [255,223,0], [255,227,0], [255,231,0], [255,235,0], [255,239,0], [255,243,0], [255,247,0], [255,251,0], [255,255,0], [255,255,3], [255,255,7], [255,255,11], [255,255,15], [255,255,19], [255,255,23], [255,255,27], [255,255,31], [255,255,35], [255,255,39], [255,255,43], [255,255,47], [255,255,51], [255,255,55], [255,255,59], [255,255,63], [255,255,67], [255,255,71], [255,255,75], [255,255,79], [255,255,83], [255,255,87], [255,255,91], [255,255,95], [255,255,99], [255,255,103], [255,255,107], [255,255,111], [255,255,115], [255,255,119], [255,255,123], [255,255,127], [255,255,131], [255,255,135], [255,255,139], [255,255,143], [255,255,147], [255,255,151], [255,255,155], [255,255,159], [255,255,163], [255,255,167], [255,255,171], [255,255,175], [255,255,179], [255,255,183], [255,255,187], [255,255,191], [255,255,195], [255,255,199], [255,255,203], [255,255,207], [255,255,211], [255,255,215], [255,255,219], [255,255,223], [255,255,227], [255,255,231], [255,255,235], [255,255,239], [255,255,243], [255,255,247], [255,255,251], [255,255,255]], + bbCm: [ [0,0,0], [2,0,0], [4,0,0], [6,0,0], [8,0,0], [10,0,0], [12,0,0], [14,0,0], [16,0,0], [18,0,0], [20,0,0], [22,0,0], [24,0,0], [26,0,0], [28,0,0], [30,0,0], [32,0,0], [34,0,0], [36,0,0], [38,0,0], [40,0,0], [42,0,0], [44,0,0], [46,0,0], [48,0,0], [50,0,0], [52,0,0], [54,0,0], [56,0,0], [58,0,0], [60,0,0], [62,0,0], [64,0,0], [66,0,0], [68,0,0], [70,0,0], [72,0,0], [74,0,0], [76,0,0], [78,0,0], [80,0,0], [82,0,0], [84,0,0], [86,0,0], [88,0,0], [90,0,0], [92,0,0], [94,0,0], [96,0,0], [98,0,0], [100,0,0], [102,0,0], [104,0,0], [106,0,0], [108,0,0], [110,0,0], [112,0,0], [114,0,0], [116,0,0], [118,0,0], [120,0,0], [122,0,0], [124,0,0], [126,0,0], [128,1,0], [130,3,0], [132,5,0], [134,7,0], [136,9,0], [138,11,0], [140,13,0], [142,15,0], [144,17,0], [146,19,0], [148,21,0], [150,23,0], [152,25,0], [154,27,0], [156,29,0], [158,31,0], [160,33,0], [162,35,0], [164,37,0], [166,39,0], [168,41,0], [170,43,0], [172,45,0], [174,47,0], [176,49,0], [178,51,0], [180,53,0], [182,55,0], [184,57,0], [186,59,0], [188,61,0], [190,63,0], [192,65,0], [194,67,0], [196,69,0], [198,71,0], [200,73,0], [202,75,0], [204,77,0], [206,79,0], [208,81,0], [210,83,0], [212,85,0], [214,87,0], [216,89,0], [218,91,0], [220,93,0], [222,95,0], [224,97,0], [226,99,0], [228,101,0], [230,103,0], [232,105,0], [234,107,0], [236,109,0], [238,111,0], [240,113,0], [242,115,0], [244,117,0], [246,119,0], [248,121,0], [250,123,0], [252,125,0], [255,127,0], [255,129,1], [255,131,3], [255,133,5], [255,135,7], [255,137,9], [255,139,11], [255,141,13], [255,143,15], [255,145,17], [255,147,19], [255,149,21], [255,151,23], [255,153,25], [255,155,27], [255,157,29], [255,159,31], [255,161,33], [255,163,35], [255,165,37], [255,167,39], [255,169,41], [255,171,43], [255,173,45], [255,175,47], [255,177,49], [255,179,51], [255,181,53], [255,183,55], [255,185,57], [255,187,59], [255,189,61], [255,191,63], [255,193,65], [255,195,67], [255,197,69], [255,199,71], [255,201,73], [255,203,75], [255,205,77], [255,207,79], [255,209,81], [255,211,83], [255,213,85], [255,215,87], [255,217,89], [255,219,91], [255,221,93], [255,223,95], [255,225,97], [255,227,99], [255,229,101], [255,231,103], [255,233,105], [255,235,107], [255,237,109], [255,239,111], [255,241,113], [255,243,115], [255,245,117], [255,247,119], [255,249,121], [255,251,123], [255,253,125], [255,255,127], [255,255,129], [255,255,131], [255,255,133], [255,255,135], [255,255,137], [255,255,139], [255,255,141], [255,255,143], [255,255,145], [255,255,147], [255,255,149], [255,255,151], [255,255,153], [255,255,155], [255,255,157], [255,255,159], [255,255,161], [255,255,163], [255,255,165], [255,255,167], [255,255,169], [255,255,171], [255,255,173], [255,255,175], [255,255,177], [255,255,179], [255,255,181], [255,255,183], [255,255,185], [255,255,187], [255,255,189], [255,255,191], [255,255,193], [255,255,195], [255,255,197], [255,255,199], [255,255,201], [255,255,203], [255,255,205], [255,255,207], [255,255,209], [255,255,211], [255,255,213], [255,255,215], [255,255,217], [255,255,219], [255,255,221], [255,255,223], [255,255,225], [255,255,227], [255,255,229], [255,255,231], [255,255,233], [255,255,235], [255,255,237], [255,255,239], [255,255,241], [255,255,243], [255,255,245], [255,255,247], [255,255,249], [255,255,251], [255,255,253], [255,255,255]], + blueCm: [ [0,0,0], [0,0,1], [0,0,2], [0,0,3], [0,0,4], [0,0,5], [0,0,6], [0,0,7], [0,0,8], [0,0,9], [0,0,10], [0,0,11], [0,0,12], [0,0,13], [0,0,14], [0,0,15], [0,0,16], [0,0,17], [0,0,18], [0,0,19], [0,0,20], [0,0,21], [0,0,22], [0,0,23], [0,0,24], [0,0,25], [0,0,26], [0,0,27], [0,0,28], [0,0,29], [0,0,30], [0,0,31], [0,0,32], [0,0,33], [0,0,34], [0,0,35], [0,0,36], [0,0,37], [0,0,38], [0,0,39], [0,0,40], [0,0,41], [0,0,42], [0,0,43], [0,0,44], [0,0,45], [0,0,46], [0,0,47], [0,0,48], [0,0,49], [0,0,50], [0,0,51], [0,0,52], [0,0,53], [0,0,54], [0,0,55], [0,0,56], [0,0,57], [0,0,58], [0,0,59], [0,0,60], [0,0,61], [0,0,62], [0,0,63], [0,0,64], [0,0,65], [0,0,66], [0,0,67], [0,0,68], [0,0,69], [0,0,70], [0,0,71], [0,0,72], [0,0,73], [0,0,74], [0,0,75], [0,0,76], [0,0,77], [0,0,78], [0,0,79], [0,0,80], [0,0,81], [0,0,82], [0,0,83], [0,0,84], [0,0,85], [0,0,86], [0,0,87], [0,0,88], [0,0,89], [0,0,90], [0,0,91], [0,0,92], [0,0,93], [0,0,94], [0,0,95], [0,0,96], [0,0,97], [0,0,98], [0,0,99], [0,0,100], [0,0,101], [0,0,102], [0,0,103], [0,0,104], [0,0,105], [0,0,106], [0,0,107], [0,0,108], [0,0,109], [0,0,110], [0,0,111], [0,0,112], [0,0,113], [0,0,114], [0,0,115], [0,0,116], [0,0,117], [0,0,118], [0,0,119], [0,0,120], [0,0,121], [0,0,122], [0,0,123], [0,0,124], [0,0,125], [0,0,126], [0,0,127], [0,0,128], [0,0,129], [0,0,130], [0,0,131], [0,0,132], [0,0,133], [0,0,134], [0,0,135], [0,0,136], [0,0,137], [0,0,138], [0,0,139], [0,0,140], [0,0,141], [0,0,142], [0,0,143], [0,0,144], [0,0,145], [0,0,146], [0,0,147], [0,0,148], [0,0,149], [0,0,150], [0,0,151], [0,0,152], [0,0,153], [0,0,154], [0,0,155], [0,0,156], [0,0,157], [0,0,158], [0,0,159], [0,0,160], [0,0,161], [0,0,162], [0,0,163], [0,0,164], [0,0,165], [0,0,166], [0,0,167], [0,0,168], [0,0,169], [0,0,170], [0,0,171], [0,0,172], [0,0,173], [0,0,174], [0,0,175], [0,0,176], [0,0,177], [0,0,178], [0,0,179], [0,0,180], [0,0,181], [0,0,182], [0,0,183], [0,0,184], [0,0,185], [0,0,186], [0,0,187], [0,0,188], [0,0,189], [0,0,190], [0,0,191], [0,0,192], [0,0,193], [0,0,194], [0,0,195], [0,0,196], [0,0,197], [0,0,198], [0,0,199], [0,0,200], [0,0,201], [0,0,202], [0,0,203], [0,0,204], [0,0,205], [0,0,206], [0,0,207], [0,0,208], [0,0,209], [0,0,210], [0,0,211], [0,0,212], [0,0,213], [0,0,214], [0,0,215], [0,0,216], [0,0,217], [0,0,218], [0,0,219], [0,0,220], [0,0,221], [0,0,222], [0,0,223], [0,0,224], [0,0,225], [0,0,226], [0,0,227], [0,0,228], [0,0,229], [0,0,230], [0,0,231], [0,0,232], [0,0,233], [0,0,234], [0,0,235], [0,0,236], [0,0,237], [0,0,238], [0,0,239], [0,0,240], [0,0,241], [0,0,242], [0,0,243], [0,0,244], [0,0,245], [0,0,246], [0,0,247], [0,0,248], [0,0,249], [0,0,250], [0,0,251], [0,0,252], [0,0,253], [0,0,254], [0,0,255]], + coolCm: [ [0,0,0], [0,0,1], [0,0,3], [0,0,5], [0,0,7], [0,0,9], [0,0,11], [0,0,13], [0,0,15], [0,0,17], [0,0,18], [0,0,20], [0,0,22], [0,0,24], [0,0,26], [0,0,28], [0,0,30], [0,0,32], [0,0,34], [0,0,35], [0,0,37], [0,0,39], [0,0,41], [0,0,43], [0,0,45], [0,0,47], [0,0,49], [0,0,51], [0,0,52], [0,0,54], [0,0,56], [0,0,58], [0,0,60], [0,0,62], [0,0,64], [0,0,66], [0,0,68], [0,0,69], [0,0,71], [0,0,73], [0,0,75], [0,0,77], [0,0,79], [0,0,81], [0,0,83], [0,0,85], [0,0,86], [0,0,88], [0,0,90], [0,0,92], [0,0,94], [0,0,96], [0,0,98], [0,0,100], [0,0,102], [0,0,103], [0,0,105], [0,1,107], [0,2,109], [0,4,111], [0,5,113], [0,6,115], [0,8,117], [0,9,119], [0,10,120], [0,12,122], [0,13,124], [0,14,126], [0,16,128], [0,17,130], [0,18,132], [0,20,134], [0,21,136], [0,23,137], [0,24,139], [0,25,141], [0,27,143], [0,28,145], [1,29,147], [1,31,149], [1,32,151], [1,33,153], [1,35,154], [2,36,156], [2,37,158], [2,39,160], [2,40,162], [2,42,164], [3,43,166], [3,44,168], [3,46,170], [3,47,171], [4,48,173], [4,50,175], [4,51,177], [4,52,179], [4,54,181], [5,55,183], [5,56,185], [5,58,187], [5,59,188], [5,61,190], [6,62,192], [6,63,194], [6,65,196], [6,66,198], [7,67,200], [7,69,202], [7,70,204], [7,71,205], [7,73,207], [8,74,209], [8,75,211], [8,77,213], [8,78,215], [8,80,217], [9,81,219], [9,82,221], [9,84,222], [9,85,224], [9,86,226], [10,88,228], [10,89,230], [10,90,232], [10,92,234], [11,93,236], [11,94,238], [11,96,239], [11,97,241], [11,99,243], [12,100,245], [12,101,247], [12,103,249], [12,104,251], [12,105,253], [13,107,255], [13,108,255], [13,109,255], [13,111,255], [14,112,255], [14,113,255], [14,115,255], [14,116,255], [14,118,255], [15,119,255], [15,120,255], [15,122,255], [15,123,255], [15,124,255], [16,126,255], [16,127,255], [16,128,255], [16,130,255], [17,131,255], [17,132,255], [17,134,255], [17,135,255], [17,136,255], [18,138,255], [18,139,255], [18,141,255], [18,142,255], [18,143,255], [19,145,255], [19,146,255], [19,147,255], [19,149,255], [19,150,255], [20,151,255], [20,153,255], [20,154,255], [20,155,255], [21,157,255], [21,158,255], [21,160,255], [21,161,255], [21,162,255], [22,164,255], [22,165,255], [22,166,255], [22,168,255], [22,169,255], [23,170,255], [23,172,255], [23,173,255], [23,174,255], [24,176,255], [24,177,255], [24,179,255], [24,180,255], [24,181,255], [25,183,255], [25,184,255], [25,185,255], [29,187,255], [32,188,255], [36,189,255], [40,191,255], [44,192,255], [47,193,255], [51,195,255], [55,196,255], [58,198,255], [62,199,255], [66,200,255], [69,202,255], [73,203,255], [77,204,255], [81,206,255], [84,207,255], [88,208,255], [92,210,255], [95,211,255], [99,212,255], [103,214,255], [106,215,255], [110,217,255], [114,218,255], [118,219,255], [121,221,255], [125,222,255], [129,223,255], [132,225,255], [136,226,255], [140,227,255], [143,229,255], [147,230,255], [151,231,255], [155,233,255], [158,234,255], [162,236,255], [166,237,255], [169,238,255], [173,240,255], [177,241,255], [180,242,255], [184,244,255], [188,245,255], [192,246,255], [195,248,255], [199,249,255], [203,250,255], [206,252,255], [210,253,255], [214,255,255], [217,255,255], [221,255,255], [225,255,255], [229,255,255], [232,255,255], [236,255,255], [240,255,255], [243,255,255], [247,255,255], [251,255,255], [255,255,255]], + cubehelix0Cm: [ [0,0,0], [2,1,2], [5,2,5], [5,2,5], [6,2,6], [7,2,7], [10,3,10], [12,5,12], [13,5,14], [14,5,16], [15,5,17], [16,6,20], [17,7,22], [18,8,24], [19,9,26], [20,10,28], [21,11,30], [22,12,33], [22,13,34], [22,14,36], [22,15,38], [24,16,40], [25,17,43], [25,18,45], [25,19,46], [25,20,48], [25,22,50], [25,23,51], [25,25,53], [25,26,54], [25,28,56], [25,28,57], [25,29,59], [25,30,61], [25,33,62], [25,35,63], [25,36,65], [25,37,67], [25,38,68], [25,40,70], [25,43,71], [24,45,72], [23,46,73], [22,48,73], [22,49,75], [22,51,76], [22,52,76], [22,54,76], [22,56,76], [22,57,77], [22,59,78], [22,61,79], [21,63,79], [20,66,79], [20,67,79], [20,68,79], [20,68,79], [20,71,79], [20,73,79], [20,75,78], [20,77,77], [20,79,76], [20,80,76], [20,81,76], [21,83,75], [22,85,74], [22,86,73], [22,89,72], [22,91,71], [23,92,71], [24,93,71], [25,94,71], [26,96,70], [28,99,68], [28,100,68], [29,101,67], [30,102,66], [31,102,65], [32,103,64], [33,104,63], [35,105,62], [38,107,61], [39,107,60], [39,108,59], [40,109,58], [43,110,57], [45,112,56], [47,113,55], [49,113,54], [51,114,53], [54,116,52], [58,117,51], [60,117,50], [62,117,49], [63,117,48], [66,118,48], [68,119,48], [71,119,48], [73,119,48], [76,119,48], [79,120,47], [81,121,46], [84,122,45], [87,122,45], [91,122,45], [94,122,46], [96,122,47], [99,122,48], [103,122,48], [107,122,48], [109,122,49], [112,122,50], [114,122,51], [118,122,52], [122,122,53], [124,122,54], [127,122,55], [130,122,56], [133,122,57], [137,122,58], [140,122,60], [142,122,62], [145,122,63], [149,122,66], [153,122,68], [155,121,70], [158,120,72], [160,119,73], [162,119,75], [164,119,77], [165,119,79], [169,119,81], [173,119,84], [175,119,86], [176,119,89], [178,119,91], [181,119,95], [183,119,99], [186,120,102], [188,121,104], [191,122,107], [192,122,110], [193,122,114], [195,122,117], [197,122,119], [198,122,122], [200,122,126], [201,122,130], [202,123,132], [203,124,135], [204,124,137], [204,125,141], [205,126,144], [206,127,147], [207,127,151], [209,127,155], [209,128,158], [210,129,160], [211,130,163], [211,131,167], [211,132,170], [211,133,173], [211,134,175], [211,135,178], [211,136,182], [211,137,186], [211,138,188], [211,139,191], [211,140,193], [211,142,196], [211,145,198], [210,146,201], [209,147,204], [209,147,206], [209,150,209], [209,153,211], [208,153,213], [207,154,215], [206,155,216], [206,157,218], [206,158,220], [206,160,221], [205,163,224], [204,165,226], [203,167,228], [202,169,230], [201,170,232], [201,172,233], [201,173,234], [200,175,235], [199,176,236], [198,178,237], [197,181,238], [196,183,239], [196,185,239], [196,187,239], [196,188,239], [195,191,240], [193,193,242], [193,194,242], [193,195,242], [193,196,242], [193,198,242], [193,199,242], [193,201,242], [193,204,242], [193,206,242], [193,208,242], [193,209,242], [193,211,242], [193,212,242], [193,214,242], [194,215,242], [195,217,242], [196,219,242], [196,220,242], [196,221,242], [197,223,241], [198,225,240], [198,226,239], [200,228,239], [201,229,239], [202,230,239], [203,231,239], [204,232,239], [205,233,239], [206,234,239], [207,235,239], [208,236,239], [209,237,239], [210,238,239], [212,238,239], [214,239,239], [215,240,239], [216,242,239], [218,243,239], [220,243,239], [221,244,239], [224,246,239], [226,247,239], [228,247,240], [230,247,241], [232,247,242], [234,248,242], [237,249,242], [238,249,243], [240,249,243], [242,249,244], [243,251,246], [244,252,247], [246,252,249], [248,252,250], [249,252,252], [251,253,253], [253,254,254], [255,255,255]], + cubehelix1Cm: [ [0,0,0], [2,0,2], [5,0,5], [6,0,7], [8,0,10], [10,0,12], [12,1,15], [15,2,17], [17,2,20], [18,2,22], [20,2,25], [21,2,29], [22,2,33], [23,3,35], [24,4,38], [25,5,40], [25,6,44], [25,7,48], [26,8,51], [27,9,53], [28,10,56], [28,11,59], [28,12,63], [27,14,66], [26,16,68], [25,17,71], [25,18,73], [25,19,74], [25,20,76], [24,22,80], [22,25,84], [22,27,85], [21,28,87], [20,30,89], [19,33,91], [17,35,94], [16,37,95], [14,39,96], [12,40,96], [11,43,99], [10,45,102], [8,47,102], [6,49,103], [5,51,104], [2,54,104], [0,58,104], [0,60,104], [0,62,104], [0,63,104], [0,66,104], [0,68,104], [0,71,104], [0,73,104], [0,76,104], [0,79,103], [0,81,102], [0,84,102], [0,86,99], [0,89,96], [0,91,96], [0,94,95], [0,96,94], [0,99,91], [0,102,89], [0,103,86], [0,105,84], [0,107,81], [0,109,79], [0,112,76], [0,113,73], [0,115,71], [0,117,68], [0,119,65], [0,122,61], [0,124,58], [0,125,56], [0,127,53], [0,128,51], [0,129,48], [0,130,45], [0,132,42], [0,135,38], [0,136,35], [0,136,33], [0,137,30], [3,138,26], [7,140,22], [10,140,21], [12,140,19], [15,140,17], [19,141,14], [22,142,10], [26,142,8], [29,142,6], [33,142,5], [38,142,2], [43,142,0], [46,142,0], [50,142,0], [53,142,0], [57,141,0], [62,141,0], [66,140,0], [72,140,0], [79,140,0], [83,139,0], [87,138,0], [91,137,0], [98,136,0], [104,135,0], [108,134,0], [113,133,0], [117,132,0], [123,131,0], [130,130,0], [134,129,0], [138,128,0], [142,127,0], [149,126,0], [155,124,0], [159,123,0], [164,121,1], [168,119,2], [174,118,6], [181,117,10], [185,116,12], [189,115,15], [193,114,17], [197,113,21], [200,113,24], [204,112,28], [209,110,33], [214,109,38], [217,108,41], [221,107,45], [224,107,48], [228,105,54], [232,104,61], [234,103,64], [237,102,68], [239,102,71], [243,102,77], [247,102,84], [249,101,89], [250,100,94], [252,99,99], [253,99,105], [255,99,112], [255,99,117], [255,99,122], [255,99,127], [255,99,131], [255,99,136], [255,99,140], [255,100,147], [255,102,155], [255,102,159], [255,102,164], [255,102,168], [255,103,174], [255,104,181], [255,105,185], [255,106,189], [255,107,193], [255,108,200], [255,109,206], [255,111,210], [255,113,215], [255,114,219], [253,117,224], [252,119,229], [250,120,232], [249,121,236], [247,122,239], [244,124,244], [242,127,249], [240,130,251], [238,132,253], [237,135,255], [234,136,255], [232,138,255], [229,140,255], [226,142,255], [224,145,255], [222,147,255], [221,150,255], [219,153,255], [215,156,255], [211,160,255], [209,162,255], [208,164,255], [206,165,255], [204,169,255], [201,173,255], [199,175,255], [198,178,255], [196,181,255], [193,183,255], [191,186,255], [189,188,255], [187,191,255], [186,193,255], [185,195,255], [184,197,255], [183,198,255], [182,202,255], [181,206,255], [180,208,255], [179,209,255], [178,211,255], [177,214,255], [175,216,255], [175,218,255], [175,220,255], [175,221,255], [175,224,255], [175,226,255], [176,228,255], [177,230,255], [178,232,255], [179,234,255], [181,237,255], [181,238,255], [182,238,255], [183,239,255], [184,240,252], [186,242,249], [187,243,249], [189,243,248], [191,244,247], [192,245,246], [194,246,245], [196,247,244], [198,248,243], [201,249,242], [203,250,242], [204,251,242], [206,252,242], [210,252,240], [214,252,239], [216,252,240], [219,252,241], [221,252,242], [224,253,242], [226,255,242], [229,255,243], [232,255,243], [234,255,244], [238,255,246], [242,255,247], [243,255,248], [245,255,249], [247,255,249], [249,255,251], [252,255,253], [255,255,255]], + greenCm: [ [0,0,0], [0,1,0], [0,2,0], [0,3,0], [0,4,0], [0,5,0], [0,6,0], [0,7,0], [0,8,0], [0,9,0], [0,10,0], [0,11,0], [0,12,0], [0,13,0], [0,14,0], [0,15,0], [0,16,0], [0,17,0], [0,18,0], [0,19,0], [0,20,0], [0,21,0], [0,22,0], [0,23,0], [0,24,0], [0,25,0], [0,26,0], [0,27,0], [0,28,0], [0,29,0], [0,30,0], [0,31,0], [0,32,0], [0,33,0], [0,34,0], [0,35,0], [0,36,0], [0,37,0], [0,38,0], [0,39,0], [0,40,0], [0,41,0], [0,42,0], [0,43,0], [0,44,0], [0,45,0], [0,46,0], [0,47,0], [0,48,0], [0,49,0], [0,50,0], [0,51,0], [0,52,0], [0,53,0], [0,54,0], [0,55,0], [0,56,0], [0,57,0], [0,58,0], [0,59,0], [0,60,0], [0,61,0], [0,62,0], [0,63,0], [0,64,0], [0,65,0], [0,66,0], [0,67,0], [0,68,0], [0,69,0], [0,70,0], [0,71,0], [0,72,0], [0,73,0], [0,74,0], [0,75,0], [0,76,0], [0,77,0], [0,78,0], [0,79,0], [0,80,0], [0,81,0], [0,82,0], [0,83,0], [0,84,0], [0,85,0], [0,86,0], [0,87,0], [0,88,0], [0,89,0], [0,90,0], [0,91,0], [0,92,0], [0,93,0], [0,94,0], [0,95,0], [0,96,0], [0,97,0], [0,98,0], [0,99,0], [0,100,0], [0,101,0], [0,102,0], [0,103,0], [0,104,0], [0,105,0], [0,106,0], [0,107,0], [0,108,0], [0,109,0], [0,110,0], [0,111,0], [0,112,0], [0,113,0], [0,114,0], [0,115,0], [0,116,0], [0,117,0], [0,118,0], [0,119,0], [0,120,0], [0,121,0], [0,122,0], [0,123,0], [0,124,0], [0,125,0], [0,126,0], [0,127,0], [0,128,0], [0,129,0], [0,130,0], [0,131,0], [0,132,0], [0,133,0], [0,134,0], [0,135,0], [0,136,0], [0,137,0], [0,138,0], [0,139,0], [0,140,0], [0,141,0], [0,142,0], [0,143,0], [0,144,0], [0,145,0], [0,146,0], [0,147,0], [0,148,0], [0,149,0], [0,150,0], [0,151,0], [0,152,0], [0,153,0], [0,154,0], [0,155,0], [0,156,0], [0,157,0], [0,158,0], [0,159,0], [0,160,0], [0,161,0], [0,162,0], [0,163,0], [0,164,0], [0,165,0], [0,166,0], [0,167,0], [0,168,0], [0,169,0], [0,170,0], [0,171,0], [0,172,0], [0,173,0], [0,174,0], [0,175,0], [0,176,0], [0,177,0], [0,178,0], [0,179,0], [0,180,0], [0,181,0], [0,182,0], [0,183,0], [0,184,0], [0,185,0], [0,186,0], [0,187,0], [0,188,0], [0,189,0], [0,190,0], [0,191,0], [0,192,0], [0,193,0], [0,194,0], [0,195,0], [0,196,0], [0,197,0], [0,198,0], [0,199,0], [0,200,0], [0,201,0], [0,202,0], [0,203,0], [0,204,0], [0,205,0], [0,206,0], [0,207,0], [0,208,0], [0,209,0], [0,210,0], [0,211,0], [0,212,0], [0,213,0], [0,214,0], [0,215,0], [0,216,0], [0,217,0], [0,218,0], [0,219,0], [0,220,0], [0,221,0], [0,222,0], [0,223,0], [0,224,0], [0,225,0], [0,226,0], [0,227,0], [0,228,0], [0,229,0], [0,230,0], [0,231,0], [0,232,0], [0,233,0], [0,234,0], [0,235,0], [0,236,0], [0,237,0], [0,238,0], [0,239,0], [0,240,0], [0,241,0], [0,242,0], [0,243,0], [0,244,0], [0,245,0], [0,246,0], [0,247,0], [0,248,0], [0,249,0], [0,250,0], [0,251,0], [0,252,0], [0,253,0], [0,254,0], [0,255,0]], + greyCm: [ [0,0,0], [1,1,1], [2,2,2], [3,3,3], [4,4,4], [5,5,5], [6,6,6], [7,7,7], [8,8,8], [9,9,9], [10,10,10], [11,11,11], [12,12,12], [13,13,13], [14,14,14], [15,15,15], [16,16,16], [17,17,17], [18,18,18], [19,19,19], [20,20,20], [21,21,21], [22,22,22], [23,23,23], [24,24,24], [25,25,25], [26,26,26], [27,27,27], [28,28,28], [29,29,29], [30,30,30], [31,31,31], [32,32,32], [33,33,33], [34,34,34], [35,35,35], [36,36,36], [37,37,37], [38,38,38], [39,39,39], [40,40,40], [41,41,41], [42,42,42], [43,43,43], [44,44,44], [45,45,45], [46,46,46], [47,47,47], [48,48,48], [49,49,49], [50,50,50], [51,51,51], [52,52,52], [53,53,53], [54,54,54], [55,55,55], [56,56,56], [57,57,57], [58,58,58], [59,59,59], [60,60,60], [61,61,61], [62,62,62], [63,63,63], [64,64,64], [65,65,65], [66,66,66], [67,67,67], [68,68,68], [69,69,69], [70,70,70], [71,71,71], [72,72,72], [73,73,73], [74,74,74], [75,75,75], [76,76,76], [77,77,77], [78,78,78], [79,79,79], [80,80,80], [81,81,81], [82,82,82], [83,83,83], [84,84,84], [85,85,85], [86,86,86], [87,87,87], [88,88,88], [89,89,89], [90,90,90], [91,91,91], [92,92,92], [93,93,93], [94,94,94], [95,95,95], [96,96,96], [97,97,97], [98,98,98], [99,99,99], [100,100,100], [101,101,101], [102,102,102], [103,103,103], [104,104,104], [105,105,105], [106,106,106], [107,107,107], [108,108,108], [109,109,109], [110,110,110], [111,111,111], [112,112,112], [113,113,113], [114,114,114], [115,115,115], [116,116,116], [117,117,117], [118,118,118], [119,119,119], [120,120,120], [121,121,121], [122,122,122], [123,123,123], [124,124,124], [125,125,125], [126,126,126], [127,127,127], [128,128,128], [129,129,129], [130,130,130], [131,131,131], [132,132,132], [133,133,133], [134,134,134], [135,135,135], [136,136,136], [137,137,137], [138,138,138], [139,139,139], [140,140,140], [141,141,141], [142,142,142], [143,143,143], [144,144,144], [145,145,145], [146,146,146], [147,147,147], [148,148,148], [149,149,149], [150,150,150], [151,151,151], [152,152,152], [153,153,153], [154,154,154], [155,155,155], [156,156,156], [157,157,157], [158,158,158], [159,159,159], [160,160,160], [161,161,161], [162,162,162], [163,163,163], [164,164,164], [165,165,165], [166,166,166], [167,167,167], [168,168,168], [169,169,169], [170,170,170], [171,171,171], [172,172,172], [173,173,173], [174,174,174], [175,175,175], [176,176,176], [177,177,177], [178,178,178], [179,179,179], [180,180,180], [181,181,181], [182,182,182], [183,183,183], [184,184,184], [185,185,185], [186,186,186], [187,187,187], [188,188,188], [189,189,189], [190,190,190], [191,191,191], [192,192,192], [193,193,193], [194,194,194], [195,195,195], [196,196,196], [197,197,197], [198,198,198], [199,199,199], [200,200,200], [201,201,201], [202,202,202], [203,203,203], [204,204,204], [205,205,205], [206,206,206], [207,207,207], [208,208,208], [209,209,209], [210,210,210], [211,211,211], [212,212,212], [213,213,213], [214,214,214], [215,215,215], [216,216,216], [217,217,217], [218,218,218], [219,219,219], [220,220,220], [221,221,221], [222,222,222], [223,223,223], [224,224,224], [225,225,225], [226,226,226], [227,227,227], [228,228,228], [229,229,229], [230,230,230], [231,231,231], [232,232,232], [233,233,233], [234,234,234], [235,235,235], [236,236,236], [237,237,237], [238,238,238], [239,239,239], [240,240,240], [241,241,241], [242,242,242], [243,243,243], [244,244,244], [245,245,245], [246,246,246], [247,247,247], [248,248,248], [249,249,249], [250,250,250], [251,251,251], [252,252,252], [253,253,253], [254,254,254], [255,255,255]], + heCm: [ [0,0,0], [42,0,10], [85,0,21], [127,0,31], [127,0,47], [127,0,63], [127,0,79], [127,0,95], [127,0,102], [127,0,109], [127,0,116], [127,0,123], [127,0,131], [127,0,138], [127,0,145], [127,0,152], [127,0,159], [127,8,157], [127,17,155], [127,25,153], [127,34,151], [127,42,149], [127,51,147], [127,59,145], [127,68,143], [127,76,141], [127,85,139], [127,93,136], [127,102,134], [127,110,132], [127,119,130], [127,127,128], [127,129,126], [127,131,124], [127,133,122], [127,135,120], [127,137,118], [127,139,116], [127,141,114], [127,143,112], [127,145,110], [127,147,108], [127,149,106], [127,151,104], [127,153,102], [127,155,100], [127,157,98], [127,159,96], [127,161,94], [127,163,92], [127,165,90], [127,167,88], [127,169,86], [127,171,84], [127,173,82], [127,175,80], [127,177,77], [127,179,75], [127,181,73], [127,183,71], [127,185,69], [127,187,67], [127,189,65], [127,191,63], [128,191,64], [129,191,65], [130,191,66], [131,192,67], [132,192,68], [133,192,69], [134,192,70], [135,193,71], [136,193,72], [137,193,73], [138,193,74], [139,194,75], [140,194,76], [141,194,77], [142,194,78], [143,195,79], [144,195,80], [145,195,81], [146,195,82], [147,196,83], [148,196,84], [149,196,85], [150,196,86], [151,196,87], [152,197,88], [153,197,89], [154,197,90], [155,197,91], [156,198,92], [157,198,93], [158,198,94], [159,198,95], [160,199,96], [161,199,97], [162,199,98], [163,199,99], [164,200,100], [165,200,101], [166,200,102], [167,200,103], [168,201,104], [169,201,105], [170,201,106], [171,201,107], [172,202,108], [173,202,109], [174,202,110], [175,202,111], [176,202,112], [177,203,113], [178,203,114], [179,203,115], [180,203,116], [181,204,117], [182,204,118], [183,204,119], [184,204,120], [185,205,121], [186,205,122], [187,205,123], [188,205,124], [189,206,125], [190,206,126], [191,206,127], [191,206,128], [192,207,129], [192,207,130], [193,208,131], [193,208,132], [194,208,133], [194,209,134], [195,209,135], [195,209,136], [196,210,137], [196,210,138], [197,211,139], [197,211,140], [198,211,141], [198,212,142], [199,212,143], [199,212,144], [200,213,145], [200,213,146], [201,214,147], [201,214,148], [202,214,149], [202,215,150], [203,215,151], [203,216,152], [204,216,153], [204,216,154], [205,217,155], [205,217,156], [206,217,157], [206,218,158], [207,218,159], [207,219,160], [208,219,161], [208,219,162], [209,220,163], [209,220,164], [210,220,165], [210,221,166], [211,221,167], [211,222,168], [212,222,169], [212,222,170], [213,223,171], [213,223,172], [214,223,173], [214,224,174], [215,224,175], [215,225,176], [216,225,177], [216,225,178], [217,226,179], [217,226,180], [218,226,181], [218,227,182], [219,227,183], [219,228,184], [220,228,185], [220,228,186], [221,229,187], [221,229,188], [222,230,189], [222,230,190], [223,230,191], [223,231,192], [224,231,193], [224,231,194], [225,232,195], [225,232,196], [226,233,197], [226,233,198], [227,233,199], [227,234,200], [228,234,201], [228,234,202], [229,235,203], [229,235,204], [230,236,205], [230,236,206], [231,236,207], [231,237,208], [232,237,209], [232,237,210], [233,238,211], [233,238,212], [234,239,213], [234,239,214], [235,239,215], [235,240,216], [236,240,217], [236,240,218], [237,241,219], [237,241,220], [238,242,221], [238,242,222], [239,242,223], [239,243,224], [240,243,225], [240,244,226], [241,244,227], [241,244,228], [242,245,229], [242,245,230], [243,245,231], [243,246,232], [244,246,233], [244,247,234], [245,247,235], [245,247,236], [246,248,237], [246,248,238], [247,248,239], [247,249,240], [248,249,241], [248,250,242], [249,250,243], [249,250,244], [250,251,245], [250,251,246], [251,251,247], [251,252,248], [252,252,249], [252,253,250], [253,253,251], [253,253,252], [254,254,253], [254,254,254], [255,255,255]], + heatCm: [ [0,0,0], [2,1,0], [5,2,0], [8,3,0], [11,4,0], [14,5,0], [17,6,0], [20,7,0], [23,8,0], [26,9,0], [29,10,0], [32,11,0], [35,12,0], [38,13,0], [41,14,0], [44,15,0], [47,16,0], [50,17,0], [53,18,0], [56,19,0], [59,20,0], [62,21,0], [65,22,0], [68,23,0], [71,24,0], [74,25,0], [77,26,0], [80,27,0], [83,28,0], [85,29,0], [88,30,0], [91,31,0], [94,32,0], [97,33,0], [100,34,0], [103,35,0], [106,36,0], [109,37,0], [112,38,0], [115,39,0], [118,40,0], [121,41,0], [124,42,0], [127,43,0], [130,44,0], [133,45,0], [136,46,0], [139,47,0], [142,48,0], [145,49,0], [148,50,0], [151,51,0], [154,52,0], [157,53,0], [160,54,0], [163,55,0], [166,56,0], [169,57,0], [171,58,0], [174,59,0], [177,60,0], [180,61,0], [183,62,0], [186,63,0], [189,64,0], [192,65,0], [195,66,0], [198,67,0], [201,68,0], [204,69,0], [207,70,0], [210,71,0], [213,72,0], [216,73,0], [219,74,0], [222,75,0], [225,76,0], [228,77,0], [231,78,0], [234,79,0], [237,80,0], [240,81,0], [243,82,0], [246,83,0], [249,84,0], [252,85,0], [255,86,0], [255,87,0], [255,88,0], [255,89,0], [255,90,0], [255,91,0], [255,92,0], [255,93,0], [255,94,0], [255,95,0], [255,96,0], [255,97,0], [255,98,0], [255,99,0], [255,100,0], [255,101,0], [255,102,0], [255,103,0], [255,104,0], [255,105,0], [255,106,0], [255,107,0], [255,108,0], [255,109,0], [255,110,0], [255,111,0], [255,112,0], [255,113,0], [255,114,0], [255,115,0], [255,116,0], [255,117,0], [255,118,0], [255,119,0], [255,120,0], [255,121,0], [255,122,0], [255,123,0], [255,124,0], [255,125,0], [255,126,0], [255,127,0], [255,128,0], [255,129,0], [255,130,0], [255,131,0], [255,132,0], [255,133,0], [255,134,0], [255,135,0], [255,136,0], [255,137,0], [255,138,0], [255,139,0], [255,140,0], [255,141,0], [255,142,0], [255,143,0], [255,144,0], [255,145,0], [255,146,0], [255,147,0], [255,148,0], [255,149,0], [255,150,0], [255,151,0], [255,152,0], [255,153,0], [255,154,0], [255,155,0], [255,156,0], [255,157,0], [255,158,0], [255,159,0], [255,160,0], [255,161,0], [255,162,0], [255,163,0], [255,164,0], [255,165,0], [255,166,3], [255,167,6], [255,168,9], [255,169,12], [255,170,15], [255,171,18], [255,172,21], [255,173,24], [255,174,27], [255,175,30], [255,176,33], [255,177,36], [255,178,39], [255,179,42], [255,180,45], [255,181,48], [255,182,51], [255,183,54], [255,184,57], [255,185,60], [255,186,63], [255,187,66], [255,188,69], [255,189,72], [255,190,75], [255,191,78], [255,192,81], [255,193,85], [255,194,88], [255,195,91], [255,196,94], [255,197,97], [255,198,100], [255,199,103], [255,200,106], [255,201,109], [255,202,112], [255,203,115], [255,204,118], [255,205,121], [255,206,124], [255,207,127], [255,208,130], [255,209,133], [255,210,136], [255,211,139], [255,212,142], [255,213,145], [255,214,148], [255,215,151], [255,216,154], [255,217,157], [255,218,160], [255,219,163], [255,220,166], [255,221,170], [255,222,173], [255,223,176], [255,224,179], [255,225,182], [255,226,185], [255,227,188], [255,228,191], [255,229,194], [255,230,197], [255,231,200], [255,232,203], [255,233,206], [255,234,209], [255,235,212], [255,236,215], [255,237,218], [255,238,221], [255,239,224], [255,240,227], [255,241,230], [255,242,233], [255,243,236], [255,244,239], [255,245,242], [255,246,245], [255,247,248], [255,248,251], [255,249,255], [255,250,255], [255,251,255], [255,252,255], [255,253,255], [255,254,255], [255,255,255]], + rainbowCm: [ [255,0,255], [250,0,255], [245,0,255], [240,0,255], [235,0,255], [230,0,255], [225,0,255], [220,0,255], [215,0,255], [210,0,255], [205,0,255], [200,0,255], [195,0,255], [190,0,255], [185,0,255], [180,0,255], [175,0,255], [170,0,255], [165,0,255], [160,0,255], [155,0,255], [150,0,255], [145,0,255], [140,0,255], [135,0,255], [130,0,255], [125,0,255], [120,0,255], [115,0,255], [110,0,255], [105,0,255], [100,0,255], [95,0,255], [90,0,255], [85,0,255], [80,0,255], [75,0,255], [70,0,255], [65,0,255], [60,0,255], [55,0,255], [50,0,255], [45,0,255], [40,0,255], [35,0,255], [30,0,255], [25,0,255], [20,0,255], [15,0,255], [10,0,255], [5,0,255], [0,0,255], [0,5,255], [0,10,255], [0,15,255], [0,20,255], [0,25,255], [0,30,255], [0,35,255], [0,40,255], [0,45,255], [0,50,255], [0,55,255], [0,60,255], [0,65,255], [0,70,255], [0,75,255], [0,80,255], [0,85,255], [0,90,255], [0,95,255], [0,100,255], [0,105,255], [0,110,255], [0,115,255], [0,120,255], [0,125,255], [0,130,255], [0,135,255], [0,140,255], [0,145,255], [0,150,255], [0,155,255], [0,160,255], [0,165,255], [0,170,255], [0,175,255], [0,180,255], [0,185,255], [0,190,255], [0,195,255], [0,200,255], [0,205,255], [0,210,255], [0,215,255], [0,220,255], [0,225,255], [0,230,255], [0,235,255], [0,240,255], [0,245,255], [0,250,255], [0,255,255], [0,255,250], [0,255,245], [0,255,240], [0,255,235], [0,255,230], [0,255,225], [0,255,220], [0,255,215], [0,255,210], [0,255,205], [0,255,200], [0,255,195], [0,255,190], [0,255,185], [0,255,180], [0,255,175], [0,255,170], [0,255,165], [0,255,160], [0,255,155], [0,255,150], [0,255,145], [0,255,140], [0,255,135], [0,255,130], [0,255,125], [0,255,120], [0,255,115], [0,255,110], [0,255,105], [0,255,100], [0,255,95], [0,255,90], [0,255,85], [0,255,80], [0,255,75], [0,255,70], [0,255,65], [0,255,60], [0,255,55], [0,255,50], [0,255,45], [0,255,40], [0,255,35], [0,255,30], [0,255,25], [0,255,20], [0,255,15], [0,255,10], [0,255,5], [0,255,0], [5,255,0], [10,255,0], [15,255,0], [20,255,0], [25,255,0], [30,255,0], [35,255,0], [40,255,0], [45,255,0], [50,255,0], [55,255,0], [60,255,0], [65,255,0], [70,255,0], [75,255,0], [80,255,0], [85,255,0], [90,255,0], [95,255,0], [100,255,0], [105,255,0], [110,255,0], [115,255,0], [120,255,0], [125,255,0], [130,255,0], [135,255,0], [140,255,0], [145,255,0], [150,255,0], [155,255,0], [160,255,0], [165,255,0], [170,255,0], [175,255,0], [180,255,0], [185,255,0], [190,255,0], [195,255,0], [200,255,0], [205,255,0], [210,255,0], [215,255,0], [220,255,0], [225,255,0], [230,255,0], [235,255,0], [240,255,0], [245,255,0], [250,255,0], [255,255,0], [255,250,0], [255,245,0], [255,240,0], [255,235,0], [255,230,0], [255,225,0], [255,220,0], [255,215,0], [255,210,0], [255,205,0], [255,200,0], [255,195,0], [255,190,0], [255,185,0], [255,180,0], [255,175,0], [255,170,0], [255,165,0], [255,160,0], [255,155,0], [255,150,0], [255,145,0], [255,140,0], [255,135,0], [255,130,0], [255,125,0], [255,120,0], [255,115,0], [255,110,0], [255,105,0], [255,100,0], [255,95,0], [255,90,0], [255,85,0], [255,80,0], [255,75,0], [255,70,0], [255,65,0], [255,60,0], [255,55,0], [255,50,0], [255,45,0], [255,40,0], [255,35,0], [255,30,0], [255,25,0], [255,20,0], [255,15,0], [255,10,0], [255,5,0], [255,0,0]], + redCm: [ [0,0,0], [1,0,0], [2,0,0], [3,0,0], [4,0,0], [5,0,0], [6,0,0], [7,0,0], [8,0,0], [9,0,0], [10,0,0], [11,0,0], [12,0,0], [13,0,0], [14,0,0], [15,0,0], [16,0,0], [17,0,0], [18,0,0], [19,0,0], [20,0,0], [21,0,0], [22,0,0], [23,0,0], [24,0,0], [25,0,0], [26,0,0], [27,0,0], [28,0,0], [29,0,0], [30,0,0], [31,0,0], [32,0,0], [33,0,0], [34,0,0], [35,0,0], [36,0,0], [37,0,0], [38,0,0], [39,0,0], [40,0,0], [41,0,0], [42,0,0], [43,0,0], [44,0,0], [45,0,0], [46,0,0], [47,0,0], [48,0,0], [49,0,0], [50,0,0], [51,0,0], [52,0,0], [53,0,0], [54,0,0], [55,0,0], [56,0,0], [57,0,0], [58,0,0], [59,0,0], [60,0,0], [61,0,0], [62,0,0], [63,0,0], [64,0,0], [65,0,0], [66,0,0], [67,0,0], [68,0,0], [69,0,0], [70,0,0], [71,0,0], [72,0,0], [73,0,0], [74,0,0], [75,0,0], [76,0,0], [77,0,0], [78,0,0], [79,0,0], [80,0,0], [81,0,0], [82,0,0], [83,0,0], [84,0,0], [85,0,0], [86,0,0], [87,0,0], [88,0,0], [89,0,0], [90,0,0], [91,0,0], [92,0,0], [93,0,0], [94,0,0], [95,0,0], [96,0,0], [97,0,0], [98,0,0], [99,0,0], [100,0,0], [101,0,0], [102,0,0], [103,0,0], [104,0,0], [105,0,0], [106,0,0], [107,0,0], [108,0,0], [109,0,0], [110,0,0], [111,0,0], [112,0,0], [113,0,0], [114,0,0], [115,0,0], [116,0,0], [117,0,0], [118,0,0], [119,0,0], [120,0,0], [121,0,0], [122,0,0], [123,0,0], [124,0,0], [125,0,0], [126,0,0], [127,0,0], [128,0,0], [129,0,0], [130,0,0], [131,0,0], [132,0,0], [133,0,0], [134,0,0], [135,0,0], [136,0,0], [137,0,0], [138,0,0], [139,0,0], [140,0,0], [141,0,0], [142,0,0], [143,0,0], [144,0,0], [145,0,0], [146,0,0], [147,0,0], [148,0,0], [149,0,0], [150,0,0], [151,0,0], [152,0,0], [153,0,0], [154,0,0], [155,0,0], [156,0,0], [157,0,0], [158,0,0], [159,0,0], [160,0,0], [161,0,0], [162,0,0], [163,0,0], [164,0,0], [165,0,0], [166,0,0], [167,0,0], [168,0,0], [169,0,0], [170,0,0], [171,0,0], [172,0,0], [173,0,0], [174,0,0], [175,0,0], [176,0,0], [177,0,0], [178,0,0], [179,0,0], [180,0,0], [181,0,0], [182,0,0], [183,0,0], [184,0,0], [185,0,0], [186,0,0], [187,0,0], [188,0,0], [189,0,0], [190,0,0], [191,0,0], [192,0,0], [193,0,0], [194,0,0], [195,0,0], [196,0,0], [197,0,0], [198,0,0], [199,0,0], [200,0,0], [201,0,0], [202,0,0], [203,0,0], [204,0,0], [205,0,0], [206,0,0], [207,0,0], [208,0,0], [209,0,0], [210,0,0], [211,0,0], [212,0,0], [213,0,0], [214,0,0], [215,0,0], [216,0,0], [217,0,0], [218,0,0], [219,0,0], [220,0,0], [221,0,0], [222,0,0], [223,0,0], [224,0,0], [225,0,0], [226,0,0], [227,0,0], [228,0,0], [229,0,0], [230,0,0], [231,0,0], [232,0,0], [233,0,0], [234,0,0], [235,0,0], [236,0,0], [237,0,0], [238,0,0], [239,0,0], [240,0,0], [241,0,0], [242,0,0], [243,0,0], [244,0,0], [245,0,0], [246,0,0], [247,0,0], [248,0,0], [249,0,0], [250,0,0], [251,0,0], [252,0,0], [253,0,0], [254,0,0], [255,0,0]], + standardCm: [ [0,0,0], [0,0,3], [1,1,6], [2,2,9], [3,3,12], [4,4,15], [5,5,18], [6,6,21], [7,7,24], [8,8,27], [9,9,30], [10,10,33], [10,10,36], [11,11,39], [12,12,42], [13,13,45], [14,14,48], [15,15,51], [16,16,54], [17,17,57], [18,18,60], [19,19,63], [20,20,66], [20,20,69], [21,21,72], [22,22,75], [23,23,78], [24,24,81], [25,25,85], [26,26,88], [27,27,91], [28,28,94], [29,29,97], [30,30,100], [30,30,103], [31,31,106], [32,32,109], [33,33,112], [34,34,115], [35,35,118], [36,36,121], [37,37,124], [38,38,127], [39,39,130], [40,40,133], [40,40,136], [41,41,139], [42,42,142], [43,43,145], [44,44,148], [45,45,151], [46,46,154], [47,47,157], [48,48,160], [49,49,163], [50,50,166], [51,51,170], [51,51,173], [52,52,176], [53,53,179], [54,54,182], [55,55,185], [56,56,188], [57,57,191], [58,58,194], [59,59,197], [60,60,200], [61,61,203], [61,61,206], [62,62,209], [63,63,212], [64,64,215], [65,65,218], [66,66,221], [67,67,224], [68,68,227], [69,69,230], [70,70,233], [71,71,236], [71,71,239], [72,72,242], [73,73,245], [74,74,248], [75,75,251], [76,76,255], [0,78,0], [1,80,1], [2,82,2], [3,84,3], [4,87,4], [5,89,5], [6,91,6], [7,93,7], [8,95,8], [9,97,9], [9,99,9], [10,101,10], [11,103,11], [12,105,12], [13,108,13], [14,110,14], [15,112,15], [16,114,16], [17,116,17], [18,118,18], [18,120,18], [19,122,19], [20,124,20], [21,126,21], [22,129,22], [23,131,23], [24,133,24], [25,135,25], [26,137,26], [27,139,27], [27,141,27], [28,143,28], [29,145,29], [30,147,30], [31,150,31], [32,152,32], [33,154,33], [34,156,34], [35,158,35], [36,160,36], [36,162,36], [37,164,37], [38,166,38], [39,168,39], [40,171,40], [41,173,41], [42,175,42], [43,177,43], [44,179,44], [45,181,45], [45,183,45], [46,185,46], [47,187,47], [48,189,48], [49,192,49], [50,194,50], [51,196,51], [52,198,52], [53,200,53], [54,202,54], [54,204,54], [55,206,55], [56,208,56], [57,210,57], [58,213,58], [59,215,59], [60,217,60], [61,219,61], [62,221,62], [63,223,63], [63,225,63], [64,227,64], [65,229,65], [66,231,66], [67,234,67], [68,236,68], [69,238,69], [70,240,70], [71,242,71], [72,244,72], [72,246,72], [73,248,73], [74,250,74], [75,252,75], [76,255,76], [78,0,0], [80,1,1], [82,2,2], [84,3,3], [86,4,4], [88,5,5], [91,6,6], [93,7,7], [95,8,8], [97,8,8], [99,9,9], [101,10,10], [103,11,11], [105,12,12], [107,13,13], [109,14,14], [111,15,15], [113,16,16], [115,16,16], [118,17,17], [120,18,18], [122,19,19], [124,20,20], [126,21,21], [128,22,22], [130,23,23], [132,24,24], [134,24,24], [136,25,25], [138,26,26], [140,27,27], [142,28,28], [144,29,29], [147,30,30], [149,31,31], [151,32,32], [153,32,32], [155,33,33], [157,34,34], [159,35,35], [161,36,36], [163,37,37], [165,38,38], [167,39,39], [169,40,40], [171,40,40], [174,41,41], [176,42,42], [178,43,43], [180,44,44], [182,45,45], [184,46,46], [186,47,47], [188,48,48], [190,48,48], [192,49,49], [194,50,50], [196,51,51], [198,52,52], [201,53,53], [203,54,54], [205,55,55], [207,56,56], [209,56,56], [211,57,57], [213,58,58], [215,59,59], [217,60,60], [219,61,61], [221,62,62], [223,63,63], [225,64,64], [228,64,64], [230,65,65], [232,66,66], [234,67,67], [236,68,68], [238,69,69], [240,70,70], [242,71,71], [244,72,72], [246,72,72], [248,73,73], [250,74,74], [252,75,75], [255,76,76]] + }; + let cmapOptions = ''; + Object.keys(cmaps).forEach(function(c) { + cmapOptions += ''; + }); + const $html = $('
' + + ' Colormap:
' + + ' Center: ' + + '
'); + const cmapUpdate = function() { + const val = $('#cmapSelect').val(); + $('#cmapSelect').change(function() { + updateCallback(val); + }); + return cmaps[val]; + }; + const spinnerSlider = new Spinner({ + $element: $html.find('#cmapCenter'), + init: 128, + min: 1, + sliderMax: 254, + step: 1, + updateCallback: updateCallback + }); + return { + html: $html, + getParams: function() { + return spinnerSlider.getValue(); + }, + getFilter: function() { + /*eslint new-cap: 0*/ + return OpenSeadragon.Filters.COLORMAP(cmapUpdate(), spinnerSlider.getValue()); + } + }; + } + }, { + name: 'Colorize', + help: 'The adjustment range (strength) is from 0 to 100.' + + 'The higher the value, the closer the colors in the ' + + 'image shift towards the given adjustment color.' + + 'Color values are between 0 to 255', + generate: function(updateCallback) { + const redSpinnerId = 'redSpinner-' + idIncrement; + const greenSpinnerId = 'greenSpinner-' + idIncrement; + const blueSpinnerId = 'blueSpinner-' + idIncrement; + const strengthSpinnerId = 'strengthSpinner-' + idIncrement; + /*eslint max-len: 0*/ + const $html = $('
' + + '
' + + '
' + + ' Red: ' + + '
' + + '
' + + ' Green: ' + + '
' + + '
' + + ' Blue: ' + + '
' + + '
' + + ' Strength: ' + + '
' + + '
' + + '
'); + const redSpinner = new Spinner({ + $element: $html.find('#' + redSpinnerId), + init: 100, + min: 0, + max: 255, + step: 1, + updateCallback: updateCallback + }); + const greenSpinner = new Spinner({ + $element: $html.find('#' + greenSpinnerId), + init: 20, + min: 0, + max: 255, + step: 1, + updateCallback: updateCallback + }); + const blueSpinner = new Spinner({ + $element: $html.find('#' + blueSpinnerId), + init: 20, + min: 0, + max: 255, + step: 1, + updateCallback: updateCallback + }); + const strengthSpinner = new Spinner({ + $element: $html.find('#' + strengthSpinnerId), + init: 50, + min: 0, + max: 100, + step: 1, + updateCallback: updateCallback + }); + return { + html: $html, + getParams: function() { + const red = redSpinner.getValue(); + const green = greenSpinner.getValue(); + const blue = blueSpinner.getValue(); + const strength = strengthSpinner.getValue(); + return 'R: ' + red + ' G: ' + green + ' B: ' + blue + + ' S: ' + strength; + }, + getFilter: function() { + const red = redSpinner.getValue(); + const green = greenSpinner.getValue(); + const blue = blueSpinner.getValue(); + const strength = strengthSpinner.getValue(); + return function(context) { + const promise = getPromiseResolver(); + Caman(context.canvas, function() { + this.colorize(red, green, blue, strength); + this.render(promise.call.back); + }); + return promise.promise; + }; + } + }; + } + }, { + name: 'Contrast', + help: 'Range is from 0 to infinity, although sane values are from 0 ' + + 'to 4 or 5. Values between 0 and 1 will lessen the contrast ' + + 'while values greater than 1 will increase it.', + generate: function(updateCallback) { + const $html = $('
'); + const spinnerSlider = new SpinnerSlider({ + $element: $html, + init: 1.3, + min: 0, + sliderMax: 4, + step: 0.1, + updateCallback: updateCallback + }); + return { + html: $html, + getParams: function() { + return spinnerSlider.getValue(); + }, + getFilter: function() { + return OpenSeadragon.Filters.CONTRAST( + spinnerSlider.getValue()); + } + }; + } + }, { + name: 'Exposure', + help: 'Range is -100 to 100. Values < 0 will decrease ' + + 'exposure while values > 0 will increase exposure', + generate: function(updateCallback) { + const $html = $('
'); + const spinnerSlider = new SpinnerSlider({ + $element: $html, + init: 10, + min: -100, + max: 100, + step: 1, + updateCallback: updateCallback + }); + return { + html: $html, + getParams: function() { + return spinnerSlider.getValue(); + }, + getFilter: function() { + const value = spinnerSlider.getValue(); + return function(context) { + const promise = getPromiseResolver(); + Caman(context.canvas, function() { + this.exposure(value); + this.render(promise.call.back); + }); + return promise.promise; + }; + } + }; + } + }, { + name: 'Gamma', + help: 'Range is from 0 to infinity, although sane values ' + + 'are from 0 to 4 or 5. Values between 0 and 1 will ' + + 'lessen the contrast while values greater than 1 will increase it.', + generate: function(updateCallback) { + const $html = $('
'); + const spinnerSlider = new SpinnerSlider({ + $element: $html, + init: 0.5, + min: 0, + sliderMax: 5, + step: 0.1, + updateCallback: updateCallback + }); + return { + html: $html, + getParams: function() { + return spinnerSlider.getValue(); + }, + getFilter: function() { + const value = spinnerSlider.getValue(); + return OpenSeadragon.Filters.GAMMA(value); + } + }; + } + }, { + name: 'Hue', + help: 'hue value is between 0 to 100 representing the ' + + 'percentage of Hue shift in the 0 to 360 range', + generate: function(updateCallback) { + const $html = $('
'); + const spinnerSlider = new SpinnerSlider({ + $element: $html, + init: 20, + min: 0, + max: 100, + step: 1, + updateCallback: updateCallback + }); + return { + html: $html, + getParams: function() { + return spinnerSlider.getValue(); + }, + getFilter: function() { + const value = spinnerSlider.getValue(); + return function(context) { + const promise = getPromiseResolver(); + Caman(context.canvas, function() { + this.hue(value); + this.render(promise.call.back); + }); + return promise.promise; + }; + } + }; + } + }, { + name: 'Saturation', + help: 'saturation value has to be between -100 and 100', + generate: function(updateCallback) { + const $html = $('
'); + const spinnerSlider = new SpinnerSlider({ + $element: $html, + init: 50, + min: -100, + max: 100, + step: 1, + updateCallback: updateCallback + }); + return { + html: $html, + getParams: function() { + return spinnerSlider.getValue(); + }, + getFilter: function() { + const value = spinnerSlider.getValue(); + return function(context) { + const promise = getPromiseResolver(); + Caman(context.canvas, function() { + this.saturation(value); + this.render(promise.call.back); + }); + return promise.promise; + }; + } + }; + } + }, { + name: 'Vibrance', + help: 'vibrance value has to be between -100 and 100', + generate: function(updateCallback) { + const $html = $('
'); + const spinnerSlider = new SpinnerSlider({ + $element: $html, + init: 50, + min: -100, + max: 100, + step: 1, + updateCallback: updateCallback + }); + return { + html: $html, + getParams: function() { + return spinnerSlider.getValue(); + }, + getFilter: function() { + const value = spinnerSlider.getValue(); + return function(context) { + const promise = getPromiseResolver(); + Caman(context.canvas, function() { + this.vibrance(value); + this.render(promise.call.back); + }); + return promise.promise; + }; + } + }; + } + }, { + name: 'Sepia', + help: 'sepia value has to be between 0 and 100', + generate: function(updateCallback) { + const $html = $('
'); + const spinnerSlider = new SpinnerSlider({ + $element: $html, + init: 50, + min: 0, + max: 100, + step: 1, + updateCallback: updateCallback + }); + return { + html: $html, + getParams: function() { + return spinnerSlider.getValue(); + }, + getFilter: function() { + const value = spinnerSlider.getValue(); + return function(context) { + const promise = getPromiseResolver(); + Caman(context.canvas, function() { + this.sepia(value); + this.render(promise.call.back); + }); + return promise.promise; + }; + } + }; + } + }, { + name: 'Noise', + help: 'Noise cannot be smaller than 0', + generate: function(updateCallback) { + const $html = $('
'); + const spinnerSlider = new SpinnerSlider({ + $element: $html, + init: 50, + min: 0, + step: 1, + updateCallback: updateCallback + }); + return { + html: $html, + getParams: function() { + return spinnerSlider.getValue(); + }, + getFilter: function() { + const value = spinnerSlider.getValue(); + return function(context) { + const promise = getPromiseResolver(); + Caman(context.canvas, function() { + this.noise(value); + this.render(promise.call.back); + }); + return promise.promise; + }; + } + }; + } + }, { + name: 'Greyscale', + generate: function() { + return { + html: '', + getParams: function() { + return ''; + }, + getFilter: function() { + return OpenSeadragon.Filters.GREYSCALE(); + } + }; + } + }, { + name: 'Sobel Edge', + generate: function() { + return { + html: '', + getParams: function() { + return ''; + }, + getFilter: function() { + return function(context) { + const imgData = context.getImageData( + 0, 0, context.canvas.width, context.canvas.height); + const pixels = imgData.data; + const originalPixels = context.getImageData(0, 0, context.canvas.width, context.canvas.height).data; + const oneRowOffset = context.canvas.width * 4; + const onePixelOffset = 4; + let Gy, Gx, idx = 0; + for (let i = 1; i < context.canvas.height - 1; i += 1) { + idx = oneRowOffset * i + 4; + for (let j = 1; j < context.canvas.width - 1; j += 1) { + Gy = originalPixels[idx - onePixelOffset + oneRowOffset] + 2 * originalPixels[idx + oneRowOffset] + originalPixels[idx + onePixelOffset + oneRowOffset]; + Gy = Gy - (originalPixels[idx - onePixelOffset - oneRowOffset] + 2 * originalPixels[idx - oneRowOffset] + originalPixels[idx + onePixelOffset - oneRowOffset]); + Gx = originalPixels[idx + onePixelOffset - oneRowOffset] + 2 * originalPixels[idx + onePixelOffset] + originalPixels[idx + onePixelOffset + oneRowOffset]; + Gx = Gx - (originalPixels[idx - onePixelOffset - oneRowOffset] + 2 * originalPixels[idx - onePixelOffset] + originalPixels[idx - onePixelOffset + oneRowOffset]); + pixels[idx] = Math.sqrt(Gx * Gx + Gy * Gy); // 0.5*Math.abs(Gx) + 0.5*Math.abs(Gy);//100*Math.atan(Gy,Gx); + pixels[idx + 1] = 0; + pixels[idx + 2] = 0; + idx += 4; + } + } + context.putImageData(imgData, 0, 0); + }; + } + }; + } + }, { + name: 'Brightness', + help: 'Brightness must be between -255 (darker) and 255 (brighter).', + generate: function(updateCallback) { + const $html = $('
'); + const spinnerSlider = new SpinnerSlider({ + $element: $html, + init: 50, + min: -255, + max: 255, + step: 1, + updateCallback: updateCallback + }); + return { + html: $html, + getParams: function() { + return spinnerSlider.getValue(); + }, + getFilter: function() { + return OpenSeadragon.Filters.BRIGHTNESS( + spinnerSlider.getValue()); + } + }; + } + }, { + name: 'Erosion', + help: 'The erosion kernel size must be an odd number.', + generate: function(updateCallback) { + const $html = $('
'); + const spinner = new Spinner({ + $element: $html, + init: 3, + min: 3, + step: 2, + updateCallback: updateCallback + }); + return { + html: $html, + getParams: function() { + return spinner.getValue(); + }, + getFilter: function() { + return OpenSeadragon.Filters.MORPHOLOGICAL_OPERATION( + spinner.getValue(), Math.min); + } + }; + } + }, { + name: 'Dilation', + help: 'The dilation kernel size must be an odd number.', + generate: function(updateCallback) { + const $html = $('
'); + const spinner = new Spinner({ + $element: $html, + init: 3, + min: 3, + step: 2, + updateCallback: updateCallback + }); + return { + html: $html, + getParams: function() { + return spinner.getValue(); + }, + getFilter: function() { + return OpenSeadragon.Filters.MORPHOLOGICAL_OPERATION( + spinner.getValue(), Math.max); + } + }; + } + }, { + name: 'Thresholding', + help: 'The threshold must be between 0 and 255.', + generate: function(updateCallback) { + const $html = $('
'); + const spinnerSlider = new SpinnerSlider({ + $element: $html, + init: 127, + min: 0, + max: 255, + step: 1, + updateCallback: updateCallback + }); + return { + html: $html, + getParams: function() { + return spinnerSlider.getValue(); + }, + getFilter: function() { + return OpenSeadragon.Filters.THRESHOLDING( + spinnerSlider.getValue()); + } + }; + } + }]; +availableFilters.sort(function(f1, f2) { + return f1.name.localeCompare(f2.name); +}); + +let idIncrement = 0; +const hashTable = {}; + +availableFilters.forEach(function(filter) { + const $li = $('
  • '); + const $plus = $('+'); + $li.append($plus); + $li.append(filter.name); + $li.appendTo($('#available')); + $plus.click(function() { + const id = 'selected_' + idIncrement++; + const generatedFilter = filter.generate(updateFilters); + hashTable[id] = { + name: filter.name, + generatedFilter: generatedFilter + }; + const $li = $('
  • '); + const $minus = $('
    -
    '); + $li.find('.wdzt-row-layout').append($minus); + $li.find('.wdzt-row-layout').append('
    ' + filter.name + '
    '); + if (filter.help) { + const $help = $('
     ? 
    '); + $help.tooltip(); + $li.find('.wdzt-row-layout').append($help); + } + $li.find('.wdzt-row-layout').append( + $('
    ') + .append(generatedFilter.html)); + $minus.click(function() { + delete hashTable[id]; + $li.remove(); + updateFilters(); + }); + $li.appendTo($('#selected')); + updateFilters(); + }); +}); + +$('#selected').sortable({ + containment: 'parent', + axis: 'y', + tolerance: 'pointer', + update: updateFilters +}); + +function getPromiseResolver() { + let call = {}; + let promise = new OpenSeadragon.Promise(resolve => { + call.back = resolve; + }); + return {call, promise}; +} + +function updateFilters() { + const filters = []; + $('#selected li').each(function() { + const id = this.id; + const filter = hashTable[id]; + filters.push(filter.generatedFilter.getFilter()); + }); + viewer.setFilterOptions({ + filters: { + processors: filters + } + }); +} + diff --git a/test/demo/filtering-plugin/index.html b/test/demo/filtering-plugin/index.html new file mode 100644 index 00000000..96798ec2 --- /dev/null +++ b/test/demo/filtering-plugin/index.html @@ -0,0 +1,74 @@ + + + + + + + + OpenSeadragon Filtering Plugin Demo + + + + + + + + + + + + + + + + +
    +

    OpenSeadragon filtering plugin demo.

    +

    You might want to check the plugin repository to see if the plugin code is up to date.

    +
    + + +
    +
    +
    +
    +
    +
    +
    +

    Available filters

    +
      +
    + +

    Selected filters

    +
      + +

      Drag and drop the selected filters to set their order.

      + +
      + +
      +
      +
      +
      + + + + + + + + diff --git a/test/demo/filtering-plugin/plugin.js b/test/demo/filtering-plugin/plugin.js new file mode 100644 index 00000000..614da0fc --- /dev/null +++ b/test/demo/filtering-plugin/plugin.js @@ -0,0 +1,371 @@ +/* + * Modified and maintained by the OpenSeadragon Community. + * + * This software was orignally developed at the National Institute of Standards and + * Technology by employees of the Federal Government. NIST assumes + * no responsibility whatsoever for its use by other parties, and makes no + * guarantees, expressed or implied, about its quality, reliability, or + * any other characteristic. + * @author Antoine Vandecreme + */ + +(function() { + + 'use strict'; + + const $ = window.OpenSeadragon; + if (!$) { + throw new Error('OpenSeadragon is missing.'); + } + // Requires OpenSeadragon >=2.1 + if (!$.version || $.version.major < 2 || + $.version.major === 2 && $.version.minor < 1) { + throw new Error( + 'Filtering plugin requires OpenSeadragon version >= 2.1'); + } + + $.Viewer.prototype.setFilterOptions = function(options) { + if (!this.filterPluginInstance) { + options = options || {}; + options.viewer = this; + this.filterPluginInstance = new $.FilterPlugin(options); + } else { + setOptions(this.filterPluginInstance, options); + } + }; + + /** + * @class FilterPlugin + * @param {Object} options The options + * @param {OpenSeadragon.Viewer} options.viewer The viewer to attach this + * plugin to. + * @param {Object[]} options.filters The filters to apply to the images. + * @param {OpenSeadragon.TiledImage[]} options.filters[x].items The tiled images + * on which to apply the filter. + * @param {function|function[]} options.filters[x].processors The processing + * function(s) to apply to the images. The parameter of this function is + * the context to modify. + */ + $.FilterPlugin = function(options) { + options = options || {}; + if (!options.viewer) { + throw new Error('A viewer must be specified.'); + } + const self = this; + this.viewer = options.viewer; + + this.viewer.addHandler('tile-loaded', tileLoadedHandler); + this.viewer.addHandler('tile-needs-update', tileUpdateHandler); + + // filterIncrement allows to determine whether a tile contains the + // latest filters results. + this.filterIncrement = 0; + + setOptions(this, options); + + async function tileLoadedHandler(event) { + await applyFilters(event.tile, event.tiledImage); + } + + function tileUpdateHandler(event) { + const tile = event.tile; + const incrementCount = tile._filterIncrement; + if (incrementCount === self.filterIncrement) { + //we _know_ we have up-to-date data to render + return; + } + //go async otherwise + return applyFilters(tile, event.tiledImage); + } + + async function applyFilters(tile, tiledImage) { + const processors = getFiltersProcessors(self, tiledImage); + + if (processors.length === 0) { + //restore the original data + const context = await tile.getOriginalData('context2d', + false); + tile.setData(context, 'context2d'); + tile._filterIncrement = self.filterIncrement; + return; + } + + const contextCopy = await tile.getOriginalData('context2d'); + const currentIncrement = self.filterIncrement; + for (let i = 0; i < processors.length; i++) { + if (self.filterIncrement !== currentIncrement) { + break; + } + await processors[i](contextCopy); + } + tile._filterIncrement = self.filterIncrement; + await tile.setData(contextCopy, 'context2d'); + } + }; + + function setOptions(instance, options) { + options = options || {}; + const filters = options.filters; + instance.filters = !filters ? [] : + $.isArray(filters) ? filters : [filters]; + for (let i = 0; i < instance.filters.length; i++) { + const filter = instance.filters[i]; + if (!filter.processors) { + throw new Error('Filter processors must be specified.'); + } + filter.processors = $.isArray(filter.processors) ? + filter.processors : [filter.processors]; + } + instance.filterIncrement++; + instance.viewer.world.invalidateItems(); + instance.viewer.forceRedraw(); + } + + function getFiltersProcessors(instance, item) { + if (instance.filters.length === 0) { + return []; + } + + let globalProcessors = null; + for (let i = 0; i < instance.filters.length; i++) { + const filter = instance.filters[i]; + if (!filter.items) { + globalProcessors = filter.processors; + } else if (filter.items === item || + $.isArray(filter.items) && filter.items.indexOf(item) >= 0) { + return filter.processors; + } + } + return globalProcessors ? globalProcessors : []; + } + + $.Filters = { + THRESHOLDING: function(threshold) { + if (threshold < 0 || threshold > 255) { + throw new Error('Threshold must be between 0 and 255.'); + } + return function(context) { + const imgData = context.getImageData( + 0, 0, context.canvas.width, context.canvas.height); + const pixels = imgData.data; + for (let i = 0; i < pixels.length; i += 4) { + const r = pixels[i]; + const g = pixels[i + 1]; + const b = pixels[i + 2]; + const v = (r + g + b) / 3; + pixels[i] = pixels[i + 1] = pixels[i + 2] = + v < threshold ? 0 : 255; + } + context.putImageData(imgData, 0, 0); + }; + }, + BRIGHTNESS: function(adjustment) { + if (adjustment < -255 || adjustment > 255) { + throw new Error( + 'Brightness adjustment must be between -255 and 255.'); + } + const precomputedBrightness = []; + for (let i = 0; i < 256; i++) { + precomputedBrightness[i] = i + adjustment; + } + return function(context) { + const imgData = context.getImageData( + 0, 0, context.canvas.width, context.canvas.height); + const pixels = imgData.data; + for (let i = 0; i < pixels.length; i += 4) { + pixels[i] = precomputedBrightness[pixels[i]]; + pixels[i + 1] = precomputedBrightness[pixels[i + 1]]; + pixels[i + 2] = precomputedBrightness[pixels[i + 2]]; + } + context.putImageData(imgData, 0, 0); + }; + }, + CONTRAST: function(adjustment) { + if (adjustment < 0) { + throw new Error('Contrast adjustment must be positive.'); + } + const precomputedContrast = []; + for (let i = 0; i < 256; i++) { + precomputedContrast[i] = i * adjustment; + } + return function(context) { + const imgData = context.getImageData( + 0, 0, context.canvas.width, context.canvas.height); + const pixels = imgData.data; + for (let i = 0; i < pixels.length; i += 4) { + pixels[i] = precomputedContrast[pixels[i]]; + pixels[i + 1] = precomputedContrast[pixels[i + 1]]; + pixels[i + 2] = precomputedContrast[pixels[i + 2]]; + } + context.putImageData(imgData, 0, 0); + }; + }, + GAMMA: function(adjustment) { + if (adjustment < 0) { + throw new Error('Gamma adjustment must be positive.'); + } + const precomputedGamma = []; + for (let i = 0; i < 256; i++) { + precomputedGamma[i] = Math.pow(i / 255, adjustment) * 255; + } + return function(context) { + const imgData = context.getImageData( + 0, 0, context.canvas.width, context.canvas.height); + const pixels = imgData.data; + for (let i = 0; i < pixels.length; i += 4) { + pixels[i] = precomputedGamma[pixels[i]]; + pixels[i + 1] = precomputedGamma[pixels[i + 1]]; + pixels[i + 2] = precomputedGamma[pixels[i + 2]]; + } + context.putImageData(imgData, 0, 0); + }; + }, + GREYSCALE: function() { + return function(context) { + const imgData = context.getImageData( + 0, 0, context.canvas.width, context.canvas.height); + const pixels = imgData.data; + for (let i = 0; i < pixels.length; i += 4) { + const val = (pixels[i] + pixels[i + 1] + pixels[i + 2]) / 3; + pixels[i] = val; + pixels[i + 1] = val; + pixels[i + 2] = val; + } + context.putImageData(imgData, 0, 0); + }; + }, + INVERT: function() { + const precomputedInvert = []; + for (let i = 0; i < 256; i++) { + precomputedInvert[i] = 255 - i; + } + return function(context) { + const imgData = context.getImageData( + 0, 0, context.canvas.width, context.canvas.height); + const pixels = imgData.data; + for (let i = 0; i < pixels.length; i += 4) { + pixels[i] = precomputedInvert[pixels[i]]; + pixels[i + 1] = precomputedInvert[pixels[i + 1]]; + pixels[i + 2] = precomputedInvert[pixels[i + 2]]; + } + context.putImageData(imgData, 0, 0); + }; + }, + MORPHOLOGICAL_OPERATION: function(kernelSize, comparator) { + if (kernelSize % 2 === 0) { + throw new Error('The kernel size must be an odd number.'); + } + const kernelHalfSize = Math.floor(kernelSize / 2); + + if (!comparator) { + throw new Error('A comparator must be defined.'); + } + + return function(context) { + const width = context.canvas.width; + const height = context.canvas.height; + const imgData = context.getImageData(0, 0, width, height); + const originalPixels = context.getImageData(0, 0, width, height) + .data; + let offset; + + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + offset = (y * width + x) * 4; + let r = originalPixels[offset], + g = originalPixels[offset + 1], + b = originalPixels[offset + 2]; + for (let j = 0; j < kernelSize; j++) { + for (let i = 0; i < kernelSize; i++) { + const pixelX = x + i - kernelHalfSize; + const pixelY = y + j - kernelHalfSize; + if (pixelX >= 0 && pixelX < width && + pixelY >= 0 && pixelY < height) { + offset = (pixelY * width + pixelX) * 4; + r = comparator(originalPixels[offset], r); + g = comparator( + originalPixels[offset + 1], g); + b = comparator( + originalPixels[offset + 2], b); + } + } + } + imgData.data[offset] = r; + imgData.data[offset + 1] = g; + imgData.data[offset + 2] = b; + } + } + context.putImageData(imgData, 0, 0); + }; + }, + CONVOLUTION: function(kernel) { + if (!$.isArray(kernel)) { + throw new Error('The kernel must be an array.'); + } + const kernelSize = Math.sqrt(kernel.length); + if ((kernelSize + 1) % 2 !== 0) { + throw new Error('The kernel must be a square matrix with odd' + + 'width and height.'); + } + const kernelHalfSize = (kernelSize - 1) / 2; + + return function(context) { + const width = context.canvas.width; + const height = context.canvas.height; + const imgData = context.getImageData(0, 0, width, height); + const originalPixels = context.getImageData(0, 0, width, height) + .data; + let offset; + + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + let r = 0, g = 0, b = 0; + for (let j = 0; j < kernelSize; j++) { + for (let i = 0; i < kernelSize; i++) { + const pixelX = x + i - kernelHalfSize; + const pixelY = y + j - kernelHalfSize; + if (pixelX >= 0 && pixelX < width && + pixelY >= 0 && pixelY < height) { + offset = (pixelY * width + pixelX) * 4; + const weight = kernel[j * kernelSize + i]; + r += originalPixels[offset] * weight; + g += originalPixels[offset + 1] * weight; + b += originalPixels[offset + 2] * weight; + } + } + } + offset = (y * width + x) * 4; + imgData.data[offset] = r; + imgData.data[offset + 1] = g; + imgData.data[offset + 2] = b; + } + } + context.putImageData(imgData, 0, 0); + }; + }, + COLORMAP: function(cmap, ctr) { + const resampledCmap = cmap.slice(0); + const diff = 255 - ctr; + for (let i = 0; i < 256; i++) { + let position = i > ctr ? + Math.min((i - ctr) / diff * 128 + 128,255) | 0 : + Math.max(0, i / (ctr / 128)) | 0; + resampledCmap[i] = cmap[position]; + } + return function(context) { + const imgData = context.getImageData( + 0, 0, context.canvas.width, context.canvas.height); + const pxl = imgData.data; + for (let i = 0; i < pxl.length; i += 4) { + const v = (pxl[i] + pxl[i + 1] + pxl[i + 2]) / 3 | 0; + const c = resampledCmap[v]; + pxl[i] = c[0]; + pxl[i + 1] = c[1]; + pxl[i + 2] = c[2]; + } + context.putImageData(imgData, 0, 0); + }; + } + }; + +}()); diff --git a/test/demo/filtering-plugin/static/minus.png b/test/demo/filtering-plugin/static/minus.png new file mode 100644 index 00000000..2977e59f Binary files /dev/null and b/test/demo/filtering-plugin/static/minus.png differ diff --git a/test/demo/filtering-plugin/static/plus.png b/test/demo/filtering-plugin/static/plus.png new file mode 100644 index 00000000..614754c2 Binary files /dev/null and b/test/demo/filtering-plugin/static/plus.png differ diff --git a/test/demo/filtering-plugin/style.css b/test/demo/filtering-plugin/style.css new file mode 100644 index 00000000..fa66afa5 --- /dev/null +++ b/test/demo/filtering-plugin/style.css @@ -0,0 +1,81 @@ +/* + * Modified and maintained by the OpenSeadragon Community. + * + * This software was orignally developed at the National Institute of Standards and + * Technology by employees of the Federal Government. NIST assumes + * no responsibility whatsoever for its use by other parties, and makes no + * guarantees, expressed or implied, about its quality, reliability, or + * any other characteristic. + */ +.demo { + line-height: normal; +} + +.demo h3 { + margin-top: 5px; + margin-bottom: 5px; +} + +#openseadragon { + width: 100%; + height: 700px; + background-color: black; +} + +.wdzt-table-layout { + display: table; +} + +.wdzt-row-layout { + display: table-row; +} + +.wdzt-cell-layout { + display: table-cell; +} + +.wdzt-full-width { + width: 100%; +} + +.wdzt-menu-slider { + margin-left: 10px; + margin-right: 10px; +} + +.column-2 { + width: 50%; + vertical-align: top; + padding: 3px; +} + +#available { + list-style-type: none; +} + +ul { + padding: 0; + border: 1px solid black; + min-height: 25px; +} + +li { + padding: 3px; +} + +#selected { + list-style-type: none; +} + +.button { + cursor: pointer; + vertical-align: text-top; +} + +.filterLabel { + min-width: 120px; +} + +#selected .filterLabel { + cursor: move; +}