openseadragon 0.3.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/lib/openseadragon/version.rb +1 -1
  3. data/vendor/assets/images/openseadragon/button_grouphover.png +0 -0
  4. data/vendor/assets/images/openseadragon/button_hover.png +0 -0
  5. data/vendor/assets/images/openseadragon/button_pressed.png +0 -0
  6. data/vendor/assets/images/openseadragon/button_rest.png +0 -0
  7. data/vendor/assets/images/openseadragon/fullpage_grouphover.png +0 -0
  8. data/vendor/assets/images/openseadragon/fullpage_hover.png +0 -0
  9. data/vendor/assets/images/openseadragon/fullpage_pressed.png +0 -0
  10. data/vendor/assets/images/openseadragon/fullpage_rest.png +0 -0
  11. data/vendor/assets/images/openseadragon/home_grouphover.png +0 -0
  12. data/vendor/assets/images/openseadragon/home_hover.png +0 -0
  13. data/vendor/assets/images/openseadragon/home_pressed.png +0 -0
  14. data/vendor/assets/images/openseadragon/home_rest.png +0 -0
  15. data/vendor/assets/images/openseadragon/next_grouphover.png +0 -0
  16. data/vendor/assets/images/openseadragon/next_hover.png +0 -0
  17. data/vendor/assets/images/openseadragon/next_pressed.png +0 -0
  18. data/vendor/assets/images/openseadragon/next_rest.png +0 -0
  19. data/vendor/assets/images/openseadragon/previous_grouphover.png +0 -0
  20. data/vendor/assets/images/openseadragon/previous_hover.png +0 -0
  21. data/vendor/assets/images/openseadragon/previous_pressed.png +0 -0
  22. data/vendor/assets/images/openseadragon/previous_rest.png +0 -0
  23. data/vendor/assets/images/openseadragon/rotateleft_grouphover.png +0 -0
  24. data/vendor/assets/images/openseadragon/rotateleft_hover.png +0 -0
  25. data/vendor/assets/images/openseadragon/rotateleft_pressed.png +0 -0
  26. data/vendor/assets/images/openseadragon/rotateleft_rest.png +0 -0
  27. data/vendor/assets/images/openseadragon/rotateright_grouphover.png +0 -0
  28. data/vendor/assets/images/openseadragon/rotateright_hover.png +0 -0
  29. data/vendor/assets/images/openseadragon/rotateright_pressed.png +0 -0
  30. data/vendor/assets/images/openseadragon/rotateright_rest.png +0 -0
  31. data/vendor/assets/images/openseadragon/zoomin_grouphover.png +0 -0
  32. data/vendor/assets/images/openseadragon/zoomin_hover.png +0 -0
  33. data/vendor/assets/images/openseadragon/zoomin_pressed.png +0 -0
  34. data/vendor/assets/images/openseadragon/zoomin_rest.png +0 -0
  35. data/vendor/assets/images/openseadragon/zoomout_grouphover.png +0 -0
  36. data/vendor/assets/images/openseadragon/zoomout_hover.png +0 -0
  37. data/vendor/assets/images/openseadragon/zoomout_pressed.png +0 -0
  38. data/vendor/assets/images/openseadragon/zoomout_rest.png +0 -0
  39. data/vendor/assets/javascripts/openseadragon/openseadragon.js +1807 -636
  40. data/vendor/assets/javascripts/openseadragon/openseadragon.js.map +1 -1
  41. metadata +7 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7f930846ab0d3eb81235fdeced93e81a0b2af275
4
- data.tar.gz: 0a104c9ab9a42d5000ec0757236976ded9b56936
3
+ metadata.gz: 9c1293c3a2424f19cf39c77892b3d3fad7447211
4
+ data.tar.gz: c3a989f62d49e246f644700e022eed592f00da41
5
5
  SHA512:
6
- metadata.gz: 8c81bc6395825d337c02808dcf87402d79b5d5a13c5e2800fb14a3701ed08473cbd58e57d496d243727f39dd47b34f4084f6c7325bd47ed07f687994c681fce6
7
- data.tar.gz: f0d33820043b30bddb41f7806d517e5bc70b63b7e8d9aae7bd646a9ccdcba9d1968208fe8f4818a63ba78f98fbb6ce927a823a59f7ddad67ba08367d0ac1a5bc
6
+ metadata.gz: 27f131316b63cc5aeed1bd97fa24a7221303ba53ae77bcd45d408311d73448e22d267fd1f3b5575ea1c79b719478c28b8099d9220821ab9a13347f4c769e3208
7
+ data.tar.gz: 0a6bf8f4ae55f16e4fca9dce29a25443b3c0dcfbacb3cdd442bada81990bf6a8792bd5f7e221ea0fdff2551d0bfc07a4f9cc9b1560d1778580322a12bc9e0492
@@ -1,3 +1,3 @@
1
1
  module Openseadragon
2
- VERSION = "0.3.3"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -1,6 +1,6 @@
1
- //! openseadragon 2.2.1
2
- //! Built on 2016-06-21
3
- //! Git commit: v2.2.1-0-babdefd
1
+ //! openseadragon 2.3.0
2
+ //! Built on 2017-07-14
3
+ //! Git commit: v2.3.0-2-b49fef3
4
4
  //! http://openseadragon.github.io
5
5
  //! License: http://openseadragon.github.io/license/
6
6
 
@@ -90,7 +90,7 @@
90
90
 
91
91
  /**
92
92
  * @namespace OpenSeadragon
93
- * @version openseadragon 2.2.1
93
+ * @version openseadragon 2.3.0
94
94
  * @classdesc The root namespace for OpenSeadragon. All utility methods
95
95
  * and classes are defined on or below this namespace.
96
96
  *
@@ -191,7 +191,11 @@
191
191
  * If 0, adjusts to fit viewer.
192
192
  *
193
193
  * @property {Number} [opacity=1]
194
- * Default opacity of the tiled images (1=opaque, 0=transparent)
194
+ * Default proportional opacity of the tiled images (1=opaque, 0=hidden)
195
+ * Hidden images do not draw and only load when preloading is allowed.
196
+ *
197
+ * @property {Boolean} [preload=false]
198
+ * Default switch for loading hidden images (true loads, false blocks)
195
199
  *
196
200
  * @property {String} [compositeOperation=null]
197
201
  * Valid values are 'source-over', 'source-atop', 'source-in', 'source-out',
@@ -417,6 +421,7 @@
417
421
  * The max number of images we should keep in memory (per drawer).
418
422
  *
419
423
  * @property {Number} [timeout=30000]
424
+ * The max number of milliseconds that an image job may take to complete.
420
425
  *
421
426
  * @property {Boolean} [useCanvas=true]
422
427
  * Set to false to not use an HTML canvas element for image rendering even if canvas is supported.
@@ -590,9 +595,16 @@
590
595
  * not use CORS, and the canvas will be tainted.
591
596
  *
592
597
  * @property {Boolean} [ajaxWithCredentials=false]
593
- * Whether to set the withCredentials XHR flag for AJAX requests (when loading tile sources).
598
+ * Whether to set the withCredentials XHR flag for AJAX requests.
599
+ * Note that this can be overridden at the {@link OpenSeadragon.TileSource} level.
600
+ *
601
+ * @property {Boolean} [loadTilesWithAjax=false]
602
+ * Whether to load tile data using AJAX requests.
594
603
  * Note that this can be overridden at the {@link OpenSeadragon.TileSource} level.
595
604
  *
605
+ * @property {Object} [ajaxHeaders={}]
606
+ * A set of headers to include when making AJAX requests for tile sources or tiles.
607
+ *
596
608
  */
597
609
 
598
610
  /**
@@ -691,19 +703,10 @@
691
703
  * @param {OpenSeadragon.Options} options - Viewer options.
692
704
  * @returns {OpenSeadragon.Viewer}
693
705
  */
