From a690b50eeeb2eb087844a6ed69e2b7149720f4ae Mon Sep 17 00:00:00 2001 From: Aiosa <34658867+Aiosa@users.noreply.github.com> Date: Sun, 10 Dec 2023 16:34:42 +0100 Subject: [PATCH] Add external execution pipeline (proof of concept implementation, needs polishing). Add filtering plugin live demo for testing. Fix issues with tile cache access outside its lifespan. Add custom css for the static page renderer and differentiate folder icons. Remove some old deprecations. --- Gruntfile.js | 7 +- src/control.js | 6 +- src/imageloader.js | 11 +- src/openseadragon.js | 110 ++- src/tile.js | 57 +- src/tilecache.js | 12 + src/tiledimage.js | 78 +- src/viewer.js | 19 +- src/world.js | 22 + style.css | 300 ++++++++ test/demo/filtering-plugin/demo.js | 750 ++++++++++++++++++++ test/demo/filtering-plugin/index.html | 74 ++ test/demo/filtering-plugin/plugin.js | 371 ++++++++++ test/demo/filtering-plugin/static/minus.png | Bin 0 -> 171 bytes test/demo/filtering-plugin/static/plus.png | Bin 0 -> 240 bytes test/demo/filtering-plugin/style.css | 81 +++ 16 files changed, 1816 insertions(+), 82 deletions(-) create mode 100644 style.css create mode 100644 test/demo/filtering-plugin/demo.js create mode 100644 test/demo/filtering-plugin/index.html create mode 100644 test/demo/filtering-plugin/plugin.js create mode 100644 test/demo/filtering-plugin/static/minus.png create mode 100644 test/demo/filtering-plugin/static/plus.png create mode 100644 test/demo/filtering-plugin/style.css 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 0000000000000000000000000000000000000000..2977e59f985e6c66b8ee618e07f439623ba6063d GIT binary patch literal 171 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VORhP%+Zh@d%sH$kc>2JB5A%Iw zSdCjaPaddO*~KIIr`_cXOcwGDY7>(`{L6QN!qCGJh9C-K75qZdi!R+~-l)|IthM zLi|DLZ}G*KqT=#l_vRU6mrgc8lom{bu_a)X#c{ h_hv6BV*MSi{eOl6|7DX%exU0ZJYD@<);T3K0RURlQS1N! literal 0 HcmV?d00001 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; +}