openseadragon 0.3.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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);