694
- window.OpenSeadragon = window.OpenSeadragon || function( options ){
695
-
706
+ function OpenSeadragon( options ){
696
707
  return new OpenSeadragon.Viewer( options );
697
-
698
- };
699
-
700
- if (typeof define === 'function' && define.amd) {
701
- define(function () {
702
- return (window.OpenSeadragon);
703
- });
704
708
  }
705
709
 
706
-
707
710
  (function( $ ){
708
711
 
709
712
 
@@ -718,10 +721,10 @@ if (typeof define === 'function' && define.amd) {
718
721
  * @since 1.0.0
719
722
  */
720
723
  $.version = {
721
- versionStr: '2.2.1',
724
+ versionStr: '2.3.0',
722
725
  major: parseInt('2', 10),
723
- minor: parseInt('2', 10),
724
- revision: parseInt('1', 10)
726
+ minor: parseInt('3', 10),
727
+ revision: parseInt('0', 10)
725
728
  };
726
729
 
727
730
 
@@ -874,7 +877,7 @@ if (typeof define === 'function' && define.amd) {
874
877
  try {
875
878
  // We test if the canvas is tainted by retrieving data from it.
876
879
  // An exception will be raised if the canvas is tainted.
877
- var data = canvas.getContext('2d').getImageData(0, 0, 1, 1);
880
+ canvas.getContext('2d').getImageData(0, 0, 1, 1);
878
881
  } catch (e) {
879
882
  isTainted = true;
880
883
  }
@@ -882,7 +885,8 @@ if (typeof define === 'function' && define.amd) {
882
885
  };
883
886
 
884
887
  /**
885
- * A ratio comparing the device screen's pixel density to the canvas's backing store pixel density. Defaults to 1 if canvas isn't supported by the browser.
888
+ * A ratio comparing the device screen's pixel density to the canvas's backing store pixel density,
889
+ * clamped to a minimum of 1. Defaults to 1 if canvas isn't supported by the browser.
886
890
  * @member {Number} pixelDensityRatio
887
891
  * @memberof OpenSeadragon
888
892
  */
@@ -895,7 +899,7 @@ if (typeof define === 'function' && define.amd) {
895
899
  context.msBackingStorePixelRatio ||
896
900
  context.oBackingStorePixelRatio ||
897
901
  context.backingStorePixelRatio || 1;
898
- return devicePixelRatio / backingStoreRatio;
902
+ return Math.max(devicePixelRatio, 1) / backingStoreRatio;
899
903
  } else {
900
904
  return 1;
901
905
  }
@@ -1020,6 +1024,8 @@ if (typeof define === 'function' && define.amd) {
1020
1024
  initialPage: 0,
1021
1025
  crossOriginPolicy: false,
1022
1026
  ajaxWithCredentials: false,
1027
+ loadTilesWithAjax: false,
1028
+ ajaxHeaders: {},
1023
1029
 
1024
1030
  //PAN AND ZOOM SETTINGS AND CONSTRAINTS
1025
1031
  panHorizontal: true,
@@ -1041,10 +1047,46 @@ if (typeof define === 'function' && define.amd) {
1041
1047
  dblClickDistThreshold: 20,
1042
1048
  springStiffness: 6.5,
1043
1049
  animationTime: 1.2,
1044
- gestureSettingsMouse: { scrollToZoom: true, clickToZoom: true, dblClickToZoom: false, pinchToZoom: false, flickEnabled: false, flickMinSpeed: 120, flickMomentum: 0.25, pinchRotate: false },
1045
- gestureSettingsTouch: { scrollToZoom: false, clickToZoom: false, dblClickToZoom: true, pinchToZoom: true, flickEnabled: true, flickMinSpeed: 120, flickMomentum: 0.25, pinchRotate: false },
1046
- gestureSettingsPen: { scrollToZoom: false, clickToZoom: true, dblClickToZoom: false, pinchToZoom: false, flickEnabled: false, flickMinSpeed: 120, flickMomentum: 0.25, pinchRotate: false },
1047
- gestureSettingsUnknown: { scrollToZoom: false, clickToZoom: false, dblClickToZoom: true, pinchToZoom: true, flickEnabled: true, flickMinSpeed: 120, flickMomentum: 0.25, pinchRotate: false },
1050
+ gestureSettingsMouse: {
1051
+ scrollToZoom: true,
1052
+ clickToZoom: true,
1053
+ dblClickToZoom: false,
1054
+ pinchToZoom: false,
1055
+ flickEnabled: false,
1056
+ flickMinSpeed: 120,
1057
+ flickMomentum: 0.25,
1058
+ pinchRotate: false
1059
+ },
1060
+ gestureSettingsTouch: {
1061
+ scrollToZoom: false,
1062
+ clickToZoom: false,
1063
+ dblClickToZoom: true,
1064
+ pinchToZoom: true,
1065
+ flickEnabled: true,
1066
+ flickMinSpeed: 120,
1067
+ flickMomentum: 0.25,
1068
+ pinchRotate: false
1069
+ },
1070
+ gestureSettingsPen: {
1071
+ scrollToZoom: false,
1072
+ clickToZoom: true,
1073
+ dblClickToZoom: false,
1074
+ pinchToZoom: false,
1075
+ flickEnabled: false,
1076
+ flickMinSpeed: 120,
1077
+ flickMomentum: 0.25,
1078
+ pinchRotate: false
1079
+ },
1080
+ gestureSettingsUnknown: {
1081
+ scrollToZoom: false,
1082
+ clickToZoom: false,
1083
+ dblClickToZoom: true,
1084
+ pinchToZoom: true,
1085
+ flickEnabled: true,
1086
+ flickMinSpeed: 120,
1087
+ flickMomentum: 0.25,
1088
+ pinchRotate: false
1089
+ },
1048
1090
  zoomPerClick: 2,
1049
1091
  zoomPerScroll: 1.2,
1050
1092
  zoomPerSecond: 1.0,
@@ -1096,6 +1138,7 @@ if (typeof define === 'function' && define.amd) {
1096
1138
 
1097
1139
  // APPEARANCE
1098
1140
  opacity: 1,
1141
+ preload: false,
1099
1142
  compositeOperation: null,
1100
1143
  placeholderFillStyle: null,
1101
1144
 
@@ -1390,6 +1433,21 @@ if (typeof define === 'function' && define.amd) {
1390
1433
  return string.charAt(0).toUpperCase() + string.slice(1);
1391
1434
  },
1392
1435
 
1436
+ /**
1437
+ * Compute the modulo of a number but makes sure to always return
1438
+ * a positive value.
1439
+ * @param {Number} number the number to computes the modulo of
1440
+ * @param {Number} modulo the modulo
1441
+ * @returns {Number} the result of the modulo of number
1442
+ */
1443
+ positiveModulo: function(number, modulo) {
1444
+ var result = number % modulo;
1445
+ if (result < 0) {
1446
+ result += modulo;
1447
+ }
1448
+ return result;
1449
+ },
1450
+
1393
1451
  /**
1394
1452
  * Determines if a point is within the bounding rectangle of the given element (hit-test).
1395
1453
  * @function
@@ -1504,7 +1562,7 @@ if (typeof define === 'function' && define.amd) {
1504
1562
  };
1505
1563
  } else {
1506
1564
  // We can't reassign the function yet, as there was no scroll.
1507
- return new $.Point(0,0);
1565
+ return new $.Point(0, 0);
1508
1566
  }
1509
1567
 
1510
1568
  return $.getPageScroll();
@@ -1672,13 +1730,15 @@ if (typeof define === 'function' && define.amd) {
1672
1730
  * @function
1673
1731
  */
1674
1732
  now: function( ) {
1675
- if (Date.now) {
1676
- $.now = Date.now;
1677
- } else {
1678
- $.now = function() { return new Date().getTime(); };
1679
- }
1733
+ if (Date.now) {
1734
+ $.now = Date.now;
1735
+ } else {
1736
+ $.now = function() {
1737
+ return new Date().getTime();
1738
+ };
1739
+ }
1680
1740
 
1681
- return $.now();
1741
+ return $.now();
1682
1742
  },
1683
1743
 
1684
1744
 
@@ -1788,7 +1848,7 @@ if (typeof define === 'function' && define.amd) {
1788
1848
  addClass: function( element, className ) {
1789
1849
  element = $.getElement( element );
1790
1850
 
1791
- if ( ! element.className ) {
1851
+ if (!element.className) {
1792
1852
  element.className = className;
1793
1853
  } else if ( ( ' ' + element.className + ' ' ).
1794
1854
  indexOf( ' ' + className + ' ' ) === -1 ) {
@@ -2012,6 +2072,7 @@ if (typeof define === 'function' && define.amd) {
2012
2072
  * @returns {String} The value of the url parameter or null if no param matches.
2013
2073
  */
2014
2074
  getUrlParameter: function( key ) {
2075
+ // eslint-disable-next-line no-use-before-define
2015
2076
  var value = URLPARAMS[ key ];
2016
2077
  return value ? value : null;
2017
2078
  },
@@ -2081,11 +2142,16 @@ if (typeof define === 'function' && define.amd) {
2081
2142
  * @param {String} options.url - the url to request
2082
2143
  * @param {Function} options.success - a function to call on a successful response
2083
2144
  * @param {Function} options.error - a function to call on when an error occurs
2145
+ * @param {Object} options.headers - headers to add to the AJAX request
2146
+ * @param {String} options.responseType - the response type of the the AJAX request
2084
2147
  * @param {Boolean} [options.withCredentials=false] - whether to set the XHR's withCredentials
2085
2148
  * @throws {Error}
2149
+ * @returns {XMLHttpRequest}
2086
2150
  */
2087
2151
  makeAjaxRequest: function( url, onSuccess, onError ) {
2088
2152
  var withCredentials;
2153
+ var headers;
2154
+ var responseType;
2089
2155
 
2090
2156
  // Note that our preferred API is that you pass in a single object; the named
2091
2157
  // arguments are for legacy support.
@@ -2093,6 +2159,8 @@ if (typeof define === 'function' && define.amd) {
2093
2159
  onSuccess = url.success;
2094
2160
  onError = url.error;
2095
2161
  withCredentials = url.withCredentials;
2162
+ headers = url.headers;
2163
+ responseType = url.responseType || null;
2096
2164
  url = url.url;
2097
2165
  }
2098
2166
 
@@ -2108,9 +2176,9 @@ if (typeof define === 'function' && define.amd) {
2108
2176
  if ( request.readyState == 4 ) {
2109
2177
  request.onreadystatechange = function(){};
2110
2178
 
2111
- // With protocols other than http/https, the status is 200
2112
- // on Firefox and 0 on other browsers
2113
- if ( request.status === 200 ||
2179
+ // With protocols other than http/https, a successful request status is in
2180
+ // the 200's on Firefox and 0 on other browsers
2181
+ if ( (request.status >= 200 && request.status < 300) ||
2114
2182
  ( request.status === 0 &&
2115
2183
  protocol !== "http:" &&
2116
2184
  protocol !== "https:" )) {
@@ -2125,13 +2193,26 @@ if (typeof define === 'function' && define.amd) {
2125
2193
  }
2126
2194
  };
2127
2195
 
2128
- if (withCredentials) {
2129
- request.withCredentials = true;
2130
- }
2131
-
2132
2196
  try {
2133
2197
  request.open( "GET", url, true );
2134
- request.send( null );
2198
+
2199
+ if (responseType) {
2200
+ request.responseType = responseType;
2201
+ }
2202
+
2203
+ if (headers) {
2204
+ for (var headerName in headers) {
2205
+ if (headers.hasOwnProperty(headerName) && headers[headerName]) {
2206
+ request.setRequestHeader(headerName, headers[headerName]);
2207
+ }
2208
+ }
2209
+ }
2210
+
2211
+ if (withCredentials) {
2212
+ request.withCredentials = true;
2213
+ }
2214
+
2215
+ request.send(null);
2135
2216
  } catch (e) {
2136
2217
  var msg = e.message;
2137
2218
 
@@ -2168,7 +2249,7 @@ if (typeof define === 'function' && define.amd) {
2168
2249
  }
2169
2250
  };
2170
2251
  xdr.onerror = function (e) {
2171
- if ( $.isFunction ( onError ) ) {
2252
+ if ($.isFunction(onError)) {
2172
2253
  onError({ // Faking an xhr object
2173
2254
  responseText: xdr.responseText,
2174
2255
  status: 444, // 444 No Response
@@ -2191,6 +2272,8 @@ if (typeof define === 'function' && define.amd) {
2191
2272
  }
2192
2273
  }
2193
2274
  }
2275
+
2276
+ return request;
2194
2277
  },
2195
2278
 
2196
2279
  /**
@@ -2331,6 +2414,7 @@ if (typeof define === 'function' && define.amd) {
2331
2414
  // Should only be used by IE8 in non standards mode
2332
2415
  $.parseJSON = function(string) {
2333
2416
  /*jshint evil:true*/
2417
+ //eslint-disable-next-line no-eval
2334
2418
  return eval('(' + string + ')');
2335
2419
  };
2336
2420
  }
@@ -2346,6 +2430,7 @@ if (typeof define === 'function' && define.amd) {
2346
2430
  */
2347
2431
  imageFormatSupported: function( extension ) {
2348
2432
  extension = extension ? extension : "";
2433
+ // eslint-disable-next-line no-use-before-define
2349
2434
  return !!FILEFORMATS[ extension.toLowerCase() ];
2350
2435
  }
2351
2436
 
@@ -2382,8 +2467,7 @@ if (typeof define === 'function' && define.amd) {
2382
2467
  (function() {
2383
2468
  //A small auto-executing routine to determine the browser vendor,
2384
2469
  //version and supporting feature sets.
2385
- var app = navigator.appName,
2386
- ver = navigator.appVersion,
2470
+ var ver = navigator.appVersion,
2387
2471
  ua = navigator.userAgent,
2388
2472
  regex;
2389
2473
 
@@ -2405,7 +2489,7 @@ if (typeof define === 'function' && define.amd) {
2405
2489
  }
2406
2490
  break;
2407
2491
  case "Netscape":
2408
- if( !!window.addEventListener ){
2492
+ if (window.addEventListener) {
2409
2493
  if ( ua.indexOf( "Firefox" ) >= 0 ) {
2410
2494
  $.Browser.vendor = $.BROWSERS.FIREFOX;
2411
2495
  $.Browser.version = parseFloat(
@@ -2602,6 +2686,23 @@ if (typeof define === 'function' && define.amd) {
2602
2686
 
2603
2687
  }(OpenSeadragon));
2604
2688
 
2689
+
2690
+ // Universal Module Definition, supports CommonJS, AMD and simple script tag
2691
+ (function (root, factory) {
2692
+ if (typeof define === 'function' && define.amd) {
2693
+ // expose as amd module
2694
+ define([], factory);
2695
+ } else if (typeof module === 'object' && module.exports) {
2696
+ // expose as commonjs module
2697
+ module.exports = factory();
2698
+ } else {
2699
+ // expose as window.OpenSeadragon
2700
+ root.OpenSeadragon = factory();
2701
+ }
2702
+ }(this, function () {
2703
+ return OpenSeadragon;
2704
+ }));
2705
+
2605
2706
  /*
2606
2707
  * OpenSeadragon - full-screen support functions
2607
2708
  *
@@ -3100,6 +3201,7 @@ $.EventSource.prototype = {
3100
3201
  * @memberof OpenSeadragon.MouseTracker#
3101
3202
  */
3102
3203
  this.dblClickDistThreshold = options.dblClickDistThreshold || $.DEFAULT_SETTINGS.dblClickDistThreshold;
3204
+ /*eslint-disable no-multi-spaces*/
3103
3205
  this.userData = options.userData || null;
3104
3206
  this.stopDelay = options.stopDelay || 50;
3105
3207
 
@@ -3122,6 +3224,7 @@ $.EventSource.prototype = {
3122
3224
  this.keyHandler = options.keyHandler || null;
3123
3225
  this.focusHandler = options.focusHandler || null;
3124
3226
  this.blurHandler = options.blurHandler || null;
3227
+ /*eslint-enable no-multi-spaces*/
3125
3228
 
3126
3229
  //Store private properties in a scope sealed hash map
3127
3230
  var _this = this;
@@ -3255,6 +3358,25 @@ $.EventSource.prototype = {
3255
3358
  return this;
3256
3359
  },
3257
3360
 
3361
+ /**
3362
+ * Returns the {@link OpenSeadragon.MouseTracker.GesturePointList|GesturePointList} for all but the given pointer device type.
3363
+ * @function
3364
+ * @param {String} type - The pointer device type: "mouse", "touch", "pen", etc.
3365
+ * @returns {Array.<OpenSeadragon.MouseTracker.GesturePointList>}
3366
+ */
3367
+ getActivePointersListsExceptType: function ( type ) {
3368
+ var delegate = THIS[ this.hash ];
3369
+ var listArray = [];
3370
+
3371
+ for (var i = 0; i < delegate.activePointersLists.length; ++i) {
3372
+ if (delegate.activePointersLists[i].type !== type) {
3373
+ listArray.push(delegate.activePointersLists[i]);
3374
+ }
3375
+ }
3376
+
3377
+ return listArray;
3378
+ },
3379
+
3258
3380
  /**
3259
3381
  * Returns the {@link OpenSeadragon.MouseTracker.GesturePointList|GesturePointList} for the given pointer device type,
3260
3382
  * creating and caching a new {@link OpenSeadragon.MouseTracker.GesturePointList|GesturePointList} if one doesn't already exist for the type.
@@ -3800,6 +3922,21 @@ $.EventSource.prototype = {
3800
3922
  blurHandler: function () { }
3801
3923
  };
3802
3924
 
3925
+ /**
3926
+ * Resets all active mousetrakers. (Added to patch issue #697 "Mouse up outside map will cause "canvas-drag" event to stick")
3927
+ *
3928
+ * @private
3929
+ * @member resetAllMouseTrackers
3930
+ * @memberof OpenSeadragon.MouseTracker
3931
+ */
3932
+ $.MouseTracker.resetAllMouseTrackers = function(){
3933
+ for(var i = 0; i < MOUSETRACKERS.length; i++){
3934
+ if (MOUSETRACKERS[i].isTracking()){
3935
+ MOUSETRACKERS[i].setTracking(false);
3936
+ MOUSETRACKERS[i].setTracking(true);
3937
+ }
3938
+ }
3939
+ };
3803
3940
 
3804
3941
  /**
3805
3942
  * Provides continuous computation of velocity (speed and direction) of active pointers.
@@ -4139,6 +4276,30 @@ $.EventSource.prototype = {
4139
4276
  }
4140
4277
  }
4141
4278
  return null;
4279
+ },
4280
+
4281
+ /**
4282
+ * @function Increment this pointer's contact count.
4283
+ * It will evaluate whether this pointer type is allowed to have multiple contacts.
4284
+ */
4285
+ addContact: function() {
4286
+ ++this.contacts;
4287
+
4288
+ if (this.contacts > 1 && (this.type === "mouse" || this.type === "pen")) {
4289
+ this.contacts = 1;
4290
+ }
4291
+ },
4292
+
4293
+ /**
4294
+ * @function Decrement this pointer's contact count.
4295
+ * It will make sure the count does not go below 0.
4296
+ */
4297
+ removeContact: function() {
4298
+ --this.contacts;
4299
+
4300
+ if (this.contacts < 0) {
4301
+ this.contacts = 0;
4302
+ }
4142
4303
  }
4143
4304
  };
4144
4305
 
@@ -4310,6 +4471,7 @@ $.EventSource.prototype = {
4310
4471
  eventParams = getCaptureEventParams( tracker, $.MouseTracker.havePointerEvents ? 'pointerevent' : pointerType );
4311
4472
  // We emulate mouse capture by hanging listeners on the document object.
4312
4473
  // (Note we listen on the capture phase so the captured handlers will get called first)
4474
+ // eslint-disable-next-line no-use-before-define
4313
4475
  if (isInIframe && canAccessEvents(window.top)) {
4314
4476
  $.addEvent(
4315
4477
  window.top,
@@ -4353,6 +4515,7 @@ $.EventSource.prototype = {
4353
4515
  eventParams = getCaptureEventParams( tracker, $.MouseTracker.havePointerEvents ? 'pointerevent' : pointerType );
4354
4516
  // We emulate mouse capture by hanging listeners on the document object.
4355
4517
  // (Note we listen on the capture phase so the captured handlers will get called first)
4518
+ // eslint-disable-next-line no-use-before-define
4356
4519
  if (isInIframe && canAccessEvents(window.top)) {
4357
4520
  $.removeEvent(
4358
4521
  window.top,
@@ -4643,7 +4806,7 @@ $.EventSource.prototype = {
4643
4806
 
4644
4807
  // Calculate deltaY
4645
4808
  if ( $.MouseTracker.wheelEventName == "mousewheel" ) {
4646
- simulatedEvent.deltaY = - 1 / $.DEFAULT_SETTINGS.pixelsPerWheelLine * event.wheelDelta;
4809
+ simulatedEvent.deltaY = -event.wheelDelta / $.DEFAULT_SETTINGS.pixelsPerWheelLine;
4647
4810
  } else {
4648
4811
  simulatedEvent.deltaY = event.detail;
4649
4812
  }
@@ -4941,23 +5104,26 @@ $.EventSource.prototype = {
4941
5104
  * @private
4942
5105
  * @inner
4943
5106
  */
4944
- function abortTouchContacts( tracker, event, pointsList ) {
5107
+ function abortContacts( tracker, event, pointsList ) {
4945
5108
  var i,
4946
5109
  gPointCount = pointsList.getLength(),
4947
5110
  abortGPoints = [];
4948
5111
 
4949
- for ( i = 0; i < gPointCount; i++ ) {
4950
- abortGPoints.push( pointsList.getByIndex( i ) );
4951
- }
5112
+ // Check contact count for hoverable pointer types before aborting
5113
+ if (pointsList.type === 'touch' || pointsList.contacts > 0) {
5114
+ for ( i = 0; i < gPointCount; i++ ) {
5115
+ abortGPoints.push( pointsList.getByIndex( i ) );
5116
+ }
4952
5117
 
4953
- if ( abortGPoints.length > 0 ) {
4954
- // simulate touchend
4955
- updatePointersUp( tracker, event, abortGPoints, 0 ); // 0 means primary button press/release or touch contact
4956
- // release pointer capture
4957
- pointsList.captureCount = 1;
4958
- releasePointer( tracker, 'touch' );
4959
- // simulate touchleave
4960
- updatePointersExit( tracker, event, abortGPoints );
5118
+ if ( abortGPoints.length > 0 ) {
5119
+ // simulate touchend/mouseup
5120
+ updatePointersUp( tracker, event, abortGPoints, 0 ); // 0 means primary button press/release or touch contact
5121
+ // release pointer capture
5122
+ pointsList.captureCount = 1;
5123
+ releasePointer( tracker, pointsList.type );
5124
+ // simulate touchleave/mouseout
5125
+ updatePointersExit( tracker, event, abortGPoints );
5126
+ }
4961
5127
  }
4962
5128
  }
4963
5129
 
@@ -4979,7 +5145,7 @@ $.EventSource.prototype = {
4979
5145
 
4980
5146
  if ( pointsList.getLength() > event.touches.length - touchCount ) {
4981
5147
  $.console.warn('Tracked touch contact count doesn\'t match event.touches.length. Removing all tracked touch pointers.');
4982
- abortTouchContacts( tracker, event, pointsList );
5148
+ abortContacts( tracker, event, pointsList );
4983
5149
  }
4984
5150
 
4985
5151
  for ( i = 0; i < touchCount; i++ ) {
@@ -5147,12 +5313,9 @@ $.EventSource.prototype = {
5147
5313
  * @inner
5148
5314
  */
5149
5315
  function onTouchCancel( tracker, event ) {
5150
- var i,
5151
- touchCount = event.changedTouches.length,
5152
- gPoints = [],
5153
- pointsList = tracker.getActivePointersListByType( 'touch' );
5154
-
5155
- abortTouchContacts( tracker, event, pointsList );
5316
+ var pointsList = tracker.getActivePointersListByType('touch');
5317
+
5318
+ abortContacts( tracker, event, pointsList );
5156
5319
  }
5157
5320
 
5158
5321
 
@@ -5505,8 +5668,7 @@ $.EventSource.prototype = {
5505
5668
  * Gesture points associated with the event.
5506
5669
  */
5507
5670
  function updatePointersExit( tracker, event, gPoints ) {
5508
- var delegate = THIS[ tracker.hash ],
5509
- pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ),
5671
+ var pointsList = tracker.getActivePointersListByType(gPoints[0].type),
5510
5672
  i,
5511
5673
  gPointCount = gPoints.length,
5512
5674
  curGPoint,
@@ -5630,6 +5792,14 @@ $.EventSource.prototype = {
5630
5792
  }
5631
5793
  }
5632
5794
 
5795
+ // Some pointers may steal control from another pointer without firing the appropriate release events
5796
+ // e.g. Touching a screen while click-dragging with certain mice.
5797
+ var otherPointsLists = tracker.getActivePointersListsExceptType(gPoints[ 0 ].type);
5798
+ for (i = 0; i < otherPointsLists.length; i++) {
5799
+ //If another pointer has contact, simulate the release
5800
+ abortContacts(tracker, event, otherPointsLists[i]); // No-op if no active pointer
5801
+ }
5802
+
5633
5803
  // Only capture and track primary button, pen, and touch contacts
5634
5804
  if ( buttonChanged !== 0 ) {
5635
5805
  // Aux Press
@@ -5680,7 +5850,7 @@ $.EventSource.prototype = {
5680
5850
  startTrackingPointer( pointsList, curGPoint );
5681
5851
  }
5682
5852
 
5683
- pointsList.contacts++;
5853
+ pointsList.addContact();
5684
5854
  //$.console.log('contacts++ ', pointsList.contacts);
5685
5855
 
5686
5856
  if ( tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) {
@@ -5741,7 +5911,6 @@ $.EventSource.prototype = {
5741
5911
  var delegate = THIS[ tracker.hash ],
5742
5912
  pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ),
5743
5913
  propagate,
5744
- insideElementReleased,
5745
5914
  releasePoint,
5746
5915
  releaseTime,
5747
5916
  i,
@@ -5806,7 +5975,7 @@ $.EventSource.prototype = {
5806
5975
  {
5807
5976
  eventSource: tracker,
5808
5977
  pointerType: gPoints[ 0 ].type,
5809
- position: getPointRelativeToAbsolute( gPoints[ 0 ].currentPos, tracker.element ),
5978
+ position: getPointRelativeToAbsolute(gPoints[0].currentPos, tracker.element),
5810
5979
  button: buttonChanged,
5811
5980
  buttons: pointsList.buttons,
5812
5981
  isTouchEvent: gPoints[ 0 ].type === 'touch',
@@ -5820,6 +5989,11 @@ $.EventSource.prototype = {
5820
5989
  }
5821
5990
  }
5822
5991
 
5992
+ // A primary mouse button may have been released while the non-primary button was down
5993
+ var otherPointsList = tracker.getActivePointersListByType("mouse");
5994
+ // Stop tracking the mouse; see https://github.com/openseadragon/openseadragon/pull/1223
5995
+ abortContacts(tracker, event, otherPointsList); // No-op if no active pointer
5996
+
5823
5997
  return false;
5824
5998
  }
5825
5999
 
@@ -5848,7 +6022,7 @@ $.EventSource.prototype = {
5848
6022
  if ( wasCaptured ) {
5849
6023
  // Pointer was activated in our element but could have been removed in any element since events are captured to our element
5850
6024
 
5851
- pointsList.contacts--;
6025
+ pointsList.removeContact();
5852
6026
  //$.console.log('contacts-- ', pointsList.contacts);
5853
6027
 
5854
6028
  if ( tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) {
@@ -6207,7 +6381,7 @@ $.EventSource.prototype = {
6207
6381
  } );
6208
6382
  }
6209
6383
  }
6210
-
6384
+
6211
6385
  // True if inside an iframe, otherwise false.
6212
6386
  // @member {Boolean} isInIframe
6213
6387
  // @private
@@ -6219,7 +6393,7 @@ $.EventSource.prototype = {
6219
6393
  return true;
6220
6394
  }
6221
6395
  })();
6222
-
6396
+
6223
6397
  // @function
6224
6398
  // @private
6225
6399
  // @inner
@@ -6232,7 +6406,7 @@ $.EventSource.prototype = {
6232
6406
  }
6233
6407
  }
6234
6408
 
6235
- } ( OpenSeadragon ) );
6409
+ }(OpenSeadragon));
6236
6410
 
6237
6411
  /*
6238
6412
  * OpenSeadragon - Control
@@ -6351,10 +6525,10 @@ $.Control = function ( element, options, container ) {
6351
6525
  if ( this.anchor == $.ControlAnchor.ABSOLUTE ) {
6352
6526
  this.wrapper = $.makeNeutralElement( "div" );
6353
6527
  this.wrapper.style.position = "absolute";
6354
- this.wrapper.style.top = typeof ( options.top ) == "number" ? ( options.top + 'px' ) : options.top;
6355
- this.wrapper.style.left = typeof ( options.left ) == "number" ? (options.left + 'px' ) : options.left;
6356
- this.wrapper.style.height = typeof ( options.height ) == "number" ? ( options.height + 'px' ) : options.height;
6357
- this.wrapper.style.width = typeof ( options.width ) == "number" ? ( options.width + 'px' ) : options.width;
6528
+ this.wrapper.style.top = typeof (options.top) == "number" ? (options.top + 'px') : options.top;
6529
+ this.wrapper.style.left = typeof (options.left) == "number" ? (options.left + 'px') : options.left;
6530
+ this.wrapper.style.height = typeof (options.height) == "number" ? (options.height + 'px') : options.height;
6531
+ this.wrapper.style.width = typeof (options.width) == "number" ? (options.width + 'px') : options.width;
6358
6532
  this.wrapper.style.margin = "0px";
6359
6533
  this.wrapper.style.padding = "0px";
6360
6534
 
@@ -6483,7 +6657,7 @@ $.Control.prototype = {
6483
6657
  i;
6484
6658
 
6485
6659
  $.extend( true, this, {
6486
- id: 'controldock-'+$.now()+'-'+Math.floor(Math.random()*1000000),
6660
+ id: 'controldock-' + $.now() + '-' + Math.floor(Math.random() * 1000000),
6487
6661
  container: $.makeNeutralElement( 'div' ),
6488
6662
  controls: []
6489
6663
  }, options );
@@ -6903,6 +7077,12 @@ $.Viewer = function( options ) {
6903
7077
  //internal state and dom identifiers
6904
7078
  id: options.id,
6905
7079
  hash: options.hash || nextHash++,
7080
+ /**
7081
+ * Index for page to be shown first next time open() is called (only used in sequenceMode).
7082
+ * @member {Number} initialPage
7083
+ * @memberof OpenSeadragon.Viewer#
7084
+ */
7085
+ initialPage: 0,
6906
7086
 
6907
7087
  //dom nodes
6908
7088
  /**
@@ -7028,7 +7208,7 @@ $.Viewer = function( options ) {
7028
7208
  $.ControlDock.call( this, options );
7029
7209
 
7030
7210
  //Deal with tile sources
7031
- if ( this.xmlPath ){
7211
+ if (this.xmlPath) {
7032
7212
  //Deprecated option. Now it is preferred to use the tileSources option
7033
7213
  this.tileSources = [ this.xmlPath ];
7034
7214
  }
@@ -7075,7 +7255,7 @@ $.Viewer = function( options ) {
7075
7255
 
7076
7256
  this.innerTracker = new $.MouseTracker({
7077
7257
  element: this.canvas,
7078
- startDisabled: this.mouseNavEnabled ? false : true,
7258
+ startDisabled: !this.mouseNavEnabled,
7079
7259
  clickTimeThreshold: this.clickTimeThreshold,
7080
7260
  clickDistThreshold: this.clickDistThreshold,
7081
7261
  dblClickTimeThreshold: this.dblClickTimeThreshold,
@@ -7098,7 +7278,7 @@ $.Viewer = function( options ) {
7098
7278
 
7099
7279
  this.outerTracker = new $.MouseTracker({
7100
7280
  element: this.container,
7101
- startDisabled: this.mouseNavEnabled ? false : true,
7281
+ startDisabled: !this.mouseNavEnabled,
7102
7282
  clickTimeThreshold: this.clickTimeThreshold,
7103
7283
  clickDistThreshold: this.clickDistThreshold,
7104
7284
  dblClickTimeThreshold: this.dblClickTimeThreshold,
@@ -7177,7 +7357,8 @@ $.Viewer = function( options ) {
7177
7357
 
7178
7358
  // Create the image loader
7179
7359
  this.imageLoader = new $.ImageLoader({
7180
- jobLimit: this.imageLoaderLimit
7360
+ jobLimit: this.imageLoaderLimit,
7361
+ timeout: options.timeout
7181
7362
  });
7182
7363
 
7183
7364
  // Create the tile cache
@@ -7288,11 +7469,13 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
7288
7469
  * except for the index property; images are added in sequence.
7289
7470
  * A TileSource specifier is anything you could pass as the tileSource property
7290
7471
  * of the options parameter for {@link OpenSeadragon.Viewer#addTiledImage}.
7472
+ * @param {Number} initialPage - If sequenceMode is true, display this page initially
7473
+ * for the given tileSources. If specified, will overwrite the Viewer's existing initialPage property.
7291
7474
  * @return {OpenSeadragon.Viewer} Chainable.
7292
7475
  * @fires OpenSeadragon.Viewer.event:open
7293
7476
  * @fires OpenSeadragon.Viewer.event:open-failed
7294
7477
  */
7295
- open: function (tileSources) {
7478
+ open: function (tileSources, initialPage) {
7296
7479
  var _this = this;
7297
7480
 
7298
7481
  this.close();
@@ -7307,23 +7490,17 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
7307
7490
  this.referenceStrip = null;
7308
7491
  }
7309
7492
 
7493
+ if (typeof initialPage != 'undefined' && !isNaN(initialPage)) {
7494
+ this.initialPage = initialPage;
7495
+ }
7496
+
7310
7497
  this.tileSources = tileSources;
7311
7498
  this._sequenceIndex = Math.max(0, Math.min(this.tileSources.length - 1, this.initialPage));
7312
7499
  if (this.tileSources.length) {
7313
7500
  this.open(this.tileSources[this._sequenceIndex]);
7314
7501
 
7315
7502
  if ( this.showReferenceStrip ){
7316
- this.referenceStrip = new $.ReferenceStrip({
7317
- id: this.referenceStripElement,
7318
- position: this.referenceStripPosition,
7319
- sizeRatio: this.referenceStripSizeRatio,
7320
- scroll: this.referenceStripScroll,
7321
- height: this.referenceStripHeight,
7322
- width: this.referenceStripWidth,
7323
- tileSources: this.tileSources,
7324
- prefixUrl: this.prefixUrl,
7325
- viewer: this
7326
- });
7503
+ this.addReferenceStrip();
7327
7504
  }
7328
7505
  }
7329
7506
 
@@ -7481,7 +7658,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
7481
7658
  this.navigator.close();
7482
7659
  }
7483
7660
 
7484
- if( ! this.preserveOverlays) {
7661
+ if (!this.preserveOverlays) {
7485
7662
  this.clearOverlays();
7486
7663
  this.overlaysContainer.innerHTML = "";
7487
7664
  }
@@ -7652,6 +7829,22 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
7652
7829
  return this;
7653
7830
  },
7654
7831
 
7832
+ /**
7833
+ * Turns debugging mode on or off for this viewer.
7834
+ *
7835
+ * @function
7836
+ * @param {Boolean} true to turn debug on, false to turn debug off.
7837
+ */
7838
+ setDebugMode: function(debugMode){
7839
+
7840
+ for (var i = 0; i < this.world.getItemCount(); i++) {
7841
+ this.world.getItemAt(i).debugMode = debugMode;
7842
+ }
7843
+
7844
+ this.debugMode = debugMode;
7845
+ this.forceRedraw();
7846
+ },
7847
+
7655
7848
  /**
7656
7849
  * @function
7657
7850
  * @return {Boolean}
@@ -7676,7 +7869,6 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
7676
7869
  bodyStyle = body.style,
7677
7870
  docStyle = document.documentElement.style,
7678
7871
  _this = this,
7679
- hash,
7680
7872
  nodes,
7681
7873
  i;
7682
7874
 
@@ -7838,9 +8030,9 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
7838
8030
  $.setPageScroll( _this.pageScroll );
7839
8031
  var pageScroll = $.getPageScroll();
7840
8032
  restoreScrollCounter++;
7841
- if ( restoreScrollCounter < 10 &&
7842
- pageScroll.x !== _this.pageScroll.x ||
7843
- pageScroll.y !== _this.pageScroll.y ) {
8033
+ if (restoreScrollCounter < 10 &&
8034
+ (pageScroll.x !== _this.pageScroll.x ||
8035
+ pageScroll.y !== _this.pageScroll.y)) {
7844
8036
  $.requestAnimationFrame( restoreScroll );
7845
8037
  }
7846
8038
  };
@@ -8034,8 +8226,22 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
8034
8226
  * @param {OpenSeadragon.Rect} [options.clip] - An area, in image pixels, to clip to
8035
8227
  * (portions of the image outside of this area will not be visible). Only works on
8036
8228
  * browsers that support the HTML5 canvas.
8037
- * @param {Number} [options.opacity] Opacity the tiled image should be drawn at by default.
8229
+ * @param {Number} [options.opacity=1] Proportional opacity of the tiled images (1=opaque, 0=hidden)
8230
+ * @param {Boolean} [options.preload=false] Default switch for loading hidden images (true loads, false blocks)
8231
+ * @param {Number} [options.degrees=0] Initial rotation of the tiled image around
8232
+ * its top left corner in degrees.
8038
8233
  * @param {String} [options.compositeOperation] How the image is composited onto other images.
8234
+ * @param {String} [options.crossOriginPolicy] The crossOriginPolicy for this specific image,
8235
+ * overriding viewer.crossOriginPolicy.
8236
+ * @param {Boolean} [options.ajaxWithCredentials] Whether to set withCredentials on tile AJAX
8237
+ * @param {Boolean} [options.loadTilesWithAjax]
8238
+ * Whether to load tile data using AJAX requests.
8239
+ * Defaults to the setting in {@link OpenSeadragon.Options}.
8240
+ * @param {Object} [options.ajaxHeaders]
8241
+ * A set of headers to include when making tile AJAX requests.
8242
+ * Note that these headers will be merged over any headers specified in {@link OpenSeadragon.Options}.
8243
+ * Specifying a falsy value for a header will clear its existing value set at the Viewer level (if any).
8244
+ * requests.
8039
8245
  * @param {Function} [options.success] A function that gets called when the image is
8040
8246
  * successfully added. It's passed the event object which contains a single property:
8041
8247
  * "item", the resulting TiledImage.
@@ -8068,9 +8274,26 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
8068
8274
  if (options.opacity === undefined) {
8069
8275
  options.opacity = this.opacity;
8070
8276
  }
8277
+ if (options.preload === undefined) {
8278
+ options.preload = this.preload;
8279
+ }
8071
8280
  if (options.compositeOperation === undefined) {
8072
8281
  options.compositeOperation = this.compositeOperation;
8073
8282
  }
8283
+ if (options.crossOriginPolicy === undefined) {
8284
+ options.crossOriginPolicy = options.tileSource.crossOriginPolicy !== undefined ? options.tileSource.crossOriginPolicy : this.crossOriginPolicy;
8285
+ }
8286
+ if (options.ajaxWithCredentials === undefined) {
8287
+ options.ajaxWithCredentials = this.ajaxWithCredentials;
8288
+ }
8289
+ if (options.loadTilesWithAjax === undefined) {
8290
+ options.loadTilesWithAjax = this.loadTilesWithAjax;
8291
+ }
8292
+ if (options.ajaxHeaders === undefined || options.ajaxHeaders === null) {
8293
+ options.ajaxHeaders = this.ajaxHeaders;
8294
+ } else if ($.isPlainObject(options.ajaxHeaders) && $.isPlainObject(this.ajaxHeaders)) {
8295
+ options.ajaxHeaders = $.extend({}, this.ajaxHeaders, options.ajaxHeaders);
8296
+ }
8074
8297
 
8075
8298
  var myQueueItem = {
8076
8299
  options: options
@@ -8133,11 +8356,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
8133
8356
 
8134
8357
  this._loadQueue.push(myQueueItem);
8135
8358
 
8136
- getTileSourceImplementation( this, options.tileSource, function( tileSource ) {
8137
-
8138
- myQueueItem.tileSource = tileSource;
8139
-
8140
- // add everybody at the front of the queue that's ready to go
8359
+ function processReadyItems() {
8141
8360
  var queueItem, tiledImage, optionsClone;
8142
8361
  while (_this._loadQueue.length) {
8143
8362
  queueItem = _this._loadQueue[0];
@@ -8171,6 +8390,8 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
8171
8390
  clip: queueItem.options.clip,
8172
8391
  placeholderFillStyle: queueItem.options.placeholderFillStyle,
8173
8392
  opacity: queueItem.options.opacity,
8393
+ preload: queueItem.options.preload,
8394
+ degrees: queueItem.options.degrees,
8174
8395
  compositeOperation: queueItem.options.compositeOperation,
8175
8396
  springStiffness: _this.springStiffness,
8176
8397
  animationTime: _this.animationTime,
@@ -8183,7 +8404,10 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
8183
8404
  minPixelRatio: _this.minPixelRatio,
8184
8405
  smoothTileEdgesMinZoom: _this.smoothTileEdgesMinZoom,
8185
8406
  iOSDevice: _this.iOSDevice,
8186
- crossOriginPolicy: _this.crossOriginPolicy,
8407
+ crossOriginPolicy: queueItem.options.crossOriginPolicy,
8408
+ ajaxWithCredentials: queueItem.options.ajaxWithCredentials,
8409
+ loadTilesWithAjax: queueItem.options.loadTilesWithAjax,
8410
+ ajaxHeaders: queueItem.options.ajaxHeaders,
8187
8411
  debugMode: _this.debugMode
8188
8412
  });
8189
8413
 
@@ -8219,9 +8443,20 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
8219
8443
  });
8220
8444
  }
8221
8445
  }
8446
+ }
8447
+
8448
+ getTileSourceImplementation( this, options.tileSource, options, function( tileSource ) {
8449
+
8450
+ myQueueItem.tileSource = tileSource;
8451
+
8452
+ // add everybody at the front of the queue that's ready to go
8453
+ processReadyItems();
8222
8454
  }, function( event ) {
8223
8455
  event.options = options;
8224
8456
  raiseAddItemFailed(event);
8457
+
8458
+ // add everybody at the front of the queue that's ready to go
8459
+ processReadyItems();
8225
8460
  } );
8226
8461
  },
8227
8462
 
@@ -8325,7 +8560,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
8325
8560
  onNextHandler = $.delegate( this, onNext ),
8326
8561
  onPreviousHandler = $.delegate( this, onPrevious ),
8327
8562
  navImages = this.navImages,
8328
- useGroup = true ;
8563
+ useGroup = true;
8329
8564
 
8330
8565
  if( this.showSequenceControl ){
8331
8566
 
@@ -8421,7 +8656,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
8421
8656
  onBlurHandler = $.delegate( this, onBlur ),
8422
8657
  navImages = this.navImages,
8423
8658
  buttons = [],
8424
- useGroup = true ;
8659
+ useGroup = true;
8425
8660
 
8426
8661
 
8427
8662
  if ( this.showNavigationControl ) {
@@ -8548,7 +8783,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
8548
8783
  if( this.toolbar ){
8549
8784
  this.toolbar.addControl(
8550
8785
  this.navControl,
8551
- {anchor: $.ControlAnchor.TOP_LEFT}
8786
+ {anchor: this.navigationControlAnchor || $.ControlAnchor.TOP_LEFT}
8552
8787
  );
8553
8788
  } else {
8554
8789
  this.addControl(
@@ -8792,7 +9027,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
8792
9027
  element = $.getElement( element );
8793
9028
  i = getOverlayIndex( this.currentOverlays, element );
8794
9029
 
8795
- if (i>=0) {
9030
+ if (i >= 0) {
8796
9031
  return this.currentOverlays[i];
8797
9032
  } else {
8798
9033
  return null;
@@ -8894,6 +9129,52 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
8894
9129
  */
8895
9130
  _cancelPendingImages: function() {
8896
9131
  this._loadQueue = [];
9132
+ },
9133
+
9134
+ /**
9135
+ * Removes the reference strip and disables displaying it.
9136
+ * @function
9137
+ */
9138
+ removeReferenceStrip: function() {
9139
+ this.showReferenceStrip = false;
9140
+
9141
+ if (this.referenceStrip) {
9142
+ this.referenceStrip.destroy();
9143
+ this.referenceStrip = null;
9144
+ }
9145
+ },
9146
+
9147
+ /**
9148
+ * Enables and displays the reference strip based on the currently set tileSources.
9149
+ * Works only when the Viewer has sequenceMode set to true.
9150
+ * @function
9151
+ */
9152
+ addReferenceStrip: function() {
9153
+ this.showReferenceStrip = true;
9154
+
9155
+ if (this.sequenceMode) {
9156
+ if (this.referenceStrip) {
9157
+ return;
9158
+ }
9159
+
9160
+ if (this.tileSources.length && this.tileSources.length > 1) {
9161
+ this.referenceStrip = new $.ReferenceStrip({
9162
+ id: this.referenceStripElement,
9163
+ position: this.referenceStripPosition,
9164
+ sizeRatio: this.referenceStripSizeRatio,
9165
+ scroll: this.referenceStripScroll,
9166
+ height: this.referenceStripHeight,
9167
+ width: this.referenceStripWidth,
9168
+ tileSources: this.tileSources,
9169
+ prefixUrl: this.prefixUrl,
9170
+ viewer: this
9171
+ });
9172
+
9173
+ this.referenceStrip.setFocus( this._sequenceIndex );
9174
+ }
9175
+ } else {
9176
+ $.console.warn('Attempting to display a reference strip while "sequenceMode" is off.');
9177
+ }
8897
9178
  }
8898
9179
  });
8899
9180
 
@@ -8913,20 +9194,28 @@ function _getSafeElemSize (oElement) {
8913
9194
  );
8914
9195
  }
8915
9196
 
9197
+
8916
9198
  /**
8917
9199
  * @function
8918
9200
  * @private
8919
9201
  */
8920
- function getTileSourceImplementation( viewer, tileSource, successCallback,
9202
+ function getTileSourceImplementation( viewer, tileSource, imgOptions, successCallback,
8921
9203
  failCallback ) {
8922
9204
  var _this = viewer;
8923
9205
 
8924
9206
  //allow plain xml strings or json strings to be parsed here
8925
9207
  if ( $.type( tileSource ) == 'string' ) {
8926
- if ( tileSource.match( /\s*<.*/ ) ) {
9208
+ //xml should start with "<" and end with ">"
9209
+ if ( tileSource.match( /^\s*<.*>\s*$/ ) ) {
8927
9210
  tileSource = $.parseXml( tileSource );
8928
- } else if ( tileSource.match( /\s*[\{\[].*/ ) ) {
8929
- tileSource = $.parseJSON(tileSource);
9211
+ //json should start with "{" or "[" and end with "}" or "]"
9212
+ } else if ( tileSource.match(/^\s*[\{\[].*[\}\]]\s*$/ ) ) {
9213
+ try {
9214
+ var tileSourceJ = $.parseJSON(tileSource);
9215
+ tileSource = tileSourceJ;
9216
+ } catch (e) {
9217
+ //tileSource = tileSource;
9218
+ }
8930
9219
  }
8931
9220
  }
8932
9221
 
@@ -8951,8 +9240,10 @@ function getTileSourceImplementation( viewer, tileSource, successCallback,
8951
9240
  //If its still a string it means it must be a url at this point
8952
9241
  tileSource = new $.TileSource({
8953
9242
  url: tileSource,
8954
- crossOriginPolicy: viewer.crossOriginPolicy,
9243
+ crossOriginPolicy: imgOptions.crossOriginPolicy !== undefined ?
9244
+ imgOptions.crossOriginPolicy : viewer.crossOriginPolicy,
8955
9245
  ajaxWithCredentials: viewer.ajaxWithCredentials,
9246
+ ajaxHeaders: viewer.ajaxHeaders,
8956
9247
  useCanvas: viewer.useCanvas,
8957
9248
  success: function( event ) {
8958
9249
  successCallback( event.tileSource );
@@ -8963,8 +9254,10 @@ function getTileSourceImplementation( viewer, tileSource, successCallback,
8963
9254
  } );
8964
9255
 
8965
9256
  } else if ($.isPlainObject(tileSource) || tileSource.nodeType) {
8966
- if (!tileSource.crossOriginPolicy && viewer.crossOriginPolicy) {
8967
- tileSource.crossOriginPolicy = viewer.crossOriginPolicy;
9257
+ if (tileSource.crossOriginPolicy === undefined &&
9258
+ (imgOptions.crossOriginPolicy !== undefined || viewer.crossOriginPolicy !== undefined)) {
9259
+ tileSource.crossOriginPolicy = imgOptions.crossOriginPolicy !== undefined ?
9260
+ imgOptions.crossOriginPolicy : viewer.crossOriginPolicy;
8968
9261
  }
8969
9262
  if (tileSource.ajaxWithCredentials === undefined) {
8970
9263
  tileSource.ajaxWithCredentials = viewer.ajaxWithCredentials;
@@ -9258,16 +9551,15 @@ function onCanvasClick( event ) {
9258
9551
  this.canvas.focus();
9259
9552
  }
9260
9553
 
9261
- if ( !event.preventDefaultAction && this.viewport && event.quick ) {
9262
- gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
9263
- if ( gestureSettings.clickToZoom ) {
9264
- this.viewport.zoomBy(
9265
- event.shift ? 1.0 / this.zoomPerClick : this.zoomPerClick,
9266
- this.viewport.pointFromPixel( event.position, true )
9267
- );
9268
- this.viewport.applyConstraints();
9269
- }
9270
- }
9554
+ var canvasClickEventArgs = {
9555
+ tracker: event.eventSource,
9556
+ position: event.position,
9557
+ quick: event.quick,
9558
+ shift: event.shift,
9559
+ originalEvent: event.originalEvent,
9560
+ preventDefaultAction: event.preventDefaultAction
9561
+ };
9562
+
9271
9563
  /**
9272
9564
  * Raised when a mouse press/release or touch/remove occurs on the {@link OpenSeadragon.Viewer#canvas} element.
9273
9565
  *
@@ -9280,15 +9572,21 @@ function onCanvasClick( event ) {
9280
9572
  * @property {Boolean} quick - True only if the clickDistThreshold and clickTimeThreshold are both passed. Useful for differentiating between clicks and drags.
9281
9573
  * @property {Boolean} shift - True if the shift key was pressed during this event.
9282
9574
  * @property {Object} originalEvent - The original DOM event.
9575
+ * @property {Boolean} preventDefaultAction - Set to true to prevent default click to zoom behaviour. Default: false.
9283
9576
  * @property {?Object} userData - Arbitrary subscriber-defined object.
9284
9577
  */
9285
- this.raiseEvent( 'canvas-click', {
9286
- tracker: event.eventSource,
9287
- position: event.position,
9288
- quick: event.quick,
9289
- shift: event.shift,
9290
- originalEvent: event.originalEvent
9291
- });
9578
+ this.raiseEvent( 'canvas-click', canvasClickEventArgs);
9579
+
9580
+ if ( !canvasClickEventArgs.preventDefaultAction && this.viewport && event.quick ) {
9581
+ gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
9582
+ if ( gestureSettings.clickToZoom ) {
9583
+ this.viewport.zoomBy(
9584
+ event.shift ? 1.0 / this.zoomPerClick : this.zoomPerClick,
9585
+ this.viewport.pointFromPixel( event.position, true )
9586
+ );
9587
+ this.viewport.applyConstraints();
9588
+ }
9589
+ }
9292
9590
  }
9293
9591
 
9294
9592
  function onCanvasDblClick( event ) {
@@ -9328,19 +9626,16 @@ function onCanvasDblClick( event ) {
9328
9626
  function onCanvasDrag( event ) {
9329
9627
  var gestureSettings;
9330
9628
 
9331
- if ( !event.preventDefaultAction && this.viewport ) {
9332
- gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
9333
- if( !this.panHorizontal ){
9334
- event.delta.x = 0;
9335
- }
9336
- if( !this.panVertical ){
9337
- event.delta.y = 0;
9338
- }
9339
- this.viewport.panBy( this.viewport.deltaPointsFromPixels( event.delta.negate() ), gestureSettings.flickEnabled );
9340
- if( this.constrainDuringPan ){
9341
- this.viewport.applyConstraints();
9342
- }
9343
- }
9629
+ var canvasDragEventArgs = {
9630
+ tracker: event.eventSource,
9631
+ position: event.position,
9632
+ delta: event.delta,
9633
+ speed: event.speed,
9634
+ direction: event.direction,
9635
+ shift: event.shift,
9636
+ originalEvent: event.originalEvent,
9637
+ preventDefaultAction: event.preventDefaultAction
9638
+ };
9344
9639
  /**
9345
9640
  * Raised when a mouse or touch drag operation occurs on the {@link OpenSeadragon.Viewer#canvas} element.
9346
9641
  *
@@ -9355,17 +9650,43 @@ function onCanvasDrag( event ) {
9355
9650
  * @property {Number} direction - Current computed direction, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0.
9356
9651
  * @property {Boolean} shift - True if the shift key was pressed during this event.
9357
9652
  * @property {Object} originalEvent - The original DOM event.
9653
+ * @property {Boolean} preventDefaultAction - Set to true to prevent default drag behaviour. Default: false.
9358
9654
  * @property {?Object} userData - Arbitrary subscriber-defined object.
9359
9655
  */
9360
- this.raiseEvent( 'canvas-drag', {
9361
- tracker: event.eventSource,
9362
- position: event.position,
9363
- delta: event.delta,
9364
- speed: event.speed,
9365
- direction: event.direction,
9366
- shift: event.shift,
9367
- originalEvent: event.originalEvent
9368
- });
9656
+ this.raiseEvent( 'canvas-drag', canvasDragEventArgs);
9657
+
9658
+ if ( !event.preventDefaultAction && this.viewport ) {
9659
+ gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
9660
+ if( !this.panHorizontal ){
9661
+ event.delta.x = 0;
9662
+ }
9663
+ if( !this.panVertical ){
9664
+ event.delta.y = 0;
9665
+ }
9666
+
9667
+ if( this.constrainDuringPan ){
9668
+ var delta = this.viewport.deltaPointsFromPixels( event.delta.negate() );
9669
+
9670
+ this.viewport.centerSpringX.target.value += delta.x;
9671
+ this.viewport.centerSpringY.target.value += delta.y;
9672
+
9673
+ var bounds = this.viewport.getBounds();
9674
+ var constrainedBounds = this.viewport.getConstrainedBounds();
9675
+
9676
+ this.viewport.centerSpringX.target.value -= delta.x;
9677
+ this.viewport.centerSpringY.target.value -= delta.y;
9678
+
9679
+ if (bounds.x != constrainedBounds.x) {
9680
+ event.delta.x = 0;
9681
+ }
9682
+
9683
+ if (bounds.y != constrainedBounds.y) {
9684
+ event.delta.y = 0;
9685
+ }
9686
+ }
9687
+
9688
+ this.viewport.panBy( this.viewport.deltaPointsFromPixels( event.delta.negate() ), gestureSettings.flickEnabled && !this.constrainDuringPan);
9689
+ }
9369
9690
  }
9370
9691
 
9371
9692
  function onCanvasDragEnd( event ) {
@@ -9447,6 +9768,11 @@ function onCanvasEnter( event ) {
9447
9768
  }
9448
9769
 
9449
9770
  function onCanvasExit( event ) {
9771
+
9772
+ if (window.location != window.parent.location){
9773
+ $.MouseTracker.resetAllMouseTrackers();
9774
+ }
9775
+
9450
9776
  /**
9451
9777
  * Raised when a pointer leaves the {@link OpenSeadragon.Viewer#canvas} element.
9452
9778
  *
@@ -10195,7 +10521,7 @@ $.Navigator = function( options ){
10195
10521
  //At some browser magnification levels the display regions lines up correctly, but at some there appears to
10196
10522
  //be a one pixel gap.
10197
10523
  this.fudge = new $.Point(1, 1);
10198
- this.totalBorderWidths = new $.Point(this.borderWidth*2, this.borderWidth*2).minus(this.fudge);
10524
+ this.totalBorderWidths = new $.Point(this.borderWidth * 2, this.borderWidth * 2).minus(this.fudge);
10199
10525
 
10200
10526
 
10201
10527
  if ( options.controlOptions.anchor != $.ControlAnchor.NONE ) {
@@ -10254,8 +10580,8 @@ $.Navigator = function( options ){
10254
10580
 
10255
10581
  if ( this._resizeWithViewer ) {
10256
10582
  if ( options.width && options.height ) {
10257
- this.element.style.height = typeof ( options.height ) == "number" ? ( options.height + 'px' ) : options.height;
10258
- this.element.style.width = typeof ( options.width ) == "number" ? ( options.width + 'px' ) : options.width;
10583
+ this.element.style.height = typeof (options.height) == "number" ? (options.height + 'px') : options.height;
10584
+ this.element.style.width = typeof (options.width) == "number" ? (options.width + 'px') : options.width;
10259
10585
  } else {
10260
10586
  viewerSize = $.getElementSize( viewer.element );
10261
10587
  this.element.style.height = Math.round( viewerSize.y * options.sizeRatio ) + 'px';
@@ -10305,8 +10631,10 @@ $.Navigator = function( options ){
10305
10631
  });
10306
10632
 
10307
10633
  viewer.world.addHandler("item-index-change", function(event) {
10308
- var item = _this.world.getItemAt(event.previousIndex);
10309
- _this.world.setItemIndex(item, event.newIndex);
10634
+ window.setTimeout(function(){
10635
+ var item = _this.world.getItemAt(event.previousIndex);
10636
+ _this.world.setItemIndex(item, event.newIndex);
10637
+ }, 1);
10310
10638
  });
10311
10639
 
10312
10640
  viewer.world.addHandler("remove-item", function(event) {
@@ -10415,9 +10743,22 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
10415
10743
  myItem._originalForNavigator = original;
10416
10744
  _this._matchBounds(myItem, original, true);
10417
10745
 
10418
- original.addHandler('bounds-change', function() {
10746
+ function matchBounds() {
10419
10747
  _this._matchBounds(myItem, original);
10420
- });
10748
+ }
10749
+
10750
+ function matchOpacity() {
10751
+ _this._matchOpacity(myItem, original);
10752
+ }
10753
+
10754
+ function matchCompositeOperation() {
10755
+ _this._matchCompositeOperation(myItem, original);
10756
+ }
10757
+
10758
+ original.addHandler('bounds-change', matchBounds);
10759
+ original.addHandler('clip-change', matchBounds);
10760
+ original.addHandler('opacity-change', matchOpacity);
10761
+ original.addHandler('composite-operation-change', matchCompositeOperation);
10421
10762
  }
10422
10763
  });
10423
10764
 
@@ -10440,9 +10781,21 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
10440
10781
 
10441
10782
  // private
10442
10783
  _matchBounds: function(myItem, theirItem, immediately) {
10443
- var bounds = theirItem.getBounds();
10784
+ var bounds = theirItem.getBoundsNoRotate();
10444
10785
  myItem.setPosition(bounds.getTopLeft(), immediately);
10445
10786
  myItem.setWidth(bounds.width, immediately);
10787
+ myItem.setRotation(theirItem.getRotation(), immediately);
10788
+ myItem.setClip(theirItem.getClip());
10789
+ },
10790
+
10791
+ // private
10792
+ _matchOpacity: function(myItem, theirItem) {
10793
+ myItem.setOpacity(theirItem.opacity);
10794
+ },
10795
+
10796
+ // private
10797
+ _matchCompositeOperation: function(myItem, theirItem) {
10798
+ myItem.setCompositeOperation(theirItem.compositeOperation);
10446
10799
  }
10447
10800
  });
10448
10801
 
@@ -10476,6 +10829,9 @@ function onCanvasDrag( event ) {
10476
10829
  event.delta
10477
10830
  )
10478
10831
  );
10832
+ if( this.viewer.constrainDuringPan ){
10833
+ this.viewer.viewport.applyConstraints();
10834
+ }
10479
10835
  }
10480
10836
  }
10481
10837
 
@@ -10618,14 +10974,14 @@ $.extend( $, /** @lends OpenSeadragon */{
10618
10974
  container = I18N,
10619
10975
  i;
10620
10976
 
10621
- for ( i = 0; i < props.length-1; i++ ) {
10977
+ for (i = 0; i < props.length - 1; i++) {
10622
10978
  // in case not a subproperty
10623
10979
  container = container[ props[ i ] ] || {};
10624
10980
  }
10625
10981
  string = container[ props[ i ] ];
10626
10982
 
10627
10983
  if ( typeof( string ) != "string" ) {
10628
- $.console.debug( "Untranslated source string:", prop );
10984
+ $.console.log( "Untranslated source string:", prop );
10629
10985
  string = ""; // FIXME: this breaks gettext()-style convention, which would return source
10630
10986
  }
10631
10987
 
@@ -10812,6 +11168,18 @@ $.Point.prototype = {
10812
11168
  );
10813
11169
  },
10814
11170
 
11171
+ /**
11172
+ * Compute the squared distance between this point and another point.
11173
+ * Useful for optimizing things like comparing distances.
11174
+ * @function
11175
+ * @param {OpenSeadragon.Point} point The point to compute the squared distance with.
11176
+ * @returns {Number} The squared distance between the 2 points
11177
+ */
11178
+ squaredDistanceTo: function( point ) {
11179
+ return Math.pow( this.x - point.x, 2 ) +
11180
+ Math.pow( this.y - point.y, 2 );
11181
+ },
11182
+
10815
11183
  /**
10816
11184
  * Apply a function to each coordinate of this point and return a new point.
10817
11185
  * @function
@@ -10854,10 +11222,7 @@ $.Point.prototype = {
10854
11222
  var sin;
10855
11223
  // Avoid float computations when possible
10856
11224
  if (degrees % 90 === 0) {
10857
- var d = degrees % 360;
10858
- if (d < 0) {
10859
- d += 360;
10860
- }
11225
+ var d = $.positiveModulo(degrees, 360);
10861
11226
  switch (d) {
10862
11227
  case 0:
10863
11228
  cos = 1;
@@ -10961,11 +11326,15 @@ $.Point.prototype = {
10961
11326
  * the extending classes implementation of 'configure'.
10962
11327
  * @param {String} [options.url]
10963
11328
  * The URL for the data necessary for this TileSource.
11329
+ * @param {String} [options.referenceStripThumbnailUrl]
11330
+ * The URL for a thumbnail image to be used by the reference strip
10964
11331
  * @param {Function} [options.success]
10965
11332
  * A function to be called upon successful creation.
10966
11333
  * @param {Boolean} [options.ajaxWithCredentials]
10967
11334
  * If this TileSource needs to make an AJAX call, this specifies whether to set
10968
11335
  * the XHR's withCredentials (for accessing secure data).
11336
+ * @param {Object} [options.ajaxHeaders]
11337
+ * A set of headers to include in AJAX requests.
10969
11338
  * @param {Number} [options.width]
10970
11339
  * Width of the source image at max resolution in pixels.
10971
11340
  * @param {Number} [options.height]
@@ -11088,8 +11457,8 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
11088
11457
  //explicit configuration via positional args in constructor
11089
11458
  //or the more idiomatic 'options' object
11090
11459
  this.ready = true;
11091
- this.aspectRatio = ( options.width && options.height ) ?
11092
- ( options.width / options.height ) : 1;
11460
+ this.aspectRatio = (options.width && options.height) ?
11461
+ (options.width / options.height) : 1;
11093
11462
  this.dimensions = new $.Point( options.width, options.height );
11094
11463
 
11095
11464
  if ( this.tileSize ){
@@ -11219,25 +11588,20 @@ $.TileSource.prototype = {
11219
11588
 
11220
11589
  /**
11221
11590
  * @function
11222
- * @param {Number} level
11591
+ * @returns {Number} The highest level in this tile source that can be contained in a single tile.
11223
11592
  */
11224
- getClosestLevel: function( rect ) {
11593
+ getClosestLevel: function() {
11225
11594
  var i,
11226
- tilesPerSide,
11227
11595
  tiles;
11228
11596
 
11229
- for( i = this.minLevel; i < this.maxLevel; i++ ){
11230
- tiles = this.getNumTiles( i );
11231
- tilesPerSide = new $.Point(
11232
- Math.floor( rect.x / this.getTileWidth(i) ),
11233
- Math.floor( rect.y / this.getTileHeight(i) )
11234
- );
11235
-
11236
- if( tiles.x + 1 >= tilesPerSide.x && tiles.y + 1 >= tilesPerSide.y ){
11597
+ for (i = this.minLevel + 1; i <= this.maxLevel; i++){
11598
+ tiles = this.getNumTiles(i);
11599
+ if (tiles.x > 1 || tiles.y > 1) {
11237
11600
  break;
11238
11601
  }
11239
11602
  }
11240
- return Math.max( 0, i - 1 );
11603
+
11604
+ return i - 1;
11241
11605
  },
11242
11606
 
11243
11607
  /**
@@ -11245,12 +11609,28 @@ $.TileSource.prototype = {
11245
11609
  * @param {Number} level
11246
11610
  * @param {OpenSeadragon.Point} point
11247
11611
  */
11248
- getTileAtPoint: function( level, point ) {
11249
- var pixel = point.times( this.dimensions.x ).times( this.getLevelScale(level) ),
11250
- tx = Math.floor( pixel.x / this.getTileWidth(level) ),
11251
- ty = Math.floor( pixel.y / this.getTileHeight(level) );
11612
+ getTileAtPoint: function(level, point) {
11613
+ var validPoint = point.x >= 0 && point.x <= 1 &&
11614
+ point.y >= 0 && point.y <= 1 / this.aspectRatio;
11615
+ $.console.assert(validPoint, "[TileSource.getTileAtPoint] must be called with a valid point.");
11616
+
11617
+ var widthScaled = this.dimensions.x * this.getLevelScale(level);
11618
+ var pixelX = point.x * widthScaled;
11619
+ var pixelY = point.y * widthScaled;
11620
+
11621
+ var x = Math.floor(pixelX / this.getTileWidth(level));
11622
+ var y = Math.floor(pixelY / this.getTileHeight(level));
11623
+
11624
+ // When point.x == 1 or point.y == 1 / this.aspectRatio we want to
11625
+ // return the last tile of the row/column
11626
+ if (point.x >= 1) {
11627
+ x = this.getNumTiles(level).x - 1;
11628
+ }
11629
+ if (point.y >= 1 / this.aspectRatio) {
11630
+ y = this.getNumTiles(level).y - 1;
11631
+ }
11252
11632
 
11253
- return new $.Point( tx, ty );
11633
+ return new $.Point(x, y);
11254
11634
  },
11255
11635
 
11256
11636
  /**
@@ -11348,7 +11728,7 @@ $.TileSource.prototype = {
11348
11728
  //TODO: Its not very flexible to require tile sources to end jsonp
11349
11729
  // request for info with a url that ends with '.js' but for
11350
11730
  // now it's the only way I see to distinguish uniformly.
11351
- callbackName = url.split( '/' ).pop().replace('.js','');
11731
+ callbackName = url.split('/').pop().replace('.js', '');
11352
11732
  $.jsonp({
11353
11733
  url: url,
11354
11734
  async: false,
@@ -11360,6 +11740,7 @@ $.TileSource.prototype = {
11360
11740
  $.makeAjaxRequest( {
11361
11741
  url: url,
11362
11742
  withCredentials: this.ajaxWithCredentials,
11743
+ headers: this.ajaxHeaders,
11363
11744
  success: function( xhr ) {
11364
11745
  var data = processResponse( xhr );
11365
11746
  callback( data );
@@ -11444,7 +11825,7 @@ $.TileSource.prototype = {
11444
11825
  },
11445
11826
 
11446
11827
  /**
11447
- * Responsible for retriving the url which will return an image for the
11828
+ * Responsible for retrieving the url which will return an image for the
11448
11829
  * region specified by the given x, y, and level components.
11449
11830
  * This method is not implemented by this class other than to throw an Error
11450
11831
  * announcing you have to implement it. Because of the variety of tile
@@ -11460,6 +11841,23 @@ $.TileSource.prototype = {
11460
11841
  throw new Error( "Method not implemented." );
11461
11842
  },
11462
11843
 
11844
+ /**
11845
+ * Responsible for retrieving the headers which will be attached to the image request for the
11846
+ * region specified by the given x, y, and level components.
11847
+ * This option is only relevant if {@link OpenSeadragon.Options}.loadTilesWithAjax is set to true.
11848
+ * The headers returned here will override headers specified at the Viewer or TiledImage level.
11849
+ * Specifying a falsy value for a header will clear its existing value set at the Viewer or
11850
+ * TiledImage level (if any).
11851
+ * @function
11852
+ * @param {Number} level
11853
+ * @param {Number} x
11854
+ * @param {Number} y
11855
+ * @returns {Object}
11856
+ */
11857
+ getTileAjaxHeaders: function( level, x, y ) {
11858
+ return {};
11859
+ },
11860
+
11463
11861
  /**
11464
11862
  * @function
11465
11863
  * @param {Number} level
@@ -11468,12 +11866,12 @@ $.TileSource.prototype = {
11468
11866
  */
11469
11867
  tileExists: function( level, x, y ) {
11470
11868
  var numTiles = this.getNumTiles( level );
11471
- return level >= this.minLevel &&
11472
- level <= this.maxLevel &&
11473
- x >= 0 &&
11474
- y >= 0 &&
11475
- x < numTiles.x &&
11476
- y < numTiles.y;
11869
+ return level >= this.minLevel &&
11870
+ level <= this.maxLevel &&
11871
+ x >= 0 &&
11872
+ y >= 0 &&
11873
+ x < numTiles.x &&
11874
+ y < numTiles.y;
11477
11875
  }
11478
11876
  };
11479
11877
 
@@ -11514,7 +11912,11 @@ function processResponse( xhr ){
11514
11912
  data = xhr.responseText;
11515
11913
  }
11516
11914
  }else if( responseText.match(/\s*[\{\[].*/) ){
11517
- data = $.parseJSON(responseText);
11915
+ try{
11916
+ data = $.parseJSON(responseText);
11917
+ } catch(e){
11918
+ data = responseText;
11919
+ }
11518
11920
  }else{
11519
11921
  data = responseText;
11520
11922
  }
@@ -11665,8 +12067,10 @@ $.extend( $.DziTileSource.prototype, $.TileSource.prototype, /** @lends OpenSead
11665
12067
  }
11666
12068
  }
11667
12069
 
11668
- return ( "http://schemas.microsoft.com/deepzoom/2008" == ns ||
11669
- "http://schemas.microsoft.com/deepzoom/2009" == ns );
12070
+ ns = (ns || '').toLowerCase();
12071
+
12072
+ return (ns.indexOf('schemas.microsoft.com/deepzoom/2008') !== -1 ||
12073
+ ns.indexOf('schemas.microsoft.com/deepzoom/2009') !== -1);
11670
12074
  },
11671
12075
 
11672
12076
  /**
@@ -11692,7 +12096,7 @@ $.extend( $.DziTileSource.prototype, $.TileSource.prototype, /** @lends OpenSead
11692
12096
 
11693
12097
  if (url && !options.tilesUrl) {
11694
12098
  options.tilesUrl = url.replace(
11695
- /([^\/]+?)(\.(dzi|xml|js))?\/?(\?.*)?$/, '$1_files/');
12099
+ /([^\/]+?)(\.(dzi|xml|js)?(\?[^\/]*)?)?\/?$/, '$1_files/');
11696
12100
 
11697
12101
  if (url.search(/\.(dzi|xml|js)\?/) != -1) {
11698
12102
  options.queryParams = url.match(/\?.*/);
@@ -11749,10 +12153,10 @@ $.extend( $.DziTileSource.prototype, $.TileSource.prototype, /** @lends OpenSead
11749
12153
  xMax = xMin + rect.width * scale;
11750
12154
  yMax = yMin + rect.height * scale;
11751
12155
 
11752
- xMin = Math.floor( xMin / this.tileSize );
11753
- yMin = Math.floor( yMin / this.tileSize );
11754
- xMax = Math.ceil( xMax / this.tileSize );
11755
- yMax = Math.ceil( yMax / this.tileSize );
12156
+ xMin = Math.floor( xMin / this._tileWidth );
12157
+ yMin = Math.floor( yMin / this._tileWidth ); // DZI tiles are square, so we just use _tileWidth
12158
+ xMax = Math.ceil( xMax / this._tileWidth );
12159
+ yMax = Math.ceil( yMax / this._tileWidth );
11756
12160
 
11757
12161
  if ( xMin <= x && x < xMax && yMin <= y && y < yMax ) {
11758
12162
  return true;
@@ -11969,6 +12373,7 @@ function configureFromObject( tileSource, configuration ){
11969
12373
  */
11970
12374
  $.IIIFTileSource = function( options ){
11971
12375
 
12376
+ /* eslint-disable camelcase */
11972
12377
 
11973
12378
  $.extend( true, this, options );
11974
12379
 
@@ -12010,7 +12415,7 @@ $.IIIFTileSource = function( options ){
12010
12415
  } else if ( canBeTiled(options.profile) ) {
12011
12416
  // use the largest of tileOptions that is smaller than the short dimension
12012
12417
  var shortDim = Math.min( this.height, this.width ),
12013
- tileOptions = [256,512,1024],
12418
+ tileOptions = [256, 512, 1024],
12014
12419
  smallerTiles = [];
12015
12420
 
12016
12421
  for ( var c = 0; c < tileOptions.length; c++ ) {
@@ -12026,11 +12431,11 @@ $.IIIFTileSource = function( options ){
12026
12431
  options.tileSize = shortDim;
12027
12432
  }
12028
12433
  } else if (this.sizes && this.sizes.length > 0) {
12029
- // This info.json can't be tiled, but we can still construct a legacy pyramid from the sizes array.
12030
- // In this mode, IIIFTileSource will call functions from the abstract baseTileSource or the
12031
- // LegacyTileSource instead of performing IIIF tiling.
12434
+ // This info.json can't be tiled, but we can still construct a legacy pyramid from the sizes array.
12435
+ // In this mode, IIIFTileSource will call functions from the abstract baseTileSource or the
12436
+ // LegacyTileSource instead of performing IIIF tiling.
12032
12437
  this.emulateLegacyImagePyramid = true;
12033
-
12438
+
12034
12439
  options.levels = constructLevels( this );
12035
12440
  // use the largest available size to define tiles
12036
12441
  $.extend( true, options, {
@@ -12065,7 +12470,7 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
12065
12470
  * @param {Object|Array} data
12066
12471
  * @param {String} optional - url
12067
12472
  */
12068
-
12473
+
12069
12474
  supports: function( data, url ) {
12070
12475
  // Version 2.0 and forwards
12071
12476
  if (data.protocol && data.protocol == 'http://iiif.io/api/image') {
@@ -12317,14 +12722,16 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
12317
12722
  */
12318
12723
  function constructLevels(options) {
12319
12724
  var levels = [];
12320
- for(var i=0; i<options.sizes.length; i++) {
12725
+ for(var i = 0; i < options.sizes.length; i++) {
12321
12726
  levels.push({
12322
12727
  url: options['@id'] + '/full/' + options.sizes[i].width + ',/0/default.jpg',
12323
12728
  width: options.sizes[i].width,
12324
12729
  height: options.sizes[i].height
12325
12730
  });
12326
12731
  }
12327
- return levels.sort(function(a,b){return a.width - b.width;});
12732
+ return levels.sort(function(a, b) {
12733
+ return a.width - b.width;
12734
+ });
12328
12735
  }
12329
12736
 
12330
12737
 
@@ -12611,7 +13018,7 @@ $.TmsTileSource = function( width, height, tileSize, tileOverlap, tilesUrl ) {
12611
13018
  } else {
12612
13019
  max = bufferedHeight / 256;
12613
13020
  }
12614
- options.maxLevel = Math.ceil(Math.log(max)/Math.log(2)) - 1;
13021
+ options.maxLevel = Math.ceil(Math.log(max) / Math.log(2)) - 1;
12615
13022
  options.tileSize = 256;
12616
13023
  options.width = bufferedWidth;
12617
13024
  options.height = bufferedHeight;
@@ -12657,13 +13064,156 @@ $.extend( $.TmsTileSource.prototype, $.TileSource.prototype, /** @lends OpenSead
12657
13064
  // Convert from Deep Zoom definition to TMS zoom definition
12658
13065
  var yTiles = this.getNumTiles( level ).y - 1;
12659
13066
 
12660
- return this.tilesUrl + level + "/" + x + "/" + (yTiles - y) + ".png";
13067
+ return this.tilesUrl + level + "/" + x + "/" + (yTiles - y) + ".png";
12661
13068
  }
12662
13069
  });
12663
13070
 
12664
13071
 
12665
13072
  }( OpenSeadragon ));
12666
13073
 
13074
+ (function($) {
13075
+
13076
+ /**
13077
+ * @class ZoomifyTileSource
13078
+ * @classdesc A tilesource implementation for the zoomify format.
13079
+ *
13080
+ * A description of the format can be found here:
13081
+ * https://ecommons.cornell.edu/bitstream/handle/1813/5410/Introducing_Zoomify_Image.pdf
13082
+ *
13083
+ * There are two ways of creating a zoomify tilesource for openseadragon
13084
+ *
13085
+ * 1) Supplying all necessary information in the tilesource object. A minimal example object for this method looks like this:
13086
+ *
13087
+ * {
13088
+ * type: "zoomifytileservice",
13089
+ * width: 1000,
13090
+ * height: 1000,
13091
+ * tilesUrl: "/test/data/zoomify/"
13092
+ * }
13093
+ *
13094
+ * The tileSize is currently hardcoded to 256 (the usual Zoomify default). The tileUrl must the path to the image _directory_.
13095
+ *
13096
+ * 2) Loading image metadata from xml file: (CURRENTLY NOT SUPPORTED)
13097
+ *
13098
+ * When creating zoomify formatted images one "xml" like file with name ImageProperties.xml
13099
+ * will be created as well. Here is an example of such a file:
13100
+ *
13101
+ * <IMAGE_PROPERTIES WIDTH="1000" HEIGHT="1000" NUMTILES="21" NUMIMAGES="1" VERSION="1.8" TILESIZE="256" />
13102
+ *
13103
+ * To use this xml file as metadata source you must supply the path to the ImageProperties.xml file and leave out all other parameters:
13104
+ * As stated above, this method of loading a zoomify tilesource is currently not supported
13105
+ *
13106
+ * {
13107
+ * type: "zoomifytileservice",
13108
+ * tilesUrl: "/test/data/zoomify/ImageProperties.xml"
13109
+ * }
13110
+
13111
+ *
13112
+ * @memberof OpenSeadragon
13113
+ * @extends OpenSeadragon.TileSource
13114
+ * @param {Number} width - the pixel width of the image.
13115
+ * @param {Number} height
13116
+ * @param {Number} tileSize
13117
+ * @param {String} tilesUrl
13118
+ */
13119
+ $.ZoomifyTileSource = function(options) {
13120
+ options.tileSize = 256;
13121
+
13122
+ var currentImageSize = {
13123
+ x: options.width,
13124
+ y: options.height
13125
+ };
13126
+ options.imageSizes = [{
13127
+ x: options.width,
13128
+ y: options.height
13129
+ }];
13130
+ options.gridSize = [this._getGridSize(options.width, options.height, options.tileSize)];
13131
+
13132
+ while (parseInt(currentImageSize.x, 10) > options.tileSize || parseInt(currentImageSize.y, 10) > options.tileSize) {
13133
+ currentImageSize.x = Math.floor(currentImageSize.x / 2);
13134
+ currentImageSize.y = Math.floor(currentImageSize.y / 2);
13135
+ options.imageSizes.push({
13136
+ x: currentImageSize.x,
13137
+ y: currentImageSize.y
13138
+ });
13139
+ options.gridSize.push(this._getGridSize(currentImageSize.x, currentImageSize.y, options.tileSize));
13140
+ }
13141
+ options.imageSizes.reverse();
13142
+ options.gridSize.reverse();
13143
+ options.minLevel = 0;
13144
+ options.maxLevel = options.gridSize.length - 1;
13145
+
13146
+ OpenSeadragon.TileSource.apply(this, [options]);
13147
+ };
13148
+
13149
+ $.extend($.ZoomifyTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.ZoomifyTileSource.prototype */ {
13150
+
13151
+ //private
13152
+ _getGridSize: function(width, height, tileSize) {
13153
+ return {
13154
+ x: Math.ceil(width / tileSize),
13155
+ y: Math.ceil(height / tileSize)
13156
+ };
13157
+ },
13158
+
13159
+ //private
13160
+ _calculateAbsoluteTileNumber: function(level, x, y) {
13161
+ var num = 0;
13162
+ var size = {};
13163
+
13164
+ //Sum up all tiles below the level we want the number of tiles
13165
+ for (var z = 0; z < level; z++) {
13166
+ size = this.gridSize[z];
13167
+ num += size.x * size.y;
13168
+ }
13169
+ //Add the tiles of the level
13170
+ size = this.gridSize[level];
13171
+ num += size.x * y + x;
13172
+ return num;
13173
+ },
13174
+
13175
+ /**
13176
+ * Determine if the data and/or url imply the image service is supported by
13177
+ * this tile source.
13178
+ * @function
13179
+ * @param {Object|Array} data
13180
+ * @param {String} optional - url
13181
+ */
13182
+ supports: function(data, url) {
13183
+ return (data.type && "zoomifytileservice" == data.type);
13184
+ },
13185
+
13186
+ /**
13187
+ *
13188
+ * @function
13189
+ * @param {Object} data - the raw configuration
13190
+ * @param {String} url - the url the data was retreived from if any.
13191
+ * @return {Object} options - A dictionary of keyword arguments sufficient
13192
+ * to configure this tile sources constructor.
13193
+ */
13194
+ configure: function(data, url) {
13195
+ return data;
13196
+ },
13197
+
13198
+ /**
13199
+ * @function
13200
+ * @param {Number} level
13201
+ * @param {Number} x
13202
+ * @param {Number} y
13203
+ */
13204
+ getTileUrl: function(level, x, y) {
13205
+ //console.log(level);
13206
+ var result = 0;
13207
+ var num = this._calculateAbsoluteTileNumber(level, x, y);
13208
+ result = Math.floor(num / 256);
13209
+ return this.tilesUrl + 'TileGroup' + result + '/' + level + '-' + x + '-' + y + '.jpg';
13210
+
13211
+ }
13212
+ });
13213
+
13214
+ }(OpenSeadragon));
13215
+
13216
+
12667
13217
  /*
12668
13218
  * OpenSeadragon - LegacyTileSource
12669
13219
  *
@@ -12835,16 +13385,6 @@ $.extend( $.LegacyTileSource.prototype, $.TileSource.prototype, /** @lends OpenS
12835
13385
  }
12836
13386
  },
12837
13387
 
12838
- /**
12839
- * @function
12840
- * @param {Number} level
12841
- * @param {OpenSeadragon.Point} point
12842
- */
12843
- getTileAtPoint: function( level, point ) {
12844
- return new $.Point( 0, 0 );
12845
- },
12846
-
12847
-
12848
13388
  /**
12849
13389
  * This method is not implemented by this class other than to throw an Error
12850
13390
  * announcing you have to implement it. Because of the variety of tile
@@ -12880,12 +13420,7 @@ function filterFiles( files ){
12880
13420
  file = files[ i ];
12881
13421
  if( file.height &&
12882
13422
  file.width &&
12883
- file.url && (
12884
- file.url.toLowerCase().match(/^.*\.(png|jpg|jpeg|gif)(?:\?.*)?$/) || (
12885
- file.mimetype &&
12886
- file.mimetype.toLowerCase().match(/^.*\/(png|jpg|jpeg|gif)$/)
12887
- )
12888
- ) ){
13423
+ file.url ){
12889
13424
  //This is sufficient to serve as a level
12890
13425
  filtered.push({
12891
13426
  url: file.url,
@@ -12898,7 +13433,7 @@ function filterFiles( files ){
12898
13433
  }
12899
13434
  }
12900
13435
 
12901
- return filtered.sort(function(a,b){
13436
+ return filtered.sort(function(a, b) {
12902
13437
  return a.height - b.height;
12903
13438
  });
12904
13439
 
@@ -12934,7 +13469,7 @@ function configureFromXML( tileSource, xmlDoc ){
12934
13469
  for ( i = 0; i < levels.length; i++ ) {
12935
13470
  level = levels[ i ];
12936
13471
 
12937
- conf.levels .push({
13472
+ conf.levels.push({
12938
13473
  url: level.getAttribute( "url" ),
12939
13474
  width: parseInt( level.getAttribute( "width" ), 10 ),
12940
13475
  height: parseInt( level.getAttribute( "height" ), 10 )
@@ -13086,8 +13621,9 @@ function configureFromObject( tileSource, configuration ){
13086
13621
  }
13087
13622
 
13088
13623
  $.addEvent(image, 'load', function () {
13089
- _this.width = image.naturalWidth;
13090
- _this.height = image.naturalHeight;
13624
+ /* IE8 fix since it has no naturalWidth and naturalHeight */
13625
+ _this.width = Object.prototype.hasOwnProperty.call(image, 'naturalWidth') ? image.naturalWidth : image.width;
13626
+ _this.height = Object.prototype.hasOwnProperty.call(image, 'naturalHeight') ? image.naturalHeight : image.height;
13091
13627
  _this.aspectRatio = _this.width / _this.height;
13092
13628
  _this.dimensions = new $.Point(_this.width, _this.height);
13093
13629
  _this._tileWidth = _this.width;
@@ -13138,14 +13674,6 @@ function configureFromObject( tileSource, configuration ){
13138
13674
  return new $.Point(0, 0);
13139
13675
  }
13140
13676
  },
13141
- /**
13142
- * @function
13143
- * @param {Number} level
13144
- * @param {OpenSeadragon.Point} point
13145
- */
13146
- getTileAtPoint: function (level, point) {
13147
- return new $.Point(0, 0);
13148
- },
13149
13677
  /**
13150
13678
  * Retrieves a tile url
13151
13679
  * @function
@@ -13182,8 +13710,9 @@ function configureFromObject( tileSource, configuration ){
13182
13710
  _buildLevels: function () {
13183
13711
  var levels = [{
13184
13712
  url: this._image.src,
13185
- width: this._image.naturalWidth,
13186
- height: this._image.naturalHeight
13713
+ /* IE8 fix since it has no naturalWidth and naturalHeight */
13714
+ width: Object.prototype.hasOwnProperty.call(this._image, 'naturalWidth') ? this._image.naturalWidth : this._image.width,
13715
+ height: Object.prototype.hasOwnProperty.call(this._image, 'naturalHeight') ? this._image.naturalHeight : this._image.height
13187
13716
  }];
13188
13717
 
13189
13718
  if (!this.buildPyramid || !$.supportsCanvas || !this.useCanvas) {
@@ -13192,8 +13721,10 @@ function configureFromObject( tileSource, configuration ){
13192
13721
  return levels;
13193
13722
  }
13194
13723
 
13195
- var currentWidth = this._image.naturalWidth;
13196
- var currentHeight = this._image.naturalHeight;
13724
+ /* IE8 fix since it has no naturalWidth and naturalHeight */
13725
+ var currentWidth = Object.prototype.hasOwnProperty.call(this._image, 'naturalWidth') ? this._image.naturalWidth : this._image.width;
13726
+ var currentHeight = Object.prototype.hasOwnProperty.call(this._image, 'naturalHeight') ? this._image.naturalHeight : this._image.height;
13727
+
13197
13728
 
13198
13729
  var bigCanvas = document.createElement("canvas");
13199
13730
  var bigContext = bigCanvas.getContext("2d");
@@ -13273,14 +13804,14 @@ function configureFromObject( tileSource, configuration ){
13273
13804
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
13274
13805
  */
13275
13806
 
13276
- (function( $ ){
13807
+ (function($) {
13277
13808
 
13278
13809
  // deprecated
13279
- $.TileSourceCollection = function( tileSize, tileSources, rows, layout ) {
13810
+ $.TileSourceCollection = function(tileSize, tileSources, rows, layout) {
13280
13811
  $.console.error('TileSourceCollection is deprecated; use World instead');
13281
13812
  };
13282
13813
 
13283
- }( OpenSeadragon ));
13814
+ }(OpenSeadragon));
13284
13815
 
13285
13816
  /*
13286
13817
  * OpenSeadragon - Button
@@ -13345,7 +13876,7 @@ $.ButtonState = {
13345
13876
  * @memberof OpenSeadragon
13346
13877
  * @extends OpenSeadragon.EventSource
13347
13878
  * @param {Object} options
13348
- * @param {Element} [options.element=null] Element to use as the button. If not specified, an HTML &lt;button&gt; element is created.
13879
+ * @param {Element} [options.element=null] Element to use as the button. If not specified, an HTML &lt;div&gt; element is created.
13349
13880
  * @param {String} [options.tooltip=null] Provides context help for the button when the
13350
13881
  * user hovers over it.
13351
13882
  * @param {String} [options.srcRest=null] URL of image to use in 'rest' state.
@@ -13404,7 +13935,7 @@ $.Button = function( options ) {
13404
13935
  * @member {Element} element
13405
13936
  * @memberof OpenSeadragon.Button#
13406
13937
  */
13407
- this.element = options.element || $.makeNeutralElement( "div" );
13938
+ this.element = options.element || $.makeNeutralElement("div");
13408
13939
 
13409
13940
  //if the user has specified the element to bind the control to explicitly
13410
13941
  //then do not add the default control images
@@ -13442,7 +13973,7 @@ $.Button = function( options ) {
13442
13973
  this.imgDown.style.visibility =
13443
13974
  "hidden";
13444
13975
 
13445
- if ( $.Browser.vendor == $.BROWSERS.FIREFOX && $.Browser.version < 3 ){
13976
+ if ($.Browser.vendor == $.BROWSERS.FIREFOX && $.Browser.version < 3) {
13446
13977
  this.imgGroup.style.top =
13447
13978
  this.imgHover.style.top =
13448
13979
  this.imgDown.style.top =
@@ -13456,13 +13987,13 @@ $.Button = function( options ) {
13456
13987
  }
13457
13988
 
13458
13989
 
13459
- this.addHandler( "press", this.onPress );
13460
- this.addHandler( "release", this.onRelease );
13461
- this.addHandler( "click", this.onClick );
13462
- this.addHandler( "enter", this.onEnter );
13463
- this.addHandler( "exit", this.onExit );
13464
- this.addHandler( "focus", this.onFocus );
13465
- this.addHandler( "blur", this.onBlur );
13990
+ this.addHandler("press", this.onPress);
13991
+ this.addHandler("release", this.onRelease);
13992
+ this.addHandler("click", this.onClick);
13993
+ this.addHandler("enter", this.onEnter);
13994
+ this.addHandler("exit", this.onExit);
13995
+ this.addHandler("focus", this.onFocus);
13996
+ this.addHandler("blur", this.onBlur);
13466
13997
 
13467
13998
  /**
13468
13999
  * The button's current state.
@@ -14011,10 +14542,7 @@ $.Rect = function(x, y, width, height, degrees) {
14011
14542
  this.degrees = typeof(degrees) === "number" ? degrees : 0;
14012
14543
 
14013
14544
  // Normalizes the rectangle.
14014
- this.degrees = this.degrees % 360;
14015
- if (this.degrees < 0) {
14016
- this.degrees += 360;
14017
- }
14545
+ this.degrees = $.positiveModulo(this.degrees, 360);
14018
14546
  var newTopLeft, newWidth;
14019
14547
  if (this.degrees >= 270) {
14020
14548
  newTopLeft = this.getTopRight();
@@ -14372,19 +14900,21 @@ $.Rect.prototype = {
14372
14900
  * @return {OpenSeadragon.Rect}
14373
14901
  */
14374
14902
  rotate: function(degrees, pivot) {
14375
- degrees = degrees % 360;
14903
+ degrees = $.positiveModulo(degrees, 360);
14376
14904
  if (degrees === 0) {
14377
14905
  return this.clone();
14378
14906
  }
14379
- if (degrees < 0) {
14380
- degrees += 360;
14381
- }
14382
14907
 
14383
14908
  pivot = pivot || this.getCenter();
14384
14909
  var newTopLeft = this.getTopLeft().rotate(degrees, pivot);
14385
14910
  var newTopRight = this.getTopRight().rotate(degrees, pivot);
14386
14911
 
14387
14912
  var diff = newTopRight.minus(newTopLeft);
14913
+ // Handle floating point error
14914
+ diff = diff.apply(function(x) {
14915
+ var EPSILON = 1e-15;
14916
+ return Math.abs(x) < EPSILON ? 0 : x;
14917
+ });
14388
14918
  var radians = Math.atan(diff.y / diff.x);
14389
14919
  if (diff.x < 0) {
14390
14920
  radians += Math.PI;
@@ -14668,6 +15198,7 @@ $.ReferenceStrip = function ( options ) {
14668
15198
  this.panelWidth = ( viewerSize.x * this.sizeRatio ) + 8;
14669
15199
  this.panelHeight = ( viewerSize.y * this.sizeRatio ) + 8;
14670
15200
  this.panels = [];
15201
+ this.miniViewers = {};
14671
15202
 
14672
15203
  /*jshint loopfunc:true*/
14673
15204
  for ( i = 0; i < viewer.tileSources.length; i++ ) {
@@ -14783,6 +15314,12 @@ $.extend( $.ReferenceStrip.prototype, $.EventSource.prototype, $.Viewer.prototyp
14783
15314
 
14784
15315
  // Overrides Viewer.destroy
14785
15316
  destroy: function() {
15317
+ if (this.miniViewers) {
15318
+ for (var key in this.miniViewers) {
15319
+ this.miniViewers[key].destroy();
15320
+ }
15321
+ }
15322
+
14786
15323
  if (this.element) {
14787
15324
  this.element.parentNode.removeChild(this.element);
14788
15325
  }
@@ -14911,9 +15448,19 @@ function loadPanels( strip, viewerSize, scroll ) {
14911
15448
  for ( i = activePanelsStart; i < activePanelsEnd && i < strip.panels.length; i++ ) {
14912
15449
  element = strip.panels[i];
14913
15450
  if ( !element.activePanel ) {
15451
+ var miniTileSource;
15452
+ var originalTileSource = strip.viewer.tileSources[i];
15453
+ if (originalTileSource.referenceStripThumbnailUrl) {
15454
+ miniTileSource = {
15455
+ type: 'image',
15456
+ url: originalTileSource.referenceStripThumbnailUrl
15457
+ };
15458
+ } else {
15459
+ miniTileSource = originalTileSource;
15460
+ }
14914
15461
  miniViewer = new $.Viewer( {
14915
15462
  id: element.id,
14916
- tileSources: [strip.viewer.tileSources[i]],
15463
+ tileSources: [miniTileSource],
14917
15464
  element: element,
14918
15465
  navigatorSizeRatio: strip.sizeRatio,
14919
15466
  showNavigator: false,
@@ -14953,6 +15500,8 @@ function loadPanels( strip, viewerSize, scroll ) {
14953
15500
  miniViewer.displayRegion
14954
15501
  );
14955
15502
 
15503
+ strip.miniViewers[element.id] = miniViewer;
15504
+
14956
15505
  element.activePanel = true;
14957
15506
  }
14958
15507
  }
@@ -15082,9 +15631,7 @@ function onKeyPress( event ) {
15082
15631
  }
15083
15632
  }
15084
15633
 
15085
-
15086
-
15087
- } ( OpenSeadragon ) );
15634
+ }(OpenSeadragon));
15088
15635
 
15089
15636
  /*
15090
15637
  * OpenSeadragon - DisplayRect
@@ -15366,6 +15913,7 @@ $.Spring.prototype = {
15366
15913
 
15367
15914
  /**
15368
15915
  * @function
15916
+ * @returns true if the value got updated, false otherwise
15369
15917
  */
15370
15918
  update: function() {
15371
15919
  this.current.time = $.now();
@@ -15386,14 +15934,17 @@ $.Spring.prototype = {
15386
15934
  transform(
15387
15935
  this.springStiffness,
15388
15936
  ( this.current.time - this.start.time ) /
15389
- ( this.target.time - this.start.time )
15937
+ ( this.target.time - this.start.time )
15390
15938
  );
15391
15939
 
15940
+ var oldValue = this.current.value;
15392
15941
  if (this._exponential) {
15393
15942
  this.current.value = Math.exp(currentValue);
15394
15943
  } else {
15395
15944
  this.current.value = currentValue;
15396
15945
  }
15946
+
15947
+ return oldValue != this.current.value;
15397
15948
  },
15398
15949
 
15399
15950
  /**
@@ -15450,15 +16001,27 @@ function transform( stiffness, x ) {
15450
16001
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15451
16002
  */
15452
16003
 
15453
- (function( $ ){
16004
+ (function($){
15454
16005
 
15455
- // private class
15456
- function ImageJob ( options ) {
16006
+ /**
16007
+ * @private
16008
+ * @class ImageJob
16009
+ * @classdesc Handles downloading of a single image.
16010
+ * @param {Object} options - Options for this ImageJob.
16011
+ * @param {String} [options.src] - URL of image to download.
16012
+ * @param {String} [options.loadWithAjax] - Whether to load this image with AJAX.
16013
+ * @param {String} [options.ajaxHeaders] - Headers to add to the image request if using AJAX.
16014
+ * @param {String} [options.crossOriginPolicy] - CORS policy to use for downloads
16015
+ * @param {Function} [options.callback] - Called once image has been downloaded.
16016
+ * @param {Function} [options.abort] - Called when this image job is aborted.
16017
+ * @param {Number} [options.timeout] - The max number of milliseconds that this image job may take to complete.
16018
+ */
16019
+ function ImageJob (options) {
15457
16020
 
15458
- $.extend( true, this, {
15459
- timeout: $.DEFAULT_SETTINGS.timeout,
15460
- jobId: null
15461
- }, options );
16021
+ $.extend(true, this, {
16022
+ timeout: $.DEFAULT_SETTINGS.timeout,
16023
+ jobId: null
16024
+ }, options);
15462
16025
 
15463
16026
  /**
15464
16027
  * Image object which will contain downloaded image.
@@ -15470,42 +16033,103 @@ function ImageJob ( options ) {
15470
16033
 
15471
16034
  ImageJob.prototype = {
15472
16035
  errorMsg: null,
16036
+
16037
+ /**
16038
+ * Starts the image job.
16039
+ * @method
16040
+ */
15473
16041
  start: function(){
15474
- var _this = this;
16042
+ var self = this;
16043
+ var selfAbort = this.abort;
15475
16044
 
15476
16045
  this.image = new Image();
15477
16046
 
15478
- if ( this.crossOriginPolicy !== false ) {
15479
- this.image.crossOrigin = this.crossOriginPolicy;
15480
- }
15481
-
15482
16047
  this.image.onload = function(){
15483
- _this.finish( true );
16048
+ self.finish(true);
15484
16049
  };
15485
- this.image.onabort = this.image.onerror = function(){
15486
- _this.errorMsg = "Image load aborted";
15487
- _this.finish( false );
16050
+ this.image.onabort = this.image.onerror = function() {
16051
+ self.errorMsg = "Image load aborted";
16052
+ self.finish(false);
15488
16053
  };
15489
16054
 
15490
- this.jobId = window.setTimeout( function(){
15491
- _this.errorMsg = "Image load exceeded timeout";
15492
- _this.finish( false );
16055
+ this.jobId = window.setTimeout(function(){
16056
+ self.errorMsg = "Image load exceeded timeout";
16057
+ self.finish(false);
15493
16058
  }, this.timeout);
15494
16059
 
15495
- this.image.src = this.src;
16060
+ // Load the tile with an AJAX request if the loadWithAjax option is
16061
+ // set. Otherwise load the image by setting the source proprety of the image object.
16062
+ if (this.loadWithAjax) {
16063
+ this.request = $.makeAjaxRequest({
16064
+ url: this.src,
16065
+ withCredentials: this.ajaxWithCredentials,
16066
+ headers: this.ajaxHeaders,
16067
+ responseType: "arraybuffer",
16068
+ success: function(request) {
16069
+ var blb;
16070
+ // Make the raw data into a blob.
16071
+ // BlobBuilder fallback adapted from
16072
+ // http://stackoverflow.com/questions/15293694/blob-constructor-browser-compatibility
16073
+ try {
16074
+ blb = new window.Blob([request.response]);
16075
+ } catch (e) {
16076
+ var BlobBuilder = (
16077
+ window.BlobBuilder ||
16078
+ window.WebKitBlobBuilder ||
16079
+ window.MozBlobBuilder ||
16080
+ window.MSBlobBuilder
16081
+ );
16082
+ if (e.name === 'TypeError' && BlobBuilder) {
16083
+ var bb = new BlobBuilder();
16084
+ bb.append(request.response);
16085
+ blb = bb.getBlob();
16086
+ }
16087
+ }
16088
+ // If the blob is empty for some reason consider the image load a failure.
16089
+ if (blb.size === 0) {
16090
+ self.errorMsg = "Empty image response.";
16091
+ self.finish(false);
16092
+ }
16093
+ // Create a URL for the blob data and make it the source of the image object.
16094
+ // This will still trigger Image.onload to indicate a successful tile load.
16095
+ var url = (window.URL || window.webkitURL).createObjectURL(blb);
16096
+ self.image.src = url;
16097
+ },
16098
+ error: function(request) {
16099
+ self.errorMsg = "Image load aborted - XHR error";
16100
+ self.finish(false);
16101
+ }
16102
+ });
16103
+
16104
+ // Provide a function to properly abort the request.
16105
+ this.abort = function() {
16106
+ self.request.abort();
16107
+
16108
+ // Call the existing abort function if available
16109
+ if (typeof selfAbort === "function") {
16110
+ selfAbort();
16111
+ }
16112
+ };
16113
+ } else {
16114
+ if (this.crossOriginPolicy !== false) {
16115
+ this.image.crossOrigin = this.crossOriginPolicy;
16116
+ }
16117
+
16118
+ this.image.src = this.src;
16119
+ }
15496
16120
  },
15497
16121
 
15498
- finish: function( successful ) {
16122
+ finish: function(successful) {
15499
16123
  this.image.onload = this.image.onerror = this.image.onabort = null;
15500
16124
  if (!successful) {
15501
16125
  this.image = null;
15502
16126
  }
15503
16127
 
15504
- if ( this.jobId ) {
15505
- window.clearTimeout( this.jobId );
16128
+ if (this.jobId) {
16129
+ window.clearTimeout(this.jobId);
15506
16130
  }
15507
16131
 
15508
- this.callback( this );
16132
+ this.callback(this);
15509
16133
  }
15510
16134
 
15511
16135
  };
@@ -15517,14 +16141,16 @@ ImageJob.prototype = {
15517
16141
  * You generally won't have to interact with the ImageLoader directly.
15518
16142
  * @param {Object} options - Options for this ImageLoader.
15519
16143
  * @param {Number} [options.jobLimit] - The number of concurrent image requests. See imageLoaderLimit in {@link OpenSeadragon.Options} for details.
16144
+ * @param {Number} [options.timeout] - The max number of milliseconds that an image job may take to complete.
15520
16145
  */
15521
- $.ImageLoader = function( options ) {
16146
+ $.ImageLoader = function(options) {
15522
16147
 
15523
- $.extend( true, this, {
16148
+ $.extend(true, this, {
15524
16149
  jobLimit: $.DEFAULT_SETTINGS.imageLoaderLimit,
16150
+ timeout: $.DEFAULT_SETTINGS.timeout,
15525
16151
  jobQueue: [],
15526
16152
  jobsInProgress: 0
15527
- }, options );
16153
+ }, options);
15528
16154
 
15529
16155
  };
15530
16156
 
@@ -15534,22 +16160,32 @@ $.ImageLoader.prototype = {
15534
16160
  /**
15535
16161
  * Add an unloaded image to the loader queue.
15536
16162
  * @method
15537
- * @param {String} src - URL of image to download.
15538
- * @param {String} crossOriginPolicy - CORS policy to use for downloads
15539
- * @param {Function} callback - Called once image has been downloaded.
15540
- */
15541
- addJob: function( options ) {
16163
+ * @param {Object} options - Options for this job.
16164
+ * @param {String} [options.src] - URL of image to download.
16165
+ * @param {String} [options.loadWithAjax] - Whether to load this image with AJAX.
16166
+ * @param {String} [options.ajaxHeaders] - Headers to add to the image request if using AJAX.
16167
+ * @param {String|Boolean} [options.crossOriginPolicy] - CORS policy to use for downloads
16168
+ * @param {Boolean} [options.ajaxWithCredentials] - Whether to set withCredentials on AJAX
16169
+ * requests.
16170
+ * @param {Function} [options.callback] - Called once image has been downloaded.
16171
+ * @param {Function} [options.abort] - Called when this image job is aborted.
16172
+ */
16173
+ addJob: function(options) {
15542
16174
  var _this = this,
15543
- complete = function( job ) {
15544
- completeJob( _this, job, options.callback );
16175
+ complete = function(job) {
16176
+ completeJob(_this, job, options.callback);
15545
16177
  },
15546
16178
  jobOptions = {
15547
16179
  src: options.src,
16180
+ loadWithAjax: options.loadWithAjax,
16181
+ ajaxHeaders: options.loadWithAjax ? options.ajaxHeaders : null,
15548
16182
  crossOriginPolicy: options.crossOriginPolicy,
16183
+ ajaxWithCredentials: options.ajaxWithCredentials,
15549
16184
  callback: complete,
15550
- abort: options.abort
16185
+ abort: options.abort,
16186
+ timeout: this.timeout
15551
16187
  },
15552
- newJob = new ImageJob( jobOptions );
16188
+ newJob = new ImageJob(jobOptions);
15553
16189
 
15554
16190
  if ( !this.jobLimit || this.jobsInProgress < this.jobLimit ) {
15555
16191
  newJob.start();
@@ -15584,21 +16220,21 @@ $.ImageLoader.prototype = {
15584
16220
  * @param job - The ImageJob that has completed.
15585
16221
  * @param callback - Called once cleanup is finished.
15586
16222
  */
15587
- function completeJob( loader, job, callback ) {
16223
+ function completeJob(loader, job, callback) {
15588
16224
  var nextJob;
15589
16225
 
15590
16226
  loader.jobsInProgress--;
15591
16227
 
15592
- if ( (!loader.jobLimit || loader.jobsInProgress < loader.jobLimit) && loader.jobQueue.length > 0) {
16228
+ if ((!loader.jobLimit || loader.jobsInProgress < loader.jobLimit) && loader.jobQueue.length > 0) {
15593
16229
  nextJob = loader.jobQueue.shift();
15594
16230
  nextJob.start();
15595
16231
  loader.jobsInProgress++;
15596
16232
  }
15597
16233
 
15598
- callback( job.image, job.errorMsg );
16234
+ callback(job.image, job.errorMsg, job.request);
15599
16235
  }
15600
16236
 
15601
- }( OpenSeadragon ));
16237
+ }(OpenSeadragon));
15602
16238
 
15603
16239
  /*
15604
16240
  * OpenSeadragon - Tile
@@ -15649,8 +16285,10 @@ function completeJob( loader, job, callback ) {
15649
16285
  * @param {String} url The URL of this tile's image.
15650
16286
  * @param {CanvasRenderingContext2D} context2D The context2D of this tile if it
15651
16287
  * is provided directly by the tile source.
16288
+ * @param {Boolean} loadWithAjax Whether this tile image should be loaded with an AJAX request .
16289
+ * @param {Object} ajaxHeaders The headers to send with this tile's AJAX request (if applicable).
15652
16290
  */
15653
- $.Tile = function(level, x, y, bounds, exists, url, context2D) {
16291
+ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, ajaxHeaders) {
15654
16292
  /**
15655
16293
  * The zoom level this tile belongs to.
15656
16294
  * @member {Number} level
@@ -15693,6 +16331,29 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D) {
15693
16331
  * @memberOf OpenSeadragon.Tile#
15694
16332
  */
15695
16333
  this.context2D = context2D;
16334
+ /**
16335
+ * Whether to load this tile's image with an AJAX request.
16336
+ * @member {Boolean} loadWithAjax
16337
+ * @memberof OpenSeadragon.Tile#
16338
+ */
16339
+ this.loadWithAjax = loadWithAjax;
16340
+ /**
16341
+ * The headers to be used in requesting this tile's image.
16342
+ * Only used if loadWithAjax is set to true.
16343
+ * @member {Object} ajaxHeaders
16344
+ * @memberof OpenSeadragon.Tile#
16345
+ */
16346
+ this.ajaxHeaders = ajaxHeaders;
16347
+ /**
16348
+ * The unique cache key for this tile.
16349
+ * @member {String} cacheKey
16350
+ * @memberof OpenSeadragon.Tile#
16351
+ */
16352
+ if (this.ajaxHeaders) {
16353
+ this.cacheKey = this.url + "+" + JSON.stringify(this.ajaxHeaders);
16354
+ } else {
16355
+ this.cacheKey = this.url;
16356
+ }
15696
16357
  /**
15697
16358
  * Is this tile loaded?
15698
16359
  * @member {Boolean} loaded
@@ -15756,11 +16417,13 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D) {
15756
16417
  */
15757
16418
  this.opacity = null;
15758
16419
  /**
15759
- * The distance of this tile to the viewport center.
15760
- * @member {Number} distance
16420
+ * The squared distance of this tile to the viewport center.
16421
+ * Use for comparing tiles.
16422
+ * @private
16423
+ * @member {Number} squaredDistance
15761
16424
  * @memberof OpenSeadragon.Tile#
15762
16425
  */
15763
- this.distance = null;
16426
+ this.squaredDistance = null;
15764
16427
  /**
15765
16428
  * The visibility score of this tile.
15766
16429
  * @member {Number} visibility
@@ -16040,6 +16703,7 @@ $.Tile.prototype = {
16040
16703
  * compatibility.
16041
16704
  * @member OverlayPlacement
16042
16705
  * @memberof OpenSeadragon
16706
+ * @see OpenSeadragon.Placement
16043
16707
  * @static
16044
16708
  * @readonly
16045
16709
  * @type {Object}
@@ -16246,6 +16910,8 @@ $.Tile.prototype = {
16246
16910
  element.prevNextSibling = element.nextSibling;
16247
16911
  container.appendChild(element);
16248
16912
 
16913
+ // have to set position before calculating size, fix #1116
16914
+ this.style.position = "absolute";
16249
16915
  // this.size is used by overlays which don't get scaled in at
16250
16916
  // least one direction when this.checkResize is set to false.
16251
16917
  this.size = $.getElementSize(element);
@@ -16284,7 +16950,6 @@ $.Tile.prototype = {
16284
16950
  style[transformProp] = "";
16285
16951
  }
16286
16952
  }
16287
- style.position = "absolute";
16288
16953
 
16289
16954
  if (style.display !== 'none') {
16290
16955
  style.display = 'block';
@@ -16399,7 +17064,7 @@ $.Tile.prototype = {
16399
17064
  * @param {OpenSeadragon.Point|OpenSeadragon.Rect|Object} location
16400
17065
  * If an object is specified, the options are the same than the constructor
16401
17066
  * except for the element which can not be changed.
16402
- * @param {OpenSeadragon.Placement} position
17067
+ * @param {OpenSeadragon.Placement} placement
16403
17068
  */
16404
17069
  update: function(location, placement) {
16405
17070
  var options = $.isPlainObject(location) ? location : {
@@ -16761,8 +17426,8 @@ $.Drawer.prototype = {
16761
17426
  return new $.Rect(
16762
17427
  topLeft.x * $.pixelDensityRatio,
16763
17428
  topLeft.y * $.pixelDensityRatio,
16764
- size.x * $.pixelDensityRatio,
16765
- size.y * $.pixelDensityRatio
17429
+ size.x * $.pixelDensityRatio,
17430
+ size.y * $.pixelDensityRatio
16766
17431
  );
16767
17432
  },
16768
17433
 
@@ -16805,6 +17470,9 @@ $.Drawer.prototype = {
16805
17470
  if (this.viewport.getRotation() === 0) {
16806
17471
  var self = this;
16807
17472
  this.viewer.addHandler('rotate', function resizeSketchCanvas() {
17473
+ if (self.viewport.getRotation() === 0) {
17474
+ return;
17475
+ }
16808
17476
  self.viewer.removeHandler('rotate', resizeSketchCanvas);
16809
17477
  var sketchCanvasSize = self._calculateSketchCanvasSize();
16810
17478
  self.sketchCanvas.width = sketchCanvasSize.x;
@@ -16899,6 +17567,24 @@ $.Drawer.prototype = {
16899
17567
  this.context.globalCompositeOperation = compositeOperation;
16900
17568
  }
16901
17569
  if (bounds) {
17570
+ // Internet Explorer, Microsoft Edge, and Safari have problems
17571
+ // when you call context.drawImage with negative x or y
17572
+ // or x + width or y + height greater than the canvas width or height respectively.
17573
+ if (bounds.x < 0) {
17574
+ bounds.width += bounds.x;
17575
+ bounds.x = 0;
17576
+ }
17577
+ if (bounds.x + bounds.width > this.canvas.width) {
17578
+ bounds.width = this.canvas.width - bounds.x;
17579
+ }
17580
+ if (bounds.y < 0) {
17581
+ bounds.height += bounds.y;
17582
+ bounds.y = 0;
17583
+ }
17584
+ if (bounds.y + bounds.height > this.canvas.height) {
17585
+ bounds.height = this.canvas.height - bounds.y;
17586
+ }
17587
+
16902
17588
  this.context.drawImage(
16903
17589
  this.sketchCanvas,
16904
17590
  bounds.x,
@@ -16929,7 +17615,7 @@ $.Drawer.prototype = {
16929
17615
  position.x - widthExt * scale,
16930
17616
  position.y - heightExt * scale,
16931
17617
  (this.canvas.width + 2 * widthExt) * scale,
16932
- (this.canvas.height + 2 * heightExt) * scale,
17618
+ (this.canvas.height + 2 * heightExt) * scale,
16933
17619
  -widthExt,
16934
17620
  -heightExt,
16935
17621
  this.canvas.width + 2 * widthExt,
@@ -16940,7 +17626,7 @@ $.Drawer.prototype = {
16940
17626
  },
16941
17627
 
16942
17628
  // private
16943
- drawDebugInfo: function( tile, count, i ){
17629
+ drawDebugInfo: function(tile, count, i, tiledImage) {
16944
17630
  if ( !this.useCanvas ) {
16945
17631
  return;
16946
17632
  }
@@ -16953,7 +17639,14 @@ $.Drawer.prototype = {
16953
17639
  context.fillStyle = this.debugGridColor;
16954
17640
 
16955
17641
  if ( this.viewport.degrees !== 0 ) {
16956
- this._offsetForRotation(this.viewport.degrees);
17642
+ this._offsetForRotation({degrees: this.viewport.degrees});
17643
+ }
17644
+ if (tiledImage.getRotation(true) % 360 !== 0) {
17645
+ this._offsetForRotation({
17646
+ degrees: tiledImage.getRotation(true),
17647
+ point: tiledImage.viewport.pixelFromPointNoRotate(
17648
+ tiledImage._getRotationPoint(true), true)
17649
+ });
16957
17650
  }
16958
17651
 
16959
17652
  context.strokeRect(
@@ -17017,6 +17710,9 @@ $.Drawer.prototype = {
17017
17710
  if ( this.viewport.degrees !== 0 ) {
17018
17711
  this._restoreRotationChanges();
17019
17712
  }
17713
+ if (tiledImage.getRotation(true) % 360 !== 0) {
17714
+ this._restoreRotationChanges();
17715
+ }
17020
17716
  context.restore();
17021
17717
  },
17022
17718
 
@@ -17050,17 +17746,22 @@ $.Drawer.prototype = {
17050
17746
  return new $.Point(canvas.width, canvas.height);
17051
17747
  },
17052
17748
 
17749
+ getCanvasCenter: function() {
17750
+ return new $.Point(this.canvas.width / 2, this.canvas.height / 2);
17751
+ },
17752
+
17053
17753
  // private
17054
- _offsetForRotation: function(degrees, useSketch) {
17055
- var cx = this.canvas.width / 2;
17056
- var cy = this.canvas.height / 2;
17754
+ _offsetForRotation: function(options) {
17755
+ var point = options.point ?
17756
+ options.point.times($.pixelDensityRatio) :
17757
+ this.getCanvasCenter();
17057
17758
 
17058
- var context = this._getContext(useSketch);
17759
+ var context = this._getContext(options.useSketch);
17059
17760
  context.save();
17060
17761
 
17061
- context.translate(cx, cy);
17062
- context.rotate(Math.PI / 180 * degrees);
17063
- context.translate(-cx, -cy);
17762
+ context.translate(point.x, point.y);
17763
+ context.rotate(Math.PI / 180 * options.degrees);
17764
+ context.translate(-point.x, -point.y);
17064
17765
  },
17065
17766
 
17066
17767
  // private
@@ -17161,11 +17862,11 @@ $.Viewport = function( options ) {
17161
17862
  //backward compatibility for positional args while prefering more
17162
17863
  //idiomatic javascript options object as the only argument
17163
17864
  var args = arguments;
17164
- if( args.length && args[ 0 ] instanceof $.Point ){
17865
+ if (args.length && args[0] instanceof $.Point) {
17165
17866
  options = {
17166
- containerSize: args[ 0 ],
17167
- contentSize: args[ 1 ],
17168
- config: args[ 2 ]
17867
+ containerSize: args[0],
17868
+ contentSize: args[1],
17869
+ config: args[2]
17169
17870
  };
17170
17871
  }
17171
17872
 
@@ -17586,10 +18287,9 @@ $.Viewport.prototype = {
17586
18287
  * @function
17587
18288
  * @private
17588
18289
  * @param {OpenSeadragon.Rect} bounds
17589
- * @param {Boolean} immediately
17590
18290
  * @return {OpenSeadragon.Rect} constrained bounds.
17591
18291
  */
17592
- _applyBoundaryConstraints: function(bounds, immediately) {
18292
+ _applyBoundaryConstraints: function(bounds) {
17593
18293
  var newBounds = new $.Rect(
17594
18294
  bounds.x,
17595
18295
  bounds.y,
@@ -17632,6 +18332,16 @@ $.Viewport.prototype = {
17632
18332
  }
17633
18333
  }
17634
18334
 
18335
+ return newBounds;
18336
+ },
18337
+
18338
+ /**
18339
+ * @function
18340
+ * @private
18341
+ * @param {Boolean} [immediately=false] - whether the function that triggered this event was
18342
+ * called with the "immediately" flag
18343
+ */
18344
+ _raiseConstraintsEvent: function(immediately) {
17635
18345
  if (this.viewer) {
17636
18346
  /**
17637
18347
  * Raised when the viewport constraints are applied (see {@link OpenSeadragon.Viewport#applyConstraints}).
@@ -17640,15 +18350,14 @@ $.Viewport.prototype = {
17640
18350
  * @memberof OpenSeadragon.Viewer
17641
18351
  * @type {object}
17642
18352
  * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
17643
- * @property {Boolean} immediately
18353
+ * @property {Boolean} immediately - whether the function that triggered this event was
18354
+ * called with the "immediately" flag
17644
18355
  * @property {?Object} userData - Arbitrary subscriber-defined object.
17645
18356
  */
17646
18357
  this.viewer.raiseEvent( 'constrain', {
17647
18358
  immediately: immediately
17648
18359
  });
17649
18360
  }
17650
-
17651
- return newBounds;
17652
18361
  },
17653
18362
 
17654
18363
  /**
@@ -17668,8 +18377,8 @@ $.Viewport.prototype = {
17668
18377
  }
17669
18378
 
17670
18379
  var bounds = this.getBoundsNoRotate();
17671
- var constrainedBounds = this._applyBoundaryConstraints(
17672
- bounds, immediately);
18380
+ var constrainedBounds = this._applyBoundaryConstraints(bounds);
18381
+ this._raiseConstraintsEvent(immediately);
17673
18382
 
17674
18383
  if (bounds.x !== constrainedBounds.x ||
17675
18384
  bounds.y !== constrainedBounds.y ||
@@ -17739,8 +18448,9 @@ $.Viewport.prototype = {
17739
18448
  newBounds.y = center.y - newBounds.height / 2;
17740
18449
  }
17741
18450
 
17742
- newBounds = this._applyBoundaryConstraints(newBounds, immediately);
18451
+ newBounds = this._applyBoundaryConstraints(newBounds);
17743
18452
  center = newBounds.getCenter();
18453
+ this._raiseConstraintsEvent(immediately);
17744
18454
  }
17745
18455
 
17746
18456
  if (immediately) {
@@ -17834,6 +18544,23 @@ $.Viewport.prototype = {
17834
18544
  },
17835
18545
 
17836
18546
 
18547
+ /**
18548
+ * Returns bounds taking constraints into account
18549
+ * Added to improve constrained panning
18550
+ * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
18551
+ * @return {OpenSeadragon.Viewport} Chainable.
18552
+ */
18553
+ getConstrainedBounds: function(current) {
18554
+ var bounds,
18555
+ constrainedBounds;
18556
+
18557
+ bounds = this.getBounds(current);
18558
+
18559
+ constrainedBounds = this._applyBoundaryConstraints(bounds);
18560
+
18561
+ return constrainedBounds;
18562
+ },
18563
+
17837
18564
  /**
17838
18565
  * @function
17839
18566
  * @param {OpenSeadragon.Point} delta
@@ -17906,7 +18633,8 @@ $.Viewport.prototype = {
17906
18633
  * @return {OpenSeadragon.Viewport} Chainable.
17907
18634
  * @fires OpenSeadragon.Viewer.event:zoom
17908
18635
  */
17909
- zoomTo: function( zoom, refPoint, immediately ) {
18636
+ zoomTo: function(zoom, refPoint, immediately) {
18637
+ var _this = this;
17910
18638
 
17911
18639
  this.zoomPoint = refPoint instanceof $.Point &&
17912
18640
  !isNaN(refPoint.x) &&
@@ -17914,13 +18642,15 @@ $.Viewport.prototype = {
17914
18642
  refPoint :
17915
18643
  null;
17916
18644
 
17917
- if ( immediately ) {
17918
- this.zoomSpring.resetTo( zoom );
18645
+ if (immediately) {
18646
+ this._adjustCenterSpringsForZoomPoint(function() {
18647
+ _this.zoomSpring.resetTo(zoom);
18648
+ });
17919
18649
  } else {
17920
- this.zoomSpring.springTo( zoom );
18650
+ this.zoomSpring.springTo(zoom);
17921
18651
  }
17922
18652
 
17923
- if( this.viewer ){
18653
+ if (this.viewer) {
17924
18654
  /**
17925
18655
  * Raised when the viewport zoom level changes (see {@link OpenSeadragon.Viewport#zoomBy} and {@link OpenSeadragon.Viewport#zoomTo}).
17926
18656
  *
@@ -17933,7 +18663,7 @@ $.Viewport.prototype = {
17933
18663
  * @property {Boolean} immediately
17934
18664
  * @property {?Object} userData - Arbitrary subscriber-defined object.
17935
18665
  */
17936
- this.viewer.raiseEvent( 'zoom', {
18666
+ this.viewer.raiseEvent('zoom', {
17937
18667
  zoom: zoom,
17938
18668
  refPoint: refPoint,
17939
18669
  immediately: immediately
@@ -17953,11 +18683,7 @@ $.Viewport.prototype = {
17953
18683
  return this;
17954
18684
  }
17955
18685
 
17956
- degrees = degrees % 360;
17957
- if (degrees < 0) {
17958
- degrees += 360;
17959
- }
17960
- this.degrees = degrees;
18686
+ this.degrees = $.positiveModulo(degrees, 360);
17961
18687
  this._setContentBounds(
17962
18688
  this.viewer.world.getHomeBounds(),
17963
18689
  this.viewer.world.getContentFactor());
@@ -18043,10 +18769,29 @@ $.Viewport.prototype = {
18043
18769
  * @returns {Boolean} True if any change has been made, false otherwise.
18044
18770
  */
18045
18771
  update: function() {
18772
+ var _this = this;
18773
+ this._adjustCenterSpringsForZoomPoint(function() {
18774
+ _this.zoomSpring.update();
18775
+ });
18776
+
18777
+ this.centerSpringX.update();
18778
+ this.centerSpringY.update();
18779
+
18780
+ var changed = this.centerSpringX.current.value !== this._oldCenterX ||
18781
+ this.centerSpringY.current.value !== this._oldCenterY ||
18782
+ this.zoomSpring.current.value !== this._oldZoom;
18783
+
18784
+ this._oldCenterX = this.centerSpringX.current.value;
18785
+ this._oldCenterY = this.centerSpringY.current.value;
18786
+ this._oldZoom = this.zoomSpring.current.value;
18787
+
18788
+ return changed;
18789
+ },
18046
18790
 
18791
+ _adjustCenterSpringsForZoomPoint: function(zoomSpringHandler) {
18047
18792
  if (this.zoomPoint) {
18048
18793
  var oldZoomPixel = this.pixelFromPoint(this.zoomPoint, true);
18049
- this.zoomSpring.update();
18794
+ zoomSpringHandler();
18050
18795
  var newZoomPixel = this.pixelFromPoint(this.zoomPoint, true);
18051
18796
 
18052
18797
  var deltaZoomPixels = newZoomPixel.minus(oldZoomPixel);
@@ -18060,21 +18805,8 @@ $.Viewport.prototype = {
18060
18805
  this.zoomPoint = null;
18061
18806
  }
18062
18807
  } else {
18063
- this.zoomSpring.update();
18808
+ zoomSpringHandler();
18064
18809
  }
18065
-
18066
- this.centerSpringX.update();
18067
- this.centerSpringY.update();
18068
-
18069
- var changed = this.centerSpringX.current.value !== this._oldCenterX ||
18070
- this.centerSpringY.current.value !== this._oldCenterY ||
18071
- this.zoomSpring.current.value !== this._oldZoom;
18072
-
18073
- this._oldCenterX = this.centerSpringX.current.value;
18074
- this._oldCenterY = this.centerSpringY.current.value;
18075
- this._oldZoom = this.zoomSpring.current.value;
18076
-
18077
- return changed;
18078
18810
  },
18079
18811
 
18080
18812
  /**
@@ -18313,6 +19045,7 @@ $.Viewport.prototype = {
18313
19045
  * in image coordinate system.
18314
19046
  * @param {Number} [pixelWidth] the width in pixel of the rectangle.
18315
19047
  * @param {Number} [pixelHeight] the height in pixel of the rectangle.
19048
+ * @returns {OpenSeadragon.Rect} This image's bounds in viewport coordinates
18316
19049
  */
18317
19050
  imageToViewportRectangle: function(imageX, imageY, pixelWidth, pixelHeight) {
18318
19051
  var rect = imageX;
@@ -18664,11 +19397,18 @@ $.Viewport.prototype = {
18664
19397
  * @param {Number} [options.minPixelRatio] - See {@link OpenSeadragon.Options}.
18665
19398
  * @param {Number} [options.smoothTileEdgesMinZoom] - See {@link OpenSeadragon.Options}.
18666
19399
  * @param {Boolean} [options.iOSDevice] - See {@link OpenSeadragon.Options}.
18667
- * @param {Number} [options.opacity=1] - Opacity the tiled image should be drawn at.
19400
+ * @param {Number} [options.opacity=1] - Set to draw at proportional opacity. If zero, images will not draw.
19401
+ * @param {Boolean} [options.preload=false] - Set true to load even when the image is hidden by zero opacity.
18668
19402
  * @param {String} [options.compositeOperation] - How the image is composited onto other images; see compositeOperation in {@link OpenSeadragon.Options} for possible values.
18669
19403
  * @param {Boolean} [options.debugMode] - See {@link OpenSeadragon.Options}.
18670
19404
  * @param {String|CanvasGradient|CanvasPattern|Function} [options.placeholderFillStyle] - See {@link OpenSeadragon.Options}.
18671
19405
  * @param {String|Boolean} [options.crossOriginPolicy] - See {@link OpenSeadragon.Options}.
19406
+ * @param {Boolean} [options.ajaxWithCredentials] - See {@link OpenSeadragon.Options}.
19407
+ * @param {Boolean} [options.loadTilesWithAjax]
19408
+ * Whether to load tile data using AJAX requests.
19409
+ * Defaults to the setting in {@link OpenSeadragon.Options}.
19410
+ * @param {Object} [options.ajaxHeaders={}]
19411
+ * A set of headers to include when making tile AJAX requests.
18672
19412
  */
18673
19413
  $.TiledImage = function( options ) {
18674
19414
  var _this = this;
@@ -18726,17 +19466,22 @@ $.TiledImage = function( options ) {
18726
19466
  var fitBoundsPlacement = options.fitBoundsPlacement || OpenSeadragon.Placement.CENTER;
18727
19467
  delete options.fitBoundsPlacement;
18728
19468
 
19469
+ var degrees = options.degrees || 0;
19470
+ delete options.degrees;
19471
+
18729
19472
  $.extend( true, this, {
18730
19473
 
18731
19474
  //internal state properties
18732
19475
  viewer: null,
18733
19476
  tilesMatrix: {}, // A '3d' dictionary [level][x][y] --> Tile.
18734
- coverage: {}, // A '3d' dictionary [level][x][y] --> Boolean.
19477
+ coverage: {}, // A '3d' dictionary [level][x][y] --> Boolean; shows what areas have been drawn.
19478
+ loadingCoverage: {}, // A '3d' dictionary [level][x][y] --> Boolean; shows what areas are loaded or are being loaded/blended.
18735
19479
  lastDrawn: [], // An unordered list of Tiles drawn last frame.
18736
19480
  lastResetTime: 0, // Last time for which the tiledImage was reset.
18737
19481
  _midDraw: false, // Is the tiledImage currently updating the viewport?
18738
19482
  _needsDraw: true, // Does the tiledImage need to update the viewport again?
18739
19483
  _hasOpaqueTile: false, // Do we have even one fully opaque tile?
19484
+ _tilesLoading: 0, // The number of pending tile requests.
18740
19485
  //configurable settings
18741
19486
  springStiffness: $.DEFAULT_SETTINGS.springStiffness,
18742
19487
  animationTime: $.DEFAULT_SETTINGS.animationTime,
@@ -18751,12 +19496,18 @@ $.TiledImage = function( options ) {
18751
19496
  iOSDevice: $.DEFAULT_SETTINGS.iOSDevice,
18752
19497
  debugMode: $.DEFAULT_SETTINGS.debugMode,
18753
19498
  crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy,
19499
+ ajaxWithCredentials: $.DEFAULT_SETTINGS.ajaxWithCredentials,
18754
19500
  placeholderFillStyle: $.DEFAULT_SETTINGS.placeholderFillStyle,
18755
19501
  opacity: $.DEFAULT_SETTINGS.opacity,
19502
+ preload: $.DEFAULT_SETTINGS.preload,
18756
19503
  compositeOperation: $.DEFAULT_SETTINGS.compositeOperation
18757
-
18758
19504
  }, options );
18759
19505
 
19506
+ this._preload = this.preload;
19507
+ delete this.preload;
19508
+
19509
+ this._fullyLoaded = false;
19510
+
18760
19511
  this._xSpring = new $.Spring({
18761
19512
  initial: x,
18762
19513
  springStiffness: this.springStiffness,
@@ -18775,6 +19526,12 @@ $.TiledImage = function( options ) {
18775
19526
  animationTime: this.animationTime
18776
19527
  });
18777
19528
 
19529
+ this._degreesSpring = new $.Spring({
19530
+ initial: degrees,
19531
+ springStiffness: this.springStiffness,
19532
+ animationTime: this.animationTime
19533
+ });
19534
+
18778
19535
  this._updateForScale();
18779
19536
 
18780
19537
  if (fitBounds) {
@@ -18813,10 +19570,41 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
18813
19570
  },
18814
19571
 
18815
19572
  /**
18816
- * Clears all tiles and triggers an update on the next call to
18817
- * {@link OpenSeadragon.TiledImage#update}.
19573
+ * @returns {Boolean} Whether all tiles necessary for this TiledImage to draw at the current view have been loaded.
18818
19574
  */
18819
- reset: function() {
19575
+ getFullyLoaded: function() {
19576
+ return this._fullyLoaded;
19577
+ },
19578
+
19579
+ // private
19580
+ _setFullyLoaded: function(flag) {
19581
+ if (flag === this._fullyLoaded) {
19582
+ return;
19583
+ }
19584
+
19585
+ this._fullyLoaded = flag;
19586
+
19587
+ /**
19588
+ * Fired when the TiledImage's "fully loaded" flag (whether all tiles necessary for this TiledImage
19589
+ * to draw at the current view have been loaded) changes.
19590
+ *
19591
+ * @event fully-loaded-change
19592
+ * @memberof OpenSeadragon.TiledImage
19593
+ * @type {object}
19594
+ * @property {Boolean} fullyLoaded - The new "fully loaded" value.
19595
+ * @property {OpenSeadragon.TiledImage} eventSource - A reference to the TiledImage which raised the event.
19596
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
19597
+ */
19598
+ this.raiseEvent('fully-loaded-change', {
19599
+ fullyLoaded: this._fullyLoaded
19600
+ });
19601
+ },
19602
+
19603
+ /**
19604
+ * Clears all tiles and triggers an update on the next call to
19605
+ * {@link OpenSeadragon.TiledImage#update}.
19606
+ */
19607
+ reset: function() {
18820
19608
  this._tileCache.clearTilesFor(this);
18821
19609
  this.lastResetTime = $.now();
18822
19610
  this._needsDraw = true;
@@ -18827,16 +19615,12 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
18827
19615
  * @returns {Boolean} Whether the TiledImage animated.
18828
19616
  */
18829
19617
  update: function() {
18830
- var oldX = this._xSpring.current.value;
18831
- var oldY = this._ySpring.current.value;
18832
- var oldScale = this._scaleSpring.current.value;
18833
-
18834
- this._xSpring.update();
18835
- this._ySpring.update();
18836
- this._scaleSpring.update();
19618
+ var xUpdated = this._xSpring.update();
19619
+ var yUpdated = this._ySpring.update();
19620
+ var scaleUpdated = this._scaleSpring.update();
19621
+ var degreesUpdated = this._degreesSpring.update();
18837
19622
 
18838
- if (this._xSpring.current.value !== oldX || this._ySpring.current.value !== oldY ||
18839
- this._scaleSpring.current.value !== oldScale) {
19623
+ if (xUpdated || yUpdated || scaleUpdated || degreesUpdated) {
18840
19624
  this._updateForScale();
18841
19625
  this._needsDraw = true;
18842
19626
  return true;
@@ -18849,9 +19633,9 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
18849
19633
  * Draws the TiledImage to its Drawer.
18850
19634
  */
18851
19635
  draw: function() {
18852
- if (this.opacity !== 0) {
19636
+ if (this.opacity !== 0 || this._preload) {
18853
19637
  this._midDraw = true;
18854
- updateViewport(this);
19638
+ this._updateViewport();
18855
19639
  this._midDraw = false;
18856
19640
  }
18857
19641
  },
@@ -18864,17 +19648,35 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
18864
19648
  },
18865
19649
 
18866
19650
  /**
19651
+ * Get this TiledImage's bounds in viewport coordinates.
19652
+ * @param {Boolean} [current=false] - Pass true for the current location;
19653
+ * false for target location.
18867
19654
  * @returns {OpenSeadragon.Rect} This TiledImage's bounds in viewport coordinates.
18868
- * @param {Boolean} [current=false] - Pass true for the current location; false for target location.
18869
19655
  */
18870
19656
  getBounds: function(current) {
18871
- if (current) {
18872
- return new $.Rect( this._xSpring.current.value, this._ySpring.current.value,
18873
- this._worldWidthCurrent, this._worldHeightCurrent );
18874
- }
19657
+ return this.getBoundsNoRotate(current)
19658
+ .rotate(this.getRotation(current), this._getRotationPoint(current));
19659
+ },
18875
19660
 
18876
- return new $.Rect( this._xSpring.target.value, this._ySpring.target.value,
18877
- this._worldWidthTarget, this._worldHeightTarget );
19661
+ /**
19662
+ * Get this TiledImage's bounds in viewport coordinates without taking
19663
+ * rotation into account.
19664
+ * @param {Boolean} [current=false] - Pass true for the current location;
19665
+ * false for target location.
19666
+ * @returns {OpenSeadragon.Rect} This TiledImage's bounds in viewport coordinates.
19667
+ */
19668
+ getBoundsNoRotate: function(current) {
19669
+ return current ?
19670
+ new $.Rect(
19671
+ this._xSpring.current.value,
19672
+ this._ySpring.current.value,
19673
+ this._worldWidthCurrent,
19674
+ this._worldHeightCurrent) :
19675
+ new $.Rect(
19676
+ this._xSpring.target.value,
19677
+ this._ySpring.target.value,
19678
+ this._worldWidthTarget,
19679
+ this._worldHeightTarget);
18878
19680
  },
18879
19681
 
18880
19682
  // deprecated
@@ -18890,9 +19692,11 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
18890
19692
  * @returns {$.Rect} The clipped bounds in viewport coordinates.
18891
19693
  */
18892
19694
  getClippedBounds: function(current) {
18893
- var bounds = this.getBounds(current);
19695
+ var bounds = this.getBoundsNoRotate(current);
18894
19696
  if (this._clip) {
18895
- var ratio = this._worldWidthCurrent / this.source.dimensions.x;
19697
+ var worldWidth = current ?
19698
+ this._worldWidthCurrent : this._worldWidthTarget;
19699
+ var ratio = worldWidth / this.source.dimensions.x;
18896
19700
  var clip = this._clip.times(ratio);
18897
19701
  bounds = new $.Rect(
18898
19702
  bounds.x + clip.x,
@@ -18900,7 +19704,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
18900
19704
  clip.width,
18901
19705
  clip.height);
18902
19706
  }
18903
- return bounds;
19707
+ return bounds.rotate(this.getRotation(current), this._getRotationPoint(current));
18904
19708
  },
18905
19709
 
18906
19710
  /**
@@ -18925,21 +19729,24 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
18925
19729
  * @param {Boolean} [current=false] - Pass true to use the current location; false for target location.
18926
19730
  * @return {OpenSeadragon.Point} A point representing the coordinates in the image.
18927
19731
  */
18928
- viewportToImageCoordinates: function( viewerX, viewerY, current ) {
19732
+ viewportToImageCoordinates: function(viewerX, viewerY, current) {
19733
+ var point;
18929
19734
  if (viewerX instanceof $.Point) {
18930
19735
  //they passed a point instead of individual components
18931
19736
  current = viewerY;
18932
- viewerY = viewerX.y;
18933
- viewerX = viewerX.x;
18934
- }
18935
-
18936
- if (current) {
18937
- return this._viewportToImageDelta(viewerX - this._xSpring.current.value,
18938
- viewerY - this._ySpring.current.value);
19737
+ point = viewerX;
19738
+ } else {
19739
+ point = new $.Point(viewerX, viewerY);
18939
19740
  }
18940
19741
 
18941
- return this._viewportToImageDelta(viewerX - this._xSpring.target.value,
18942
- viewerY - this._ySpring.target.value);
19742
+ point = point.rotate(-this.getRotation(current), this._getRotationPoint(current));
19743
+ return current ?
19744
+ this._viewportToImageDelta(
19745
+ point.x - this._xSpring.current.value,
19746
+ point.y - this._ySpring.current.value) :
19747
+ this._viewportToImageDelta(
19748
+ point.x - this._xSpring.target.value,
19749
+ point.y - this._ySpring.target.value);
18943
19750
  },
18944
19751
 
18945
19752
  // private
@@ -18957,7 +19764,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
18957
19764
  * @param {Boolean} [current=false] - Pass true to use the current location; false for target location.
18958
19765
  * @return {OpenSeadragon.Point} A point representing the coordinates in the viewport.
18959
19766
  */
18960
- imageToViewportCoordinates: function( imageX, imageY, current ) {
19767
+ imageToViewportCoordinates: function(imageX, imageY, current) {
18961
19768
  if (imageX instanceof $.Point) {
18962
19769
  //they passed a point instead of individual components
18963
19770
  current = imageY;
@@ -18974,7 +19781,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
18974
19781
  point.y += this._ySpring.target.value;
18975
19782
  }
18976
19783
 
18977
- return point;
19784
+ return point.rotate(this.getRotation(current), this._getRotationPoint(current));
18978
19785
  },
18979
19786
 
18980
19787
  /**
@@ -18988,7 +19795,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
18988
19795
  * @param {Boolean} [current=false] - Pass true to use the current location; false for target location.
18989
19796
  * @return {OpenSeadragon.Rect} A rect representing the coordinates in the viewport.
18990
19797
  */
18991
- imageToViewportRectangle: function( imageX, imageY, pixelWidth, pixelHeight, current ) {
19798
+ imageToViewportRectangle: function(imageX, imageY, pixelWidth, pixelHeight, current) {
18992
19799
  var rect = imageX;
18993
19800
  if (rect instanceof $.Rect) {
18994
19801
  //they passed a rect instead of individual components
@@ -19005,7 +19812,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
19005
19812
  coordA.y,
19006
19813
  coordB.x,
19007
19814
  coordB.y,
19008
- rect.degrees
19815
+ rect.degrees + this.getRotation(current)
19009
19816
  );
19010
19817
  },
19011
19818
 
@@ -19037,7 +19844,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
19037
19844
  coordA.y,
19038
19845
  coordB.x,
19039
19846
  coordB.y,
19040
- rect.degrees
19847
+ rect.degrees - this.getRotation(current)
19041
19848
  );
19042
19849
  },
19043
19850
 
@@ -19085,6 +19892,20 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
19085
19892
  OpenSeadragon.getElementPosition( this.viewer.element ));
19086
19893
  },
19087
19894
 
19895
+ // private
19896
+ // Convert rectangle in viewport coordinates to this tiled image point
19897
+ // coordinates (x in [0, 1] and y in [0, aspectRatio])
19898
+ _viewportToTiledImageRectangle: function(rect) {
19899
+ var scale = this._scaleSpring.current.value;
19900
+ rect = rect.rotate(-this.getRotation(true), this._getRotationPoint(true));
19901
+ return new $.Rect(
19902
+ (rect.x - this._xSpring.current.value) / scale,
19903
+ (rect.y - this._ySpring.current.value) / scale,
19904
+ rect.width / scale,
19905
+ rect.height / scale,
19906
+ rect.degrees);
19907
+ },
19908
+
19088
19909
  /**
19089
19910
  * Convert a viewport zoom to an image zoom.
19090
19911
  * Image zoom: ratio of the original image size to displayed image size.
@@ -19098,7 +19919,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
19098
19919
  viewportToImageZoom: function( viewportZoom ) {
19099
19920
  var ratio = this._scaleSpring.current.value *
19100
19921
  this.viewport._containerInnerSize.x / this.source.dimensions.x;
19101
- return ratio * viewportZoom ;
19922
+ return ratio * viewportZoom;
19102
19923
  },
19103
19924
 
19104
19925
  /**
@@ -19249,6 +20070,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
19249
20070
  * @param {OpenSeadragon.Rect|null} newClip - An area, in image pixels, to clip to
19250
20071
  * (portions of the image outside of this area will not be visible). Only works on
19251
20072
  * browsers that support the HTML5 canvas.
20073
+ * @fires OpenSeadragon.TiledImage.event:clip-change
19252
20074
  */
19253
20075
  setClip: function(newClip) {
19254
20076
  $.console.assert(!newClip || newClip instanceof $.Rect,
@@ -19261,6 +20083,16 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
19261
20083
  }
19262
20084
 
19263
20085
  this._needsDraw = true;
20086
+ /**
20087
+ * Raised when the TiledImage's clip is changed.
20088
+ * @event clip-change
20089
+ * @memberOf OpenSeadragon.TiledImage
20090
+ * @type {object}
20091
+ * @property {OpenSeadragon.TiledImage} eventSource - A reference to the
20092
+ * TiledImage which raised the event.
20093
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
20094
+ */
20095
+ this.raiseEvent('clip-change');
19264
20096
  },
19265
20097
 
19266
20098
  /**
@@ -19272,10 +20104,85 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
19272
20104
 
19273
20105
  /**
19274
20106
  * @param {Number} opacity Opacity the tiled image should be drawn at.
20107
+ * @fires OpenSeadragon.TiledImage.event:opacity-change
19275
20108
  */
19276
20109
  setOpacity: function(opacity) {
20110
+ if (opacity === this.opacity) {
20111
+ return;
20112
+ }
20113
+
19277
20114
  this.opacity = opacity;
19278
20115
  this._needsDraw = true;
20116
+ /**
20117
+ * Raised when the TiledImage's opacity is changed.
20118
+ * @event opacity-change
20119
+ * @memberOf OpenSeadragon.TiledImage
20120
+ * @type {object}
20121
+ * @property {Number} opacity - The new opacity value.
20122
+ * @property {OpenSeadragon.TiledImage} eventSource - A reference to the
20123
+ * TiledImage which raised the event.
20124
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
20125
+ */
20126
+ this.raiseEvent('opacity-change', {
20127
+ opacity: this.opacity
20128
+ });
20129
+ },
20130
+
20131
+ /**
20132
+ * @returns {Boolean} whether the tiledImage can load its tiles even when it has zero opacity.
20133
+ */
20134
+ getPreload: function() {
20135
+ return this._preload;
20136
+ },
20137
+
20138
+ /**
20139
+ * Set true to load even when hidden. Set false to block loading when hidden.
20140
+ */
20141
+ setPreload: function(preload) {
20142
+ this._preload = !!preload;
20143
+ this._needsDraw = true;
20144
+ },
20145
+
20146
+ /**
20147
+ * Get the rotation of this tiled image in degrees.
20148
+ * @param {Boolean} [current=false] True for current rotation, false for target.
20149
+ * @returns {Number} the rotation of this tiled image in degrees.
20150
+ */
20151
+ getRotation: function(current) {
20152
+ return current ?
20153
+ this._degreesSpring.current.value :
20154
+ this._degreesSpring.target.value;
20155
+ },
20156
+
20157
+ /**
20158
+ * Set the current rotation of this tiled image in degrees.
20159
+ * @param {Number} degrees the rotation in degrees.
20160
+ * @param {Boolean} [immediately=false] Whether to animate to the new angle
20161
+ * or rotate immediately.
20162
+ * @fires OpenSeadragon.TiledImage.event:bounds-change
20163
+ */
20164
+ setRotation: function(degrees, immediately) {
20165
+ if (this._degreesSpring.target.value === degrees &&
20166
+ this._degreesSpring.isAtTargetValue()) {
20167
+ return;
20168
+ }
20169
+ if (immediately) {
20170
+ this._degreesSpring.resetTo(degrees);
20171
+ } else {
20172
+ this._degreesSpring.springTo(degrees);
20173
+ }
20174
+ this._needsDraw = true;
20175
+ this._raiseBoundsChange();
20176
+ },
20177
+
20178
+ /**
20179
+ * @private
20180
+ * Get the point around which this tiled image is rotated
20181
+ * @param {Boolean} current True for current rotation point, false for target.
20182
+ * @returns {OpenSeadragon.Point}
20183
+ */
20184
+ _getRotationPoint: function(current) {
20185
+ return this.getBoundsNoRotate(current).getCenter();
19279
20186
  },
19280
20187
 
19281
20188
  /**
@@ -19287,10 +20194,28 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
19287
20194
 
19288
20195
  /**
19289
20196
  * @param {String} compositeOperation the tiled image should be drawn with this globalCompositeOperation.
20197
+ * @fires OpenSeadragon.TiledImage.event:composite-operation-change
19290
20198
  */
19291
20199
  setCompositeOperation: function(compositeOperation) {
20200
+ if (compositeOperation === this.compositeOperation) {
20201
+ return;
20202
+ }
20203
+
19292
20204
  this.compositeOperation = compositeOperation;
19293
20205
  this._needsDraw = true;
20206
+ /**
20207
+ * Raised when the TiledImage's opacity is changed.
20208
+ * @event composite-operation-change
20209
+ * @memberOf OpenSeadragon.TiledImage
20210
+ * @type {object}
20211
+ * @property {String} compositeOperation - The new compositeOperation value.
20212
+ * @property {OpenSeadragon.TiledImage} eventSource - A reference to the
20213
+ * TiledImage which raised the event.
20214
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
20215
+ */
20216
+ this.raiseEvent('composite-operation-change', {
20217
+ compositeOperation: this.compositeOperation
20218
+ });
19294
20219
  },
19295
20220
 
19296
20221
  // private
@@ -19336,7 +20261,8 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
19336
20261
  * @event bounds-change
19337
20262
  * @memberOf OpenSeadragon.TiledImage
19338
20263
  * @type {object}
19339
- * @property {OpenSeadragon.World} eventSource - A reference to the TiledImage which raised the event.
20264
+ * @property {OpenSeadragon.TiledImage} eventSource - A reference to the
20265
+ * TiledImage which raised the event.
19340
20266
  * @property {?Object} userData - Arbitrary subscriber-defined object.
19341
20267
  */
19342
20268
  this.raiseEvent('bounds-change');
@@ -19345,184 +20271,208 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
19345
20271
  // private
19346
20272
  _isBottomItem: function() {
19347
20273
  return this.viewer.world.getItemAt(0) === this;
19348
- }
19349
- });
19350
-
19351
- /**
19352
- * @private
19353
- * @inner
19354
- * Pretty much every other line in this needs to be documented so it's clear
19355
- * how each piece of this routine contributes to the drawing process. That's
19356
- * why there are so many TODO's inside this function.
19357
- */
19358
- function updateViewport( tiledImage ) {
19359
-
19360
- tiledImage._needsDraw = false;
20274
+ },
19361
20275
 
19362
- var tile,
19363
- level,
19364
- best = null,
19365
- haveDrawn = false,
19366
- currentTime = $.now(),
19367
- viewportBounds = tiledImage.viewport.getBoundsWithMargins( true ),
19368
- zeroRatioC = tiledImage.viewport.deltaPixelsFromPointsNoRotate(
19369
- tiledImage.source.getPixelRatio( 0 ),
19370
- true
19371
- ).x * tiledImage._scaleSpring.current.value,
19372
- lowestLevel = Math.max(
19373
- tiledImage.source.minLevel,
19374
- Math.floor(
19375
- Math.log( tiledImage.minZoomImageRatio ) /
19376
- Math.log( 2 )
19377
- )
19378
- ),
19379
- highestLevel = Math.min(
19380
- Math.abs(tiledImage.source.maxLevel),
20276
+ // private
20277
+ _getLevelsInterval: function() {
20278
+ var lowestLevel = Math.max(
20279
+ this.source.minLevel,
20280
+ Math.floor(Math.log(this.minZoomImageRatio) / Math.log(2))
20281
+ );
20282
+ var currentZeroRatio = this.viewport.deltaPixelsFromPointsNoRotate(
20283
+ this.source.getPixelRatio(0), true).x *
20284
+ this._scaleSpring.current.value;
20285
+ var highestLevel = Math.min(
20286
+ Math.abs(this.source.maxLevel),
19381
20287
  Math.abs(Math.floor(
19382
- Math.log( zeroRatioC / tiledImage.minPixelRatio ) /
19383
- Math.log( 2 )
20288
+ Math.log(currentZeroRatio / this.minPixelRatio) / Math.log(2)
19384
20289
  ))
19385
- ),
19386
- renderPixelRatioC,
19387
- renderPixelRatioT,
19388
- zeroRatioT,
19389
- optimalRatio,
19390
- levelOpacity,
19391
- levelVisibility;
19392
-
19393
- // Reset tile's internal drawn state
19394
- while (tiledImage.lastDrawn.length > 0) {
19395
- tile = tiledImage.lastDrawn.pop();
19396
- tile.beingDrawn = false;
19397
- }
19398
-
19399
- if (!tiledImage.wrapHorizontal && !tiledImage.wrapVertical) {
19400
- var tiledImageBounds = tiledImage.getClippedBounds(true);
19401
- var intersection = viewportBounds.intersection(tiledImageBounds);
19402
- if (intersection === null) {
19403
- return;
19404
- }
19405
- viewportBounds = intersection;
19406
- }
19407
- viewportBounds = viewportBounds.getBoundingBox();
19408
- viewportBounds.x -= tiledImage._xSpring.current.value;
19409
- viewportBounds.y -= tiledImage._ySpring.current.value;
19410
-
19411
- var viewportTL = viewportBounds.getTopLeft();
19412
- var viewportBR = viewportBounds.getBottomRight();
19413
-
19414
- //Don't draw if completely outside of the viewport
19415
- if ( !tiledImage.wrapHorizontal && (viewportBR.x < 0 || viewportTL.x > tiledImage._worldWidthCurrent ) ) {
19416
- return;
19417
- }
19418
-
19419
- if ( !tiledImage.wrapVertical && ( viewportBR.y < 0 || viewportTL.y > tiledImage._worldHeightCurrent ) ) {
19420
- return;
19421
- }
19422
-
19423
- // Calculate viewport rect / bounds
19424
- if ( !tiledImage.wrapHorizontal ) {
19425
- viewportTL.x = Math.max( viewportTL.x, 0 );
19426
- viewportBR.x = Math.min( viewportBR.x, tiledImage._worldWidthCurrent );
19427
- }
20290
+ );
19428
20291
 
19429
- if ( !tiledImage.wrapVertical ) {
19430
- viewportTL.y = Math.max( viewportTL.y, 0 );
19431
- viewportBR.y = Math.min( viewportBR.y, tiledImage._worldHeightCurrent );
19432
- }
20292
+ // Calculations for the interval of levels to draw
20293
+ // can return invalid intervals; fix that here if necessary
20294
+ lowestLevel = Math.min(lowestLevel, highestLevel);
20295
+ return {
20296
+ lowestLevel: lowestLevel,
20297
+ highestLevel: highestLevel
20298
+ };
20299
+ },
19433
20300
 
19434
- // Calculations for the interval of levels to draw
19435
- // (above in initial var statement)
19436
- // can return invalid intervals; fix that here if necessary
19437
- lowestLevel = Math.min( lowestLevel, highestLevel );
20301
+ /**
20302
+ * @private
20303
+ * @inner
20304
+ * Pretty much every other line in this needs to be documented so it's clear
20305
+ * how each piece of this routine contributes to the drawing process. That's
20306
+ * why there are so many TODO's inside this function.
20307
+ */
20308
+ _updateViewport: function() {
20309
+ this._needsDraw = false;
20310
+ this._tilesLoading = 0;
20311
+ this.loadingCoverage = {};
19438
20312
 
19439
- // Update any level that will be drawn
19440
- var drawLevel; // FIXME: drawLevel should have a more explanatory name
19441
- for ( level = highestLevel; level >= lowestLevel; level-- ) {
19442
- drawLevel = false;
20313
+ // Reset tile's internal drawn state
20314
+ while (this.lastDrawn.length > 0) {
20315
+ var tile = this.lastDrawn.pop();
20316
+ tile.beingDrawn = false;
20317
+ }
19443
20318
 
19444
- //Avoid calculations for draw if we have already drawn this
19445
- renderPixelRatioC = tiledImage.viewport.deltaPixelsFromPointsNoRotate(
19446
- tiledImage.source.getPixelRatio( level ),
19447
- true
19448
- ).x * tiledImage._scaleSpring.current.value;
20319
+ var viewport = this.viewport;
20320
+ var drawArea = this._viewportToTiledImageRectangle(
20321
+ viewport.getBoundsWithMargins(true));
19449
20322
 
19450
- if ( ( !haveDrawn && renderPixelRatioC >= tiledImage.minPixelRatio ) ||
19451
- ( level == lowestLevel ) ) {
19452
- drawLevel = true;
19453
- haveDrawn = true;
19454
- } else if ( !haveDrawn ) {
19455
- continue;
20323
+ if (!this.wrapHorizontal && !this.wrapVertical) {
20324
+ var tiledImageBounds = this._viewportToTiledImageRectangle(
20325
+ this.getClippedBounds(true));
20326
+ drawArea = drawArea.intersection(tiledImageBounds);
20327
+ if (drawArea === null) {
20328
+ return;
20329
+ }
19456
20330
  }
19457
20331
 
19458
- //Perform calculations for draw if we haven't drawn this
19459
- renderPixelRatioT = tiledImage.viewport.deltaPixelsFromPointsNoRotate(
19460
- tiledImage.source.getPixelRatio( level ),
19461
- false
19462
- ).x * tiledImage._scaleSpring.current.value;
20332
+ var levelsInterval = this._getLevelsInterval();
20333
+ var lowestLevel = levelsInterval.lowestLevel;
20334
+ var highestLevel = levelsInterval.highestLevel;
20335
+ var bestTile = null;
20336
+ var haveDrawn = false;
20337
+ var currentTime = $.now();
19463
20338
 
19464
- zeroRatioT = tiledImage.viewport.deltaPixelsFromPointsNoRotate(
19465
- tiledImage.source.getPixelRatio(
19466
- Math.max(
19467
- tiledImage.source.getClosestLevel( tiledImage.viewport.containerSize ) - 1,
19468
- 0
19469
- )
19470
- ),
19471
- false
19472
- ).x * tiledImage._scaleSpring.current.value;
20339
+ // Update any level that will be drawn
20340
+ for (var level = highestLevel; level >= lowestLevel; level--) {
20341
+ var drawLevel = false;
19473
20342
 
19474
- optimalRatio = tiledImage.immediateRender ?
19475
- 1 :
19476
- zeroRatioT;
20343
+ //Avoid calculations for draw if we have already drawn this
20344
+ var currentRenderPixelRatio = viewport.deltaPixelsFromPointsNoRotate(
20345
+ this.source.getPixelRatio(level),
20346
+ true
20347
+ ).x * this._scaleSpring.current.value;
19477
20348
 
19478
- levelOpacity = Math.min( 1, ( renderPixelRatioC - 0.5 ) / 0.5 );
20349
+ if (level === lowestLevel ||
20350
+ (!haveDrawn && currentRenderPixelRatio >= this.minPixelRatio)) {
20351
+ drawLevel = true;
20352
+ haveDrawn = true;
20353
+ } else if (!haveDrawn) {
20354
+ continue;
20355
+ }
19479
20356
 
19480
- levelVisibility = optimalRatio / Math.abs(
19481
- optimalRatio - renderPixelRatioT
19482
- );
20357
+ //Perform calculations for draw if we haven't drawn this
20358
+ var targetRenderPixelRatio = viewport.deltaPixelsFromPointsNoRotate(
20359
+ this.source.getPixelRatio(level),
20360
+ false
20361
+ ).x * this._scaleSpring.current.value;
20362
+
20363
+ var targetZeroRatio = viewport.deltaPixelsFromPointsNoRotate(
20364
+ this.source.getPixelRatio(
20365
+ Math.max(
20366
+ this.source.getClosestLevel(),
20367
+ 0
20368
+ )
20369
+ ),
20370
+ false
20371
+ ).x * this._scaleSpring.current.value;
20372
+
20373
+ var optimalRatio = this.immediateRender ? 1 : targetZeroRatio;
20374
+ var levelOpacity = Math.min(1, (currentRenderPixelRatio - 0.5) / 0.5);
20375
+ var levelVisibility = optimalRatio / Math.abs(
20376
+ optimalRatio - targetRenderPixelRatio
20377
+ );
19483
20378
 
19484
- // Update the level and keep track of 'best' tile to load
19485
- best = updateLevel(
19486
- tiledImage,
19487
- haveDrawn,
19488
- drawLevel,
19489
- level,
19490
- levelOpacity,
19491
- levelVisibility,
19492
- viewportTL,
19493
- viewportBR,
19494
- currentTime,
19495
- best
19496
- );
20379
+ // Update the level and keep track of 'best' tile to load
20380
+ bestTile = updateLevel(
20381
+ this,
20382
+ haveDrawn,
20383
+ drawLevel,
20384
+ level,
20385
+ levelOpacity,
20386
+ levelVisibility,
20387
+ drawArea,
20388
+ currentTime,
20389
+ bestTile
20390
+ );
19497
20391
 
19498
- // Stop the loop if lower-res tiles would all be covered by
19499
- // already drawn tiles
19500
- if ( providesCoverage( tiledImage.coverage, level ) ) {
19501
- break;
20392
+ // Stop the loop if lower-res tiles would all be covered by
20393
+ // already drawn tiles
20394
+ if (providesCoverage(this.coverage, level)) {
20395
+ break;
20396
+ }
19502
20397
  }
19503
- }
19504
20398
 
19505
- // Perform the actual drawing
19506
- drawTiles( tiledImage, tiledImage.lastDrawn );
20399
+ // Perform the actual drawing
20400
+ drawTiles(this, this.lastDrawn);
19507
20401
 
19508
- // Load the new 'best' tile
19509
- if (best && !best.context2D) {
19510
- loadTile( tiledImage, best, currentTime );
19511
- }
20402
+ // Load the new 'best' tile
20403
+ if (bestTile && !bestTile.context2D) {
20404
+ loadTile(this, bestTile, currentTime);
20405
+ this._needsDraw = true;
20406
+ this._setFullyLoaded(false);
20407
+ } else {
20408
+ this._setFullyLoaded(this._tilesLoading === 0);
20409
+ }
20410
+ },
19512
20411
 
19513
- }
20412
+ // private
20413
+ _getCornerTiles: function(level, topLeftBound, bottomRightBound) {
20414
+ var leftX;
20415
+ var rightX;
20416
+ if (this.wrapHorizontal) {
20417
+ leftX = $.positiveModulo(topLeftBound.x, 1);
20418
+ rightX = $.positiveModulo(bottomRightBound.x, 1);
20419
+ } else {
20420
+ leftX = Math.max(0, topLeftBound.x);
20421
+ rightX = Math.min(1, bottomRightBound.x);
20422
+ }
20423
+ var topY;
20424
+ var bottomY;
20425
+ var aspectRatio = 1 / this.source.aspectRatio;
20426
+ if (this.wrapVertical) {
20427
+ topY = $.positiveModulo(topLeftBound.y, aspectRatio);
20428
+ bottomY = $.positiveModulo(bottomRightBound.y, aspectRatio);
20429
+ } else {
20430
+ topY = Math.max(0, topLeftBound.y);
20431
+ bottomY = Math.min(aspectRatio, bottomRightBound.y);
20432
+ }
19514
20433
 
20434
+ var topLeftTile = this.source.getTileAtPoint(level, new $.Point(leftX, topY));
20435
+ var bottomRightTile = this.source.getTileAtPoint(level, new $.Point(rightX, bottomY));
20436
+ var numTiles = this.source.getNumTiles(level);
19515
20437
 
19516
- function updateLevel( tiledImage, haveDrawn, drawLevel, level, levelOpacity, levelVisibility, viewportTL, viewportBR, currentTime, best ){
20438
+ if (this.wrapHorizontal) {
20439
+ topLeftTile.x += numTiles.x * Math.floor(topLeftBound.x);
20440
+ bottomRightTile.x += numTiles.x * Math.floor(bottomRightBound.x);
20441
+ }
20442
+ if (this.wrapVertical) {
20443
+ topLeftTile.y += numTiles.y * Math.floor(topLeftBound.y / aspectRatio);
20444
+ bottomRightTile.y += numTiles.y * Math.floor(bottomRightBound.y / aspectRatio);
20445
+ }
19517
20446
 
19518
- var x, y,
19519
- tileTL,
19520
- tileBR,
19521
- numberOfTiles,
19522
- viewportCenter = tiledImage.viewport.pixelFromPoint( tiledImage.viewport.getCenter() );
20447
+ return {
20448
+ topLeft: topLeftTile,
20449
+ bottomRight: bottomRightTile,
20450
+ };
20451
+ }
20452
+ });
19523
20453
 
20454
+ /**
20455
+ * @private
20456
+ * @inner
20457
+ * Updates all tiles at a given resolution level.
20458
+ * @param {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.
20459
+ * @param {Boolean} haveDrawn
20460
+ * @param {Boolean} drawLevel
20461
+ * @param {Number} level
20462
+ * @param {Number} levelOpacity
20463
+ * @param {Number} levelVisibility
20464
+ * @param {OpenSeadragon.Point} viewportTL - The index of the most top-left visible tile.
20465
+ * @param {OpenSeadragon.Point} viewportBR - The index of the most bottom-right visible tile.
20466
+ * @param {Number} currentTime
20467
+ * @param {OpenSeadragon.Tile} best - The current "best" tile to draw.
20468
+ */
20469
+ function updateLevel(tiledImage, haveDrawn, drawLevel, level, levelOpacity,
20470
+ levelVisibility, drawArea, currentTime, best) {
19524
20471
 
19525
- if( tiledImage.viewer ){
20472
+ var topLeftBound = drawArea.getBoundingBox().getTopLeft();
20473
+ var bottomRightBound = drawArea.getBoundingBox().getBottomRight();
20474
+
20475
+ if (tiledImage.viewer) {
19526
20476
  /**
19527
20477
  * <em>- Needs documentation -</em>
19528
20478
  *
@@ -19535,41 +20485,50 @@ function updateLevel( tiledImage, haveDrawn, drawLevel, level, levelOpacity, lev
19535
20485
  * @property {Object} level
19536
20486
  * @property {Object} opacity
19537
20487
  * @property {Object} visibility
19538
- * @property {Object} topleft
19539
- * @property {Object} bottomright
20488
+ * @property {OpenSeadragon.Rect} drawArea
20489
+ * @property {Object} topleft deprecated, use drawArea instead
20490
+ * @property {Object} bottomright deprecated, use drawArea instead
19540
20491
  * @property {Object} currenttime
19541
20492
  * @property {Object} best
19542
20493
  * @property {?Object} userData - Arbitrary subscriber-defined object.
19543
20494
  */
19544
- tiledImage.viewer.raiseEvent( 'update-level', {
20495
+ tiledImage.viewer.raiseEvent('update-level', {
19545
20496
  tiledImage: tiledImage,
19546
20497
  havedrawn: haveDrawn,
19547
20498
  level: level,
19548
20499
  opacity: levelOpacity,
19549
20500
  visibility: levelVisibility,
19550
- topleft: viewportTL,
19551
- bottomright: viewportBR,
20501
+ drawArea: drawArea,
20502
+ topleft: topLeftBound,
20503
+ bottomright: bottomRightBound,
19552
20504
  currenttime: currentTime,
19553
20505
  best: best
19554
20506
  });
19555
20507
  }
19556
20508
 
19557
- //OK, a new drawing so do your calculations
19558
- tileTL = tiledImage.source.getTileAtPoint( level, viewportTL.divide( tiledImage._scaleSpring.current.value ));
19559
- tileBR = tiledImage.source.getTileAtPoint( level, viewportBR.divide( tiledImage._scaleSpring.current.value ));
19560
- numberOfTiles = tiledImage.source.getNumTiles( level );
19561
-
19562
- resetCoverage( tiledImage.coverage, level );
19563
-
19564
- if ( !tiledImage.wrapHorizontal ) {
19565
- tileBR.x = Math.min( tileBR.x, numberOfTiles.x - 1 );
19566
- }
19567
- if ( !tiledImage.wrapVertical ) {
19568
- tileBR.y = Math.min( tileBR.y, numberOfTiles.y - 1 );
19569
- }
20509
+ resetCoverage(tiledImage.coverage, level);
20510
+ resetCoverage(tiledImage.loadingCoverage, level);
19570
20511
 
19571
- for ( x = tileTL.x; x <= tileBR.x; x++ ) {
19572
- for ( y = tileTL.y; y <= tileBR.y; y++ ) {
20512
+ //OK, a new drawing so do your calculations
20513
+ var cornerTiles = tiledImage._getCornerTiles(level, topLeftBound, bottomRightBound);
20514
+ var topLeftTile = cornerTiles.topLeft;
20515
+ var bottomRightTile = cornerTiles.bottomRight;
20516
+ var numberOfTiles = tiledImage.source.getNumTiles(level);
20517
+
20518
+ var viewportCenter = tiledImage.viewport.pixelFromPoint(
20519
+ tiledImage.viewport.getCenter());
20520
+ for (var x = topLeftTile.x; x <= bottomRightTile.x; x++) {
20521
+ for (var y = topLeftTile.y; y <= bottomRightTile.y; y++) {
20522
+
20523
+ // Optimisation disabled with wrapping because getTileBounds does not
20524
+ // work correctly with x and y outside of the number of tiles
20525
+ if (!tiledImage.wrapHorizontal && !tiledImage.wrapVertical) {
20526
+ var tileBounds = tiledImage.source.getTileBounds(level, x, y);
20527
+ if (drawArea.intersection(tileBounds) === null) {
20528
+ // This tile is outside of the viewport, no need to draw it
20529
+ continue;
20530
+ }
20531
+ }
19573
20532
 
19574
20533
  best = updateTile(
19575
20534
  tiledImage,
@@ -19591,11 +20550,29 @@ function updateLevel( tiledImage, haveDrawn, drawLevel, level, levelOpacity, lev
19591
20550
  return best;
19592
20551
  }
19593
20552
 
19594
- function updateTile( tiledImage, drawLevel, haveDrawn, x, y, level, levelOpacity, levelVisibility, viewportCenter, numberOfTiles, currentTime, best){
20553
+ /**
20554
+ * @private
20555
+ * @inner
20556
+ * Update a single tile at a particular resolution level.
20557
+ * @param {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.
20558
+ * @param {Boolean} haveDrawn
20559
+ * @param {Boolean} drawLevel
20560
+ * @param {Number} x
20561
+ * @param {Number} y
20562
+ * @param {Number} level
20563
+ * @param {Number} levelOpacity
20564
+ * @param {Number} levelVisibility
20565
+ * @param {OpenSeadragon.Point} viewportCenter
20566
+ * @param {Number} numberOfTiles
20567
+ * @param {Number} currentTime
20568
+ * @param {OpenSeadragon.Tile} best - The current "best" tile to draw.
20569
+ */
20570
+ function updateTile( tiledImage, haveDrawn, drawLevel, x, y, level, levelOpacity, levelVisibility, viewportCenter, numberOfTiles, currentTime, best){
19595
20571
 
19596
20572
  var tile = getTile(
19597
20573
  x, y,
19598
20574
  level,
20575
+ tiledImage,
19599
20576
  tiledImage.source,
19600
20577
  tiledImage.tilesMatrix,
19601
20578
  currentTime,
@@ -19625,6 +20602,9 @@ function updateTile( tiledImage, drawLevel, haveDrawn, x, y, level, levelOpacity
19625
20602
 
19626
20603
  setCoverage( tiledImage.coverage, level, x, y, false );
19627
20604
 
20605
+ var loadingCoverage = tile.loaded || tile.loading || isCovered(tiledImage.loadingCoverage, level, x, y);
20606
+ setCoverage(tiledImage.loadingCoverage, level, x, y, loadingCoverage);
20607
+
19628
20608
  if ( !tile.exists ) {
19629
20609
  return best;
19630
20610
  }
@@ -19654,7 +20634,7 @@ function updateTile( tiledImage, drawLevel, haveDrawn, x, y, level, levelOpacity
19654
20634
  if (tile.context2D) {
19655
20635
  setTileLoaded(tiledImage, tile);
19656
20636
  } else {
19657
- var imageRecord = tiledImage._tileCache.getImageRecord(tile.url);
20637
+ var imageRecord = tiledImage._tileCache.getImageRecord(tile.cacheKey);
19658
20638
  if (imageRecord) {
19659
20639
  var image = imageRecord.getImage();
19660
20640
  setTileLoaded(tiledImage, tile, image);
@@ -19677,20 +20657,47 @@ function updateTile( tiledImage, drawLevel, haveDrawn, x, y, level, levelOpacity
19677
20657
  }
19678
20658
  } else if ( tile.loading ) {
19679
20659
  // the tile is already in the download queue
19680
- // thanks josh1093 for finally translating this typo
19681
- } else {
20660
+ tiledImage._tilesLoading++;
20661
+ } else if (!loadingCoverage) {
19682
20662
  best = compareTiles( best, tile );
19683
20663
  }
19684
20664
 
19685
20665
  return best;
19686
20666
  }
19687
20667
 
19688
- function getTile( x, y, level, tileSource, tilesMatrix, time, numTiles, worldWidth, worldHeight ) {
20668
+ /**
20669
+ * @private
20670
+ * @inner
20671
+ * Obtains a tile at the given location.
20672
+ * @param {Number} x
20673
+ * @param {Number} y
20674
+ * @param {Number} level
20675
+ * @param {OpenSeadragon.TiledImage} tiledImage
20676
+ * @param {OpenSeadragon.TileSource} tileSource
20677
+ * @param {Object} tilesMatrix - A '3d' dictionary [level][x][y] --> Tile.
20678
+ * @param {Number} time
20679
+ * @param {Number} numTiles
20680
+ * @param {Number} worldWidth
20681
+ * @param {Number} worldHeight
20682
+ * @returns {OpenSeadragon.Tile}
20683
+ */
20684
+ function getTile(
20685
+ x, y,
20686
+ level,
20687
+ tiledImage,
20688
+ tileSource,
20689
+ tilesMatrix,
20690
+ time,
20691
+ numTiles,
20692
+ worldWidth,
20693
+ worldHeight
20694
+ ) {
19689
20695
  var xMod,
19690
20696
  yMod,
19691
20697
  bounds,
19692
20698
  exists,
19693
20699
  url,
20700
+ ajaxHeaders,
19694
20701
  context2D,
19695
20702
  tile;
19696
20703
 
@@ -19707,6 +20714,18 @@ function getTile( x, y, level, tileSource, tilesMatrix, time, numTiles, worldWid
19707
20714
  bounds = tileSource.getTileBounds( level, xMod, yMod );
19708
20715
  exists = tileSource.tileExists( level, xMod, yMod );
19709
20716
  url = tileSource.getTileUrl( level, xMod, yMod );
20717
+
20718
+ // Headers are only applicable if loadTilesWithAjax is set
20719
+ if (tiledImage.loadTilesWithAjax) {
20720
+ ajaxHeaders = tileSource.getTileAjaxHeaders( level, xMod, yMod );
20721
+ // Combine tile AJAX headers with tiled image AJAX headers (if applicable)
20722
+ if ($.isPlainObject(tiledImage.ajaxHeaders)) {
20723
+ ajaxHeaders = $.extend({}, tiledImage.ajaxHeaders, ajaxHeaders);
20724
+ }
20725
+ } else {
20726
+ ajaxHeaders = null;
20727
+ }
20728
+
19710
20729
  context2D = tileSource.getContext2D ?
19711
20730
  tileSource.getContext2D(level, xMod, yMod) : undefined;
19712
20731
 
@@ -19720,7 +20739,9 @@ function getTile( x, y, level, tileSource, tilesMatrix, time, numTiles, worldWid
19720
20739
  bounds,
19721
20740
  exists,
19722
20741
  url,
19723
- context2D
20742
+ context2D,
20743
+ tiledImage.loadTilesWithAjax,
20744
+ ajaxHeaders
19724
20745
  );
19725
20746
  }
19726
20747
 
@@ -19730,13 +20751,24 @@ function getTile( x, y, level, tileSource, tilesMatrix, time, numTiles, worldWid
19730
20751
  return tile;
19731
20752
  }
19732
20753
 
20754
+ /**
20755
+ * @private
20756
+ * @inner
20757
+ * Dispatch a job to the ImageLoader to load the Image for a Tile.
20758
+ * @param {OpenSeadragon.TiledImage} tiledImage
20759
+ * @param {OpenSeadragon.Tile} tile
20760
+ * @param {Number} time
20761
+ */
19733
20762
  function loadTile( tiledImage, tile, time ) {
19734
20763
  tile.loading = true;
19735
20764
  tiledImage._imageLoader.addJob({
19736
20765
  src: tile.url,
20766
+ loadWithAjax: tile.loadWithAjax,
20767
+ ajaxHeaders: tile.ajaxHeaders,
19737
20768
  crossOriginPolicy: tiledImage.crossOriginPolicy,
19738
- callback: function( image, errorMsg ){
19739
- onTileLoad( tiledImage, tile, time, image, errorMsg );
20769
+ ajaxWithCredentials: tiledImage.ajaxWithCredentials,
20770
+ callback: function( image, errorMsg, tileRequest ){
20771
+ onTileLoad( tiledImage, tile, time, image, errorMsg, tileRequest );
19740
20772
  },
19741
20773
  abort: function() {
19742
20774
  tile.loading = false;
@@ -19744,7 +20776,18 @@ function loadTile( tiledImage, tile, time ) {
19744
20776
  });
19745
20777
  }
19746
20778
 
19747
- function onTileLoad( tiledImage, tile, time, image, errorMsg ) {
20779
+ /**
20780
+ * @private
20781
+ * @inner
20782
+ * Callback fired when a Tile's Image finished downloading.
20783
+ * @param {OpenSeadragon.TiledImage} tiledImage
20784
+ * @param {OpenSeadragon.Tile} tile
20785
+ * @param {Number} time
20786
+ * @param {Image} image
20787
+ * @param {String} errorMsg
20788
+ * @param {XMLHttpRequest} tileRequest
20789
+ */
20790
+ function onTileLoad( tiledImage, tile, time, image, errorMsg, tileRequest ) {
19748
20791
  if ( !image ) {
19749
20792
  $.console.log( "Tile %s failed to load: %s - error: %s", tile, tile.url, errorMsg );
19750
20793
  /**
@@ -19757,8 +20800,15 @@ function onTileLoad( tiledImage, tile, time, image, errorMsg ) {
19757
20800
  * @property {OpenSeadragon.TiledImage} tiledImage - The tiled image the tile belongs to.
19758
20801
  * @property {number} time - The time in milliseconds when the tile load began.
19759
20802
  * @property {string} message - The error message.
20803
+ * @property {XMLHttpRequest} tileRequest - The XMLHttpRequest used to load the tile if available.
19760
20804
  */
19761
- tiledImage.viewer.raiseEvent("tile-load-failed", {tile: tile, tiledImage: tiledImage, time: time, message: errorMsg});
20805
+ tiledImage.viewer.raiseEvent("tile-load-failed", {
20806
+ tile: tile,
20807
+ tiledImage: tiledImage,
20808
+ time: time,
20809
+ message: errorMsg,
20810
+ tileRequest: tileRequest
20811
+ });
19762
20812
  tile.loading = false;
19763
20813
  tile.exists = false;
19764
20814
  return;
@@ -19771,9 +20821,8 @@ function onTileLoad( tiledImage, tile, time, image, errorMsg ) {
19771
20821
  }
19772
20822
 
19773
20823
  var finish = function() {
19774
- var cutoff = Math.ceil( Math.log(
19775
- tiledImage.source.getTileWidth(tile.level) ) / Math.log( 2 ) );
19776
- setTileLoaded(tiledImage, tile, image, cutoff);
20824
+ var cutoff = tiledImage.source.getClosestLevel();
20825
+ setTileLoaded(tiledImage, tile, image, cutoff, tileRequest);
19777
20826
  };
19778
20827
 
19779
20828
  // Check if we're mid-update; this can happen on IE8 because image load events for
@@ -19786,7 +20835,15 @@ function onTileLoad( tiledImage, tile, time, image, errorMsg ) {
19786
20835
  }
19787
20836
  }
19788
20837
 
19789
- function setTileLoaded(tiledImage, tile, image, cutoff) {
20838
+ /**
20839
+ * @private
20840
+ * @inner
20841
+ * @param {OpenSeadragon.TiledImage} tiledImage
20842
+ * @param {OpenSeadragon.Tile} tile
20843
+ * @param {Image} image
20844
+ * @param {Number} cutoff
20845
+ */
20846
+ function setTileLoaded(tiledImage, tile, image, cutoff, tileRequest) {
19790
20847
  var increment = 0;
19791
20848
 
19792
20849
  function getCompletionCallback() {
@@ -19821,6 +20878,7 @@ function setTileLoaded(tiledImage, tile, image, cutoff) {
19821
20878
  * @property {Image} image - The image of the tile.
19822
20879
  * @property {OpenSeadragon.TiledImage} tiledImage - The tiled image of the loaded tile.
19823
20880
  * @property {OpenSeadragon.Tile} tile - The tile which has been loaded.
20881
+ * @property {XMLHttpRequest} tiledImage - The AJAX request that loaded this tile (if applicable).
19824
20882
  * @property {function} getCompletionCallback - A function giving a callback to call
19825
20883
  * when the asynchronous processing of the image is done. The image will be
19826
20884
  * marked as entirely loaded when the callback has been called once for each
@@ -19829,6 +20887,7 @@ function setTileLoaded(tiledImage, tile, image, cutoff) {
19829
20887
  tiledImage.viewer.raiseEvent("tile-loaded", {
19830
20888
  tile: tile,
19831
20889
  tiledImage: tiledImage,
20890
+ tileRequest: tileRequest,
19832
20891
  image: image,
19833
20892
  getCompletionCallback: getCompletionCallback
19834
20893
  });
@@ -19836,6 +20895,16 @@ function setTileLoaded(tiledImage, tile, image, cutoff) {
19836
20895
  getCompletionCallback()();
19837
20896
  }
19838
20897
 
20898
+ /**
20899
+ * @private
20900
+ * @inner
20901
+ * @param {OpenSeadragon.Tile} tile
20902
+ * @param {Boolean} overlap
20903
+ * @param {OpenSeadragon.Viewport} viewport
20904
+ * @param {OpenSeadragon.Point} viewportCenter
20905
+ * @param {Number} levelVisibility
20906
+ * @param {OpenSeadragon.TiledImage} tiledImage
20907
+ */
19839
20908
  function positionTile( tile, overlap, viewport, viewportCenter, levelVisibility, tiledImage ){
19840
20909
  var boundsTL = tile.bounds.getTopLeft();
19841
20910
 
@@ -19849,12 +20918,12 @@ function positionTile( tile, overlap, viewport, viewportCenter, levelVisibility,
19849
20918
  boundsSize.x *= tiledImage._scaleSpring.current.value;
19850
20919
  boundsSize.y *= tiledImage._scaleSpring.current.value;
19851
20920
 
19852
- var positionC = viewport.pixelFromPointNoRotate(boundsTL, true),
19853
- positionT = viewport.pixelFromPointNoRotate(boundsTL, false),
19854
- sizeC = viewport.deltaPixelsFromPointsNoRotate(boundsSize, true),
19855
- sizeT = viewport.deltaPixelsFromPointsNoRotate(boundsSize, false),
19856
- tileCenter = positionT.plus( sizeT.divide( 2 ) ),
19857
- tileDistance = viewportCenter.distanceTo( tileCenter );
20921
+ var positionC = viewport.pixelFromPointNoRotate(boundsTL, true),
20922
+ positionT = viewport.pixelFromPointNoRotate(boundsTL, false),
20923
+ sizeC = viewport.deltaPixelsFromPointsNoRotate(boundsSize, true),
20924
+ sizeT = viewport.deltaPixelsFromPointsNoRotate(boundsSize, false),
20925
+ tileCenter = positionT.plus( sizeT.divide( 2 ) ),
20926
+ tileSquaredDistance = viewportCenter.squaredDistanceTo( tileCenter );
19858
20927
 
19859
20928
  if ( !overlap ) {
19860
20929
  sizeC = sizeC.plus( new $.Point( 1, 1 ) );
@@ -19862,11 +20931,27 @@ function positionTile( tile, overlap, viewport, viewportCenter, levelVisibility,
19862
20931
 
19863
20932
  tile.position = positionC;
19864
20933
  tile.size = sizeC;
19865
- tile.distance = tileDistance;
20934
+ tile.squaredDistance = tileSquaredDistance;
19866
20935
  tile.visibility = levelVisibility;
19867
20936
  }
19868
20937
 
19869
-
20938
+ /**
20939
+ * @private
20940
+ * @inner
20941
+ * Updates the opacity of a tile according to the time it has been on screen
20942
+ * to perform a fade-in.
20943
+ * Updates coverage once a tile is fully opaque.
20944
+ * Returns whether the fade-in has completed.
20945
+ *
20946
+ * @param {OpenSeadragon.TiledImage} tiledImage
20947
+ * @param {OpenSeadragon.Tile} tile
20948
+ * @param {Number} x
20949
+ * @param {Number} y
20950
+ * @param {Number} level
20951
+ * @param {Number} levelOpacity
20952
+ * @param {Number} currentTime
20953
+ * @returns {Boolean}
20954
+ */
19870
20955
  function blendTile( tiledImage, tile, x, y, level, levelOpacity, currentTime ){
19871
20956
  var blendTimeMillis = 1000 * tiledImage.blendTime,
19872
20957
  deltaTime,
@@ -19907,6 +20992,12 @@ function blendTile( tiledImage, tile, x, y, level, levelOpacity, currentTime ){
19907
20992
  * Note that out-of-bounds tiles provide coverage in this sense, since
19908
20993
  * there's no content that they would need to cover. Tiles at non-existent
19909
20994
  * levels that are within the image bounds, however, do not.
20995
+ *
20996
+ * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean.
20997
+ * @param {Number} level - The resolution level of the tile.
20998
+ * @param {Number} x - The X position of the tile.
20999
+ * @param {Number} y - The Y position of the tile.
21000
+ * @returns {Boolean}
19910
21001
  */
19911
21002
  function providesCoverage( coverage, level, x, y ) {
19912
21003
  var rows,
@@ -19946,6 +21037,12 @@ function providesCoverage( coverage, level, x, y ) {
19946
21037
  * Returns true if the given tile is completely covered by higher-level
19947
21038
  * tiles of higher resolution representing the same content. If neither x
19948
21039
  * nor y is given, returns true if the entire visible level is covered.
21040
+ *
21041
+ * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean.
21042
+ * @param {Number} level - The resolution level of the tile.
21043
+ * @param {Number} x - The X position of the tile.
21044
+ * @param {Number} y - The Y position of the tile.
21045
+ * @returns {Boolean}
19949
21046
  */
19950
21047
  function isCovered( coverage, level, x, y ) {
19951
21048
  if ( x === undefined || y === undefined ) {
@@ -19964,6 +21061,12 @@ function isCovered( coverage, level, x, y ) {
19964
21061
  * @private
19965
21062
  * @inner
19966
21063
  * Sets whether the given tile provides coverage or not.
21064
+ *
21065
+ * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean.
21066
+ * @param {Number} level - The resolution level of the tile.
21067
+ * @param {Number} x - The X position of the tile.
21068
+ * @param {Number} y - The Y position of the tile.
21069
+ * @param {Boolean} covers - Whether the tile provides coverage.
19967
21070
  */
19968
21071
  function setCoverage( coverage, level, x, y, covers ) {
19969
21072
  if ( !coverage[ level ] ) {
@@ -19987,6 +21090,9 @@ function setCoverage( coverage, level, x, y, covers ) {
19987
21090
  * Resets coverage information for the given level. This should be called
19988
21091
  * after every draw routine. Note that at the beginning of the next draw
19989
21092
  * routine, coverage for every visible tile should be explicitly set.
21093
+ *
21094
+ * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean.
21095
+ * @param {Number} level - The resolution level of tiles to completely reset.
19990
21096
  */
19991
21097
  function resetCoverage( coverage, level ) {
19992
21098
  coverage[ level ] = {};
@@ -19997,6 +21103,10 @@ function resetCoverage( coverage, level ) {
19997
21103
  * @inner
19998
21104
  * Determines whether the 'last best' tile for the area is better than the
19999
21105
  * tile in question.
21106
+ *
21107
+ * @param {OpenSeadragon.Tile} previousBest
21108
+ * @param {OpenSeadragon.Tile} tile
21109
+ * @returns {OpenSeadragon.Tile} The new best tile.
20000
21110
  */
20001
21111
  function compareTiles( previousBest, tile ) {
20002
21112
  if ( !previousBest ) {
@@ -20006,7 +21116,7 @@ function compareTiles( previousBest, tile ) {
20006
21116
  if ( tile.visibility > previousBest.visibility ) {
20007
21117
  return tile;
20008
21118
  } else if ( tile.visibility == previousBest.visibility ) {
20009
- if ( tile.distance < previousBest.distance ) {
21119
+ if ( tile.squaredDistance < previousBest.squaredDistance ) {
20010
21120
  return tile;
20011
21121
  }
20012
21122
  }
@@ -20014,8 +21124,15 @@ function compareTiles( previousBest, tile ) {
20014
21124
  return previousBest;
20015
21125
  }
20016
21126
 
21127
+ /**
21128
+ * @private
21129
+ * @inner
21130
+ * Draws a TiledImage.
21131
+ * @param {OpenSeadragon.TiledImage} tiledImage
21132
+ * @param {OpenSeadragon.Tile[]} lastDrawn - An unordered list of Tiles drawn last frame.
21133
+ */
20017
21134
  function drawTiles( tiledImage, lastDrawn ) {
20018
- if (lastDrawn.length === 0) {
21135
+ if (tiledImage.opacity === 0 || lastDrawn.length === 0) {
20019
21136
  return;
20020
21137
  }
20021
21138
  var tile = lastDrawn[0];
@@ -20030,7 +21147,12 @@ function drawTiles( tiledImage, lastDrawn ) {
20030
21147
 
20031
21148
  var zoom = tiledImage.viewport.getZoom(true);
20032
21149
  var imageZoom = tiledImage.viewportToImageZoom(zoom);
20033
- if (imageZoom > tiledImage.smoothTileEdgesMinZoom && !tiledImage.iOSDevice) {
21150
+
21151
+ if (lastDrawn.length > 1 &&
21152
+ imageZoom > tiledImage.smoothTileEdgesMinZoom &&
21153
+ !tiledImage.iOSDevice &&
21154
+ tiledImage.getRotation(true) % 360 === 0 && // TODO: support tile edge smoothing with tiled image rotation.
21155
+ $.supportsCanvas) {
20034
21156
  // When zoomed in a lot (>100%) the tile edges are visible.
20035
21157
  // So we have to composite them at ~100% and scale them up together.
20036
21158
  // Note: Disabled on iOS devices per default as it causes a native crash
@@ -20054,10 +21176,23 @@ function drawTiles( tiledImage, lastDrawn ) {
20054
21176
  tiledImage._drawer._clear(true, bounds);
20055
21177
  }
20056
21178
 
20057
- // When scaling, we must rotate only when blending the sketch canvas to avoid
20058
- // interpolation
20059
- if (tiledImage.viewport.degrees !== 0 && !sketchScale) {
20060
- tiledImage._drawer._offsetForRotation(tiledImage.viewport.degrees, useSketch);
21179
+ // When scaling, we must rotate only when blending the sketch canvas to
21180
+ // avoid interpolation
21181
+ if (!sketchScale) {
21182
+ if (tiledImage.viewport.degrees !== 0) {
21183
+ tiledImage._drawer._offsetForRotation({
21184
+ degrees: tiledImage.viewport.degrees,
21185
+ useSketch: useSketch
21186
+ });
21187
+ }
21188
+ if (tiledImage.getRotation(true) % 360 !== 0) {
21189
+ tiledImage._drawer._offsetForRotation({
21190
+ degrees: tiledImage.getRotation(true),
21191
+ point: tiledImage.viewport.pixelFromPointNoRotate(
21192
+ tiledImage._getRotationPoint(true), true),
21193
+ useSketch: useSketch
21194
+ });
21195
+ }
20061
21196
  }
20062
21197
 
20063
21198
  var usedClip = false;
@@ -20065,6 +21200,7 @@ function drawTiles( tiledImage, lastDrawn ) {
20065
21200
  tiledImage._drawer.saveContext(useSketch);
20066
21201
 
20067
21202
  var box = tiledImage.imageToViewportRectangle(tiledImage._clip, true);
21203
+ box = box.rotate(-tiledImage.getRotation(true), tiledImage._getRotationPoint(true));
20068
21204
  var clipRect = tiledImage._drawer.viewportToDrawerRectangle(box);
20069
21205
  if (sketchScale) {
20070
21206
  clipRect = clipRect.times(sketchScale);
@@ -20125,14 +21261,31 @@ function drawTiles( tiledImage, lastDrawn ) {
20125
21261
  tiledImage._drawer.restoreContext( useSketch );
20126
21262
  }
20127
21263
 
20128
- if (tiledImage.viewport.degrees !== 0 && !sketchScale) {
20129
- tiledImage._drawer._restoreRotationChanges(useSketch);
21264
+ if (!sketchScale) {
21265
+ if (tiledImage.getRotation(true) % 360 !== 0) {
21266
+ tiledImage._drawer._restoreRotationChanges(useSketch);
21267
+ }
21268
+ if (tiledImage.viewport.degrees !== 0) {
21269
+ tiledImage._drawer._restoreRotationChanges(useSketch);
21270
+ }
20130
21271
  }
20131
21272
 
20132
21273
  if (useSketch) {
20133
- var offsetForRotation = tiledImage.viewport.degrees !== 0 && sketchScale;
20134
- if (offsetForRotation) {
20135
- tiledImage._drawer._offsetForRotation(tiledImage.viewport.degrees, false);
21274
+ if (sketchScale) {
21275
+ if (tiledImage.viewport.degrees !== 0) {
21276
+ tiledImage._drawer._offsetForRotation({
21277
+ degrees: tiledImage.viewport.degrees,
21278
+ useSketch: false
21279
+ });
21280
+ }
21281
+ if (tiledImage.getRotation(true) % 360 !== 0) {
21282
+ tiledImage._drawer._offsetForRotation({
21283
+ degrees: tiledImage.getRotation(true),
21284
+ point: tiledImage.viewport.pixelFromPointNoRotate(
21285
+ tiledImage._getRotationPoint(true), true),
21286
+ useSketch: false
21287
+ });
21288
+ }
20136
21289
  }
20137
21290
  tiledImage._drawer.blendSketch({
20138
21291
  opacity: tiledImage.opacity,
@@ -20141,19 +21294,32 @@ function drawTiles( tiledImage, lastDrawn ) {
20141
21294
  compositeOperation: tiledImage.compositeOperation,
20142
21295
  bounds: bounds
20143
21296
  });
20144
- if (offsetForRotation) {
20145
- tiledImage._drawer._restoreRotationChanges(false);
21297
+ if (sketchScale) {
21298
+ if (tiledImage.getRotation(true) % 360 !== 0) {
21299
+ tiledImage._drawer._restoreRotationChanges(false);
21300
+ }
21301
+ if (tiledImage.viewport.degrees !== 0) {
21302
+ tiledImage._drawer._restoreRotationChanges(false);
21303
+ }
20146
21304
  }
20147
21305
  }
20148
21306
  drawDebugInfo( tiledImage, lastDrawn );
20149
21307
  }
20150
21308
 
21309
+ /**
21310
+ * @private
21311
+ * @inner
21312
+ * Draws special debug information for a TiledImage if in debug mode.
21313
+ * @param {OpenSeadragon.TiledImage} tiledImage
21314
+ * @param {OpenSeadragon.Tile[]} lastDrawn - An unordered list of Tiles drawn last frame.
21315
+ */
20151
21316
  function drawDebugInfo( tiledImage, lastDrawn ) {
20152
21317
  if( tiledImage.debugMode ) {
20153
21318
  for ( var i = lastDrawn.length - 1; i >= 0; i-- ) {
20154
21319
  var tile = lastDrawn[ i ];
20155
21320
  try {
20156
- tiledImage._drawer.drawDebugInfo( tile, lastDrawn.length, i );
21321
+ tiledImage._drawer.drawDebugInfo(
21322
+ tile, lastDrawn.length, i, tiledImage);
20157
21323
  } catch(e) {
20158
21324
  $.console.error(e);
20159
21325
  }
@@ -20305,6 +21471,7 @@ $.TileCache.prototype = {
20305
21471
  * may temporarily surpass that number, but should eventually come back down to the max specified.
20306
21472
  * @param {Object} options - Tile info.
20307
21473
  * @param {OpenSeadragon.Tile} options.tile - The tile to cache.
21474
+ * @param {String} options.tile.cacheKey - The unique key used to identify this tile in the cache.
20308
21475
  * @param {Image} options.image - The image of the tile to cache.
20309
21476
  * @param {OpenSeadragon.TiledImage} options.tiledImage - The TiledImage that owns that tile.
20310
21477
  * @param {Number} [options.cutoff=0] - If adding this tile goes over the cache max count, this
@@ -20314,16 +21481,16 @@ $.TileCache.prototype = {
20314
21481
  cacheTile: function( options ) {
20315
21482
  $.console.assert( options, "[TileCache.cacheTile] options is required" );
20316
21483
  $.console.assert( options.tile, "[TileCache.cacheTile] options.tile is required" );
20317
- $.console.assert( options.tile.url, "[TileCache.cacheTile] options.tile.url is required" );
21484
+ $.console.assert( options.tile.cacheKey, "[TileCache.cacheTile] options.tile.cacheKey is required" );
20318
21485
  $.console.assert( options.tiledImage, "[TileCache.cacheTile] options.tiledImage is required" );
20319
21486
 
20320
21487
  var cutoff = options.cutoff || 0;
20321
21488
  var insertionIndex = this._tilesLoaded.length;
20322
21489
 
20323
- var imageRecord = this._imagesLoaded[options.tile.url];
21490
+ var imageRecord = this._imagesLoaded[options.tile.cacheKey];
20324
21491
  if (!imageRecord) {
20325
21492
  $.console.assert( options.image, "[TileCache.cacheTile] options.image is required to create an ImageRecord" );
20326
- imageRecord = this._imagesLoaded[options.tile.url] = new ImageRecord({
21493
+ imageRecord = this._imagesLoaded[options.tile.cacheKey] = new ImageRecord({
20327
21494
  image: options.image
20328
21495
  });
20329
21496
 
@@ -20397,9 +21564,9 @@ $.TileCache.prototype = {
20397
21564
  },
20398
21565
 
20399
21566
  // private
20400
- getImageRecord: function(url) {
20401
- $.console.assert(url, '[TileCache.getImageRecord] url is required');
20402
- return this._imagesLoaded[url];
21567
+ getImageRecord: function(cacheKey) {
21568
+ $.console.assert(cacheKey, '[TileCache.getImageRecord] cacheKey is required');
21569
+ return this._imagesLoaded[cacheKey];
20403
21570
  },
20404
21571
 
20405
21572
  // private
@@ -20411,11 +21578,11 @@ $.TileCache.prototype = {
20411
21578
  tile.unload();
20412
21579
  tile.cacheImageRecord = null;
20413
21580
 
20414
- var imageRecord = this._imagesLoaded[tile.url];
21581
+ var imageRecord = this._imagesLoaded[tile.cacheKey];
20415
21582
  imageRecord.removeTile(tile);
20416
21583
  if (!imageRecord.getTileCount()) {
20417
21584
  imageRecord.destroy();
20418
- delete this._imagesLoaded[tile.url];
21585
+ delete this._imagesLoaded[tile.cacheKey];
20419
21586
  this._imagesLoadedCount--;
20420
21587
  }
20421
21588
 
@@ -20533,6 +21700,7 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
20533
21700
  this._needsDraw = true;
20534
21701
 
20535
21702
  item.addHandler('bounds-change', this._delegatedFigureSizes);
21703
+ item.addHandler('clip-change', this._delegatedFigureSizes);
20536
21704
 
20537
21705
  /**
20538
21706
  * Raised when an item is added to the World.
@@ -20633,6 +21801,7 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
20633
21801
  }
20634
21802
 
20635
21803
  item.removeHandler('bounds-change', this._delegatedFigureSizes);
21804
+ item.removeHandler('clip-change', this._delegatedFigureSizes);
20636
21805
  item.destroy();
20637
21806
  this._items.splice( index, 1 );
20638
21807
  this._figureSizes();
@@ -20649,9 +21818,11 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
20649
21818
  // We need to make sure any pending images are canceled so the world items don't get messed up
20650
21819
  this.viewer._cancelPendingImages();
20651
21820
  var item;
20652
- for (var i = 0; i < this._items.length; i++) {
21821
+ var i;
21822
+ for (i = 0; i < this._items.length; i++) {
20653
21823
  item = this._items[i];
20654
21824
  item.removeHandler('bounds-change', this._delegatedFigureSizes);
21825
+ item.removeHandler('clip-change', this._delegatedFigureSizes);
20655
21826
  item.destroy();
20656
21827
  }
20657
21828
 
@@ -20822,7 +21993,7 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
20822
21993
  var item = this._items[0];
20823
21994
  var bounds = item.getBounds();
20824
21995
  this._contentFactor = item.getContentSize().x / bounds.width;
20825
- var clippedBounds = item.getClippedBounds();
21996
+ var clippedBounds = item.getClippedBounds().getBoundingBox();
20826
21997
  var left = clippedBounds.x;
20827
21998
  var top = clippedBounds.y;
20828
21999
  var right = clippedBounds.x + clippedBounds.width;
@@ -20832,7 +22003,7 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
20832
22003
  bounds = item.getBounds();
20833
22004
  this._contentFactor = Math.max(this._contentFactor,
20834
22005
  item.getContentSize().x / bounds.width);
20835
- clippedBounds = item.getClippedBounds();
22006
+ clippedBounds = item.getClippedBounds().getBoundingBox();
20836
22007
  left = Math.min(left, clippedBounds.x);
20837
22008
  top = Math.min(top, clippedBounds.y);
20838
22009
  right = Math.max(right, clippedBounds.x + clippedBounds.width);