diff --git a/changelog.txt b/changelog.txt index eb18e085..8a661e31 100644 --- a/changelog.txt +++ b/changelog.txt @@ -7,6 +7,9 @@ OPENSEADRAGON CHANGELOG * Added support for commonjs (#984) * Added an option to addTiledImage to change the crossOriginPolicy (#981) * Fixed issue with tiles not appearing with wrapHorizontal/wrapVertical if you pan too far away from the origin (#987) +* The Viewer's tileSources option is now smarter about detecting JSON vs XML vs URL (#999) +* The navigationControlAnchor option now works for custom toolbar as well (#1004) +* Added getFullyLoaded method and "fully-loaded-change" event to TiledImage to know when tiles are fully loaded (#837) 2.2.1: diff --git a/src/tiledimage.js b/src/tiledimage.js index 69747482..34cd2e26 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -165,6 +165,8 @@ $.TiledImage = function( options ) { compositeOperation: $.DEFAULT_SETTINGS.compositeOperation }, options ); + this._fullyLoaded = false; + this._xSpring = new $.Spring({ initial: x, springStiffness: this.springStiffness, @@ -220,6 +222,37 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag return this._needsDraw; }, + /** + * @returns {Boolean} Whether all tiles necessary for this TiledImage to draw at the current view have been loaded. + */ + getFullyLoaded: function() { + return this._fullyLoaded; + }, + + // private + _setFullyLoaded: function(flag) { + if (flag === this._fullyLoaded) { + return; + } + + this._fullyLoaded = flag; + + /** + * Fired when the TiledImage's "fully loaded" flag (whether all tiles necessary for this TiledImage + * to draw at the current view have been loaded) changes. + * + * @event fully-loaded-change + * @memberof OpenSeadragon.TiledImage + * @type {object} + * @property {Boolean} fullyLoaded - The new "fully loaded" value. + * @property {OpenSeadragon.TiledImage} eventSource - A reference to the TiledImage which raised the event. + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ + this.raiseEvent('fully-loaded-change', { + fullyLoaded: this._fullyLoaded + }); + }, + /** * Clears all tiles and triggers an update on the next call to * {@link OpenSeadragon.TiledImage#update}. @@ -948,8 +981,10 @@ function updateViewport( tiledImage ) { // Load the new 'best' tile if (best && !best.context2D) { loadTile( tiledImage, best, currentTime ); + tiledImage._setFullyLoaded(false); + } else { + tiledImage._setFullyLoaded(true); } - } diff --git a/src/viewer.js b/src/viewer.js index e2d68862..842f7f2d 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -1749,7 +1749,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, if( this.toolbar ){ this.toolbar.addControl( this.navControl, - {anchor: $.ControlAnchor.TOP_LEFT} + {anchor: this.navigationControlAnchor || $.ControlAnchor.TOP_LEFT} ); } else { this.addControl( @@ -2124,9 +2124,11 @@ function getTileSourceImplementation( viewer, tileSource, imgOptions, successCal //allow plain xml strings or json strings to be parsed here if ( $.type( tileSource ) == 'string' ) { - if ( tileSource.match( /\s*<.*/ ) ) { + //xml should start with "<" and end with ">" + if ( tileSource.match( /^\s*<.*>\s*$/ ) ) { tileSource = $.parseXml( tileSource ); - } else if ( tileSource.match( /\s*[\{\[].*/ ) ) { + //json should start with "{" or "[" and end with "}" or "]" + } else if ( tileSource.match(/^\s*[\{\[].*[\}\]]\s*$/ ) ) { tileSource = $.parseJSON(tileSource); } } diff --git a/test/modules/multi-image.js b/test/modules/multi-image.js index 1cfd0515..4cbd5211 100644 --- a/test/modules/multi-image.js +++ b/test/modules/multi-image.js @@ -211,44 +211,51 @@ asyncTest('Transparent image on top of others', function() { viewer.open('/test/data/testpattern.dzi'); - // TODO: replace with fully-loaded event listener when available. - setTimeout(function() { - var imageData = viewer.drawer.context.getImageData(0, 0, 500, 500); - // Pixel 250,250 will be in the hole of the A - var expectedVal = getPixelValue(imageData, 250, 250); + var density = OpenSeadragon.pixelDensityRatio; - notEqual(expectedVal.r, 0, 'Red channel should not be 0'); - notEqual(expectedVal.g, 0, 'Green channel should not be 0'); - notEqual(expectedVal.b, 0, 'Blue channel should not be 0'); - notEqual(expectedVal.a, 0, 'Alpha channel should not be 0'); + viewer.addHandler('open', function() { + var firstImage = viewer.world.getItemAt(0); + firstImage.addHandler('fully-loaded-change', function() { + var imageData = viewer.drawer.context.getImageData(0, 0, + 500 * OpenSeadragon.pixelDensityRatio, 500 * density); - viewer.addSimpleImage({ - url: '/test/data/A.png' + // Pixel 250,250 will be in the hole of the A + var expectedVal = getPixelValue(imageData, 250 * density, 250 * density); + + notEqual(expectedVal.r, 0, 'Red channel should not be 0'); + notEqual(expectedVal.g, 0, 'Green channel should not be 0'); + notEqual(expectedVal.b, 0, 'Blue channel should not be 0'); + notEqual(expectedVal.a, 0, 'Alpha channel should not be 0'); + + viewer.addSimpleImage({ + url: '/test/data/A.png', + success: function() { + var secondImage = viewer.world.getItemAt(1); + secondImage.addHandler('fully-loaded-change', function() { + var imageData = viewer.drawer.context.getImageData(0, 0, 500 * density, 500 * density); + var actualVal = getPixelValue(imageData, 250 * density, 250 * density); + + equal(actualVal.r, expectedVal.r, + 'Red channel should not change in transparent part of the A'); + equal(actualVal.g, expectedVal.g, + 'Green channel should not change in transparent part of the A'); + equal(actualVal.b, expectedVal.b, + 'Blue channel should not change in transparent part of the A'); + equal(actualVal.a, expectedVal.a, + 'Alpha channel should not change in transparent part of the A'); + + var onAVal = getPixelValue(imageData, 333 * density, 250 * density); + equal(onAVal.r, 0, 'Red channel should be null on the A'); + equal(onAVal.g, 0, 'Green channel should be null on the A'); + equal(onAVal.b, 0, 'Blue channel should be null on the A'); + equal(onAVal.a, 255, 'Alpha channel should be 255 on the A'); + + start(); + }); + } + }); }); - - // TODO: replace with fully-loaded event listener when available. - setTimeout(function() { - var imageData = viewer.drawer.context.getImageData(0, 0, 500, 500); - var actualVal = getPixelValue(imageData, 250, 250); - - equal(actualVal.r, expectedVal.r, - 'Red channel should not change in transparent part of the A'); - equal(actualVal.g, expectedVal.g, - 'Green channel should not change in transparent part of the A'); - equal(actualVal.b, expectedVal.b, - 'Blue channel should not change in transparent part of the A'); - equal(actualVal.a, expectedVal.a, - 'Alpha channel should not change in transparent part of the A'); - - var onAVal = getPixelValue(imageData, 333, 250); - equal(onAVal.r, 0, 'Red channel should be null on the A'); - equal(onAVal.g, 0, 'Green channel should be null on the A'); - equal(onAVal.b, 0, 'Blue channel should be null on the A'); - equal(onAVal.a, 255, 'Alpha channel should be 255 on the A'); - - start(); - }, 500); - }, 500); + }); function getPixelValue(imageData, x, y) { var offset = 4 * (y * imageData.width + x); diff --git a/test/modules/tiledimage.js b/test/modules/tiledimage.js index 9c2356a6..8ff45ccd 100644 --- a/test/modules/tiledimage.js +++ b/test/modules/tiledimage.js @@ -207,8 +207,8 @@ Util.spyOnce(viewer.drawer, 'setClip', function(rect) { var homeBounds = viewer.viewport.getHomeBounds(); - var canvasClip = viewer.viewport - .viewportToViewerElementRectangle(homeBounds); + var canvasClip = viewer.drawer + .viewportToDrawerRectangle(homeBounds); var precision = 0.00000001; Util.assertRectangleEquals(rect, canvasClip, precision, 'clipping should be ' + canvasClip); @@ -222,6 +222,7 @@ }); }); + // ---------- asyncTest('getClipBounds', function() { var clip = new OpenSeadragon.Rect(100, 200, 800, 500); @@ -367,6 +368,7 @@ ]); }); + // ---------- asyncTest('fitBounds in constructor', function() { function assertRectEquals(actual, expected, message) { @@ -412,6 +414,7 @@ }]); }); + // ---------- asyncTest('fitBounds with clipping', function() { function assertRectEquals(actual, expected, message) { @@ -455,4 +458,40 @@ fitBoundsPlacement: OpenSeadragon.Placement.TOP_LEFT }]); }); + + // ---------- + asyncTest('fullyLoaded', function() { + viewer.addHandler('open', function openHandler() { + viewer.removeHandler('open', openHandler); + + var image = viewer.world.getItemAt(0); + equal(image.getFullyLoaded(), false, 'not fully loaded at first'); + + var count = 0; + + var fullyLoadedChangeHandler = function(event) { + if (count === 0) { + equal(event.fullyLoaded, true, 'event includes true fullyLoaded property'); + equal(image.getFullyLoaded(), true, 'image is fully loaded after event'); + viewer.viewport.zoomBy(5, null, true); + } else if (count === 1) { + equal(event.fullyLoaded, false, 'event includes false fullyLoaded property'); + equal(image.getFullyLoaded(), false, 'image is not fully loaded after zoom'); + } else { + image.removeHandler('fully-loaded-change', fullyLoadedChangeHandler); + equal(image.getFullyLoaded(), true, 'image is once again fully loaded'); + start(); + } + + count++; + }; + + image.addHandler('fully-loaded-change', fullyLoadedChangeHandler); + }); + + viewer.open([{ + tileSource: '/test/data/tall.dzi', + }]); + }); + })();