Compare commits

...

617 commits

Author SHA1 Message Date
Ian Gilman
99fcb408fe Changelog for #2702 2025-04-02 09:36:33 -07:00
Ian Gilman
479ef0ec82
Merge pull request #2702 from achu1998/expose-nav-button
Exposed Zoom In and Out methods for end users
2025-04-02 09:33:44 -07:00
achu1998
9e01a1e18b Made changes according to review. 2025-04-01 11:04:04 +10:30
achu1998
2e3f0923f5 Explicitly bind 'this' to the Viewer instance 2025-03-29 14:17:54 +10:30
achu1998
3550dfafd3 Refactored code to minimise the changes required 2025-03-29 13:47:02 +10:30
achu1998
12de02938a Exposed Zoom In and Out methods for end users 2025-03-29 13:33:25 +10:30
Ian Gilman
8f093f54df Changelog and minor fixes for #2698 2025-03-26 09:44:50 -07:00
Ian Gilman
6bdd186219
Merge pull request #2698 from lokaesshwar/duplicate_ids_overlay_html_2682
Duplicate ids overlay html 2682
2025-03-26 09:36:17 -07:00
lokaesshwar
c54c292873 Fix #2682: Ensure unique ID for overlay wrapper to prevent duplicate IDs 2025-03-26 19:38:02 +05:30
lokaesshwar
4c7d9a2ffa Resolved Duplicate IDs in overlay HTML 2025-03-14 00:35:22 +05:30
Ian Gilman
595066abd1 Changelog for #2690 2025-03-12 09:32:08 -07:00
Ian Gilman
b4e14d4804
Merge pull request #2690 from MichaelWGibson/load-area-fixes
Make load code more robust to out of image panning
2025-03-12 09:31:08 -07:00
Michael Gibson
be87b47469 Make load code more robust to out of image panning 2025-03-10 11:19:44 -06:00
Ian Gilman
62fa378f51 Changelog for #2686 2025-03-10 10:04:26 -07:00
Ian Gilman
c386eca94d
Merge pull request #2686 from MichaelWGibson/animation-dest-tile-loading
During animations, load tiles in the destination region
2025-03-10 09:59:12 -07:00
Michael Gibson
8c6d1f4f88 Update documentation to match new flag behavior 2025-03-07 21:21:40 -07:00
Michael Gibson
3cb5152905 Change flag name 2025-03-07 21:18:42 -07:00
Michael Gibson
5cc76737b4 Fix flag type 2025-02-28 15:37:13 -07:00
Michael Gibson
f312bae614 Document flag 2025-02-28 15:35:25 -07:00
Michael Gibson
97d388be7d Add loadTilesOnAnimationPath flag 2025-02-28 11:30:30 -07:00
Michael Gibson
fd27327737 Factor out common traversal code 2025-02-28 11:10:29 -07:00
Michael Gibson
5c60f669ec Clean up 2025-02-28 09:33:49 -07:00
Michael Gibson
6ceac75b7a During animations, load tiles in the destination region 2025-02-28 09:17:41 -07:00
Ian Gilman
2387c3afd5 Stopped sponsor images from wrapping 2025-01-31 09:51:18 -08:00
Ian Gilman
5184ed6c9f Changelog for #2676 2025-01-31 09:40:00 -08:00
Ian Gilman
b22b689dae
Merge pull request #2676 from bennlich/patch-1
Update viewer.addOverlay doc
2025-01-31 09:38:38 -08:00
Benny Lichtner
138215e22f
delete trailing whitepsace 2025-01-30 20:12:05 +01:00
Ian Gilman
22e766bd1a Trimming sponsor logos 2025-01-30 09:37:34 -08:00
Benny Lichtner
337af91373
Update viewer.addOverlay doc
Mention the preserveOverlays option
2025-01-30 14:45:30 +01:00
Ian Gilman
ced1bde338 Changelog for #2643 2025-01-23 09:45:25 -08:00
Ian Gilman
dbc72d4d98
Merge pull request #2643 from RationAI/fix/fallback-canvas-drawer
Flexible support for drawers to modify cache
2025-01-23 09:37:52 -08:00
Aiosa
994f0e25a4 Fix renaming also in tests. 2025-01-14 12:26:28 +01:00
Aiosa
71c3c8437a Rename new drawer base api to reflect that it deals with internal cache. 2025-01-14 12:20:39 +01:00
Aiosa
6e81bedc39 Improve docs, change order of calls in prepareForRendering for better code stability. 2025-01-14 12:14:28 +01:00
Ian Gilman
a9562fbb64
Improved sponsor logo styling 2025-01-13 09:57:54 -08:00
Ian Gilman
b4c7e75109 Merge branch 'master' of github.com:openseadragon/openseadragon 2025-01-13 09:47:20 -08:00
Ian Gilman
301191a0b8 Actually adding the logos 2025-01-13 09:46:57 -08:00
Ian Gilman
edb422007c
Merge pull request #2661 from openseadragon/ian
Additional sponsor logos
2025-01-13 09:45:09 -08:00
Ian Gilman
f8aad5f237 Additional sponsor logos 2025-01-13 09:44:18 -08:00
Aiosa
1286fa4549 Test drawers collide with drawer render testing - remove after cache tests. 2025-01-10 22:13:16 +01:00
Aiosa
03b7c5b9a6 Update tests, fix async cache bug - first convert to supported format, then process. Record drawer ID in the internal cache type. 2025-01-10 22:05:16 +01:00
Ian Gilman
7dfde9da3a Changelog for #2644 2025-01-10 09:53:09 -08:00
Ian Gilman
ffe12888cf
Merge pull request #2644 from RationAI/feat/import
Import Openseadragon Dynamically
2025-01-10 09:51:54 -08:00
Ian Gilman
8b30e7a307 Changelog for #2657 2025-01-10 09:34:25 -08:00
Ian Gilman
306adb9b6a
Merge pull request #2657 from cff29546/master
use unique hash for MouseTracker
2025-01-10 09:32:12 -08:00
Aiosa
226a44c498 Fix docs. 2025-01-10 14:57:41 +01:00
Aiosa
b1feb367b8 Fix typo. 2025-01-10 14:49:59 +01:00
Aiosa
cb06a5c0fb Debug fallback to canvas drawer, small bugfixes, add support for drawer cleanup in cache. 2025-01-10 14:46:52 +01:00
Ian Gilman
9b8b4c2728 Changelog for #2652 2025-01-09 10:06:05 -08:00
Ian Gilman
d6740dab25 Merge branch 'master' of github.com:openseadragon/openseadragon 2025-01-09 10:04:58 -08:00
Ian Gilman
003dd3a9da
Merge pull request #2652 from pearcetm/fix-2651
Mark tiled image as needing update when opacity changes
2025-01-09 10:02:35 -08:00
Ian Gilman
a1fa790e19 Changelog for #2649 2025-01-09 09:45:33 -08:00
Ian Gilman
3aaa8fa721
Merge pull request #2649 from Seafret/master
Fixed issue with MouseTracker where hasGestureHandlers and hasScrollHandler values were not getting updated upon dynamically adding/removing handlers.
2025-01-09 09:42:51 -08:00
Min Xiang
39671f4d78 use unique hash for MouseTracker 2025-01-09 07:53:05 +08:00
Aiosa
426700b1c6 Temporarily disable tests - discussing design. 2025-01-07 19:57:37 +01:00
Aiosa
6315662078 DrawerBase API for internal cache. 2025-01-07 19:56:21 +01:00
Ian Gilman
e193557e56 Changelog for #2636 2025-01-06 09:45:36 -08:00
Ian Gilman
b8b6a9656a
Merge pull request #2636 from pcram-techcyte/v5.0.1-hasTransparency-fix
Typo in hasTransparency check in webgldrawer
2025-01-06 09:42:55 -08:00
Tom
f5f9eab200 mark tiled image as needing an update when opacity changes 2025-01-06 09:40:23 -05:00
Tom
3f22b6aa3a mark tiled image as needing an update when opacity changes 2025-01-06 09:39:28 -05:00
Jessica R.
641e085259
Updated MouseTracker's hasGestureHandlers and hasScrollHandler to be getters 2025-01-02 23:07:13 -07:00
Jessica R.
9527f15f52
Changed hasScrollHandler to be a function to better work with dynamically added handlers
Previously, we were setting hasScrollHandler once in the constructor and never re-assigning it after the initial creation. Changing hasScrollHandler to a function better accommodates dynamically added/removed handlers. See previous commit for change to hasGestureHandlers for further details and reasoning.
2025-01-01 22:45:12 -07:00
Jessica R.
865c3884b2
Changed hasGestureHandlers to be a function to better work with dynamically added handlers
Previously, we were setting hasGestureHandlers once in the constructor and never re-assigning it after the initial creation. This resulted in MouseTrackers created without gesture handlers never updating its hasGestureHandlers values, even when gesture handlers were dynamically added after creation. By changing this to a function, our MouseTrackers' hasGestureHandlers value should always be in the proper state, even if handlers are dynamically created or removed.
2025-01-01 22:38:48 -07:00
Ian Gilman
56d0d982da Changelog for #2640 2024-12-20 09:32:24 -08:00
Ian Gilman
58f4e6a36e
Merge pull request #2640 from harshkg23/master
Refactor assessNumericValue for improved clarity and debugging
2024-12-20 09:28:11 -08:00
Paul Cram
ef0f14a03d Revert "Add useTwoPassRendering as an option to the webgl drawer"
This reverts commit 55f53e8369.
2024-12-19 15:32:00 -05:00
Harsh Kumar Gupta
d31562e34f Fix the function arguments for the required file 2024-12-20 00:31:40 +05:30
Aiosa
ad943e5472 feat: ability to attach OSD to window reference if available 2024-12-19 16:22:50 +01:00
Aiosa
4c2b0715af fix: typo 2024-12-19 16:20:50 +01:00
Aiosa
a5c569ab5d fix: reference to the cpuData on webgldrawer 2024-12-19 16:11:53 +01:00
Harsh Kumar Gupta
8fdd639bf0 Rename parameters in helper functions for clarity and consistency 2024-12-19 16:30:04 +05:30
Harsh Kumar Gupta
a0fcfc20ad Correct function names in test helpers for consistency 2024-12-19 16:12:27 +05:30
Harsh Kumar Gupta
15627ee18e Bump version to 5.0.1 and update argument names for clarity in helper functions 2024-12-19 15:52:14 +05:30
Harsh Kumar Gupta
33c3d4b380 Corrected arguments name and fixed the identation issue 2024-12-19 15:24:34 +05:30
Harsh Kumar Gupta
74ffb66f40 Refactor test helper functions for improved readability and consistency 2024-12-18 00:53:44 +05:30
Paul Cram
55f53e8369 Add useTwoPassRendering as an option to the webgl drawer 2024-12-17 10:36:31 -05:00
pcram-techcyte
acb7563257
Merge branch 'openseadragon:master' into v5.0.1-hasTransparency-fix 2024-12-17 09:01:36 -05:00
Ian Gilman
c06e719198 Changelog for #2407 2024-12-16 09:30:47 -08:00
Ian Gilman
64bb7e25c7
Merge pull request #2407 from RationAI/cache-overhaul-reviewed
Cache Overhaul for OpenSeadragon (reviewed).
2024-12-16 09:26:13 -08:00
Aiosa
c392f2d205 Change location of logos, add link 2024-12-12 19:28:58 +01:00
pcram-techcyte
45bd3f48f9
Typo in hasTransparency check 2024-12-10 09:12:39 -05:00
Aiosa
bee3c243fb Delete demos with old plugins. 2024-12-10 14:38:34 +01:00
Aiosa
563c7674c7 Add sponsors section to README. 2024-12-10 14:37:30 +01:00
Aiosa
fec033f9d2 Clear up few todos, introduce tile processing promise to await single tiles. 2024-12-10 14:16:19 +01:00
Aiosa
e12a349f52 Merge branch 'temporary-merge' into cache-overhaul-reviewed 2024-12-10 13:51:56 +01:00
Aiosa
bee5cb2471 Merge conflicts from master. 2024-12-10 13:50:41 +01:00
Ian Gilman
c8c7b481b8 Started on version 6 2024-12-09 09:37:19 -08:00
Ian Gilman
480de92d0a Version 5.0.1 2024-12-09 09:31:27 -08:00
Ian Gilman
e9dd9e9dfc Changelog for #2627 2024-12-06 09:32:44 -08:00
Ian Gilman
07502e6d58
Merge pull request #2627 from pearcetm/pixel-density-change
Fix #2623
2024-12-06 09:30:41 -08:00
Tom
1843bbd89c update docs 2024-12-04 16:42:25 -05:00
Tom
a5987db8d5 fix #2623 2024-12-04 16:41:24 -05:00
Aiosa
039ffbd37a Fix typo 2024-12-04 09:16:04 +01:00
Aiosa
1a7a5ee1d6 Respect tainted() when uploading a texture. 2024-12-04 09:08:00 +01:00
Ian Gilman
9b96707dec Changelog for #2620 2024-12-02 09:51:24 -08:00
Ian Gilman
3ce307f309
Merge pull request #2620 from sbarex/master
Update webgldrawer.js
2024-12-02 09:48:24 -08:00
Aiosa
d6bb8d3bd1 Fix typo. Remove tileSource prototype extension - adds implementation for methods that throw exceptions. 2024-11-30 10:03:39 +01:00
Aiosa
271f437568 Force prototype extension on inlined tile source. Ensure zombies are processed with invalidation before restored in the system. WebGL drawer can use canvas drawer. Add changelog updates. 2024-11-30 09:52:27 +01:00
sbarex
5438fd65e1
Update webgldrawer.js
Fixed a bug that caused a completely white image to be rendered when two-pass rendering mode was requested (required when multiple images, using transparency, or in debug mode).
2024-11-26 23:38:13 +01:00
Aiosa
85e8b381b8 Add cache clear on viewer.clear(). Add tile and viewer state checks to invalidation. Fix IIF test (timer is now built-in). 2024-11-26 15:24:51 +01:00
Aiosa
ef7628f098 Fix bug: webgl 'manual instanced rendering' return -> continue, do not count as drawn index. 2024-11-26 14:08:32 +01:00
Aiosa
17f13885c7 Add back overlapFraction logic for webgl drawer. 2024-11-26 00:48:43 +01:00
Aiosa
6b4c0f873a Add maxTilesPerFrame degradation, fix tests. 2024-11-22 13:03:16 +01:00
Aiosa
8b16628950 Write documentation fo cache/data overhaul. Rename url type to be more explicit. 2024-11-22 12:43:14 +01:00
Ian Gilman
04211fbb27 Changelog for #2619 2024-11-21 09:58:12 -08:00
Ian Gilman
3885fa7bfc
Merge pull request #2619 from jbakarich/fix/2612/incorrect-display-region-rotation
Remove displayRegion transform
2024-11-21 09:53:08 -08:00
Aiosa
f03f2a5d31 Add fixes for working smoothing in the webgl renderer. 2024-11-21 16:51:38 +01:00
Aiosa
ce4b16616d Merge branch 'openseadragon-master' into cache-overhaul-reviewed 2024-11-21 16:13:36 +01:00
Aiosa
af9bf9e07f Resolve conflicts, implement setImageSmoothingEnabled on webgl drawer by invalidation routine. 2024-11-21 16:12:52 +01:00
Aiosa
1851405fcf Remove warning from cache attaching if the tile has already reference. 2024-11-21 15:48:11 +01:00
Aiosa
0bc7deccd7 Fixed tests & bugs for the new design of invalidation inside events. 2024-11-21 15:35:27 +01:00
Jacob Bakarich
2dc0e29647 Remove displayRegion transform
ref #2612

- Remove display region transform in `setFlip()` as it is not needed for flipping and was overriding existing rotation transforms.
2024-11-20 14:07:26 -07:00
Ian Gilman
0e3abe16f4 Changelog for #2615 2024-11-15 09:32:07 -08:00
Ian Gilman
cd9e4e8c4e
Merge pull request #2615 from pearcetm/webgl-glnearest
Add support for imageSmoothingEnabled to WebGLDrawer
2024-11-15 09:27:18 -08:00
Tom
02805226d4 only redraw after setImageSmoothingEnabled() when changed 2024-11-14 13:13:13 -05:00
Tom
4163a46a24 Update docs 2024-11-13 16:49:54 -05:00
Tom
16dbfc62be Add support for imageSmoothingEnabled to WebGLDrawer 2024-11-13 16:43:46 -05:00
Aiosa
e24f7d1358 Update demo plugins to the new api design 2024-11-13 15:04:09 +01:00
Aiosa
541fe2e4df Redesign working cache: it is now owned by the event, not a tile. Tests are not yet updated. 2024-11-13 14:35:50 +01:00
Aiosa
e059b8982e Add try-catch for a plugin 2024-11-07 12:22:18 +01:00
Aiosa
3b1b2d6d23 Fix: reference the correct drawer in invalidation routine. 2024-11-07 12:01:02 +01:00
Aiosa
535507568f Fix docs syntax. 2024-11-05 12:06:59 +01:00
Aiosa
cc7474ec9b
Merge branch 'openseadragon:master' into cache-overhaul-reviewed 2024-11-05 18:59:34 +09:00
Aiosa
9bfdd55b2e Make tile-invalidated event before tile-loaded. Try to fix behavior of maxTilesperFrame 2024-11-05 10:58:41 +01:00
Aiosa
5fdeb382ea Increase the test timeout: it seems that 5 seconds is not enough, maybe reason for tests failing. 2024-11-01 22:20:15 +01:00
Aiosa
cf65f1a4f4 Add ability to run only a specific module from CLI. 2024-11-01 22:06:18 +01:00
Aiosa
f127014f0f Design of separated events: drop update data support for tile-loaded, use only invalidated event. 2024-11-01 21:43:12 +01:00
Ian Gilman
e1e0ec6b24 Changelog for #2606 2024-10-28 09:17:59 -07:00
Ian Gilman
faf6d19db5
Merge pull request #2606 from pearcetm/viewerMargins
Fix problem with asymmetric viewport margins in webgl drawer
2024-10-28 09:14:09 -07:00
Tom
fab607584c fix problem with asymmetric viewport margins 2024-10-25 11:15:52 -06:00
Aiosa
cd60aff5dc Fix demo further: clear tile processing logics, perform locking on tile level, delete tile if drawn process encountered _destroyed state. 2024-10-23 15:54:31 +02:00
Aiosa
207bc88aab Fix demo further: _queuedInvalidateTiles was not being used, simplify how tile cache updates are being called (but without priorities). 2024-10-23 14:37:43 +02:00
Aiosa
6cbe359398 Fix demo filtering plugin: certain guards made rendering not being properly updated. 2024-10-23 10:07:16 +02:00
Ian Gilman
82314dd4bf Changelog for #2600 2024-10-22 09:43:43 -07:00
Ian Gilman
12e4607a30
Merge pull request #2600 from pearcetm/viewerMargins
Make webgl drawer account for viewport margins
2024-10-22 09:41:40 -07:00
Aiosa
20177116e7 Integration tests: bugfixing of manipulation of tiles that share data: when tiles are loaded, when tiles are processed, also await async data preparation befre finishing the invalidation event. 2024-10-22 17:25:02 +02:00
Aiosa
e403e29312 Fix tests: bug in type equality checking 2024-10-21 12:59:21 +02:00
Aiosa
82e1160508 Clean up outdated TODO comments. 2024-10-21 11:00:23 +02:00
Aiosa
d5cdf59993 Fix styling on the plugin demo. 2024-10-21 10:00:53 +02:00
Aiosa
3c6c7e0ab7 Add plugin interaction demo. 2024-10-21 09:55:23 +02:00
Aiosa
68f0ed8901 Do not spit out warns on invalid tile unload (preemtive working cache deletion), do not ignore working cache even if __restore=true. 2024-10-21 09:00:24 +02:00
Aiosa
b3cdeabf02 Flexible timeout for tests. 2024-10-18 14:56:05 +02:00
Aiosa
0cd17abafd Clean up old comments in a test. 2024-10-18 14:40:54 +02:00
Aiosa
1e47bd6add Add cache tests, add more robust CacheRecord creation/deletion logics. Zombies now do not replace data, prevents also potential memory leak. 2024-10-18 14:38:04 +02:00
Tom
6c20101c8b make webgl drawer account for viewport margins 2024-10-17 15:06:04 -04:00
Aiosa
bf25e2f069 Minor documentation and test cleanup. 2024-10-17 13:24:11 +02:00
Aiosa
06ac68d00e Html drawer add canvas to supported formats 2024-10-17 12:17:49 +02:00
Aiosa
1b6fea72d8 Add assets for fallback compatibility filtering plugin demo. 2024-10-17 12:17:24 +02:00
Aiosa
0b63a943b6 Tests & Bugfixes: new cache tests, working cache preemptively deleted when restore() called, zombie cache had bug (restored cache had no attached tile reference and restoration failed since we relied on any existing tile on the cache to inherit state), deprecated old HTMLDrawer props on tile, rewritten HTMLDrawer to work also with cache API. 2024-10-17 12:10:04 +02:00
Aiosa
f8e5cff117 Feature/Optimization: cache can be created by a callback (async or sync), to avoid premature data creation 2024-10-16 16:31:08 +02:00
Aiosa
b6693ee50d Fixed outdated demo pages. 2024-10-16 11:12:20 +02:00
Ian Gilman
8bdc0a4146 Changelog for #2590 2024-10-08 09:26:56 -07:00
Ian Gilman
3e0e39900e
Merge pull request #2590 from pearcetm/fix-setClip
Bug fix in TiledImage.setClip
2024-10-08 09:24:41 -07:00
Tom
263a664e58 add _needsUpdate during tiledImage.setClip() 2024-10-07 18:19:38 -04:00
Aiosa
3d21ec897b Set fully loaded for reset() call on tiled image to false. Add old plugins demo to see how they behave. Remove basic2 demo as it was added by accident. 2024-10-07 11:18:36 +02:00
Aiosa
2033814227 Update documentation and minor cleanup. 2024-10-05 11:50:21 +02:00
Ian Gilman
17c34a7985 Changelog for #2582 2024-09-24 09:50:10 -07:00
Ian Gilman
53e56932bd
Merge pull request #2582 from BeebBenjamin/2576-overlay-wrapping
Fixed: removeChild on Node
2024-09-24 09:43:56 -07:00
Richard Benjamin Allen
2b010a9a59 Fixed: JQuery cannot be used to get position with wrapper 2024-09-19 21:13:28 +01:00
Richard Benjamin Allen
4d4ccb99ba Fixed: Removed unused style attributes 2024-09-18 00:16:41 +01:00
Richard Benjamin Allen
c0eae951c6 Fixed: Removed unnecessary complexity 2024-09-17 23:38:48 +01:00
Richard Benjamin Allen
b28b77fdbd FIxed: Removed unnecessary references in selectors
We can test the overlay dimensions still
2024-09-17 23:30:06 +01:00
Richard Benjamin Allen
3b99665b32 Fixed: removeChild on Node
The overlay.js code has been changed to apply a div wrapper to the overlay element as opposed to its innerHTML.
2024-09-17 12:16:00 +01:00
Ian Gilman
50d7db595b
Merge pull request #2581 from openseadragon/dependabot/npm_and_yarn/serve-static-1.16.2
Bump serve-static from 1.14.2 to 1.16.2
2024-09-16 09:47:18 -07:00
dependabot[bot]
fe7e9a3315
Bump serve-static from 1.14.2 to 1.16.2
Bumps [serve-static](https://github.com/expressjs/serve-static) from 1.14.2 to 1.16.2.
- [Release notes](https://github.com/expressjs/serve-static/releases)
- [Changelog](https://github.com/expressjs/serve-static/blob/v1.16.2/HISTORY.md)
- [Commits](https://github.com/expressjs/serve-static/compare/v1.14.2...v1.16.2)

---
updated-dependencies:
- dependency-name: serve-static
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-16 13:29:14 +00:00
Aiosa
e3af370832 Merge branch 'cache-overhaul-reviewed' of github.com:RationAI/openseadragon into cache-overhaul-reviewed 2024-08-24 10:00:09 +02:00
Aiosa
63180a1589 Simplify filtering plugin demo. 2024-08-24 09:59:18 +02:00
Aiosa
c04b6af937
Merge branch 'master' into cache-overhaul-reviewed 2024-08-24 09:53:23 +02:00
Aiosa
29b01cf1bd First visually correct design: tile invalidation event manages three caches that are shared among equal tiles (based on cache key). Works with both latest drawers and shared caches. 2024-08-24 09:49:16 +02:00
Ian Gilman
a10b9086ab Changelog for #2570 2024-08-16 09:39:04 -07:00
Ian Gilman
a9f81190dd
Merge pull request #2570 from pearcetm/resize-after-drawer-swap
Remove resize handler from viewer when webgl drawer is destroyed
2024-08-16 09:36:49 -07:00
Tom
f3b92e0562 remove resize handler when webgl drawer is destroyed 2024-08-15 17:51:06 -04:00
Aiosa
cba40f4db8 Merge branch 'cache-overhaul-reviewed' of github.com:RationAI/openseadragon into cache-overhaul-reviewed 2024-08-15 12:58:07 +02:00
Aiosa
1b6f79661b Commit before merging master v5.0 2024-08-15 12:58:01 +02:00
Aiosa
999ff30e74
Merge branch 'master' into cache-overhaul-reviewed 2024-08-15 11:53:50 +01:00
Ian Gilman
d97ba581b0 Started 5.0.1 2024-08-14 09:37:49 -07:00
Ian Gilman
f28b7fc116 Version 5.0.0 2024-08-14 09:31:15 -07:00
Ian Gilman
a803e826a3 Changelog for #2563 2024-08-05 09:28:19 -07:00
Ian Gilman
8d2fd759cc Merge branch 'master' of github.com:openseadragon/openseadragon 2024-08-05 09:26:42 -07:00
Ian Gilman
d4c9b601c8
Merge pull request #2563 from openseadragon/ms-pixelDensityRatio-docs
JSDoc comments for OpenSeadragon.getCurrentPixelDensityRatio
2024-08-05 09:26:18 -07:00
Mark Salsbery
7740accfaf Update openseadragon.js
Fix jsdoc comments for OpenSeadragon.getCurrentPixelDensityRatio
2024-08-04 15:29:12 -07:00
Ian Gilman
8f6771ba91 Changelog for #2557 and #2558 2024-07-17 09:19:32 -07:00
Ian Gilman
980deca107
Merge pull request #2558 from pearcetm/webgl-update
Mark image-unloaded and tile-ready events as private
2024-07-17 09:17:54 -07:00
Ian Gilman
2db1c2f1da
Merge pull request #2557 from pearcetm/acceleration-test
add check for missing webgl context in WebGLDrawer.isSupported()
2024-07-17 09:16:58 -07:00
Tom
b87eaf3040 mark image-unloaded and tile-ready events as private 2024-07-16 14:17:29 -04:00
Tom
37ce7cc256 add check for missing webgl context in WebGLDrawer.isSupported() 2024-07-16 14:07:27 -04:00
Ian Gilman
74b69d99de Changelog for #2546 2024-07-16 09:36:24 -07:00
Ian Gilman
819d4cb20c
Merge pull request #2546 from BeebBenjamin/flip-overlay
Fixed: Invert overlay scale and rotate on flip
2024-07-16 09:30:02 -07:00
Richard Benjamin Allen
c1c1d480dd Fixed: Try renaming to something more descriptive 2024-07-16 08:38:39 +01:00
Richard Benjamin Allen
2c5d2eb7c6 Fixed: More meaning full variable and help text, innerStyle not outer 2024-07-12 20:36:11 +01:00
Richard Benjamin Allen
396fcb33a5 Fixed: Outer div added to element to allow independent flipping
An outer div has been added to the internal HTML of the overlay element to allow for independent flipping of the content.  Flipping will invert the `scaleX` value of the transform property for the style of the element.  By setting the value `overlayContentFlipped: true` in the OSD config we can flip the content in the opposite direction to the overlay, but by setting this to false we can flip the content along with the overlay.  This allows for some people who are using images in their overlay to flip the images along with the overlay.
2024-07-11 22:08:37 +01:00
Richard Benjamin Allen
176fae11e5 Fixed: Add inner div if text and invert x 2024-07-09 14:08:58 +01:00
Richard Benjamin Allen
fd9b701272 Fixed: Position.x not working on Scaled overlay 2024-07-09 10:37:51 +01:00
Ian Gilman
c0655ba370
Merge pull request #2553 from openseadragon/ms-IE11
More dropped support for IE11
2024-06-24 09:51:15 -07:00
Mark Salsbery
c08ef0ee36 Merge branch 'master' into ms-IE11 2024-06-23 16:36:59 -07:00
Mark Salsbery
fc7b942bc2 Update changelog.txt 2024-06-23 16:35:22 -07:00
Mark Salsbery
2290e5d08d More dropped support for IE11 2024-06-23 16:32:00 -07:00
Ian Gilman
ee6751e02f
Merge pull request #2548 from openseadragon/dependabot/npm_and_yarn/braces-3.0.3
Bump braces from 3.0.2 to 3.0.3
2024-06-20 09:24:13 -07:00
dependabot[bot]
3ed100d218
Bump braces from 3.0.2 to 3.0.3
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-18 23:24:55 +00:00
Richard Benjamin Allen
2c6b970f29 Fixed: Remove trailing space 2024-06-18 11:28:29 +01:00
Richard Benjamin Allen
263dc85fbd Fixed: Remove trailing spaces 2024-06-18 11:22:23 +01:00
Richard Benjamin Allen
79eecdcc76 Fixed: Remove duplicate line 2024-06-18 11:10:11 +01:00
Richard Benjamin Allen
44347fb2d0 Fixed: Invert overlay scale and rotate on flip 2024-06-18 10:57:55 +01:00
Ian Gilman
72d7c5353e
Merge pull request #2544 from openseadragon/ms-avif
Added avif to supported image formats
2024-06-17 09:35:06 -07:00
Mark Salsbery
88d7eb8c60 Update changelog.txt 2024-06-14 14:51:31 -07:00
Mark Salsbery
db0ab10428 Added avif to supported image formats (#2439) 2024-06-14 14:49:42 -07:00
Ian Gilman
fd74ea3dca Changelog for #2539 2024-06-10 09:27:44 -07:00
Ian Gilman
a96d186650
Merge pull request #2539 from eug-L/master
Fix Ajax headers not propagated to navigator
2024-06-10 09:24:05 -07:00
eug-L
35d468c4db propagate ajaxWithCredentials to navigator 2024-06-09 20:52:37 +08:00
Eugene Lu
65c727079c
Merge branch 'openseadragon:master' into master 2024-06-09 20:32:23 +08:00
Ian Gilman
feb5e13c32 Changelog for #2537 2024-06-04 09:29:40 -07:00
Ian Gilman
94954a699c Merge branch 'master' of github.com:openseadragon/openseadragon 2024-06-04 09:27:39 -07:00
Ian Gilman
3f5735be67
Merge pull request #2537 from pearcetm/fix-tile-loading
Fix sorting logic for best tiles to load
2024-06-04 09:25:12 -07:00
eug-L
834ed6ede5 fix ajax headers not propagated to navigator 2024-06-04 15:24:56 +08:00
Aiosa
0a035afc2d Merge master 2024-06-01 16:37:12 +02:00
Aiosa
cdb89ff5ad Add job queue full event. 2024-06-01 15:02:31 +02:00
Tom
65d30e7ce1 add minPixelRatio guard back in; fix tabs and spaces in comments' 2024-05-31 16:24:04 -04:00
Tom
f7c12a716b undo extra tabs before ach line introduced automatically by copy-and-pasting code 2024-05-22 18:59:19 -04:00
Tom
be30b429f8 remove unused code paths 2024-05-22 18:54:29 -04:00
Tom
3f03bd6e20 swap logic of haveDrawn and drawLevel within _updateTile 2024-05-22 17:36:16 -04:00
Tom
9ef1c5952e revert swapping order of arguments in _updateTile call to fix behavior of canvas drawer 2024-05-21 16:33:48 -04:00
Tom
5be44521b5 Fix sorting logic for best tiles to load 2024-05-21 04:30:17 -04:00
Ian Gilman
8d66acde53 Changelog for #2521 2024-04-29 10:50:35 -07:00
Ian Gilman
f3a942c522
Merge pull request #2521 from pearcetm/fix-2519
Fix #2519
2024-04-29 10:47:42 -07:00
Ian Gilman
3d54f8f686 Changelog for #2518 2024-04-29 10:41:38 -07:00
Ian Gilman
441ea71f40 Merge branch 'master' of github.com:openseadragon/openseadragon 2024-04-29 10:40:40 -07:00
Ian Gilman
d68a4024f8
Merge pull request #2518 from pearcetm/fix-canvas
Fix #2517
2024-04-29 10:38:44 -07:00
Tom
8b401e65e3 fix #2519 by checking minimumOverlapRequired on a per-tiled image basis 2024-04-28 08:38:03 -04:00
Tom
9d6a785aac add comment; clean up console.log messages from testing 2024-04-27 16:41:18 -04:00
Tom
f2c8db5db0 fix #2517 2024-04-27 16:38:30 -04:00
Ian Gilman
f8ef6818de Changelog for #2511 2024-04-12 09:24:16 -07:00
Ian Gilman
4f2bcbb1e3
Merge pull request #2511 from pearcetm/fix-canvas
Fix CanvasDrawer when viewport is flipped and a TiledImage is rotated
2024-04-12 09:19:39 -07:00
Tom
7ce5499868 fix comments 2024-04-11 12:45:11 -04:00
Ian Gilman
e809c9f198
Merge pull request #2512 from gaiborjosue/master
fix(citation): Fixed style issues for citation.cff
2024-04-11 09:32:43 -07:00
Tom
f7c8e4cf33 fix canvas drawer when viewport is flipped and tiled image is rotated. fix webgl drawer debug info when viewport is flipped. 2024-04-10 16:42:15 -04:00
Edward Gaibor
41c3abb12a
Update CITATION.cff 2024-04-10 12:35:30 -04:00
Edward Gaibor
3d97ac8ae7
Update CITATION.cff 2024-04-10 12:33:29 -04:00
Edward Gaibor
86395dfc78
Update CITATION.cff 2024-04-10 12:33:01 -04:00
Ian Gilman
49e2d7a987
Merge pull request #2509 from openseadragon/citation
Added citation file
2024-04-10 09:27:12 -07:00
Ian Gilman
5fefb8f976 Added Aseem and changed authors to chronological 2024-04-09 09:33:03 -07:00
Ian Gilman
6a4c3838c8 Added citation file 2024-04-08 09:43:16 -07:00
Ian Gilman
9f7b18adb6 Changelog for #2507 2024-04-08 09:36:39 -07:00
Ian Gilman
6ea7779318
Merge pull request #2507 from frameflare/master
chore: fix some comments
2024-04-08 09:35:11 -07:00
frameflare
0ba2f75213 Signed-off-by: frameflare <yangzenghua@outlook.com>
chore: fix some comments

Signed-off-by: frameflare <yangzenghua@outlook.com>
2024-04-07 10:50:24 +08:00
Ian Gilman
31c2c85c4f Changelog for #2506 2024-04-05 09:20:02 -07:00
Ian Gilman
0621958ca2
Merge pull request #2506 from eug-L/master
Getter & setter for Viewport.maxZoomPixelRatio
2024-04-05 09:18:22 -07:00
eug-L
4451131999 update flags for setMaxZoomPixelRatio 2024-04-05 12:47:15 +08:00
eug-L
b0926b3e69 Getter & setter for Viewport.maxZoomPixelRatio 2024-04-04 16:02:09 +08:00
Ian Gilman
63d8d63c1f Changelog for #2505 2024-04-03 09:28:56 -07:00
Ian Gilman
a2f6da52c0
Merge pull request #2505 from eug-L/imageToViewportZoom
update error message for Viewport.imageToViewportZoom
2024-04-03 09:26:24 -07:00
eug-L
5990e5649b update error message for Viewport.imageToViewportZoom 2024-04-03 23:06:42 +08:00
Ian Gilman
6a47104b4f Updated for version 4.1.1 2024-04-01 10:12:04 -07:00
Ian Gilman
9b7fc464ca Changelog for #2492 2024-03-07 09:30:00 -08:00
Ian Gilman
262d04c6c6
Merge pull request #2492 from pearcetm/fix-webgl-tilepadding
Make WebGLDrawer deal with tiles with padding
2024-03-07 09:29:19 -08:00
Ian Gilman
9a92d9e9ad Changelog for #2491 2024-03-07 09:24:47 -08:00
Ian Gilman
553795e8fa Merge branch 'master' of github.com:openseadragon/openseadragon 2024-03-07 09:23:24 -08:00
Ian Gilman
afeea19403
Merge pull request #2491 from pearcetm/fix-2490
Fix #2490
2024-03-07 09:21:59 -08:00
Tom
029a40aa21 deal with tiles that have padding 2024-03-05 15:39:54 -05:00
Tom
3e04c8854e make navigator.update's parameter optional, default to navigator.viewer.viewport 2024-03-05 13:01:42 -05:00
Aiosa
e0f442209b Fix black viewport with testing filtering demo on webgl renderer. Introduce managed mock getters for tests. 2024-03-05 10:48:07 +01:00
Tom
2cb1835533 fix docs 2024-03-04 21:32:14 -05:00
Tom
c177246675 fix #2490 2024-03-04 21:28:12 -05:00
Aiosa
e2c633a23b Small bugfixes, rename some properties. Add more redraw calls. 2024-03-04 19:23:47 +01:00
Aiosa
47419a090a Fix circular references in JSON test log serialization. 2024-03-04 10:49:05 +01:00
Aiosa
52ef8156c0 Fixed: internal cache not allways calling destructor, refresh handler was not called on internal cache. Polish code. Improve filtering demo. 2024-03-03 17:59:39 +01:00
Aiosa
a9b50a8fdb Test fixes (except gl null reference error - test fails sometimes). 2024-03-03 16:39:15 +01:00
Aiosa
135fa76fde Merge webgl PR - problems with tained canvas and texture upload. 2024-03-03 15:17:54 +01:00
Aiosa
360f0d6796 Fix docs, commit before upstream merge. 2024-03-03 14:50:01 +01:00
Ian Gilman
4d26c800ba Changelog for #2488 2024-03-01 09:25:16 -08:00
Ian Gilman
a42a0d1822
Merge pull request #2488 from pearcetm/setPosition-fix
Mark TiledImage as needing an update when position, scale, or rotation are set immediately
2024-03-01 09:18:33 -08:00
Tom
e4fd005fb0 Mark TiledImage as needing an update when position, scale, or rotation are set with immediately=true 2024-02-29 16:44:09 -05:00
Ian Gilman
1c0797e800 Changelog for #2472 2024-02-27 09:35:01 -08:00
Ian Gilman
01e70ab7d8
Merge pull request #2472 from pearcetm/canvas-fallback
In webgl drawer, fall back to canvas drawer for tiled images with tainted data
2024-02-27 09:33:06 -08:00
Tom
b6501a3786 fix typo in docs. change to options object for requestDrawer API 2024-02-22 13:30:05 -05:00
Ian Gilman
7dfc60a97f Changelog for #2478 2024-02-22 09:31:32 -08:00
Ian Gilman
da5abc1b39
Merge pull request #2478 from pearcetm/webgl-clipping
fix clip behavior with webgl drawer
2024-02-22 09:30:01 -08:00
Tom
5df791fc82 support viewport flipping for clip and cropping polygons in webglviewer 2024-02-21 18:12:22 -05:00
Tom
ae5f08b9bd call _setClip for test spyOnce 2024-02-21 17:53:23 -05:00
Tom
a0bcbc4d21 fix clip behavior with webgl drawer 2024-02-21 17:49:46 -05:00
Tom
c6e3e06194 Merge branch 'master' into canvas-fallback 2024-02-21 16:13:36 -05:00
Tom
1442e5d4e4 revert change in drawercomparison.js used for testing 2024-02-21 16:02:29 -05:00
Tom
99c35aa3cb consolidate drawer creation into viewer api. add drawer-error event 2024-02-21 16:01:18 -05:00
Tom
02898cfc17 clean up bound event handlers in webgldrawer.destroy 2024-02-12 18:05:33 -05:00
Ian Gilman
59645e3a0d Changelog for #2468 2024-02-12 09:53:00 -08:00
Ian Gilman
4c331a2000
Merge pull request #2468 from thec0keman/fix-this-scoping
Fix `this` scoping
2024-02-12 09:52:01 -08:00
Ian Gilman
b62fba80d6 Changelog for #2469 2024-02-12 09:32:37 -08:00
Ian Gilman
1c60e45a7f
Merge pull request #2469 from pearcetm/placeholder-for-webgl
Support placeholderFillStyle in WebGLDrawer, and other fixes
2024-02-12 09:29:20 -08:00
Tom
6e4914ada3 better cleanup for html drawer in destroy 2024-02-12 09:32:17 -05:00
Tom
8967e2bb03 support hot-swapping drawers with viewer.setDrawer() 2024-02-12 09:30:26 -05:00
Tom
0a154a3b21 In webgl drawer, fall back to canvas drawer for tiled images with tainted data 2024-02-11 11:51:38 -05:00
Aiosa
63f0adbc15 Fix issues with tile reference in cache: keep the 'most fresh' ref. 2024-02-11 17:18:03 +01:00
Aiosa
d91df0126b Add base drawer options and fix docs. Implement 'simple internal cache' for drawer data, optional to use. 2024-02-11 11:27:02 +01:00
Tom
3f21f84df4 clean up code added for testing 2024-02-09 18:16:30 -05:00
Tom
c734de531f fix placeholder positioning for canvas drawer 2024-02-09 17:18:54 -05:00
Tom
d4e82d374e account for viewport rotation in addition to tiledImage rotation 2024-02-09 15:19:40 -05:00
Tom
5b2c6d7ed9 add support for placeholderFillStyle to webgl drawer. fix spring logic to avoid getting stuck updating due to floating point math. update tilesource-swap demo. 2024-02-09 15:06:52 -05:00
John Ratcliff
8f483a3ba0 Fix this scoping 2024-02-09 11:23:38 -08:00
Aiosa
cae6ec6bee Revert weird tiledImage check - tests worked now also without. Add generic drawerswitcher utility for demos. 2024-02-08 13:11:10 +01:00
Aiosa
a97fe34d74 Remove merge marks. 2024-02-05 09:47:10 +01:00
Aiosa
9ef2d46e75 Fix tests: always fetch up-to-date pixel values, prevent adding loaded tile to the 'bestTiles' array. Enforce _needsDraw check to be based on lastDrawn - we are async now. 2024-02-05 09:42:26 +01:00
Aiosa
fcf20be8ea Drawers now use new cache API to draw onto a canvas. The type conversion now requires also the tile argument so that conversion can rely on the tile metadata. 2024-02-04 18:48:25 +01:00
Aiosa
3fa13570ef Resolve Merge with First WebGL PR: code is merged, but not operable [drawer refactoring needs interaction implemented with cache system]. 2024-02-04 15:41:05 +01:00
Ian Gilman
f7d86ac244 Changelog for #2466 2024-02-02 09:29:54 -08:00
Ian Gilman
e3c3634266
Merge pull request #2466 from pearcetm/fix-docs
fix jsdoc formatting for drawer classes
2024-02-02 09:26:43 -08:00
Tom
3ced918fb4 fix jsdoc formatting for drawer classes 2024-02-01 14:29:10 -05:00
Tom
23e50b6038 fix jsdoc formatting for drawer classes 2024-02-01 14:24:25 -05:00
Ian Gilman
fdaa3e2f16 Changelog for #2464, #2462 2024-02-01 09:43:51 -08:00
Ian Gilman
2e0868023a
Merge pull request #2464 from jonasengelmann/fix/touch-gesture-dragToPan
Fix dragToPan when flickEnabled is activated
2024-02-01 09:38:28 -08:00
Ian Gilman
98769af678
Merge pull request #2462 from pearcetm/fix-zero-opacity-case
Fix zero opacity case
2024-02-01 09:33:04 -08:00
jonasengelmann
e08274e2b0 fix dragToPan when flickEnabled is activated #2463 2024-02-01 18:01:21 +01:00
Tom
68623ace26 skip drawing zero-opacity images for webgl drawer. remove checks for preload in html and canvas drawers since that is done in tiledimage. 2024-02-01 10:46:45 -05:00
Tom
921171ed2b move logic about zero opacity case into getDrawArea 2024-02-01 08:47:03 -05:00
Tom
84a55968ee Allow preloading of tiles for zero opacity tiled images 2024-01-31 15:40:47 -05:00
Tom
1b44de3b68 Stop tiles from loading if opacity of tiled image is 0 2024-01-31 15:37:09 -05:00
Ian Gilman
f9a8b97cf9 Changelog for #2310 2024-01-31 09:47:49 -08:00
Ian Gilman
52fc10ffa2
Merge pull request #2310 from pearcetm/rotation-seams
Refactor drawing code, add WebGL drawer, and enable plugin renderers
2024-01-31 09:38:56 -08:00
Tom
59519e0e96 fix docs 2024-01-30 13:14:54 -05:00
Tom
d490592b99 expand event validation logic 2024-01-29 13:12:01 -05:00
Tom
024cee42f5 make event handler validation private 2024-01-29 13:01:44 -05:00
Tom
a55071f67b fix documentation for rejectEventHandler 2024-01-29 11:25:36 -05:00
Tom
2add108539 reject adding handlers for certain event names 2024-01-29 10:39:55 -05:00
Tom
c0104c7480 add validation at time of binding event handlers 2024-01-26 12:23:02 -05:00
Ian Gilman
5be1ab6f7d Changelog for #2455 2024-01-25 11:26:33 -08:00
Ian Gilman
4811dd0320
Merge pull request #2455 from BeebBenjamin/webp-support
Fix #2454, add webp to supported image formats
2024-01-25 11:23:25 -08:00
Richard Benjamin Allen
362a060512 Fix, change set supported format example 2024-01-25 16:47:51 +00:00
Tom
d2376b571f clean up comments and error logging 2024-01-24 13:02:50 -05:00
Richard Benjamin Allen
4e9134f66a Fix #2454, add webp to supported image formats 2024-01-22 09:43:02 +00:00
Tom
dc03989bbe throw error if MAX_TEXTURE_IMAGE_UNITS is a bad value 2024-01-19 14:42:34 -05:00
Tom
3cdd5fd711 throw error if MAX_TEXTURE_IMAGE_UNITS is a bad value 2024-01-19 13:19:43 -05:00
Tom
f6c35b44ed move qunit.starTest log message to test.js 2024-01-19 12:54:24 -05:00
Tom
43de595c24 clean up code from testing the MAX_TEXTURE_IMAGE_UNITS error issue 2024-01-18 16:20:28 -05:00
Tom
a2d31e9610 cleaned up additional viewers in tests 2024-01-18 16:06:49 -05:00
Tom
8271b6d8b2 more test details logged 2024-01-18 15:44:29 -05:00
Tom
aae82ab16a more test details logged 2024-01-18 15:34:14 -05:00
Tom
525d9b52fe more test details logged 2024-01-18 13:47:14 -05:00
Tom
bb676a8699 add test info to console.log 2024-01-18 13:32:30 -05:00
Tom
fd648c26e9 add logging directly to webgl drawer 2024-01-18 13:17:35 -05:00
Tom
f6c67b2159 remove additional test logging for testing travis ci 2024-01-18 12:56:03 -05:00
Tom
0a2657cb5b additional test logging for testing travis ci 2024-01-18 12:45:27 -05:00
Tom
44b66be0a9 update tests 2024-01-17 17:07:25 -05:00
Tom
db11611d7d add logging of gl max_texture_image_units errors to multi-image test 2024-01-17 16:13:34 -05:00
Tom
b902256a59 clean up comments. modify tests. add check for max textures in webgldrawer 2024-01-15 14:39:15 -05:00
Tom
f563600635 test canvas and webgl drawers in multi-image 2024-01-11 14:42:11 -05:00
Tom
e0fd78344b add description of WebGLDrawer drawing pipeline 2024-01-11 13:54:01 -05:00
Tom
3bab3c2190 small cleanups 2024-01-11 13:29:59 -05:00
Tom
500c22feb1 consolidate tiled-image-drawn event description and firing code 2024-01-10 18:00:01 -05:00
Tom
8be2ca2dfb test drawers separately 2024-01-10 17:46:17 -05:00
Tom
834795b4b8 fix basic demo 2024-01-10 14:48:48 -05:00
Tom
9725ac0398 update tiled-image-drawn event description 2024-01-10 12:21:05 -05:00
Tom
58f918aaa1 clean up tests 2024-01-10 12:13:00 -05:00
Tom
f3a90159c1 cleaning up webgldrawer 2024-01-08 19:18:58 -05:00
Tom
1239b22c30 updates to viewer 2023-12-22 13:46:36 -05:00
Tom
1a1e2c20c0 fix viewport.update so tests don't stall 2023-12-21 16:31:23 -05:00
Tom
22ecdfd75d fix documentation for viewport.update 2023-12-21 16:12:19 -05:00
Tom
bbee6e9605 update animating logic 2023-12-21 16:09:45 -05:00
Tom
40a4e42835 make reference strip always use the canvas drawer 2023-12-21 15:29:25 -05:00
Tom
1588f6b715 add documentation; rename parameters for clarity 2023-12-21 15:10:23 -05:00
Tom
55882b9215 fix docs. rename matrix.js to matrix3.js 2023-12-19 15:57:15 -05:00
Tom
d5a3cabf13 fixing jsdocs 2023-12-18 17:01:17 -05:00
Tom
9616e26dd2 updates in drawers and tiled image 2023-12-15 13:13:40 -05:00
Tom
074b65bfcd clean up comments and small fixes 2023-12-14 20:59:55 -05:00
Tom
3f7e7141b9 fixes from review of drawerbase.js 2023-12-14 19:14:05 -05:00
Tom
2d8c6fe4c3 cleaning up docs 2023-12-13 18:11:09 -05:00
Tom
a364c3f168 responding to review 2023-12-12 21:45:24 -05:00
Aiosa
3d6eb1b91c Fix broken tests (bad logics in event handling). 2023-12-10 17:58:50 +01:00
Aiosa
cf2413e0c9 Fix test for the preload hack (and fix the parentheses to always call updateMulti). 2023-12-10 16:49:56 +01:00
Aiosa
a690b50eee Add external execution pipeline (proof of concept implementation, needs polishing). Add filtering plugin live demo for testing. Fix issues with tile cache access outside its lifespan. Add custom css for the static page renderer and differentiate folder icons. Remove some old deprecations. 2023-12-10 16:34:42 +01:00
Tom
e50d3639ce bugfix in canvasdrawer tiled-image-drawn event 2023-12-02 15:47:01 -05:00
Tom
1200f0b081 clean up outdated code; add tiled-image-drawn event to canvasdrawer so tests work with this viewer 2023-12-02 15:40:47 -05:00
Tom
32f12b3ba5 check for valid ASSERT in tilesource-dynamic-url test 2023-11-29 19:34:41 -05:00
Tom
9dc96390fc update tests to skip fullscreen and certain drawer-related events 2023-11-29 19:04:27 -05:00
Tom
6b995c018b re-enable html drawer in drawer comparison demo; deal with merge conflict resolution issues 2023-11-29 17:35:49 -05:00
pearcetm
36d8b3d9ad
Merge branch 'master' into rotation-seams 2023-11-29 17:28:42 -05:00
Tom
c86158f0ca cleaning up changes 2023-11-29 16:46:14 -05:00
Aiosa
90ce0669c5 Add auto recognition of the need for tiledImage draw call. Fix ajax-headers test: did not finish because we don't call tile-loaded on cached tiles by default. 2023-11-27 12:12:54 +01:00
Aiosa
2c67860c61 Implement cache manipulation strategy: default copy on access if tile in the rendering process, remove 'canvas' type support, many bugfixes and new tests. 2023-11-26 21:32:26 +01:00
pearcetm
a578b97d96
Merge pull request #5 from Aiosa/rotation-seams
Small rendering bugfixes, URL params in demo pages.
2023-11-24 09:41:30 -05:00
Aiosa
191ba39cbb Remove demo references to the modular renderer. 2023-11-21 20:53:06 +01:00
Aiosa
23496cb049 Remove also file references. 2023-11-21 12:56:19 +01:00
Aiosa
f4efe2970a Remove modular implementation -> will be introduced in subsequent PR. 2023-11-21 12:54:09 +01:00
Aiosa
2a1090ffa8 Fix wrong test comparison. Add equality comparator to TileSource API. Return
deprecated support for getCompletionCallback. Turn on zombie cache if sources replaced & equal.
2023-11-19 16:14:28 +01:00
Aiosa
219049976c Add tests for zombie and data type conversion, ensure destructors are called.
Fix bugs (zombie was disabled on item replace, fix zombie cache system by separating to its own cache array). Fix CacheRecord destructor & dijkstra. Deduce cache only from originalCacheKey. Force explicit type declaration with types on users.
2023-11-18 20:16:35 +01:00
Aiosa
c3ab9a08e7 Merge branch 'cache-overhaul-reviewed' of github.com:RationAI/openseadragon into cache-overhaul-reviewed 2023-11-17 20:22:13 +01:00
Aiosa
023a864a36
Merge branch 'openseadragon:master' into cache-overhaul-reviewed 2023-11-17 20:21:24 +01:00
Ian Gilman
6abcc76a93 Changelog for #2431 2023-11-13 09:57:19 -08:00
Ian Gilman
c92f8ad65d
Merge pull request #2431 from cavenel/master
Fix zoom out when wheel event.deltaY is zero
2023-11-13 09:53:42 -08:00
Christophe Avenel
c1038af37d
Revert "Add debounce of pan events"
This reverts commit e836ffcc61.
2023-11-08 18:52:05 +01:00
Ian Gilman
89ae9c1376 Changelog for #2416 2023-11-06 09:32:00 -08:00
Ian Gilman
a8d37d892e
Merge pull request #2416 from lutzhelm/2415-iiif-tiling-error-for-descending-sizes
Fix #2415, order IIIF sizes on tile source init
2023-11-06 09:27:08 -08:00
Christophe Avenel
e836ffcc61 Add debounce of pan events
Group pan events together if needed, in order to avoid lag if drawing of layers takes more time than it should.
2023-10-31 15:38:38 +01:00
Christophe Avenel
810987210c
Fix zoom out when wheel event.deltaY is zero
Don't apply zoom in or zoom out when deltaY is zero on wheel events.
Should fix https://github.com/openseadragon/openseadragon/issues/947 and not make OpenSeadragon zoom out when scrolling horizontally on a touchpad
2023-10-25 09:26:48 +02:00
Aiosa
6447009c18 Remove blend time from comparison demo (not functional). 2023-10-23 16:05:18 +02:00
Aiosa
cd9d340038 Removed date loader (will be handled by future OSD cache system). Attempt to use instanced rendering. Refactoring of the module. 2023-10-23 16:03:47 +02:00
Lutz Helm
5272cf092c Ensure descending size order also works for level 0 2023-10-10 14:04:31 +02:00
Lutz Helm
b92a428e88 Sort levelSizes instead of sizes 2023-10-10 14:02:44 +02:00
Aiosa
31f9a71109 Working tiledimage-level transparency in a single pass. 2023-09-27 15:16:23 +02:00
Aiosa
174c4c709a Remove logs from debugging. 2023-09-26 15:47:50 +02:00
Aiosa
e3024deb46 Modular webgl2 drawer: fix small bugs. Add drawer IDs to demo page urls to allow refreshing/direct running 2023-09-26 15:31:43 +02:00
Aiosa
f796925ae5 Remove irrelevant code and comments. 2023-09-25 08:52:45 +02:00
Aiosa
750d45be81 Implement asynchronous tile processing logic wrt. tile cache conversion. 2023-09-24 22:30:28 +02:00
Lutz Helm
5542ead48b Fix #2415, order IIIF sizes on tile source init 2023-09-19 09:55:53 +02:00
Aiosa
f01a7a4b3c Cache Overhaul for OpenSeadragon (areas to discuss marked with FIXME). 2023-09-08 08:47:43 +02:00
Ian Gilman
640526b444 Changelog for #2393 2023-08-22 10:21:35 -07:00
Ian Gilman
8d2f8449ba
Merge pull request #2393 from VIRAT9358/fix496
fixed position issue
2023-08-22 10:16:17 -07:00
ViratSr
ed49d46237 syntax fixed and if statement amended 2023-08-17 10:39:45 +05:30
ViratSr
9b051f55de #fix496 2023-08-16 00:10:55 +05:30
ViratSr
b15ba75551 fixed position issue 2023-08-15 16:46:10 +05:30
Ian Gilman
17cd6b51ce Changelog for #2387 and tweaks to its demo page 2023-08-08 09:39:45 -07:00
Ian Gilman
cca81a37d5
Merge pull request #2387 from schuefflerlab/BestTiles
Introduced option maxTilesPerFrame
2023-08-08 09:25:17 -07:00
Peter
0bdd807d9d Cleaned code according to comments in PR 2023-08-08 11:05:33 +02:00
Peter
f8ad0acfa4 Sort the N tiles only once instead of when adding new indiviual tiles 2023-08-02 21:04:43 +02:00
Peter
55a05963a2 Sort N tiles always for a better look and feel. 2023-08-02 20:30:30 +02:00
pearcetm
e07745d790
Merge pull request #4 from Aiosa/rotation-seams
First modular webgl drawer implementation by @Aiosa

Thanks for putting this together @Aiosa! I'm going to merge it into the large refactor PR. It'd be great to have a demo page that includes all these features, for testing/debugging and to show off the capabilities.
2023-08-01 17:18:32 -04:00
Ian Gilman
4a3fc5b325
Merge pull request #2389 from openseadragon/ms-mousetracker-docs
MouseTracker options docs fixes
2023-08-01 09:33:12 -07:00
Tom
2fcdcc3f6a add attribution for rStats code 2023-08-01 12:20:13 -04:00
Mark Salsbery
fa8196ef4f changelog update 2023-07-31 13:16:36 -07:00
Mark Salsbery
1f47f5def1 MouseTracker options docs fixes 2023-07-31 13:08:07 -07:00
Tom
77b5766fa2 update drawerperformance demo 2023-07-31 15:48:29 -04:00
Peter
13955b0bf6 added demo with maxTilesPerFrame = 3 2023-07-31 10:14:27 +02:00
Tom
c77a7e7386 enhance performance demo. rename variables for clarity. 2023-07-28 17:49:24 -04:00
Peter
17873001b8 outsourced tile sorting from tile comparing. 2023-07-28 13:15:47 +02:00
Aiosa
054ad0d53b
Merge branch 'pearcetm:rotation-seams' into rotation-seams 2023-07-28 12:25:58 +02:00
Aiosa
a907aaa160 Merge remote-tracking branch 'origin/rotation-seams' into rotation-seams 2023-07-28 12:24:15 +02:00
Aiosa
b6273b95b8 First modular webgl drawer implementation. 2023-07-28 12:23:59 +02:00
pearcetm
d912ff3196
Merge pull request #3 from Aiosa/rotation-seams
Exposing matrix implementation, automatic drawer recognition

Thanks, @Aiosa!
2023-07-26 19:28:29 -04:00
Aiosa
ca45e96142
Merge branch 'rotation-seams' into rotation-seams 2023-07-26 23:58:07 +02:00
Aiosa
046612f8da Better drawer resolution, also supporting inline class spec. Fix minor review stuff. 2023-07-26 23:55:46 +02:00
Tom
1e37745764 sync webgldemodrawer with webgldrawer 2023-07-26 15:44:38 -04:00
Tom
f4ded1630f code cleanup 2023-07-26 15:42:18 -04:00
Aiosa
3c3842bdce Change comments TO DO -> TODO. Add automatic recognition of drawers. Matrix as an exposed class. 2023-07-26 20:01:26 +02:00
Peter
ccb4ae9f86 Fixed _updateViewport to come to a fullyLoaded state with n tiles.
Improved _compareTiles
2023-07-20 17:27:49 +02:00
Peter
9684a83b8c Updated Comment 2023-07-20 15:17:02 +02:00
P. Schueffler
7ad748799e
Pr for upstream (#3)
Added maxTilesPerFrame
2023-07-20 14:45:15 +02:00
Tom
f510301922 add performance test demo page. reduce number of drawing calls drawing pipeline of webgl drawer. 2023-07-19 18:18:56 -04:00
Ian Gilman
f50df84287
Merge pull request #2386 from openseadragon/dependabot/npm_and_yarn/word-wrap-1.2.4
Bump word-wrap from 1.2.3 to 1.2.4
2023-07-18 14:00:49 -07:00
dependabot[bot]
9f31919dc1
Bump word-wrap from 1.2.3 to 1.2.4
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-18 20:28:16 +00:00
Ian Gilman
e51053a6a3 Changelog for #2382 2023-07-17 14:03:59 -07:00
Ian Gilman
eec052f054
Merge pull request #2382 from AndrewADev/update-grunt-contrib-qunit
Update grunt-contrib-qunit and extend FullScreen test
2023-07-17 14:02:02 -07:00
Andrew Armbruster
be4d5ad7f7 Update grunt-contrib-qunit and extend FullScreen test
Update the package responsible for running tests, grunt-contrib-qunit.

The package change includes a significant bump to the puppeteer version used to run the tests (from v9 -> v19: https://github.com/gruntjs/grunt-contrib-qunit#release-history).

Running the tests with the newer puppeteer version leads to issues running the FullScreen test when run in the 'old'/current default headless mode (the test fails with a timeout).

Updating to the 'new' headless mode (eventually to be the default anyway), seems to allow the fullscreen request to succeed.

Thus, go ahead and extend the FullScreen test to verify the correct values for a successful request, as well as an additional step to exit fullscreen mode.
2023-07-16 13:10:56 +02:00
Tom
01a4ea6f2c Update documentation and remove commented code. Change from get type to getType(). 2023-07-09 14:10:20 -04:00
Tom
6feed0a68e Add clipping to demo. Update documentation and remove commented code. Change from get type to getType(). 2023-07-09 12:05:17 -04:00
Tom
0a3aa6172d add type property to drawers. only add extra padding to tiles for drawers that need it. 2023-06-30 16:38:07 -04:00
Tom
048b43e196 remove forced transparency from images in drawercomparison demo. change from context2d naming to canvas. 2023-06-30 16:06:17 -04:00
Tom
cc412f0a20 add documentation; remove unnecessary deprecated code 2023-06-29 21:55:59 -04:00
Tom
3a5a738508 remove viewer.destroy() from some tests 2023-06-29 17:06:45 -04:00
Tom
83ec2bb1f0 tiled image opacity works now with no overlapping regions at tile borders 2023-06-29 16:41:45 -04:00
Tom
24c4d2d2bc Remove tile-drawn event and replace with tiled-image-drawn for webgl drawer, and update related tests. 2023-06-27 13:57:02 -04:00
Tom
2ec2c0f2c7 remove three.js and associated plugin renderer; fix demo 2023-06-26 21:44:03 -04:00
Tom
386ca85db8 implement native webgl renderer, and many associated changes related to drawing pipeline and testing 2023-06-26 21:29:08 -04:00
Ian Gilman
ffbd8f985a Changelog for #2367 2023-06-21 14:19:06 -07:00
Ian Gilman
88f419330e Merge branch 'master' of github.com:openseadragon/openseadragon 2023-06-21 14:17:06 -07:00
Ian Gilman
3386a73e44
Merge pull request #2367 from akansjain/master
Removed createCallback function
2023-06-21 14:10:28 -07:00
akansjain
eaa41f7f05 Replaced the comment with Deprecated 2023-06-21 13:34:58 +05:30
Tom
128975ea0f only calculate tile positions when necessary 2023-06-18 19:28:48 -04:00
Tom
0584ca7be9 add to-do comments 2023-06-18 19:09:22 -04:00
Tom
37b8ef9519 deprecate useCanvas option; add option for drawer and drawerOptions 2023-06-18 19:08:33 -04:00
Tom
7bb02d51d0 reverted change to argument order in _updateTile call 2023-06-14 07:51:50 -07:00
akansjain
7dec7b9aba Added console.error() in createCallback() function 2023-06-09 17:58:22 +05:30
Tom
2e248baf91 additional fixes to get tests to pass 2023-06-08 12:10:55 -07:00
Tom
354590a17a convert DrawerBase and drawer implementations to classes; add html drawer to the demo page 2023-06-07 12:42:16 -07:00
Tom
8fda8ceae7 re-enable sequence mode demo viewer 2023-06-07 10:16:21 -04:00
Tom
8fe6a5f6e6 merging remote 2023-06-07 10:14:58 -04:00
Tom
1bc93be3f2 update tile drawing logic 2023-06-06 19:37:24 -04:00
Tom
36084703f0 conditional usage of draw() vs event handlers depending on initialization context 2023-06-06 18:08:53 -04:00
Tom
1089f79a5b remove drawer class 2023-06-06 18:08:50 -04:00
Tom
2a0af48bc1 updates to threejsdrawer, bugxfixes/improvements 2023-06-06 18:08:30 -04:00
Tom
dab8a9a3cd move tile update logic back to TiledImage to keep drawing logic cleaner 2023-06-06 18:08:30 -04:00
Tom
2f3bef0865 split canvas from html rendering 2023-06-06 18:08:30 -04:00
Tom
467597e2c2 cleaning up drawer APIs 2023-06-06 18:08:30 -04:00
Tom
5eece7d73a fix cropping when viewer is rotated 2023-06-06 18:08:30 -04:00
Tom
69c4ccf33e revert debugging changes in overlay demo 2023-06-06 18:08:30 -04:00
Tom
5328761877 finished many implementation details and demo 2023-06-06 18:08:01 -04:00
Tom
f9ab63944b ThreeJSRenderer nearly complete, starting refactor of core to support plugin drawers. 2023-06-06 18:05:17 -04:00
akansjain
3fc156d513 Removed createCallback function 2023-06-06 23:04:04 +05:30
Ian Gilman
c9cd7352c4 Changelog for #2364 2023-06-02 14:23:11 -07:00
Ian Gilman
ebab356c20
Merge pull request #2364 from SebDelile/master
workaround tile edge smoothing maxZoom + flip
2023-06-02 14:18:54 -07:00
Delile Sebastien
f96db3cf0f complete comment 2023-06-02 18:59:22 +02:00
Delile Sebastien
f004ecf323 fix comment 2023-06-01 10:20:14 +02:00
Delile Sebastien
2c03c1bbe9 workaround tile edge smoothing maxZoom + flip 2023-05-31 16:11:42 +02:00
Ian Gilman
b405a904a6
Merge pull request #2362 from AndrewADev/update-qunit
Update to most recent QUnit
2023-05-30 14:31:02 -07:00
Ian Gilman
c2a1df9ae0 Changelog for #2361 2023-05-30 14:25:44 -07:00
Ian Gilman
c69950c6e4
Merge pull request #2361 from AndrewADev/allow-es6-syntax
Allow Using ES6 Syntax
2023-05-30 14:24:34 -07:00
Andrew Armbruster
c7df7be7f1 Catch rejected Promise on failed fullscreen request
Add a `catch()` rejection of request for fullscreen (standard Fullscreen API implementation), as well as fullscreen exit. For now there is no actual handling, but we send a message to the internal error channel.

Motivation: The fullscreen request was causing certain tests to fail when run with newer versions of QUnit (starting in 2.5.0), which now fails tests that have unhandled Promise rejections (as we did here).

The exit case is not currently covered by tests, but works the same way (also returns a promise).

See: https://fullscreen.spec.whatwg.org/#ref-for-dom-element-requestfullscreen%E2%91%A0
2023-05-28 14:15:55 +02:00
Andrew Armbruster
ff67cf1cae Support ES6 Syntax
Bump ESLint's parser options to support ES6 syntax, which allows developers to start using a number of features associated with modern JavaScript.

Doing this now that #2286 has been marked as resolved, since IE 11 support was the last known blocker for such changes.
2023-05-28 12:41:30 +02:00
Andrew A
ad1179b9db Update to most recent QUnit
Update to most recent version of QUnit, which has been published under a new name for a while now.

Accordingly, replace the old package with the new one in both dependencies, as well as update references in test HTML files.
2023-05-27 13:16:28 +02:00
Ian Gilman
ea54427f42
Merge pull request #2359 from openseadragon/ian
Updated copyright to 2023
2023-05-25 14:53:05 -07:00
Ian Gilman
b9cb52a184 Updated copyright to 2023 2023-05-25 14:52:20 -07:00
Ian Gilman
3d57722b9f Changelog for #2300 2023-05-25 14:37:50 -07:00
Ian Gilman
ae9ad111c4
Merge pull request #2300 from AndrewADev/add-es-api-linting
Add ESLint plugin for linting browser compatibility
2023-05-25 14:35:47 -07:00
Ian Gilman
160cae7a4a Starting version 5.0.0 2023-05-25 13:58:21 -07:00
Ian Gilman
884968139d Version 4.1.0 2023-05-25 13:53:17 -07:00
Ian Gilman
324dc4ed30 Changelog for #2356 2023-05-24 14:03:14 -07:00
Ian Gilman
dedd85e184
Merge pull request #2356 from lcl45/rotating-navigator-region
Added navigator's region rotation
2023-05-24 13:59:23 -07:00
lcl45
7fb5744495 Resolved suggestions
- Avoid useless parameters _navigatorRotate since it already exists within the navigator;
- Automatically rotate the image in the demo page.
2023-05-24 08:45:15 +02:00
oro.niccolo
d897e5454f Added navigator's region rotation
When the "navigatorRotate" option is set to false, rotate the navigator's region accordigly to the image rotation.
2023-05-23 09:27:32 +02:00
Ian Gilman
01d940b938 Changelog for #2346 2023-05-19 15:04:46 -07:00
Ian Gilman
e6f2d3e07b
Merge pull request #2346 from uschmidt83/ajax-headers
Add `setAjaxHeaders` method to Viewer and TiledImage
2023-05-19 15:01:54 -07:00
Uwe Schmidt
4060dee4f9 Make updateAjaxHeaders private 2023-05-19 22:22:10 +02:00
Uwe Schmidt
e983bd8108 Add docs for updating ajax headers 2023-05-12 23:48:31 +02:00
Uwe Schmidt
857a825ed8 Fix some jsdoc typos 2023-05-12 23:37:34 +02:00
Uwe Schmidt
6fd828974d Update navigator ajax headers 2023-05-12 21:46:16 +02:00
Uwe Schmidt
e51aa4a9c5 Refine setAjaxHeaders
- Allow null to clear headers (same as empty object)
- Add TiledImage._updateAjaxHeaders
- Add error message in case of invalid headers
2023-05-05 01:13:44 +02:00
Uwe Schmidt
182c11481e Add tests for setAjaxHeaders 2023-05-05 00:03:22 +02:00
Ian Gilman
6981cfed01 Changelog for #2347 2023-05-04 13:45:06 -07:00
Ian Gilman
d4057aca12
Merge pull request #2347 from pearcetm/navigator-fix
update navigator size when calling setHeight or setWidth
2023-05-04 13:42:17 -07:00
Tom
528df37335 update navigator size when calling setHeight or setWidth 2023-05-03 22:38:49 -04:00
Uwe Schmidt
b23e8295d3 Propagate updated ajax headers by default 2023-05-03 23:51:07 +02:00
Uwe Schmidt
13603dd5f4 Update reference strip ajax headers 2023-05-03 19:33:50 +02:00
Uwe Schmidt
fe7a5eb01b Make ES5-compatible 2023-05-03 18:46:18 +02:00
Uwe Schmidt
3c2628f182 Add setAjaxHeaders method to Viewer and TiledImage
- First draft, not tested at all
- See openseadragon/openseadragon#1748
2023-05-03 16:05:58 +02:00
Ian Gilman
b1274515aa Tiny basic.html tweak 2023-04-27 14:27:40 -07:00
Ian Gilman
b088fc0da3 Changelog for #2337 2023-04-27 14:27:09 -07:00
Ian Gilman
d36c76127c
Merge pull request #2337 from ruven/master
Use resolution level dimensions provided in the info.json "sizes" field
2023-04-27 14:24:23 -07:00
Ruven
c5404006b2
Further optimization: code moved into constructor thereby eliminating need for getLevelSize() function. 2023-04-25 12:06:27 +02:00
Ruven
5fd125dc92
Added implementation of getTileAtPoint() function. This eliminates flickering at level transitions caused by mis-match in resolution size calculation between the getTileAtPoint() and getNumTiles() functions. 2023-04-24 22:44:46 +02:00
Ruven
877c3b68ed
Refactored code to take into account optimization suggestions (https://github.com/openseadragon/openseadragon/pull/2337#discussion_r1170931340) 2023-04-24 17:24:18 +02:00
Ruven
0c358c140d
Use resolution level dimensions provided in the info.json "sizes" field to determine tile sizes as well as the number of tiles that exist at a particular resolution. Fall back to calculation using ceil() if no resolution sizes provided. Avoids rounding errors for edge tiles and fixes https://github.com/openseadragon/openseadragon/issues/2321 2023-04-17 21:08:18 +02:00
Ian Gilman
60a6a610e0 Changelog for #2334 2023-04-10 14:23:31 -07:00
Ian Gilman
f91dbdd2c7
Merge pull request #2334 from Ughuuu/Fix-TileLoader-re-trying-even-if-successful
Fix TileLoader re-trying even if successful #2
2023-04-10 14:18:58 -07:00
Dragos Daian
d18a4c3fd7 mark tile.exists so that the retired tiles appear. 2023-04-08 16:48:04 +02:00
Ian Gilman
ebda5a909b Changelog for #2333 2023-04-07 13:49:12 -07:00
Ian Gilman
4f737f1714
Merge pull request #2333 from robertjcolley/master
Fixes navigator rotation not honoring immediately parameter
2023-04-07 13:47:35 -07:00
Robert Colley
e6b9c79bc1
Fixes #2332
Mirror the main viewer's rotation and account for immediately. Without 
this, viewer.setRotation(90, true) would rotate the main viewer without 
animation and the navigator with animation.
2023-04-06 18:10:38 -04:00
Ian Gilman
8b305546ed Changelog for #2324 2023-04-06 14:26:15 -07:00
Ian Gilman
5b56d6fa16
Merge pull request #2324 from rsimon/master
Applied touch rotate fix suggested in #2319
2023-04-06 14:22:54 -07:00
Ian Gilman
2e99e48603 Fixed changelog for #2136 2023-04-06 14:12:30 -07:00
Ian Gilman
b45d500f1e Changelog for #2328 2023-04-04 14:51:05 -07:00
Ian Gilman
cec0acafe1
Merge pull request #2328 from craigberry/master
Handle XML embedded in tilesource JSON
2023-04-04 14:48:52 -07:00
Ian Gilman
073b2e0b1f Changelog for #2318 2023-03-31 15:30:19 -07:00
Ian Gilman
3488d18e4c
Merge pull request #2318 from donotloveshampo/patch-1
Fixed #2314
2023-03-31 15:27:43 -07:00
Craig A. Berry
bff93af17e Handle XML embedded in tilesource JSON
The existing check for whether the tilesource data is XML or JSON
looks for a tag anywhere in the response content, which incorrectly
flags the content as XML in the case where it is really JSON with
XML embedded in it.

This commit corrects that problem by requiring the tag to occur as
the first non-whitespace part of the content. This is basically a
poor person's well-formedness check since well-formed XML cannot
have non-whitespace content outside of the root node.

N.B. While malformed XML content with non-whitespace characters
before the first element has been getting identified as XML, it
has not been getting parsed correctly.  With current parsing
infrastructure, the content has been getting replaced by a parsing
error message.  With more up-to-date parsing infrastructure, it
will throw an error.  Either way we're not losing anything by failing
to identify malformed XML as XML.

Addresses issue #2325.
2023-03-30 16:38:35 -05:00
Rainer Simon
84d2dcf11d Applied touch rotate fix suggested in #2319 2023-03-25 18:38:58 +01:00
donotloveshampo
244e54da69
Fixed #2314 2023-03-17 16:27:12 +08:00
Ian Gilman
292b62ba21 Change log for #2317 2023-03-16 13:26:59 -07:00
Ian Gilman
dec2afabb2 Merge branch 'master' of github.com:openseadragon/openseadragon 2023-03-16 13:23:40 -07:00
Ian Gilman
2ae9fb0e13
Merge pull request #2317 from pearcetm/after-resize-event
Add after-resize event
2023-03-16 13:22:50 -07:00
Ian Gilman
56ae09fc8a Changelog for #2316 2023-03-14 14:02:07 -07:00
Ian Gilman
5f816246e4
Merge pull request #2316 from pearcetm/scaled-crop-bugfix
partial bugfix for #2312
2023-03-14 13:57:35 -07:00
Tom
fb32cc7198 add after-resize event 2023-03-13 22:19:06 -04:00
Tom
a01c230b6e partial bugfix for #2312 2023-03-13 21:56:06 -04:00
Tom
8a4c95ef4b conditional usage of draw() vs event handlers depending on initialization context 2023-03-13 21:39:54 -04:00
Tom
9821b4589d remove drawer class 2023-03-13 17:56:53 -04:00
Tom
6d1895364f updates to threejsdrawer, bugxfixes/improvements 2023-03-13 17:43:35 -04:00
Tom
ade59513df move tile update logic back to TiledImage to keep drawing logic cleaner 2023-03-13 15:56:04 -04:00
Tom
641951aaed split canvas from html rendering 2023-03-12 11:42:03 -04:00
Tom
6159ca7c7c cleaning up drawer APIs 2023-03-11 11:38:21 -05:00
Ian Gilman
bf06f271c6 Changelog for #2293 2023-03-10 13:52:32 -08:00
Ian Gilman
cbb0a009e7
Merge pull request #2293 from pearcetm/zoom-constraints-fix
Take zoom constraint into account within _fitBounds
2023-03-10 13:49:41 -08:00
Ian Gilman
25b4d7a3ca
Merge pull request #2308 from AndrewADev/set-node-lts-in-ci
Update Travis to use latest Node LTS
2023-03-07 14:27:02 -08:00
Ian Gilman
624609d800 Changelog for #2306 2023-03-06 14:25:38 -08:00
Ian Gilman
afae0ec5ec
Merge pull request #2306 from MohitBansal321/addButton
add custom button for this viewer
2023-03-06 14:22:34 -08:00
Tom
a6e621b562 fix cropping when viewer is rotated 2023-03-06 14:29:39 -05:00
Tom
7c81d068e6 revert debugging changes in overlay demo 2023-03-05 16:12:30 -05:00
Tom
d1b8001adc finished many implementation details and demo 2023-03-05 16:08:32 -05:00
Andrew Armbruster
2764e3ad9a Try an even newer build env
This would buy even more time to find an alternative to pinning the runner version.
2023-02-28 21:16:41 +01:00
Andrew Armbruster
5c12e1ce1c Use newer runner version
Request a newer runner version from Travis CI due to a glibc version bump in newer versions of Node.
2023-02-28 21:04:22 +01:00
Andrew Armbruster
7d2ceac61a Travis uses latest Node LTS
Update CI config to use the most recent LTS.

For some other possible values, see: https://docs.travis-ci.com/user/languages/javascript-with-nodejs/#specifying-nodejs-versions
2023-02-28 20:21:10 +01:00
Mohit Bansal
8a201c12b4 add given button function to buttongroup 2023-02-28 01:05:22 +05:30
Mohit Bansal
f393547d9e add custom button 2023-02-27 23:52:29 +05:30
Andrew A
b0233f6a24 Drop non-standard lint command
Stick to existing approach leveraging grunt for dev tasks.
2023-02-24 19:51:42 +01:00
Andrew A
2932b0a800 Exclude IE 11 from query
We are ready to drop IE 11, so no need for it to be included in the browserslist

See: https://github.com/openseadragon/openseadragon/pull/2300#pullrequestreview-1308240522
2023-02-24 19:50:38 +01:00
Ian Gilman
59083b7669 Changelog for #2301 2023-02-23 15:07:09 -08:00
Ian Gilman
f0d15e3e79
Merge pull request #2301 from MohitBansal321/focusEvent
add canvas-focus and canvas-blur events to Viewer
2023-02-23 15:04:11 -08:00
Mohit Bansal
795e85bebb update doc comments 2023-02-22 10:21:51 +05:30
Tom
376ee38d3c ThreeJSRenderer nearly complete, starting refactor of core to support plugin drawers. 2023-02-21 18:59:40 -05:00
Mohit Bansal
c05af1d38b add focus and blur event 2023-02-19 15:49:43 +05:30
Andrew A
ab0ddcae3d Add a lint script
Make it easy to lint in stand-alone fashion.

Note that I'm going with a direct call to ESLint, as using `grunt eslint` leads to warnings about a circular dependency (per trace it seems to originate in istanbul?)
2023-02-18 15:55:03 +01:00
Andrew A
09a119afe3 Add ESLint plugin for ES API linting
Adding [eslint-plugin-compat](https://github.com/amilajack/eslint-plugin-compat) to check for usage of incompatible APIs.

For a list of browsers, we start out using both the 'defaults' query along with IE 11, which is still expressly supported in OSD v4 (current).
2023-02-18 15:50:21 +01:00
Ian Gilman
25aabbe766
Merge pull request #2298 from MohitBansal321/npmOutdated
some npm packages outdated
2023-02-17 14:16:07 -08:00
Ian Gilman
f2fa9dbb41 Changelog for #2297 2023-02-17 14:01:56 -08:00
Ian Gilman
9026c3a71d
Merge pull request #2297 from KevinBritten/docs/button-type-fix
2087 add type Element to documentation for custom ui buttons
2023-02-17 13:57:33 -08:00
Mohit Bansal
e765860aeb Merge branch 'master' of https://github.com/openseadragon/openseadragon into npmOutdated 2023-02-17 15:26:10 +05:30
Kevin Britten
8ec6e55304
Merge branch 'openseadragon:master' into docs/button-type-fix 2023-02-16 17:21:44 -05:00
Ian Gilman
5a5420972c Changelog for #2291 2023-02-16 14:16:56 -08:00
Ian Gilman
c7992411a5
Merge pull request #2291 from MohitBansal321/keyboardHandling
Consolidate viewer keyboard handling into keydown handler
2023-02-16 14:13:45 -08:00
Kevin Britten
cf3bf91b8d add type Element to documentation for custom ui buttons 2023-02-16 14:51:30 -05:00
Mohit Bansal
8442cc2b2b update npm packages 2023-02-15 19:15:22 +05:30
Mohit Bansal
0ab81df9b8 remove unwanted property 2023-02-15 09:37:03 +05:30
Mohit Bansal
6be459e451 formatting 2023-02-13 23:13:08 +05:30
Mohit Bansal
78928e3510 add comment 2023-02-13 23:01:19 +05:30
Mohit Bansal
4909320089 correction of keyCode 2023-02-12 13:30:41 +05:30
Mohit Bansal
3a8738fd43 spacing && documentation 2023-02-12 12:43:41 +05:30
Mohit Bansal
dac697c74e formatting 2023-02-10 16:44:55 +05:30
Mohit Bansal
4bc9ea4573 add keypress handler's event 2023-02-09 12:55:44 +05:30
Mohit Bansal
cfa88b701f replace canvasKeyPressEventArgs to canvasKeyDownEventArgs 2023-02-08 19:22:27 +05:30
Tom
16fc72d8fb Take zoom constraint into account within _fitBounds when constraints = true and immediately = false 2023-02-07 18:12:26 -05:00
Mohit Bansal
f36d44dc67 Consolidate viewer keyboard handling into keydown handler 2023-02-07 03:31:57 +05:30
Ian Gilman
ead728dc35 Changelog for #2282 2023-02-03 14:07:08 -08:00
Ian Gilman
3cf3fb50b2
Merge pull request #2282 from RationAI/master
Ensure tile-loaded event completionCallback is called only once.
2023-02-03 14:03:39 -08:00
Aiosa
37d4f62ce9 Remove discouraging note on getCompletionCallback use docs. 2023-02-02 17:18:12 +01:00
Aiosa
57486732b1 Prevent early tile completion with call order instead of guard flag. Improve getCompletionCallback docs. 2023-02-01 10:25:10 +01:00
Aiosa
55e7d2439a Change completionCallback with 'tile-loaded' event to support original scenario of async completion notification with additional guarding flags. 2023-01-31 08:05:02 +01:00
Aiosa
81d86570da Typo in the stopping comparison condition. 2023-01-28 14:08:00 +01:00
Aiosa
947109718c Ensure tile-loaded event completionCallback is called only once. Check when context2D used after cache creation. 2023-01-28 08:42:07 +01:00
Ian Gilman
ca5cc84988 Changelog for #2280 and #2238 2023-01-26 14:13:52 -08:00
Ian Gilman
dbb60cf026
Merge pull request #2280 from damonsson/patch-1
fix problem with click precision on ReferenceStrip
2023-01-26 14:11:16 -08:00
Damian Murawski
2484de5010
remove trailing space 2023-01-26 11:44:39 +01:00
Damian Murawski
8550b4fea5
fix problem with click precision on ReferenceStrip
temporary fix for #1992 . Just adding 4px which works in all use cases.
2023-01-25 23:15:12 +01:00
Ian Gilman
6f7e8c7a49
Merge pull request #2238 from Ughuuu/pr-1386-3
add loop for re-trying failed tiles [Take 3]
2023-01-24 15:15:58 -08:00
Ian Gilman
e6f7c78626 Changelog for #2273 2023-01-24 14:58:13 -08:00
Ian Gilman
d22eceed66
Merge pull request #2273 from RationAI/master
EventSource: promises, priorities and execution breaks
2023-01-24 14:53:07 -08:00
Dragos Daian
b4700d28bd Also add documentation for tileRetryDelay 2023-01-23 22:16:05 +01:00
Dragos Daian
f0f12c459e try fix with check for null and undefined 2023-01-23 22:10:23 +01:00
Dragos Daian
5d70a807da fix build error 2023-01-23 19:54:51 +01:00
Dragos Daian
77bc130636 Add tileRetryMax documentation. 2023-01-23 19:49:43 +01:00
Aiosa
377f2bd04f Merge branch 'master' of github.com:RationAI/openseadragon 2023-01-21 09:00:58 +01:00
Aiosa
7a7acdbe57
Merge branch 'openseadragon:master' into master 2023-01-21 08:00:29 +00:00
Aiosa
de00939d8d Revert async support and event breaking support in EventSource. 2023-01-21 09:00:24 +01:00
Ian Gilman
a1004bac86 Changelog for #2276 2023-01-20 13:53:46 -08:00
Ian Gilman
5a8e7ddcf8
Merge pull request #2276 from ambujsahu81/patch-1
Fix #2264: Navigator display rectangle is off when the page has box-sizing: border-box
2023-01-20 13:51:52 -08:00
ambujsahu81
f3a76a267c add box-sizing property to the navigator display region 2023-01-20 18:26:54 +05:30
Aiosa
c8dbb2c757 Implement support for async function and promise type recognition with $.type. Add $.Promise proxy. Implement support for promises in EventSource. Implement ability to abort events as well as prioritize events. 2023-01-17 11:13:48 +01:00
Ian Gilman
d80b6ad4ce Changelog for #2270 2023-01-12 13:57:44 -08:00
Ian Gilman
3d9ae49948
Merge pull request #2270 from hrghauri/issue-2192
issues/2192 fix - Introduced canvas-key-press for keypress.
2023-01-12 13:54:50 -08:00
Haris Ghauri
a9f0523b62 issues/2192 fix. 2023-01-11 23:15:24 -05:00
Ian Gilman
a111d0f616 Starting 4.0.1 2022-12-16 14:22:50 -08:00
Dragos Daian
c3dec09d9c add loop for re-trying failed tiles [Take 3] 2022-11-20 12:27:51 +01:00
113 changed files with 16404 additions and 10268 deletions

1
.browserslistrc Normal file
View file

@ -0,0 +1 @@
defaults

View file

@ -1,14 +1,16 @@
{
"root": true,
"plugins": ["compat"],
"extends": [
"eslint:recommended"
"eslint:recommended",
"plugin:compat/recommended"
],
"env": {
"es6": false,
"es6": true,
"browser": true
},
"parserOptions": {
"ecmaVersion": 5,
"ecmaVersion": 6,
"sourceType": "script",
"ecmaFeatures": {
"globalReturn": false,

View file

@ -1,6 +1,10 @@
# Specify dist until Travis CI default Runner OS updates to one
# with glibc version required by newer Node versions
# See: https://github.com/nodejs/node/issues/42351#issuecomment-1068424442
dist: jammy
language: node_js
sudo: false
node_js:
- "16.14.2"
- lts/*
before_install:
- npm install -g grunt-cli

39
CITATION.cff Normal file
View file

@ -0,0 +1,39 @@
cff-version: 1.2.0
title: OpenSeadragon
message: "If you use this software, please cite it using the metadata from this file."
type: software
authors:
- given-names: Ian
family-names: Gilman
email: ian@iangilman.com
- given-names: Aseem
family-names: Kishore
- given-names: Chris
family-names: Thatcher
- given-names: Mark
family-names: Salsbery
- given-names: Antoine
family-names: Vandecreme
- given-names: Thomas
family-names: Pearce
identifiers:
- type: url
value: https://openseadragon.github.io/
description: Homepage
- type: url
value: https://github.com/openseadragon/openseadragon
description: Repository
repository-code: https://github.com/openseadragon/openseadragon
url: https://openseadragon.github.io/
abstract: "An open-source, web-based viewer for high-resolution zoomable images, implemented in pure JavaScript, for desktop and mobile."
keywords:
- javascript
- image
- zooming
- viewer
- image-viewer
- high-resolution
- iiif
license: BSD-3-Clause
version: 5.0.1
date-released: 2024-11-09

View file

@ -61,6 +61,10 @@ Our tests are based on [QUnit](https://qunitjs.com/) and [Puppeteer](https://git
grunt test
To test a specific module (`navigator` here) only:
grunt test --module="navigator"
If you wish to work interactively with the tests or test your changes:
grunt connect watch
@ -69,6 +73,12 @@ and open `http://localhost:8000/test/test.html` in your browser.
Another good page, if you want to interactively test out your changes, is `http://localhost:8000/test/demo/basic.html`.
> Note: corresponding npm commands for the above are:
> - npm run test
> - npm run test -- --module="navigator"
> - npm run dev
You can also get a report of the tests' code coverage:
grunt coverage

View file

@ -28,6 +28,7 @@ module.exports = function(grunt) {
coverageDir = 'coverage/' + dateFormat(new Date(), 'yyyymmdd-HHMMss'),
sources = [
"src/openseadragon.js",
"src/matrix3.js",
"src/fullscreen.js",
"src/eventsource.js",
"src/mousetracker.js",
@ -48,6 +49,8 @@ module.exports = function(grunt) {
"src/legacytilesource.js",
"src/imagetilesource.js",
"src/tilesourcecollection.js",
"src/priorityqueue.js",
"src/datatypeconvertor.js",
"src/button.js",
"src/buttongroup.js",
"src/rectangle.js",
@ -57,11 +60,14 @@ module.exports = function(grunt) {
"src/imageloader.js",
"src/tile.js",
"src/overlay.js",
"src/drawer.js",
"src/drawerbase.js",
"src/htmldrawer.js",
"src/canvasdrawer.js",
"src/webgldrawer.js",
"src/viewport.js",
"src/tiledimage.js",
"src/tilecache.js",
"src/world.js"
"src/world.js",
];
var banner = "//! <%= pkg.name %> <%= pkg.version %>\n" +
@ -75,6 +81,11 @@ module.exports = function(grunt) {
grunt.config.set('gitInfo', rev);
});
let moduleFilter = '';
if (grunt.option('module')) {
moduleFilter = '?module=' + grunt.option('module')
}
// ----------
// Project configuration.
grunt.initConfig({
@ -160,13 +171,16 @@ module.exports = function(grunt) {
qunit: {
normal: {
options: {
urls: [ "http://localhost:8000/test/test.html" ],
timeout: 10000
}
urls: [ "http://localhost:8000/test/test.html" + moduleFilter ],
timeout: 10000,
puppeteer: {
headless: 'new'
}
},
},
coverage: {
options: {
urls: [ "http://localhost:8000/test/coverage.html" ],
urls: [ "http://localhost:8000/test/coverage.html" + moduleFilter ],
coverage: {
src: ['src/*.js'],
htmlReport: coverageDir + '/html/',
@ -187,7 +201,12 @@ module.exports = function(grunt) {
server: {
options: {
port: 8000,
base: "."
base: {
path: ".",
options: {
stylesheet: 'style.css'
}
}
}
}
},

View file

@ -1,5 +1,5 @@
Copyright (C) 2009 CodePlex Foundation
Copyright (C) 2010-2022 OpenSeadragon contributors
Copyright (C) 2010-2024 OpenSeadragon contributors
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

View file

@ -27,3 +27,13 @@ OpenSeadragon is released under the New BSD license. For details, see the [LICEN
[github-releases]: https://github.com/openseadragon/openseadragon/releases
[github-contributing]: https://github.com/openseadragon/openseadragon/blob/master/CONTRIBUTING.md
[github-license]: https://github.com/openseadragon/openseadragon/blob/master/LICENSE.txt
## Sponsors
We are grateful for the (development or financial) contribution to the OpenSeadragon project.
<a href="https://www.bbmri-eric.eu"><img alt="BBMRI ERIC Logo" src="assets/logos/bbmri-logo.png" height="70" /></a>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a href="https://www.pitt.edu/"><img alt="University of Pittsburgh Logo" src="assets/logos/pitt-logo.png" height="70" /></a>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a href="https://www.stanford.edu/"><img alt="Stanford University Logo" src="assets/logos/stanford-logo.png" height="70" /></a>

BIN
assets/logos/bbmri-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
assets/logos/pitt-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View file

@ -1,9 +1,110 @@
OPENSEADRAGON CHANGELOG
=======================
6.0.0: (in progress...)
* NEW BEHAVIOR: OpenSeadragon Data Pipeline Overhaul (#2407, #2643 @Aiosa)
* DEPRECATION: Properties on tile that manage drawer data, or store data to draw: Tile.[element|imgElement|style|context2D|getImage|getCanvasContext] and transitively Tile.getScaleForEdgeSmoothing
* DEPRECATION: TileSource data lifecycle handlers: system manages these automatically: TileSource.[createTileCache|destroyTileCache|getTileCacheData|getTileCacheDataAsImage|getTileCacheDataAsContext2D]
* Tiles data is driven by caches: tiles can have multiple caches and cache can reference multiple tiles.
* Data types & conversion pipeline: caches support automated conversion between types, and call optionally destructors. These are asynchronous.
* Data conversion reasoning: the system keeps costs of convertors and seeks the cheapest conversion to a target format (using Dijkstra).
* Async support: events can now await handlers. Added OpenSeadragon.Promise proxy object. This object supports also synchronous mode.
* Drawers define what data they are able to work with, and receive automatically data from one of the declared types.
* Drawers now store data only inside cache, and provide optional type convertors to move data into a format they can work with.
* TileSource equality operator. TileSource destructor support. TileSources now must output type of the data they download [context.finish].
* Zombies: data can outlive tiles, and be kept in the system to wait if they are not suddenly needed. Turned on automatically with TiledImage addition with `replace: true` and equality test success.
* ImagesLoadedPerFrame is boosted 10 times when system is fresh (reset / open) and then declines back to original value.
* CacheRecord supports 'internal cache' of 'SimpleCache' type. This cache can be used by drawers to hide complex types used for rendering. Such caches are stored internally on CacheRecord objects.
* CacheRecord drives asynchronous data management and ensures correct behavior through awaiting Promises.
* TileCache adds new methods for cache modification: renameCache, cloneCache, injectCache, replaceCache, restoreTilesThatShareOriginalCache, safeUnloadCache, unloadCacheForTile and more. Used internally within invalidation events
* Tiles have up to two 'originalCacheKey' and 'cacheKey' caches, which keep original data and target drawn data (if modified).
* Invalidation Pipeline: New event 'tile-invalidated' and requestInvalidate methods on World and TiledImage. Tiles get methods to modify data to draw, system prepares data for drawing and swaps them with the current main tile cache.
* New test suites for the new cache system, conversion pipeline and invalidation events.
* New testing/demo utilities (MockSeadragon, DrawerSwitcher for switching drawers in demos, getBuiltInDrawersForTest for testing all drawers), serialization guard in tests to remove circular references.
* New demos, demonstrating the new pipeline. New demos for older plugins to show how compatible new version is.
* Misc: updated CSS for dev server, new dev & test commands.
* New option: loadDestinationTilesOnAnimation. With it on, during animations, OSD loads tiles in the destination region, rather than the areas passed through on the way to the destination. This new feature is on by default. (#2686, #2690 @MichaelWGibson)
* Overlay wrapper elements now have a "openseadragon-overlay-wrapper" class. If the overlay element has an ID, the wrapper gets a variant on that ID, but if the overlay element does not have an ID, we no longer give the wrapper an ID. (#2698 @lokaesshwar)
* The functions the viewer uses to operate the zoom in and zoom out buttons are now accessible to be called programatically (#2702 @achu1998)
* Improved how OpenSeadragon is imported in various environments (#2644 @Aiosa)
* Improved documentation (#2676 @bennlich)
* Improved unit tests (#2640 @harshkg23)
* Fixed: Transparency detection didn't always work properly (#2636 @pcram-techcyte)
* Fixed: MouseTracker's hasGestureHandlers and hasScrollHandler values were not getting updated upon dynamically adding/removing handlers (#2649 @Seafret)
* Fixed: Sometimes images wouldn't update when you changed their opacity (#2652 @pearcetm)
* Fixed: Possible MouseTracker hash collision (#2657 @cff29546)
5.0.1:
* Improved overlay handling so it plays better with other libraries (#2582 @BeebBenjamin)
* WebGLDrawer now supports the imageSmoothingEnabled option (#2615 @pearcetm)
* Fixed: If you switched from WebGL drawer to canvas drawer, it didn't clean up properly (#2570 @pearcetm)
* Fixed: TiledImage.setClip would sometimes leave tiles unloaded (#2590 @pearcetm)
* Fixed: The WebGL drawer didn't support viewportMargins (#2600, #2606 @pearcetm)
* Fixed: If you set viewport rotation before flip, the navigator display region would be drawn wrong (#2619 @jbakarich)
* Fixed: In some cases, the WebGL drawer would draw the image in white (#2620 @sbarex)
* Fixed: If the user changed the page zoom or moved the window a different monitor, the image would disappear (#2627 @pearcetm)
5.0.0:
* BREAKING CHANGE: Dropped support for IE11 (#2300, #2361, #2553 @AndrewADev, @msalsbery)
* DEPRECATION: The OpenSeadragon.createCallback function is no longer recommended (#2367 @akansjain)
* The viewer now uses WebGL when available (#2310, #2462, #2466, #2468, #2469, #2472, #2478, #2488, #2492, #2521, #2537, #2557, #2558 @pearcetm, @Aiosa, @thec0keman)
* Added webp to supported image formats (#2455 @BeebBenjamin)
* Added avif to supported image formats (#2544 @msalsbery)
* Introduced maxTilesPerFrame option to allow loading more tiles simultaneously (#2387 @jetic83)
* Now when creating a viewer or navigator, we leave its position style alone if possible (#2393 @VIRAT9358)
* Added getter & setter for Viewport.maxZoomPixelRatio (#2506 @eug-L)
* Overlays are now positioned properly when the viewport is flipped (#2546 @BeebBenjamin)
* Added overlayPreserveContentDirection option to keep overlays readable when viewport is flipped (#2546 @BeebBenjamin)
* Test improvements (#2382 @AndrewADev)
* MouseTracker options documentation fixes (#2389 @msalsbery)
* Improved documentation and error message for Viewport.imageToViewportZoom (#2505 @eug-L)
* Fixed documentation typos (#2507 @frameflare)
* Additional documentation fixes (#2563 @msalsbery)
* Fixed: Sometimes if the viewport was flipped and the user zoomed in far enough, it would flip back (#2364 @SebDelile)
* Fixed: Two-finger tap on a Mac trackpad would zoom you out (#2431 @cavenel)
* Fixed: dragToPan gesture could not be disabled when flickEnabled was activated (#2464 @jonasengelmann)
* Fixed: placeholderFillStyle didn't work properly when the image was rotated (#2469 @pearcetm)
* Fixed: Sometimes exponential springs wouldn't ever settle (#2469 @pearcetm)
* Fixed: The navigator wouldn't update its tracking rectangle when the navigator was resized (#2491 @pearcetm)
* Fixed: The drawer would improperly crop when the viewport was flipped and a tiled image was rotated (#2511 @pearcetm, @eug-L)
* Fixed: Flipped viewport caused image to be flipped again when going fullscreen or resizing (#2518 @pearcetm)
* Fixed: Viewer ajax options (loadTilesWithAjax, ajaxHeaders, and ajaxWithCredentials) weren't being propogated to the navigator. (#2539 @eug-L)
4.1.1:
* Fixed: Strange behavior if IIIF sizes were not in ascending order (#2416 @lutzhelm)
4.1.0:
* NEW BEHAVIOR: When `navigatorRotate` is false, while the navigator image doesn't rotate, the red outline now does (#2356 @lcl45)
* The viewer no longer emits `canvas-key` events for both keydown and keypress events; canvas-key is now just for keydown, and the new `canvas-key-press` is for keypress (#2270 @hrghauri)
* You can now specify a priority when calling addHandler, to control when your event handler gets called relative to others (#2273 @Aiosa)
* Added tileRetryMax and tileRetryDelay options, so the viewer can retry loading failed tiles (#2238 @Ughuuu @paaddyy, #2334 @Ughuuu @Titan21)
* All of the viewers keyboard handling is now in response to keydown events (it used to be split between keydown and keypress) (#2291 @MohitBansal321)
* Added `canvas-focus` and `canvas-blur` events to Viewer (#2301 @MohitBansal321)
* You can now more easily add custom buttons to the viewer (#2306 @MohitBansal321)
* The fitBounds function now takes zoom constraints into account (#2293 @pearcetm)
* The viewer now has an `after-resize` event what happens after the viewport bounds have been updated, to complement the `resize` event which happens before (#2317 @pearcetm)
* IIIFTileSource now uses resolution level dimensions provided in the info.json "sizes" field for more accurate tile requests (#2337 @ruven)
* Added setAjaxHeaders method to Viewer and TiledImage (#2346 @uschmidt83)
* Improved documentation (#2297 @KevinBritten)
* Fixed: The `tile-loaded` event's completionCallback could be called more than once in some circumstances (#2282 @Aiosa, @pearcetm)
* Fixed: Navigator display rectangle was off if the page had `box-sizing: border-box` (#2276 @ambujsahu81)
* Fixed: Code that required identifying functions would fail for async functions (#2273 @Aiosa)
* Fixed: Reference strip click detection was not accurate for long reference strips (#2280 @damonsson)
* Fixed: Translation problems in some circumstances with cropping polygons enabled (#2316 @pearcetm)
* Fixed: The navigator area rectangle would grow larger when you zoom in very far (#2318 @donotloveshampo)
* Fixed: JSON with embedded XML was being incorrectly identified as XML (#2328 @craigberry)
* Fixed: Touch/pinch rotate was not working properly on some platforms (#2324 @rsimon, @pearcetm)
* Fixed: Navigator rotation didn't honor `immediately` parameter (#2333 @robertjcolley)
* Fixed: The navigator didn't update for its new size in certain circumstances (#2347 @pearcetm)
4.0.0:
* NEW BEHAVIOR: Setting the viewport rotation now animates by default (pass false for the new `immediately` parameter to disable) (#2136 @jonasengelmann)
* NEW BEHAVIOR: Setting the viewport rotation now animates by default (pass true for the new `immediately` parameter to disable) (#2136 @jonasengelmann)
* NEW BEHAVIOR: The auto resize now takes both width and height into account when scaling the contents proportionally to the viewer (#2256 @pearcetm)
* DEPRECATION: Don't access the viewport's degrees property directly anymore; instead use setRotation and getRotation (#2136 @jonasengelmann)
* New gesture: Double-click and drag to zoom (on by default for touch) (#2225 @HamzaTatheer)

7587
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{
"name": "openseadragon",
"version": "4.0.0",
"version": "5.0.1",
"description": "Provides a smooth, zoomable user interface for HTML/Javascript.",
"keywords": [
"image",
@ -29,22 +29,25 @@
"url": "https://github.com/openseadragon/openseadragon.git"
},
"devDependencies": {
"grunt": "^1.4.1",
"grunt-contrib-clean": "^2.0.0",
"eslint-plugin-compat": "^4.1.2",
"grunt": "^1.6.1",
"grunt-contrib-clean": "^2.0.1",
"grunt-contrib-compress": "^2.0.0",
"grunt-contrib-concat": "^2.0.0",
"grunt-contrib-concat": "^2.1.0",
"grunt-contrib-connect": "^3.0.0",
"grunt-contrib-qunit": "^6.2.0",
"grunt-contrib-qunit": "^7.0.1",
"grunt-contrib-uglify": "^5.0.1",
"grunt-contrib-watch": "^1.1.0",
"grunt-eslint": "^24.0.0",
"grunt-eslint": "^24.0.1",
"grunt-git-describe": "^2.4.4",
"grunt-istanbul": "^0.8.0",
"grunt-text-replace": "^0.4.0",
"qunitjs": "2.4.1"
"qunit": "^2.19.4"
},
"scripts": {
"test": "grunt test",
"prepare": "grunt build"
"prepare": "grunt build",
"build": "grunt build",
"dev": "grunt dev"
}
}
}

View file

@ -2,7 +2,7 @@
* OpenSeadragon - Button
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@ -167,13 +167,6 @@ $.Button = function( options ) {
this.imgDown.style.visibility =
"hidden";
if ($.Browser.vendor === $.BROWSERS.FIREFOX && $.Browser.version < 3) {
this.imgGroup.style.top =
this.imgHover.style.top =
this.imgDown.style.top =
"";
}
this.element.appendChild( this.imgRest );
this.element.appendChild( this.imgGroup );
this.element.appendChild( this.imgHover );

View file

@ -2,7 +2,7 @@
* OpenSeadragon - ButtonGroup
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@ -112,6 +112,17 @@ $.ButtonGroup = function( options ) {
/** @lends OpenSeadragon.ButtonGroup.prototype */
$.ButtonGroup.prototype = {
/**
* Adds the given button to this button group.
*
* @function
* @param {OpenSeadragon.Button} button
*/
addButton: function( button ){
this.buttons.push(button);
this.element.appendChild(button.element);
},
/**
* TODO: Figure out why this is used on the public API and if a more useful
* api can be created.

1054
src/canvasdrawer.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@
* OpenSeadragon - Control
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@ -194,11 +194,7 @@ $.Control.prototype = {
* @param {Number} opactiy - a value between 1 and 0 inclusively.
*/
setOpacity: function( opacity ) {
if ( this.element[ $.SIGNAL ] && $.Browser.vendor === $.BROWSERS.IE ) {
$.setElementOpacity( this.element, opacity, true );
} else {
$.setElementOpacity( this.wrapper, opacity, true );
}
$.setElementOpacity( this.wrapper, opacity, true );
}
};

View file

@ -2,7 +2,7 @@
* OpenSeadragon - ControlDock
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@ -59,7 +59,9 @@
if( this.element ){
this.element = $.getElement( this.element );
this.element.appendChild( this.container );
this.element.style.position = 'relative';
if( $.getElementStyle(this.element).position === 'static' ){
this.element.style.position = 'relative';
}
this.container.style.width = '100%';
this.container.style.height = '100%';
}

488
src/datatypeconvertor.js Normal file
View file

@ -0,0 +1,488 @@
/*
* OpenSeadragon.convertor (static property)
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2024 OpenSeadragon contributors
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of CodePlex Foundation nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
(function($){
/**
* modified from https://gist.github.com/Prottoy2938/66849e04b0bac459606059f5f9f3aa1a
* @private
*/
class WeightedGraph {
constructor() {
this.adjacencyList = {};
this.vertices = {};
}
/**
* Add vertex to graph
* @param vertex unique vertex ID
* @return {boolean} true if inserted, false if exists (no-op)
*/
addVertex(vertex) {
if (!this.vertices[vertex]) {
this.vertices[vertex] = new $.PriorityQueue.Node(0, vertex);
this.adjacencyList[vertex] = [];
return true;
}
return false;
}
/**
* Add edge to graph
* @param vertex1 id, must exist by calling addVertex()
* @param vertex2 id, must exist by calling addVertex()
* @param weight
* @param transform function that transforms on path vertex1 -> vertex2
* @return {boolean} true if new edge, false if replaced existing
*/
addEdge(vertex1, vertex2, weight, transform) {
if (weight < 0) {
$.console.error("WeightedGraph: negative weights will make for invalid shortest path computation!");
}
const outgoingPaths = this.adjacencyList[vertex1],
replacedEdgeIndex = outgoingPaths.findIndex(edge => edge.target === this.vertices[vertex2]),
newEdge = { target: this.vertices[vertex2], origin: this.vertices[vertex1], weight, transform };
if (replacedEdgeIndex < 0) {
this.adjacencyList[vertex1].push(newEdge);
return true;
}
this.adjacencyList[vertex1][replacedEdgeIndex] = newEdge;
return false;
}
/**
* @return {{path: ConversionStep[], cost: number}|undefined} cheapest path from start to finish
*/
dijkstra(start, finish) {
let path = []; //to return at end
if (start === finish) {
return {path: path, cost: 0};
}
const nodes = new OpenSeadragon.PriorityQueue();
let smallestNode;
//build up initial state
for (let vertex in this.vertices) {
vertex = this.vertices[vertex];
if (vertex.value === start) {
vertex.key = 0; //keys are known distances
nodes.insertNode(vertex);
} else {
vertex.key = Infinity;
delete vertex.index;
}
vertex._previous = null;
}
// as long as there is something to visit
while (nodes.getCount() > 0) {
smallestNode = nodes.remove();
if (smallestNode.value === finish) {
break;
}
const neighbors = this.adjacencyList[smallestNode.value];
for (let neighborKey in neighbors) {
let edge = neighbors[neighborKey];
//relax node
let newCost = smallestNode.key + edge.weight;
let nextNeighbor = edge.target;
if (newCost < nextNeighbor.key) {
nextNeighbor._previous = smallestNode;
//key change
nodes.decreaseKey(nextNeighbor, newCost);
}
}
}
if (!smallestNode || !smallestNode._previous || smallestNode.value !== finish) {
return undefined; //no path
}
let finalCost = smallestNode.key; //final weight last node
// done, build the shortest path
while (smallestNode._previous) {
//backtrack
const to = smallestNode.value,
parent = smallestNode._previous,
from = parent.value;
path.push(this.adjacencyList[from].find(x => x.target.value === to));
smallestNode = parent;
}
return {
path: path.reverse(),
cost: finalCost
};
}
}
/**
* Edge.transform function on the conversion path in OpenSeadragon.converter.getConversionPath().
* It can be also conversion to undefined if used as destructor implementation.
*
* @callback TypeConvertor
* @memberof OpenSeadragon
* @param {OpenSeadragon.Tile} tile reference tile that owns the data
* @param {any} data data in the input format
* @returns {any} data in the output format
*/
/**
* Destructor called every time a data type is to be destroyed or converted to another type.
*
* @callback TypeDestructor
* @memberof OpenSeadragon
* @param {any} data data in the format the destructor is registered for
* @returns {any} can return any value that is carried over to the caller if desirable.
* Note: not used by the OSD cache system.
*/
/**
* Node on the conversion path in OpenSeadragon.converter.getConversionPath().
*
* @typedef {Object} ConversionStep
* @memberof OpenSeadragon
* @param {OpenSeadragon.PriorityQueue.Node} target - Target node of the conversion step.
* Its value is the target format.
* @param {OpenSeadragon.PriorityQueue.Node} origin - Origin node of the conversion step.
* Its value is the origin format.
* @param {number} weight cost of the conversion
* @param {TypeConvertor} transform the conversion itself
*/
/**
* Class that orchestrates automated data types conversion. Do not instantiate
* this class, use OpenSeadragon.convertor - a global instance, instead.
* @class DataTypeConvertor
* @memberOf OpenSeadragon
*/
$.DataTypeConvertor = class {
constructor() {
this.graph = new WeightedGraph();
this.destructors = {};
this.copyings = {};
// Teaching OpenSeadragon built-in conversions:
const imageCreator = (tile, url) => new $.Promise((resolve, reject) => {
if (!$.supportsAsync) {
throw "Not supported in sync mode!";
}
const img = new Image();
img.onerror = img.onabort = reject;
img.onload = () => resolve(img);
img.src = url;
});
const canvasContextCreator = (tile, imageData) => {
const canvas = document.createElement( 'canvas' );
canvas.width = imageData.width;
canvas.height = imageData.height;
const context = canvas.getContext('2d', { willReadFrequently: true });
context.drawImage( imageData, 0, 0 );
return context;
};
this.learn("context2d", "webImageUrl", (tile, ctx) => ctx.canvas.toDataURL(), 1, 2);
this.learn("image", "webImageUrl", (tile, image) => image.url);
this.learn("image", "context2d", canvasContextCreator, 1, 1);
this.learn("url", "image", imageCreator, 1, 1);
//Copies
this.learn("image", "image", (tile, image) => imageCreator(tile, image.src), 1, 1);
this.learn("url", "url", (tile, url) => url, 0, 1); //strings are immutable, no need to copy
this.learn("context2d", "context2d", (tile, ctx) => canvasContextCreator(tile, ctx.canvas));
/**
* Free up canvas memory
* (iOS 12 or higher on 2GB RAM device has only 224MB canvas memory,
* and Safari keeps canvas until its height and width will be set to 0).
*/
this.learnDestroy("context2d", ctx => {
ctx.canvas.width = 0;
ctx.canvas.height = 0;
});
}
/**
* Unique identifier (unlike toString.call(x)) to be guessed
* from the data value. This type guess is more strict than
* OpenSeadragon.type() implementation, but for most type recognition
* this test relies on the output of OpenSeadragon.type().
*
* Note: although we try to implement the type guessing, do
* not rely on this functionality! Prefer explicit type declaration.
*
* @function guessType
* @param x object to get unique identifier for
* - can be array, in that case, alphabetically-ordered list of inner unique types
* is returned (null, undefined are ignored)
* - if $.isPlainObject(x) is true, then the object can define
* getType function to specify its type
* - otherwise, toString.call(x) is applied to get the parameter description
* @return {string} unique variable descriptor
*/
guessType( x ) {
if (Array.isArray(x)) {
const types = [];
for (let item of x) {
if (item === undefined || item === null) {
continue;
}
const type = this.guessType(item);
if (!types.includes(type)) {
types.push(type);
}
}
types.sort();
return `Array [${types.join(",")}]`;
}
const guessType = $.type(x);
if (guessType === "dom-node") {
//distinguish nodes
return guessType.nodeName.toLowerCase();
}
if (guessType === "object") {
if ($.isFunction(x.getType)) {
return x.getType();
}
}
return guessType;
}
/**
* Teach the system to convert data type 'from' -> 'to'
* @param {string} from unique ID of the data item 'from'
* @param {string} to unique ID of the data item 'to'
* @param {OpenSeadragon.TypeConvertor} callback convertor that takes two arguments: a tile reference, and
* a data object of a type 'from'; and converts this data object to type 'to'. It can return also the value
* wrapped in a Promise (returned in resolve) or it can be async function.
* @param {Number} [costPower=0] positive cost class of the conversion, smaller or equal than 7.
* Should reflect the actual cost of the conversion:
* - if nothing must be done and only reference is retrieved (or a constant operation done),
* return 0 (default)
* - if a linear amount of work is necessary,
* return 1
* ... and so on, basically the number in O() complexity power exponent (for simplification)
* @param {Number} [costMultiplier=1] multiplier of the cost class, e.g. O(3n^2) would
* use costPower=2, costMultiplier=3; can be between 1 and 10^5
*/
learn(from, to, callback, costPower = 0, costMultiplier = 1) {
$.console.assert(costPower >= 0 && costPower <= 7, "[DataTypeConvertor] Conversion costPower must be between <0, 7>.");
$.console.assert($.isFunction(callback), "[DataTypeConvertor:learn] Callback must be a valid function!");
if (from === to) {
this.copyings[to] = callback;
} else {
//we won't know if somebody added multiple edges, though it will choose some edge anyway
costPower++;
costMultiplier = Math.min(Math.max(costMultiplier, 1), 10 ^ 5);
this.graph.addVertex(from);
this.graph.addVertex(to);
this.graph.addEdge(from, to, costPower * 10 ^ 5 + costMultiplier, callback);
this._known = {}; //invalidate precomputed paths :/
}
}
/**
* Teach the system to destroy data type 'type'
* for example, textures loaded to GPU have to be also manually removed when not needed anymore.
* Needs to be defined only when the created object has extra deletion process.
* @param {string} type
* @param {OpenSeadragon.TypeDestructor} callback destructor, receives the object created,
* it is basically a type conversion to 'undefined' - thus the type.
*/
learnDestroy(type, callback) {
this.destructors[type] = callback;
}
/**
* Convert data item x of type 'from' to any of the 'to' types, chosen is the cheapest known conversion.
* Data is destroyed upon conversion. For different behavior, implement your conversion using the
* path rules obtained from getConversionPath().
* Note: conversion DOES NOT COPY data if [to] contains type 'from' (e.g., the cheapest conversion is no conversion).
* It automatically calls destructor on immediate types, but NOT on the x and the result. You should call these
* manually if these should be destroyed.
* @param {OpenSeadragon.Tile} tile
* @param {any} data data item to convert
* @param {string} from data item type
* @param {string} to desired type(s)
* @return {OpenSeadragon.Promise<?>} promise resolution with type 'to' or undefined if the conversion failed
*/
convert(tile, data, from, ...to) {
const conversionPath = this.getConversionPath(from, to);
if (!conversionPath) {
$.console.error(`[OpenSeadragon.convertor.convert] Conversion ${from} ---> ${to} cannot be done!`);
return $.Promise.resolve();
}
const stepCount = conversionPath.length,
_this = this;
const step = (x, i, destroy = true) => {
if (i >= stepCount) {
return $.Promise.resolve(x);
}
let edge = conversionPath[i];
let y = edge.transform(tile, x);
if (y === undefined) {
$.console.error(`[OpenSeadragon.convertor.convert] data mid result undefined value (while converting using %s)`, edge);
return $.Promise.resolve();
}
//node.value holds the type string
if (destroy) {
_this.destroy(x, edge.origin.value);
}
const result = $.type(y) === "promise" ? y : $.Promise.resolve(y);
return result.then(res => step(res, i + 1));
};
//destroy only mid-results, but not the original value
return step(data, 0, false);
}
/**
* Copy the data item given.
* @param {OpenSeadragon.Tile} tile
* @param {any} data data item to convert
* @param {string} type data type
* @return {OpenSeadragon.Promise<?>|undefined} promise resolution with data passed from constructor
*/
copy(tile, data, type) {
const copyTransform = this.copyings[type];
if (copyTransform) {
const y = copyTransform(tile, data);
return $.type(y) === "promise" ? y : $.Promise.resolve(y);
}
$.console.warn(`[OpenSeadragon.convertor.copy] is not supported with type %s`, type);
return $.Promise.resolve(undefined);
}
/**
* Destroy the data item given.
* @param {string} type data type
* @param {any} data
* @return {OpenSeadragon.Promise<any>|undefined} promise resolution with data passed from constructor, or undefined
* if not such conversion exists
*/
destroy(data, type) {
const destructor = this.destructors[type];
if (destructor) {
const y = destructor(data);
return $.type(y) === "promise" ? y : $.Promise.resolve(y);
}
return undefined;
}
/**
* Get possible system type conversions and cache result.
* @param {string} from data item type
* @param {string|string[]} to array of accepted types
* @return {ConversionStep[]|undefined} array of required conversions (returns empty array
* for from===to), or undefined if the system cannot convert between given types.
* Each object has 'transform' function that converts between neighbouring types, such
* that x = arr[i].transform(x) is valid input for convertor arr[i+1].transform(), e.g.
* arr[i+1].transform(arr[i].transform( ... )) is a valid conversion procedure.
*
* Note: if a function is returned, it is a callback called once the data is ready.
*/
getConversionPath(from, to) {
let bestConvertorPath, selectedType;
let knownFrom = this._known[from];
if (!knownFrom) {
this._known[from] = knownFrom = {};
}
if (Array.isArray(to)) {
$.console.assert(to.length > 0, "[getConversionPath] conversion 'to' type must be defined.");
let bestCost = Infinity;
//FIXME: pre-compute all paths in 'to' array? could be efficient for multiple
// type system, but overhead for simple use cases... now we just use the first type if costs unknown
selectedType = to[0];
for (const outType of to) {
const conversion = knownFrom[outType];
if (conversion && bestCost > conversion.cost) {
bestConvertorPath = conversion;
bestCost = conversion.cost;
selectedType = outType;
}
}
} else {
$.console.assert(typeof to === "string", "[getConversionPath] conversion 'to' type must be defined.");
bestConvertorPath = knownFrom[to];
selectedType = to;
}
if (!bestConvertorPath) {
bestConvertorPath = this.graph.dijkstra(from, selectedType);
this._known[from][selectedType] = bestConvertorPath;
}
return bestConvertorPath ? bestConvertorPath.path : undefined;
}
/**
* Return a list of known conversion types
* @return {string[]}
*/
getKnownTypes() {
return Object.keys(this.graph.vertices);
}
/**
* Check whether given type is known to the convertor
* @param {string} type type to test
* @return {boolean}
*/
existsType(type) {
return !!this.graph.vertices[type];
}
};
/**
* Static convertor available throughout OpenSeadragon.
*
* Built-in conversions include types:
* - context2d canvas 2d context
* - image HTMLImage element
* - url url string carrying or pointing to 2D raster data
* - canvas HTMLCanvas element
*
* @type OpenSeadragon.DataTypeConvertor
* @memberOf OpenSeadragon
*/
$.convertor = new $.DataTypeConvertor();
}(OpenSeadragon));

View file

@ -2,7 +2,7 @@
* OpenSeadragon - DisplayRect
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are

View file

@ -1,767 +0,0 @@
/*
* OpenSeadragon - Drawer
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of CodePlex Foundation nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
(function( $ ){
/**
* @class Drawer
* @memberof OpenSeadragon
* @classdesc Handles rendering of tiles for an {@link OpenSeadragon.Viewer}.
* @param {Object} options - Options for this Drawer.
* @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this Drawer.
* @param {OpenSeadragon.Viewport} options.viewport - Reference to Viewer viewport.
* @param {Element} options.element - Parent element.
* @param {Number} [options.debugGridColor] - See debugGridColor in {@link OpenSeadragon.Options} for details.
*/
$.Drawer = function( options ) {
$.console.assert( options.viewer, "[Drawer] options.viewer is required" );
//backward compatibility for positional args while preferring more
//idiomatic javascript options object as the only argument
var args = arguments;
if( !$.isPlainObject( options ) ){
options = {
source: args[ 0 ], // Reference to Viewer tile source.
viewport: args[ 1 ], // Reference to Viewer viewport.
element: args[ 2 ] // Parent element.
};
}
$.console.assert( options.viewport, "[Drawer] options.viewport is required" );
$.console.assert( options.element, "[Drawer] options.element is required" );
if ( options.source ) {
$.console.error( "[Drawer] options.source is no longer accepted; use TiledImage instead" );
}
this.viewer = options.viewer;
this.viewport = options.viewport;
this.debugGridColor = typeof options.debugGridColor === 'string' ? [options.debugGridColor] : options.debugGridColor || $.DEFAULT_SETTINGS.debugGridColor;
if (options.opacity) {
$.console.error( "[Drawer] options.opacity is no longer accepted; set the opacity on the TiledImage instead" );
}
this.useCanvas = $.supportsCanvas && ( this.viewer ? this.viewer.useCanvas : true );
/**
* The parent element of this Drawer instance, passed in when the Drawer was created.
* The parent of {@link OpenSeadragon.Drawer#canvas}.
* @member {Element} container
* @memberof OpenSeadragon.Drawer#
*/
this.container = $.getElement( options.element );
/**
* A &lt;canvas&gt; element if the browser supports them, otherwise a &lt;div&gt; element.
* Child element of {@link OpenSeadragon.Drawer#container}.
* @member {Element} canvas
* @memberof OpenSeadragon.Drawer#
*/
this.canvas = $.makeNeutralElement( this.useCanvas ? "canvas" : "div" );
/**
* 2d drawing context for {@link OpenSeadragon.Drawer#canvas} if it's a &lt;canvas&gt; element, otherwise null.
* @member {Object} context
* @memberof OpenSeadragon.Drawer#
*/
this.context = this.useCanvas ? this.canvas.getContext( "2d" ) : null;
/**
* Sketch canvas used to temporarily draw tiles which cannot be drawn directly
* to the main canvas due to opacity. Lazily initialized.
*/
this.sketchCanvas = null;
this.sketchContext = null;
/**
* @member {Element} element
* @memberof OpenSeadragon.Drawer#
* @deprecated Alias for {@link OpenSeadragon.Drawer#container}.
*/
this.element = this.container;
// We force our container to ltr because our drawing math doesn't work in rtl.
// This issue only affects our canvas renderer, but we do it always for consistency.
// Note that this means overlays you want to be rtl need to be explicitly set to rtl.
this.container.dir = 'ltr';
// check canvas available width and height, set canvas width and height such that the canvas backing store is set to the proper pixel density
if (this.useCanvas) {
var viewportSize = this._calculateCanvasSize();
this.canvas.width = viewportSize.x;
this.canvas.height = viewportSize.y;
}
this.canvas.style.width = "100%";
this.canvas.style.height = "100%";
this.canvas.style.position = "absolute";
$.setElementOpacity( this.canvas, this.opacity, true );
// Allow pointer events to pass through the canvas element so implicit
// pointer capture works on touch devices
$.setElementPointerEventsNone( this.canvas );
$.setElementTouchActionNone( this.canvas );
// explicit left-align
this.container.style.textAlign = "left";
this.container.appendChild( this.canvas );
// Image smoothing for canvas rendering (only if canvas is used).
// Canvas default is "true", so this will only be changed if user specified "false".
this._imageSmoothingEnabled = true;
};
/** @lends OpenSeadragon.Drawer.prototype */
$.Drawer.prototype = {
// deprecated
addOverlay: function( element, location, placement, onDraw ) {
$.console.error("drawer.addOverlay is deprecated. Use viewer.addOverlay instead.");
this.viewer.addOverlay( element, location, placement, onDraw );
return this;
},
// deprecated
updateOverlay: function( element, location, placement ) {
$.console.error("drawer.updateOverlay is deprecated. Use viewer.updateOverlay instead.");
this.viewer.updateOverlay( element, location, placement );
return this;
},
// deprecated
removeOverlay: function( element ) {
$.console.error("drawer.removeOverlay is deprecated. Use viewer.removeOverlay instead.");
this.viewer.removeOverlay( element );
return this;
},
// deprecated
clearOverlays: function() {
$.console.error("drawer.clearOverlays is deprecated. Use viewer.clearOverlays instead.");
this.viewer.clearOverlays();
return this;
},
/**
* This function converts the given point from to the drawer coordinate by
* multiplying it with the pixel density.
* This function does not take rotation into account, thus assuming provided
* point is at 0 degree.
* @param {OpenSeadragon.Point} point - the pixel point to convert
* @returns {OpenSeadragon.Point} Point in drawer coordinate system.
*/
viewportCoordToDrawerCoord: function(point) {
var vpPoint = this.viewport.pixelFromPointNoRotate(point, true);
return new $.Point(
vpPoint.x * $.pixelDensityRatio,
vpPoint.y * $.pixelDensityRatio
);
},
/**
* This function will create multiple polygon paths on the drawing context by provided polygons,
* then clip the context to the paths.
* @param {OpenSeadragon.Point[][]} polygons - an array of polygons. A polygon is an array of OpenSeadragon.Point
* @param {Boolean} useSketch - Whether to use the sketch canvas or not.
*/
clipWithPolygons: function (polygons, useSketch) {
if (!this.useCanvas) {
return;
}
var context = this._getContext(useSketch);
context.beginPath();
polygons.forEach(function (polygon) {
polygon.forEach(function (coord, i) {
context[i === 0 ? 'moveTo' : 'lineTo'](coord.x, coord.y);
});
});
context.clip();
},
/**
* Set the opacity of the drawer.
* @param {Number} opacity
* @returns {OpenSeadragon.Drawer} Chainable.
*/
setOpacity: function( opacity ) {
$.console.error("drawer.setOpacity is deprecated. Use tiledImage.setOpacity instead.");
var world = this.viewer.world;
for (var i = 0; i < world.getItemCount(); i++) {
world.getItemAt( i ).setOpacity( opacity );
}
return this;
},
/**
* Get the opacity of the drawer.
* @returns {Number}
*/
getOpacity: function() {
$.console.error("drawer.getOpacity is deprecated. Use tiledImage.getOpacity instead.");
var world = this.viewer.world;
var maxOpacity = 0;
for (var i = 0; i < world.getItemCount(); i++) {
var opacity = world.getItemAt( i ).getOpacity();
if ( opacity > maxOpacity ) {
maxOpacity = opacity;
}
}
return maxOpacity;
},
// deprecated
needsUpdate: function() {
$.console.error( "[Drawer.needsUpdate] this function is deprecated. Use World.needsDraw instead." );
return this.viewer.world.needsDraw();
},
// deprecated
numTilesLoaded: function() {
$.console.error( "[Drawer.numTilesLoaded] this function is deprecated. Use TileCache.numTilesLoaded instead." );
return this.viewer.tileCache.numTilesLoaded();
},
// deprecated
reset: function() {
$.console.error( "[Drawer.reset] this function is deprecated. Use World.resetItems instead." );
this.viewer.world.resetItems();
return this;
},
// deprecated
update: function() {
$.console.error( "[Drawer.update] this function is deprecated. Use Drawer.clear and World.draw instead." );
this.clear();
this.viewer.world.draw();
return this;
},
/**
* @returns {Boolean} True if rotation is supported.
*/
canRotate: function() {
return this.useCanvas;
},
/**
* Destroy the drawer (unload current loaded tiles)
*/
destroy: function() {
//force unloading of current canvas (1x1 will be gc later, trick not necessarily needed)
this.canvas.width = 1;
this.canvas.height = 1;
this.sketchCanvas = null;
this.sketchContext = null;
},
/**
* Clears the Drawer so it's ready to draw another frame.
*/
clear: function() {
this.canvas.innerHTML = "";
if ( this.useCanvas ) {
var viewportSize = this._calculateCanvasSize();
if( this.canvas.width !== viewportSize.x ||
this.canvas.height !== viewportSize.y ) {
this.canvas.width = viewportSize.x;
this.canvas.height = viewportSize.y;
this._updateImageSmoothingEnabled(this.context);
if ( this.sketchCanvas !== null ) {
var sketchCanvasSize = this._calculateSketchCanvasSize();
this.sketchCanvas.width = sketchCanvasSize.x;
this.sketchCanvas.height = sketchCanvasSize.y;
this._updateImageSmoothingEnabled(this.sketchContext);
}
}
this._clear();
}
},
_clear: function (useSketch, bounds) {
if (!this.useCanvas) {
return;
}
var context = this._getContext(useSketch);
if (bounds) {
context.clearRect(bounds.x, bounds.y, bounds.width, bounds.height);
} else {
var canvas = context.canvas;
context.clearRect(0, 0, canvas.width, canvas.height);
}
},
/**
* Scale from OpenSeadragon viewer rectangle to drawer rectangle
* (ignoring rotation)
* @param {OpenSeadragon.Rect} rectangle - The rectangle in viewport coordinate system.
* @returns {OpenSeadragon.Rect} Rectangle in drawer coordinate system.
*/
viewportToDrawerRectangle: function(rectangle) {
var topLeft = this.viewport.pixelFromPointNoRotate(rectangle.getTopLeft(), true);
var size = this.viewport.deltaPixelsFromPointsNoRotate(rectangle.getSize(), true);
return new $.Rect(
topLeft.x * $.pixelDensityRatio,
topLeft.y * $.pixelDensityRatio,
size.x * $.pixelDensityRatio,
size.y * $.pixelDensityRatio
);
},
/**
* Draws the given tile.
* @param {OpenSeadragon.Tile} tile - The tile to draw.
* @param {Function} drawingHandler - Method for firing the drawing event if using canvas.
* drawingHandler({context, tile, rendered})
* @param {Boolean} useSketch - Whether to use the sketch canvas or not.
* where <code>rendered</code> is the context with the pre-drawn image.
* @param {Float} [scale=1] - Apply a scale to tile position and size. Defaults to 1.
* @param {OpenSeadragon.Point} [translate] A translation vector to offset tile position
* @param {Boolean} [shouldRoundPositionAndSize] - Tells whether to round
* position and size of tiles supporting alpha channel in non-transparency
* context.
* @param {OpenSeadragon.TileSource} source - The source specification of the tile.
*/
drawTile: function( tile, drawingHandler, useSketch, scale, translate, shouldRoundPositionAndSize, source) {
$.console.assert(tile, '[Drawer.drawTile] tile is required');
$.console.assert(drawingHandler, '[Drawer.drawTile] drawingHandler is required');
if (this.useCanvas) {
var context = this._getContext(useSketch);
scale = scale || 1;
tile.drawCanvas(context, drawingHandler, scale, translate, shouldRoundPositionAndSize, source);
} else {
tile.drawHTML( this.canvas );
}
},
_getContext: function( useSketch ) {
var context = this.context;
if ( useSketch ) {
if (this.sketchCanvas === null) {
this.sketchCanvas = document.createElement( "canvas" );
var sketchCanvasSize = this._calculateSketchCanvasSize();
this.sketchCanvas.width = sketchCanvasSize.x;
this.sketchCanvas.height = sketchCanvasSize.y;
this.sketchContext = this.sketchCanvas.getContext( "2d" );
// If the viewport is not currently rotated, the sketchCanvas
// will have the same size as the main canvas. However, if
// the viewport get rotated later on, we will need to resize it.
if (this.viewport.getRotation() === 0) {
var self = this;
this.viewer.addHandler('rotate', function resizeSketchCanvas() {
if (self.viewport.getRotation() === 0) {
return;
}
self.viewer.removeHandler('rotate', resizeSketchCanvas);
var sketchCanvasSize = self._calculateSketchCanvasSize();
self.sketchCanvas.width = sketchCanvasSize.x;
self.sketchCanvas.height = sketchCanvasSize.y;
});
}
this._updateImageSmoothingEnabled(this.sketchContext);
}
context = this.sketchContext;
}
return context;
},
// private
saveContext: function( useSketch ) {
if (!this.useCanvas) {
return;
}
this._getContext( useSketch ).save();
},
// private
restoreContext: function( useSketch ) {
if (!this.useCanvas) {
return;
}
this._getContext( useSketch ).restore();
},
// private
setClip: function(rect, useSketch) {
if (!this.useCanvas) {
return;
}
var context = this._getContext( useSketch );
context.beginPath();
context.rect(rect.x, rect.y, rect.width, rect.height);
context.clip();
},
// private
drawRectangle: function(rect, fillStyle, useSketch) {
if (!this.useCanvas) {
return;
}
var context = this._getContext( useSketch );
context.save();
context.fillStyle = fillStyle;
context.fillRect(rect.x, rect.y, rect.width, rect.height);
context.restore();
},
/**
* Blends the sketch canvas in the main canvas.
* @param {Object} options The options
* @param {Float} options.opacity The opacity of the blending.
* @param {Float} [options.scale=1] The scale at which tiles were drawn on
* the sketch. Default is 1.
* Use scale to draw at a lower scale and then enlarge onto the main canvas.
* @param {OpenSeadragon.Point} [options.translate] A translation vector
* that was used to draw the tiles
* @param {String} [options.compositeOperation] - How the image is
* composited onto other images; see compositeOperation in
* {@link OpenSeadragon.Options} for possible values.
* @param {OpenSeadragon.Rect} [options.bounds] The part of the sketch
* canvas to blend in the main canvas. If specified, options.scale and
* options.translate get ignored.
*/
blendSketch: function(opacity, scale, translate, compositeOperation) {
var options = opacity;
if (!$.isPlainObject(options)) {
options = {
opacity: opacity,
scale: scale,
translate: translate,
compositeOperation: compositeOperation
};
}
if (!this.useCanvas || !this.sketchCanvas) {
return;
}
opacity = options.opacity;
compositeOperation = options.compositeOperation;
var bounds = options.bounds;
this.context.save();
this.context.globalAlpha = opacity;
if (compositeOperation) {
this.context.globalCompositeOperation = compositeOperation;
}
if (bounds) {
// Internet Explorer, Microsoft Edge, and Safari have problems
// when you call context.drawImage with negative x or y
// or x + width or y + height greater than the canvas width or height respectively.
if (bounds.x < 0) {
bounds.width += bounds.x;
bounds.x = 0;
}
if (bounds.x + bounds.width > this.canvas.width) {
bounds.width = this.canvas.width - bounds.x;
}
if (bounds.y < 0) {
bounds.height += bounds.y;
bounds.y = 0;
}
if (bounds.y + bounds.height > this.canvas.height) {
bounds.height = this.canvas.height - bounds.y;
}
this.context.drawImage(
this.sketchCanvas,
bounds.x,
bounds.y,
bounds.width,
bounds.height,
bounds.x,
bounds.y,
bounds.width,
bounds.height
);
} else {
scale = options.scale || 1;
translate = options.translate;
var position = translate instanceof $.Point ?
translate : new $.Point(0, 0);
var widthExt = 0;
var heightExt = 0;
if (translate) {
var widthDiff = this.sketchCanvas.width - this.canvas.width;
var heightDiff = this.sketchCanvas.height - this.canvas.height;
widthExt = Math.round(widthDiff / 2);
heightExt = Math.round(heightDiff / 2);
}
this.context.drawImage(
this.sketchCanvas,
position.x - widthExt * scale,
position.y - heightExt * scale,
(this.canvas.width + 2 * widthExt) * scale,
(this.canvas.height + 2 * heightExt) * scale,
-widthExt,
-heightExt,
this.canvas.width + 2 * widthExt,
this.canvas.height + 2 * heightExt
);
}
this.context.restore();
},
// private
drawDebugInfo: function(tile, count, i, tiledImage) {
if ( !this.useCanvas ) {
return;
}
var colorIndex = this.viewer.world.getIndexOfItem(tiledImage) % this.debugGridColor.length;
var context = this.context;
context.save();
context.lineWidth = 2 * $.pixelDensityRatio;
context.font = 'small-caps bold ' + (13 * $.pixelDensityRatio) + 'px arial';
context.strokeStyle = this.debugGridColor[colorIndex];
context.fillStyle = this.debugGridColor[colorIndex];
if (this.viewport.getRotation(true) % 360 !== 0 ) {
this._offsetForRotation({degrees: this.viewport.getRotation(true)});
}
if (tiledImage.getRotation(true) % 360 !== 0) {
this._offsetForRotation({
degrees: tiledImage.getRotation(true),
point: tiledImage.viewport.pixelFromPointNoRotate(
tiledImage._getRotationPoint(true), true)
});
}
if (tiledImage.viewport.getRotation(true) % 360 === 0 &&
tiledImage.getRotation(true) % 360 === 0) {
if(tiledImage._drawer.viewer.viewport.getFlip()) {
tiledImage._drawer._flip();
}
}
context.strokeRect(
tile.position.x * $.pixelDensityRatio,
tile.position.y * $.pixelDensityRatio,
tile.size.x * $.pixelDensityRatio,
tile.size.y * $.pixelDensityRatio
);
var tileCenterX = (tile.position.x + (tile.size.x / 2)) * $.pixelDensityRatio;
var tileCenterY = (tile.position.y + (tile.size.y / 2)) * $.pixelDensityRatio;
// Rotate the text the right way around.
context.translate( tileCenterX, tileCenterY );
context.rotate( Math.PI / 180 * -this.viewport.getRotation(true) );
context.translate( -tileCenterX, -tileCenterY );
if( tile.x === 0 && tile.y === 0 ){
context.fillText(
"Zoom: " + this.viewport.getZoom(),
tile.position.x * $.pixelDensityRatio,
(tile.position.y - 30) * $.pixelDensityRatio
);
context.fillText(
"Pan: " + this.viewport.getBounds().toString(),
tile.position.x * $.pixelDensityRatio,
(tile.position.y - 20) * $.pixelDensityRatio
);
}
context.fillText(
"Level: " + tile.level,
(tile.position.x + 10) * $.pixelDensityRatio,
(tile.position.y + 20) * $.pixelDensityRatio
);
context.fillText(
"Column: " + tile.x,
(tile.position.x + 10) * $.pixelDensityRatio,
(tile.position.y + 30) * $.pixelDensityRatio
);
context.fillText(
"Row: " + tile.y,
(tile.position.x + 10) * $.pixelDensityRatio,
(tile.position.y + 40) * $.pixelDensityRatio
);
context.fillText(
"Order: " + i + " of " + count,
(tile.position.x + 10) * $.pixelDensityRatio,
(tile.position.y + 50) * $.pixelDensityRatio
);
context.fillText(
"Size: " + tile.size.toString(),
(tile.position.x + 10) * $.pixelDensityRatio,
(tile.position.y + 60) * $.pixelDensityRatio
);
context.fillText(
"Position: " + tile.position.toString(),
(tile.position.x + 10) * $.pixelDensityRatio,
(tile.position.y + 70) * $.pixelDensityRatio
);
if (this.viewport.getRotation(true) % 360 !== 0 ) {
this._restoreRotationChanges();
}
if (tiledImage.getRotation(true) % 360 !== 0) {
this._restoreRotationChanges();
}
if (tiledImage.viewport.getRotation(true) % 360 === 0 &&
tiledImage.getRotation(true) % 360 === 0) {
if(tiledImage._drawer.viewer.viewport.getFlip()) {
tiledImage._drawer._flip();
}
}
context.restore();
},
// private
debugRect: function(rect) {
if ( this.useCanvas ) {
var context = this.context;
context.save();
context.lineWidth = 2 * $.pixelDensityRatio;
context.strokeStyle = this.debugGridColor[0];
context.fillStyle = this.debugGridColor[0];
context.strokeRect(
rect.x * $.pixelDensityRatio,
rect.y * $.pixelDensityRatio,
rect.width * $.pixelDensityRatio,
rect.height * $.pixelDensityRatio
);
context.restore();
}
},
/**
* Turns image smoothing on or off for this viewer. Note: Ignored in some (especially older) browsers that do not support this property.
*
* @function
* @param {Boolean} [imageSmoothingEnabled] - Whether or not the image is
* drawn smoothly on the canvas; see imageSmoothingEnabled in
* {@link OpenSeadragon.Options} for more explanation.
*/
setImageSmoothingEnabled: function(imageSmoothingEnabled){
if ( this.useCanvas ) {
this._imageSmoothingEnabled = imageSmoothingEnabled;
this._updateImageSmoothingEnabled(this.context);
this.viewer.forceRedraw();
}
},
// private
_updateImageSmoothingEnabled: function(context){
context.msImageSmoothingEnabled = this._imageSmoothingEnabled;
context.imageSmoothingEnabled = this._imageSmoothingEnabled;
},
/**
* Get the canvas size
* @param {Boolean} sketch If set to true return the size of the sketch canvas
* @returns {OpenSeadragon.Point} The size of the canvas
*/
getCanvasSize: function(sketch) {
var canvas = this._getContext(sketch).canvas;
return new $.Point(canvas.width, canvas.height);
},
getCanvasCenter: function() {
return new $.Point(this.canvas.width / 2, this.canvas.height / 2);
},
// private
_offsetForRotation: function(options) {
var point = options.point ?
options.point.times($.pixelDensityRatio) :
this.getCanvasCenter();
var context = this._getContext(options.useSketch);
context.save();
context.translate(point.x, point.y);
if(this.viewer.viewport.flipped){
context.rotate(Math.PI / 180 * -options.degrees);
context.scale(-1, 1);
} else{
context.rotate(Math.PI / 180 * options.degrees);
}
context.translate(-point.x, -point.y);
},
// private
_flip: function(options) {
options = options || {};
var point = options.point ?
options.point.times($.pixelDensityRatio) :
this.getCanvasCenter();
var context = this._getContext(options.useSketch);
context.translate(point.x, 0);
context.scale(-1, 1);
context.translate(-point.x, 0);
},
// private
_restoreRotationChanges: function(useSketch) {
var context = this._getContext(useSketch);
context.restore();
},
// private
_calculateCanvasSize: function() {
var pixelDensityRatio = $.pixelDensityRatio;
var viewportSize = this.viewport.getContainerSize();
return {
// canvas width and height are integers
x: Math.round(viewportSize.x * pixelDensityRatio),
y: Math.round(viewportSize.y * pixelDensityRatio)
};
},
// private
_calculateSketchCanvasSize: function() {
var canvasSize = this._calculateCanvasSize();
if (this.viewport.getRotation() === 0) {
return canvasSize;
}
// If the viewport is rotated, we need a larger sketch canvas in order
// to support edge smoothing.
var sketchCanvasSize = Math.ceil(Math.sqrt(
canvasSize.x * canvasSize.x +
canvasSize.y * canvasSize.y));
return {
x: sketchCanvasSize,
y: sketchCanvasSize
};
}
};
}( OpenSeadragon ));

426
src/drawerbase.js Normal file
View file

@ -0,0 +1,426 @@
/*
* OpenSeadragon - DrawerBase
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of CodePlex Foundation nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
(function( $ ){
/**
* @typedef BaseDrawerOptions
* @memberOf OpenSeadragon
* @property {boolean} [usePrivateCache=false] specify whether the drawer should use
* detached (=internal) cache object in case it has to perform custom type conversion atop
* what cache performs. In that case, drawer must implement internalCacheCreate() which gets data in one
* of formats the drawer declares as supported. This method must return object to be used during drawing.
* You should probably implement also internalCacheFree() to provide cleanup logics.
*
* @property {boolean} [preloadCache=true]
* When internalCacheCreate is used, it can be applied offline (asynchronously) during data processing = preloading,
* or just in time before rendering (if necessary). Preloading supports
*/
const OpenSeadragon = $; // (re)alias back to OpenSeadragon for JSDoc
/**
* @class OpenSeadragon.DrawerBase
* @classdesc Base class for Drawers that handle rendering of tiles for an {@link OpenSeadragon.Viewer}.
* @param {Object} options - Options for this Drawer.
* @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this Drawer.
* @param {OpenSeadragon.Viewport} options.viewport - Reference to Viewer viewport.
* @param {HTMLElement} options.element - Parent element.
* @abstract
*/
OpenSeadragon.DrawerBase = class DrawerBase{
constructor(options){
$.console.assert( options.viewer, "[Drawer] options.viewer is required" );
$.console.assert( options.viewport, "[Drawer] options.viewport is required" );
$.console.assert( options.element, "[Drawer] options.element is required" );
this._id = this.getType() + $.now();
this.viewer = options.viewer;
this.viewport = options.viewport;
this.debugGridColor = typeof options.debugGridColor === 'string' ? [options.debugGridColor] : options.debugGridColor || $.DEFAULT_SETTINGS.debugGridColor;
this.options = $.extend({}, this.defaultOptions, options.options);
this.container = $.getElement( options.element );
this._renderingTarget = this._createDrawingElement();
this.canvas.style.width = "100%";
this.canvas.style.height = "100%";
this.canvas.style.position = "absolute";
// set canvas.style.left = 0 so the canvas is positioned properly in ltr and rtl html
this.canvas.style.left = "0";
$.setElementOpacity( this.canvas, this.viewer.opacity, true );
// Allow pointer events to pass through the canvas element so implicit
// pointer capture works on touch devices
$.setElementPointerEventsNone( this.canvas );
$.setElementTouchActionNone( this.canvas );
// explicit left-align
this.container.style.textAlign = "left";
this.container.appendChild( this.canvas );
this._checkInterfaceImplementation();
this.setInternalCacheNeedsRefresh(); // initializes stamp
}
/**
* Retrieve default options for the current drawer.
* The base implementation provides default shared options.
* Overrides should enumerate all defaults or extend from this implementation.
* return $.extend({}, super.options, { ... custom drawer instance options ... });
* @returns {BaseDrawerOptions} common options
*/
get defaultOptions() {
return {
usePrivateCache: false,
preloadCache: true,
};
}
// protect the canvas member with a getter
get canvas(){
return this._renderingTarget;
}
get element(){
$.console.error('Drawer.element is deprecated. Use Drawer.container instead.');
return this.container;
}
/**
* Get unique drawer ID
* @return {string}
*/
getId() {
return this._id;
}
/**
* @abstract
* @returns {String | undefined} What type of drawer this is. Must be overridden by extending classes.
*/
getType(){
$.console.error('Drawer.getType must be implemented by child class');
return undefined;
}
/**
* Retrieve required data formats the data must be converted to.
* This list MUST BE A VALID SUBSET OF getSupportedDataFormats()
* @abstract
* @return {string[]}
*/
getRequiredDataFormats() {
return this.getSupportedDataFormats();
}
/**
* Retrieve data types
* @abstract
* @return {string[]}
*/
getSupportedDataFormats() {
throw "Drawer.getSupportedDataFormats must define its supported rendering data types!";
}
/**
* Check a particular cache record is compatible.
* This function _MUST_ be called: if it returns a falsey
* value, the rendering _MUST NOT_ proceed. It should
* await next animation frames and check again for availability.
* @param {OpenSeadragon.Tile} tile
* @return {any|undefined} undefined if cache not available, compatible data otherwise.
*/
getDataToDraw(tile) {
const cache = tile.getCache(tile.cacheKey);
if (!cache) {
$.console.warn("Attempt to draw tile %s when not cached!", tile);
return undefined;
}
const dataCache = cache.getDataForRendering(this, tile);
return dataCache && dataCache.data;
}
/**
* @abstract
* @returns {Boolean} Whether the drawer implementation is supported by the browser. Must be overridden by extending classes.
*/
static isSupported() {
$.console.error('Drawer.isSupported must be implemented by child class');
}
/**
* @abstract
* @returns {Element} the element to draw into
* @private
*/
_createDrawingElement() {
$.console.error('Drawer._createDrawingElement must be implemented by child class');
return null;
}
/**
* @abstract
* @param {Array} tiledImages - An array of TiledImages that are ready to be drawn.
* @private
*/
draw(tiledImages) {
$.console.error('Drawer.draw must be implemented by child class');
}
/**
* @abstract
* @returns {Boolean} True if rotation is supported.
*/
canRotate() {
$.console.error('Drawer.canRotate must be implemented by child class');
}
/**
* @abstract
*/
destroy() {
$.console.error('Drawer.destroy must be implemented by child class');
}
/**
* Destroy internal cache. Should be called within destroy() when
* usePrivateCache is set to true. Ensures cleanup of anything created
* by internalCacheCreate(...).
*/
destroyInternalCache() {
this.viewer.tileCache.clearDrawerInternalCache(this);
}
/**
* @param {TiledImage} tiledImage the tiled image that is calling the function
* @returns {Boolean} Whether this drawer requires enforcing minimum tile overlap to avoid showing seams.
* @private
*/
minimumOverlapRequired(tiledImage) {
return false;
}
/**
* @abstract
* @param {Boolean} [imageSmoothingEnabled] - Whether or not the image is
* drawn smoothly on the canvas; see imageSmoothingEnabled in
* {@link OpenSeadragon.Options} for more explanation.
*/
setImageSmoothingEnabled(imageSmoothingEnabled){
$.console.error('Drawer.setImageSmoothingEnabled must be implemented by child class');
}
/**
* Optional public API to draw a rectangle (e.g. for debugging purposes)
* Child classes can override this method if they wish to support this
* @param {OpenSeadragon.Rect} rect
*/
drawDebuggingRect(rect) {
$.console.warn('[drawer].drawDebuggingRect is not implemented by this drawer');
}
// Deprecated functions
clear(){
$.console.warn('[drawer].clear() is deprecated. The drawer is responsible for clearing itself as needed before drawing tiles.');
}
/**
* If options.usePrivateCache is true, this method MUST RETURN the private cache content
* @param {OpenSeadragon.CacheRecord} cache
* @param {OpenSeadragon.Tile} tile
* @return any
*/
internalCacheCreate(cache, tile) {}
/**
* It is possible to perform any necessary cleanup on internal cache, necessary if you
* need to clean up some memory (e.g. destroy canvas by setting with & height to 0).
* @param {*} data object returned by internalCacheCreate(...)
*/
internalCacheFree(data) {}
/**
* Call to invalidate internal cache. It will be rebuilt. With synchronous converions,
* it will be rebuilt immediatelly. With asynchronous, it will be rebuilt once invalidation
* routine happens, e.g. you should call also requestInvalidate() if you need to happen
* it as soon as possible.
*/
setInternalCacheNeedsRefresh() {
this._dataNeedsRefresh = $.now();
}
// Private functions
/**
* Ensures that child classes have provided implementations for public API methods
* draw, canRotate, destroy, and setImageSmoothinEnabled. Throws an exception if the original
* placeholder methods are still in place.
* @private
*
*/
_checkInterfaceImplementation(){
if (this._createDrawingElement === $.DrawerBase.prototype._createDrawingElement) {
throw(new Error("[drawer]._createDrawingElement must be implemented by child class"));
}
if (this.draw === $.DrawerBase.prototype.draw) {
throw(new Error("[drawer].draw must be implemented by child class"));
}
if (this.canRotate === $.DrawerBase.prototype.canRotate) {
throw(new Error("[drawer].canRotate must be implemented by child class"));
}
if (this.destroy === $.DrawerBase.prototype.destroy) {
throw(new Error("[drawer].destroy must be implemented by child class"));
}
if (this.setImageSmoothingEnabled === $.DrawerBase.prototype.setImageSmoothingEnabled) {
throw(new Error("[drawer].setImageSmoothingEnabled must be implemented by child class"));
}
}
// Utility functions
/**
* Scale from OpenSeadragon viewer rectangle to drawer rectangle
* (ignoring rotation)
* @param {OpenSeadragon.Rect} rectangle - The rectangle in viewport coordinate system.
* @returns {OpenSeadragon.Rect} Rectangle in drawer coordinate system.
*/
viewportToDrawerRectangle(rectangle) {
var topLeft = this.viewport.pixelFromPointNoRotate(rectangle.getTopLeft(), true);
var size = this.viewport.deltaPixelsFromPointsNoRotate(rectangle.getSize(), true);
return new $.Rect(
topLeft.x * $.pixelDensityRatio,
topLeft.y * $.pixelDensityRatio,
size.x * $.pixelDensityRatio,
size.y * $.pixelDensityRatio
);
}
/**
* This function converts the given point from to the drawer coordinate by
* multiplying it with the pixel density.
* This function does not take rotation into account, thus assuming provided
* point is at 0 degree.
* @param {OpenSeadragon.Point} point - the pixel point to convert
* @returns {OpenSeadragon.Point} Point in drawer coordinate system.
*/
viewportCoordToDrawerCoord(point) {
var vpPoint = this.viewport.pixelFromPointNoRotate(point, true);
return new $.Point(
vpPoint.x * $.pixelDensityRatio,
vpPoint.y * $.pixelDensityRatio
);
}
// Internal utility functions
/**
* Calculate width and height of the canvas based on viewport dimensions
* and pixelDensityRatio
* @private
* @returns {OpenSeadragon.Point} {x, y} size of the canvas
*/
_calculateCanvasSize() {
var pixelDensityRatio = $.pixelDensityRatio;
var viewportSize = this.viewport.getContainerSize();
return new OpenSeadragon.Point( Math.round(viewportSize.x * pixelDensityRatio), Math.round(viewportSize.y * pixelDensityRatio));
}
/**
* Called by implementations to fire the tiled-image-drawn event (used by tests)
* @private
*/
_raiseTiledImageDrawnEvent(tiledImage, tiles){
if(!this.viewer) {
return;
}
/**
* Raised when a tiled image is drawn to the canvas. Used internally for testing.
* The update-viewport event is preferred if you want to know when a frame has been drawn.
*
* @event tiled-image-drawn
* @memberof OpenSeadragon.Viewer
* @type {object}
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
* @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.
* @property {Array} tiles - An array of Tile objects that were drawn.
* @property {?Object} userData - Arbitrary subscriber-defined object.
* @private
*/
this.viewer.raiseEvent( 'tiled-image-drawn', {
tiledImage: tiledImage,
tiles: tiles,
});
}
/**
* Called by implementations to fire the drawer-error event
* @private
*/
_raiseDrawerErrorEvent(tiledImage, errorMessage){
if(!this.viewer) {
return;
}
/**
* Raised when a tiled image is drawn to the canvas. Used internally for testing.
* The update-viewport event is preferred if you want to know when a frame has been drawn.
*
* @event drawer-error
* @memberof OpenSeadragon.Viewer
* @type {object}
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
* @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.
* @property {OpenSeadragon.DrawerBase} drawer - The drawer that raised the error.
* @property {String} error - A message describing the error.
* @property {?Object} userData - Arbitrary subscriber-defined object.
* @protected
*/
this.viewer.raiseEvent( 'drawer-error', {
tiledImage: tiledImage,
drawer: this,
error: errorMessage,
});
}
};
}( OpenSeadragon ));

View file

@ -2,7 +2,7 @@
* OpenSeadragon - DziTileSource
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@ -167,6 +167,14 @@ $.extend( $.DziTileSource.prototype, $.TileSource.prototype, /** @lends OpenSead
},
/**
* Equality comparator
*/
equals: function(otherSource) {
return this.tilesUrl === otherSource.tilesUrl;
},
/**
* @function
* @param {Number} level

View file

@ -2,7 +2,7 @@
* OpenSeadragon - EventSource
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@ -37,9 +37,19 @@
/**
* Event handler method signature used by all OpenSeadragon events.
*
* @callback EventHandler
* @typedef {function(OpenSeadragon.Event): void} OpenSeadragon.EventHandler
* @memberof OpenSeadragon
* @param {Object} event - See individual events for event-specific properties.
* @param {OpenSeadragon.Event} event - The event object containing event-specific properties.
* @returns {void} This handler does not return a value.
*/
/**
* Event handler method signature used by all OpenSeadragon events.
*
* @typedef {function(OpenSeadragon.Event): Promise<void>} OpenSeadragon.AsyncEventHandler
* @memberof OpenSeadragon
* @param {OpenSeadragon.Event} event - The event object containing event-specific properties.
* @returns {Promise<void>} This handler does not return a value.
*/
@ -51,6 +61,7 @@
*/
$.EventSource = function() {
this.events = {};
this._rejectedEventList = {};
};
/** @lends OpenSeadragon.EventSource.prototype */
@ -58,62 +69,79 @@ $.EventSource.prototype = {
/**
* Add an event handler to be triggered only once (or a given number of times)
* for a given event.
* for a given event. It is not removable with removeHandler().
* @function
* @param {String} eventName - Name of event to register.
* @param {OpenSeadragon.EventHandler} handler - Function to call when event
* @param {OpenSeadragon.EventHandler|OpenSeadragon.AsyncEventHandler} handler - Function to call when event
* is triggered.
* @param {Object} [userData=null] - Arbitrary object to be passed unchanged
* to the handler.
* @param {Number} [times=1] - The number of times to handle the event
* before removing it.
* @param {Number} [priority=0] - Handler priority. By default, all priorities are 0. Higher number = priority.
* @returns {Boolean} - True if the handler was added, false if it was rejected
*/
addOnceHandler: function(eventName, handler, userData, times) {
var self = this;
addOnceHandler: function(eventName, handler, userData, times, priority) {
const self = this;
times = times || 1;
var count = 0;
var onceHandler = function(event) {
let count = 0;
const onceHandler = function(event) {
count++;
if (count === times) {
self.removeHandler(eventName, onceHandler);
}
handler(event);
return handler(event);
};
this.addHandler(eventName, onceHandler, userData);
return this.addHandler(eventName, onceHandler, userData, priority);
},
/**
* Add an event handler for a given event.
* @function
* @param {String} eventName - Name of event to register.
* @param {OpenSeadragon.EventHandler} handler - Function to call when event is triggered.
* @param {OpenSeadragon.EventHandler|OpenSeadragon.AsyncEventHandler} handler - Function to call when event is triggered.
* @param {Object} [userData=null] - Arbitrary object to be passed unchanged to the handler.
* @param {Number} [priority=0] - Handler priority. By default, all priorities are 0. Higher number = priority.
* @returns {Boolean} - True if the handler was added, false if it was rejected
*/
addHandler: function ( eventName, handler, userData ) {
var events = this.events[ eventName ];
addHandler: function ( eventName, handler, userData, priority ) {
if(Object.prototype.hasOwnProperty.call(this._rejectedEventList, eventName)){
$.console.error(`Error adding handler for ${eventName}. ${this._rejectedEventList[eventName]}`);
return false;
}
let events = this.events[ eventName ];
if ( !events ) {
this.events[ eventName ] = events = [];
}
if ( handler && $.isFunction( handler ) ) {
events[ events.length ] = { handler: handler, userData: userData || null };
let index = events.length,
event = { handler: handler, userData: userData || null, priority: priority || 0 };
events[ index ] = event;
while ( index > 0 && events[ index - 1 ].priority < events[ index ].priority ) {
events[ index ] = events[ index - 1 ];
events[ index - 1 ] = event;
index--;
}
}
return true;
},
/**
* Remove a specific event handler for a given event.
* @function
* @param {String} eventName - Name of event for which the handler is to be removed.
* @param {OpenSeadragon.EventHandler} handler - Function to be removed.
* @param {OpenSeadragon.EventHandler|OpenSeadragon.AsyncEventHandler} handler - Function to be removed.
*/
removeHandler: function ( eventName, handler ) {
var events = this.events[ eventName ],
handlers = [],
i;
const events = this.events[ eventName ],
handlers = [];
if ( !events ) {
return;
}
if ( $.isArray( events ) ) {
for ( i = 0; i < events.length; i++ ) {
for ( let i = 0; i < events.length; i++ ) {
if ( events[i].handler !== handler ) {
handlers.push( events[ i ] );
}
@ -128,7 +156,7 @@ $.EventSource.prototype = {
* @returns {number} amount of events
*/
numberOfHandlers: function (eventName) {
var events = this.events[ eventName ];
const events = this.events[ eventName ];
if ( !events ) {
return 0;
}
@ -145,7 +173,7 @@ $.EventSource.prototype = {
if ( eventName ){
this.events[ eventName ] = [];
} else{
for ( var eventType in this.events ) {
for ( let eventType in this.events ) {
this.events[ eventType ] = [];
}
}
@ -156,8 +184,8 @@ $.EventSource.prototype = {
* @function
* @param {String} eventName - Name of event to get handlers for.
*/
getHandler: function ( eventName ) {
var events = this.events[ eventName ];
getHandler: function ( eventName) {
let events = this.events[ eventName ];
if ( !events || !events.length ) {
return null;
}
@ -165,9 +193,8 @@ $.EventSource.prototype = {
[ events[ 0 ] ] :
Array.apply( null, events );
return function ( source, args ) {
var i,
length = events.length;
for ( i = 0; i < length; i++ ) {
let length = events.length;
for ( let i = 0; i < length; i++ ) {
if ( events[ i ] ) {
args.eventSource = source;
args.userData = events[ i ].userData;
@ -178,23 +205,106 @@ $.EventSource.prototype = {
},
/**
* Trigger an event, optionally passing additional information.
* Get a function which iterates the list of all handlers registered for a given event,
* calling the handler for each and awaiting async ones.
* @function
* @param {String} eventName - Name of event to get handlers for.
* @param {any} bindTarget - Bound target to return with the promise on finish
*/
getAwaitingHandler: function ( eventName, bindTarget ) {
let events = this.events[ eventName ];
if ( !events || !events.length ) {
return null;
}
events = events.length === 1 ?
[ events[ 0 ] ] :
Array.apply( null, events );
return function ( source, args ) {
// We return a promise that gets resolved after all the events finish.
// Returning loop result is not correct, loop promises chain dynamically
// and outer code could process finishing logics in the middle of event loop.
return new $.Promise(resolve => {
const length = events.length;
function loop(index) {
if ( index >= length || !events[ index ] ) {
resolve(bindTarget);
return null;
}
args.eventSource = source;
args.userData = events[ index ].userData;
let result = events[ index ].handler( args );
result = (!result || $.type(result) !== "promise") ? $.Promise.resolve() : result;
return result.then(() => loop(index + 1));
}
loop(0);
});
};
},
/**
* Trigger an event, optionally passing additional information. Does not await async handlers, i.e.
* OpenSeadragon.AsyncEventHandler.
* @function
* @param {String} eventName - Name of event to register.
* @param {Object} eventArgs - Event-specific data.
* @returns {Boolean} True if the event was fired, false if it was rejected because of rejectEventHandler(eventName)
*/
raiseEvent: function( eventName, eventArgs ) {
//uncomment if you want to get a log of all events
//$.console.log( eventName );
var handler = this.getHandler( eventName );
//$.console.log( "Event fired:", eventName );
if ( handler ) {
if ( !eventArgs ) {
eventArgs = {};
}
handler( this, eventArgs );
if(Object.prototype.hasOwnProperty.call(this._rejectedEventList, eventName)){
$.console.error(`Error adding handler for ${eventName}. ${this._rejectedEventList[eventName]}`);
return false;
}
const handler = this.getHandler( eventName );
if ( handler ) {
handler( this, eventArgs || {} );
}
return true;
},
/**
* Trigger an event, optionally passing additional information.
* This events awaits every asynchronous or promise-returning function, i.e.
* OpenSeadragon.AsyncEventHandler.
* @param {String} eventName - Name of event to register.
* @param {Object} eventArgs - Event-specific data.
* @param {?} [bindTarget = null] - Promise-resolved value on the event finish
* @return {OpenSeadragon.Promise|undefined} - Promise resolved upon the event completion.
*/
raiseEventAwaiting: function ( eventName, eventArgs, bindTarget = null ) {
//uncomment if you want to get a log of all events
//$.console.log( "Awaiting event fired:", eventName );
const awaitingHandler = this.getAwaitingHandler(eventName, bindTarget);
if (awaitingHandler) {
return awaitingHandler(this, eventArgs || {});
}
return $.Promise.resolve(bindTarget);
},
/**
* Set an event name as being disabled, and provide an optional error message
* to be printed to the console
* @param {String} eventName - Name of the event
* @param {String} [errorMessage] - Optional string to print to the console
* @private
*/
rejectEventHandler(eventName, errorMessage = ''){
this._rejectedEventList[eventName] = errorMessage;
},
/**
* Explicitly allow an event handler to be added for this event type, undoing
* the effects of rejectEventHandler
* @param {String} eventName - Name of the event
* @private
*/
allowEventHandler(eventName){
delete this._rejectedEventList[eventName];
}
};

View file

@ -2,7 +2,7 @@
* OpenSeadragon - full-screen support functions
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@ -67,10 +67,14 @@
return document.fullscreenElement;
};
fullScreenApi.requestFullScreen = function( element ) {
return element.requestFullscreen();
return element.requestFullscreen().catch(function (msg) {
$.console.error('Fullscreen request failed: ', msg);
});
};
fullScreenApi.exitFullScreen = function() {
document.exitFullscreen();
document.exitFullscreen().catch(function (msg) {
$.console.error('Error while exiting fullscreen: ', msg);
});
};
fullScreenApi.fullScreenEventName = "fullscreenchange";
fullScreenApi.fullScreenErrorEventName = "fullscreenerror";

288
src/htmldrawer.js Normal file
View file

@ -0,0 +1,288 @@
/*
* OpenSeadragon - HTMLDrawer
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of CodePlex Foundation nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
(function( $ ){
const OpenSeadragon = $; // alias back for JSDoc
/**
* @class OpenSeadragon.HTMLDrawer
* @extends OpenSeadragon.DrawerBase
* @classdesc HTML-based implementation of DrawerBase for an {@link OpenSeadragon.Viewer}.
* @param {Object} options - Options for this Drawer.
* @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this Drawer.
* @param {OpenSeadragon.Viewport} options.viewport - Reference to Viewer viewport.
* @param {Element} options.element - Parent element.
* @param {Number} [options.debugGridColor] - See debugGridColor in {@link OpenSeadragon.Options} for details.
*/
class HTMLDrawer extends OpenSeadragon.DrawerBase{
constructor(options){
super(options);
/**
* The HTML element (div) that this drawer uses for drawing
* @member {Element} canvas
* @memberof OpenSeadragon.HTMLDrawer#
*/
/**
* The parent element of this Drawer instance, passed in when the Drawer was created.
* The parent of {@link OpenSeadragon.WebGLDrawer#canvas}.
* @member {Element} container
* @memberof OpenSeadragon.HTMLDrawer#
*/
// Reject listening for the tile-drawing event, which this drawer does not fire
this.viewer.rejectEventHandler("tile-drawing", "The HTMLDrawer does not raise the tile-drawing event");
// Since the tile-drawn event is fired by this drawer, make sure handlers can be added for it
this.viewer.allowEventHandler("tile-drawn");
// works with canvas & image objects
function _prepareTile(tile, data) {
const element = $.makeNeutralElement( "div" );
const imgElement = data.cloneNode();
imgElement.style.msInterpolationMode = "nearest-neighbor";
imgElement.style.width = "100%";
imgElement.style.height = "100%";
const style = element.style;
style.position = "absolute";
return {
element, imgElement, style, data
};
}
// The actual placing logics will not happen at draw event, but when the cache is created:
$.convertor.learn("context2d", HTMLDrawer.canvasCacheType, (t, d) => _prepareTile(t, d.canvas), 1, 1);
$.convertor.learn("image", HTMLDrawer.imageCacheType, _prepareTile, 1, 1);
// Also learn how to move back, since these elements can be just used as-is
$.convertor.learn(HTMLDrawer.canvasCacheType, "context2d", (t, d) => d.data.getContext('2d'), 1, 3);
$.convertor.learn(HTMLDrawer.imageCacheType, "image", (t, d) => d.data, 1, 3);
function _freeTile(data) {
if ( data.imgElement && data.imgElement.parentNode ) {
data.imgElement.parentNode.removeChild( data.imgElement );
}
if ( data.element && data.element.parentNode ) {
data.element.parentNode.removeChild( data.element );
}
}
$.convertor.learnDestroy(HTMLDrawer.canvasCacheType, _freeTile);
$.convertor.learnDestroy(HTMLDrawer.imageCacheType, _freeTile);
}
static get imageCacheType() {
return 'htmlDrawer[image]';
}
static get canvasCacheType() {
return 'htmlDrawer[canvas]';
}
/**
* @returns {Boolean} always true
*/
static isSupported() {
return true;
}
/**
*
* @returns 'html'
*/
getType(){
return 'html';
}
getSupportedDataFormats() {
return [HTMLDrawer.imageCacheType, HTMLDrawer.canvasCacheType];
}
/**
* @param {TiledImage} tiledImage the tiled image that is calling the function
* @returns {Boolean} Whether this drawer requires enforcing minimum tile overlap to avoid showing seams.
* @private
*/
minimumOverlapRequired(tiledImage) {
return true;
}
/**
* create the HTML element (e.g. canvas, div) that the image will be drawn into
* @returns {Element} the div to draw into
*/
_createDrawingElement(){
return $.makeNeutralElement("div");
}
/**
* Draws the TiledImages
*/
draw(tiledImages) {
var _this = this;
this._prepareNewFrame(); // prepare to draw a new frame
tiledImages.forEach(function(tiledImage){
if (tiledImage.opacity !== 0) {
_this._drawTiles(tiledImage);
}
});
}
/**
* @returns {Boolean} False - rotation is not supported.
*/
canRotate() {
return false;
}
/**
* Destroy the drawer (unload current loaded tiles)
*/
destroy() {
this.container.removeChild(this.canvas);
}
/**
* This function is ignored by the HTML Drawer. Implementing it is required by DrawerBase.
* @param {Boolean} [imageSmoothingEnabled] - Whether or not the image is
* drawn smoothly on the canvas; see imageSmoothingEnabled in
* {@link OpenSeadragon.Options} for more explanation.
*/
setImageSmoothingEnabled(){
// noop - HTML Drawer does not deal with this property
}
/**
* Clears the Drawer so it's ready to draw another frame.
* @private
*
*/
_prepareNewFrame() {
this.canvas.innerHTML = "";
}
/**
* Draws a TiledImage.
* @private
*
*/
_drawTiles( tiledImage ) {
var lastDrawn = tiledImage.getTilesToDraw().map(info => info.tile);
if (tiledImage.opacity === 0 || (lastDrawn.length === 0 && !tiledImage.placeholderFillStyle)) {
return;
}
// Iterate over the tiles to draw, and draw them
for (var i = lastDrawn.length - 1; i >= 0; i--) {
var tile = lastDrawn[ i ];
this._drawTile( tile );
if( this.viewer ){
/**
* Raised when a tile is drawn to the canvas. Only valid for
* context2d and html drawers.
*
* @event tile-drawn
* @memberof OpenSeadragon.Viewer
* @type {object}
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
* @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.
* @property {OpenSeadragon.Tile} tile
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
this.viewer.raiseEvent( 'tile-drawn', {
tiledImage: tiledImage,
tile: tile
});
}
}
}
/**
* Draws the given tile.
* @private
* @param {OpenSeadragon.Tile} tile - The tile to draw.
* @param {Function} drawingHandler - Method for firing the drawing event if using canvas.
* drawingHandler({context, tile, rendered})
*/
_drawTile( tile ) {
$.console.assert(tile, '[Drawer._drawTile] tile is required');
let container = this.canvas;
if ( !tile.loaded ) {
$.console.warn(
"Attempting to draw tile %s when it's not yet loaded.",
tile.toString()
);
return;
}
//EXPERIMENTAL - trying to figure out how to scale the container
// content during animation of the container size.
const dataObject = this.getDataToDraw(tile);
if (!dataObject) {
return;
}
if ( dataObject.element.parentNode !== container ) {
container.appendChild( dataObject.element );
}
if ( dataObject.imgElement.parentNode !== dataObject.element ) {
dataObject.element.appendChild( dataObject.imgElement );
}
dataObject.style.top = tile.position.y + "px";
dataObject.style.left = tile.position.x + "px";
dataObject.style.height = tile.size.y + "px";
dataObject.style.width = tile.size.x + "px";
if (tile.flipped) {
dataObject.style.transform = "scaleX(-1)";
}
$.setElementOpacity( dataObject.element, tile.opacity );
}
}
$.HTMLDrawer = HTMLDrawer;
}( OpenSeadragon ));

View file

@ -2,7 +2,7 @@
* OpenSeadragon - IIIFTileSource
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@ -141,6 +141,18 @@ $.IIIFTileSource = function( options ){
}
}
// Create an array with our exact resolution sizes if these have been supplied
if( this.sizes ) {
var sizeLength = this.sizes.length;
if ( (sizeLength === options.maxLevel) || (sizeLength === options.maxLevel + 1) ) {
this.levelSizes = this.sizes.slice().sort(( size1, size2 ) => size1.width - size2.width);
// Need to take into account that the list may or may not include the full resolution size
if( sizeLength === options.maxLevel ) {
this.levelSizes.push( {width: this.width, height: this.height} );
}
}
}
$.TileSource.apply( this, [ options ] );
};
@ -251,7 +263,7 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
if (data.preferredFormats) {
for (var f = 0; f < data.preferredFormats.length; f++ ) {
if ( OpenSeadragon.imageFormatSupported(data.preferredFormats[f]) ) {
if ( $.imageFormatSupported(data.preferredFormats[f]) ) {
data.tileFormat = data.preferredFormats[f];
break;
}
@ -333,7 +345,17 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
}
}
return $.TileSource.prototype.getNumTiles.call(this, level);
// Use supplied list of scaled resolution sizes if these exist
if( this.levelSizes ) {
var levelSize = this.levelSizes[level];
var x = Math.ceil( levelSize.width / this.getTileWidth(level) ),
y = Math.ceil( levelSize.height / this.getTileHeight(level) );
return new $.Point( x, y );
}
// Otherwise call default TileSource->getNumTiles() function
else {
return $.TileSource.prototype.getNumTiles.call(this, level);
}
},
@ -348,6 +370,34 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
return new $.Point(0, 0);
}
// Use supplied list of scaled resolution sizes if these exist
if( this.levelSizes ) {
var validPoint = point.x >= 0 && point.x <= 1 &&
point.y >= 0 && point.y <= 1 / this.aspectRatio;
$.console.assert(validPoint, "[TileSource.getTileAtPoint] must be called with a valid point.");
var widthScaled = this.levelSizes[level].width;
var pixelX = point.x * widthScaled;
var pixelY = point.y * widthScaled;
var x = Math.floor(pixelX / this.getTileWidth(level));
var y = Math.floor(pixelY / this.getTileHeight(level));
// When point.x == 1 or point.y == 1 / this.aspectRatio we want to
// return the last tile of the row/column
if (point.x >= 1) {
x = this.getNumTiles(level).x - 1;
}
var EPSILON = 1e-15;
if (point.y >= 1 / this.aspectRatio - EPSILON) {
y = this.getNumTiles(level).y - 1;
}
return new $.Point(x, y);
}
// Otherwise call default TileSource->getTileAtPoint() function
return $.TileSource.prototype.getTileAtPoint.call(this, level, point);
},
@ -375,10 +425,9 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
var IIIF_ROTATION = '0',
//## get the scale (level as a decimal)
scale = Math.pow( 0.5, this.maxLevel - level ),
//# image dimensions at this level
levelWidth = Math.round( this.width * scale ),
levelHeight = Math.round( this.height * scale ),
levelWidth,
levelHeight,
//## iiif region
tileWidth,
@ -396,6 +445,17 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
iiifQuality,
uri;
// Use supplied list of scaled resolution sizes if these exist
if( this.levelSizes ) {
levelWidth = this.levelSizes[level].width;
levelHeight = this.levelSizes[level].height;
}
// Otherwise calculate the sizes ourselves
else {
levelWidth = Math.ceil( this.width * scale );
levelHeight = Math.ceil( this.height * scale );
}
tileWidth = this.getTileWidth(level);
tileHeight = this.getTileHeight(level);
iiifTileSizeWidth = Math.round( tileWidth / scale );
@ -426,8 +486,8 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
} else {
iiifRegion = [ iiifTileX, iiifTileY, iiifTileW, iiifTileH ].join( ',' );
}
iiifSizeW = Math.round( iiifTileW * scale );
iiifSizeH = Math.round( iiifTileH * scale );
iiifSizeW = Math.min( tileWidth, levelWidth - (x * tileWidth) );
iiifSizeH = Math.min( tileHeight, levelHeight - (y * tileHeight) );
if ( this.version === 2 && iiifSizeW === this.width ) {
iiifSize = "full";
} else if ( this.version === 3 && iiifSizeW === this.width && iiifSizeH === this.height ) {
@ -443,6 +503,13 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
return uri;
},
/**
* Equality comparator
*/
equals: function(otherSource) {
return this._id === otherSource._id;
},
__testonly__: {
canBeTiled: canBeTiled,
constructLevels: constructLevels

View file

@ -2,7 +2,7 @@
* OpenSeadragon - ImageLoader
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@ -37,6 +37,8 @@
/**
* @class ImageJob
* @classdesc Handles downloading of a single image.
*
* @memberof OpenSeadragon
* @param {Object} options - Options for this ImageJob.
* @param {String} [options.src] - URL of image to download.
* @param {Tile} [options.tile] - Tile that belongs the data to.
@ -46,21 +48,23 @@
* @param {Boolean} [options.ajaxWithCredentials] - Whether to set withCredentials on AJAX requests.
* @param {String} [options.crossOriginPolicy] - CORS policy to use for downloads
* @param {String} [options.postData] - HTTP POST data (usually but not necessarily in k=v&k2=v2... form,
* see TileSource::getPostData) or null
* see TileSource::getTilePostData) or null
* @param {Function} [options.callback] - Called once image has been downloaded.
* @param {Function} [options.abort] - Called when this image job is aborted.
* @param {Number} [options.timeout] - The max number of milliseconds that this image job may take to complete.
* @param {Number} [options.tries] - Actual number of the current try.
*/
$.ImageJob = function(options) {
$.extend(true, this, {
timeout: $.DEFAULT_SETTINGS.timeout,
jobId: null
jobId: null,
tries: 0
}, options);
/**
* Data object which will contain downloaded image data.
* @member {Image|*} image data object, by default an Image object (depends on TileSource)
* @member {Image|*} data data object, by default an Image object (depends on TileSource)
* @memberof OpenSeadragon.ImageJob#
*/
this.data = null;
@ -85,13 +89,16 @@ $.ImageJob.prototype = {
/**
* Starts the image job.
* @method
* @memberof OpenSeadragon.ImageJob#
*/
start: function() {
this.tries++;
var self = this;
var selfAbort = this.abort;
this.jobId = window.setTimeout(function () {
self.finish(null, null, "Image load exceeded timeout (" + self.timeout + " ms)");
self.fail("Image load exceeded timeout (" + self.timeout + " ms)", null);
}, this.timeout);
this.abort = function() {
@ -108,17 +115,48 @@ $.ImageJob.prototype = {
* Finish this job.
* @param {*} data data that has been downloaded
* @param {XMLHttpRequest} request reference to the request if used
* @param {string} errorMessage description upon failure
* @param {string} dataType data type identifier
* fallback compatibility behavior: dataType treated as errorMessage if data is falsey value
* @memberof OpenSeadragon.ImageJob#
*/
finish: function(data, request, errorMessage ) {
finish: function(data, request, dataType) {
if (!this.jobId) {
return;
}
// old behavior, no deprecation due to possible finish calls with invalid data item (e.g. different error)
if (data === null || data === undefined || data === false) {
this.fail(dataType || "[downloadTileStart->finish()] Retrieved data is invalid!", request);
return;
}
this.data = data;
this.request = request;
this.errorMsg = errorMessage;
this.errorMsg = null;
this.dataType = dataType;
if (this.jobId) {
window.clearTimeout(this.jobId);
}
this.callback(this);
},
/**
* Finish this job as a failure.
* @param {string} errorMessage description upon failure
* @param {XMLHttpRequest} request reference to the request if used
*/
fail: function(errorMessage, request) {
this.data = null;
this.request = request;
this.errorMsg = errorMessage;
this.dataType = null;
if (this.jobId) {
window.clearTimeout(this.jobId);
this.jobId = null;
}
this.callback(this);
}
};
@ -138,6 +176,7 @@ $.ImageLoader = function(options) {
jobLimit: $.DEFAULT_SETTINGS.imageLoaderLimit,
timeout: $.DEFAULT_SETTINGS.timeout,
jobQueue: [],
failedTiles: [],
jobsInProgress: 0
}, options);
@ -158,11 +197,12 @@ $.ImageLoader.prototype = {
* @param {String} [options.ajaxHeaders] - Headers to add to the image request if using AJAX.
* @param {String|Boolean} [options.crossOriginPolicy] - CORS policy to use for downloads
* @param {String} [options.postData] - POST parameters (usually but not necessarily in k=v&k2=v2... form,
* see TileSource::getPostData) or null
* see TileSource::getTilePostData) or null
* @param {Boolean} [options.ajaxWithCredentials] - Whether to set withCredentials on AJAX
* requests.
* @param {Function} [options.callback] - Called once image has been downloaded.
* @param {Function} [options.abort] - Called when this image job is aborted.
* @returns {boolean} true if job was immediatelly started, false if queued
*/
addJob: function(options) {
if (!options.source) {
@ -175,10 +215,7 @@ $.ImageLoader.prototype = {
};
}
var _this = this,
complete = function(job) {
completeJob(_this, job, options.callback);
},
const _this = this,
jobOptions = {
src: options.src,
tile: options.tile || {},
@ -188,7 +225,7 @@ $.ImageLoader.prototype = {
crossOriginPolicy: options.crossOriginPolicy,
ajaxWithCredentials: options.ajaxWithCredentials,
postData: options.postData,
callback: complete,
callback: (job) => completeJob(_this, job, options.callback),
abort: options.abort,
timeout: this.timeout
},
@ -197,10 +234,17 @@ $.ImageLoader.prototype = {
if ( !this.jobLimit || this.jobsInProgress < this.jobLimit ) {
newJob.start();
this.jobsInProgress++;
return true;
}
else {
this.jobQueue.push( newJob );
}
this.jobQueue.push( newJob );
return false;
},
/**
* @returns {boolean} true if a job can be submitted
*/
canAcceptNewJob() {
return !this.jobLimit || this.jobsInProgress < this.jobLimit;
},
/**
@ -220,7 +264,8 @@ $.ImageLoader.prototype = {
};
/**
* Cleans up ImageJob once completed.
* Cleans up ImageJob once completed. Restarts job after tileRetryDelay seconds if failed
* but max tileRetryMax times
* @method
* @private
* @param loader - ImageLoader used to start job.
@ -228,17 +273,30 @@ $.ImageLoader.prototype = {
* @param callback - Called once cleanup is finished.
*/
function completeJob(loader, job, callback) {
var nextJob;
if (job.errorMsg && job.data === null && job.tries < 1 + loader.tileRetryMax) {
loader.failedTiles.push(job);
}
let nextJob;
loader.jobsInProgress--;
if ((!loader.jobLimit || loader.jobsInProgress < loader.jobLimit) && loader.jobQueue.length > 0) {
if (loader.canAcceptNewJob() && loader.jobQueue.length > 0) {
nextJob = loader.jobQueue.shift();
nextJob.start();
loader.jobsInProgress++;
}
callback(job.data, job.errorMsg, job.request);
if (loader.tileRetryMax > 0 && loader.jobQueue.length === 0) {
if (loader.canAcceptNewJob() && loader.failedTiles.length > 0) {
nextJob = loader.failedTiles.shift();
setTimeout(function () {
nextJob.start();
}, loader.tileRetryDelay);
loader.jobsInProgress++;
}
}
callback(job.data, job.errorMsg, job.request, job.dataType);
}
}(OpenSeadragon));

View file

@ -2,7 +2,7 @@
* OpenSeadragon - ImageTileSource
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@ -31,253 +31,235 @@
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
(function ($) {
/**
* @class ImageTileSource
* @classdesc The ImageTileSource allows a simple image to be loaded
* into an OpenSeadragon Viewer.
* There are 2 ways to open an ImageTileSource:
* 1. viewer.open({type: 'image', url: fooUrl});
* 2. viewer.open(new OpenSeadragon.ImageTileSource({url: fooUrl}));
*
* With the first syntax, the crossOriginPolicy, ajaxWithCredentials and
* useCanvas options are inherited from the viewer if they are not
* specified directly in the options object.
*
* @memberof OpenSeadragon
* @extends OpenSeadragon.TileSource
* @param {Object} options Options object.
* @param {String} options.url URL of the image
* @param {Boolean} [options.buildPyramid=true] If set to true (default), a
* pyramid will be built internally to provide a better downsampling.
* @param {String|Boolean} [options.crossOriginPolicy=false] Valid values are
* 'Anonymous', 'use-credentials', and false. If false, image requests will
* not use CORS preventing internal pyramid building for images from other
* domains.
* @param {String|Boolean} [options.ajaxWithCredentials=false] Whether to set
* the withCredentials XHR flag for AJAX requests (when loading tile sources).
* @param {Boolean} [options.useCanvas=true] Set to false to prevent any use
* of the canvas API.
*/
$.ImageTileSource = class extends $.TileSource {
/**
* @class ImageTileSource
* @classdesc The ImageTileSource allows a simple image to be loaded
* into an OpenSeadragon Viewer.
* There are 2 ways to open an ImageTileSource:
* 1. viewer.open({type: 'image', url: fooUrl});
* 2. viewer.open(new OpenSeadragon.ImageTileSource({url: fooUrl}));
*
* With the first syntax, the crossOriginPolicy, ajaxWithCredentials and
* useCanvas options are inherited from the viewer if they are not
* specified directly in the options object.
*
* @memberof OpenSeadragon
* @extends OpenSeadragon.TileSource
* @param {Object} options Options object.
* @param {String} options.url URL of the image
* @param {Boolean} [options.buildPyramid=true] If set to true (default), a
* pyramid will be built internally to provide a better downsampling.
* @param {String|Boolean} [options.crossOriginPolicy=false] Valid values are
* 'Anonymous', 'use-credentials', and false. If false, image requests will
* not use CORS preventing internal pyramid building for images from other
* domains.
* @param {String|Boolean} [options.ajaxWithCredentials=false] Whether to set
* the withCredentials XHR flag for AJAX requests (when loading tile sources).
* @param {Boolean} [options.useCanvas=true] Set to false to prevent any use
* of the canvas API.
*/
$.ImageTileSource = function (options) {
options = $.extend({
constructor(props) {
super($.extend({
buildPyramid: true,
crossOriginPolicy: false,
ajaxWithCredentials: false,
useCanvas: true
}, options);
$.TileSource.apply(this, [options]);
}, props));
}
};
/**
* Determine if the data and/or url imply the image service is supported by
* this tile source.
* @function
* @param {Object|Array} data
* @param {String} url - optional
*/
supports(data, url) {
return data.type && data.type === "image";
}
/**
*
* @function
* @param {Object} options - the options
* @param {String} dataUrl - the url the image was retrieved from, if any.
* @param {String} postData - HTTP POST data in k=v&k2=v2... form or null
* @returns {Object} options - A dictionary of keyword arguments sufficient
* to configure this tile sources constructor.
*/
configure(options, dataUrl, postData) {
return options;
}
/**
* Responsible for retrieving, and caching the
* image metadata pertinent to this TileSources implementation.
* @function
* @param {String} url
* @throws {Error}
*/
getImageInfo(url) {
const image = new Image(),
_this = this;
$.extend($.ImageTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.ImageTileSource.prototype */{
/**
* Determine if the data and/or url imply the image service is supported by
* this tile source.
* @function
* @param {Object|Array} data
* @param {String} optional - url
*/
supports: function (data, url) {
return data.type && data.type === "image";
},
/**
*
* @function
* @param {Object} options - the options
* @param {String} dataUrl - the url the image was retrieved from, if any.
* @param {String} postData - HTTP POST data in k=v&k2=v2... form or null
* @returns {Object} options - A dictionary of keyword arguments sufficient
* to configure this tile sources constructor.
*/
configure: function (options, dataUrl, postData) {
return options;
},
/**
* Responsible for retrieving, and caching the
* image metadata pertinent to this TileSources implementation.
* @function
* @param {String} url
* @throws {Error}
*/
getImageInfo: function (url) {
var image = this._image = new Image();
var _this = this;
if (this.crossOriginPolicy) {
image.crossOrigin = this.crossOriginPolicy;
}
if (this.ajaxWithCredentials) {
image.useCredentials = this.ajaxWithCredentials;
}
if (this.crossOriginPolicy) {
image.crossOrigin = this.crossOriginPolicy;
}
if (this.ajaxWithCredentials) {
image.useCredentials = this.ajaxWithCredentials;
}
$.addEvent(image, 'load', function () {
_this.width = image.naturalWidth;
_this.height = image.naturalHeight;
_this.aspectRatio = _this.width / _this.height;
_this.dimensions = new $.Point(_this.width, _this.height);
_this._tileWidth = _this.width;
_this._tileHeight = _this.height;
_this.tileOverlap = 0;
_this.minLevel = 0;
_this.image = image;
_this.levels = _this._buildLevels(image);
_this.maxLevel = _this.levels.length - 1;
$.addEvent(image, 'load', function () {
_this.width = image.naturalWidth;
_this.height = image.naturalHeight;
_this.aspectRatio = _this.width / _this.height;
_this.dimensions = new $.Point(_this.width, _this.height);
_this._tileWidth = _this.width;
_this._tileHeight = _this.height;
_this.tileOverlap = 0;
_this.minLevel = 0;
_this.levels = _this._buildLevels();
_this.maxLevel = _this.levels.length - 1;
_this.ready = true;
_this.ready = true;
// Note: this event is documented elsewhere, in TileSource
_this.raiseEvent('ready', {tileSource: _this});
});
// Note: this event is documented elsewhere, in TileSource
_this.raiseEvent('ready', {tileSource: _this});
$.addEvent(image, 'error', function () {
_this.image = null;
// Note: this event is documented elsewhere, in TileSource
_this.raiseEvent('open-failed', {
message: "Error loading image at " + url,
source: url
});
});
$.addEvent(image, 'error', function () {
// Note: this event is documented elsewhere, in TileSource
_this.raiseEvent('open-failed', {
message: "Error loading image at " + url,
source: url
});
});
image.src = url;
}
/**
* @function
* @param {Number} level
*/
getLevelScale(level) {
let levelScale = NaN;
if (level >= this.minLevel && level <= this.maxLevel) {
levelScale =
this.levels[level].width /
this.levels[this.maxLevel].width;
}
return levelScale;
}
/**
* @function
* @param {Number} level
*/
getNumTiles(level) {
if (this.getLevelScale(level)) {
return new $.Point(1, 1);
}
return new $.Point(0, 0);
}
/**
* Retrieves a tile url
* @function
* @param {Number} level Level of the tile
* @param {Number} x x coordinate of the tile
* @param {Number} y y coordinate of the tile
*/
getTileUrl(level, x, y) {
if (level === this.maxLevel) {
return this.url; //for original image, preserve url
}
//make up url by positional args
return `${this.url}?l=${level}&x=${x}&y=${y}`;
}
image.src = url;
},
/**
* @function
* @param {Number} level
*/
getLevelScale: function (level) {
var levelScale = NaN;
if (level >= this.minLevel && level <= this.maxLevel) {
levelScale =
this.levels[level].width /
this.levels[this.maxLevel].width;
}
return levelScale;
},
/**
* @function
* @param {Number} level
*/
getNumTiles: function (level) {
var scale = this.getLevelScale(level);
if (scale) {
return new $.Point(1, 1);
} else {
return new $.Point(0, 0);
}
},
/**
* Retrieves a tile url
* @function
* @param {Number} level Level of the tile
* @param {Number} x x coordinate of the tile
* @param {Number} y y coordinate of the tile
*/
getTileUrl: function (level, x, y) {
var url = null;
if (level >= this.minLevel && level <= this.maxLevel) {
url = this.levels[level].url;
}
return url;
},
/**
* Retrieves a tile context 2D
* @function
* @param {Number} level Level of the tile
* @param {Number} x x coordinate of the tile
* @param {Number} y y coordinate of the tile
*/
getContext2D: function (level, x, y) {
var context = null;
if (level >= this.minLevel && level <= this.maxLevel) {
context = this.levels[level].context2D;
}
return context;
},
/**
* Destroys ImageTileSource
* @function
*/
destroy: function () {
this._freeupCanvasMemory();
},
/**
* Equality comparator
*/
equals(otherSource) {
return this.url === otherSource.url;
}
// private
//
// Builds the different levels of the pyramid if possible
// (i.e. if canvas API enabled and no canvas tainting issue).
_buildLevels: function () {
var levels = [{
url: this._image.src,
width: this._image.naturalWidth,
height: this._image.naturalHeight
}];
getTilePostData(level, x, y) {
return {level: level, x: x, y: y};
}
if (!this.buildPyramid || !$.supportsCanvas || !this.useCanvas) {
// We don't need the image anymore. Allows it to be GC.
delete this._image;
return levels;
}
/**
* Retrieves a tile context 2D
* @deprecated
*/
getContext2D(level, x, y) {
$.console.error('Using [TiledImage.getContext2D] (for plain images only) is deprecated. ' +
'Use overridden downloadTileStart (https://openseadragon.github.io/examples/advanced-data-model/) instead.');
return this._createContext2D();
}
var currentWidth = this._image.naturalWidth;
var currentHeight = this._image.naturalHeight;
downloadTileStart(job) {
const tileData = job.postData;
if (tileData.level === this.maxLevel) {
job.finish(this.image, null, "image");
return;
}
if (tileData.level >= this.minLevel && tileData.level <= this.maxLevel) {
const levelData = this.levels[tileData.level];
const context = this._createContext2D(this.image, levelData.width, levelData.height);
job.finish(context, null, "context2d");
return;
}
job.fail(`Invalid level ${tileData.level} for plain image source. Did you forget to set buildPyramid=true?`);
}
var bigCanvas = document.createElement("canvas");
var bigContext = bigCanvas.getContext("2d");
downloadTileAbort(job) {
//no-op
}
bigCanvas.width = currentWidth;
bigCanvas.height = currentHeight;
bigContext.drawImage(this._image, 0, 0, currentWidth, currentHeight);
// We cache the context of the highest level because the browser
// is a lot faster at downsampling something it already has
// downsampled before.
levels[0].context2D = bigContext;
// We don't need the image anymore. Allows it to be GC.
delete this._image;
// private
//
// Builds the different levels of the pyramid if possible
// (i.e. if canvas API enabled and no canvas tainting issue).
_buildLevels(image) {
const levels = [{
url: image.src,
width: image.naturalWidth,
height: image.naturalHeight
}];
if ($.isCanvasTainted(bigCanvas)) {
// If the canvas is tainted, we can't compute the pyramid.
return levels;
}
// We build smaller levels until either width or height becomes
// 1 pixel wide.
while (currentWidth >= 2 && currentHeight >= 2) {
currentWidth = Math.floor(currentWidth / 2);
currentHeight = Math.floor(currentHeight / 2);
var smallCanvas = document.createElement("canvas");
var smallContext = smallCanvas.getContext("2d");
smallCanvas.width = currentWidth;
smallCanvas.height = currentHeight;
smallContext.drawImage(bigCanvas, 0, 0, currentWidth, currentHeight);
levels.splice(0, 0, {
context2D: smallContext,
width: currentWidth,
height: currentHeight
});
bigCanvas = smallCanvas;
bigContext = smallContext;
}
if (!this.buildPyramid || !$.supportsCanvas || !this.useCanvas) {
return levels;
},
/**
* Free up canvas memory
* (iOS 12 or higher on 2GB RAM device has only 224MB canvas memory,
* and Safari keeps canvas until its height and width will be set to 0).
* @function
*/
_freeupCanvasMemory: function () {
for (var i = 0; i < this.levels.length; i++) {
if(this.levels[i].context2D){
this.levels[i].context2D.canvas.height = 0;
this.levels[i].context2D.canvas.width = 0;
}
}
},
});
}
let currentWidth = image.naturalWidth,
currentHeight = image.naturalHeight;
// We build smaller levels until either width or height becomes
// 2 pixel wide.
while (currentWidth >= 2 && currentHeight >= 2) {
currentWidth = Math.floor(currentWidth / 2);
currentHeight = Math.floor(currentHeight / 2);
levels.push({
width: currentWidth,
height: currentHeight,
});
}
return levels.reverse();
}
_createContext2D(data, w, h) {
const canvas = document.createElement("canvas"),
context = canvas.getContext("2d");
canvas.width = w;
canvas.height = h;
context.drawImage(data, 0, 0, w, h);
return context;
}
};
}(OpenSeadragon));

View file

@ -2,7 +2,7 @@
* OpenSeadragon - LegacyTileSource
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@ -187,6 +187,21 @@ $.extend( $.LegacyTileSource.prototype, $.TileSource.prototype, /** @lends OpenS
url = this.levels[ level ].url;
}
return url;
},
/**
* Equality comparator
*/
equals: function (otherSource) {
if (!otherSource.levels || otherSource.levels.length !== this.levels.length) {
return false;
}
for (let i = this.minLevel; i <= this.maxLevel; i++) {
if (this.levels[i].url !== otherSource.levels[i].url) {
return false;
}
}
return true;
}
} );

209
src/matrix3.js Normal file
View file

@ -0,0 +1,209 @@
/*
* OpenSeadragon - Mat3
*
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of CodePlex Foundation nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
/*
* Portions of this source file are taken from WegGL Fundamentals:
*
* Copyright 2012, Gregg Tavares.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Gregg Tavares. nor the names of his
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
(function( $ ){
// Modified from https://webglfundamentals.org/webgl/lessons/webgl-2d-matrices.html
/**
*
*
* @class Mat3
* @classdesc A left-to-right matrix representation, useful for affine transforms for
* positioning tiles for drawing
*
* @memberof OpenSeadragon
*
* @param {Array} [values] - Initial values for the matrix
*
**/
class Mat3{
constructor(values){
if(!values) {
values = [
0, 0, 0,
0, 0, 0,
0, 0, 0
];
}
this.values = values;
}
/**
* @function makeIdentity
* @memberof OpenSeadragon.Mat3
* @static
* @returns {OpenSeadragon.Mat3} an identity matrix
*/
static makeIdentity(){
return new Mat3([
1, 0, 0,
0, 1, 0,
0, 0, 1
]);
}
/**
* @function makeTranslation
* @memberof OpenSeadragon.Mat3
* @static
* @param {Number} tx The x value of the translation
* @param {Number} ty The y value of the translation
* @returns {OpenSeadragon.Mat3} A translation matrix
*/
static makeTranslation(tx, ty) {
return new Mat3([
1, 0, 0,
0, 1, 0,
tx, ty, 1,
]);
}
/**
* @function makeRotation
* @memberof OpenSeadragon.Mat3
* @static
* @param {Number} angleInRadians The desired rotation angle, in radians
* @returns {OpenSeadragon.Mat3} A rotation matrix
*/
static makeRotation(angleInRadians) {
var c = Math.cos(angleInRadians);
var s = Math.sin(angleInRadians);
return new Mat3([
c, -s, 0,
s, c, 0,
0, 0, 1,
]);
}
/**
* @function makeScaling
* @memberof OpenSeadragon.Mat3
* @static
* @param {Number} sx The x value of the scaling
* @param {Number} sy The y value of the scaling
* @returns {OpenSeadragon.Mat3} A scaling matrix
*/
static makeScaling(sx, sy) {
return new Mat3([
sx, 0, 0,
0, sy, 0,
0, 0, 1,
]);
}
/**
* @alias multiply
* @memberof! OpenSeadragon.Mat3
* @param {OpenSeadragon.Mat3} other the matrix to multiply with
* @returns {OpenSeadragon.Mat3} The result of matrix multiplication
*/
multiply(other) {
let a = this.values;
let b = other.values;
var a00 = a[0 * 3 + 0];
var a01 = a[0 * 3 + 1];
var a02 = a[0 * 3 + 2];
var a10 = a[1 * 3 + 0];
var a11 = a[1 * 3 + 1];
var a12 = a[1 * 3 + 2];
var a20 = a[2 * 3 + 0];
var a21 = a[2 * 3 + 1];
var a22 = a[2 * 3 + 2];
var b00 = b[0 * 3 + 0];
var b01 = b[0 * 3 + 1];
var b02 = b[0 * 3 + 2];
var b10 = b[1 * 3 + 0];
var b11 = b[1 * 3 + 1];
var b12 = b[1 * 3 + 2];
var b20 = b[2 * 3 + 0];
var b21 = b[2 * 3 + 1];
var b22 = b[2 * 3 + 2];
return new Mat3([
b00 * a00 + b01 * a10 + b02 * a20,
b00 * a01 + b01 * a11 + b02 * a21,
b00 * a02 + b01 * a12 + b02 * a22,
b10 * a00 + b11 * a10 + b12 * a20,
b10 * a01 + b11 * a11 + b12 * a21,
b10 * a02 + b11 * a12 + b12 * a22,
b20 * a00 + b21 * a10 + b22 * a20,
b20 * a01 + b21 * a11 + b22 * a21,
b20 * a02 + b21 * a12 + b22 * a22,
]);
}
}
$.Mat3 = Mat3;
}( OpenSeadragon ));

View file

@ -2,7 +2,7 @@
* OpenSeadragon - MouseTracker
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@ -57,16 +57,16 @@
* @param {Boolean} [options.startDisabled=false]
* If true, event tracking on the element will not start until
* {@link OpenSeadragon.MouseTracker.setTracking|setTracking} is called.
* @param {Number} options.clickTimeThreshold
* @param {Number} [options.clickTimeThreshold=300]
* The number of milliseconds within which a pointer down-up event combination
* will be treated as a click gesture.
* @param {Number} options.clickDistThreshold
* @param {Number} [options.clickDistThreshold=5]
* The maximum distance allowed between a pointer down event and a pointer up event
* to be treated as a click gesture.
* @param {Number} options.dblClickTimeThreshold
* @param {Number} [options.dblClickTimeThreshold=300]
* The number of milliseconds within which two pointer down-up event combinations
* will be treated as a double-click gesture.
* @param {Number} options.dblClickDistThreshold
* @param {Number} [options.dblClickDistThreshold=20]
* The maximum distance allowed between two pointer click events
* to be treated as a click gesture.
* @param {Number} [options.stopDelay=50]
@ -135,7 +135,7 @@
};
}
this.hash = Math.random(); // An unique hash for this tracker.
this.hash = uniqueHash(); // An unique hash for this tracker.
/**
* The element for which pointer events are being monitored.
* @member {Element} element
@ -277,13 +277,6 @@
sentDragEvent: false
};
this.hasGestureHandlers = !!( this.pressHandler || this.nonPrimaryPressHandler ||
this.releaseHandler || this.nonPrimaryReleaseHandler ||
this.clickHandler || this.dblClickHandler ||
this.dragHandler || this.dragEndHandler ||
this.pinchHandler );
this.hasScrollHandler = !!this.scrollHandler;
if ( $.MouseTracker.havePointerEvents ) {
$.setElementPointerEvents( this.element, 'auto' );
}
@ -357,7 +350,7 @@
getActivePointersListByType: function ( type ) {
var delegate = THIS[ this.hash ],
i,
len = delegate.activePointersLists.length,
len = delegate ? delegate.activePointersLists.length : 0,
list;
for ( i = 0; i < len; i++ ) {
@ -367,7 +360,9 @@
}
list = new $.MouseTracker.GesturePointList( type );
delegate.activePointersLists.push( list );
if(delegate){
delegate.activePointersLists.push( list );
}
return list;
},
@ -389,6 +384,30 @@
return count;
},
/**
* Do we currently have any assigned gesture handlers.
* @returns {Boolean} Do we currently have any assigned gesture handlers.
*/
get hasGestureHandlers() {
return !!(this.pressHandler ||
this.nonPrimaryPressHandler ||
this.releaseHandler ||
this.nonPrimaryReleaseHandler ||
this.clickHandler ||
this.dblClickHandler ||
this.dragHandler ||
this.dragEndHandler ||
this.pinchHandler);
},
/**
* Do we currently have a scroll handler.
* @returns {Boolean} Do we currently have a scroll handler.
*/
get hasScrollHandler() {
return !!this.scrollHandler;
},
/**
* Implement or assign implementation to these handlers during or after
* calling the constructor.
@ -1114,10 +1133,9 @@
/**
* Detect available mouse wheel event name.
*/
$.MouseTracker.wheelEventName = ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version > 8 ) ||
( 'onwheel' in document.createElement( 'div' ) ) ? 'wheel' : // Modern browsers support 'wheel'
document.onmousewheel !== undefined ? 'mousewheel' : // Webkit and IE support at least 'mousewheel'
'DOMMouseScroll'; // Assume old Firefox
$.MouseTracker.wheelEventName = ( 'onwheel' in document.createElement( 'div' ) ) ? 'wheel' : // Modern browsers support 'wheel'
document.onmousewheel !== undefined ? 'mousewheel' : // Webkit (and unsupported IE) support at least 'mousewheel'
'DOMMouseScroll'; // Assume old Firefox (deprecated)
/**
* Detect browser pointer device event model(s) and build appropriate list of events to subscribe to.
@ -1130,7 +1148,7 @@
}
if ( window.PointerEvent ) {
// IE11 and other W3C Pointer Event implementations (see http://www.w3.org/TR/pointerevents)
// W3C Pointer Event implementations (see http://www.w3.org/TR/pointerevents)
$.MouseTracker.havePointerEvents = true;
$.MouseTracker.subscribeEvents.push( "pointerenter", "pointerleave", "pointerover", "pointerout", "pointerdown", "pointerup", "pointermove", "pointercancel" );
// Pointer events capture support
@ -1669,7 +1687,6 @@
/**
* Gets a W3C Pointer Events model compatible pointer type string from a DOM pointer event.
* IE10 used a long integer value, but the W3C specification (and IE11+) use a string "mouse", "touch", "pen", etc.
*
* Note: Called for both pointer events and legacy mouse events
* ($.MouseTracker.havePointerEvents determines which)
@ -1677,14 +1694,7 @@
* @inner
*/
function getPointerType( event ) {
if ( $.MouseTracker.havePointerEvents ) {
// Note: IE pointer events bug - sends invalid pointerType on lostpointercapture events
// and possibly other events. We rely on sane, valid property values in DOM events, so for
// IE, when the pointerType is missing, we'll default to 'mouse'...should be right most of the time
return event.pointerType || (( $.Browser.vendor === $.BROWSERS.IE ) ? 'mouse' : '');
} else {
return 'mouse';
}
return $.MouseTracker.havePointerEvents && event.pointerType ? event.pointerType : 'mouse';
}
@ -2070,7 +2080,7 @@
// y-index scrolling.
// event.deltaMode: 0=pixel, 1=line, 2=page
// TODO: Deltas in pixel mode should be accumulated then a scroll value computed after $.DEFAULT_SETTINGS.pixelsPerWheelLine threshold reached
nDelta = event.deltaY < 0 ? 1 : -1;
nDelta = event.deltaY ? (event.deltaY < 0 ? 1 : -1) : 0;
eventInfo = {
originalEvent: event,
@ -2552,15 +2562,14 @@
};
// Most browsers implicitly capture touch pointer events
// Note no IE versions have element.hasPointerCapture() so no implicit
// pointer capture possible
// Note no IE versions (unsupported) have element.hasPointerCapture() so
// no implicit pointer capture possible
// var implicitlyCaptured = ($.MouseTracker.havePointerEvents &&
// event.target.hasPointerCapture &&
// $.Browser.vendor !== $.BROWSERS.IE) ?
// event.target.hasPointerCapture(event.pointerId) : false;
var implicitlyCaptured = $.MouseTracker.havePointerEvents &&
gPoint.type === 'touch' &&
$.Browser.vendor !== $.BROWSERS.IE;
gPoint.type === 'touch';
//$.console.log('pointerdown ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : ''));
@ -3772,4 +3781,19 @@
}
}
/**
* @function
* @private
* @inner
*/
function uniqueHash( ) {
let uniqueId = Date.now().toString(36) + Math.random().toString(36).substring(2);
while (uniqueId in THIS) {
// rehash when not unique
uniqueId = Date.now().toString(36) + Math.random().toString(36).substring(2);
}
return uniqueId;
}
}(OpenSeadragon));

View file

@ -2,7 +2,7 @@
* OpenSeadragon - Navigator
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@ -170,9 +170,6 @@ $.Navigator = function( options ){
style.border = borderWidth + 'px solid ' + options.displayRegionColor;
style.margin = '0px';
style.padding = '0px';
//TODO: IE doesn't like this property being set
//try{ style.outline = '2px auto #909'; }catch(e){/*ignore*/}
style.background = 'transparent';
// We use square bracket notation on the statement below, because float is a keyword.
@ -181,9 +178,9 @@ $.Navigator = function( options ){
style['float'] = 'left'; //Webkit
style.cssFloat = 'left'; //Firefox
style.styleFloat = 'left'; //IE
style.zIndex = 999999999;
style.cursor = 'default';
style.boxSizing = 'content-box';
}( this.displayRegion.style, this.borderWidth ));
$.setElementPointerEventsNone( this.displayRegion );
$.setElementTouchActionNone( this.displayRegion );
@ -223,19 +220,19 @@ $.Navigator = function( options ){
this.displayRegionContainer.appendChild(this.displayRegion);
this.element.getElementsByTagName('div')[0].appendChild(this.displayRegionContainer);
function rotate(degrees) {
function rotate(degrees, immediately) {
_setTransformRotate(_this.displayRegionContainer, degrees);
_setTransformRotate(_this.displayRegion, -degrees);
_this.viewport.setRotation(degrees);
_this.viewport.setRotation(degrees, immediately);
}
if (options.navigatorRotate) {
var degrees = options.viewer.viewport ?
options.viewer.viewport.getRotation() :
options.viewer.degrees || 0;
rotate(degrees);
rotate(degrees, true);
options.viewer.addHandler("rotate", function (args) {
rotate(args.degrees);
rotate(args.degrees, args.immediately);
});
}
@ -309,8 +306,9 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
this.viewport.resize( containerSize, true );
this.viewport.goHome(true);
this.oldContainerSize = containerSize;
this.drawer.clear();
this.world.update();
this.world.draw();
this.update(this.viewer.viewport);
}
}
},
@ -323,6 +321,7 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
this.width = width;
this.element.style.width = typeof (width) === "number" ? (width + 'px') : width;
this._resizeWithViewer = false;
this.updateSize();
},
/**
@ -333,6 +332,7 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
this.height = height;
this.element.style.height = typeof (height) === "number" ? (height + 'px') : height;
this._resizeWithViewer = false;
this.updateSize();
},
/**
@ -347,7 +347,6 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
},
setDisplayTransform: function(rule) {
setElementTransform(this.displayRegion, rule);
setElementTransform(this.canvas, rule);
setElementTransform(this.element, rule);
},
@ -355,7 +354,7 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
/**
* Used to update the navigator minimap's viewport rectangle when a change in the viewer's viewport occurs.
* @function
* @param {OpenSeadragon.Viewport} The viewport this navigator is tracking.
* @param {OpenSeadragon.Viewport} [viewport] The viewport to display. Default: the viewport this navigator is tracking.
*/
update: function( viewport ) {
@ -366,6 +365,10 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
topleft,
bottomright;
if(!viewport){
viewport = this.viewer.viewport;
}
viewerSize = $.getElementSize( this.viewer.element );
if ( this._resizeWithViewer && viewerSize.x && viewerSize.y && !viewerSize.equals( this.oldViewerSize ) ) {
this.oldViewerSize = viewerSize;
@ -394,15 +397,20 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
bottomright = this.viewport.pixelFromPointNoRotate(bounds.getBottomRight(), false)
.minus( this.totalBorderWidths );
if (!this.navigatorRotate) {
var degrees = viewport.getRotation(true);
_setTransformRotate(this.displayRegion, -degrees);
}
//update style for navigator-box
var style = this.displayRegion.style;
style.display = this.world.getItemCount() ? 'block' : 'none';
style.top = Math.round( topleft.y ) + 'px';
style.left = Math.round( topleft.x ) + 'px';
style.top = topleft.y.toFixed(2) + "px";
style.left = topleft.x.toFixed(2) + "px";
var width = Math.abs( topleft.x - bottomright.x );
var height = Math.abs( topleft.y - bottomright.y );
var width = bottomright.x - topleft.x;
var height = bottomright.y - topleft.y;
// make sure width and height are non-negative so IE doesn't throw
style.width = Math.round( Math.max( width, 0 ) ) + 'px';
style.height = Math.round( Math.max( height, 0 ) ) + 'px';

View file

@ -2,7 +2,7 @@
* OpenSeadragon
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@ -60,7 +60,7 @@
/*
* Portions of this source file taken from mattsnider.com:
*
* Copyright (c) 2006-2022 Matt Snider
* Copyright (c) 2006-2013 Matt Snider
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
@ -190,6 +190,16 @@
* Zoom level to use when image is first opened or the home button is clicked.
* If 0, adjusts to fit viewer.
*
* @property {String|DrawerImplementation|Array} [drawer = ['webgl', 'canvas', 'html']]
* Which drawer to use. Valid strings are 'webgl', 'canvas', and 'html'. Valid drawer
* implementations are constructors of classes that extend OpenSeadragon.DrawerBase.
* An array of strings and/or constructors can be used to indicate the priority
* of different implementations, which will be tried in order based on browser support.
*
* @property {Object} drawerOptions
* Options to pass to the selected drawer implementation. For details
* please see {@link OpenSeadragon.DrawerOptions}.
*
* @property {Number} [opacity=1]
* Default proportional opacity of the tiled images (1=opaque, 0=hidden)
* Hidden images do not draw and only load when preloading is allowed.
@ -204,9 +214,9 @@
* For complete list of modes, please @see {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation/ globalCompositeOperation}
*
* @property {Boolean} [imageSmoothingEnabled=true]
* Image smoothing for canvas rendering (only if canvas is used). Note: Ignored
* Image smoothing for rendering (only if the canvas or webgl drawer is used). Note: Ignored
* by some (especially older) browsers which do not support this canvas property.
* This property can be changed in {@link Viewer.Drawer.setImageSmoothingEnabled}.
* This property can be changed in {@link Viewer.DrawerBase.setImageSmoothingEnabled}.
*
* @property {String|CanvasGradient|CanvasPattern|Function} [placeholderFillStyle=null]
* Draws a colored rectangle behind the tile if it is not loaded yet.
@ -230,6 +240,11 @@
* @property {Boolean} [flipped=false]
* Initial flip state.
*
* @property {Boolean} [overlayPreserveContentDirection=true]
* When the viewport is flipped (by pressing 'f'), the overlay is flipped using ScaleX.
* Normally, this setting (default true) keeps the overlay's content readable by flipping it back.
* To make the content flip with the overlay, set overlayPreserveContentDirection to false.
*
* @property {Number} [minZoomLevel=null]
*
* @property {Number} [maxZoomLevel=null]
@ -290,6 +305,12 @@
* @property {Number} [rotationIncrement=90]
* The number of degrees to rotate right or left when the rotate buttons or keyboard shortcuts are activated.
*
* @property {Number} [maxTilesPerFrame=1]
* The number of tiles loaded per frame. As the frame rate of the client's machine is usually high (e.g., 50 fps),
* one tile per frame should be a good choice. However, for large screens or lower frame rates, the number of
* loaded tiles per frame can be adjusted here. Reasonable values might be 2 or 3 tiles per frame.
* (Note that the actual frame rate is given by the client's browser and machine).
*
* @property {Number} [pixelsPerWheelLine=40]
* For pixel-resolution scrolling devices, the number of pixels equal to one scroll line.
*
@ -334,6 +355,9 @@
* Specifies the animation duration per each {@link OpenSeadragon.Spring}
* which occur when the image is dragged, zoomed or rotated.
*
* @property {Boolean} [loadDestinationTilesOnAnimation=true]
* If true, tiles are loaded only at the destination of an animation.
* If false, tiles are loaded along the animation path during the animation.
* @property {OpenSeadragon.GestureSettings} [gestureSettingsMouse]
* Settings for gestures generated by a mouse pointer device. (See {@link OpenSeadragon.GestureSettings})
* @property {Boolean} [gestureSettingsMouse.dragToPan=true] - Pan on drag gesture
@ -495,8 +519,14 @@
* @property {Number} [timeout=30000]
* The max number of milliseconds that an image job may take to complete.
*
* @property {Number} [tileRetryMax=0]
* The max number of retries when a tile download fails. By default it's 0, so retries are disabled.
*
* @property {Number} [tileRetryDelay=2500]
* Milliseconds to wait after each tile retry if tileRetryMax is set.
*
* @property {Boolean} [useCanvas=true]
* Set to false to not use an HTML canvas element for image rendering even if canvas is supported.
* Deprecated. Use the `drawer` option to specify preferred renderer.
*
* @property {Number} [minPixelRatio=0.5]
* The higher the minPixelRatio, the lower the quality of the image that
@ -559,50 +589,50 @@
* viewing the first image and the 'next' button will wrap to the first
* image when viewing the last image.
*
* @property {String} zoomInButton
* Set the id of the custom 'Zoom in' button to use.
*@property {String|Element} zoomInButton
* Set the id or element of the custom 'Zoom in' button to use.
* This is useful to have a custom button anywhere in the web page.<br>
* To only change the button images, consider using
* {@link OpenSeadragon.Options.navImages}
*
* @property {String} zoomOutButton
* Set the id of the custom 'Zoom out' button to use.
* @property {String|Element} zoomOutButton
* Set the id or element of the custom 'Zoom out' button to use.
* This is useful to have a custom button anywhere in the web page.<br>
* To only change the button images, consider using
* {@link OpenSeadragon.Options.navImages}
*
* @property {String} homeButton
* Set the id of the custom 'Go home' button to use.
* @property {String|Element} homeButton
* Set the id or element of the custom 'Go home' button to use.
* This is useful to have a custom button anywhere in the web page.<br>
* To only change the button images, consider using
* {@link OpenSeadragon.Options.navImages}
*
* @property {String} fullPageButton
* Set the id of the custom 'Toggle full page' button to use.
* @property {String|Element} fullPageButton
* Set the id or element of the custom 'Toggle full page' button to use.
* This is useful to have a custom button anywhere in the web page.<br>
* To only change the button images, consider using
* {@link OpenSeadragon.Options.navImages}
*
* @property {String} rotateLeftButton
* Set the id of the custom 'Rotate left' button to use.
* @property {String|Element} rotateLeftButton
* Set the id or element of the custom 'Rotate left' button to use.
* This is useful to have a custom button anywhere in the web page.<br>
* To only change the button images, consider using
* {@link OpenSeadragon.Options.navImages}
*
* @property {String} rotateRightButton
* Set the id of the custom 'Rotate right' button to use.
* @property {String|Element} rotateRightButton
* Set the id or element of the custom 'Rotate right' button to use.
* This is useful to have a custom button anywhere in the web page.<br>
* To only change the button images, consider using
* {@link OpenSeadragon.Options.navImages}
*
* @property {String} previousButton
* Set the id of the custom 'Previous page' button to use.
* @property {String|Element} previousButton
* Set the id or element of the custom 'Previous page' button to use.
* This is useful to have a custom button anywhere in the web page.<br>
* To only change the button images, consider using
* {@link OpenSeadragon.Options.navImages}
*
* @property {String} nextButton
* Set the id of the custom 'Next page' button to use.
* @property {String|Element} nextButton
* Set the id or element of the custom 'Next page' button to use.
* This is useful to have a custom button anywhere in the web page.<br>
* To only change the button images, consider using
* {@link OpenSeadragon.Options.navImages}
@ -695,6 +725,12 @@
* NOTE: passing POST data from URL by this feature only supports string values, however,
* TileSource can send any data using POST as long as the header is correct
* (@see OpenSeadragon.TileSource.prototype.getTilePostData)
*
* @property {Boolean} [callTileLoadedWithCachedData=false]
* tile-loaded event is called only for tiles that downloaded new data or
* their data is stored in the original form in a suplementary cache object.
* Caches that render directly from re-used cache does not trigger this event again,
* as possible modifications would be applied twice.
*/
/**
@ -732,6 +768,20 @@
*
*/
/**
* @typedef {Object.<string, Object>} DrawerOptions - give the renderer options (both shared - BaseDrawerOptions, and custom).
* Supports arbitrary keys: you can register any drawer on the OpenSeadragon namespace, it will get automatically recognized
* and its getType() implementation will define what key to specify the options with.
* @memberof OpenSeadragon
* @property {BaseDrawerOptions} [webgl] - options if the WebGLDrawer is used.
* @property {BaseDrawerOptions} [canvas] - options if the CanvasDrawer is used.
* @property {BaseDrawerOptions} [html] - options if the HTMLDrawer is used.
* @property {BaseDrawerOptions} [custom] - options if a custom drawer is used.
*
* //Note: if you want to add change options for target drawer change type to {BaseDrawerOptions & MyDrawerOpts}
*/
/**
* The names for the image resources used for the image navigation buttons.
*
@ -826,14 +876,20 @@ function OpenSeadragon( options ){
* @private
*/
var class2type = {
'[object Boolean]': 'boolean',
'[object Number]': 'number',
'[object String]': 'string',
'[object Function]': 'function',
'[object Array]': 'array',
'[object Date]': 'date',
'[object RegExp]': 'regexp',
'[object Object]': 'object'
'[object Boolean]': 'boolean',
'[object Number]': 'number',
'[object String]': 'string',
'[object Function]': 'function',
'[object AsyncFunction]': 'function',
'[object Promise]': 'promise',
'[object Array]': 'array',
'[object Date]': 'date',
'[object RegExp]': 'regexp',
'[object Object]': 'object',
'[object HTMLUnknownElement]': 'dom-node',
'[object HTMLImageElement]': 'image',
'[object HTMLCanvasElement]': 'canvas',
'[object CanvasRenderingContext2D]': 'context2d'
},
// Save a reference to some core methods
toString = Object.prototype.toString,
@ -849,7 +905,6 @@ function OpenSeadragon( options ){
return $.type(obj) === "function";
};
/**
* Taken from jQuery 1.6.1
* @function isArray
@ -1028,11 +1083,20 @@ function OpenSeadragon( options ){
return supported >= 3;
}());
/**
* If true, OpenSeadragon uses async execution, else it uses synchronous execution.
* Note that disabling async means no plugins that use Promises / async will work with OSD.
* @member {boolean}
* @memberof OpenSeadragon
*/
$.supportsAsync = true;
/**
* A ratio comparing the device screen's pixel density to the canvas's backing store pixel density,
* clamped to a minimum of 1. Defaults to 1 if canvas isn't supported by the browser.
* @member {Number} pixelDensityRatio
* @function getCurrentPixelDensityRatio
* @memberof OpenSeadragon
* @returns {Number}
*/
$.getCurrentPixelDensityRatio = function() {
if ( $.supportsCanvas ) {
@ -1050,6 +1114,8 @@ function OpenSeadragon( options ){
};
/**
* A ratio comparing the device screen's pixel density to the canvas's backing store pixel density,
* clamped to a minimum of 1. Defaults to 1 if canvas isn't supported by the browser.
* @member {Number} pixelDensityRatio
* @memberof OpenSeadragon
*/
@ -1190,6 +1256,7 @@ function OpenSeadragon( options ){
loadTilesWithAjax: false,
ajaxHeaders: {},
splitHashDataForPost: false,
callTileLoadedWithCachedData: false,
//PAN AND ZOOM SETTINGS AND CONSTRAINTS
panHorizontal: true,
@ -1211,6 +1278,7 @@ function OpenSeadragon( options ){
dblClickDistThreshold: 20,
springStiffness: 6.5,
animationTime: 1.2,
loadDestinationTilesOnAnimation: true,
gestureSettingsMouse: {
dragToPan: true,
scrollToZoom: true,
@ -1281,6 +1349,7 @@ function OpenSeadragon( options ){
preserveImageSizeOnResize: false, // requires autoResize=true
minScrollDeltaTime: 50,
rotationIncrement: 90,
maxTilesPerFrame: 1,
//DEFAULT CONTROL SETTINGS
showSequenceControl: true, //SEQUENCE
@ -1322,15 +1391,36 @@ function OpenSeadragon( options ){
degrees: 0,
// INITIAL FLIP STATE
flipped: false,
flipped: false,
overlayPreserveContentDirection: true,
// APPEARANCE
opacity: 1,
preload: false,
compositeOperation: null,
imageSmoothingEnabled: true,
placeholderFillStyle: null,
subPixelRoundingForTransparency: null,
opacity: 1, // to be passed into each TiledImage
compositeOperation: null, // to be passed into each TiledImage
// DRAWER SETTINGS
drawer: ['webgl', 'canvas', 'html'], // prefer using webgl, then canvas (i.e. context2d), then fallback to html
drawerOptions: {
webgl: {
},
canvas: {
},
html: {
},
custom: {
}
},
// TILED IMAGE SETTINGS
preload: false, // to be passed into each TiledImage
imageSmoothingEnabled: true, // to be passed into each TiledImage
placeholderFillStyle: null, // to be passed into each TiledImage
subPixelRoundingForTransparency: null, // to be passed into each TiledImage
//REFERENCE STRIP SETTINGS
showReferenceStrip: false,
@ -1353,7 +1443,8 @@ function OpenSeadragon( options ){
imageLoaderLimit: 0,
maxImageCacheCount: 200,
timeout: 30000,
useCanvas: true, // Use canvas element for drawing if available
tileRetryMax: 0,
tileRetryDelay: 2500,
//INTERFACE RESOURCE SETTINGS
prefixUrl: "/images/",
@ -1421,16 +1512,6 @@ function OpenSeadragon( options ){
},
/**
* TODO: get rid of this. I can't see how it's required at all. Looks
* like an early legacy code artifact.
* @static
* @ignore
*/
SIGNAL: "----seadragon----",
/**
* Returns a function which invokes the method as if it were a method belonging to the object.
* @function
@ -2243,42 +2324,6 @@ function OpenSeadragon( options ){
},
/**
* Similar to OpenSeadragon.delegate, but it does not immediately call
* the method on the object, returning a function which can be called
* repeatedly to delegate the method. It also allows additional arguments
* to be passed during construction which will be added during each
* invocation, and each invocation can add additional arguments as well.
*
* @function
* @param {Object} object
* @param {Function} method
* @param [args] any additional arguments are passed as arguments to the
* created callback
* @returns {Function}
*/
createCallback: function( object, method ) {
//TODO: This pattern is painful to use and debug. It's much cleaner
// to use pinning plus anonymous functions. Get rid of this
// pattern!
var initialArgs = [],
i;
for ( i = 2; i < arguments.length; i++ ) {
initialArgs.push( arguments[ i ] );
}
return function() {
var args = initialArgs.concat( [] ),
i;
for ( i = 0; i < arguments.length; i++ ) {
args.push( arguments[ i ] );
}
return method.apply( object, args );
};
},
/**
* Retrieves the value of a url parameter from the window.location string.
* @function
@ -2311,46 +2356,29 @@ function OpenSeadragon( options ){
/**
* Create an XHR object
* @private
* @param {type} [local] If set to true, the XHR will be file: protocol
* compatible if possible (but may raise a warning in the browser).
* @param {type} [local] Deprecated. Ignored (IE/ActiveXObject file protocol no longer supported).
* @returns {XMLHttpRequest}
*/
createAjaxRequest: function( local ) {
// IE11 does not support window.ActiveXObject so we just try to
// create one to see if it is supported.
// See: http://msdn.microsoft.com/en-us/library/ie/dn423948%28v=vs.85%29.aspx
var supportActiveX;
try {
/* global ActiveXObject:true */
supportActiveX = !!new ActiveXObject( "Microsoft.XMLHTTP" );
} catch( e ) {
supportActiveX = false;
}
if ( supportActiveX ) {
if ( window.XMLHttpRequest ) {
$.createAjaxRequest = function( local ) {
if ( local ) {
return new ActiveXObject( "Microsoft.XMLHTTP" );
}
return new XMLHttpRequest();
};
} else {
$.createAjaxRequest = function() {
return new ActiveXObject( "Microsoft.XMLHTTP" );
};
}
} else if ( window.XMLHttpRequest ) {
createAjaxRequest: function() {
if ( window.XMLHttpRequest ) {
$.createAjaxRequest = function() {
return new XMLHttpRequest();
};
return new XMLHttpRequest();
} else {
throw new Error( "Browser doesn't support XMLHttpRequest." );
}
return $.createAjaxRequest( local );
},
/**
* Makes an AJAX request.
* @param {String} url - the url to request
* @param {Function} onSuccess
* @param {Function} onError
* @throws {Error}
* @returns {XMLHttpRequest}
* @deprecated deprecated way of calling this function
*//**
* Makes an AJAX request.
* @param {Object} options
* @param {String} options.url - the url to request
@ -2359,7 +2387,7 @@ function OpenSeadragon( options ){
* @param {Object} options.headers - headers to add to the AJAX request
* @param {String} options.responseType - the response type of the AJAX request
* @param {String} options.postData - HTTP POST data (usually but not necessarily in k=v&k2=v2... form,
* see TileSource::getPostData), GET method used if null
* see TileSource::getTilePostData), GET method used if null
* @param {Boolean} [options.withCredentials=false] - whether to set the XHR's withCredentials
* @throws {Error}
* @returns {XMLHttpRequest}
@ -2380,10 +2408,12 @@ function OpenSeadragon( options ){
responseType = url.responseType || null;
postData = url.postData || null;
url = url.url;
} else {
$.console.warn("OpenSeadragon.makeAjaxRequest() deprecated usage!");
}
var protocol = $.getUrlProtocol( url );
var request = $.createAjaxRequest( protocol === "file:" );
var request = $.createAjaxRequest();
if ( !$.isFunction( onSuccess ) ) {
throw new Error( "makeAjaxRequest requires a success callback" );
@ -2552,17 +2582,6 @@ function OpenSeadragon( options ){
return xmlDoc;
};
} else if ( window.ActiveXObject ) {
$.parseXml = function( string ) {
var xmlDoc = null;
xmlDoc = new ActiveXObject( "Microsoft.XMLDOM" );
xmlDoc.async = false;
xmlDoc.loadXML( string );
return xmlDoc;
};
} else {
throw new Error( "Browser doesn't support XML DOM." );
}
@ -2599,26 +2618,31 @@ function OpenSeadragon( options ){
* Preexisting formats that are not being updated are left unchanged.
* By default, the defined formats are
* <pre><code>{
* avif: true,
* bmp: false,
* jpeg: true,
* jpg: true,
* png: true,
* tif: false,
* wdp: false
* wdp: false,
* webp: true
* }
* </code></pre>
* @function
* @example
* // sets webp as supported and png as unsupported
* setImageFormatsSupported({webp: true, png: false});
* // sets bmp as supported and png as unsupported
* setImageFormatsSupported({bmp: true, png: false});
* @param {Object} formats An object containing format extensions as
* keys and booleans as values.
*/
setImageFormatsSupported: function(formats) {
//TODO: how to deal with this within the data pipeline?
// $.console.warn("setImageFormatsSupported method is deprecated. You should check that" +
// " the system supports your TileSources by implementing corresponding data type convertors.");
// eslint-disable-next-line no-use-before-define
$.extend(FILEFORMATS, formats);
}
},
});
@ -2665,12 +2689,14 @@ function OpenSeadragon( options ){
var FILEFORMATS = {
avif: true,
bmp: false,
jpeg: true,
jpg: true,
png: true,
tif: false,
wdp: false
wdp: false,
webp: true
},
URLPARAMS = {};
@ -2685,6 +2711,10 @@ function OpenSeadragon( options ){
//console.error( 'appVersion: ' + navigator.appVersion );
//console.error( 'userAgent: ' + navigator.userAgent );
//TODO navigator.appName is deprecated. Should be 'Netscape' for all browsers
// but could be dropped at any time
// See https://developer.mozilla.org/en-US/docs/Web/API/Navigator/appName
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent
switch( navigator.appName ){
case "Microsoft Internet Explorer":
if( !!window.attachEvent &&
@ -2770,8 +2800,8 @@ function OpenSeadragon( options ){
//determine if this browser supports element.style.opacity
$.Browser.opacity = true;
if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 11 ) {
$.console.error('Internet Explorer versions < 11 are not supported by OpenSeadragon');
if ( $.Browser.vendor === $.BROWSERS.IE ) {
$.console.error('Internet Explorer is not supported by OpenSeadragon');
}
})();
@ -2878,21 +2908,143 @@ function OpenSeadragon( options ){
}
}
/**
* @template T
* @typedef {function(): OpenSeadragon.Promise<T>} AsyncNullaryFunction
* Represents an asynchronous function that takes no arguments and returns a promise of type T.
*/
/**
* @template T, A
* @typedef {function(A): OpenSeadragon.Promise<T>} AsyncUnaryFunction
* Represents an asynchronous function that:
* @param {A} arg - The single argument of type A.
* @returns {OpenSeadragon.Promise<T>} A promise that resolves to a value of type T.
*/
/**
* @template T, A, B
* @typedef {function(A, B): OpenSeadragon.Promise<T>} AsyncBinaryFunction
* Represents an asynchronous function that:
* @param {A} arg1 - The first argument of type A.
* @param {B} arg2 - The second argument of type B.
* @returns {OpenSeadragon.Promise<T>} A promise that resolves to a value of type T.
*/
/**
* Promise proxy in OpenSeadragon, enables $.supportsAsync feature.
* This proxy is also necessary because OperaMini does not implement Promises (checks fail).
* @type {PromiseConstructor}
*/
$.Promise = window["Promise"] && $.supportsAsync ? window["Promise"] : class {
constructor(handler) {
this._error = false;
this.__value = undefined;
try {
// Make sure to unwrap all nested promises!
handler(
(value) => {
while (value instanceof $.Promise) {
value = value._value;
}
this._value = value;
},
(error) => {
while (error instanceof $.Promise) {
error = error._value;
}
this._value = error;
this._error = true;
}
);
} catch (e) {
this._value = e;
this._error = true;
}
}
then(handler) {
if (!this._error) {
try {
this._value = handler(this._value);
} catch (e) {
this._value = e;
this._error = true;
}
}
return this;
}
catch(handler) {
if (this._error) {
try {
this._value = handler(this._value);
this._error = false;
} catch (e) {
this._value = e;
this._error = true;
}
}
return this;
}
get _value() {
return this.__value;
}
set _value(val) {
if (val && val.constructor === this.constructor) {
val = val._value; //unwrap
}
this.__value = val;
}
static resolve(value) {
return new this((resolve) => resolve(value));
}
static reject(error) {
return new this((_, reject) => reject(error));
}
static all(functions) {
return new this((resolve) => {
// no async support, just execute them
return resolve(functions.map(fn => fn()));
});
}
static race(functions) {
if (functions.length < 1) {
return this.resolve();
}
// no async support, just execute the first
return new this((resolve) => {
return resolve(functions[0]());
});
}
};
}(OpenSeadragon));
// Universal Module Definition, supports CommonJS, AMD and simple script tag
(function (root, factory) {
(function (root, $) {
if (typeof define === 'function' && define.amd) {
// expose as amd module
define([], factory);
define([], function () {
return $;
});
} else if (typeof module === 'object' && module.exports) {
// expose as commonjs module
module.exports = factory();
module.exports = $;
} else {
if (!root) {
root = typeof window === 'object' && window;
if (!root) {
$.console.error("OpenSeadragon must run in browser environment!");
}
}
// expose as window.OpenSeadragon
root.OpenSeadragon = factory();
root.OpenSeadragon = $;
}
}(this, function () {
return OpenSeadragon;
}));
}(this, OpenSeadragon));

View file

@ -2,7 +2,7 @@
* OpenSeadragon - OsmTileSource
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@ -139,6 +139,13 @@ $.extend( $.OsmTileSource.prototype, $.TileSource.prototype, /** @lends OpenSead
*/
getTileUrl: function( level, x, y ) {
return this.tilesUrl + (level - 8) + "/" + x + "/" + y + ".png";
},
/**
* Equality comparator
*/
equals: function(otherSource) {
return this.tilesUrl === otherSource.tilesUrl;
}
});

View file

@ -2,7 +2,7 @@
* OpenSeadragon - Overlay
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@ -128,8 +128,18 @@
};
}
this.elementWrapper = document.createElement('div');
this.element = options.element;
this.style = options.element.style;
this.elementWrapper.appendChild(this.element);
if (this.element.id) {
this.elementWrapper.id = "overlay-wrapper-" + this.element.id; // Unique ID if element has one
}
// Always add a class for styling & selection
this.elementWrapper.classList.add("openseadragon-overlay-wrapper");
this.style = this.elementWrapper.style;
this._init(options);
};
@ -196,7 +206,7 @@
* @function
*/
destroy: function() {
var element = this.element;
var element = this.elementWrapper;
var style = this.style;
if (element.parentNode) {
@ -241,7 +251,7 @@
* @param {Element} container
*/
drawHTML: function(container, viewport) {
var element = this.element;
var element = this.elementWrapper;
if (element.parentNode !== container) {
//save the source parent for later if we need it
element.prevElementParent = element.parentNode;
@ -252,43 +262,57 @@
this.style.position = "absolute";
// this.size is used by overlays which don't get scaled in at
// least one direction when this.checkResize is set to false.
this.size = $.getElementSize(element);
this.size = $.getElementSize(this.elementWrapper);
}
var positionAndSize = this._getOverlayPositionAndSize(viewport);
var position = positionAndSize.position;
var size = this.size = positionAndSize.size;
var rotate = positionAndSize.rotate;
var outerScale = "";
if (viewport.overlayPreserveContentDirection) {
outerScale = viewport.flipped ? " scaleX(-1)" : " scaleX(1)";
}
var rotate = viewport.flipped ? -positionAndSize.rotate : positionAndSize.rotate;
var scale = viewport.flipped ? " scaleX(-1)" : "";
// call the onDraw callback if it exists to allow one to overwrite
// the drawing/positioning/sizing of the overlay
if (this.onDraw) {
this.onDraw(position, size, this.element);
} else {
var style = this.style;
var innerStyle = this.element.style;
innerStyle.display = "block";
style.left = position.x + "px";
style.top = position.y + "px";
if (this.width !== null) {
style.width = size.x + "px";
innerStyle.width = size.x + "px";
}
if (this.height !== null) {
style.height = size.y + "px";
innerStyle.height = size.y + "px";
}
var transformOriginProp = $.getCssPropertyWithVendorPrefix(
'transformOrigin');
var transformProp = $.getCssPropertyWithVendorPrefix(
'transform');
if (transformOriginProp && transformProp) {
if (rotate) {
if (rotate && !viewport.flipped) {
innerStyle[transformProp] = "";
style[transformOriginProp] = this._getTransformOrigin();
style[transformProp] = "rotate(" + rotate + "deg)";
} else if (!rotate && viewport.flipped) {
innerStyle[transformProp] = outerScale;
style[transformOriginProp] = this._getTransformOrigin();
style[transformProp] = scale;
} else if (rotate && viewport.flipped){
innerStyle[transformProp] = outerScale;
style[transformOriginProp] = this._getTransformOrigin();
style[transformProp] = "rotate(" + rotate + "deg)" + scale;
} else {
innerStyle[transformProp] = "";
style[transformOriginProp] = "";
style[transformProp] = "";
}
}
style.display = 'block';
style.display = 'flex';
}
},
@ -314,6 +338,9 @@
}
}
if (viewport.flipped) {
position.x = (viewport.getContainerSize().x - position.x);
}
return {
position: position,
size: size,
@ -337,7 +364,7 @@
}
if (this.checkResize &&
(this.width === null || this.height === null)) {
var eltSize = this.size = $.getElementSize(this.element);
var eltSize = this.size = $.getElementSize(this.elementWrapper);
if (this.width === null) {
width = eltSize.x;
}

View file

@ -1,7 +1,7 @@
/*
* OpenSeadragon - Placement
*
* Copyright (C) 2010-2016 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are

View file

@ -2,7 +2,7 @@
* OpenSeadragon - Point
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are

362
src/priorityqueue.js Normal file
View file

@ -0,0 +1,362 @@
/*
* OpenSeadragon - Queue
*
* Copyright (C) 2024 OpenSeadragon contributors (modified)
* Copyright (C) Google Inc., The Closure Library Authors.
* https://github.com/google/closure-library
*
* SPDX-License-Identifier: Apache-2.0
*/
(function($) {
/**
* @class PriorityQueue
* @classdesc Fast priority queue. Implemented as a Heap.
*/
$.PriorityQueue = class {
/**
* @param {?OpenSeadragon.PriorityQueue} optHeap Optional Heap or
* Object to initialize heap with.
*/
constructor(optHeap = undefined) {
/**
* The nodes of the heap.
*
* This is a densely packed array containing all nodes of the heap, using
* the standard flat representation of a tree as an array (i.e. element [0]
* at the top, with [1] and [2] as the second row, [3] through [6] as the
* third, etc). Thus, the children of element `i` are `2i+1` and `2i+2`, and
* the parent of element `i` is `⌊(i-1)/2⌋`.
*
* The only invariant is that children's keys must be greater than parents'.
*
* @private
*/
this.nodes_ = [];
if (optHeap) {
this.insertAll(optHeap);
}
}
/**
* Insert the given value into the heap with the given key.
* @param {K} key The key.
* @param {V} value The value.
*/
insert(key, value) {
this.insertNode(new Node(key, value));
}
/**
* Insert node item.
* @param node
*/
insertNode(node) {
const nodes = this.nodes_;
node.index = nodes.length;
nodes.push(node);
this.moveUp_(node.index);
}
/**
* Adds multiple key-value pairs from another Heap or Object
* @param {?OpenSeadragon.PriorityQueue} heap Object containing the data to add.
*/
insertAll(heap) {
let keys, values;
if (heap instanceof $.PriorityQueue) {
keys = heap.getKeys();
values = heap.getValues();
// If it is a heap and the current heap is empty, I can rely on the fact
// that the keys/values are in the correct order to put in the underlying
// structure.
if (this.getCount() <= 0) {
const nodes = this.nodes_;
for (let i = 0; i < keys.length; i++) {
const node = new Node(keys[i], values[i]);
node.index = nodes.length;
nodes.push(node);
}
return;
}
} else {
throw "insertAll supports only OpenSeadragon.PriorityQueue object!";
}
for (let i = 0; i < keys.length; i++) {
this.insert(keys[i], values[i]);
}
}
/**
* Retrieves and removes the root value of this heap.
* @return {Node} The root node item removed from the root of the heap. Returns
* undefined if the heap is empty.
*/
remove() {
const nodes = this.nodes_;
const count = nodes.length;
const rootNode = nodes[0];
if (count <= 0) {
return undefined;
} else if (count == 1) { // eslint-disable-line
nodes.length = 0;
} else {
nodes[0] = nodes.pop();
if (nodes[0]) {
nodes[0].index = 0;
}
this.moveDown_(0);
}
if (rootNode) {
delete rootNode.index;
}
return rootNode;
}
/**
* Retrieves but does not remove the root value of this heap.
* @return {V} The value at the root of the heap. Returns
* undefined if the heap is empty.
*/
peek() {
const nodes = this.nodes_;
if (nodes.length == 0) { // eslint-disable-line
return undefined;
}
return nodes[0].value;
}
/**
* Retrieves but does not remove the key of the root node of this heap.
* @return {string} The key at the root of the heap. Returns undefined if the
* heap is empty.
*/
peekKey() {
return this.nodes_[0] && this.nodes_[0].key;
}
/**
* Move the node up in hierarchy
* @param {Node} node the node
* @param {K} key new ley, must be smaller than current key
*/
decreaseKey(node, key) {
if (node.index === undefined) {
node.key = key;
this.insertNode(node);
} else {
node.key = key;
this.moveUp_(node.index);
}
}
/**
* Moves the node at the given index down to its proper place in the heap.
* @param {number} index The index of the node to move down.
* @private
*/
moveDown_(index) {
const nodes = this.nodes_;
const count = nodes.length;
// Save the node being moved down.
const node = nodes[index];
// While the current node has a child.
while (index < (count >> 1)) {
const leftChildIndex = this.getLeftChildIndex_(index);
const rightChildIndex = this.getRightChildIndex_(index);
// Determine the index of the smaller child.
const smallerChildIndex = rightChildIndex < count &&
nodes[rightChildIndex].key < nodes[leftChildIndex].key ?
rightChildIndex :
leftChildIndex;
// If the node being moved down is smaller than its children, the node
// has found the correct index it should be at.
if (nodes[smallerChildIndex].key > node.key) {
break;
}
// If not, then take the smaller child as the current node.
nodes[index] = nodes[smallerChildIndex];
nodes[index].index = index;
index = smallerChildIndex;
}
nodes[index] = node;
if (node) {
node.index = index;
}
}
/**
* Moves the node at the given index up to its proper place in the heap.
* @param {number} index The index of the node to move up.
* @private
*/
moveUp_(index) {
const nodes = this.nodes_;
const node = nodes[index];
// While the node being moved up is not at the root.
while (index > 0) {
// If the parent is greater than the node being moved up, move the parent
// down.
const parentIndex = this.getParentIndex_(index);
if (nodes[parentIndex].key > node.key) {
nodes[index] = nodes[parentIndex];
nodes[index].index = index;
index = parentIndex;
} else {
break;
}
}
nodes[index] = node;
if (node) {
node.index = index;
}
}
/**
* Gets the index of the left child of the node at the given index.
* @param {number} index The index of the node to get the left child for.
* @return {number} The index of the left child.
* @private
*/
getLeftChildIndex_(index) {
return index * 2 + 1;
}
/**
* Gets the index of the right child of the node at the given index.
* @param {number} index The index of the node to get the right child for.
* @return {number} The index of the right child.
* @private
*/
getRightChildIndex_(index) {
return index * 2 + 2;
}
/**
* Gets the index of the parent of the node at the given index.
* @param {number} index The index of the node to get the parent for.
* @return {number} The index of the parent.
* @private
*/
getParentIndex_(index) {
return (index - 1) >> 1;
}
/**
* Gets the values of the heap.
* @return {!Array<*>} The values in the heap.
*/
getValues() {
const nodes = this.nodes_;
const rv = [];
const l = nodes.length;
for (let i = 0; i < l; i++) {
rv.push(nodes[i].value);
}
return rv;
}
/**
* Gets the keys of the heap.
* @return {!Array<string>} The keys in the heap.
*/
getKeys() {
const nodes = this.nodes_;
const rv = [];
const l = nodes.length;
for (let i = 0; i < l; i++) {
rv.push(nodes[i].key);
}
return rv;
}
/**
* Whether the heap contains the given value.
* @param {V} val The value to check for.
* @return {boolean} Whether the heap contains the value.
*/
containsValue(val) {
return this.nodes_.some((node) => node.value == val); // eslint-disable-line
}
/**
* Whether the heap contains the given key.
* @param {string} key The key to check for.
* @return {boolean} Whether the heap contains the key.
*/
containsKey(key) {
return this.nodes_.some((node) => node.value == key); // eslint-disable-line
}
/**
* Clones a heap and returns a new heap
* @return {!OpenSeadragon.PriorityQueue} A new Heap with the same key-value pairs.
*/
clone() {
return new $.PriorityQueue(this);
}
/**
* The number of key-value pairs in the map
* @return {number} The number of pairs.
*/
getCount() {
return this.nodes_.length;
}
/**
* Returns true if this heap contains no elements.
* @return {boolean} Whether this heap contains no elements.
*/
isEmpty() {
return this.nodes_.length === 0;
}
/**
* Removes all elements from the heap.
*/
clear() {
this.nodes_.length = 0;
}
};
$.PriorityQueue.Node = class {
constructor(key, value) {
/**
* The key.
* @type {K}
* @private
*/
this.key = key;
/**
* The value.
* @type {V}
* @private
*/
this.value = value;
/**
* The node index value. Updated in the heap.
* @type {number}
* @private
*/
this.index = 0;
}
clone() {
return new Node(this.key, this.value);
}
};
}(OpenSeadragon));

View file

@ -2,7 +2,7 @@
* OpenSeadragon - Profiler
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are

View file

@ -2,7 +2,7 @@
* OpenSeadragon - Rect
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are

View file

@ -2,7 +2,7 @@
* OpenSeadragon - ReferenceStrip
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@ -46,7 +46,7 @@ var THIS = {};
*
* This idea is a reexpression of the idea of dzi collections
* which allows a clearer algorithm to reuse the tile sources already
* supported by OpenSeadragon, in heterogenious or homogenious
* supported by OpenSeadragon, in heterogeneous or homogeneous
* sequences just like mixed groups already supported by the viewer
* for the purpose of image sequnces.
*
@ -193,7 +193,6 @@ $.ReferenceStrip = function ( options ) {
element.style.display = 'inline';
element.style['float'] = 'left'; //Webkit
element.style.cssFloat = 'left'; //Firefox
element.style.styleFloat = 'left'; //IE
element.style.padding = '2px';
$.setElementTouchActionNone( element );
$.setElementPointerEventsNone( element );
@ -300,7 +299,8 @@ function onStripClick( event ) {
var page;
if ( 'horizontal' === this.scroll ) {
page = Math.floor(event.position.x / this.panelWidth);
// +4px fix to solve problem with precision on thumbnail selection if there is a lot of them
page = Math.floor(event.position.x / (this.panelWidth + 4));
} else {
page = Math.floor(event.position.y / this.panelHeight);
}
@ -454,7 +454,7 @@ function loadPanels( strip, viewerSize, scroll ) {
animationTime: 0,
loadTilesWithAjax: strip.viewer.loadTilesWithAjax,
ajaxHeaders: strip.viewer.ajaxHeaders,
useCanvas: strip.useCanvas
drawer: 'canvas', //always use canvas for the reference strip
} );
// Allow pointer events to pass through miniViewer's canvas/container
// elements so implicit pointer capture works on touch devices

View file

@ -2,7 +2,7 @@
* OpenSeadragon - Spring
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@ -206,12 +206,13 @@ $.Spring.prototype = {
/**
* @function
* @returns true if the value got updated, false otherwise
* @returns true if the spring is still updating its value, false if it is
* already at the target value.
*/
update: function() {
this.current.time = $.now();
var startValue, targetValue;
let startValue, targetValue;
if (this._exponential) {
startValue = this.start._logValue;
targetValue = this.target._logValue;
@ -220,24 +221,25 @@ $.Spring.prototype = {
targetValue = this.target.value;
}
var currentValue = (this.current.time >= this.target.time) ?
targetValue :
startValue +
( targetValue - startValue ) *
transform(
this.springStiffness,
( this.current.time - this.start.time ) /
( this.target.time - this.start.time )
);
var oldValue = this.current.value;
if (this._exponential) {
this.current.value = Math.exp(currentValue);
if(this.current.time >= this.target.time){
this.current.value = this.target.value;
} else {
this.current.value = currentValue;
let currentValue = startValue +
( targetValue - startValue ) *
transform(
this.springStiffness,
( this.current.time - this.start.time ) /
( this.target.time - this.start.time )
);
if (this._exponential) {
this.current.value = Math.exp(currentValue);
} else {
this.current.value = currentValue;
}
}
return oldValue !== this.current.value;
return this.current.value !== this.target.value;
},
/**

View file

@ -2,7 +2,7 @@
* OpenSeadragon - getString/setString
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are

View file

@ -2,7 +2,7 @@
* OpenSeadragon - Tile
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@ -45,15 +45,15 @@
* @param {Boolean} exists Is this tile a part of a sparse image? ( Also has
* this tile failed to load? )
* @param {String|Function} url The URL of this tile's image or a function that returns a url.
* @param {CanvasRenderingContext2D} context2D The context2D of this tile if it
* is provided directly by the tile source.
* @param {CanvasRenderingContext2D} [context2D=undefined] The context2D of this tile if it
* * is provided directly by the tile source. Deprecated: use Tile::addCache(...) instead.
* @param {Boolean} loadWithAjax Whether this tile image should be loaded with an AJAX request .
* @param {Object} ajaxHeaders The headers to send with this tile's AJAX request (if applicable).
* @param {OpenSeadragon.Rect} sourceBounds The portion of the tile to use as the source of the
* drawing operation, in pixels. Note that this only works when drawing with canvas; when drawing
* with HTML the entire tile is always used.
* @param {String} postData HTTP POST data (usually but not necessarily in k=v&k2=v2... form,
* see TileSource::getPostData) or null
* see TileSource::getTilePostData) or null
* @param {String} cacheKey key to act as a tile cache, must be unique for tiles with unique image data
*/
$.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, ajaxHeaders, sourceBounds, postData, cacheKey) {
@ -81,6 +81,12 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, aja
* @memberof OpenSeadragon.Tile#
*/
this.bounds = bounds;
/**
* Where this tile fits, in normalized coordinates, after positioning
* @member {OpenSeadragon.Rect} positionedBounds
* @memberof OpenSeadragon.Tile#
*/
this.positionedBounds = new OpenSeadragon.Rect(bounds.x, bounds.y, bounds.width, bounds.height);
/**
* The portion of the tile to use as the source of the drawing operation, in pixels. Note that
* this only works when drawing with canvas; when drawing with HTML the entire tile is always used.
@ -97,16 +103,16 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, aja
/**
* Private property to hold string url or url retriever function.
* Consumers should access via Tile.getUrl()
* @private
* @member {String|Function} url
* @memberof OpenSeadragon.Tile#
* @private
*/
this._url = url;
/**
* Post parameters for this tile. For example, it can be an URL-encoded string
* in k1=v1&k2=v2... format, or a JSON, or a FormData instance... or null if no POST request used
* @member {String} postData HTTP POST data (usually but not necessarily in k=v&k2=v2... form,
* see TileSource::getPostData) or null
* see TileSource::getTilePostData) or null
* @memberof OpenSeadragon.Tile#
*/
this.postData = postData;
@ -115,7 +121,9 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, aja
* @member {CanvasRenderingContext2D} context2D
* @memberOf OpenSeadragon.Tile#
*/
this.context2D = context2D;
if (context2D) {
this.context2D = context2D;
}
/**
* Whether to load this tile's image with an AJAX request.
* @member {Boolean} loadWithAjax
@ -135,12 +143,10 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, aja
" in Tile class is deprecated. TileSource.prototype.getTileHashKey will be used.");
cacheKey = $.TileSource.prototype.getTileHashKey(level, x, y, url, ajaxHeaders, postData);
}
/**
* The unique cache key for this tile.
* @member {String} cacheKey
* @memberof OpenSeadragon.Tile#
*/
this.cacheKey = cacheKey;
this._cKey = cacheKey || "";
this._ocKey = cacheKey || "";
/**
* Is this tile loaded?
* @member {Boolean} loaded
@ -153,26 +159,6 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, aja
* @memberof OpenSeadragon.Tile#
*/
this.loading = false;
/**
* The HTML div element for this tile
* @member {Element} element
* @memberof OpenSeadragon.Tile#
*/
this.element = null;
/**
* The HTML img element for this tile.
* @member {Element} imgElement
* @memberof OpenSeadragon.Tile#
*/
this.imgElement = null;
/**
* The alias of this.element.style.
* @member {String} style
* @memberof OpenSeadragon.Tile#
*/
this.style = null;
/**
* This tile's position on screen, in pixels.
* @member {OpenSeadragon.Point} position
@ -206,9 +192,9 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, aja
/**
* The squared distance of this tile to the viewport center.
* Use for comparing tiles.
* @private
* @member {Number} squaredDistance
* @memberof OpenSeadragon.Tile#
* @private
*/
this.squaredDistance = null;
/**
@ -252,6 +238,32 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, aja
* @memberof OpenSeadragon.Tile#
*/
this.isBottomMost = false;
/**
* Owner of this tile. Do not change this property manually.
* @member {OpenSeadragon.TiledImage}
* @memberof OpenSeadragon.Tile#
*/
this.tiledImage = null;
/**
* Array of cached tile data associated with the tile.
* @member {Object}
* @private
*/
this._caches = {};
/**
* Processing flag, exempt the tile from removal when there are ongoing updates
* @member {Boolean|Number}
* @private
*/
this.processing = false;
/**
* Processing promise, resolves when the tile exits processing, or
* resolves immediatelly if not in the processing state.
* @member {OpenSeadragon.Promise}
* @private
*/
this.processingPromise = $.Promise.resolve();
};
/** @lends OpenSeadragon.Tile.prototype */
@ -267,69 +279,42 @@ $.Tile.prototype = {
return this.level + "/" + this.x + "_" + this.y;
},
// private
_hasTransparencyChannel: function() {
console.warn("Tile.prototype._hasTransparencyChannel() has been " +
"deprecated and will be removed in the future. Use TileSource.prototype.hasTransparency() instead.");
return !!this.context2D || this.getUrl().match('.png');
/**
* The unique main cache key for this tile. Created automatically
* from the given tiledImage.source.getTileHashKey(...) implementation.
* @member {String} cacheKey
* @memberof OpenSeadragon.Tile#
*/
get cacheKey() {
return this._cKey;
},
set cacheKey(value) {
if (value === this.cacheKey) {
return;
}
const cache = this.getCache(value);
if (!cache) {
// It's better to first set cache, then change the key to existing one. Warn if otherwise.
$.console.warn("[Tile.cacheKey] should not be set manually. Use addCache() with setAsMain=true.");
}
this._updateMainCacheKey(value);
},
/**
* Renders the tile in an html container.
* @function
* @param {Element} container
* By default equal to tile.cacheKey, marks a cache associated with this tile
* that holds the cache original data (it was loaded with). In case you
* change the tile data, the tile original data should be left with the cache
* 'originalCacheKey' and the new, modified data should be stored in cache 'cacheKey'.
* This key is used in cache resolution: in case new tile data is requested, if
* this cache key exists in the cache it is loaded.
* @member {String} originalCacheKey
* @memberof OpenSeadragon.Tile#
*/
drawHTML: function( container ) {
if (!this.cacheImageRecord) {
$.console.warn(
'[Tile.drawHTML] attempting to draw tile %s when it\'s not cached',
this.toString());
return;
}
if ( !this.loaded ) {
$.console.warn(
"Attempting to draw tile %s when it's not yet loaded.",
this.toString()
);
return;
}
//EXPERIMENTAL - trying to figure out how to scale the container
// content during animation of the container size.
if ( !this.element ) {
var image = this.getImage();
if (!image) {
return;
}
this.element = $.makeNeutralElement( "div" );
this.imgElement = image.cloneNode();
this.imgElement.style.msInterpolationMode = "nearest-neighbor";
this.imgElement.style.width = "100%";
this.imgElement.style.height = "100%";
this.style = this.element.style;
this.style.position = "absolute";
}
if ( this.element.parentNode !== container ) {
container.appendChild( this.element );
}
if ( this.imgElement.parentNode !== this.element ) {
this.element.appendChild( this.imgElement );
}
this.style.top = this.position.y + "px";
this.style.left = this.position.x + "px";
this.style.height = this.size.y + "px";
this.style.width = this.size.x + "px";
if (this.flipped) {
this.style.transform = "scaleX(-1)";
}
$.setElementOpacity( this.element, this.opacity );
set originalCacheKey(value) {
throw "Original Cache Key cannot be managed manually!";
},
get originalCacheKey() {
return this._ocKey;
},
/**
@ -340,7 +325,7 @@ $.Tile.prototype = {
* @returns {Image}
*/
get image() {
$.console.error("[Tile.image] property has been deprecated. Use [Tile.prototype.getImage] instead.");
$.console.error("[Tile.image] property has been deprecated. Use [Tile.getData] instead.");
return this.getImage();
},
@ -352,16 +337,80 @@ $.Tile.prototype = {
* @returns {String}
*/
get url() {
$.console.error("[Tile.url] property has been deprecated. Use [Tile.prototype.getUrl] instead.");
$.console.error("[Tile.url] property has been deprecated. Use [Tile.getUrl] instead.");
return this.getUrl();
},
/**
* The HTML div element for this tile
* @member {Element} element
* @memberof OpenSeadragon.Tile#
* @deprecated
*/
get element() {
$.console.error("Tile::element property is deprecated. Use cache API instead. Moreover, this property might be unstable.");
const cache = this.getCache();
if (!cache || !cache.loaded) {
return null;
}
if (cache.type !== OpenSeadragon.HTMLDrawer.canvasCacheType || cache.type !== OpenSeadragon.HTMLDrawer.imageCacheType) {
$.console.error("Access to HtmlDrawer property via Tile instance: HTMLDrawer must be used!");
return null;
}
return cache.data.element;
},
/**
* The HTML img element for this tile.
* @member {Element} imgElement
* @memberof OpenSeadragon.Tile#
* @deprecated
*/
get imgElement() {
$.console.error("Tile::imgElement property is deprecated. Use cache API instead. Moreover, this property might be unstable.");
const cache = this.getCache();
if (!cache || !cache.loaded) {
return null;
}
if (cache.type !== OpenSeadragon.HTMLDrawer.canvasCacheType || cache.type !== OpenSeadragon.HTMLDrawer.imageCacheType) {
$.console.error("Access to HtmlDrawer property via Tile instance: HTMLDrawer must be used!");
return null;
}
return cache.data.imgElement;
},
/**
* The alias of this.element.style.
* @member {String} style
* @memberof OpenSeadragon.Tile#
* @deprecated
*/
get style() {
$.console.error("Tile::style property is deprecated. Use cache API instead. Moreover, this property might be unstable.");
const cache = this.getCache();
if (!cache || !cache.loaded) {
return null;
}
if (cache.type !== OpenSeadragon.HTMLDrawer.canvasCacheType || cache.type !== OpenSeadragon.HTMLDrawer.imageCacheType) {
$.console.error("Access to HtmlDrawer property via Tile instance: HTMLDrawer must be used!");
return null;
}
return cache.data.style;
},
/**
* Get the Image object for this tile.
* @returns {Image}
* @returns {?Image}
*/
getImage: function() {
return this.cacheImageRecord.getImage();
$.console.error("[Tile.getImage] property has been deprecated. Use 'tile-invalidated' routine event instead.");
//this method used to ensure the underlying data model conformed to given type - convert instead of getData()
const cache = this.getCache(this.cacheKey);
if (!cache) {
return undefined;
}
cache.transformTo("image");
return cache.data;
},
/**
@ -379,131 +428,290 @@ $.Tile.prototype = {
/**
* Get the CanvasRenderingContext2D instance for tile image data drawn
* onto Canvas if enabled and available
* @returns {CanvasRenderingContext2D}
* @returns {CanvasRenderingContext2D|undefined}
*/
getCanvasContext: function() {
return this.context2D || this.cacheImageRecord.getRenderedContext();
$.console.error("[Tile.getCanvasContext] property has been deprecated. Use 'tile-invalidated' routine event instead.");
//this method used to ensure the underlying data model conformed to given type - convert instead of getData()
const cache = this.getCache(this.cacheKey);
if (!cache) {
return undefined;
}
cache.transformTo("context2d");
return cache.data;
},
/**
* Renders the tile in a canvas-based context.
* @function
* @param {Canvas} context
* @param {Function} drawingHandler - Method for firing the drawing event.
* drawingHandler({context, tile, rendered})
* where <code>rendered</code> is the context with the pre-drawn image.
* @param {Number} [scale=1] - Apply a scale to position and size
* @param {OpenSeadragon.Point} [translate] - A translation vector
* @param {Boolean} [shouldRoundPositionAndSize] - Tells whether to round
* position and size of tiles supporting alpha channel in non-transparency
* context.
* @param {OpenSeadragon.TileSource} source - The source specification of the tile.
* The context2D of this tile if it is provided directly by the tile source.
* @deprecated
* @type {CanvasRenderingContext2D}
*/
drawCanvas: function( context, drawingHandler, scale, translate, shouldRoundPositionAndSize, source) {
get context2D() {
$.console.error("[Tile.context2D] property has been deprecated. Use 'tile-invalidated' routine event instead.");
return this.getCanvasContext();
},
var position = this.position.times($.pixelDensityRatio),
size = this.size.times($.pixelDensityRatio),
rendered;
/**
* The context2D of this tile if it is provided directly by the tile source.
* @deprecated
*/
set context2D(value) {
$.console.error("[Tile.context2D] property has been deprecated. Use 'tile-invalidated' routine event instead.");
const cache = this._caches[this.cacheKey];
if (cache) {
this.removeCache(this.cacheKey);
}
this.addCache(this.cacheKey, value, 'context2d', true, false);
},
if (!this.context2D && !this.cacheImageRecord) {
$.console.warn(
'[Tile.drawCanvas] attempting to draw tile %s when it\'s not cached',
this.toString());
return;
/**
* The default cache for this tile.
* @deprecated
* @type OpenSeadragon.CacheRecord
*/
get cacheImageRecord() {
$.console.error("[Tile.cacheImageRecord] property has been deprecated. Use Tile::getCache.");
return this.getCache(this.cacheKey);
},
/**
* The default cache for this tile.
* @deprecated
*/
set cacheImageRecord(value) {
$.console.error("[Tile.cacheImageRecord] property has been deprecated. Use Tile::addCache.");
const cache = this._caches[this.cacheKey];
if (cache) {
this.removeCache(this.cacheKey);
}
rendered = this.getCanvasContext();
if ( !this.loaded || !rendered ){
$.console.warn(
"Attempting to draw tile %s when it's not yet loaded.",
this.toString()
);
return;
}
context.save();
context.globalAlpha = this.opacity;
if (typeof scale === 'number' && scale !== 1) {
// draw tile at a different scale
position = position.times(scale);
size = size.times(scale);
}
if (translate instanceof $.Point) {
// shift tile position slightly
position = position.plus(translate);
}
//if we are supposed to be rendering fully opaque rectangle,
//ie its done fading or fading is turned off, and if we are drawing
//an image with an alpha channel, then the only way
//to avoid seeing the tile underneath is to clear the rectangle
if (context.globalAlpha === 1 && this.hasTransparency) {
if (shouldRoundPositionAndSize) {
// Round to the nearest whole pixel so we don't get seams from overlap.
position.x = Math.round(position.x);
position.y = Math.round(position.y);
size.x = Math.round(size.x);
size.y = Math.round(size.y);
if (value) {
if (value.loaded) {
this.addCache(this.cacheKey, value.data, value.type, true, false);
} else {
value.await().then(x => this.addCache(this.cacheKey, x, value.type, true, false));
}
}
},
//clearing only the inside of the rectangle occupied
//by the png prevents edge flikering
context.clearRect(
position.x,
position.y,
size.x,
size.y
);
/**
* Cache key for main cache that is 'cache-equal', but different from original cache key
* @return {string}
* @private
*/
buildDistinctMainCacheKey: function () {
return this.cacheKey === this.originalCacheKey ? "mod://" + this.originalCacheKey : this.cacheKey;
},
/**
* Read tile cache data object (CacheRecord)
* @param {string} [key=this.cacheKey] cache key to read that belongs to this tile
* @return {OpenSeadragon.CacheRecord}
*/
getCache: function(key = this._cKey) {
const cache = this._caches[key];
if (cache) {
cache.withTileReference(this);
}
return cache;
},
/**
* Create tile cache for given data object.
*
* Using `setAsMain` updates also main tile cache key - the main cache key used to draw this tile.
* In that case, the cache should be ready to be rendered immediatelly (converted to one of the supported formats
* of the currently employed drawer).
*
* NOTE: if the existing cache already exists,
* data parameter is ignored and inherited from the existing cache object.
* WARNING: if you override main tile cache key to point to a different cache, the invalidation routine
* will no longer work. If you need to modify tile main data, prefer to use invalidation routine instead.
*
* @param {string} key cache key, if unique, new cache object is created, else existing cache attached
* @param {*} data this data will be IGNORED if cache already exists; therefore if
* `typeof data === 'function'` holds (both async and normal functions), the data is called to obtain
* the data item: this is an optimization to load data only when necessary.
* @param {string} [type=undefined] data type, will be guessed if not provided (not recommended),
* if data is a callback the type is a mandatory field, not setting it results in undefined behaviour
* @param {boolean} [setAsMain=false] if true, the key will be set as the tile.cacheKey,
* no effect if key === this.cacheKey
* @param [_safely=true] private
* @returns {OpenSeadragon.CacheRecord|null} - The cache record the tile was attached to.
*/
addCache: function(key, data, type = undefined, setAsMain = false, _safely = true) {
const tiledImage = this.tiledImage;
if (!tiledImage) {
return null; //async can access outside its lifetime
}
// This gives the application a chance to make image manipulation
// changes as we are rendering the image
drawingHandler({context: context, tile: this, rendered: rendered});
var sourceWidth, sourceHeight;
if (this.sourceBounds) {
sourceWidth = Math.min(this.sourceBounds.width, rendered.canvas.width);
sourceHeight = Math.min(this.sourceBounds.height, rendered.canvas.height);
} else {
sourceWidth = rendered.canvas.width;
sourceHeight = rendered.canvas.height;
if (!type) {
if (!this.__typeWarningReported) {
$.console.warn(this, "[Tile.addCache] called without type specification. " +
"Automated deduction is potentially unsafe: prefer specification of data type explicitly.");
this.__typeWarningReported = true;
}
if (typeof data === 'function') {
$.console.error("[TileCache.cacheTile] options.data as a callback requires type argument! Current is " + type);
}
type = $.convertor.guessType(data);
}
context.translate(position.x + size.x / 2, 0);
if (this.flipped) {
context.scale(-1, 1);
const overwritesMainCache = key === this.cacheKey;
if (_safely && (overwritesMainCache || setAsMain)) {
// Need to get the supported type for rendering out of the active drawer.
const supportedTypes = tiledImage.viewer.drawer.getSupportedDataFormats();
const conversion = $.convertor.getConversionPath(type, supportedTypes);
$.console.assert(conversion, "[Tile.addCache] data was set for the default tile cache we are unable" +
`to render. Make sure OpenSeadragon.convertor was taught to convert ${type} to (one of): ${conversion.toString()}`);
}
context.drawImage(
rendered.canvas,
0,
0,
sourceWidth,
sourceHeight,
-size.x / 2,
position.y,
size.x,
size.y
);
context.restore();
const cachedItem = tiledImage._tileCache.cacheTile({
data: data,
dataType: type,
tile: this,
cacheKey: key,
cutoff: tiledImage.source.getClosestLevel(),
});
const havingRecord = this._caches[key];
if (havingRecord !== cachedItem) {
this._caches[key] = cachedItem;
if (havingRecord) {
havingRecord.removeTile(this);
tiledImage._tileCache.safeUnloadCache(havingRecord);
}
}
// Update cache key if differs and main requested
if (!overwritesMainCache && setAsMain) {
this._updateMainCacheKey(key);
}
return cachedItem;
},
/**
* Add cache object to the tile
*
* @param {string} key cache key, if unique, new cache object is created, else existing cache attached
* @param {OpenSeadragon.CacheRecord} cache the cache object to attach to this tile
* @param {boolean} [setAsMain=false] if true, the key will be set as the tile.cacheKey,
* no effect if key === this.cacheKey
* @param [_safely=true] private
* @returns {OpenSeadragon.CacheRecord|null} - Returns cache parameter reference if attached.
*/
setCache(key, cache, setAsMain = false, _safely = true) {
const tiledImage = this.tiledImage;
if (!tiledImage) {
return null; //async can access outside its lifetime
}
const overwritesMainCache = key === this.cacheKey;
if (_safely) {
$.console.assert(cache instanceof $.CacheRecord, "[Tile.setCache] cache must be a CacheRecord object!");
if (overwritesMainCache || setAsMain) {
// Need to get the supported type for rendering out of the active drawer.
const supportedTypes = tiledImage.viewer.drawer.getSupportedDataFormats();
const conversion = $.convertor.getConversionPath(cache.type, supportedTypes);
$.console.assert(conversion, "[Tile.setCache] data was set for the default tile cache we are unable" +
`to render. Make sure OpenSeadragon.convertor was taught to convert ${cache.type} to (one of): ${conversion.toString()}`);
}
}
const havingRecord = this._caches[key];
if (havingRecord !== cache) {
this._caches[key] = cache;
cache.addTile(this); // keep reference bidirectional
if (havingRecord) {
havingRecord.removeTile(this);
tiledImage._tileCache.safeUnloadCache(havingRecord);
}
}
// Update cache key if differs and main requested
if (!overwritesMainCache && setAsMain) {
this._updateMainCacheKey(key);
}
return cache;
},
/**
* Sets the main cache key for this tile and
* performs necessary updates
* @param value
* @private
*/
_updateMainCacheKey: function(value) {
let ref = this._caches[this._cKey];
if (ref) {
// make sure we free drawer internal cache if people change cache key externally
ref.destroyInternalCache();
}
this._cKey = value;
},
/**
* Get the number of caches available to this tile
* @returns {number} number of caches
*/
getCacheSize: function() {
return Object.values(this._caches).length;
},
/**
* Free tile cache. Removes by default the cache record if no other tile uses it.
* @param {string} key cache key, required
* @param {boolean} [freeIfUnused=true] set to false if zombie should be created
* @return {OpenSeadragon.CacheRecord|undefined} reference to the cache record if it was removed,
* undefined if removal was refused to perform (e.g. does not exist, it is an original data target etc.)
*/
removeCache: function(key, freeIfUnused = true) {
const deleteTarget = this._caches[key];
if (!deleteTarget) {
// try to erase anyway in case the cache got stuck in memory
this.tiledImage._tileCache.unloadCacheForTile(this, key, freeIfUnused, true);
return undefined;
}
const currentMainKey = this.cacheKey,
originalDataKey = this.originalCacheKey,
sameBuiltinKeys = currentMainKey === originalDataKey;
if (!sameBuiltinKeys && originalDataKey === key) {
$.console.warn("[Tile.removeCache] original data must not be manually deleted: other parts of the code might rely on it!",
"If you want the tile not to preserve the original data, toggle of data perseverance in tile.setData().");
return undefined;
}
if (currentMainKey === key) {
if (!sameBuiltinKeys && this._caches[originalDataKey]) {
// if we have original data let's revert back
this._updateMainCacheKey(originalDataKey);
} else {
$.console.warn("[Tile.removeCache] trying to remove the only cache that can be used to draw the tile!",
"If you want to remove the main cache, first set different cache as main with tile.addCache()");
return undefined;
}
}
if (this.tiledImage._tileCache.unloadCacheForTile(this, key, freeIfUnused, false)) {
//if we managed to free tile from record, we are sure we decreased cache count
delete this._caches[key];
}
return deleteTarget;
},
/**
* Get the ratio between current and original size.
* @function
* @returns {Float}
* @deprecated
* @returns {number}
*/
getScaleForEdgeSmoothing: function() {
var context;
if (this.cacheImageRecord) {
context = this.cacheImageRecord.getRenderedContext();
} else if (this.context2D) {
context = this.context2D;
} else {
// getCanvasContext is deprecated and so should be this method.
$.console.warn("[Tile.getScaleForEdgeSmoothing] is deprecated, the following error is the consequence:");
const context = this.getCanvasContext();
if (!context) {
$.console.warn(
'[Tile.drawCanvas] attempting to get tile scale %s when tile\'s not cached',
this.toString());
@ -524,8 +732,8 @@ $.Tile.prototype = {
// the sketch canvas to the top and left and we must use negative coordinates to repaint it
// to the main canvas. In that case, some browsers throw:
// INDEX_SIZE_ERR: DOM Exception 1: Index or size was negative, or greater than the allowed value.
var x = Math.max(1, Math.ceil((sketchCanvasSize.x - canvasSize.x) / 2));
var y = Math.max(1, Math.ceil((sketchCanvasSize.y - canvasSize.y) / 2));
const x = Math.max(1, Math.ceil((sketchCanvasSize.x - canvasSize.x) / 2));
const y = Math.max(1, Math.ceil((sketchCanvasSize.y - canvasSize.y) / 2));
return new $.Point(x, y).minus(
this.position
.times($.pixelDensityRatio)
@ -537,21 +745,59 @@ $.Tile.prototype = {
},
/**
* Removes tile from its container.
* Reflect that a cache object was renamed. Called internally from TileCache.
* Do NOT call manually.
* @function
* @private
*/
unload: function() {
if ( this.imgElement && this.imgElement.parentNode ) {
this.imgElement.parentNode.removeChild( this.imgElement );
reflectCacheRenamed: function (oldKey, newKey) {
let cache = this._caches[oldKey];
if (!cache) {
return; // nothing to fix
}
if ( this.element && this.element.parentNode ) {
this.element.parentNode.removeChild( this.element );
// Do update via private refs, old key no longer exists in cache
if (oldKey === this._ocKey) {
this._ocKey = newKey;
}
if (oldKey === this._cKey) {
this._cKey = newKey;
}
// Working key is never updated, it will be invalidated (but do not dereference cache, just fix the pointers)
this._caches[newKey] = cache;
delete this._caches[oldKey];
},
this.element = null;
this.imgElement = null;
/**
* Check if two tiles are data-equal
* @param {OpenSeadragon.Tile} tile
*/
equals(tile) {
return this._ocKey === tile._ocKey;
},
/**
* Removes tile from the system: it will still be present in the
* OSD memory, but marked as loaded=false, and its data will be erased.
* @param {boolean} [erase=false]
*/
unload: function(erase = false) {
if (!this.loaded) {
return;
}
this.tiledImage._tileCache.unloadTile(this, erase);
},
/**
* this method shall be called only by cache system when the tile is already empty of data
* @private
*/
_unload: function () {
this.tiledImage = null;
this._caches = {};
this._cacheSize = 0;
this.loaded = false;
this.loading = false;
this._cKey = this._ocKey;
}
};

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@
* OpenSeadragon - TileSource
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@ -55,7 +55,8 @@
* @param {Object} options
* You can either specify a URL, or literally define the TileSource (by specifying
* width, height, tileSize, tileOverlap, minLevel, and maxLevel). For the former,
* the extending class is expected to implement 'getImageInfo' and 'configure'.
* the extending class is expected to implement 'supports' and 'configure'.
* Note that _in this case, the child class of getImageInfo() is ignored!_
* For the latter, the construction is assumed to occur through
* the extending classes implementation of 'configure'.
* @param {String} [options.url]
@ -72,6 +73,7 @@
* @param {Boolean} [options.splitHashDataForPost]
* First occurrence of '#' in the options.url is used to split URL
* and the latter part is treated as POST data (applies to getImageInfo(...))
* Does not work if getImageInfo() is overridden and used (see the options description)
* @param {Number} [options.width]
* Width of the source image at max resolution in pixels.
* @param {Number} [options.height]
@ -139,6 +141,12 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
} );
}
/**
* Retrieve context2D of this tile source
* @memberOf OpenSeadragon.TileSource
* @function getContext2D
*/
/**
* Ratio of width to height
* @member {Number} aspectRatio
@ -170,6 +178,8 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
* @memberof OpenSeadragon.TileSource#
*/
// TODO potentially buggy behavior: what if .url is used by child class before it calls super constructor?
// this can happen if old JS class definition is used
if( 'string' === $.type( arguments[ 0 ] ) ){
this.url = arguments[0];
}
@ -370,6 +380,7 @@ $.TileSource.prototype = {
point.y >= 0 && point.y <= 1 / this.aspectRatio;
$.console.assert(validPoint, "[TileSource.getTileAtPoint] must be called with a valid point.");
var widthScaled = this.dimensions.x * this.getLevelScale(level);
var pixelX = point.x * widthScaled;
var pixelY = point.y * widthScaled;
@ -424,6 +435,13 @@ $.TileSource.prototype = {
/**
* Responsible for retrieving, and caching the
* image metadata pertinent to this TileSources implementation.
* There are three scenarios of opening a tile source: providing a parseable string, plain object, or an URL.
* This method is only called by OSD if the TileSource configuration is a non-parseable string (~url).
*
* The string can contain a hash `#` symbol, followed by
* key=value arguments. If this is the case, this method sends this
* data as a POST body.
*
* @function
* @param {String} url
* @throws {Error}
@ -553,7 +571,7 @@ $.TileSource.prototype = {
* @property {String} message
* @property {String} source
* @property {String} postData - HTTP POST data (usually but not necessarily in k=v&k2=v2... form,
* see TileSource::getPostData) or null
* see TileSource::getTilePostData) or null
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
_this.raiseEvent( 'open-failed', {
@ -568,13 +586,13 @@ $.TileSource.prototype = {
},
/**
* Responsible determining if a the particular TileSource supports the
* Responsible for determining if the particular TileSource supports the
* data format ( and allowed to apply logic against the url the data was
* loaded from, if any ). Overriding implementations are expected to do
* something smart with data and / or url to determine support. Also
* understand that iteration order of TileSources is not guarunteed so
* understand that iteration order of TileSources is not guaranteed so
* please make sure your data or url is expressive enough to ensure a simple
* and sufficient mechanisim for clear determination.
* and sufficient mechanism for clear determination.
* @function
* @param {String|Object|Array|Document} data
* @param {String} url - the url the data was loaded
@ -585,6 +603,17 @@ $.TileSource.prototype = {
return false;
},
/**
* Check whether two tileSources are equal. This is used for example
* when replacing tile-sources, which turns on the zombie cache before
* old item removal.
* @param {OpenSeadragon.TileSource} otherSource
* @returns {Boolean}
*/
equals: function (otherSource) {
return false;
},
/**
* Responsible for parsing and configuring the
* image metadata pertinent to this TileSources implementation.
@ -607,6 +636,16 @@ $.TileSource.prototype = {
throw new Error( "Method not implemented." );
},
/**
* Shall this source need to free some objects
* upon unloading, it must be done here. For example, canvas
* size must be set to 0 for safari to free.
* @param {OpenSeadragon.Viewer} viewer
*/
destroy: function ( viewer ) {
//no-op
},
/**
* Responsible for retrieving the url which will return an image for the
* region specified by the given x, y, and level components.
@ -663,6 +702,11 @@ $.TileSource.prototype = {
* The headers returned here will override headers specified at the Viewer or TiledImage level.
* Specifying a falsy value for a header will clear its existing value set at the Viewer or
* TiledImage level (if any).
*
* Note that the headers of existing tiles don't automatically change when this function
* returns updated headers. To do that, you need to call {@link OpenSeadragon.Viewer#setAjaxHeaders}
* and propagate the changes.
*
* @function
* @param {Number} level
* @param {Number} x
@ -677,9 +721,12 @@ $.TileSource.prototype = {
* The tile cache object is uniquely determined by this key and used to lookup
* the image data in cache: keys should be different if images are different.
*
* In case a tile has context2D property defined (TileSource.prototype.getContext2D)
* or its context2D is set manually; the cache is not used and this function
* is irrelevant.
* You can return falsey tile cache key, in which case the tile will
* be created without invoking ImageJob --- but with data=null. Then,
* you are responsible for manually creating the cache data. This is useful
* particularly if you want to use empty TiledImage with client-side derived data
* only. The default tile-cache key is then called "" - an empty string.
*
* Note: default behaviour does not take into account post data.
* @param {Number} level tile level it was fetched with
* @param {Number} x x-coordinate in the pyramid level
@ -687,6 +734,9 @@ $.TileSource.prototype = {
* @param {String} url the tile was fetched with
* @param {Object} ajaxHeaders the tile was fetched with
* @param {*} postData data the tile was fetched with (type depends on getTilePostData(..) return type)
* @return {?String} can return the cache key or null, in that case an empty cache is initialized
* without downloading any data for internal use: user has to define the cache contents manually, via
* the cache interface of this class.
*/
getTileHashKey: function(level, x, y, url, ajaxHeaders, postData) {
function withHeaders(hash) {
@ -717,10 +767,15 @@ $.TileSource.prototype = {
/**
* Decide whether tiles have transparency: this is crucial for correct images blending.
* Overriden on a tile level by setting tile.hasTransparency = true;
* @param context2D unused, deprecated argument
* @param url tile.getUrl() value for given tile
* @param ajaxHeaders tile.ajaxHeaders value for given tile
* @param post tile.post value for given tile
* @returns {boolean} true if the image has transparency
*/
hasTransparency: function(context2D, url, ajaxHeaders, post) {
return !!context2D || url.match('.png');
return url.match('.png');
},
/**
@ -732,45 +787,49 @@ $.TileSource.prototype = {
* @param {String} [context.ajaxHeaders] - Headers to add to the image request if using AJAX.
* @param {Boolean} [context.ajaxWithCredentials] - Whether to set withCredentials on AJAX requests.
* @param {String} [context.crossOriginPolicy] - CORS policy to use for downloads
* @param {String} [context.postData] - HTTP POST data (usually but not necessarily in k=v&k2=v2... form,
* see TileSource::getPostData) or null
* @param {?String|?Object} [context.postData] - HTTP POST data (usually but not necessarily
* in k=v&k2=v2... form, see TileSource::getTilePostData) or null
* @param {*} [context.userData] - Empty object to attach your own data and helper variables to.
* @param {Function} [context.finish] - Should be called unless abort() was executed, e.g. on all occasions,
* be it successful or unsuccessful request.
* Usage: context.finish(data, request, errMessage). Pass the downloaded data object or null upon failure.
* Add also reference to an ajax request if used. Provide error message in case of failure.
* @param {Function} [context.finish] - Should be called unless abort() was executed upon successful
* data retrieval.
* Usage: context.finish(data, request, dataType=undefined). Pass the downloaded data object
* add also reference to an ajax request if used. Optionally, specify what data type the data is.
* @param {Function} [context.fail] - Should be called unless abort() was executed upon unsuccessful request.
* Usage: context.fail(errMessage, request). Provide error message in case of failure,
* add also reference to an ajax request if used.
* @param {Function} [context.abort] - Called automatically when the job times out.
* Usage: context.abort().
* @param {Function} [context.callback] @private - Called automatically once image has been downloaded
* Usage: if you decide to abort the request (no fail/finish will be called), call context.abort().
* @param {Function} [context.callback] Private parameter. Called automatically once image has been downloaded
* (triggered by finish).
* @param {Number} [context.timeout] @private - The max number of milliseconds that
* @param {Number} [context.timeout] Private parameter. The max number of milliseconds that
* this image job may take to complete.
* @param {string} [context.errorMsg] @private - The final error message, default null (set by finish).
* @param {string} [context.errorMsg] Private parameter. The final error message, default null (set by finish).
*/
downloadTileStart: function (context) {
var dataStore = context.userData,
const dataStore = context.userData,
image = new Image();
dataStore.image = image;
dataStore.request = null;
var finish = function(error) {
if (!image) {
context.finish(null, dataStore.request, "Image load failed: undefined Image instance.");
const finalize = function(error) {
if (error || !image) {
context.fail(error || "[downloadTileStart] Image load failed: undefined Image instance.",
dataStore.request);
return;
}
image.onload = image.onerror = image.onabort = null;
context.finish(error ? null : image, dataStore.request, error);
context.finish(image, dataStore.request, "image");
};
image.onload = function () {
finish();
finalize();
};
image.onabort = image.onerror = function() {
finish("Image load aborted.");
finalize("[downloadTileStart] Image load aborted.");
};
// Load the tile with an AJAX request if the loadWithAjax option is
// set. Otherwise load the image by setting the source proprety of the image object.
// set. Otherwise load the image by setting the source property of the image object.
if (context.loadWithAjax) {
dataStore.request = $.makeAjaxRequest({
url: context.src,
@ -786,21 +845,21 @@ $.TileSource.prototype = {
try {
blb = new window.Blob([request.response]);
} catch (e) {
var BlobBuilder = (
const BlobBuilder = (
window.BlobBuilder ||
window.WebKitBlobBuilder ||
window.MozBlobBuilder ||
window.MSBlobBuilder
);
if (e.name === 'TypeError' && BlobBuilder) {
var bb = new BlobBuilder();
const bb = new BlobBuilder();
bb.append(request.response);
blb = bb.getBlob();
}
}
// If the blob is empty for some reason consider the image load a failure.
if (blb.size === 0) {
finish("Empty image response.");
finalize("[downloadTileStart] Empty image response.");
} else {
// Create a URL for the blob data and make it the source of the image object.
// This will still trigger Image.onload to indicate a successful tile load.
@ -808,7 +867,7 @@ $.TileSource.prototype = {
}
},
error: function(request) {
finish("Image load aborted - XHR error");
finalize("[downloadTileStart] Image load aborted - XHR error");
}
});
} else {
@ -822,6 +881,8 @@ $.TileSource.prototype = {
/**
* Provide means of aborting the execution.
* Note that if you override this function, you should override also downloadTileStart().
* Note that calling job.abort() would create an infinite loop!
*
* @param {ImageJob} context job, the same object as with downloadTileStart(..)
* @param {*} [context.userData] - Empty object to attach (and mainly read) your own data.
*/
@ -840,33 +901,44 @@ $.TileSource.prototype = {
* cacheObject parameter should be used to attach the data to, there are no
* conventions on how it should be stored - all the logic is implemented within *TileCache() functions.
*
* Note that if you override any of *TileCache() functions, you should override all of them.
* @param {object} cacheObject context cache object
* Note that
* - data is cached automatically as cacheObject.data
* - if you override any of *TileCache() functions, you should override all of them.
* - these functions might be called over shared cache object managed by other TileSources simultaneously.
* @param {OpenSeadragon.CacheRecord} cacheObject context cache object
* @param {*} data image data, the data sent to ImageJob.prototype.finish(), by default an Image object
* @param {Tile} tile instance the cache was created with
* @param {OpenSeadragon.Tile} tile instance the cache was created with
* @deprecated
*/
createTileCache: function(cacheObject, data, tile) {
cacheObject._data = data;
$.console.error("[TileSource.createTileCache] has been deprecated. Use cache API of a tile instead.");
//no-op, we create the cache automatically
},
/**
* Cache object destructor, unset all properties you created to allow GC collection.
* Note that if you override any of *TileCache() functions, you should override all of them.
* @param {object} cacheObject context cache object
* Note that these functions might be called over shared cache object managed by other TileSources simultaneously.
* Original cache data is cacheObject.data, but do not delete it manually! It is taken care for,
* you might break things.
* @param {OpenSeadragon.CacheRecord} cacheObject context cache object
* @deprecated
*/
destroyTileCache: function (cacheObject) {
cacheObject._data = null;
cacheObject._renderedContext = null;
$.console.error("[TileSource.destroyTileCache] has been deprecated. Use cache API of a tile instead.");
//no-op, handled internally
},
/**
* Raw data getter
* Note that if you override any of *TileCache() functions, you should override all of them.
* @param {object} cacheObject context cache object
* @returns {*} cache data
* Raw data getter, should return anything that is compatible with the system, or undefined
* if the system can handle it.
* @param {OpenSeadragon.CacheRecord} cacheObject context cache object
* @returns {OpenSeadragon.Promise<?>} cache data
* @deprecated
*/
getTileCacheData: function(cacheObject) {
return cacheObject._data;
$.console.error("[TileSource.getTileCacheData] has been deprecated. Use cache API of a tile instead.");
return cacheObject.getDataAs(undefined, false);
},
/**
@ -874,11 +946,14 @@ $.TileSource.prototype = {
* - plugins might need image representation of the data
* - div HTML rendering relies on image element presence
* Note that if you override any of *TileCache() functions, you should override all of them.
* @param {object} cacheObject context cache object
* Note that these functions might be called over shared cache object managed by other TileSources simultaneously.
* @param {OpenSeadragon.CacheRecord} cacheObject context cache object
* @returns {Image} cache data as an Image
* @deprecated
*/
getTileCacheDataAsImage: function(cacheObject) {
return cacheObject._data; //the data itself by default is Image
$.console.error("[TileSource.getTileCacheDataAsImage] has been deprecated. Use cache API of a tile instead.");
return cacheObject.getImage();
},
/**
@ -886,21 +961,13 @@ $.TileSource.prototype = {
* - most heavily used rendering method is a canvas-based approach,
* convert the data to a canvas and return it's 2D context
* Note that if you override any of *TileCache() functions, you should override all of them.
* @param {object} cacheObject context cache object
* @param {OpenSeadragon.CacheRecord} cacheObject context cache object
* @returns {CanvasRenderingContext2D} context of the canvas representation of the cache data
* @deprecated
*/
getTileCacheDataAsContext2D: function(cacheObject) {
if (!cacheObject._renderedContext) {
var canvas = document.createElement( 'canvas' );
canvas.width = cacheObject._data.width;
canvas.height = cacheObject._data.height;
cacheObject._renderedContext = canvas.getContext('2d');
cacheObject._renderedContext.drawImage( cacheObject._data, 0, 0 );
//since we are caching the prerendered image on a canvas
//allow the image to not be held in memory
cacheObject._data = null;
}
return cacheObject._renderedContext;
$.console.error("[TileSource.getTileCacheDataAsContext2D] has been deprecated. Use cache API of a tile instead.");
return cacheObject.getRenderedContext();
}
};
@ -932,7 +999,7 @@ function processResponse( xhr ){
throw new Error( $.getString( "Errors.Status", status, statusText ) );
}
if( responseText.match(/\s*<.*/) ){
if( responseText.match(/^\s*<.*/) ){
try{
data = ( xhr.responseXML && xhr.responseXML.documentElement ) ?
xhr.responseXML :

View file

@ -2,7 +2,7 @@
* OpenSeadragon - TileSourceCollection
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are

View file

@ -2,7 +2,7 @@
* OpenSeadragon - TmsTileSource
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@ -131,6 +131,13 @@ $.extend( $.TmsTileSource.prototype, $.TileSource.prototype, /** @lends OpenSead
var yTiles = this.getNumTiles( level ).y - 1;
return this.tilesUrl + level + "/" + x + "/" + (yTiles - y) + ".png";
},
/**
* Equality comparator
*/
equals: function (otherSource) {
return this.tilesUrl === otherSource.tilesUrl;
}
});

View file

@ -2,7 +2,7 @@
* OpenSeadragon - Viewer
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@ -89,6 +89,21 @@ $.Viewer = function( options ) {
delete options.config;
}
// Move deprecated drawer options from the base options object into a sub-object
// This is an array to make it easy to add additional properties to convert to
// drawer options later if it makes sense to set at the drawer level rather than
// per tiled image (for example, subPixelRoundingForTransparency).
let drawerOptionList = [
'useCanvas', // deprecated
];
options.drawerOptions = Object.assign({},
drawerOptionList.reduce((drawerOptions, option) => {
drawerOptions[option] = options[option];
delete options[option];
return drawerOptions;
}, {}),
options.drawerOptions);
//Public properties
//Allow the options object to override global defaults
$.extend( true, this, {
@ -198,6 +213,7 @@ $.Viewer = function( options ) {
$.console.warn("Hash " + this.hash + " has already been used.");
}
//Private state properties
THIS[ this.hash ] = {
fsBoundsDelta: new $.Point( 1, 1 ),
@ -307,7 +323,9 @@ $.Viewer = function( options ) {
nonPrimaryPressHandler: $.delegate( this, onCanvasNonPrimaryPress ),
nonPrimaryReleaseHandler: $.delegate( this, onCanvasNonPrimaryRelease ),
scrollHandler: $.delegate( this, onCanvasScroll ),
pinchHandler: $.delegate( this, onCanvasPinch )
pinchHandler: $.delegate( this, onCanvasPinch ),
focusHandler: $.delegate( this, onCanvasFocus ),
blurHandler: $.delegate( this, onCanvasBlur ),
});
this.outerTracker = new $.MouseTracker({
@ -381,24 +399,25 @@ $.Viewer = function( options ) {
// Create the viewport
this.viewport = new $.Viewport({
containerSize: THIS[ this.hash ].prevContainerSize,
springStiffness: this.springStiffness,
animationTime: this.animationTime,
minZoomImageRatio: this.minZoomImageRatio,
maxZoomPixelRatio: this.maxZoomPixelRatio,
visibilityRatio: this.visibilityRatio,
wrapHorizontal: this.wrapHorizontal,
wrapVertical: this.wrapVertical,
defaultZoomLevel: this.defaultZoomLevel,
minZoomLevel: this.minZoomLevel,
maxZoomLevel: this.maxZoomLevel,
viewer: this,
degrees: this.degrees,
flipped: this.flipped,
navigatorRotate: this.navigatorRotate,
homeFillsViewer: this.homeFillsViewer,
margins: this.viewportMargins,
silenceMultiImageWarnings: this.silenceMultiImageWarnings
containerSize: THIS[ this.hash ].prevContainerSize,
springStiffness: this.springStiffness,
animationTime: this.animationTime,
minZoomImageRatio: this.minZoomImageRatio,
maxZoomPixelRatio: this.maxZoomPixelRatio,
visibilityRatio: this.visibilityRatio,
wrapHorizontal: this.wrapHorizontal,
wrapVertical: this.wrapVertical,
defaultZoomLevel: this.defaultZoomLevel,
minZoomLevel: this.minZoomLevel,
maxZoomLevel: this.maxZoomLevel,
viewer: this,
degrees: this.degrees,
flipped: this.flipped,
overlayPreserveContentDirection: this.overlayPreserveContentDirection,
navigatorRotate: this.navigatorRotate,
homeFillsViewer: this.homeFillsViewer,
margins: this.viewportMargins,
silenceMultiImageWarnings: this.silenceMultiImageWarnings
});
this.viewport._setContentBounds(this.world.getHomeBounds(), this.world.getContentFactor());
@ -406,21 +425,52 @@ $.Viewer = function( options ) {
// Create the image loader
this.imageLoader = new $.ImageLoader({
jobLimit: this.imageLoaderLimit,
timeout: options.timeout
timeout: options.timeout,
tileRetryMax: this.tileRetryMax,
tileRetryDelay: this.tileRetryDelay
});
// Create the tile cache
this.tileCache = new $.TileCache({
viewer: this,
maxImageCacheCount: this.maxImageCacheCount
});
// Create the drawer
this.drawer = new $.Drawer({
viewer: this,
viewport: this.viewport,
element: this.canvas,
debugGridColor: this.debugGridColor
});
//Create the drawer based on selected options
if (Object.prototype.hasOwnProperty.call(this.drawerOptions, 'useCanvas') ){
$.console.error('useCanvas is deprecated, use the "drawer" option to indicate preferred drawer(s)');
// for backwards compatibility, use HTMLDrawer if useCanvas is defined and is falsey
if (!this.drawerOptions.useCanvas){
this.drawer = $.HTMLDrawer;
}
delete this.drawerOptions.useCanvas;
}
let drawerCandidates = Array.isArray(this.drawer) ? this.drawer : [this.drawer];
if (drawerCandidates.length === 0){
// if an empty array was passed in, throw a warning and use the defaults
// note: if the drawer option is not specified, the defaults will already be set so this won't apply
drawerCandidates = [$.DEFAULT_SETTINGS.drawer].flat(); // ensure it is a list
$.console.warn('No valid drawers were selected. Using the default value.');
}
this.drawer = null;
for (const drawerCandidate of drawerCandidates){
let success = this.requestDrawer(drawerCandidate, {mainDrawer: true, redrawImmediately: false});
if(success){
break;
}
}
if (!this.drawer){
$.console.error('No drawer could be created!');
throw('Error with creating the selected drawer(s)');
}
// Pass the imageSmoothingEnabled option along to the drawer
this.drawer.setImageSmoothingEnabled(this.imageSmoothingEnabled);
// Overlay container
this.overlaysContainer = $.makeNeutralElement( "div" );
@ -466,6 +516,10 @@ $.Viewer = function( options ) {
displayRegionColor: this.navigatorDisplayRegionColor,
crossOriginPolicy: this.crossOriginPolicy,
animationTime: this.animationTime,
drawer: this.drawer.getType(),
loadTilesWithAjax: this.loadTilesWithAjax,
ajaxHeaders: this.ajaxHeaders,
ajaxWithCredentials: this.ajaxWithCredentials,
});
}
@ -492,11 +546,6 @@ $.Viewer = function( options ) {
beginControlsAutoHide( _this );
} );
// Initial canvas options
if ( this.imageSmoothingEnabled !== undefined && !this.imageSmoothingEnabled){
this.drawer.setImageSmoothingEnabled(this.imageSmoothingEnabled);
}
// Register the viewer
$._viewers.set(this.element, this);
};
@ -713,6 +762,30 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
return this;
},
/**
* Updates data within every tile in the viewer. Should be called
* when tiles are outdated and should be re-processed. Useful mainly
* for plugins that change tile data.
* @function
* @param {Boolean} [restoreTiles=true] if true, tile processing starts from the tile original data
* @fires OpenSeadragon.Viewer.event:tile-invalidated
* @return {OpenSeadragon.Promise<?>}
*/
requestInvalidate: function (restoreTiles = true) {
if ( !THIS[ this.hash ] ) {
//this viewer has already been destroyed: returning immediately
return $.Promise.resolve();
}
const tStamp = $.now();
const worldPromise = this.world.requestInvalidate(restoreTiles, tStamp);
if (!this.navigator) {
return worldPromise;
}
const navigatorPromise = this.navigator.world.requestInvalidate(restoreTiles, tStamp);
return $.Promise.all([worldPromise, navigatorPromise]);
},
/**
* @function
@ -739,6 +812,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
THIS[ this.hash ].animating = false;
this.world.removeAll();
this.tileCache.clear();
this.imageLoader.clear();
/**
@ -884,12 +958,79 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
this.removeAllHandlers();
},
/**
* Request a drawer for this viewer, as a supported string or drawer constructor.
* @param {String | OpenSeadragon.DrawerBase} drawerCandidate The type of drawer to try to construct.
* @param { Object } options
* @param { Boolean } [options.mainDrawer] Whether to use this as the viewer's main drawer. Default = true.
* @param { Boolean } [options.redrawImmediately] Whether to immediately draw a new frame. Only used if options.mainDrawer = true. Default = true.
* @param { Object } [options.drawerOptions] Options for this drawer. Defaults to viewer.drawerOptions.
* for this viewer type. See {@link OpenSeadragon.Options}.
* @returns {Object | Boolean} The drawer that was created, or false if the requested drawer is not supported
*/
requestDrawer(drawerCandidate, options){
const defaultOpts = {
mainDrawer: true,
redrawImmediately: true,
drawerOptions: null
};
options = $.extend(true, defaultOpts, options);
const mainDrawer = options.mainDrawer;
const redrawImmediately = options.redrawImmediately;
const drawerOptions = options.drawerOptions;
const oldDrawer = this.drawer;
let Drawer = null;
//if the candidate inherits from a drawer base, use it
if (drawerCandidate && drawerCandidate.prototype instanceof $.DrawerBase) {
Drawer = drawerCandidate;
drawerCandidate = 'custom';
} else if (typeof drawerCandidate === "string") {
Drawer = $.determineDrawer(drawerCandidate);
}
if(!Drawer){
$.console.warn('Unsupported drawer! Drawer must be an existing string type, or a class that extends OpenSeadragon.DrawerBase.');
}
// if the drawer is supported, create it and return true
if (Drawer && Drawer.isSupported()) {
// first destroy the previous drawer
if(oldDrawer && mainDrawer){
oldDrawer.destroy();
}
// create the new drawer
const newDrawer = new Drawer({
viewer: this,
viewport: this.viewport,
element: this.canvas,
debugGridColor: this.debugGridColor,
options: drawerOptions || this.drawerOptions[drawerCandidate],
});
if(mainDrawer){
this.drawer = newDrawer;
if(redrawImmediately){
this.forceRedraw();
}
}
return newDrawer;
}
return false;
},
/**
* @function
* @returns {Boolean}
*/
isMouseNavEnabled: function () {
return this.innerTracker.isTracking();
return this.innerTracker.tracking;
},
/**
@ -962,7 +1103,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
* Turns debugging mode on or off for this viewer.
*
* @function
* @param {Boolean} true to turn debug on, false to turn debug off.
* @param {Boolean} debugMode true to turn debug on, false to turn debug off.
*/
setDebugMode: function(debugMode){
@ -974,12 +1115,69 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
this.forceRedraw();
},
/**
* Update headers to include when making AJAX requests.
*
* Unless `propagate` is set to false (which is likely only useful in rare circumstances),
* the updated headers are propagated to all tiled images, each of which will subsequently
* propagate the changed headers to all their tiles.
* If applicable, the headers of the viewer's navigator and reference strip will also be updated.
*
* Note that the rules for merging headers still apply, i.e. headers returned by
* {@link OpenSeadragon.TileSource#getTileAjaxHeaders} take precedence over
* `TiledImage.ajaxHeaders`, which take precedence over the headers here in the viewer.
*
* @function
* @param {Object} ajaxHeaders Updated AJAX headers.
* @param {Boolean} [propagate=true] Whether to propagate updated headers to tiled images, etc.
*/
setAjaxHeaders: function(ajaxHeaders, propagate) {
if (ajaxHeaders === null) {
ajaxHeaders = {};
}
if (!$.isPlainObject(ajaxHeaders)) {
$.console.error('[Viewer.setAjaxHeaders] Ignoring invalid headers, must be a plain object');
return;
}
if (propagate === undefined) {
propagate = true;
}
this.ajaxHeaders = ajaxHeaders;
if (propagate) {
for (var i = 0; i < this.world.getItemCount(); i++) {
this.world.getItemAt(i)._updateAjaxHeaders(true);
}
if (this.navigator) {
this.navigator.setAjaxHeaders(this.ajaxHeaders, true);
}
if (this.referenceStrip && this.referenceStrip.miniViewers) {
for (var key in this.referenceStrip.miniViewers) {
this.referenceStrip.miniViewers[key].setAjaxHeaders(this.ajaxHeaders, true);
}
}
}
},
/**
* Adds the given button to this viewer.
*
* @function
* @param {OpenSeadragon.Button} button
*/
addButton: function( button ){
this.buttonGroup.addButton(button);
},
/**
* @function
* @returns {Boolean}
*/
isFullPage: function () {
return THIS[ this.hash ].fullPage;
return THIS[this.hash] && THIS[ this.hash ].fullPage;
},
@ -1026,7 +1224,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
return this;
}
if ( fullPage ) {
if ( fullPage && this.element ) {
this.elementSize = $.getElementSize( this.element );
this.pageScroll = $.getPageScroll();
@ -1373,7 +1571,9 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
* (portions of the image outside of this area will not be visible). Only works on
* browsers that support the HTML5 canvas.
* @param {Number} [options.opacity=1] Proportional opacity of the tiled images (1=opaque, 0=hidden)
* @param {Boolean} [options.preload=false] Default switch for loading hidden images (true loads, false blocks)
* @param {Boolean} [options.preload=false] Default switch for loading hidden images (true loads, false blocks)
* @param {Boolean} [options.zombieCache] In the case that this method removes any TiledImage instance,
* allow the item-referenced cache to remain in memory even without active tiles. Default false.
* @param {Number} [options.degrees=0] Initial rotation of the tiled image around
* its top left corner in degrees.
* @param {Boolean} [options.flipped=false] Whether to horizontally flip the image.
@ -1388,7 +1588,6 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
* A set of headers to include when making tile AJAX requests.
* Note that these headers will be merged over any headers specified in {@link OpenSeadragon.Options}.
* Specifying a falsy value for a header will clear its existing value set at the Viewer level (if any).
* requests.
* @param {Function} [options.success] A function that gets called when the image is
* successfully added. It's passed the event object which contains a single property:
* "item", which is the resulting instance of TiledImage.
@ -1436,10 +1635,8 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
if (options.loadTilesWithAjax === undefined) {
options.loadTilesWithAjax = this.loadTilesWithAjax;
}
if (options.ajaxHeaders === undefined || options.ajaxHeaders === null) {
options.ajaxHeaders = this.ajaxHeaders;
} else if ($.isPlainObject(options.ajaxHeaders) && $.isPlainObject(this.ajaxHeaders)) {
options.ajaxHeaders = $.extend({}, this.ajaxHeaders, options.ajaxHeaders);
if (!$.isPlainObject(options.ajaxHeaders)) {
options.ajaxHeaders = {};
}
var myQueueItem = {
@ -1514,11 +1711,15 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
_this._loadQueue.splice(0, 1);
if (queueItem.options.replace) {
var newIndex = _this.world.getIndexOfItem(queueItem.options.replaceItem);
const replaced = queueItem.options.replaceItem;
const newIndex = _this.world.getIndexOfItem(replaced);
if (newIndex !== -1) {
queueItem.options.index = newIndex;
}
_this.world.removeItem(queueItem.options.replaceItem);
if (!replaced._zombieCache && replaced.source.equals(queueItem.tileSource)) {
replaced.allowZombieCache(true);
}
_this.world.removeItem(replaced);
}
tiledImage = new $.TiledImage({
@ -1546,6 +1747,8 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
minZoomImageRatio: _this.minZoomImageRatio,
wrapHorizontal: _this.wrapHorizontal,
wrapVertical: _this.wrapVertical,
maxTilesPerFrame: _this.maxTilesPerFrame,
loadDestinationTilesOnAnimation: _this.loadDestinationTilesOnAnimation,
immediateRender: _this.immediateRender,
blendTime: _this.blendTime,
alwaysBlend: _this.alwaysBlend,
@ -1557,7 +1760,8 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
loadTilesWithAjax: queueItem.options.loadTilesWithAjax,
ajaxHeaders: queueItem.options.ajaxHeaders,
debugMode: _this.debugMode,
subPixelRoundingForTransparency: _this.subPixelRoundingForTransparency
subPixelRoundingForTransparency: _this.subPixelRoundingForTransparency,
callTileLoadedWithCachedData: _this.callTileLoadedWithCachedData,
});
if (_this.collectionMode) {
@ -1801,11 +2005,11 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
//////////////////////////////////////////////////////////////////////////
// Navigation Controls
//////////////////////////////////////////////////////////////////////////
var beginZoomingInHandler = $.delegate( this, beginZoomingIn ),
endZoomingHandler = $.delegate( this, endZooming ),
doSingleZoomInHandler = $.delegate( this, doSingleZoomIn ),
beginZoomingOutHandler = $.delegate( this, beginZoomingOut ),
doSingleZoomOutHandler = $.delegate( this, doSingleZoomOut ),
var beginZoomingInHandler = $.delegate( this, this.startZoomInAction ),
endZoomingHandler = $.delegate( this, this.endZoomAction ),
doSingleZoomInHandler = $.delegate( this, this.singleZoomInAction ),
beginZoomingOutHandler = $.delegate( this, this.startZoomOutAction ),
doSingleZoomOutHandler = $.delegate( this, this.singleZoomOutAction ),
onHomeHandler = $.delegate( this, onHome ),
onFullScreenHandler = $.delegate( this, onFullScreen ),
onRotateLeftHandler = $.delegate( this, onRotateLeft ),
@ -2019,8 +2223,9 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
/**
* Adds an html element as an overlay to the current viewport. Useful for
* highlighting words or areas of interest on an image or other zoomable
* interface. The overlays added via this method are removed when the viewport
* is closed which include when changing page.
* interface. Unless the viewer has been configured with the preserveOverlays
* option, overlays added via this method are removed when the viewport
* is closed (including in sequence mode when changing page).
* @method
* @param {Element|String|Object} element - A reference to an element or an id for
* the element which will be overlaid. Or an Object specifying the configuration for the overlay.
@ -2032,7 +2237,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
* viewport which the location coordinates will be treated as relative
* to.
* @param {function} [onDraw] - If supplied the callback is called when the overlay
* needs to be drawn. It it the responsibility of the callback to do any drawing/positioning.
* needs to be drawn. It is the responsibility of the callback to do any drawing/positioning.
* It is passed position, size and element.
* @returns {OpenSeadragon.Viewer} Chainable.
* @fires OpenSeadragon.Viewer.event:add-overlay
@ -2344,7 +2549,6 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
width: this.referenceStripWidth,
tileSources: this.tileSources,
prefixUrl: this.prefixUrl,
useCanvas: this.useCanvas,
viewer: this
});
@ -2373,8 +2577,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
},
/**
* Update pixel density ratio, clears all tiles and triggers updates for
* all items if the ratio has changed.
* Update pixel density ratio and forces a resize operation.
* @private
*/
_updatePixelDensityRatio: function() {
@ -2382,8 +2585,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
var currentPixelDensityRatio = $.getCurrentPixelDensityRatio();
if (previusPixelDensityRatio !== currentPixelDensityRatio) {
$.pixelDensityRatio = currentPixelDensityRatio;
this.world.resetItems();
this.forceRedraw();
this.forceResize();
}
},
@ -2423,6 +2625,69 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
isAnimating: function () {
return THIS[ this.hash ].animating;
},
/**
* Starts continuous zoom-in animation (typically bound to mouse-down on the zoom-in button).
* @function
* @memberof OpenSeadragon.Viewer.prototype
*/
startZoomInAction: function () {
THIS[ this.hash ].lastZoomTime = $.now();
THIS[ this.hash ].zoomFactor = this.zoomPerSecond;
THIS[ this.hash ].zooming = true;
scheduleZoom( this );
},
/**
* Starts continuous zoom-out animation (typically bound to mouse-down on the zoom-out button).
* @function
* @memberof OpenSeadragon.Viewer.prototype
*/
startZoomOutAction: function () {
THIS[ this.hash ].lastZoomTime = $.now();
THIS[ this.hash ].zoomFactor = 1.0 / this.zoomPerSecond;
THIS[ this.hash ].zooming = true;
scheduleZoom( this );
},
/**
* Stops any continuous zoom animation (typically bound to mouse-up/leave events on a button).
* @function
* @memberof OpenSeadragon.Viewer.prototype
*/
endZoomAction: function () {
THIS[ this.hash ].zooming = false;
},
/**
* Performs single-step zoom-in operation (typically bound to click/enter on the zoom-in button).
* @function
* @memberof OpenSeadragon.Viewer.prototype
*/
singleZoomInAction: function () {
if ( this.viewport ) {
THIS[ this.hash ].zooming = false;
this.viewport.zoomBy(
this.zoomPerClick / 1.0
);
this.viewport.applyConstraints();
}
},
/**
* Performs single-step zoom-out operation (typically bound to click/enter on the zoom-out button).
* @function
* @memberof OpenSeadragon.Viewer.prototype
*/
singleZoomOutAction: function () {
if ( this.viewport ) {
THIS[ this.hash ].zooming = false;
this.viewport.zoomBy(
1.0 / this.zoomPerClick
);
this.viewport.applyConstraints();
}
},
});
@ -2493,7 +2758,6 @@ function getTileSourceImplementation( viewer, tileSource, imgOptions, successCal
ajaxHeaders: imgOptions.ajaxHeaders ?
imgOptions.ajaxHeaders : viewer.ajaxHeaders,
splitHashDataForPost: viewer.splitHashDataForPost,
useCanvas: viewer.useCanvas,
success: function( event ) {
successCallback( event.tileSource );
}
@ -2511,9 +2775,6 @@ function getTileSourceImplementation( viewer, tileSource, imgOptions, successCal
if (tileSource.ajaxWithCredentials === undefined) {
tileSource.ajaxWithCredentials = viewer.ajaxWithCredentials;
}
if (tileSource.useCanvas === undefined) {
tileSource.useCanvas = viewer.useCanvas;
}
if ( $.isFunction( tileSource.getTileUrl ) ) {
//Custom tile source
@ -2534,7 +2795,7 @@ function getTileSourceImplementation( viewer, tileSource, imgOptions, successCal
waitUntilReady(new $TileSource(options), tileSource);
}
} else {
//can assume it's already a tile source implementation
//can assume it's already a tile source implementation, force inheritance
waitUntilReady(tileSource, tileSource);
}
});
@ -2755,7 +3016,7 @@ function onCanvasKeyDown( event ) {
if ( !canvasKeyDownEventArgs.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) {
switch( event.keyCode ){
case 38://up arrow
case 38://up arrow/shift uparrow
if (!canvasKeyDownEventArgs.preventVerticalPan) {
if ( event.shift ) {
this.viewport.zoomBy(1.1);
@ -2766,7 +3027,7 @@ function onCanvasKeyDown( event ) {
}
event.preventDefault = true;
break;
case 40://down arrow
case 40://down arrow/shift downarrow
if (!canvasKeyDownEventArgs.preventVerticalPan) {
if ( event.shift ) {
this.viewport.zoomBy(0.9);
@ -2791,35 +3052,12 @@ function onCanvasKeyDown( event ) {
}
event.preventDefault = true;
break;
default:
//console.log( 'navigator keycode %s', event.keyCode );
event.preventDefault = false;
break;
}
} else {
event.preventDefault = false;
}
}
function onCanvasKeyPress( event ) {
var canvasKeyPressEventArgs = {
originalEvent: event.originalEvent,
preventDefaultAction: false,
preventVerticalPan: event.preventVerticalPan || !this.panVertical,
preventHorizontalPan: event.preventHorizontalPan || !this.panHorizontal
};
// This event is documented in onCanvasKeyDown
this.raiseEvent('canvas-key', canvasKeyPressEventArgs);
if ( !canvasKeyPressEventArgs.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) {
switch( event.keyCode ){
case 43://=|+
case 61://=|+
case 187://=|+
this.viewport.zoomBy(1.1);
this.viewport.applyConstraints();
event.preventDefault = true;
break;
case 45://-|_
case 189://-|_
this.viewport.zoomBy(0.9);
this.viewport.applyConstraints();
event.preventDefault = true;
@ -2829,74 +3067,71 @@ function onCanvasKeyPress( event ) {
this.viewport.applyConstraints();
event.preventDefault = true;
break;
case 119://w
case 87://W
if (!canvasKeyPressEventArgs.preventVerticalPan) {
case 87://W/w
if (!canvasKeyDownEventArgs.preventVerticalPan) {
if ( event.shift ) {
this.viewport.zoomBy(1.1);
} else {
this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, -40)));
}
this.viewport.applyConstraints();
}
event.preventDefault = true;
break;
case 115://s
case 83://S
if (!canvasKeyPressEventArgs.preventVerticalPan) {
if ( event.shift ) {
this.viewport.zoomBy(0.9);
} else {
this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, 40)));
}
this.viewport.applyConstraints();
}
event.preventDefault = true;
break;
case 97://a
if (!canvasKeyPressEventArgs.preventHorizontalPan) {
case 83://S/s
if (!canvasKeyDownEventArgs.preventVerticalPan) {
if ( event.shift ) {
this.viewport.zoomBy(0.9);
} else {
this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, 40)));
}
this.viewport.applyConstraints();
}
event.preventDefault = true;
break;
case 65://a/A
if (!canvasKeyDownEventArgs.preventHorizontalPan) {
this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(-40, 0)));
this.viewport.applyConstraints();
}
event.preventDefault = true;
break;
case 100://d
if (!canvasKeyPressEventArgs.preventHorizontalPan) {
this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(40, 0)));
this.viewport.applyConstraints();
case 68://d/D
if (!canvasKeyDownEventArgs.preventHorizontalPan) {
this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(40, 0)));
this.viewport.applyConstraints();
}
event.preventDefault = true;
break;
case 114: //r - clockwise rotation
if(this.viewport.flipped){
this.viewport.setRotation(this.viewport.getRotation() - this.rotationIncrement);
} else{
this.viewport.setRotation(this.viewport.getRotation() + this.rotationIncrement);
}
this.viewport.applyConstraints();
event.preventDefault = true;
break;
case 82: //R - counterclockwise rotation
if(this.viewport.flipped){
this.viewport.setRotation(this.viewport.getRotation() + this.rotationIncrement);
} else{
this.viewport.setRotation(this.viewport.getRotation() - this.rotationIncrement);
}
this.viewport.applyConstraints();
event.preventDefault = true;
break;
case 102: //f
this.viewport.toggleFlip();
event.preventDefault = true;
break;
case 106: //j - previous image source
this.goToPreviousPage();
break;
case 107: //k - next image source
this.goToNextPage();
break;
case 82: //r - clockwise rotation/R - counterclockwise rotation
if(event.shift){
if(this.viewport.flipped){
this.viewport.setRotation(this.viewport.getRotation() + this.rotationIncrement);
} else{
this.viewport.setRotation(this.viewport.getRotation() - this.rotationIncrement);
}
}else{
if(this.viewport.flipped){
this.viewport.setRotation(this.viewport.getRotation() - this.rotationIncrement);
} else{
this.viewport.setRotation(this.viewport.getRotation() + this.rotationIncrement);
}
}
this.viewport.applyConstraints();
event.preventDefault = true;
break;
case 70: //f/F
this.viewport.toggleFlip();
event.preventDefault = true;
break;
case 74: //j - previous image source
this.goToPreviousPage();
break;
case 75: //k - next image source
this.goToNextPage();
break;
default:
// console.log( 'navigator keycode %s', event.keyCode );
//console.log( 'navigator keycode %s', event.keyCode );
event.preventDefault = false;
break;
}
@ -2905,7 +3140,24 @@ function onCanvasKeyPress( event ) {
}
}
function onCanvasKeyPress( event ) {
var canvasKeyPressEventArgs = {
originalEvent: event.originalEvent,
};
/**
* Raised when a keyboard key is pressed and the focus is on the {@link OpenSeadragon.Viewer#canvas} element.
*
* @event canvas-key-press
* @memberof OpenSeadragon.Viewer
* @type {object}
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
* @property {Object} originalEvent - The original DOM event.
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
this.raiseEvent('canvas-key-press', canvasKeyPressEventArgs);
}
function onCanvasClick( event ) {
var gestureSettings;
@ -3125,10 +3377,11 @@ function onCanvasDragEnd( event ) {
*/
this.raiseEvent('canvas-drag-end', canvasDragEndEventArgs);
gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
if (!canvasDragEndEventArgs.preventDefaultAction && this.viewport) {
if ( !THIS[ this.hash ].draggingToZoom &&
gestureSettings.dragToPan &&
gestureSettings.flickEnabled &&
event.speed >= gestureSettings.flickMinSpeed) {
var amplitudeX = 0;
@ -3417,11 +3670,49 @@ function onCanvasPinch( event ) {
event.gesturePoints[0].currentPos.x - event.gesturePoints[1].currentPos.x);
var angle2 = Math.atan2(event.gesturePoints[0].lastPos.y - event.gesturePoints[1].lastPos.y,
event.gesturePoints[0].lastPos.x - event.gesturePoints[1].lastPos.x);
this.viewport.setRotation(this.viewport.getRotation() + ((angle1 - angle2) * (180 / Math.PI)));
centerPt = this.viewport.pointFromPixel( event.center, true );
this.viewport.rotateTo(this.viewport.getRotation(true) + ((angle1 - angle2) * (180 / Math.PI)), centerPt, true);
}
}
}
function onCanvasFocus( event ) {
/**
* Raised when the {@link OpenSeadragon.Viewer#canvas} element gets keyboard focus.
*
* @event canvas-focus
* @memberof OpenSeadragon.Viewer
* @type {object}
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
* @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
* @property {Object} originalEvent - The original DOM event.
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
this.raiseEvent( 'canvas-focus', {
tracker: event.eventSource,
originalEvent: event.originalEvent
});
}
function onCanvasBlur( event ) {
/**
* Raised when the {@link OpenSeadragon.Viewer#canvas} element loses keyboard focus.
*
* @event canvas-blur
* @memberof OpenSeadragon.Viewer
* @type {object}
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
* @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
* @property {Object} originalEvent - The original DOM event.
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
this.raiseEvent( 'canvas-blur', {
tracker: event.eventSource,
originalEvent: event.originalEvent
});
}
function onCanvasScroll( event ) {
var canvasScrollEventArgs,
gestureSettings,
@ -3617,7 +3908,7 @@ function updateOnce( viewer ) {
var viewportChange = viewer.viewport.update();
var animated = viewer.world.update() || viewportChange;
var animated = viewer.world.update(viewportChange) || viewportChange;
if (viewportChange) {
/**
@ -3707,7 +3998,6 @@ function updateOnce( viewer ) {
function drawWorld( viewer ) {
viewer.imageLoader.clear();
viewer.drawer.clear();
viewer.world.draw();
/**
@ -3730,28 +4020,6 @@ function resolveUrl( prefix, url ) {
}
function beginZoomingIn() {
THIS[ this.hash ].lastZoomTime = $.now();
THIS[ this.hash ].zoomFactor = this.zoomPerSecond;
THIS[ this.hash ].zooming = true;
scheduleZoom( this );
}
function beginZoomingOut() {
THIS[ this.hash ].lastZoomTime = $.now();
THIS[ this.hash ].zoomFactor = 1.0 / this.zoomPerSecond;
THIS[ this.hash ].zooming = true;
scheduleZoom( this );
}
function endZooming() {
THIS[ this.hash ].zooming = false;
}
function scheduleZoom( viewer ) {
$.requestAnimationFrame( $.delegate( viewer, doZoom ) );
}
@ -3775,28 +4043,6 @@ function doZoom() {
}
function doSingleZoomIn() {
if ( this.viewport ) {
THIS[ this.hash ].zooming = false;
this.viewport.zoomBy(
this.zoomPerClick / 1.0
);
this.viewport.applyConstraints();
}
}
function doSingleZoomOut() {
if ( this.viewport ) {
THIS[ this.hash ].zooming = false;
this.viewport.zoomBy(
1.0 / this.zoomPerClick
);
this.viewport.applyConstraints();
}
}
function lightUp() {
if (this.buttonGroup) {
this.buttonGroup.emulateEnter();
@ -3861,4 +4107,22 @@ function onFlip() {
this.viewport.toggleFlip();
}
/**
* Find drawer
*/
$.determineDrawer = function( id ){
for (let property in OpenSeadragon) {
const drawer = OpenSeadragon[ property ],
proto = drawer.prototype;
if( proto &&
proto instanceof OpenSeadragon.DrawerBase &&
$.isFunction( proto.getType ) &&
proto.getType.call( drawer ) === id
){
return drawer;
}
}
return null;
};
}( OpenSeadragon ));

View file

@ -2,7 +2,7 @@
* OpenSeadragon - Viewport
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@ -711,6 +711,8 @@ $.Viewport.prototype = {
if(constraints){
this.panTo(center, false);
newZoom = this._applyZoomConstraints(newZoom);
this.zoomTo(newZoom, null, false);
var constrainedBounds = this.getConstrainedBounds();
@ -1076,7 +1078,10 @@ $.Viewport.prototype = {
if( this.viewer ){
/**
* Raised when the viewer is resized (see {@link OpenSeadragon.Viewport#resize}).
* Raised when a viewer resize operation is initiated (see {@link OpenSeadragon.Viewport#resize}).
* This event happens before the viewport bounds have been updated.
* See also {@link OpenSeadragon.Viewer#after-resize} which reflects
* the new viewport bounds following the resize action.
*
* @event resize
* @memberof OpenSeadragon.Viewer
@ -1092,7 +1097,29 @@ $.Viewport.prototype = {
});
}
return this.fitBounds( newBounds, true );
var output = this.fitBounds( newBounds, true );
if( this.viewer ){
/**
* Raised after the viewer is resized (see {@link OpenSeadragon.Viewport#resize}).
* See also {@link OpenSeadragon.Viewer#resize} event which happens
* before the new bounds have been calculated and applied.
*
* @event after-resize
* @memberof OpenSeadragon.Viewer
* @type {object}
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
* @property {OpenSeadragon.Point} newContainerSize
* @property {Boolean} maintain
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
this.viewer.raiseEvent( 'after-resize', {
newContainerSize: newContainerSize,
maintain: maintain
});
}
return output;
},
// private
@ -1106,7 +1133,7 @@ $.Viewport.prototype = {
/**
* Update the zoom, degrees, and center (X and Y) springs.
* @function
* @returns {Boolean} True if any change has been made, false otherwise.
* @returns {Boolean} True if the viewport is still animating, false otherwise.
*/
update: function() {
var _this = this;
@ -1138,7 +1165,13 @@ $.Viewport.prototype = {
this._oldZoom = this.zoomSpring.current.value;
this._oldDegrees = this.degreesSpring.current.value;
return changed;
var isAnimating = changed ||
!this.zoomSpring.isAtTargetValue() ||
!this.centerSpringX.isAtTargetValue() ||
!this.centerSpringY.isAtTargetValue() ||
!this.degreesSpring.isAtTargetValue();
return isAnimating;
},
// private - pass true to use spring, or a number for degrees for immediate rotation
@ -1678,7 +1711,7 @@ $.Viewport.prototype = {
* 1 means original image size, 0.5 half size...
* Viewport zoom: ratio of the displayed image's width to viewport's width.
* 1 means identical width, 2 means image's width is twice the viewport's width...
* Note: not accurate with multi-image.
* Note: not accurate with multi-image; use [TiledImage.imageToViewportZoom] for the specific image of interest.
* @function
* @param {Number} imageZoom The image zoom
* target zoom.
@ -1690,7 +1723,7 @@ $.Viewport.prototype = {
if (count > 1) {
if (!this.silenceMultiImageWarnings) {
$.console.error('[Viewport.imageToViewportZoom] is not accurate ' +
'with multi-image.');
'with multi-image. Instead, use [TiledImage.imageToViewportZoom] for the specific image of interest');
}
} else if (count === 1) {
// It is better to use TiledImage.imageToViewportZoom
@ -1756,7 +1789,41 @@ $.Viewport.prototype = {
*/
this.viewer.raiseEvent('flip', {flipped: state});
return this;
}
},
/**
* Gets current max zoom pixel ratio
* @function
* @returns {Number} Max zoom pixel ratio
*/
getMaxZoomPixelRatio: function() {
return this.maxZoomPixelRatio;
},
/**
* Sets max zoom pixel ratio
* @function
* @param {Number} ratio - Max zoom pixel ratio
* @param {Boolean} [applyConstraints=true] - Apply constraints after setting ratio;
* Takes effect only if current zoom is greater than set max zoom pixel ratio
* @param {Boolean} [immediately=false] - Whether to animate to new zoom
*/
setMaxZoomPixelRatio: function(ratio, applyConstraints = true, immediately = false) {
$.console.assert(!isNaN(ratio), "[Viewport.setMaxZoomPixelRatio] ratio must be a number");
if (isNaN(ratio)) {
return;
}
this.maxZoomPixelRatio = ratio;
if (applyConstraints) {
if (this.getZoom() > this.getMaxZoom()) {
this.applyConstraints(immediately);
}
}
},
};

1301
src/webgldrawer.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@
* OpenSeadragon - World
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 OpenSeadragon contributors
* Copyright (C) 2010-2024 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@ -69,6 +69,7 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
/**
* Add the specified item.
* @param {OpenSeadragon.TiledImage} item - The item to add.
* @param {Object} options - Options affecting insertion.
* @param {Number} [options.index] - Index for the item. If not specified, goes at the top.
* @fires OpenSeadragon.World.event:add-item
* @fires OpenSeadragon.World.event:metrics-change
@ -231,6 +232,206 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
}
},
/**
* Forces the system consider all tiles across all tiled images
* as outdated, and fire tile update event on relevant tiles
* Detailed description is available within the 'tile-invalidated'
* event.
* @param {Boolean} [restoreTiles=true] if true, tile processing starts from the tile original data
* @param {number} [tStamp=OpenSeadragon.now()] optionally provide tStamp of the update event
* @function
* @fires OpenSeadragon.Viewer.event:tile-invalidated
* @return {OpenSeadragon.Promise<?>}
*/
requestInvalidate: function (restoreTiles = true, tStamp = $.now()) {
$.__updated = tStamp;
// const priorityTiles = this._items.map(item => item._lastDrawn.map(x => x.tile)).flat();
// const promise = this.requestTileInvalidateEvent(priorityTiles, tStamp, restoreTiles);
// return promise.then(() => this.requestTileInvalidateEvent(this.viewer.tileCache.getLoadedTilesFor(null), tStamp, restoreTiles));
// Tile-first retrieval fires computation on tiles that share cache, which are filtered out by processing property
return this.requestTileInvalidateEvent(this.viewer.tileCache.getLoadedTilesFor(null), tStamp, restoreTiles);
// Cache-first update tile retrieval is nicer since there might be many tiles sharing
// return this.requestTileInvalidateEvent(new Set(Object.values(this.viewer.tileCache._cachesLoaded)
// .map(c => !c._destroyed && c._tiles[0])), tStamp, restoreTiles);
},
/**
* Requests tile data update.
* @function OpenSeadragon.Viewer.prototype._updateSequenceButtons
* @private
* @param {Iterable<OpenSeadragon.Tile>} tilesToProcess tiles to update
* @param {Number} tStamp timestamp in milliseconds, if active timestamp of the same value is executing,
* changes are added to the cycle, else they await next iteration
* @param {Boolean} [restoreTiles=true] if true, tile processing starts from the tile original data
* @param {Boolean} [_allowTileUnloaded=false] internal flag for calling on tiles that come new to the system
* @fires OpenSeadragon.Viewer.event:tile-invalidated
* @return {OpenSeadragon.Promise<?>}
*/
requestTileInvalidateEvent: function(tilesToProcess, tStamp, restoreTiles = true, _allowTileUnloaded = false) {
if (!this.viewer.isOpen()) {
return $.Promise.resolve();
}
const tileList = [],
tileFinishResolvers = [];
for (const tile of tilesToProcess) {
// We allow re-execution on tiles that are in process but have too low processing timestamp,
// which must be solved by ensuring subsequent data calls in the suddenly outdated processing
// pipeline take no effect.
if (!tile || (!_allowTileUnloaded && !tile.loaded)) {
continue;
}
const tileCache = tile.getCache(tile.originalCacheKey);
if (tileCache.__invStamp && tileCache.__invStamp >= tStamp) {
continue;
}
for (let t of tileCache._tiles) {
// Mark all related tiles as processing and register callback to unmark later on
t.processing = tStamp;
t.processingPromise = new $.Promise((resolve) => {
tileFinishResolvers.push(() => {
t.processing = false;
resolve(t);
});
});
}
tileCache.__invStamp = tStamp;
tileList.push(tile);
}
if (!tileList.length) {
return $.Promise.resolve();
}
// We call the event on the parent viewer window no matter what
const eventTarget = this.viewer.viewer || this.viewer;
// However, we must pick the correct drawer reference (navigator VS viewer)
const drawer = this.viewer.drawer;
const jobList = tileList.map(tile => {
const tiledImage = tile.tiledImage;
const originalCache = tile.getCache(tile.originalCacheKey);
let workingCache = null;
const getWorkingCacheData = (type) => {
if (workingCache) {
return workingCache.getDataAs(type, false);
}
const targetCopyKey = restoreTiles ? tile.originalCacheKey : tile.cacheKey;
const origCache = tile.getCache(targetCopyKey);
if (!origCache) {
$.console.error("[Tile::getData] There is no cache available for tile with key %s", targetCopyKey);
return $.Promise.reject();
}
// Here ensure type is defined, rquired by data callbacks
type = type || origCache.type;
workingCache = new $.CacheRecord().withTileReference(tile);
return origCache.getDataAs(type, true).then(data => {
workingCache.addTile(tile, data, type);
return workingCache.data;
});
};
const setWorkingCacheData = (value, type) => {
if (!workingCache) {
workingCache = new $.CacheRecord().withTileReference(tile);
workingCache.addTile(tile, value, type);
return $.Promise.resolve();
}
return workingCache.setDataAs(value, type);
};
const atomicCacheSwap = () => {
if (workingCache) {
let newCacheKey = tile.buildDistinctMainCacheKey();
tiledImage._tileCache.injectCache({
tile: tile,
cache: workingCache,
targetKey: newCacheKey,
setAsMainCache: true,
tileAllowNotLoaded: tile.loading
});
} else if (restoreTiles) {
// If we requested restore, perform now
tiledImage._tileCache.restoreTilesThatShareOriginalCache(tile, tile.getCache(tile.originalCacheKey), true);
}
};
/**
* @event tile-invalidated
* @memberof OpenSeadragon.Viewer
* @type {object}
* @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.
* @property {OpenSeadragon.Tile} tile
* @property {AsyncNullaryFunction<boolean>} outdated - predicate that evaluates to true if the event
* is outdated and should not be longer processed (has no effect)
* @property {AsyncUnaryFunction<any, string>} getData - get data of desired type (string argument)
* @property {AsyncBinaryFunction<undefined, any, string>} setData - set data (any)
* and the type of the data (string)
* @property {function} resetData - function that deletes any previous data modification in the current
* execution pipeline
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
return eventTarget.raiseEventAwaiting('tile-invalidated', {
tile: tile,
tiledImage: tiledImage,
outdated: () => originalCache.__invStamp !== tStamp || (!tile.loaded && !tile.loading),
getData: getWorkingCacheData,
setData: setWorkingCacheData,
resetData: () => {
if (workingCache) {
workingCache.destroy();
workingCache = null;
}
}
}).then(_ => {
if (originalCache.__invStamp === tStamp && (tile.loaded || tile.loading)) {
if (workingCache) {
return workingCache.prepareForRendering(drawer).then(c => {
if (c && originalCache.__invStamp === tStamp) {
atomicCacheSwap();
originalCache.__invStamp = null;
}
});
}
// If we requested restore, perform now
if (restoreTiles) {
const freshOriginalCacheRef = tile.getCache(tile.originalCacheKey);
return freshOriginalCacheRef.prepareForRendering(drawer).then((c) => {
if (c && originalCache.__invStamp === tStamp) {
atomicCacheSwap();
originalCache.__invStamp = null;
}
});
}
// Preventive call to ensure we stay compatible
const freshMainCacheRef = tile.getCache();
return freshMainCacheRef.prepareForRendering(drawer).then(() => {
atomicCacheSwap();
originalCache.__invStamp = null;
});
} else if (workingCache) {
workingCache.destroy();
workingCache = null;
}
return null;
}).catch(e => {
$.console.error("Update routine error:", e);
});
});
return $.Promise.all(jobList).then(() => {
for (let resolve of tileFinishResolvers) {
resolve();
}
this.draw();
});
},
/**
* Clears all tiles and triggers updates for all items.
*/
@ -242,11 +443,14 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
/**
* Updates (i.e. animates bounds of) all items.
* @function
* @param viewportChanged Whether the viewport changed, which indicates that
* all TiledImages need to be updated.
*/
update: function() {
update: function(viewportChanged) {
var animated = false;
for ( var i = 0; i < this._items.length; i++ ) {
animated = this._items[i].update() || animated;
animated = this._items[i].update(viewportChanged) || animated;
}
return animated;
@ -256,11 +460,11 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
* Draws all items.
*/
draw: function() {
for ( var i = 0; i < this._items.length; i++ ) {
this._items[i].draw();
}
this.viewer.drawer.draw(this._items);
this._needsDraw = false;
for (let item of this._items) {
this._needsDraw = item.setDrawn() || this._needsDraw;
}
},
/**

View file

@ -77,7 +77,7 @@
options.minLevel = 0;
options.maxLevel = options.gridSize.length - 1;
OpenSeadragon.TileSource.apply(this, [options]);
$.TileSource.apply(this, [options]);
};
$.extend($.ZoomifyTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.ZoomifyTileSource.prototype */ {
@ -143,6 +143,13 @@
result = Math.floor(num / 256);
return this.tilesUrl + 'TileGroup' + result + '/' + level + '-' + x + '-' + y + '.' + this.fileFormat;
},
/**
* Equality comparator
*/
equals: function (otherSource) {
return this.tilesUrl === otherSource.tilesUrl;
}
});

300
style.css Normal file
View file

@ -0,0 +1,300 @@
* {
margin: 0;
padding: 0;
outline: 0;
}
body {
padding: 80px 100px;
font: 13px "Helvetica Neue", "Lucida Grande", "Arial";
background: #ECE9E9 -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fff), to(#ECE9E9));
background: #ECE9E9 -moz-linear-gradient(top, #fff, #ECE9E9);
background-repeat: no-repeat;
color: #555;
-webkit-font-smoothing: antialiased;
}
h1, h2, h3 {
font-size: 22px;
color: #343434;
}
h1 em, h2 em {
padding: 0 5px;
font-weight: normal;
}
h1 {
font-size: 60px;
}
h2 {
margin-top: 10px;
}
h3 {
margin: 5px 0 10px 0;
padding-bottom: 5px;
border-bottom: 1px solid #eee;
font-size: 18px;
}
ul li {
list-style: none;
}
ul li:hover {
cursor: pointer;
color: #2e2e2e;
}
ul li .path {
padding-left: 5px;
font-weight: bold;
}
ul li .line {
padding-right: 5px;
font-style: italic;
}
ul li:first-child .path {
padding-left: 0;
}
p {
line-height: 1.5;
}
a {
color: #555;
text-decoration: none;
}
a:hover {
color: #303030;
}
#stacktrace {
margin-top: 15px;
}
.directory h1 {
margin-bottom: 15px;
font-size: 18px;
}
ul#files {
width: 100%;
height: 100%;
overflow: hidden;
}
ul#files li {
float: left;
width: 30%;
line-height: 25px;
margin: 1px;
}
ul#files li a {
display: block;
height: 25px;
border: 1px solid transparent;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
overflow: hidden;
white-space: nowrap;
}
ul#files li a:focus,
ul#files li a:hover {
background: rgba(255,255,255,0.65);
border: 1px solid #ececec;
}
ul#files li a.highlight {
-webkit-transition: background .4s ease-in-out;
background: #ffff4f;
border-color: #E9DC51;
}
#search {
display: block;
position: fixed;
top: 20px;
right: 20px;
width: 90px;
-webkit-transition: width ease 0.2s, opacity ease 0.4s;
-moz-transition: width ease 0.2s, opacity ease 0.4s;
-webkit-border-radius: 32px;
-moz-border-radius: 32px;
-webkit-box-shadow: inset 0px 0px 3px rgba(0, 0, 0, 0.25), inset 0px 1px 3px rgba(0, 0, 0, 0.7), 0px 1px 0px rgba(255, 255, 255, 0.03);
-moz-box-shadow: inset 0px 0px 3px rgba(0, 0, 0, 0.25), inset 0px 1px 3px rgba(0, 0, 0, 0.7), 0px 1px 0px rgba(255, 255, 255, 0.03);
-webkit-font-smoothing: antialiased;
text-align: left;
font: 13px "Helvetica Neue", Arial, sans-serif;
padding: 4px 10px;
border: none;
background: transparent;
margin-bottom: 0;
outline: none;
opacity: 0.7;
color: #888;
}
#search:focus {
width: 120px;
opacity: 1.0;
}
/*views*/
#files span {
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
text-indent: 10px;
}
#files .name {
background-repeat: no-repeat;
}
#files .icon .name {
text-indent: 28px;
}
/*tiles*/
.view-tiles .name {
width: 100%;
background-position: 8px 5px;
margin-left: 30px;
}
.view-tiles .size,
.view-tiles .date {
display: none;
}
.view-tiles a {
position: relative;
}
/*hack: reuse empty to find folders*/
#files .size:empty {
width: 20px;
height: 14px;
background-color: #f9d342; /* Folder color */
position: absolute;
border-radius: 4px;
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1); /* Optional shadow for effect */
display: block !important;
float: left;
left: 13px;
top: 5px;
}
#files .size:empty:before {
content: '';
position: absolute;
top: -2px;
left: 2px;
width: 12px;
height: 4px;
background-color: #f9d342;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
#files .size:empty:after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 8px;
height: 4px;
background-color: #e8c233; /* Slightly darker shade for the tab */
border-top-left-radius: 2px;
border-bottom-right-radius: 2px;
}
/*details*/
ul#files.view-details li {
float: none;
display: block;
width: 90%;
}
ul#files.view-details li.header {
height: 25px;
background: #000;
color: #fff;
font-weight: bold;
}
.view-details .header {
border-radius: 5px;
}
.view-details .name {
width: 60%;
background-position: 8px 5px;
}
.view-details .size {
width: 10%;
}
.view-details .date {
width: 30%;
}
.view-details .size,
.view-details .date {
text-align: right;
direction: rtl;
}
/*mobile*/
@media (max-width: 768px) {
body {
font-size: 13px;
line-height: 16px;
padding: 0;
}
#search {
position: static;
width: 100%;
font-size: 2em;
line-height: 1.8em;
text-indent: 10px;
border: 0;
border-radius: 0;
padding: 10px 0;
margin: 0;
}
#search:focus {
width: 100%;
border: 0;
opacity: 1;
}
.directory h1 {
font-size: 2em;
line-height: 1.5em;
color: #fff;
background: #000;
padding: 15px 10px;
margin: 0;
}
ul#files {
border-top: 1px solid #cacaca;
}
ul#files li {
float: none;
width: auto !important;
display: block;
border-bottom: 1px solid #cacaca;
font-size: 2em;
line-height: 1.2em;
text-indent: 0;
margin: 0;
}
ul#files li:nth-child(odd) {
background: #e0e0e0;
}
ul#files li a {
height: auto;
border: 0;
border-radius: 0;
padding: 15px 10px;
}
ul#files li a:focus,
ul#files li a:hover {
border: 0;
}
#files .header,
#files .size,
#files .date {
display: none !important;
}
#files .name {
float: none;
display: inline-block;
width: 100%;
text-indent: 0;
background-position: 0 50%;
}
#files .icon .name {
text-indent: 41px;
}
#files .size:empty {
top: 23px;
left: 5px;
}
}

View file

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<title>OpenSeadragon QUnit</title>
<link rel="stylesheet" href="/node_modules/qunitjs/qunit/qunit.css">
<link rel="stylesheet" href="/node_modules/qunit/qunit/qunit.css">
<link rel="stylesheet" href="/test/lib/jquery-ui-1.10.2/css/smoothness/jquery-ui-1.10.2.min.css">
<link rel="stylesheet" href="/test/helpers/test.css">
</head>
@ -15,7 +15,7 @@
var isCoverageTest = true;
</script>
<script src="/node_modules/qunitjs/qunit/qunit.js"></script>
<script src="/node_modules/qunit/qunit/qunit.js"></script>
<script src="/test/lib/jquery-1.9.1.min.js"></script>
<script src="/test/lib/jquery-ui-1.10.2/js/jquery-ui-1.10.2.min.js"></script>
<script src="/test/lib/jquery.simulate.js"></script>
@ -58,6 +58,7 @@
<!-- Helpers -->
<script src="/test/helpers/legacy.mouse.shim.js"></script>
<script src="/test/helpers/mocks.js"></script>
<script src="/test/helpers/test.js"></script>
<script src="/test/helpers/touch.js"></script>
@ -65,6 +66,7 @@
<!-- Polyfill must be inserted first because it is testing functions
reassignments which could be done by other test. -->
<script src="/test/modules/polyfills.js"></script>
<script src="/test/modules/event-source.js"></script>
<script src="/test/modules/basic.js"></script>
<script src="/test/modules/strings.js"></script>
<script src="/test/modules/formats.js"></script>

View file

@ -18,7 +18,8 @@
"jpg",
"png",
"gif"
]
]
}
]
],
"xmp": "<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n <rdf:Description rdf:about=\"\"\n xmlns:xmp=\"http://ns.adobe.com/xap/1.0/\"\n xmlns:aux=\"http://ns.adobe.com/exif/1.0/aux/\"\n xmlns:photoshop=\"http://ns.adobe.com/photoshop/1.0/\"\n xmlns:Iptc4xmpCore=\"http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/\"\n xmlns:tiff=\"http://ns.adobe.com/tiff/1.0/\"\n xmlns:exif=\"http://ns.adobe.com/exif/1.0/\"\n xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n xmp:CreatorTool=\"Capture One 11 Macintosh\"\n xmp:CreateDate=\"2019-08-21T20:54:20\"\n xmp:Rating=\"4\"\n aux:SerialNumber=\"GP001601\"\n aux:ImageNumber=\"30012\"\n aux:Firmware=\"IQ280, Factory Firmware: 8.05.36\"\n photoshop:CaptionWriter=\"\"\n photoshop:Headline=\"\"\n photoshop:City=\"\"\n photoshop:State=\"\"\n photoshop:Country=\"\"\n photoshop:Source=\"\"\n photoshop:Instructions=\"\"\n photoshop:AuthorsPosition=\"\"\n photoshop:TransmissionReference=\"\"\n photoshop:Credit=\"\"\n photoshop:LegacyIPTCDigest=\"B135BFFD6A4CFB049F834A3F370A9AE3\"\n photoshop:DateCreated=\"2019-08-21T20:54:20\"\n Iptc4xmpCore:Location=\"\"\n Iptc4xmpCore:IntellectualGenre=\"\"\n Iptc4xmpCore:CountryCode=\"\"\n tiff:ImageWidth=\"2088\"\n tiff:ImageLength=\"3000\"\n tiff:Compression=\"1\"\n tiff:PhotometricInterpretation=\"2\"\n tiff:Orientation=\"1\"\n tiff:SamplesPerPixel=\"3\"\n tiff:PlanarConfiguration=\"1\"\n tiff:XResolution=\"400/1\"\n tiff:YResolution=\"400/1\"\n tiff:ResolutionUnit=\"2\"\n tiff:Make=\"Phase One\"\n tiff:Model=\"IQ280\"\n tiff:Software=\"Capture One 11 Macintosh\"\n exif:ExifVersion=\"0230\"\n exif:PixelXDimension=\"2088\"\n exif:PixelYDimension=\"3000\"\n exif:ExposureTime=\"1/4\"\n exif:ExposureProgram=\"1\"\n exif:ShutterSpeedValue=\"2/1\"\n exif:MeteringMode=\"1\"\n exif:LightSource=\"255\"\n exif:FocalPlaneXResolution=\"63015385/32768\"\n exif:FocalPlaneYResolution=\"63015385/32768\"\n exif:FocalPlaneResolutionUnit=\"3\"\n exif:FileSource=\"3\"\n exif:SceneType=\"1\"\n exif:WhiteBalance=\"5\"\n exif:ImageUniqueID=\"00E058000075000004019E064100753C\">\n <Iptc4xmpCore:CreatorContactInfo\n Iptc4xmpCore:CiEmailWork=\"digicc@library.illinois.edu\"\n Iptc4xmpCore:CiTelWork=\"+1(217)2442062\"\n Iptc4xmpCore:CiAdrPcode=\"61801\"\n Iptc4xmpCore:CiUrlWork=\"http://www.library.illinois.edu/staff/preservation/digitization/\"\n Iptc4xmpCore:CiAdrExtadr=\"1408 W. Gregory Drive\"\n Iptc4xmpCore:CiAdrCity=\"Urbana\"\n Iptc4xmpCore:CiAdrCtry=\"United States\"\n Iptc4xmpCore:CiAdrRegion=\"Illinois\"/>\n <tiff:BitsPerSample>\n <rdf:Seq>\n <rdf:li>8 8 8</rdf:li>\n </rdf:Seq>\n </tiff:BitsPerSample>\n <exif:ISOSpeedRatings>\n <rdf:Seq>\n <rdf:li>50</rdf:li>\n </rdf:Seq>\n </exif:ISOSpeedRatings>\n <dc:creator>\n <rdf:Seq>\n <rdf:li>University of Illinois Library</rdf:li>\n </rdf:Seq>\n </dc:creator>\n </rdf:Description>\n </rdf:RDF>"
}

View file

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<title>OpenSeadragon Basic Demo</title>
<script type="text/javascript" src='../../build/openseadragon/openseadragon.js'></script>
<script type="text/javascript" src='../lib/jquery-1.9.1.min.js'></script>
<style type="text/css">
.openseadragon1 {
width: 800px;
height: 600px;
}
</style>
</head>
<body>
<div>
Simple demo page to show a default OpenSeadragon viewer.
</div>
<div id="contentDiv" class="openseadragon1"></div>
<script type="text/javascript">
var viewer = OpenSeadragon({
// debugMode: true,
id: "contentDiv",
prefixUrl: "../../build/openseadragon/images/",
tileSources: "../data/testpattern.dzi",
drawer: 'html',
showNavigator: true,
});
</script>
</body>
</html>

View file

@ -25,7 +25,7 @@
id: "contentDiv",
prefixUrl: "../../build/openseadragon/images/",
tileSources: "../data/testpattern.dzi",
showNavigator:true
showNavigator: true,
});
</script>
</body>

View file

@ -16,7 +16,6 @@
// debugMode: true,
zoomPerScroll: 1.02,
showNavigator: testNavigator,
useCanvas: true,
// defaultZoomLevel: 2,
// homeFillsViewer: true,
// sequenceMode: true,
@ -131,8 +130,9 @@
var box = new OpenSeadragon.Rect(margins.left, margins.top,
$('#contentDiv').width() - (margins.left + margins.right),
$('#contentDiv').height() - (margins.top + margins.bottom));
self.viewer.drawer.debugRect(box);
// If drawDebuggingRect is implemented, use it to show the box.
// This is not implemented by all drawers however.
self.viewer.drawer.drawDebuggingRect(box);
});
}

View file

@ -41,8 +41,9 @@
constrainDuringPan: true,
visibilityRatio: 1,
prefixUrl: "../../build/openseadragon/images/",
minZoomImageRatio: 1
minZoomImageRatio: 1,
crossOriginPolicy: 'Anonymous',
});
</script>
</body>
</html>
</html>

View file

@ -0,0 +1,114 @@
<!DOCTYPE html>
<html>
<head>
<title>Drawer Comparison Demo</title>
<script type="text/javascript" src='../../build/openseadragon/openseadragon.js'></script>
<script type="text/javascript" src='../lib/jquery-1.9.1.min.js'></script>
<script type="text/javascript" src='../lib/jquery-ui-1.10.2/js/jquery-ui-1.10.2.min.js'></script>
<link rel="stylesheet" href="../lib/jquery-ui-1.10.2/css/smoothness/jquery-ui-1.10.2.min.css">
<script type="module" src="./drawercomparison.js"></script>
<style type="text/css">
.content{
max-width:960px;
margin: 0 auto;
}
.mirrored{
display:grid;
grid-template-columns:50% 50%;
gap: 2em;
}
.viewer-container {
/* width: 600px;
height: 400px; */
aspect-ratio: 4 / 3;
border: thin gray solid;
position:relative;
}
.example-code{
background-color:tan;
border: thin black solid;
padding:10px;
display:inline-block;
width:95%;
}
.description pre{
display:inline-block;
background-color:gainsboro;
padding:0;
margin:0;
}
.image-options{
display: grid;
grid-template-columns: 2em 9em 1fr;
padding:3px;
border: thin gray solid;
}
.option-grid{
display: grid;
grid-template-columns: 7em 7em 9em 9em 10em 9em;
/* grid-template-columns: repeat(5, auto); */
}
.image-options input[type=number]{
width: 5em;
}
.image-options select{
width: 5em;
}
</style>
</head>
<body>
<div class="content">
<h2>Compare behavior of <strong>Context2d</strong> and <strong>WebGL</strong> drawers</h2>
<div class="mirrored">
<div>
<h3 id="title-w1">Loading...</h3>
<div id="canvasdrawer" class="viewer-container"></div>
</div>
<div>
<h3 id="title-w2">Loading...</h3>
<div id="webgl" class="viewer-container"></div>
</div>
</div>
<div id="image-picker">
<h3>Image options (drag and drop to re-order images)</h3>
</div>
<h2>HTMLDrawer: legacy pre-HTML5 drawer that uses &lt;img&gt; elements for tiles</h2>
<div class="mirrored">
<div>
<div class="description">
HTML-based rendering can be selected in two different ways:
</div>
<pre class="example-code">
// via the 'html' drawer option:
let viewer = OpenSeadragon({
...
drawer: 'html',
...
});
// or by passing the HTMLDrawer constructor
let viewer = OpenSeadragon({
...
drawer:OpenSeadragon.HTMLDrawer,
...
});
</pre>
</div>
<div id="htmldrawer" class="viewer-container"></div>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,371 @@
const sources = {
"rainbow":"../data/testpattern.dzi",
"leaves":"../data/iiif_2_0_sizes/info.json",
"bblue":{
type:'image',
url: "../data/BBlue.png",
},
"duomo":"https://openseadragon.github.io/example-images/duomo/duomo.dzi",
}
const labels = {
rainbow: 'Rainbow Grid',
leaves: 'Leaves',
bblue: 'Blue B',
duomo: 'Duomo',
}
const drawers = {
canvas: "Context2d drawer (default in OSD &lt;= 4.1.0)",
webgl: "New WebGL drawer"
}
const viewportMargins = {
left: 100,
top: 0,
right: 0,
bottom: 50,
};
//Support drawer type from the url
const url = new URL(window.location.href);
const drawer1 = url.searchParams.get("left") || 'canvas';
const drawer2 = url.searchParams.get("right") || 'webgl';
$("#title-w1").html(drawers[drawer1]);
$("#title-w2").html(drawers[drawer2]);
//Double viewer setup for comparison - CanvasDrawer and WebGLDrawer
// viewer1: canvas drawer
let viewer1 = window.viewer1 = OpenSeadragon({
id: "canvasdrawer",
prefixUrl: "../../build/openseadragon/images/",
minZoomImageRatio:0.01,
maxZoomPixelRatio:100,
smoothTileEdgesMinZoom:1.1,
crossOriginPolicy: 'Anonymous',
ajaxWithCredentials: false,
// maxImageCacheCount: 30,
drawer:drawer1,
blendTime:0,
showNavigator:true,
viewportMargins,
});
// viewer2: webgl drawer
let viewer2 = window.viewer2 = OpenSeadragon({
id: "webgl",
prefixUrl: "../../build/openseadragon/images/",
minZoomImageRatio:0.01,
maxZoomPixelRatio:100,
smoothTileEdgesMinZoom:1.1,
crossOriginPolicy: 'Anonymous',
ajaxWithCredentials: false,
// maxImageCacheCount: 30,
drawer:drawer2,
blendTime:0,
showNavigator:true,
viewportMargins,
});
// // viewer3: html drawer, unused
var viewer3 = window.viewer3 = OpenSeadragon({
id: "htmldrawer",
drawer:'html',
blendTime:2,
prefixUrl: "../../build/openseadragon/images/",
minZoomImageRatio:0.01,
customDrawer: OpenSeadragon.HTMLDrawer,
tileSources: [sources['leaves'], sources['rainbow'], sources['duomo']],
sequenceMode: true,
crossOriginPolicy: 'Anonymous',
ajaxWithCredentials: false
});
// Sync navigation of viewer1 and viewer 2
var viewer1Leading = false;
var viewer2Leading = false;
var viewer1Handler = function() {
if (viewer2Leading) {
return;
}
viewer1Leading = true;
viewer2.viewport.zoomTo(viewer1.viewport.getZoom());
viewer2.viewport.panTo(viewer1.viewport.getCenter());
viewer2.viewport.rotateTo(viewer1.viewport.getRotation());
viewer2.viewport.setFlip(viewer1.viewport.flipped);
viewer1Leading = false;
};
var viewer2Handler = function() {
if (viewer1Leading) {
return;
}
viewer2Leading = true;
viewer1.viewport.zoomTo(viewer2.viewport.getZoom());
viewer1.viewport.panTo(viewer2.viewport.getCenter());
viewer1.viewport.rotateTo(viewer2.viewport.getRotation());
viewer1.viewport.setFlip(viewer1.viewport.flipped);
viewer2Leading = false;
};
viewer1.addHandler('zoom', viewer1Handler);
viewer2.addHandler('zoom', viewer2Handler);
viewer1.addHandler('pan', viewer1Handler);
viewer2.addHandler('pan', viewer2Handler);
viewer1.addHandler('rotate', viewer1Handler);
viewer2.addHandler('rotate', viewer2Handler);
viewer1.addHandler('flip', viewer1Handler);
viewer2.addHandler('flip', viewer2Handler);
$('#image-picker').sortable({
update: function(event, ui){
let thisItem = ui.item.find('.toggle').data('item1');
let items = $('#image-picker input.toggle:checked').toArray().map(item=>$(item).data('item1'));
let newIndex = items.indexOf(thisItem);
if(thisItem){
viewer1.world.setItemIndex(thisItem, newIndex);
}
thisItem = ui.item.find('.toggle').data('item2');
items = $('#image-picker input.toggle:checked').toArray().map(item=>$(item).data('item2'));
newIndex = items.indexOf(thisItem);
if(thisItem){
viewer2.world.setItemIndex(thisItem, newIndex);
}
}
});
Object.keys(sources).forEach((key, index)=>{
let element = makeImagePickerElement(key, labels[key])
$('#image-picker').append(element);
if(index === 0){
element.find('.toggle').prop('checked',true);
}
})
$('#image-picker').append(makeComparisonSwitcher());
$('#image-picker input.toggle').on('change',function(){
let data = $(this).data();
if(this.checked){
addTileSource(viewer1, data.image, this);
addTileSource(viewer2, data.image, this);
} else {
if(data.item1){
viewer1.world.removeItem(data.item1);
viewer2.world.removeItem(data.item2);
$(this).data({item1: null, item2: null});
}
}
}).trigger('change');
$('#image-picker input:not(.toggle)').on('change',function(){
let data = $(this).data();
let value = $(this).val();
let tiledImage1 = $(`#image-picker input.toggle[data-image=${data.image}]`).data('item1');
let tiledImage2 = $(`#image-picker input.toggle[data-image=${data.image}]`).data('item2');
updateTiledImage(tiledImage1, data, value, this);
updateTiledImage(tiledImage2, data, value, this);
});
function updateTiledImage(tiledImage, data, value, item){
let field = data.field;
if(tiledImage){
//item = tiledImage
if(field == 'x'){
let bounds = tiledImage.getBoundsNoRotate();
let position = new OpenSeadragon.Point(Number(value), bounds.y);
tiledImage.setPosition(position);
} else if ( field == 'y'){
let bounds = tiledImage.getBoundsNoRotate();
let position = new OpenSeadragon.Point(bounds.x, Number(value));
tiledImage.setPosition(position);
} else if (field == 'width'){
tiledImage.setWidth(Number(value));
} else if (field == 'degrees'){
tiledImage.setRotation(Number(value));
} else if (field == 'opacity'){
tiledImage.setOpacity(Number(value));
} else if (field == 'flipped'){
tiledImage.setFlip($(item).prop('checked'));
} else if (field == 'smoothing'){
const checked = $(item).prop('checked');
viewer1.drawer.setImageSmoothingEnabled(checked);
viewer2.drawer.setImageSmoothingEnabled(checked);
$('[data-field=smoothing]').prop('checked', checked);
} else if (field == 'cropped'){
if( $(item).prop('checked') ){
let scale = tiledImage.source.width;
let croppingPolygons = [ [{x:0.2*scale, y:0.2*scale}, {x:0.8*scale, y:0.2*scale}, {x:0.5*scale, y:0.8*scale}] ];
tiledImage.setCroppingPolygons(croppingPolygons);
} else {
tiledImage.resetCroppingPolygons();
}
} else if (field == 'clipped'){
if( $(item).prop('checked') ){
let scale = tiledImage.source.width;
let clipRect = new OpenSeadragon.Rect(0.1*scale, 0.2*scale, 0.6*scale, 0.4*scale);
tiledImage.setClip(clipRect);
} else {
tiledImage.setClip(null);
}
} else if (field == 'debug'){
if( $(item).prop('checked') ){
tiledImage.debugMode = true;
} else {
tiledImage.debugMode = false;
}
}
} else {
//viewer-level option
}
}
$('.image-options select[data-field=composite]').append(getCompositeOperationOptions()).on('change',function(){
let data = $(this).data();
let tiledImage1 = $(`#image-picker input.toggle[data-image=${data.image}]`).data('item1');
if(tiledImage1){
tiledImage1.setCompositeOperation(this.value == 'null' ? null : this.value);
}
let tiledImage2 = $(`#image-picker input.toggle[data-image=${data.image}]`).data('item2');
if(tiledImage2){
tiledImage2.setCompositeOperation(this.value == 'null' ? null : this.value);
}
}).trigger('change');
$('.image-options select[data-field=wrapping]').append(getWrappingOptions()).on('change',function(){
let data = $(this).data();
let tiledImage = $(`#image-picker input.toggle[data-image=${data.image}]`).data('item1');
if(tiledImage){
switch(this.value){
case "None": tiledImage.wrapHorizontal = tiledImage.wrapVertical = false; break;
case "Horizontal": tiledImage.wrapHorizontal = true; tiledImage.wrapVertical = false; break;
case "Vertical": tiledImage.wrapHorizontal = false; tiledImage.wrapVertical = true; break;
case "Both": tiledImage.wrapHorizontal = tiledImage.wrapVertical = true; break;
}
tiledImage.redraw();//trigger a redraw for the webgl renderer.
}
tiledImage = $(`#image-picker input.toggle[data-image=${data.image}]`).data('item2');
if(tiledImage){
switch(this.value){
case "None": tiledImage.wrapHorizontal = tiledImage.wrapVertical = false; break;
case "Horizontal": tiledImage.wrapHorizontal = true; tiledImage.wrapVertical = false; break;
case "Vertical": tiledImage.wrapHorizontal = false; tiledImage.wrapVertical = true; break;
case "Both": tiledImage.wrapHorizontal = tiledImage.wrapVertical = true; break;
}
tiledImage.redraw();//trigger a redraw for the webgl renderer.
}
}).trigger('change');
function getWrappingOptions(){
let opts = ['None', 'Horizontal', 'Vertical', 'Both'];
let elements = opts.map((opt, i)=>{
let el = $('<option>',{value:opt}).text(opt);
if(i===0){
el.attr('selected',true);
}
return el[0];
// $('.image-options select').append(el);
});
return $(elements);
}
function getCompositeOperationOptions(){
let opts = [null,'source-over','source-in','source-out','source-atop',
'destination-over','destination-in','destination-out','destination-atop',
'lighten','darken','copy','xor','multiply','screen','overlay','color-dodge',
'color-burn','hard-light','soft-light','difference','exclusion',
'hue','saturation','color','luminosity'];
let elements = opts.map((opt, i)=>{
let el = $('<option>',{value:opt}).text(opt);
if(i===0){
el.attr('selected',true);
}
return el[0];
// $('.image-options select').append(el);
});
return $(elements);
}
function addTileSource(viewer, image, checkbox){
let options = $(`#image-picker input[data-image=${image}][type=number]`).toArray().reduce((acc, input)=>{
let field = $(input).data('field');
if(field){
acc[field] = Number(input.value);
}
return acc;
}, {});
options.flipped = $(`#image-picker input[data-image=${image}][data-type=flipped]`).prop('checked');
let items = $('#image-picker input.toggle:checked').toArray();
let insertionIndex = items.indexOf(checkbox);
let tileSource = sources[image];
if(tileSource){
viewer&&viewer.addTiledImage({tileSource: tileSource, ...options, index: insertionIndex});
viewer&&viewer.world.addOnceHandler('add-item',function(ev){
let item = ev.item;
let field = viewer === viewer1 ? 'item1' : 'item2';
$(checkbox).data(field,item);
// item.source.hasTransparency = ()=>true; //simulate image with transparency, to show seams in default renderer
});
}
}
function getAvailableDrawerSelect(name, selectedDrawer) {
return `
<select name="${name}">
${Object.entries(drawers).map(([k, v]) => {
const selected = selectedDrawer === k ? "selected" : "";
return `<option value="${k}" ${selected}>${v}</option>`;
}).join("\n")}
</select>`;
}
function makeComparisonSwitcher() {
const left = getAvailableDrawerSelect("left", drawer1),
right = getAvailableDrawerSelect("right", drawer2);
return `
<div>
Note: you can run the comparison with desired drawers like this: drawercomparison.html?left=[type]&right=[type]
<form method="get">
${left}
${right}
<button>Submit</button>
</form>
</div>`;
}
function makeImagePickerElement(key, label){
return $(`<div class="image-options">
<span class="ui-icon ui-icon-arrowthick-2-n-s"></span>
<label><input type="checkbox" data-image="" class="toggle"> __title__</label>
<div class="option-grid">
<label>X: <input type="number" value="0" data-image="" data-field="x"> </label>
<label>Y: <input type="number" value="0" data-image="" data-field="y"> </label>
<label>Width: <input type="number" value="1" data-image="" data-field="width" min="0"> </label>
<label>Degrees: <input type="number" value="0" data-image="" data-field="degrees"> </label>
<label>Opacity: <input type="number" value="1" data-image="" data-field="opacity" min="0" max="1" step="0.2"> </label>
<span></span>
<label>Flipped: <input type="checkbox" data-image="" data-field="flipped"></label>
<label>Cropped: <input type="checkbox" data-image="" data-field="cropped"></label>
<label>Clipped: <input type="checkbox" data-image="" data-field="clipped"></label>
<label>Chess Tile Opacity: <input type="checkbox" data-image="" data-field="tile-level-opecity"></label>
<label>Debug: <input type="checkbox" data-image="" data-field="debug"></label>
<label>Composite: <select data-image="" data-field="composite"></select></label>
<label>Wrap: <select data-image="" data-field="wrapping"></select></label>
<label>Smoothing: <input type="checkbox" data-image="" data-field="smoothing" checked></label>
</div>
</div>`.replaceAll('data-image=""', `data-image="${key}"`).replace('__title__', label));
}

View file

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<title>Drawer Comparison Demo</title>
<script type="text/javascript" src='../../build/openseadragon/openseadragon.js'></script>
<script type="text/javascript" src='../lib/jquery-1.9.1.min.js'></script>
<script type="text/javascript" src='../lib/jquery-ui-1.10.2/js/jquery-ui-1.10.2.min.js'></script>
<link rel="stylesheet" href="../lib/jquery-ui-1.10.2/css/smoothness/jquery-ui-1.10.2.min.css">
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/stats.js/17/Stats.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script> -->
<script type="module" src="./drawerperformance.js"></script>
<style type="text/css">
.content{
max-width:960px;
margin: 0 auto;
}
#drawer{
height:800px;
}
</style>
</head>
<body>
<div class="content">
<h2>Compare performance of drawer implementations</h2>
<div>
<label>Select a drawer: </label>
<select id="select-drawer">
</select>
<label>Num images: </label>
<input id="input-number" type="number" value="1" min="1" step="1">
<button id="create-drawer">Create drawer</button>
</div>
<div id="drawer"></div>
</div>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,843 @@
/*
* Modified and maintained by the OpenSeadragon Community.
*
* This software was orignally developed at the National Institute of Standards and
* Technology by employees of the Federal Government. NIST assumes
* no responsibility whatsoever for its use by other parties, and makes no
* guarantees, expressed or implied, about its quality, reliability, or
* any other characteristic.
* @author Antoine Vandecreme <antoine.vandecreme@nist.gov>
*/
/**
* This class is an improvement over the basic jQuery spinner to support
* 'Enter' to update the value (with validity checks).
* @param {Object} options Options object
* @return {Spinner} A spinner object
*/
class Spinner {
constructor(options) {
options.$element.html('<input type="text" size="1" ' +
'class="ui-widget-content ui-corner-all"/>');
const self = this,
$spinner = options.$element.find('input');
this.value = options.init;
$spinner.spinner({
min: options.min,
max: options.max,
step: options.step,
spin: function(event, ui) {
/*jshint unused:true */
self.value = ui.value;
options.updateCallback(self.value);
}
});
$spinner.val(this.value);
$spinner.keyup(function(e) {
if (e.which === 13) {
if (!this.value.match(/^-?\d?\.?\d*$/)) {
this.value = options.init;
} else if (options.min !== undefined &&
this.value < options.min) {
this.value = options.min;
} else if (options.max !== undefined &&
this.value > options.max) {
this.value = options.max;
}
self.value = this.value;
options.updateCallback(self.value);
}
});
}
getValue() {
return this.value;
}
}
class SpinnerSlider {
constructor(options) {
let idIncrement = 0;
this.hash = idIncrement++;
const spinnerId = 'wdzt-spinner-slider-spinner-' + this.hash;
const sliderId = 'wdzt-spinner-slider-slider-' + this.hash;
this.value = options.init;
const self = this;
options.$element.html(`
<div class="wdzt-table-layout wdzt-full-width">
<div class="wdzt-row-layout">
<div class="wdzt-cell-layout">
<input id="${spinnerId}" type="text" size="1"
class="ui-widget-content ui-corner-all"/>
</div>
<div class="wdzt-cell-layout wdzt-full-width">
<div id="${sliderId}" class="wdzt-menu-slider">
</div>
</div>
</div>
</div>
`);
const $slider = options.$element.find('#' + sliderId)
.slider({
min: options.min,
max: options.sliderMax !== undefined ?
options.sliderMax : options.max,
step: options.step,
value: this.value,
slide: function (event, ui) {
/*jshint unused:true */
self.value = ui.value;
$spinner.spinner('value', self.value);
options.updateCallback(self.value);
}
});
const $spinner = options.$element.find('#' + spinnerId)
.spinner({
min: options.min,
max: options.max,
step: options.step,
spin: function (event, ui) {
/*jshint unused:true */
self.value = ui.value;
$slider.slider('value', self.value);
options.updateCallback(self.value);
}
});
$spinner.val(this.value);
$spinner.keyup(function (e) {
if (e.which === 13) {
self.value = $spinner.spinner('value');
$slider.slider('value', self.value);
options.updateCallback(self.value);
}
});
}
getValue () {
return this.value;
};
}
const switcher = new DrawerSwitcher();
switcher.addDrawerOption("drawer");
$("#title-drawer").html(switcher.activeName("drawer"));
switcher.render("#title-banner");
const sources = {
'Highsmith': "https://openseadragon.github.io/example-images/highsmith/highsmith.dzi",
'Rainbow Grid': "../../data/testpattern.dzi",
'Leaves': "../../data/iiif_2_0_sizes/info.json",
"Duomo":"https://openseadragon.github.io/example-images/duomo/duomo.dzi",
}
const url = new URL(window.location);
const targetSource = url.searchParams.get("image") || Object.values(sources)[0];
const viewer = window.viewer = new OpenSeadragon({
id: 'openseadragon',
prefixUrl: '/build/openseadragon/images/',
tileSources: targetSource,
crossOriginPolicy: 'Anonymous',
drawer: switcher.activeImplementation("drawer"),
showNavigator: true,
wrapHorizontal: true,
gestureSettingsMouse: {
clickToZoom: false
}
});
$("#image-select")
.html(Object.entries(sources).map(([k, v]) =>
`<option value="${v}" ${targetSource === v ? "selected" : ""}>${k}</option>`).join("\n"))
.on('change', e => {
url.searchParams.set('image', e.target.value);
window.history.pushState(null, '', url.toString());
viewer.addTiledImage({tileSource: e.target.value, index: 0, replace: true});
});
// Prevent Caman from caching the canvas because without this:
// 1. We have a memory leak
// 2. Non-caman filters in between 2 camans filters get ignored.
Caman.Store.put = function() {};
// List of filters with their templates.
const availableFilters = [
{
name: 'Invert',
generate: function() {
return {
html: '',
getParams: function() {
return '';
},
getFilter: function() {
/*eslint new-cap: 0*/
return OpenSeadragon.Filters.INVERT();
}
};
}
}, {
name: 'Colormap',
generate: function(updateCallback) {
const cmaps = {
aCm: [ [0,0,0], [0,4,0], [0,8,0], [0,12,0], [0,16,0], [0,20,0], [0,24,0], [0,28,0], [0,32,0], [0,36,0], [0,40,0], [0,44,0], [0,48,0], [0,52,0], [0,56,0], [0,60,0], [0,64,0], [0,68,0], [0,72,0], [0,76,0], [0,80,0], [0,85,0], [0,89,0], [0,93,0], [0,97,0], [0,101,0], [0,105,0], [0,109,0], [0,113,0], [0,117,0], [0,121,0], [0,125,0], [0,129,2], [0,133,5], [0,137,7], [0,141,10], [0,145,13], [0,149,15], [0,153,18], [0,157,21], [0,161,23], [0,165,26], [0,170,29], [0,174,31], [0,178,34], [0,182,37], [0,186,39], [0,190,42], [0,194,45], [0,198,47], [0,202,50], [0,206,53], [0,210,55], [0,214,58], [0,218,61], [0,222,63], [0,226,66], [0,230,69], [0,234,71], [0,238,74], [0,242,77], [0,246,79], [0,250,82], [0,255,85], [3,251,87], [7,247,90], [11,243,92], [15,239,95], [19,235,98], [23,231,100], [27,227,103], [31,223,106], [35,219,108], [39,215,111], [43,211,114], [47,207,116], [51,203,119], [55,199,122], [59,195,124], [63,191,127], [67,187,130], [71,183,132], [75,179,135], [79,175,138], [83,171,140], [87,167,143], [91,163,146], [95,159,148], [99,155,151], [103,151,154], [107,147,156], [111,143,159], [115,139,162], [119,135,164], [123,131,167], [127,127,170], [131,123,172], [135,119,175], [139,115,177], [143,111,180], [147,107,183], [151,103,185], [155,99,188], [159,95,191], [163,91,193], [167,87,196], [171,83,199], [175,79,201], [179,75,204], [183,71,207], [187,67,209], [191,63,212], [195,59,215], [199,55,217], [203,51,220], [207,47,223], [211,43,225], [215,39,228], [219,35,231], [223,31,233], [227,27,236], [231,23,239], [235,19,241], [239,15,244], [243,11,247], [247,7,249], [251,3,252], [255,0,255], [255,0,251], [255,0,247], [255,0,244], [255,0,240], [255,0,237], [255,0,233], [255,0,230], [255,0,226], [255,0,223], [255,0,219], [255,0,216], [255,0,212], [255,0,208], [255,0,205], [255,0,201], [255,0,198], [255,0,194], [255,0,191], [255,0,187], [255,0,184], [255,0,180], [255,0,177], [255,0,173], [255,0,170], [255,0,166], [255,0,162], [255,0,159], [255,0,155], [255,0,152], [255,0,148], [255,0,145], [255,0,141], [255,0,138], [255,0,134], [255,0,131], [255,0,127], [255,0,123], [255,0,119], [255,0,115], [255,0,112], [255,0,108], [255,0,104], [255,0,100], [255,0,96], [255,0,92], [255,0,88], [255,0,85], [255,0,81], [255,0,77], [255,0,73], [255,0,69], [255,0,65], [255,0,61], [255,0,57], [255,0,54], [255,0,50], [255,0,46], [255,0,42], [255,0,38], [255,0,34], [255,0,30], [255,0,27], [255,0,23], [255,0,19], [255,0,15], [255,0,11], [255,0,7], [255,0,3], [255,0,0], [255,4,0], [255,8,0], [255,12,0], [255,17,0], [255,21,0], [255,25,0], [255,30,0], [255,34,0], [255,38,0], [255,43,0], [255,47,0], [255,51,0], [255,56,0], [255,60,0], [255,64,0], [255,69,0], [255,73,0], [255,77,0], [255,82,0], [255,86,0], [255,90,0], [255,95,0], [255,99,0], [255,103,0], [255,108,0], [255,112,0], [255,116,0], [255,121,0], [255,125,0], [255,129,0], [255,133,0], [255,138,0], [255,142,0], [255,146,0], [255,151,0], [255,155,0], [255,159,0], [255,164,0], [255,168,0], [255,172,0], [255,177,0], [255,181,0], [255,185,0], [255,190,0], [255,194,0], [255,198,0], [255,203,0], [255,207,0], [255,211,0], [255,216,0], [255,220,0], [255,224,0], [255,229,0], [255,233,0], [255,237,0], [255,242,0], [255,246,0], [255,250,0], [255,255,0]],
bCm: [ [0,0,0], [0,0,4], [0,0,8], [0,0,12], [0,0,16], [0,0,20], [0,0,24], [0,0,28], [0,0,32], [0,0,36], [0,0,40], [0,0,44], [0,0,48], [0,0,52], [0,0,56], [0,0,60], [0,0,64], [0,0,68], [0,0,72], [0,0,76], [0,0,80], [0,0,85], [0,0,89], [0,0,93], [0,0,97], [0,0,101], [0,0,105], [0,0,109], [0,0,113], [0,0,117], [0,0,121], [0,0,125], [0,0,129], [0,0,133], [0,0,137], [0,0,141], [0,0,145], [0,0,149], [0,0,153], [0,0,157], [0,0,161], [0,0,165], [0,0,170], [0,0,174], [0,0,178], [0,0,182], [0,0,186], [0,0,190], [0,0,194], [0,0,198], [0,0,202], [0,0,206], [0,0,210], [0,0,214], [0,0,218], [0,0,222], [0,0,226], [0,0,230], [0,0,234], [0,0,238], [0,0,242], [0,0,246], [0,0,250], [0,0,255], [3,0,251], [7,0,247], [11,0,243], [15,0,239], [19,0,235], [23,0,231], [27,0,227], [31,0,223], [35,0,219], [39,0,215], [43,0,211], [47,0,207], [51,0,203], [55,0,199], [59,0,195], [63,0,191], [67,0,187], [71,0,183], [75,0,179], [79,0,175], [83,0,171], [87,0,167], [91,0,163], [95,0,159], [99,0,155], [103,0,151], [107,0,147], [111,0,143], [115,0,139], [119,0,135], [123,0,131], [127,0,127], [131,0,123], [135,0,119], [139,0,115], [143,0,111], [147,0,107], [151,0,103], [155,0,99], [159,0,95], [163,0,91], [167,0,87], [171,0,83], [175,0,79], [179,0,75], [183,0,71], [187,0,67], [191,0,63], [195,0,59], [199,0,55], [203,0,51], [207,0,47], [211,0,43], [215,0,39], [219,0,35], [223,0,31], [227,0,27], [231,0,23], [235,0,19], [239,0,15], [243,0,11], [247,0,7], [251,0,3], [255,0,0], [255,3,0], [255,7,0], [255,11,0], [255,15,0], [255,19,0], [255,23,0], [255,27,0], [255,31,0], [255,35,0], [255,39,0], [255,43,0], [255,47,0], [255,51,0], [255,55,0], [255,59,0], [255,63,0], [255,67,0], [255,71,0], [255,75,0], [255,79,0], [255,83,0], [255,87,0], [255,91,0], [255,95,0], [255,99,0], [255,103,0], [255,107,0], [255,111,0], [255,115,0], [255,119,0], [255,123,0], [255,127,0], [255,131,0], [255,135,0], [255,139,0], [255,143,0], [255,147,0], [255,151,0], [255,155,0], [255,159,0], [255,163,0], [255,167,0], [255,171,0], [255,175,0], [255,179,0], [255,183,0], [255,187,0], [255,191,0], [255,195,0], [255,199,0], [255,203,0], [255,207,0], [255,211,0], [255,215,0], [255,219,0], [255,223,0], [255,227,0], [255,231,0], [255,235,0], [255,239,0], [255,243,0], [255,247,0], [255,251,0], [255,255,0], [255,255,3], [255,255,7], [255,255,11], [255,255,15], [255,255,19], [255,255,23], [255,255,27], [255,255,31], [255,255,35], [255,255,39], [255,255,43], [255,255,47], [255,255,51], [255,255,55], [255,255,59], [255,255,63], [255,255,67], [255,255,71], [255,255,75], [255,255,79], [255,255,83], [255,255,87], [255,255,91], [255,255,95], [255,255,99], [255,255,103], [255,255,107], [255,255,111], [255,255,115], [255,255,119], [255,255,123], [255,255,127], [255,255,131], [255,255,135], [255,255,139], [255,255,143], [255,255,147], [255,255,151], [255,255,155], [255,255,159], [255,255,163], [255,255,167], [255,255,171], [255,255,175], [255,255,179], [255,255,183], [255,255,187], [255,255,191], [255,255,195], [255,255,199], [255,255,203], [255,255,207], [255,255,211], [255,255,215], [255,255,219], [255,255,223], [255,255,227], [255,255,231], [255,255,235], [255,255,239], [255,255,243], [255,255,247], [255,255,251], [255,255,255]],
bbCm: [ [0,0,0], [2,0,0], [4,0,0], [6,0,0], [8,0,0], [10,0,0], [12,0,0], [14,0,0], [16,0,0], [18,0,0], [20,0,0], [22,0,0], [24,0,0], [26,0,0], [28,0,0], [30,0,0], [32,0,0], [34,0,0], [36,0,0], [38,0,0], [40,0,0], [42,0,0], [44,0,0], [46,0,0], [48,0,0], [50,0,0], [52,0,0], [54,0,0], [56,0,0], [58,0,0], [60,0,0], [62,0,0], [64,0,0], [66,0,0], [68,0,0], [70,0,0], [72,0,0], [74,0,0], [76,0,0], [78,0,0], [80,0,0], [82,0,0], [84,0,0], [86,0,0], [88,0,0], [90,0,0], [92,0,0], [94,0,0], [96,0,0], [98,0,0], [100,0,0], [102,0,0], [104,0,0], [106,0,0], [108,0,0], [110,0,0], [112,0,0], [114,0,0], [116,0,0], [118,0,0], [120,0,0], [122,0,0], [124,0,0], [126,0,0], [128,1,0], [130,3,0], [132,5,0], [134,7,0], [136,9,0], [138,11,0], [140,13,0], [142,15,0], [144,17,0], [146,19,0], [148,21,0], [150,23,0], [152,25,0], [154,27,0], [156,29,0], [158,31,0], [160,33,0], [162,35,0], [164,37,0], [166,39,0], [168,41,0], [170,43,0], [172,45,0], [174,47,0], [176,49,0], [178,51,0], [180,53,0], [182,55,0], [184,57,0], [186,59,0], [188,61,0], [190,63,0], [192,65,0], [194,67,0], [196,69,0], [198,71,0], [200,73,0], [202,75,0], [204,77,0], [206,79,0], [208,81,0], [210,83,0], [212,85,0], [214,87,0], [216,89,0], [218,91,0], [220,93,0], [222,95,0], [224,97,0], [226,99,0], [228,101,0], [230,103,0], [232,105,0], [234,107,0], [236,109,0], [238,111,0], [240,113,0], [242,115,0], [244,117,0], [246,119,0], [248,121,0], [250,123,0], [252,125,0], [255,127,0], [255,129,1], [255,131,3], [255,133,5], [255,135,7], [255,137,9], [255,139,11], [255,141,13], [255,143,15], [255,145,17], [255,147,19], [255,149,21], [255,151,23], [255,153,25], [255,155,27], [255,157,29], [255,159,31], [255,161,33], [255,163,35], [255,165,37], [255,167,39], [255,169,41], [255,171,43], [255,173,45], [255,175,47], [255,177,49], [255,179,51], [255,181,53], [255,183,55], [255,185,57], [255,187,59], [255,189,61], [255,191,63], [255,193,65], [255,195,67], [255,197,69], [255,199,71], [255,201,73], [255,203,75], [255,205,77], [255,207,79], [255,209,81], [255,211,83], [255,213,85], [255,215,87], [255,217,89], [255,219,91], [255,221,93], [255,223,95], [255,225,97], [255,227,99], [255,229,101], [255,231,103], [255,233,105], [255,235,107], [255,237,109], [255,239,111], [255,241,113], [255,243,115], [255,245,117], [255,247,119], [255,249,121], [255,251,123], [255,253,125], [255,255,127], [255,255,129], [255,255,131], [255,255,133], [255,255,135], [255,255,137], [255,255,139], [255,255,141], [255,255,143], [255,255,145], [255,255,147], [255,255,149], [255,255,151], [255,255,153], [255,255,155], [255,255,157], [255,255,159], [255,255,161], [255,255,163], [255,255,165], [255,255,167], [255,255,169], [255,255,171], [255,255,173], [255,255,175], [255,255,177], [255,255,179], [255,255,181], [255,255,183], [255,255,185], [255,255,187], [255,255,189], [255,255,191], [255,255,193], [255,255,195], [255,255,197], [255,255,199], [255,255,201], [255,255,203], [255,255,205], [255,255,207], [255,255,209], [255,255,211], [255,255,213], [255,255,215], [255,255,217], [255,255,219], [255,255,221], [255,255,223], [255,255,225], [255,255,227], [255,255,229], [255,255,231], [255,255,233], [255,255,235], [255,255,237], [255,255,239], [255,255,241], [255,255,243], [255,255,245], [255,255,247], [255,255,249], [255,255,251], [255,255,253], [255,255,255]],
blueCm: [ [0,0,0], [0,0,1], [0,0,2], [0,0,3], [0,0,4], [0,0,5], [0,0,6], [0,0,7], [0,0,8], [0,0,9], [0,0,10], [0,0,11], [0,0,12], [0,0,13], [0,0,14], [0,0,15], [0,0,16], [0,0,17], [0,0,18], [0,0,19], [0,0,20], [0,0,21], [0,0,22], [0,0,23], [0,0,24], [0,0,25], [0,0,26], [0,0,27], [0,0,28], [0,0,29], [0,0,30], [0,0,31], [0,0,32], [0,0,33], [0,0,34], [0,0,35], [0,0,36], [0,0,37], [0,0,38], [0,0,39], [0,0,40], [0,0,41], [0,0,42], [0,0,43], [0,0,44], [0,0,45], [0,0,46], [0,0,47], [0,0,48], [0,0,49], [0,0,50], [0,0,51], [0,0,52], [0,0,53], [0,0,54], [0,0,55], [0,0,56], [0,0,57], [0,0,58], [0,0,59], [0,0,60], [0,0,61], [0,0,62], [0,0,63], [0,0,64], [0,0,65], [0,0,66], [0,0,67], [0,0,68], [0,0,69], [0,0,70], [0,0,71], [0,0,72], [0,0,73], [0,0,74], [0,0,75], [0,0,76], [0,0,77], [0,0,78], [0,0,79], [0,0,80], [0,0,81], [0,0,82], [0,0,83], [0,0,84], [0,0,85], [0,0,86], [0,0,87], [0,0,88], [0,0,89], [0,0,90], [0,0,91], [0,0,92], [0,0,93], [0,0,94], [0,0,95], [0,0,96], [0,0,97], [0,0,98], [0,0,99], [0,0,100], [0,0,101], [0,0,102], [0,0,103], [0,0,104], [0,0,105], [0,0,106], [0,0,107], [0,0,108], [0,0,109], [0,0,110], [0,0,111], [0,0,112], [0,0,113], [0,0,114], [0,0,115], [0,0,116], [0,0,117], [0,0,118], [0,0,119], [0,0,120], [0,0,121], [0,0,122], [0,0,123], [0,0,124], [0,0,125], [0,0,126], [0,0,127], [0,0,128], [0,0,129], [0,0,130], [0,0,131], [0,0,132], [0,0,133], [0,0,134], [0,0,135], [0,0,136], [0,0,137], [0,0,138], [0,0,139], [0,0,140], [0,0,141], [0,0,142], [0,0,143], [0,0,144], [0,0,145], [0,0,146], [0,0,147], [0,0,148], [0,0,149], [0,0,150], [0,0,151], [0,0,152], [0,0,153], [0,0,154], [0,0,155], [0,0,156], [0,0,157], [0,0,158], [0,0,159], [0,0,160], [0,0,161], [0,0,162], [0,0,163], [0,0,164], [0,0,165], [0,0,166], [0,0,167], [0,0,168], [0,0,169], [0,0,170], [0,0,171], [0,0,172], [0,0,173], [0,0,174], [0,0,175], [0,0,176], [0,0,177], [0,0,178], [0,0,179], [0,0,180], [0,0,181], [0,0,182], [0,0,183], [0,0,184], [0,0,185], [0,0,186], [0,0,187], [0,0,188], [0,0,189], [0,0,190], [0,0,191], [0,0,192], [0,0,193], [0,0,194], [0,0,195], [0,0,196], [0,0,197], [0,0,198], [0,0,199], [0,0,200], [0,0,201], [0,0,202], [0,0,203], [0,0,204], [0,0,205], [0,0,206], [0,0,207], [0,0,208], [0,0,209], [0,0,210], [0,0,211], [0,0,212], [0,0,213], [0,0,214], [0,0,215], [0,0,216], [0,0,217], [0,0,218], [0,0,219], [0,0,220], [0,0,221], [0,0,222], [0,0,223], [0,0,224], [0,0,225], [0,0,226], [0,0,227], [0,0,228], [0,0,229], [0,0,230], [0,0,231], [0,0,232], [0,0,233], [0,0,234], [0,0,235], [0,0,236], [0,0,237], [0,0,238], [0,0,239], [0,0,240], [0,0,241], [0,0,242], [0,0,243], [0,0,244], [0,0,245], [0,0,246], [0,0,247], [0,0,248], [0,0,249], [0,0,250], [0,0,251], [0,0,252], [0,0,253], [0,0,254], [0,0,255]],
coolCm: [ [0,0,0], [0,0,1], [0,0,3], [0,0,5], [0,0,7], [0,0,9], [0,0,11], [0,0,13], [0,0,15], [0,0,17], [0,0,18], [0,0,20], [0,0,22], [0,0,24], [0,0,26], [0,0,28], [0,0,30], [0,0,32], [0,0,34], [0,0,35], [0,0,37], [0,0,39], [0,0,41], [0,0,43], [0,0,45], [0,0,47], [0,0,49], [0,0,51], [0,0,52], [0,0,54], [0,0,56], [0,0,58], [0,0,60], [0,0,62], [0,0,64], [0,0,66], [0,0,68], [0,0,69], [0,0,71], [0,0,73], [0,0,75], [0,0,77], [0,0,79], [0,0,81], [0,0,83], [0,0,85], [0,0,86], [0,0,88], [0,0,90], [0,0,92], [0,0,94], [0,0,96], [0,0,98], [0,0,100], [0,0,102], [0,0,103], [0,0,105], [0,1,107], [0,2,109], [0,4,111], [0,5,113], [0,6,115], [0,8,117], [0,9,119], [0,10,120], [0,12,122], [0,13,124], [0,14,126], [0,16,128], [0,17,130], [0,18,132], [0,20,134], [0,21,136], [0,23,137], [0,24,139], [0,25,141], [0,27,143], [0,28,145], [1,29,147], [1,31,149], [1,32,151], [1,33,153], [1,35,154], [2,36,156], [2,37,158], [2,39,160], [2,40,162], [2,42,164], [3,43,166], [3,44,168], [3,46,170], [3,47,171], [4,48,173], [4,50,175], [4,51,177], [4,52,179], [4,54,181], [5,55,183], [5,56,185], [5,58,187], [5,59,188], [5,61,190], [6,62,192], [6,63,194], [6,65,196], [6,66,198], [7,67,200], [7,69,202], [7,70,204], [7,71,205], [7,73,207], [8,74,209], [8,75,211], [8,77,213], [8,78,215], [8,80,217], [9,81,219], [9,82,221], [9,84,222], [9,85,224], [9,86,226], [10,88,228], [10,89,230], [10,90,232], [10,92,234], [11,93,236], [11,94,238], [11,96,239], [11,97,241], [11,99,243], [12,100,245], [12,101,247], [12,103,249], [12,104,251], [12,105,253], [13,107,255], [13,108,255], [13,109,255], [13,111,255], [14,112,255], [14,113,255], [14,115,255], [14,116,255], [14,118,255], [15,119,255], [15,120,255], [15,122,255], [15,123,255], [15,124,255], [16,126,255], [16,127,255], [16,128,255], [16,130,255], [17,131,255], [17,132,255], [17,134,255], [17,135,255], [17,136,255], [18,138,255], [18,139,255], [18,141,255], [18,142,255], [18,143,255], [19,145,255], [19,146,255], [19,147,255], [19,149,255], [19,150,255], [20,151,255], [20,153,255], [20,154,255], [20,155,255], [21,157,255], [21,158,255], [21,160,255], [21,161,255], [21,162,255], [22,164,255], [22,165,255], [22,166,255], [22,168,255], [22,169,255], [23,170,255], [23,172,255], [23,173,255], [23,174,255], [24,176,255], [24,177,255], [24,179,255], [24,180,255], [24,181,255], [25,183,255], [25,184,255], [25,185,255], [29,187,255], [32,188,255], [36,189,255], [40,191,255], [44,192,255], [47,193,255], [51,195,255], [55,196,255], [58,198,255], [62,199,255], [66,200,255], [69,202,255], [73,203,255], [77,204,255], [81,206,255], [84,207,255], [88,208,255], [92,210,255], [95,211,255], [99,212,255], [103,214,255], [106,215,255], [110,217,255], [114,218,255], [118,219,255], [121,221,255], [125,222,255], [129,223,255], [132,225,255], [136,226,255], [140,227,255], [143,229,255], [147,230,255], [151,231,255], [155,233,255], [158,234,255], [162,236,255], [166,237,255], [169,238,255], [173,240,255], [177,241,255], [180,242,255], [184,244,255], [188,245,255], [192,246,255], [195,248,255], [199,249,255], [203,250,255], [206,252,255], [210,253,255], [214,255,255], [217,255,255], [221,255,255], [225,255,255], [229,255,255], [232,255,255], [236,255,255], [240,255,255], [243,255,255], [247,255,255], [251,255,255], [255,255,255]],
cubehelix0Cm: [ [0,0,0], [2,1,2], [5,2,5], [5,2,5], [6,2,6], [7,2,7], [10,3,10], [12,5,12], [13,5,14], [14,5,16], [15,5,17], [16,6,20], [17,7,22], [18,8,24], [19,9,26], [20,10,28], [21,11,30], [22,12,33], [22,13,34], [22,14,36], [22,15,38], [24,16,40], [25,17,43], [25,18,45], [25,19,46], [25,20,48], [25,22,50], [25,23,51], [25,25,53], [25,26,54], [25,28,56], [25,28,57], [25,29,59], [25,30,61], [25,33,62], [25,35,63], [25,36,65], [25,37,67], [25,38,68], [25,40,70], [25,43,71], [24,45,72], [23,46,73], [22,48,73], [22,49,75], [22,51,76], [22,52,76], [22,54,76], [22,56,76], [22,57,77], [22,59,78], [22,61,79], [21,63,79], [20,66,79], [20,67,79], [20,68,79], [20,68,79], [20,71,79], [20,73,79], [20,75,78], [20,77,77], [20,79,76], [20,80,76], [20,81,76], [21,83,75], [22,85,74], [22,86,73], [22,89,72], [22,91,71], [23,92,71], [24,93,71], [25,94,71], [26,96,70], [28,99,68], [28,100,68], [29,101,67], [30,102,66], [31,102,65], [32,103,64], [33,104,63], [35,105,62], [38,107,61], [39,107,60], [39,108,59], [40,109,58], [43,110,57], [45,112,56], [47,113,55], [49,113,54], [51,114,53], [54,116,52], [58,117,51], [60,117,50], [62,117,49], [63,117,48], [66,118,48], [68,119,48], [71,119,48], [73,119,48], [76,119,48], [79,120,47], [81,121,46], [84,122,45], [87,122,45], [91,122,45], [94,122,46], [96,122,47], [99,122,48], [103,122,48], [107,122,48], [109,122,49], [112,122,50], [114,122,51], [118,122,52], [122,122,53], [124,122,54], [127,122,55], [130,122,56], [133,122,57], [137,122,58], [140,122,60], [142,122,62], [145,122,63], [149,122,66], [153,122,68], [155,121,70], [158,120,72], [160,119,73], [162,119,75], [164,119,77], [165,119,79], [169,119,81], [173,119,84], [175,119,86], [176,119,89], [178,119,91], [181,119,95], [183,119,99], [186,120,102], [188,121,104], [191,122,107], [192,122,110], [193,122,114], [195,122,117], [197,122,119], [198,122,122], [200,122,126], [201,122,130], [202,123,132], [203,124,135], [204,124,137], [204,125,141], [205,126,144], [206,127,147], [207,127,151], [209,127,155], [209,128,158], [210,129,160], [211,130,163], [211,131,167], [211,132,170], [211,133,173], [211,134,175], [211,135,178], [211,136,182], [211,137,186], [211,138,188], [211,139,191], [211,140,193], [211,142,196], [211,145,198], [210,146,201], [209,147,204], [209,147,206], [209,150,209], [209,153,211], [208,153,213], [207,154,215], [206,155,216], [206,157,218], [206,158,220], [206,160,221], [205,163,224], [204,165,226], [203,167,228], [202,169,230], [201,170,232], [201,172,233], [201,173,234], [200,175,235], [199,176,236], [198,178,237], [197,181,238], [196,183,239], [196,185,239], [196,187,239], [196,188,239], [195,191,240], [193,193,242], [193,194,242], [193,195,242], [193,196,242], [193,198,242], [193,199,242], [193,201,242], [193,204,242], [193,206,242], [193,208,242], [193,209,242], [193,211,242], [193,212,242], [193,214,242], [194,215,242], [195,217,242], [196,219,242], [196,220,242], [196,221,242], [197,223,241], [198,225,240], [198,226,239], [200,228,239], [201,229,239], [202,230,239], [203,231,239], [204,232,239], [205,233,239], [206,234,239], [207,235,239], [208,236,239], [209,237,239], [210,238,239], [212,238,239], [214,239,239], [215,240,239], [216,242,239], [218,243,239], [220,243,239], [221,244,239], [224,246,239], [226,247,239], [228,247,240], [230,247,241], [232,247,242], [234,248,242], [237,249,242], [238,249,243], [240,249,243], [242,249,244], [243,251,246], [244,252,247], [246,252,249], [248,252,250], [249,252,252], [251,253,253], [253,254,254], [255,255,255]],
cubehelix1Cm: [ [0,0,0], [2,0,2], [5,0,5], [6,0,7], [8,0,10], [10,0,12], [12,1,15], [15,2,17], [17,2,20], [18,2,22], [20,2,25], [21,2,29], [22,2,33], [23,3,35], [24,4,38], [25,5,40], [25,6,44], [25,7,48], [26,8,51], [27,9,53], [28,10,56], [28,11,59], [28,12,63], [27,14,66], [26,16,68], [25,17,71], [25,18,73], [25,19,74], [25,20,76], [24,22,80], [22,25,84], [22,27,85], [21,28,87], [20,30,89], [19,33,91], [17,35,94], [16,37,95], [14,39,96], [12,40,96], [11,43,99], [10,45,102], [8,47,102], [6,49,103], [5,51,104], [2,54,104], [0,58,104], [0,60,104], [0,62,104], [0,63,104], [0,66,104], [0,68,104], [0,71,104], [0,73,104], [0,76,104], [0,79,103], [0,81,102], [0,84,102], [0,86,99], [0,89,96], [0,91,96], [0,94,95], [0,96,94], [0,99,91], [0,102,89], [0,103,86], [0,105,84], [0,107,81], [0,109,79], [0,112,76], [0,113,73], [0,115,71], [0,117,68], [0,119,65], [0,122,61], [0,124,58], [0,125,56], [0,127,53], [0,128,51], [0,129,48], [0,130,45], [0,132,42], [0,135,38], [0,136,35], [0,136,33], [0,137,30], [3,138,26], [7,140,22], [10,140,21], [12,140,19], [15,140,17], [19,141,14], [22,142,10], [26,142,8], [29,142,6], [33,142,5], [38,142,2], [43,142,0], [46,142,0], [50,142,0], [53,142,0], [57,141,0], [62,141,0], [66,140,0], [72,140,0], [79,140,0], [83,139,0], [87,138,0], [91,137,0], [98,136,0], [104,135,0], [108,134,0], [113,133,0], [117,132,0], [123,131,0], [130,130,0], [134,129,0], [138,128,0], [142,127,0], [149,126,0], [155,124,0], [159,123,0], [164,121,1], [168,119,2], [174,118,6], [181,117,10], [185,116,12], [189,115,15], [193,114,17], [197,113,21], [200,113,24], [204,112,28], [209,110,33], [214,109,38], [217,108,41], [221,107,45], [224,107,48], [228,105,54], [232,104,61], [234,103,64], [237,102,68], [239,102,71], [243,102,77], [247,102,84], [249,101,89], [250,100,94], [252,99,99], [253,99,105], [255,99,112], [255,99,117], [255,99,122], [255,99,127], [255,99,131], [255,99,136], [255,99,140], [255,100,147], [255,102,155], [255,102,159], [255,102,164], [255,102,168], [255,103,174], [255,104,181], [255,105,185], [255,106,189], [255,107,193], [255,108,200], [255,109,206], [255,111,210], [255,113,215], [255,114,219], [253,117,224], [252,119,229], [250,120,232], [249,121,236], [247,122,239], [244,124,244], [242,127,249], [240,130,251], [238,132,253], [237,135,255], [234,136,255], [232,138,255], [229,140,255], [226,142,255], [224,145,255], [222,147,255], [221,150,255], [219,153,255], [215,156,255], [211,160,255], [209,162,255], [208,164,255], [206,165,255], [204,169,255], [201,173,255], [199,175,255], [198,178,255], [196,181,255], [193,183,255], [191,186,255], [189,188,255], [187,191,255], [186,193,255], [185,195,255], [184,197,255], [183,198,255], [182,202,255], [181,206,255], [180,208,255], [179,209,255], [178,211,255], [177,214,255], [175,216,255], [175,218,255], [175,220,255], [175,221,255], [175,224,255], [175,226,255], [176,228,255], [177,230,255], [178,232,255], [179,234,255], [181,237,255], [181,238,255], [182,238,255], [183,239,255], [184,240,252], [186,242,249], [187,243,249], [189,243,248], [191,244,247], [192,245,246], [194,246,245], [196,247,244], [198,248,243], [201,249,242], [203,250,242], [204,251,242], [206,252,242], [210,252,240], [214,252,239], [216,252,240], [219,252,241], [221,252,242], [224,253,242], [226,255,242], [229,255,243], [232,255,243], [234,255,244], [238,255,246], [242,255,247], [243,255,248], [245,255,249], [247,255,249], [249,255,251], [252,255,253], [255,255,255]],
greenCm: [ [0,0,0], [0,1,0], [0,2,0], [0,3,0], [0,4,0], [0,5,0], [0,6,0], [0,7,0], [0,8,0], [0,9,0], [0,10,0], [0,11,0], [0,12,0], [0,13,0], [0,14,0], [0,15,0], [0,16,0], [0,17,0], [0,18,0], [0,19,0], [0,20,0], [0,21,0], [0,22,0], [0,23,0], [0,24,0], [0,25,0], [0,26,0], [0,27,0], [0,28,0], [0,29,0], [0,30,0], [0,31,0], [0,32,0], [0,33,0], [0,34,0], [0,35,0], [0,36,0], [0,37,0], [0,38,0], [0,39,0], [0,40,0], [0,41,0], [0,42,0], [0,43,0], [0,44,0], [0,45,0], [0,46,0], [0,47,0], [0,48,0], [0,49,0], [0,50,0], [0,51,0], [0,52,0], [0,53,0], [0,54,0], [0,55,0], [0,56,0], [0,57,0], [0,58,0], [0,59,0], [0,60,0], [0,61,0], [0,62,0], [0,63,0], [0,64,0], [0,65,0], [0,66,0], [0,67,0], [0,68,0], [0,69,0], [0,70,0], [0,71,0], [0,72,0], [0,73,0], [0,74,0], [0,75,0], [0,76,0], [0,77,0], [0,78,0], [0,79,0], [0,80,0], [0,81,0], [0,82,0], [0,83,0], [0,84,0], [0,85,0], [0,86,0], [0,87,0], [0,88,0], [0,89,0], [0,90,0], [0,91,0], [0,92,0], [0,93,0], [0,94,0], [0,95,0], [0,96,0], [0,97,0], [0,98,0], [0,99,0], [0,100,0], [0,101,0], [0,102,0], [0,103,0], [0,104,0], [0,105,0], [0,106,0], [0,107,0], [0,108,0], [0,109,0], [0,110,0], [0,111,0], [0,112,0], [0,113,0], [0,114,0], [0,115,0], [0,116,0], [0,117,0], [0,118,0], [0,119,0], [0,120,0], [0,121,0], [0,122,0], [0,123,0], [0,124,0], [0,125,0], [0,126,0], [0,127,0], [0,128,0], [0,129,0], [0,130,0], [0,131,0], [0,132,0], [0,133,0], [0,134,0], [0,135,0], [0,136,0], [0,137,0], [0,138,0], [0,139,0], [0,140,0], [0,141,0], [0,142,0], [0,143,0], [0,144,0], [0,145,0], [0,146,0], [0,147,0], [0,148,0], [0,149,0], [0,150,0], [0,151,0], [0,152,0], [0,153,0], [0,154,0], [0,155,0], [0,156,0], [0,157,0], [0,158,0], [0,159,0], [0,160,0], [0,161,0], [0,162,0], [0,163,0], [0,164,0], [0,165,0], [0,166,0], [0,167,0], [0,168,0], [0,169,0], [0,170,0], [0,171,0], [0,172,0], [0,173,0], [0,174,0], [0,175,0], [0,176,0], [0,177,0], [0,178,0], [0,179,0], [0,180,0], [0,181,0], [0,182,0], [0,183,0], [0,184,0], [0,185,0], [0,186,0], [0,187,0], [0,188,0], [0,189,0], [0,190,0], [0,191,0], [0,192,0], [0,193,0], [0,194,0], [0,195,0], [0,196,0], [0,197,0], [0,198,0], [0,199,0], [0,200,0], [0,201,0], [0,202,0], [0,203,0], [0,204,0], [0,205,0], [0,206,0], [0,207,0], [0,208,0], [0,209,0], [0,210,0], [0,211,0], [0,212,0], [0,213,0], [0,214,0], [0,215,0], [0,216,0], [0,217,0], [0,218,0], [0,219,0], [0,220,0], [0,221,0], [0,222,0], [0,223,0], [0,224,0], [0,225,0], [0,226,0], [0,227,0], [0,228,0], [0,229,0], [0,230,0], [0,231,0], [0,232,0], [0,233,0], [0,234,0], [0,235,0], [0,236,0], [0,237,0], [0,238,0], [0,239,0], [0,240,0], [0,241,0], [0,242,0], [0,243,0], [0,244,0], [0,245,0], [0,246,0], [0,247,0], [0,248,0], [0,249,0], [0,250,0], [0,251,0], [0,252,0], [0,253,0], [0,254,0], [0,255,0]],
greyCm: [ [0,0,0], [1,1,1], [2,2,2], [3,3,3], [4,4,4], [5,5,5], [6,6,6], [7,7,7], [8,8,8], [9,9,9], [10,10,10], [11,11,11], [12,12,12], [13,13,13], [14,14,14], [15,15,15], [16,16,16], [17,17,17], [18,18,18], [19,19,19], [20,20,20], [21,21,21], [22,22,22], [23,23,23], [24,24,24], [25,25,25], [26,26,26], [27,27,27], [28,28,28], [29,29,29], [30,30,30], [31,31,31], [32,32,32], [33,33,33], [34,34,34], [35,35,35], [36,36,36], [37,37,37], [38,38,38], [39,39,39], [40,40,40], [41,41,41], [42,42,42], [43,43,43], [44,44,44], [45,45,45], [46,46,46], [47,47,47], [48,48,48], [49,49,49], [50,50,50], [51,51,51], [52,52,52], [53,53,53], [54,54,54], [55,55,55], [56,56,56], [57,57,57], [58,58,58], [59,59,59], [60,60,60], [61,61,61], [62,62,62], [63,63,63], [64,64,64], [65,65,65], [66,66,66], [67,67,67], [68,68,68], [69,69,69], [70,70,70], [71,71,71], [72,72,72], [73,73,73], [74,74,74], [75,75,75], [76,76,76], [77,77,77], [78,78,78], [79,79,79], [80,80,80], [81,81,81], [82,82,82], [83,83,83], [84,84,84], [85,85,85], [86,86,86], [87,87,87], [88,88,88], [89,89,89], [90,90,90], [91,91,91], [92,92,92], [93,93,93], [94,94,94], [95,95,95], [96,96,96], [97,97,97], [98,98,98], [99,99,99], [100,100,100], [101,101,101], [102,102,102], [103,103,103], [104,104,104], [105,105,105], [106,106,106], [107,107,107], [108,108,108], [109,109,109], [110,110,110], [111,111,111], [112,112,112], [113,113,113], [114,114,114], [115,115,115], [116,116,116], [117,117,117], [118,118,118], [119,119,119], [120,120,120], [121,121,121], [122,122,122], [123,123,123], [124,124,124], [125,125,125], [126,126,126], [127,127,127], [128,128,128], [129,129,129], [130,130,130], [131,131,131], [132,132,132], [133,133,133], [134,134,134], [135,135,135], [136,136,136], [137,137,137], [138,138,138], [139,139,139], [140,140,140], [141,141,141], [142,142,142], [143,143,143], [144,144,144], [145,145,145], [146,146,146], [147,147,147], [148,148,148], [149,149,149], [150,150,150], [151,151,151], [152,152,152], [153,153,153], [154,154,154], [155,155,155], [156,156,156], [157,157,157], [158,158,158], [159,159,159], [160,160,160], [161,161,161], [162,162,162], [163,163,163], [164,164,164], [165,165,165], [166,166,166], [167,167,167], [168,168,168], [169,169,169], [170,170,170], [171,171,171], [172,172,172], [173,173,173], [174,174,174], [175,175,175], [176,176,176], [177,177,177], [178,178,178], [179,179,179], [180,180,180], [181,181,181], [182,182,182], [183,183,183], [184,184,184], [185,185,185], [186,186,186], [187,187,187], [188,188,188], [189,189,189], [190,190,190], [191,191,191], [192,192,192], [193,193,193], [194,194,194], [195,195,195], [196,196,196], [197,197,197], [198,198,198], [199,199,199], [200,200,200], [201,201,201], [202,202,202], [203,203,203], [204,204,204], [205,205,205], [206,206,206], [207,207,207], [208,208,208], [209,209,209], [210,210,210], [211,211,211], [212,212,212], [213,213,213], [214,214,214], [215,215,215], [216,216,216], [217,217,217], [218,218,218], [219,219,219], [220,220,220], [221,221,221], [222,222,222], [223,223,223], [224,224,224], [225,225,225], [226,226,226], [227,227,227], [228,228,228], [229,229,229], [230,230,230], [231,231,231], [232,232,232], [233,233,233], [234,234,234], [235,235,235], [236,236,236], [237,237,237], [238,238,238], [239,239,239], [240,240,240], [241,241,241], [242,242,242], [243,243,243], [244,244,244], [245,245,245], [246,246,246], [247,247,247], [248,248,248], [249,249,249], [250,250,250], [251,251,251], [252,252,252], [253,253,253], [254,254,254], [255,255,255]],
heCm: [ [0,0,0], [42,0,10], [85,0,21], [127,0,31], [127,0,47], [127,0,63], [127,0,79], [127,0,95], [127,0,102], [127,0,109], [127,0,116], [127,0,123], [127,0,131], [127,0,138], [127,0,145], [127,0,152], [127,0,159], [127,8,157], [127,17,155], [127,25,153], [127,34,151], [127,42,149], [127,51,147], [127,59,145], [127,68,143], [127,76,141], [127,85,139], [127,93,136], [127,102,134], [127,110,132], [127,119,130], [127,127,128], [127,129,126], [127,131,124], [127,133,122], [127,135,120], [127,137,118], [127,139,116], [127,141,114], [127,143,112], [127,145,110], [127,147,108], [127,149,106], [127,151,104], [127,153,102], [127,155,100], [127,157,98], [127,159,96], [127,161,94], [127,163,92], [127,165,90], [127,167,88], [127,169,86], [127,171,84], [127,173,82], [127,175,80], [127,177,77], [127,179,75], [127,181,73], [127,183,71], [127,185,69], [127,187,67], [127,189,65], [127,191,63], [128,191,64], [129,191,65], [130,191,66], [131,192,67], [132,192,68], [133,192,69], [134,192,70], [135,193,71], [136,193,72], [137,193,73], [138,193,74], [139,194,75], [140,194,76], [141,194,77], [142,194,78], [143,195,79], [144,195,80], [145,195,81], [146,195,82], [147,196,83], [148,196,84], [149,196,85], [150,196,86], [151,196,87], [152,197,88], [153,197,89], [154,197,90], [155,197,91], [156,198,92], [157,198,93], [158,198,94], [159,198,95], [160,199,96], [161,199,97], [162,199,98], [163,199,99], [164,200,100], [165,200,101], [166,200,102], [167,200,103], [168,201,104], [169,201,105], [170,201,106], [171,201,107], [172,202,108], [173,202,109], [174,202,110], [175,202,111], [176,202,112], [177,203,113], [178,203,114], [179,203,115], [180,203,116], [181,204,117], [182,204,118], [183,204,119], [184,204,120], [185,205,121], [186,205,122], [187,205,123], [188,205,124], [189,206,125], [190,206,126], [191,206,127], [191,206,128], [192,207,129], [192,207,130], [193,208,131], [193,208,132], [194,208,133], [194,209,134], [195,209,135], [195,209,136], [196,210,137], [196,210,138], [197,211,139], [197,211,140], [198,211,141], [198,212,142], [199,212,143], [199,212,144], [200,213,145], [200,213,146], [201,214,147], [201,214,148], [202,214,149], [202,215,150], [203,215,151], [203,216,152], [204,216,153], [204,216,154], [205,217,155], [205,217,156], [206,217,157], [206,218,158], [207,218,159], [207,219,160], [208,219,161], [208,219,162], [209,220,163], [209,220,164], [210,220,165], [210,221,166], [211,221,167], [211,222,168], [212,222,169], [212,222,170], [213,223,171], [213,223,172], [214,223,173], [214,224,174], [215,224,175], [215,225,176], [216,225,177], [216,225,178], [217,226,179], [217,226,180], [218,226,181], [218,227,182], [219,227,183], [219,228,184], [220,228,185], [220,228,186], [221,229,187], [221,229,188], [222,230,189], [222,230,190], [223,230,191], [223,231,192], [224,231,193], [224,231,194], [225,232,195], [225,232,196], [226,233,197], [226,233,198], [227,233,199], [227,234,200], [228,234,201], [228,234,202], [229,235,203], [229,235,204], [230,236,205], [230,236,206], [231,236,207], [231,237,208], [232,237,209], [232,237,210], [233,238,211], [233,238,212], [234,239,213], [234,239,214], [235,239,215], [235,240,216], [236,240,217], [236,240,218], [237,241,219], [237,241,220], [238,242,221], [238,242,222], [239,242,223], [239,243,224], [240,243,225], [240,244,226], [241,244,227], [241,244,228], [242,245,229], [242,245,230], [243,245,231], [243,246,232], [244,246,233], [244,247,234], [245,247,235], [245,247,236], [246,248,237], [246,248,238], [247,248,239], [247,249,240], [248,249,241], [248,250,242], [249,250,243], [249,250,244], [250,251,245], [250,251,246], [251,251,247], [251,252,248], [252,252,249], [252,253,250], [253,253,251], [253,253,252], [254,254,253], [254,254,254], [255,255,255]],
heatCm: [ [0,0,0], [2,1,0], [5,2,0], [8,3,0], [11,4,0], [14,5,0], [17,6,0], [20,7,0], [23,8,0], [26,9,0], [29,10,0], [32,11,0], [35,12,0], [38,13,0], [41,14,0], [44,15,0], [47,16,0], [50,17,0], [53,18,0], [56,19,0], [59,20,0], [62,21,0], [65,22,0], [68,23,0], [71,24,0], [74,25,0], [77,26,0], [80,27,0], [83,28,0], [85,29,0], [88,30,0], [91,31,0], [94,32,0], [97,33,0], [100,34,0], [103,35,0], [106,36,0], [109,37,0], [112,38,0], [115,39,0], [118,40,0], [121,41,0], [124,42,0], [127,43,0], [130,44,0], [133,45,0], [136,46,0], [139,47,0], [142,48,0], [145,49,0], [148,50,0], [151,51,0], [154,52,0], [157,53,0], [160,54,0], [163,55,0], [166,56,0], [169,57,0], [171,58,0], [174,59,0], [177,60,0], [180,61,0], [183,62,0], [186,63,0], [189,64,0], [192,65,0], [195,66,0], [198,67,0], [201,68,0], [204,69,0], [207,70,0], [210,71,0], [213,72,0], [216,73,0], [219,74,0], [222,75,0], [225,76,0], [228,77,0], [231,78,0], [234,79,0], [237,80,0], [240,81,0], [243,82,0], [246,83,0], [249,84,0], [252,85,0], [255,86,0], [255,87,0], [255,88,0], [255,89,0], [255,90,0], [255,91,0], [255,92,0], [255,93,0], [255,94,0], [255,95,0], [255,96,0], [255,97,0], [255,98,0], [255,99,0], [255,100,0], [255,101,0], [255,102,0], [255,103,0], [255,104,0], [255,105,0], [255,106,0], [255,107,0], [255,108,0], [255,109,0], [255,110,0], [255,111,0], [255,112,0], [255,113,0], [255,114,0], [255,115,0], [255,116,0], [255,117,0], [255,118,0], [255,119,0], [255,120,0], [255,121,0], [255,122,0], [255,123,0], [255,124,0], [255,125,0], [255,126,0], [255,127,0], [255,128,0], [255,129,0], [255,130,0], [255,131,0], [255,132,0], [255,133,0], [255,134,0], [255,135,0], [255,136,0], [255,137,0], [255,138,0], [255,139,0], [255,140,0], [255,141,0], [255,142,0], [255,143,0], [255,144,0], [255,145,0], [255,146,0], [255,147,0], [255,148,0], [255,149,0], [255,150,0], [255,151,0], [255,152,0], [255,153,0], [255,154,0], [255,155,0], [255,156,0], [255,157,0], [255,158,0], [255,159,0], [255,160,0], [255,161,0], [255,162,0], [255,163,0], [255,164,0], [255,165,0], [255,166,3], [255,167,6], [255,168,9], [255,169,12], [255,170,15], [255,171,18], [255,172,21], [255,173,24], [255,174,27], [255,175,30], [255,176,33], [255,177,36], [255,178,39], [255,179,42], [255,180,45], [255,181,48], [255,182,51], [255,183,54], [255,184,57], [255,185,60], [255,186,63], [255,187,66], [255,188,69], [255,189,72], [255,190,75], [255,191,78], [255,192,81], [255,193,85], [255,194,88], [255,195,91], [255,196,94], [255,197,97], [255,198,100], [255,199,103], [255,200,106], [255,201,109], [255,202,112], [255,203,115], [255,204,118], [255,205,121], [255,206,124], [255,207,127], [255,208,130], [255,209,133], [255,210,136], [255,211,139], [255,212,142], [255,213,145], [255,214,148], [255,215,151], [255,216,154], [255,217,157], [255,218,160], [255,219,163], [255,220,166], [255,221,170], [255,222,173], [255,223,176], [255,224,179], [255,225,182], [255,226,185], [255,227,188], [255,228,191], [255,229,194], [255,230,197], [255,231,200], [255,232,203], [255,233,206], [255,234,209], [255,235,212], [255,236,215], [255,237,218], [255,238,221], [255,239,224], [255,240,227], [255,241,230], [255,242,233], [255,243,236], [255,244,239], [255,245,242], [255,246,245], [255,247,248], [255,248,251], [255,249,255], [255,250,255], [255,251,255], [255,252,255], [255,253,255], [255,254,255], [255,255,255]],
rainbowCm: [ [255,0,255], [250,0,255], [245,0,255], [240,0,255], [235,0,255], [230,0,255], [225,0,255], [220,0,255], [215,0,255], [210,0,255], [205,0,255], [200,0,255], [195,0,255], [190,0,255], [185,0,255], [180,0,255], [175,0,255], [170,0,255], [165,0,255], [160,0,255], [155,0,255], [150,0,255], [145,0,255], [140,0,255], [135,0,255], [130,0,255], [125,0,255], [120,0,255], [115,0,255], [110,0,255], [105,0,255], [100,0,255], [95,0,255], [90,0,255], [85,0,255], [80,0,255], [75,0,255], [70,0,255], [65,0,255], [60,0,255], [55,0,255], [50,0,255], [45,0,255], [40,0,255], [35,0,255], [30,0,255], [25,0,255], [20,0,255], [15,0,255], [10,0,255], [5,0,255], [0,0,255], [0,5,255], [0,10,255], [0,15,255], [0,20,255], [0,25,255], [0,30,255], [0,35,255], [0,40,255], [0,45,255], [0,50,255], [0,55,255], [0,60,255], [0,65,255], [0,70,255], [0,75,255], [0,80,255], [0,85,255], [0,90,255], [0,95,255], [0,100,255], [0,105,255], [0,110,255], [0,115,255], [0,120,255], [0,125,255], [0,130,255], [0,135,255], [0,140,255], [0,145,255], [0,150,255], [0,155,255], [0,160,255], [0,165,255], [0,170,255], [0,175,255], [0,180,255], [0,185,255], [0,190,255], [0,195,255], [0,200,255], [0,205,255], [0,210,255], [0,215,255], [0,220,255], [0,225,255], [0,230,255], [0,235,255], [0,240,255], [0,245,255], [0,250,255], [0,255,255], [0,255,250], [0,255,245], [0,255,240], [0,255,235], [0,255,230], [0,255,225], [0,255,220], [0,255,215], [0,255,210], [0,255,205], [0,255,200], [0,255,195], [0,255,190], [0,255,185], [0,255,180], [0,255,175], [0,255,170], [0,255,165], [0,255,160], [0,255,155], [0,255,150], [0,255,145], [0,255,140], [0,255,135], [0,255,130], [0,255,125], [0,255,120], [0,255,115], [0,255,110], [0,255,105], [0,255,100], [0,255,95], [0,255,90], [0,255,85], [0,255,80], [0,255,75], [0,255,70], [0,255,65], [0,255,60], [0,255,55], [0,255,50], [0,255,45], [0,255,40], [0,255,35], [0,255,30], [0,255,25], [0,255,20], [0,255,15], [0,255,10], [0,255,5], [0,255,0], [5,255,0], [10,255,0], [15,255,0], [20,255,0], [25,255,0], [30,255,0], [35,255,0], [40,255,0], [45,255,0], [50,255,0], [55,255,0], [60,255,0], [65,255,0], [70,255,0], [75,255,0], [80,255,0], [85,255,0], [90,255,0], [95,255,0], [100,255,0], [105,255,0], [110,255,0], [115,255,0], [120,255,0], [125,255,0], [130,255,0], [135,255,0], [140,255,0], [145,255,0], [150,255,0], [155,255,0], [160,255,0], [165,255,0], [170,255,0], [175,255,0], [180,255,0], [185,255,0], [190,255,0], [195,255,0], [200,255,0], [205,255,0], [210,255,0], [215,255,0], [220,255,0], [225,255,0], [230,255,0], [235,255,0], [240,255,0], [245,255,0], [250,255,0], [255,255,0], [255,250,0], [255,245,0], [255,240,0], [255,235,0], [255,230,0], [255,225,0], [255,220,0], [255,215,0], [255,210,0], [255,205,0], [255,200,0], [255,195,0], [255,190,0], [255,185,0], [255,180,0], [255,175,0], [255,170,0], [255,165,0], [255,160,0], [255,155,0], [255,150,0], [255,145,0], [255,140,0], [255,135,0], [255,130,0], [255,125,0], [255,120,0], [255,115,0], [255,110,0], [255,105,0], [255,100,0], [255,95,0], [255,90,0], [255,85,0], [255,80,0], [255,75,0], [255,70,0], [255,65,0], [255,60,0], [255,55,0], [255,50,0], [255,45,0], [255,40,0], [255,35,0], [255,30,0], [255,25,0], [255,20,0], [255,15,0], [255,10,0], [255,5,0], [255,0,0]],
redCm: [ [0,0,0], [1,0,0], [2,0,0], [3,0,0], [4,0,0], [5,0,0], [6,0,0], [7,0,0], [8,0,0], [9,0,0], [10,0,0], [11,0,0], [12,0,0], [13,0,0], [14,0,0], [15,0,0], [16,0,0], [17,0,0], [18,0,0], [19,0,0], [20,0,0], [21,0,0], [22,0,0], [23,0,0], [24,0,0], [25,0,0], [26,0,0], [27,0,0], [28,0,0], [29,0,0], [30,0,0], [31,0,0], [32,0,0], [33,0,0], [34,0,0], [35,0,0], [36,0,0], [37,0,0], [38,0,0], [39,0,0], [40,0,0], [41,0,0], [42,0,0], [43,0,0], [44,0,0], [45,0,0], [46,0,0], [47,0,0], [48,0,0], [49,0,0], [50,0,0], [51,0,0], [52,0,0], [53,0,0], [54,0,0], [55,0,0], [56,0,0], [57,0,0], [58,0,0], [59,0,0], [60,0,0], [61,0,0], [62,0,0], [63,0,0], [64,0,0], [65,0,0], [66,0,0], [67,0,0], [68,0,0], [69,0,0], [70,0,0], [71,0,0], [72,0,0], [73,0,0], [74,0,0], [75,0,0], [76,0,0], [77,0,0], [78,0,0], [79,0,0], [80,0,0], [81,0,0], [82,0,0], [83,0,0], [84,0,0], [85,0,0], [86,0,0], [87,0,0], [88,0,0], [89,0,0], [90,0,0], [91,0,0], [92,0,0], [93,0,0], [94,0,0], [95,0,0], [96,0,0], [97,0,0], [98,0,0], [99,0,0], [100,0,0], [101,0,0], [102,0,0], [103,0,0], [104,0,0], [105,0,0], [106,0,0], [107,0,0], [108,0,0], [109,0,0], [110,0,0], [111,0,0], [112,0,0], [113,0,0], [114,0,0], [115,0,0], [116,0,0], [117,0,0], [118,0,0], [119,0,0], [120,0,0], [121,0,0], [122,0,0], [123,0,0], [124,0,0], [125,0,0], [126,0,0], [127,0,0], [128,0,0], [129,0,0], [130,0,0], [131,0,0], [132,0,0], [133,0,0], [134,0,0], [135,0,0], [136,0,0], [137,0,0], [138,0,0], [139,0,0], [140,0,0], [141,0,0], [142,0,0], [143,0,0], [144,0,0], [145,0,0], [146,0,0], [147,0,0], [148,0,0], [149,0,0], [150,0,0], [151,0,0], [152,0,0], [153,0,0], [154,0,0], [155,0,0], [156,0,0], [157,0,0], [158,0,0], [159,0,0], [160,0,0], [161,0,0], [162,0,0], [163,0,0], [164,0,0], [165,0,0], [166,0,0], [167,0,0], [168,0,0], [169,0,0], [170,0,0], [171,0,0], [172,0,0], [173,0,0], [174,0,0], [175,0,0], [176,0,0], [177,0,0], [178,0,0], [179,0,0], [180,0,0], [181,0,0], [182,0,0], [183,0,0], [184,0,0], [185,0,0], [186,0,0], [187,0,0], [188,0,0], [189,0,0], [190,0,0], [191,0,0], [192,0,0], [193,0,0], [194,0,0], [195,0,0], [196,0,0], [197,0,0], [198,0,0], [199,0,0], [200,0,0], [201,0,0], [202,0,0], [203,0,0], [204,0,0], [205,0,0], [206,0,0], [207,0,0], [208,0,0], [209,0,0], [210,0,0], [211,0,0], [212,0,0], [213,0,0], [214,0,0], [215,0,0], [216,0,0], [217,0,0], [218,0,0], [219,0,0], [220,0,0], [221,0,0], [222,0,0], [223,0,0], [224,0,0], [225,0,0], [226,0,0], [227,0,0], [228,0,0], [229,0,0], [230,0,0], [231,0,0], [232,0,0], [233,0,0], [234,0,0], [235,0,0], [236,0,0], [237,0,0], [238,0,0], [239,0,0], [240,0,0], [241,0,0], [242,0,0], [243,0,0], [244,0,0], [245,0,0], [246,0,0], [247,0,0], [248,0,0], [249,0,0], [250,0,0], [251,0,0], [252,0,0], [253,0,0], [254,0,0], [255,0,0]],
standardCm: [ [0,0,0], [0,0,3], [1,1,6], [2,2,9], [3,3,12], [4,4,15], [5,5,18], [6,6,21], [7,7,24], [8,8,27], [9,9,30], [10,10,33], [10,10,36], [11,11,39], [12,12,42], [13,13,45], [14,14,48], [15,15,51], [16,16,54], [17,17,57], [18,18,60], [19,19,63], [20,20,66], [20,20,69], [21,21,72], [22,22,75], [23,23,78], [24,24,81], [25,25,85], [26,26,88], [27,27,91], [28,28,94], [29,29,97], [30,30,100], [30,30,103], [31,31,106], [32,32,109], [33,33,112], [34,34,115], [35,35,118], [36,36,121], [37,37,124], [38,38,127], [39,39,130], [40,40,133], [40,40,136], [41,41,139], [42,42,142], [43,43,145], [44,44,148], [45,45,151], [46,46,154], [47,47,157], [48,48,160], [49,49,163], [50,50,166], [51,51,170], [51,51,173], [52,52,176], [53,53,179], [54,54,182], [55,55,185], [56,56,188], [57,57,191], [58,58,194], [59,59,197], [60,60,200], [61,61,203], [61,61,206], [62,62,209], [63,63,212], [64,64,215], [65,65,218], [66,66,221], [67,67,224], [68,68,227], [69,69,230], [70,70,233], [71,71,236], [71,71,239], [72,72,242], [73,73,245], [74,74,248], [75,75,251], [76,76,255], [0,78,0], [1,80,1], [2,82,2], [3,84,3], [4,87,4], [5,89,5], [6,91,6], [7,93,7], [8,95,8], [9,97,9], [9,99,9], [10,101,10], [11,103,11], [12,105,12], [13,108,13], [14,110,14], [15,112,15], [16,114,16], [17,116,17], [18,118,18], [18,120,18], [19,122,19], [20,124,20], [21,126,21], [22,129,22], [23,131,23], [24,133,24], [25,135,25], [26,137,26], [27,139,27], [27,141,27], [28,143,28], [29,145,29], [30,147,30], [31,150,31], [32,152,32], [33,154,33], [34,156,34], [35,158,35], [36,160,36], [36,162,36], [37,164,37], [38,166,38], [39,168,39], [40,171,40], [41,173,41], [42,175,42], [43,177,43], [44,179,44], [45,181,45], [45,183,45], [46,185,46], [47,187,47], [48,189,48], [49,192,49], [50,194,50], [51,196,51], [52,198,52], [53,200,53], [54,202,54], [54,204,54], [55,206,55], [56,208,56], [57,210,57], [58,213,58], [59,215,59], [60,217,60], [61,219,61], [62,221,62], [63,223,63], [63,225,63], [64,227,64], [65,229,65], [66,231,66], [67,234,67], [68,236,68], [69,238,69], [70,240,70], [71,242,71], [72,244,72], [72,246,72], [73,248,73], [74,250,74], [75,252,75], [76,255,76], [78,0,0], [80,1,1], [82,2,2], [84,3,3], [86,4,4], [88,5,5], [91,6,6], [93,7,7], [95,8,8], [97,8,8], [99,9,9], [101,10,10], [103,11,11], [105,12,12], [107,13,13], [109,14,14], [111,15,15], [113,16,16], [115,16,16], [118,17,17], [120,18,18], [122,19,19], [124,20,20], [126,21,21], [128,22,22], [130,23,23], [132,24,24], [134,24,24], [136,25,25], [138,26,26], [140,27,27], [142,28,28], [144,29,29], [147,30,30], [149,31,31], [151,32,32], [153,32,32], [155,33,33], [157,34,34], [159,35,35], [161,36,36], [163,37,37], [165,38,38], [167,39,39], [169,40,40], [171,40,40], [174,41,41], [176,42,42], [178,43,43], [180,44,44], [182,45,45], [184,46,46], [186,47,47], [188,48,48], [190,48,48], [192,49,49], [194,50,50], [196,51,51], [198,52,52], [201,53,53], [203,54,54], [205,55,55], [207,56,56], [209,56,56], [211,57,57], [213,58,58], [215,59,59], [217,60,60], [219,61,61], [221,62,62], [223,63,63], [225,64,64], [228,64,64], [230,65,65], [232,66,66], [234,67,67], [236,68,68], [238,69,69], [240,70,70], [242,71,71], [244,72,72], [246,72,72], [248,73,73], [250,74,74], [252,75,75], [255,76,76]]
};
let cmapOptions = '';
Object.keys(cmaps).forEach(function(c) {
cmapOptions += '<option value="' + c + '">' + c + '</option>';
});
const $html = $('<div>' +
' Colormap: <select id="cmapSelect">' + cmapOptions +
' </select><br>' +
' Center: <span id="cmapCenter"></span>' +
'</div>');
const cmapUpdate = function() {
const val = $('#cmapSelect').val();
$('#cmapSelect').change(function() {
updateCallback(val);
});
return cmaps[val];
};
const spinnerSlider = new Spinner({
$element: $html.find('#cmapCenter'),
init: 128,
min: 1,
sliderMax: 254,
step: 1,
updateCallback: updateCallback
});
return {
html: $html,
getParams: function() {
return spinnerSlider.getValue();
},
getFilter: function() {
/*eslint new-cap: 0*/
return OpenSeadragon.Filters.COLORMAP(cmapUpdate(), spinnerSlider.getValue());
}
};
}
}, {
name: 'Colorize',
help: 'The adjustment range (strength) is from 0 to 100.' +
'The higher the value, the closer the colors in the ' +
'image shift towards the given adjustment color.' +
'Color values are between 0 to 255',
generate: function(updateCallback) {
const redSpinnerId = 'redSpinner-' + idIncrement;
const greenSpinnerId = 'greenSpinner-' + idIncrement;
const blueSpinnerId = 'blueSpinner-' + idIncrement;
const strengthSpinnerId = 'strengthSpinner-' + idIncrement;
/*eslint max-len: 0*/
const $html = $('<div class="wdzt-table-layout">' +
'<div class="wdzt-row-layout">' +
' <div class="wdzt-cell-layout">' +
' Red: <span id="' + redSpinnerId + '"></span>' +
' </div>' +
' <div class="wdzt-cell-layout">' +
' Green: <span id="' + greenSpinnerId + '"></span>' +
' </div>' +
' <div class="wdzt-cell-layout">' +
' Blue: <span id="' + blueSpinnerId + '"></span>' +
' </div>' +
' <div class="wdzt-cell-layout">' +
' Strength: <span id="' + strengthSpinnerId + '"></span>' +
' </div>' +
'</div>' +
'</div>');
const redSpinner = new Spinner({
$element: $html.find('#' + redSpinnerId),
init: 100,
min: 0,
max: 255,
step: 1,
updateCallback: updateCallback
});
const greenSpinner = new Spinner({
$element: $html.find('#' + greenSpinnerId),
init: 20,
min: 0,
max: 255,
step: 1,
updateCallback: updateCallback
});
const blueSpinner = new Spinner({
$element: $html.find('#' + blueSpinnerId),
init: 20,
min: 0,
max: 255,
step: 1,
updateCallback: updateCallback
});
const strengthSpinner = new Spinner({
$element: $html.find('#' + strengthSpinnerId),
init: 50,
min: 0,
max: 100,
step: 1,
updateCallback: updateCallback
});
return {
html: $html,
getParams: function() {
const red = redSpinner.getValue();
const green = greenSpinner.getValue();
const blue = blueSpinner.getValue();
const strength = strengthSpinner.getValue();
return 'R: ' + red + ' G: ' + green + ' B: ' + blue +
' S: ' + strength;
},
getFilter: function() {
const red = redSpinner.getValue();
const green = greenSpinner.getValue();
const blue = blueSpinner.getValue();
const strength = strengthSpinner.getValue();
return function(context) {
const promise = getPromiseResolver();
Caman(context.canvas, function() {
this.colorize(red, green, blue, strength);
this.render(promise.call.back);
});
return promise.promise;
};
}
};
}
}, {
name: 'Contrast',
help: 'Range is from 0 to infinity, although sane values are from 0 ' +
'to 4 or 5. Values between 0 and 1 will lessen the contrast ' +
'while values greater than 1 will increase it.',
generate: function(updateCallback) {
const $html = $('<div></div>');
const spinnerSlider = new SpinnerSlider({
$element: $html,
init: 1.3,
min: 0,
sliderMax: 4,
step: 0.1,
updateCallback: updateCallback
});
return {
html: $html,
getParams: function() {
return spinnerSlider.getValue();
},
getFilter: function() {
return OpenSeadragon.Filters.CONTRAST(
spinnerSlider.getValue());
}
};
}
}, {
name: 'Exposure',
help: 'Range is -100 to 100. Values < 0 will decrease ' +
'exposure while values > 0 will increase exposure',
generate: function(updateCallback) {
const $html = $('<div></div>');
const spinnerSlider = new SpinnerSlider({
$element: $html,
init: 10,
min: -100,
max: 100,
step: 1,
updateCallback: updateCallback
});
return {
html: $html,
getParams: function() {
return spinnerSlider.getValue();
},
getFilter: function() {
const value = spinnerSlider.getValue();
return function(context) {
const promise = getPromiseResolver();
Caman(context.canvas, function() {
this.exposure(value);
this.render(promise.call.back);
});
return promise.promise;
};
}
};
}
}, {
name: 'Gamma',
help: 'Range is from 0 to infinity, although sane values ' +
'are from 0 to 4 or 5. Values between 0 and 1 will ' +
'lessen the contrast while values greater than 1 will increase it.',
generate: function(updateCallback) {
const $html = $('<div></div>');
const spinnerSlider = new SpinnerSlider({
$element: $html,
init: 0.5,
min: 0,
sliderMax: 5,
step: 0.1,
updateCallback: updateCallback
});
return {
html: $html,
getParams: function() {
return spinnerSlider.getValue();
},
getFilter: function() {
const value = spinnerSlider.getValue();
return OpenSeadragon.Filters.GAMMA(value);
}
};
}
}, {
name: 'Hue',
help: 'hue value is between 0 to 100 representing the ' +
'percentage of Hue shift in the 0 to 360 range',
generate: function(updateCallback) {
const $html = $('<div></div>');
const spinnerSlider = new SpinnerSlider({
$element: $html,
init: 20,
min: 0,
max: 100,
step: 1,
updateCallback: updateCallback
});
return {
html: $html,
getParams: function() {
return spinnerSlider.getValue();
},
getFilter: function() {
const value = spinnerSlider.getValue();
return function(context) {
const promise = getPromiseResolver();
Caman(context.canvas, function() {
this.hue(value);
this.render(promise.call.back);
});
return promise.promise;
};
}
};
}
}, {
name: 'Saturation',
help: 'saturation value has to be between -100 and 100',
generate: function(updateCallback) {
const $html = $('<div></div>');
const spinnerSlider = new SpinnerSlider({
$element: $html,
init: 50,
min: -100,
max: 100,
step: 1,
updateCallback: updateCallback
});
return {
html: $html,
getParams: function() {
return spinnerSlider.getValue();
},
getFilter: function() {
const value = spinnerSlider.getValue();
return function(context) {
const promise = getPromiseResolver();
Caman(context.canvas, function() {
this.saturation(value);
this.render(promise.call.back);
});
return promise.promise;
};
}
};
}
}, {
name: 'Vibrance',
help: 'vibrance value has to be between -100 and 100',
generate: function(updateCallback) {
const $html = $('<div></div>');
const spinnerSlider = new SpinnerSlider({
$element: $html,
init: 50,
min: -100,
max: 100,
step: 1,
updateCallback: updateCallback
});
return {
html: $html,
getParams: function() {
return spinnerSlider.getValue();
},
getFilter: function() {
const value = spinnerSlider.getValue();
return function(context) {
const promise = getPromiseResolver();
Caman(context.canvas, function() {
this.vibrance(value);
this.render(promise.call.back);
});
return promise.promise;
};
}
};
}
}, {
name: 'Sepia',
help: 'sepia value has to be between 0 and 100',
generate: function(updateCallback) {
const $html = $('<div></div>');
const spinnerSlider = new SpinnerSlider({
$element: $html,
init: 50,
min: 0,
max: 100,
step: 1,
updateCallback: updateCallback
});
return {
html: $html,
getParams: function() {
return spinnerSlider.getValue();
},
getFilter: function() {
const value = spinnerSlider.getValue();
return function(context) {
const promise = getPromiseResolver();
Caman(context.canvas, function() {
this.sepia(value);
this.render(promise.call.back);
});
return promise.promise;
};
}
};
}
}, {
name: 'Noise',
help: 'Noise cannot be smaller than 0',
generate: function(updateCallback) {
const $html = $('<div></div>');
const spinnerSlider = new SpinnerSlider({
$element: $html,
init: 50,
min: 0,
step: 1,
updateCallback: updateCallback
});
return {
html: $html,
getParams: function() {
return spinnerSlider.getValue();
},
getFilter: function() {
const value = spinnerSlider.getValue();
return function(context) {
const promise = getPromiseResolver();
Caman(context.canvas, function() {
this.noise(value);
this.render(promise.call.back);
});
return promise.promise;
};
}
};
}
}, {
name: 'Greyscale',
generate: function() {
return {
html: '',
getParams: function() {
return '';
},
getFilter: function() {
return OpenSeadragon.Filters.GREYSCALE();
}
};
}
}, {
name: 'Sobel Edge',
generate: function() {
return {
html: '',
getParams: function() {
return '';
},
getFilter: function() {
return function(context) {
const imgData = context.getImageData(
0, 0, context.canvas.width, context.canvas.height);
const pixels = imgData.data;
const originalPixels = context.getImageData(0, 0, context.canvas.width, context.canvas.height).data;
const oneRowOffset = context.canvas.width * 4;
const onePixelOffset = 4;
let Gy, Gx, idx = 0;
for (let i = 1; i < context.canvas.height - 1; i += 1) {
idx = oneRowOffset * i + 4;
for (let j = 1; j < context.canvas.width - 1; j += 1) {
Gy = originalPixels[idx - onePixelOffset + oneRowOffset] + 2 * originalPixels[idx + oneRowOffset] + originalPixels[idx + onePixelOffset + oneRowOffset];
Gy = Gy - (originalPixels[idx - onePixelOffset - oneRowOffset] + 2 * originalPixels[idx - oneRowOffset] + originalPixels[idx + onePixelOffset - oneRowOffset]);
Gx = originalPixels[idx + onePixelOffset - oneRowOffset] + 2 * originalPixels[idx + onePixelOffset] + originalPixels[idx + onePixelOffset + oneRowOffset];
Gx = Gx - (originalPixels[idx - onePixelOffset - oneRowOffset] + 2 * originalPixels[idx - onePixelOffset] + originalPixels[idx - onePixelOffset + oneRowOffset]);
pixels[idx] = Math.sqrt(Gx * Gx + Gy * Gy); // 0.5*Math.abs(Gx) + 0.5*Math.abs(Gy);//100*Math.atan(Gy,Gx);
pixels[idx + 1] = 0;
pixels[idx + 2] = 0;
idx += 4;
}
}
context.putImageData(imgData, 0, 0);
};
}
};
}
}, {
name: 'Brightness',
help: 'Brightness must be between -255 (darker) and 255 (brighter).',
generate: function(updateCallback) {
const $html = $('<div></div>');
const spinnerSlider = new SpinnerSlider({
$element: $html,
init: 50,
min: -255,
max: 255,
step: 1,
updateCallback: updateCallback
});
return {
html: $html,
getParams: function() {
return spinnerSlider.getValue();
},
getFilter: function() {
return OpenSeadragon.Filters.BRIGHTNESS(
spinnerSlider.getValue());
}
};
}
}, {
name: 'Erosion',
help: 'The erosion kernel size must be an odd number.',
generate: function(updateCallback) {
const $html = $('<div></div>');
const spinner = new Spinner({
$element: $html,
init: 3,
min: 3,
step: 2,
updateCallback: updateCallback
});
return {
html: $html,
getParams: function() {
return spinner.getValue();
},
getFilter: function() {
return OpenSeadragon.Filters.MORPHOLOGICAL_OPERATION(
spinner.getValue(), Math.min);
}
};
}
}, {
name: 'Dilation',
help: 'The dilation kernel size must be an odd number.',
generate: function(updateCallback) {
const $html = $('<div></div>');
const spinner = new Spinner({
$element: $html,
init: 3,
min: 3,
step: 2,
updateCallback: updateCallback
});
return {
html: $html,
getParams: function() {
return spinner.getValue();
},
getFilter: function() {
return OpenSeadragon.Filters.MORPHOLOGICAL_OPERATION(
spinner.getValue(), Math.max);
}
};
}
}, {
name: 'Thresholding',
help: 'The threshold must be between 0 and 255.',
generate: function(updateCallback) {
const $html = $('<div></div>');
const spinnerSlider = new SpinnerSlider({
$element: $html,
init: 127,
min: 0,
max: 255,
step: 1,
updateCallback: updateCallback
});
return {
html: $html,
getParams: function() {
return spinnerSlider.getValue();
},
getFilter: function() {
return OpenSeadragon.Filters.THRESHOLDING(
spinnerSlider.getValue());
}
};
}
}];
availableFilters.sort(function(f1, f2) {
return f1.name.localeCompare(f2.name);
});
let idIncrement = 0;
const hashTable = {};
availableFilters.forEach(function(filter) {
const $li = $('<li></li>');
const $plus = $('<img src="static/plus.png" alt="+" class="button">');
$li.append($plus);
$li.append(filter.name);
$li.appendTo($('#available'));
$plus.click(function() {
const id = 'selected_' + idIncrement++;
const generatedFilter = filter.generate(updateFilters);
hashTable[id] = {
name: filter.name,
generatedFilter: generatedFilter
};
const $li = $('<li id="' + id + '"><div class="wdzt-table-layout"><div class="wdzt-row-layout"></div></div></li>');
const $minus = $('<div class="wdzt-cell-layout"><img src="static/minus.png" alt="-" class="button"></div>');
$li.find('.wdzt-row-layout').append($minus);
$li.find('.wdzt-row-layout').append('<div class="wdzt-cell-layout filterLabel">' + filter.name + '</div>');
if (filter.help) {
const $help = $('<div class="wdzt-cell-layout"><span title="' + filter.help + '">&nbsp;?&emsp;</span></div>');
$help.tooltip();
$li.find('.wdzt-row-layout').append($help);
}
$li.find('.wdzt-row-layout').append(
$('<div class="wdzt-cell-layout wdzt-full-width"></div>')
.append(generatedFilter.html));
$minus.click(function() {
delete hashTable[id];
$li.remove();
updateFilters();
});
$li.appendTo($('#selected'));
updateFilters();
});
});
$('#selected').sortable({
containment: 'parent',
axis: 'y',
tolerance: 'pointer',
update: updateFilters
});
function getPromiseResolver() {
let call = {};
let promise = new OpenSeadragon.Promise(resolve => {
call.back = resolve;
});
return {call, promise};
}
function updateFilters() {
const filters = [];
$('#selected li').each(function() {
const id = this.id;
const filter = hashTable[id];
filters.push(filter.generatedFilter.getFilter());
});
viewer.setFilterOptions({
filters: {
processors: filters
}
});
}
window.debugCache = function () {
for (let cacheKey in viewer.tileCache._cachesLoaded) {
let cache = viewer.tileCache._cachesLoaded[cacheKey];
if (!cache.loaded) {
console.log(cacheKey, "skipping...");
}
if (cache.type === "context2d") {
console.log(cacheKey, cache.data.canvas.width, cache.data.canvas.height);
} else {
console.log(cacheKey, cache.data);
}
}
}
// Monitoring of tiles:
let monitoredTile = null;
async function updateCanvas(node, tile, targetCacheKey) {
const data = await tile.getCache(targetCacheKey)?.getDataAs('context2d', true);
if (!data) {
const text = document.createElement("span");
text.innerHTML = targetCacheKey + "<br> empty";
node.replaceChildren(text);
} else {
node.replaceChildren(data.canvas);
}
}
async function processTile(tile) {
console.log("Selected tile", tile);
await Promise.all([
updateCanvas(document.getElementById("tile-original"), tile, tile.originalCacheKey),
updateCanvas(document.getElementById("tile-main"), tile, tile.cacheKey),
]);
}
viewer.addHandler('tile-invalidated', async event => {
if (event.tile === monitoredTile) {
await processTile(monitoredTile);
}
}, null, -Infinity); // as a last handler
// When testing code, you can call in OSD $.debugTile(message, tile) and it will log only for selected tiles on the canvas
OpenSeadragon.debugTile = function (msg, t) {
if (monitoredTile && monitoredTile.x === t.x && monitoredTile.y === t.y && monitoredTile.level === t.level) {
console.log(msg, t);
}
}
viewer.addHandler("canvas-release", e => {
const tiledImage = viewer.world.getItemAt(viewer.world.getItemCount()-1);
if (!tiledImage) {
monitoredTile = null;
return;
}
const position = viewer.viewport.windowToViewportCoordinates(e.position);
let tiles = tiledImage._lastDrawn;
for (let i = 0; i < tiles.length; i++) {
if (tiles[i].tile.bounds.containsPoint(position)) {
monitoredTile = tiles[i].tile;
return processTile(monitoredTile);
}
}
monitoredTile = null;
});

View file

@ -0,0 +1,82 @@
<!DOCTYPE html>
<!--
/*
* Modified and maintained by the OpenSeadragon Community.
*
* This software was orignally developed at the National Institute of Standards and
* Technology by employees of the Federal Government. NIST assumes
* no responsibility whatsoever for its use by other parties, and makes no
* guarantees, expressed or implied, about its quality, reliability, or
* any other characteristic.
*/
-->
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OpenSeadragon Filtering Plugin Demo</title>
<script type="text/javascript" src='/build/openseadragon/openseadragon.js'></script>
<!-- JQuery -->
<script src="/test/lib/jquery-1.9.1.min.js"></script>
<link rel="stylesheet" href="/test/lib/jquery-ui-1.10.2/css/smoothness/jquery-ui-1.10.2.min.css">
<script src="/test/lib/jquery-ui-1.10.2/js/jquery-ui-1.10.2.min.js"></script>
<!-- Local -->
<link rel="stylesheet" href="style.css">
<script src="plugin.js"></script>
<script src="/test/helpers/drawer-switcher.js"></script>
<!-- Thirdparty -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/camanjs/4.1.2/caman.full.min.js"></script>
</head>
<body>
<section class="home home-title" id="title-banner">
<h1>OpenSeadragon filtering plugin demo: <span id="title-drawer"></span></h1>
<p>You might want to check the plugin repository to see if the plugin code is up to date.</p>
</section>
<section class="demo">
<div class="wdzt-table-layout wdzt-full-width">
<div class="wdzt-row-layout">
<div class="wdzt-cell-layout column-2">
<div id="openseadragon"></div>
</div>
<div class="wdzt-cell-layout column-2">
<select id="image-select">
</select>
<h3>Available filters</h3>
<ul id="available">
</ul>
<h3>Selected filters</h3>
<ul id="selected"></ul>
<p>Drag and drop the selected filters to set their order.</p>
<br>
<label>
<input type="checkbox" onchange="viewer.setDebugMode(this.checked);">
Debug mode
</label>
</div>
</div>
</div>
</section>
<section class="monitoring">
Monitoring of a tile lifecycle: (use filters and click on a tile to start monitoring)
<div style="display: flex">
<div id="tile-original"></div>
<div id="tile-main"></div>
</div>
</section>
<script src="demo.js"></script>
</body>
</html>

View file

@ -0,0 +1,337 @@
/*
* Modified and maintained by the OpenSeadragon Community.
*
* This software was orignally developed at the National Institute of Standards and
* Technology by employees of the Federal Government. NIST assumes
* no responsibility whatsoever for its use by other parties, and makes no
* guarantees, expressed or implied, about its quality, reliability, or
* any other characteristic.
* @author Antoine Vandecreme <antoine.vandecreme@nist.gov>
*/
(function() {
'use strict';
const $ = window.OpenSeadragon;
if (!$) {
throw new Error('OpenSeadragon is missing.');
}
$.Viewer.prototype.setFilterOptions = function(options) {
if (!this.filterPluginInstance) {
options = options || {};
options.viewer = this;
this.filterPluginInstance = new $.FilterPlugin(options);
} else {
setOptions(this.filterPluginInstance, options);
}
};
/**
* @class FilterPlugin
* @param {Object} options The options
* @param {OpenSeadragon.Viewer} options.viewer The viewer to attach this
* plugin to.
* @param {Object[]} options.filters The filters to apply to the images.
* @param {OpenSeadragon.TiledImage[]} options.filters[x].items The tiled images
* on which to apply the filter.
* @param {function|function[]} options.filters[x].processors The processing
* function(s) to apply to the images. The parameter of this function is
* the context to modify.
*/
$.FilterPlugin = function(options) {
options = options || {};
if (!options.viewer) {
throw new Error('A viewer must be specified.');
}
const self = this;
this.viewer = options.viewer;
this.viewer.addHandler('tile-invalidated', applyFilters);
setOptions(this, options);
async function applyFilters(e) {
const tiledImage = e.tiledImage,
processors = getFiltersProcessors(self, tiledImage);
if (processors.length === 0) {
return;
}
const contextCopy = await e.getData('context2d');
if (!contextCopy) return;
for (let i = 0; i < processors.length; i++) {
if (e.outdated()) return;
await processors[i](contextCopy);
}
if (e.outdated()) return;
await e.setData(contextCopy, 'context2d');
}
};
function setOptions(instance, options) {
options = options || {};
const filters = options.filters;
instance.filters = !filters ? [] :
$.isArray(filters) ? filters : [filters];
for (let i = 0; i < instance.filters.length; i++) {
const filter = instance.filters[i];
if (!filter.processors) {
throw new Error('Filter processors must be specified.');
}
filter.processors = $.isArray(filter.processors) ?
filter.processors : [filter.processors];
}
instance.viewer.requestInvalidate();
}
function getFiltersProcessors(instance, item) {
if (instance.filters.length === 0) {
return [];
}
let globalProcessors = null;
for (let i = 0; i < instance.filters.length; i++) {
const filter = instance.filters[i];
if (!filter.items) {
globalProcessors = filter.processors;
} else if (filter.items === item ||
$.isArray(filter.items) && filter.items.indexOf(item) >= 0) {
return filter.processors;
}
}
return globalProcessors ? globalProcessors : [];
}
$.Filters = {
THRESHOLDING: function(threshold) {
if (threshold < 0 || threshold > 255) {
throw new Error('Threshold must be between 0 and 255.');
}
return function(context) {
const imgData = context.getImageData(
0, 0, context.canvas.width, context.canvas.height);
const pixels = imgData.data;
for (let i = 0; i < pixels.length; i += 4) {
const r = pixels[i];
const g = pixels[i + 1];
const b = pixels[i + 2];
const v = (r + g + b) / 3;
pixels[i] = pixels[i + 1] = pixels[i + 2] =
v < threshold ? 0 : 255;
}
context.putImageData(imgData, 0, 0);
};
},
BRIGHTNESS: function(adjustment) {
if (adjustment < -255 || adjustment > 255) {
throw new Error(
'Brightness adjustment must be between -255 and 255.');
}
const precomputedBrightness = [];
for (let i = 0; i < 256; i++) {
precomputedBrightness[i] = i + adjustment;
}
return function(context) {
const imgData = context.getImageData(
0, 0, context.canvas.width, context.canvas.height);
const pixels = imgData.data;
for (let i = 0; i < pixels.length; i += 4) {
pixels[i] = precomputedBrightness[pixels[i]];
pixels[i + 1] = precomputedBrightness[pixels[i + 1]];
pixels[i + 2] = precomputedBrightness[pixels[i + 2]];
}
context.putImageData(imgData, 0, 0);
};
},
CONTRAST: function(adjustment) {
if (adjustment < 0) {
throw new Error('Contrast adjustment must be positive.');
}
const precomputedContrast = [];
for (let i = 0; i < 256; i++) {
precomputedContrast[i] = i * adjustment;
}
return function(context) {
const imgData = context.getImageData(
0, 0, context.canvas.width, context.canvas.height);
const pixels = imgData.data;
for (let i = 0; i < pixels.length; i += 4) {
pixels[i] = precomputedContrast[pixels[i]];
pixels[i + 1] = precomputedContrast[pixels[i + 1]];
pixels[i + 2] = precomputedContrast[pixels[i + 2]];
}
context.putImageData(imgData, 0, 0);
};
},
GAMMA: function(adjustment) {
if (adjustment < 0) {
throw new Error('Gamma adjustment must be positive.');
}
const precomputedGamma = [];
for (let i = 0; i < 256; i++) {
precomputedGamma[i] = Math.pow(i / 255, adjustment) * 255;
}
return function(context) {
const imgData = context.getImageData(
0, 0, context.canvas.width, context.canvas.height);
const pixels = imgData.data;
for (let i = 0; i < pixels.length; i += 4) {
pixels[i] = precomputedGamma[pixels[i]];
pixels[i + 1] = precomputedGamma[pixels[i + 1]];
pixels[i + 2] = precomputedGamma[pixels[i + 2]];
}
context.putImageData(imgData, 0, 0);
};
},
GREYSCALE: function() {
return function(context) {
const imgData = context.getImageData(
0, 0, context.canvas.width, context.canvas.height);
const pixels = imgData.data;
for (let i = 0; i < pixels.length; i += 4) {
const val = (pixels[i] + pixels[i + 1] + pixels[i + 2]) / 3;
pixels[i] = val;
pixels[i + 1] = val;
pixels[i + 2] = val;
}
context.putImageData(imgData, 0, 0);
};
},
INVERT: function() {
const precomputedInvert = [];
for (let i = 0; i < 256; i++) {
precomputedInvert[i] = 255 - i;
}
return function(context) {
const imgData = context.getImageData(
0, 0, context.canvas.width, context.canvas.height);
const pixels = imgData.data;
for (let i = 0; i < pixels.length; i += 4) {
pixels[i] = precomputedInvert[pixels[i]];
pixels[i + 1] = precomputedInvert[pixels[i + 1]];
pixels[i + 2] = precomputedInvert[pixels[i + 2]];
}
context.putImageData(imgData, 0, 0);
};
},
MORPHOLOGICAL_OPERATION: function(kernelSize, comparator) {
if (kernelSize % 2 === 0) {
throw new Error('The kernel size must be an odd number.');
}
const kernelHalfSize = Math.floor(kernelSize / 2);
if (!comparator) {
throw new Error('A comparator must be defined.');
}
return function(context) {
const width = context.canvas.width;
const height = context.canvas.height;
const imgData = context.getImageData(0, 0, width, height);
const originalPixels = context.getImageData(0, 0, width, height)
.data;
let offset;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
offset = (y * width + x) * 4;
let r = originalPixels[offset],
g = originalPixels[offset + 1],
b = originalPixels[offset + 2];
for (let j = 0; j < kernelSize; j++) {
for (let i = 0; i < kernelSize; i++) {
const pixelX = x + i - kernelHalfSize;
const pixelY = y + j - kernelHalfSize;
if (pixelX >= 0 && pixelX < width &&
pixelY >= 0 && pixelY < height) {
offset = (pixelY * width + pixelX) * 4;
r = comparator(originalPixels[offset], r);
g = comparator(
originalPixels[offset + 1], g);
b = comparator(
originalPixels[offset + 2], b);
}
}
}
imgData.data[offset] = r;
imgData.data[offset + 1] = g;
imgData.data[offset + 2] = b;
}
}
context.putImageData(imgData, 0, 0);
};
},
CONVOLUTION: function(kernel) {
if (!$.isArray(kernel)) {
throw new Error('The kernel must be an array.');
}
const kernelSize = Math.sqrt(kernel.length);
if ((kernelSize + 1) % 2 !== 0) {
throw new Error('The kernel must be a square matrix with odd' +
'width and height.');
}
const kernelHalfSize = (kernelSize - 1) / 2;
return function(context) {
const width = context.canvas.width;
const height = context.canvas.height;
const imgData = context.getImageData(0, 0, width, height);
const originalPixels = context.getImageData(0, 0, width, height)
.data;
let offset;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let r = 0, g = 0, b = 0;
for (let j = 0; j < kernelSize; j++) {
for (let i = 0; i < kernelSize; i++) {
const pixelX = x + i - kernelHalfSize;
const pixelY = y + j - kernelHalfSize;
if (pixelX >= 0 && pixelX < width &&
pixelY >= 0 && pixelY < height) {
offset = (pixelY * width + pixelX) * 4;
const weight = kernel[j * kernelSize + i];
r += originalPixels[offset] * weight;
g += originalPixels[offset + 1] * weight;
b += originalPixels[offset + 2] * weight;
}
}
}
offset = (y * width + x) * 4;
imgData.data[offset] = r;
imgData.data[offset + 1] = g;
imgData.data[offset + 2] = b;
}
}
context.putImageData(imgData, 0, 0);
};
},
COLORMAP: function(cmap, ctr) {
const resampledCmap = cmap.slice(0);
const diff = 255 - ctr;
for (let i = 0; i < 256; i++) {
let position = i > ctr ?
Math.min((i - ctr) / diff * 128 + 128,255) | 0 :
Math.max(0, i / (ctr / 128)) | 0;
resampledCmap[i] = cmap[position];
}
return function(context) {
const imgData = context.getImageData(
0, 0, context.canvas.width, context.canvas.height);
const pxl = imgData.data;
for (let i = 0; i < pxl.length; i += 4) {
const v = (pxl[i] + pxl[i + 1] + pxl[i + 2]) / 3 | 0;
const c = resampledCmap[v];
pxl[i] = c[0];
pxl[i + 1] = c[1];
pxl[i + 2] = c[2];
}
context.putImageData(imgData, 0, 0);
};
}
};
}());

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

View file

@ -0,0 +1,81 @@
/*
* Modified and maintained by the OpenSeadragon Community.
*
* This software was orignally developed at the National Institute of Standards and
* Technology by employees of the Federal Government. NIST assumes
* no responsibility whatsoever for its use by other parties, and makes no
* guarantees, expressed or implied, about its quality, reliability, or
* any other characteristic.
*/
.demo {
line-height: normal;
}
.demo h3 {
margin-top: 5px;
margin-bottom: 5px;
}
#openseadragon {
width: 100%;
height: 700px;
background-color: black;
}
.wdzt-table-layout {
display: table;
}
.wdzt-row-layout {
display: table-row;
}
.wdzt-cell-layout {
display: table-cell;
}
.wdzt-full-width {
width: 100%;
}
.wdzt-menu-slider {
margin-left: 10px;
margin-right: 10px;
}
.column-2 {
width: 50%;
vertical-align: top;
padding: 3px;
}
#available {
list-style-type: none;
}
ul {
padding: 0;
border: 1px solid black;
min-height: 25px;
}
li {
padding: 3px;
}
#selected {
list-style-type: none;
}
.button {
cursor: pointer;
vertical-align: text-top;
}
.filterLabel {
min-width: 120px;
}
#selected .filterLabel {
cursor: move;
}

View file

@ -59,7 +59,8 @@
this.viewer = OpenSeadragon({
id: "contentDiv",
prefixUrl: "../../build/openseadragon/images/",
tileSources: tileSources
tileSources: tileSources,
crossOriginPolicy: 'Anonymous',
});
this.viewer.addHandler('open', function() {

View file

@ -109,14 +109,14 @@
opacity: getOpacity( layerName )
};
var addLayerHandler = function( event ) {
if ( event.options === options ) {
viewer.removeHandler( "add-layer", addLayerHandler );
layers[layerName] = event.drawer;
if ( event.item.source.levels[0].url.includes(layerName) ) {
viewer.world.removeHandler( "add-item", addLayerHandler );
layers[layerName] = event.item;
updateOrder();
}
};
viewer.addHandler( "add-layer", addLayerHandler );
viewer.addLayer( options );
viewer.world.addHandler( "add-item", addLayerHandler );
viewer.addTiledImage( options );
}
function left() {
@ -146,13 +146,15 @@
}
function updateOrder() {
var nbLayers = viewer.getLayersCount();
var nbLayers = viewer.world.getItemCount();
if ( nbLayers < 2 ) {
return;
}
$.each( $( "#used select option" ), function( index, value ) {
var layer = value.innerHTML;
viewer.setLayerLevel( layers[layer], nbLayers -1 - index );
if (layers[layer]) {
viewer.world.setItemIndex( layers[layer], nbLayers -1 - index );
}
} );
}

View file

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<title>OpenSeadragon maxTilesPerFrame Demo</title>
<script type="text/javascript" src='../../build/openseadragon/openseadragon.js'></script>
<script type="text/javascript" src='../lib/jquery-1.9.1.min.js'></script>
<style type="text/css">
.openseadragon1 {
width: 100vw;
height: 100vh;
}
</style>
</head>
<body>
<div>
Simple demo page to show an OpenSeadragon viewer with a higher maxTilesPerFrame.
</div>
<div id="contentDiv" class="openseadragon1"></div>
<script type="text/javascript">
var viewer = OpenSeadragon({
debugMode: true,
id: "contentDiv",
prefixUrl: "../../build/openseadragon/images/",
tileSources: "https://openseadragon.github.io/example-images/duomo/duomo.dzi",
showNavigator:true,
crossOriginPolicy: 'Anonymous',
maxTilesPerFrame:3,
});
</script>
</body>
</html>

View file

@ -26,7 +26,8 @@
prefixUrl: "../../build/openseadragon/images/",
tileSources: "../data/testpattern.dzi",
minZoomImageRatio: 0,
maxZoomPixelRatio: 10
maxZoomPixelRatio: 10,
overlayPreserveContentDirection: true // Change this to false to test overlay content flipping
});
viewer.addHandler("open", function(event) {
@ -42,6 +43,19 @@
rotationMode: OpenSeadragon.OverlayRotationMode.BOUNDING_BOX
});
// test with image of letter B to see that images flip too
elt = document.createElement("div");
elt.className = "runtime-overlay";
elt.style.outline = "1px solid blue";
elt.style.height = "100px";
elt.innerHTML = "<div><img src='../data/BBlue.png' width='100%' height='100%'/></div>"
viewer.addOverlay({
element: elt,
location: new OpenSeadragon.Point(0.0, 0.0),
width: 0.1,
height: 0.1
});
elt = document.createElement("div");
elt.className = "runtime-overlay";
elt.style.background = "white";
@ -96,4 +110,4 @@
</script>
</body>
</html>
</html>

View file

@ -0,0 +1,143 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OpenSeadragon Filtering Plugin Demo</title>
<script type="text/javascript" src='/build/openseadragon/openseadragon.js'></script>
<style>
textarea {
width: 900px;
padding: 10px;
font-size: 16px;
font-family: 'Courier New', Courier, monospace;
border: 1px solid #ccc;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
background-color: #fff;
resize: vertical;
display: block;
max-width: 90%;
}
button {
margin-top: 10px;
padding: 10px 20px;
font-size: 16px;
color: #fff;
background-color: #007bff;
border: none;
border-radius: 5px;
cursor: pointer;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
button:hover {
background-color: #0056b3;
display: block;
}
</style>
<script>
let _pA, _pB;
function executeScript() {
const scriptContent = document.getElementById("scriptInput").value;
try {
eval(scriptContent);
if (! (typeof window.pluginA === "function") || ! (typeof window.pluginB === "function")) {
alert("pluginA and pluginB functions must be defined!");
return;
}
if (_pA) {
viewer.removeHandler('tile-invalidated', _pA);
}
if (_pB) {
viewer.removeHandler('tile-invalidated', _pB);
}
_pA = window.pluginA;
_pB = window.pluginB;
viewer.addHandler('tile-invalidated', _pA, null, window.orderPluginA || 0);
viewer.addHandler('tile-invalidated', _pB, null, window.orderPluginB || 0);
viewer.requestInvalidate();
} catch (error) {
alert("Error executing script: " + error.message);
}
}
</script>
<!-- JQuery -->
<script src="/test/lib/jquery-1.9.1.min.js"></script>
<link rel="stylesheet" href="/test/lib/jquery-ui-1.10.2/css/smoothness/jquery-ui-1.10.2.min.css">
<script src="/test/lib/jquery-ui-1.10.2/js/jquery-ui-1.10.2.min.js"></script>
</head>
<body>
<section class="home home-title" id="title-banner">
<h1>OpenSeadragon plugin demo</h1>
<p>You should see two plugins interacting. You can change the order of plugins and the logics!</p>
</section>
<div style="display: flex; flex-direction: row; flex-wrap: wrap;">
<section class="demo" id="demo" style="width: 800px; height: 600px"></section>
<div>
<textarea id="scriptInput" rows="25" cols="120" placeholder="" style="height: 470px">
// window.pluginA must be defined! draw small gradient square
window.pluginA = async function(e) {
const ctx = await e.getData('context2d');
if (ctx) {
const gradient = ctx.createLinearGradient(0, 0, 50, 50);
gradient.addColorStop(0, 'blue');
gradient.addColorStop(0.5, 'green');
gradient.addColorStop(1, 'red');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 50, 50);
await e.setData(ctx, 'context2d');
}
};
// window.pluginB must be defined! overlay with color opacity 40%
window.pluginB = async function(e) {
const ctx = await e.getData('context2d');
if (ctx) {
const canvas = ctx.canvas;
ctx.fillStyle = "rgba(156, 0, 26, 0.4)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
await e.setData(ctx, 'context2d');
}
};
// higher number = earlier execution
window.orderPluginA = 1;
window.orderPluginB = 0;
</textarea>
<textarea style="height: 120px; background-color: #e5e5e5" disabled>
// Application of the plugins done automatically:
viewer.addHandler('tile-invalidated', window.pluginA, null, window.orderPluginA);
viewer.addHandler('tile-invalidated', window.pluginB, null, window.orderPluginB);
viewer.requestInvalidate();
</textarea>
<button onclick="executeScript()">Apply</button>
</div>
</div>
<script>
const viewer = window.viewer = OpenSeadragon({
id: "demo",
prefixUrl: "../../build/openseadragon/images/",
tileSources: "https://openseadragon.github.io/example-images/duomo/duomo.dzi",
drawer: 'webgl',
crossOriginPolicy: 'Anonymous',
wrapHorizontal: true,
showNavigator: true,
});
</script>
</body>
</html>

View file

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html>
<head>
<title>OpenSeadragon Basic Demo</title>
<script type="text/javascript" src='../../build/openseadragon/openseadragon.js'></script>
<script type="text/javascript" src='../lib/jquery-1.9.1.min.js'></script>
<style type="text/css">
.openseadragon1 {
width: 800px;
height: 600px;
}
</style>
</head>
<body>
<div>
Simple demo page. The navigator region is expected to rotate when the image is rotated.
The default behaviour is to keep the region still as the image below it rotates.
</div>
<div id="contentDiv" class="openseadragon1"></div>
<script type="text/javascript">
var viewer = OpenSeadragon({
// debugMode: true,
id: "contentDiv",
prefixUrl: "../../build/openseadragon/images/",
tileSources: "../data/testpattern.dzi",
showNavigator: true,
navigatorRotate: false
});
viewer.viewport.setRotation(45)
</script>
</body>
</html>

87
test/demo/rstats.css Normal file
View file

@ -0,0 +1,87 @@
.alarm{
color: #b70000;
text-shadow: 0 0 0 #b70000,
0 0 1px #fff,
0 0 1px #fff,
0 0 2px #fff,
0 0 2px #fff,
0 0 3px #fff,
0 0 3px #fff,
0 0 4px #fff,
0 0 4px #fff;
}
.rs-base{
position: absolute;
z-index: 10000;
padding: 10px;
background-color: #222;
font-size: 10px;
line-height: 1.2em;
width: 350px;
font-family: 'Roboto Condensed', tahoma, sans-serif;
left: 0;
top: 0;
overflow: hidden;
color:white;
}
.rs-base h1{
margin: 0;
padding: 0;
font-size: 1.4em;
color: #fff;
margin-bottom: 5px;
cursor: pointer;
}
.rs-base div.rs-group{
margin-bottom: 10px;
}
.rs-base div.rs-group.hidden{
display: none;
}
.rs-base div.rs-fraction{
position: relative;
margin-bottom: 5px;
}
.rs-base div.rs-fraction p{
width: 120px;
text-align: right;
margin: 0;
padding: 0;
}
.rs-base div.rs-legend{
position: absolute;
line-height: 1em;
}
.rs-base div.rs-counter-base{
position: relative;
margin: 2px 0;
height: 1em;
}
.rs-base span.rs-counter-id{
position: absolute;
left: 0;
top: 0;
}
.rs-base div.rs-counter-value{
position: absolute;
left: 90px;
width: 30px;
height: 1em;
top: 0;
text-align: right;
}
.rs-base canvas.rs-canvas{
position: absolute;
right: 0;
}

View file

@ -32,17 +32,18 @@
type: 'legacy-image-pyramid',
levels: [
{
url: 'http://openseadragon.github.io/example-images/duomo/duomo_files/8/0_0.jpg',
url: 'https://openseadragon.github.io/example-images/duomo/duomo_files/8/0_0.jpg',
width: 218,
height: 160
}
]
],
degrees: 30,
};
var duomo = {
Image: {
xmlns: 'http://schemas.microsoft.com/deepzoom/2008',
Url: 'http://openseadragon.github.io/example-images/duomo/duomo_files/',
Url: 'https://openseadragon.github.io/example-images/duomo/duomo_files/',
Format: 'jpg',
Overlap: '2',
TileSize: '256',
@ -59,11 +60,14 @@
tileSources: duomoStandin,
minZoomImageRatio: 0.1,
defaultZoomLevel: 0.1,
zoomPerClick: 1
zoomPerClick: 1,
crossOriginPolicy: 'Anonymous'
});
let swapped = false;
viewer.addHandler('canvas-click', function(event) {
if (event.quick) {
if (event.quick && !swapped) {
swapped = true;
var standin = viewer.world.getItemAt(0);
var standinBounds = standin.getBounds();
viewer.viewport.fitBounds(standinBounds);
@ -76,16 +80,13 @@
index: 0, // Add the new image below the stand-in.
success: function(event) {
var fullImage = event.item;
// The changeover will look better if we wait for the first tile to be drawn.
// The changeover will look better if we wait for the first draw after the changeover.
var tileDrawnHandler = function(event) {
if (event.tiledImage === fullImage) {
viewer.removeHandler('tile-drawn', tileDrawnHandler);
viewer.removeHandler('update-viewport', tileDrawnHandler);
viewer.world.removeItem(standin);
}
};
viewer.addHandler('tile-drawn', tileDrawnHandler);
viewer.addHandler('update-viewport', tileDrawnHandler);
}
});
}

View file

@ -25,8 +25,9 @@
// debugMode: true,
id: "contentDiv",
prefixUrl: "../../build/openseadragon/images/",
tileSources: "http://wellcomelibrary.org/iiif-img/b11768265-0/a6801943-b8b4-4674-908c-7d5b27e70569/info.json",
tileSources: "https://openseadragon.github.io/example-images/highsmith/highsmith.dzi",
showNavigator:true,
crossOriginPolicy: 'Anonymous',
timeout: 0
});

View file

@ -25,8 +25,9 @@
// debugMode: true,
id: "contentDiv",
prefixUrl: "../../build/openseadragon/images/",
tileSources: "http://wellcomelibrary.org/iiif-img/b11768265-0/a6801943-b8b4-4674-908c-7d5b27e70569/info.json",
tileSources: "https://openseadragon.github.io/example-images/highsmith/highsmith.dzi",
showNavigator:true,
crossOriginPolicy: 'Anonymous',
timeout: 1000 * 60 * 60 * 24
});

View file

@ -0,0 +1,76 @@
/**
* Ability to switch between different drawers.
* Usage: with two viewers, we would do
*
* const switcher = new DrawerSwitcher();
* switcher.addDrawerOption("drawer_left", "Select drawer for the left viewer", "canvas");
* switcher.addDrawerOption("drawer_right", "Select drawer for the right viewer", "webgl");
* const viewer1 = window.viewer1 = new OpenSeadragon({
* id: 'openseadragon',
* ...
* drawer:switcher.activeImplementation("drawer_left"),
* });
* $("#my-title-for-left-drawer").html(`Viewer using drawer ${switcher.activeName("drawer_left")}`);
* $("#container").html(switcher.render());
* // OR switcher.render("#container")
* // ..do the same for the second viewer
*/
class DrawerSwitcher {
url = new URL(window.location.href);
drawers = {
canvas: "Context2d drawer (default in OSD &lt;= 4.1.0)",
webgl: "New WebGL drawer"
};
_data = {}
addDrawerOption(urlQueryName, title="Select drawer:", defaultDrawerImplementation="canvas") {
const drawer = this.url.searchParams.get(urlQueryName) || defaultDrawerImplementation;
if (!this.drawers[drawer]) throw "Unsupported drawer implementation: " + drawer;
let context = this._data[urlQueryName] = {
query: urlQueryName,
implementation: drawer,
title: title
};
}
activeName(urlQueryName) {
return this.drawers[this.activeImplementation(urlQueryName)];
}
activeImplementation(urlQueryName) {
return this._data[urlQueryName].implementation;
}
_getFormData(useNewline=true) {
return Object.values(this._data).map(ctx => `${ctx.title}&nbsp;
<select name="${ctx.query}">
${Object.entries(this.drawers).map(([k, v]) => {
const selected = ctx.implementation === k ? "selected" : "";
return `<option value="${k}" ${selected}>${v}</option>`;
}).join("\n")}
</select>`).join(useNewline ? "<br>" : "");
}
_preserveOtherSeachParams() {
let res = [], registered = Object.keys(this._data);
for (let [k, v] of this.url.searchParams.entries()) {
if (!registered.includes(k)) {
res.push(`<input name="${k}" type="hidden" value=${v} />`);
}
}
return res.join('\n');
}
render(selector, useNewline=undefined) {
useNewline = typeof useNewline === "boolean" ? useNewline : Object.keys(this._data).length > 1;
const html = `<div>
<form method="get">
${this._preserveOtherSeachParams()}
${this._getFormData()}${useNewline ? "<br>":""}<button>Submit</button>
</form>
</div>`;
if (selector) $(selector).append(html);
return html;
}
}

View file

@ -1,4 +1,4 @@
(function($, undefined) {
(function($) {
/**
* Plugin to force OpenSeadragon to use the legacy mouse pointer event model

95
test/helpers/mocks.js Normal file
View file

@ -0,0 +1,95 @@
// Test-wide mocks for more test stability: tests might require calling functions that expect
// presence of certain mock properties. It is better to include maintened mock props than to copy
// over all the place
window.MockSeadragon = {
/**
* Get mocked tile: loaded state, cutoff such that it is not kept in cache by force,
* level: 1, x: 0, y: 0, all coords: [x0 y0 w0 h0]
*
* Requires TiledImage referece (mock or real)
* @return {OpenSeadragon.Tile}
*/
getTile(url, tiledImage, props={}) {
const dummyRect = new OpenSeadragon.Rect(0, 0, 0, 0, 0);
//default cutoof = 0 --> use level 1 to not to keep caches from unloading (cutoff = navigator data, kept in cache)
const dummyTile = new OpenSeadragon.Tile(1, 0, 0, dummyRect, true, url,
undefined, true, null, dummyRect, null, url);
dummyTile.tiledImage = tiledImage;
//by default set as ready
dummyTile.loaded = true;
dummyTile.loading = false;
//override anything we need
OpenSeadragon.extend(tiledImage, props);
return dummyTile;
},
/**
* Get mocked viewer: it has not all props that might be required. If your
* tests fails because they do not find some props on a viewer, add them here.
*
* Requires a drawer reference (mock or real). Automatically created if not provided.
* @return {OpenSeadragon.Viewer}
*/
getViewer(drawer=null, props={}) {
drawer = drawer || this.getDrawer();
return OpenSeadragon.extend(new class extends OpenSeadragon.EventSource {
forceRedraw () {}
drawer = drawer
tileCache = new OpenSeadragon.TileCache()
}, props);
},
/**
* Get mocked viewer: it has not all props that might be required. If your
* tests fails because they do not find some props on a viewer, add them here.
* @return {OpenSeadragon.Viewer}
*/
getDrawer(props={}) {
return OpenSeadragon.extend({
getType: function () {
return "mock";
}
}, props);
},
/**
* Get mocked tiled image: it has not all props that might be required. If your
* tests fails because they do not find some props on a tiled image, add them here.
*
* Requires viewer reference (mock or real). Automatically created if not provided.
* @return {OpenSeadragon.TiledImage}
*/
getTiledImage(viewer=null, props={}) {
viewer = viewer || this.getViewer();
return OpenSeadragon.extend({
viewer: viewer,
source: OpenSeadragon.TileSource.prototype,
redraw: function() {},
_tileCache: viewer.tileCache
}, props);
},
/**
* Get mocked tile source
* @return {OpenSeadragon.TileSource}
*/
getTileSource(props={}) {
return new OpenSeadragon.TileSource(OpenSeadragon.extend({
width: 1500,
height: 1000,
tileWidth: 200,
tileHeight: 150,
tileOverlap: 0
}, props));
},
/**
* Get mocked cache record
* @return {OpenSeadragon.CacheRecord}
*/
getCacheRecord(props={}) {
return OpenSeadragon.extend(new OpenSeadragon.CacheRecord(), props);
}
};

View file

@ -1,4 +1,4 @@
/* global $, Util */
/* global $, QUnit, Util */
(function () {
@ -59,31 +59,31 @@
},
// ----------
equalsWithVariance: function ( value1, value2, variance ) {
return Math.abs( value1 - value2 ) <= variance;
equalsWithVariance: function (actual, expected, variance) {
return Math.abs(actual - expected) <= variance;
},
// ----------
assessNumericValue: function ( assert, value1, value2, variance, message ) {
assert.ok( Util.equalsWithVariance( value1, value2, variance ), message + " Expected:" + value1 + " Found: " + value2 + " Variance: " + variance );
assessNumericValue: function (assert, actual, expected, variance, message) {
assert.ok(
Util.equalsWithVariance(actual, expected, variance),
message + " Actual: " + actual + " Expected: " + expected + " Variance: " + variance
);
},
// ----------
assertPointsEquals: function (assert, pointA, pointB, precision, message) {
Util.assessNumericValue(assert, pointA.x, pointB.x, precision, message + " x: ");
Util.assessNumericValue(assert, pointA.y, pointB.y, precision, message + " y: ");
assertPointsEquals: function (assert, actualPoint, expectedPoint, precision, message) {
Util.assessNumericValue(assert, actualPoint.x, expectedPoint.x, precision, message + " x: ");
Util.assessNumericValue(assert, actualPoint.y, expectedPoint.y, precision, message + " y: ");
},
// ----------
assertRectangleEquals: function (assert, rectA, rectB, precision, message) {
Util.assessNumericValue(assert, rectA.x, rectB.x, precision, message + " x: ");
Util.assessNumericValue(assert, rectA.y, rectB.y, precision, message + " y: ");
Util.assessNumericValue(assert, rectA.width, rectB.width, precision,
message + " width: ");
Util.assessNumericValue(assert, rectA.height, rectB.height, precision,
message + " height: ");
Util.assessNumericValue(assert, rectA.degrees, rectB.degrees, precision,
message + " degrees: ");
assertRectangleEquals: function (assert, actualRect, expectedRect, precision, message) {
Util.assessNumericValue(assert, actualRect.x, expectedRect.x, precision, message + " x: ");
Util.assessNumericValue(assert, actualRect.y, expectedRect.y, precision, message + " y: ");
Util.assessNumericValue(assert, actualRect.width, expectedRect.width, precision, message + " width: ");
Util.assessNumericValue(assert, actualRect.height, expectedRect.height, precision, message + " height: ");
Util.assessNumericValue(assert, actualRect.degrees, expectedRect.degrees, precision, message + " degrees: ");
},
// ----------
@ -146,9 +146,15 @@
obj0[member0]();
assert.equal(called, true, 'called through for ' + member0);
assert.equal(errored, true, 'errored for ' + member0);
}
},
};
// Log the name of the currently running test when it starts. Uses console.log rather than
// $.console.log so that the message is printed even after the $.console is diverted (see below).
QUnit.testStart((details) => {
console.log(`Starting test ${details.module}.${details.name}`);
});
/*
Test console log capture
@ -174,12 +180,45 @@
}
};
// OSD has circular references, if a console log tries to serialize
// certain object, remove these references from a clone (do not delete prop
// on the original object).
// NOTE: this does not work if someone replaces the original class with
// a mock object! Try to mock functions only, or ensure mock objects
// do not hold circular references.
const circularOSDReferences = {
'Tile': 'tiledImage',
'CacheRecord': ['_tRef', '_tiles'],
'World': 'viewer',
'DrawerBase': ['viewer', 'viewport'],
'CanvasDrawer': ['viewer', 'viewport'],
'WebGLDrawer': ['viewer', 'viewport'],
'TiledImage': ['viewer', '_drawer'],
};
for ( var i in testLog ) {
if ( testLog.hasOwnProperty( i ) && testLog[i].push ) {
// Circular reference removal
const osdCircularStructureReplacer = function (key, value) {
for (let ClassType in circularOSDReferences) {
if (value instanceof OpenSeadragon[ClassType]) {
const instance = {};
Object.assign(instance, value);
let circProps = circularOSDReferences[ClassType];
if (!Array.isArray(circProps)) circProps = [circProps];
for (let prop of circProps) {
instance[prop] = '__circular_reference__';
}
return instance;
}
}
return value;
};
testConsole[i] = ( function ( arr ) {
return function () {
var args = Array.prototype.slice.call( arguments, 0 ); // Coerce to true Array
arr.push( JSON.stringify( args ) ); // Store as JSON to avoid tedious array-equality tests
arr.push( JSON.stringify( args, osdCircularStructureReplacer ) ); // Store as JSON to avoid tedious array-equality tests
};
} )( testLog[i] );
@ -201,5 +240,29 @@
};
OpenSeadragon.console = testConsole;
OpenSeadragon.getBuiltInDrawersForTest = function() {
const drawers = [];
for (let property in OpenSeadragon) {
const drawer = OpenSeadragon[ property ],
proto = drawer.prototype;
if( proto &&
proto instanceof OpenSeadragon.DrawerBase &&
$.isFunction( proto.getType )){
drawers.push(proto.getType.call( drawer ));
}
}
return drawers;
};
OpenSeadragon.Viewer.prototype.waitForFinishedJobsForTest = function () {
let finish;
let int = setInterval(() => {
if (this.imageLoader.jobsInProgress < 1) {
finish();
}
}, 50);
return new OpenSeadragon.Promise((resolve) => finish = resolve);
};
} )();

View file

@ -27,6 +27,7 @@
};
var viewer = null;
var DONE = false;
var OriginalLoader = OpenSeadragon.ImageLoader;
var OriginalAjax = OpenSeadragon.makeAjaxRequest;
@ -80,7 +81,7 @@
tileExists: function ( level, x, y ) {
return true;
}
},
});
var Loader = function(options) {
@ -96,7 +97,9 @@
OriginalLoader.prototype.addJob.apply(this, [options]);
} else {
//no ajax means we would wait for invalid image link to load, close - passed
viewer.close();
setTimeout(() => {
viewer.close();
});
}
}
});
@ -138,7 +141,9 @@
//first AJAX firing is the image info getter, second is the first tile request: can exit
ajaxCounter++;
if (ajaxCounter > 1) {
viewer.close();
setTimeout(() => {
viewer.close();
});
return null;
}
@ -156,7 +161,7 @@
ASSERT = null;
if (viewer && viewer.close) {
viewer.close();
DONE ? viewer.destroy () : viewer.close();
}
viewer = null;
@ -183,33 +188,34 @@
});
var failHandler = function (event) {
testPostData(event.postData, "event: 'open-failed'");
viewer.removeHandler('open-failed', failHandler);
viewer.close();
ASSERT.ok(false, 'Open-failed shoud not be called. We have custom function of fetching the data that succeeds.');
};
viewer.addHandler('open-failed', failHandler);
var readyHandler = function (event) {
//relies on Tilesource contructor extending itself with options object
testPostData(event.postData, "event: 'ready'");
viewer.removeHandler('ready', readyHandler);
};
viewer.addHandler('ready', readyHandler);
var openHandlerCalled = false;
var openHandler = function(event) {
viewer.removeHandler('open', openHandler);
ASSERT.ok(true, 'Open event was sent');
openHandlerCalled = true;
};
var readyHandler = function (event) {
testPostData(event.item.source.getTilePostData(0, 0, 0), "event: 'add-item'");
viewer.world.removeHandler('add-item', readyHandler);
viewer.addHandler('close', closeHandler);
viewer.world.draw();
};
var closeHandler = function(event) {
ASSERT.ok(openHandlerCalled, 'Open event was sent.');
viewer.removeHandler('close', closeHandler);
$('#example').empty();
ASSERT.ok(true, 'Close event was sent');
timeWatcher.done();
};
//make sure we call add-item before the system default 0 priority, it fires download on tiles and removes
// which calls internally viewer.close
viewer.world.addHandler('add-item', readyHandler, null, Infinity);
viewer.addHandler('open', openHandler);
};
@ -271,6 +277,7 @@
return "d1=a1&d2=a2###";
},
}, true, true);
DONE = true; // mark the module as completed so the viewer can be destroyed
});
})();

View file

@ -49,12 +49,13 @@
loadTilesWithAjax: true,
ajaxHeaders: {
'X-Viewer-Header': 'ViewerHeaderValue'
}
},
callTileLoadedWithCachedData: true
});
},
afterEach: function() {
if (viewer && viewer.close) {
viewer.close();
if (viewer){
viewer.destroy();
}
viewer = null;
@ -245,4 +246,130 @@
tileSource: staticHeaderTileSource
});
});
QUnit.test('Viewer headers can be updated', function(assert) {
var done = assert.async();
var newHeaders = {
'X-Viewer-Header': 'ViewerHeaderValue-Updated',
'X-Viewer-Header2': 'ViewerHeaderValue2'
}
var newHeaders2 = {
Range: 'test',
}
var tileLoaded = function tileLoaded(evt) {
viewer.removeHandler('tile-loaded', tileLoaded);
// set new Viewer headers and propagate to TiledImage and Tile
viewer.setAjaxHeaders(newHeaders);
viewer.addHandler('tile-loaded', tileLoaded2);
};
var tileLoaded2 = function tileLoaded(evt) {
viewer.removeHandler('tile-loaded', tileLoaded2);
assert.deepEqual(viewer.ajaxHeaders, newHeaders);
assert.deepEqual(evt.tiledImage.ajaxHeaders, newHeaders);
assert.deepEqual(
evt.tile.ajaxHeaders,
OpenSeadragon.extend(
{}, viewer.ajaxHeaders, evt.tiledImage.ajaxHeaders,
{ Range: getTileRangeHeader(evt.tile.level, evt.tile.x, evt.tile.y) }
)
);
// set new Viewer headers and propagate to TiledImage and Tile
viewer.setAjaxHeaders(newHeaders2, true);
viewer.addHandler('tile-loaded', tileLoaded3);
};
var tileLoaded3 = function tileLoaded(evt) {
viewer.removeHandler('tile-loaded', tileLoaded3);
assert.deepEqual(viewer.ajaxHeaders, newHeaders2);
assert.deepEqual(evt.tiledImage.ajaxHeaders, newHeaders2);
assert.equal(evt.tile.ajaxHeaders['X-Viewer-Header'], undefined);
assert.equal(evt.tile.ajaxHeaders['X-Viewer-Header2'], undefined);
// 'Range' header entry set per tile and must not be overwritten by Viewer header
assert.equal(evt.tile.ajaxHeaders.Range, getTileRangeHeader(evt.tile.level, evt.tile.x, evt.tile.y));
// set new Viewer headers but do not propagate to TiledImage and Tile
viewer.setAjaxHeaders(newHeaders, false);
viewer.addHandler('tile-loaded', tileLoaded4);
};
var tileLoaded4 = function tileLoaded(evt) {
viewer.removeHandler('tile-loaded', tileLoaded4);
assert.deepEqual(viewer.ajaxHeaders, newHeaders);
assert.deepEqual(evt.tiledImage.ajaxHeaders, newHeaders2);
assert.equal(evt.tile.ajaxHeaders['X-Viewer-Header'], undefined);
assert.equal(evt.tile.ajaxHeaders['X-Viewer-Header2'], undefined);
done();
};
viewer.addHandler('tile-loaded', tileLoaded);
viewer.open(customTileSource);
});
QUnit.test('TiledImage headers can be updated', function(assert) {
var done = assert.async();
var tileSourceHeaders = {
'X-Tile-Header': 'TileHeaderValue'
}
var newHeaders = {
'X-TiledImage-Header': 'TiledImageHeaderValue-Updated',
'X-TiledImage-Header2': 'TiledImageHeaderValue2'
}
var newHeaders2 = {
'X-Viewer-Header': 'ViewerHeaderValue-Updated',
'X-Tile-Header': 'TileHeaderValue-Updated'
}
var tileLoaded = function tileLoaded(evt) {
viewer.removeHandler('tile-loaded', tileLoaded);
// set new TiledImage headers and propagate to Tile
evt.tiledImage.setAjaxHeaders(newHeaders);
viewer.addHandler('tile-loaded', tileLoaded2);
};
var tileLoaded2 = function tileLoaded(evt) {
viewer.removeHandler('tile-loaded', tileLoaded2);
assert.deepEqual(viewer.ajaxHeaders, { 'X-Viewer-Header': 'ViewerHeaderValue' });
assert.deepEqual(evt.tiledImage._ownAjaxHeaders, newHeaders);
assert.deepEqual(evt.tiledImage.ajaxHeaders, OpenSeadragon.extend({}, viewer.ajaxHeaders, newHeaders));
assert.deepEqual(evt.tile.ajaxHeaders, OpenSeadragon.extend({}, viewer.ajaxHeaders, newHeaders, tileSourceHeaders));
// set new TiledImage headers (that overwrite header entries of Viewer and Tile) and propagate to Tile
evt.tiledImage.setAjaxHeaders(newHeaders2, true);
viewer.addHandler('tile-loaded', tileLoaded3);
};
var tileLoaded3 = function tileLoaded(evt) {
viewer.removeHandler('tile-loaded', tileLoaded3);
assert.deepEqual(viewer.ajaxHeaders, { 'X-Viewer-Header': 'ViewerHeaderValue' });
assert.deepEqual(evt.tiledImage._ownAjaxHeaders, newHeaders2);
assert.deepEqual(evt.tiledImage.ajaxHeaders, OpenSeadragon.extend({}, viewer.ajaxHeaders, newHeaders2));
assert.deepEqual(evt.tile.ajaxHeaders, OpenSeadragon.extend({}, viewer.ajaxHeaders, newHeaders2, tileSourceHeaders));
// set new TiledImage headers but do not propagate to Tile
evt.tiledImage.setAjaxHeaders(null, false);
viewer.addHandler('tile-loaded', tileLoaded4);
};
var tileLoaded4 = function tileLoaded(evt) {
viewer.removeHandler('tile-loaded', tileLoaded4);
assert.deepEqual(viewer.ajaxHeaders, { 'X-Viewer-Header': 'ViewerHeaderValue' });
assert.deepEqual(evt.tiledImage._ownAjaxHeaders, {});
assert.deepEqual(evt.tiledImage.ajaxHeaders, viewer.ajaxHeaders);
assert.deepEqual(evt.tile.ajaxHeaders, OpenSeadragon.extend({}, viewer.ajaxHeaders, newHeaders2, tileSourceHeaders));
done();
};
viewer.addHandler('tile-loaded', tileLoaded);
viewer.addTiledImage({
ajaxHeaders: {
'X-TiledImage-Header': 'TiledImageHeaderValue'
},
tileSource: OpenSeadragon.extend({}, customTileSource, {
getTileAjaxHeaders: function() {
return tileSourceHeaders;
}
}),
});
});
})();

View file

@ -16,8 +16,8 @@
});
},
afterEach: function () {
if (viewer && viewer.close) {
viewer.close();
if (viewer){
viewer.destroy();
}
viewer = null;
@ -224,32 +224,44 @@
});
QUnit.test('FullScreen', function(assert) {
var done = assert.async();
if (!OpenSeadragon.supportsFullScreen) {
const done = assert.async();
assert.expect(0);
done();
return;
}
var timeWatcher = Util.timeWatcher(assert, 7000);
viewer.addHandler("open", function () {
viewer.addHandler('open', function () {
assert.ok(!OpenSeadragon.isFullScreen(), 'Started out not fullscreen');
var checkEnteringPreFullScreen = function(event) {
const checkEnteringPreFullScreen = (event) => {
viewer.removeHandler('pre-full-screen', checkEnteringPreFullScreen);
assert.ok(event.fullScreen, 'Switching to fullscreen');
assert.ok(!OpenSeadragon.isFullScreen(), 'Not yet fullscreen');
};
// The fullscreen mode is always denied during tests so we are
// exiting directly.
var checkExitingFullScreen = function(event) {
const checkExitingFullScreen = (event) => {
viewer.removeHandler('full-screen', checkExitingFullScreen);
assert.ok(!event.fullScreen, 'Exiting fullscreen');
assert.ok(!OpenSeadragon.isFullScreen(), 'Disabled fullscreen');
done();
assert.ok(!event.fullScreen, 'Disabling fullscreen');
assert.ok(!OpenSeadragon.isFullScreen(), 'Fullscreen disabled');
timeWatcher.done();
}
// The 'new' headless mode allows us to enter fullscreen, so verify
// that we see the correct values returned. We will then close out
// of fullscreen to check the same values when exiting.
const checkAcquiredFullScreen = (event) => {
viewer.removeHandler('full-screen', checkAcquiredFullScreen);
viewer.addHandler('full-screen', checkExitingFullScreen);
assert.ok(event.fullScreen, 'Acquired fullscreen');
assert.ok(OpenSeadragon.isFullScreen(), 'Fullscreen enabled. Note: this test might fail ' +
'because fullscreen might be blocked by your browser - not a trusted event!');
viewer.setFullScreen(false);
};
viewer.addHandler("pre-full-screen", checkEnteringPreFullScreen);
viewer.addHandler("full-screen", checkExitingFullScreen);
viewer.addHandler('pre-full-screen', checkEnteringPreFullScreen);
viewer.addHandler('full-screen', checkAcquiredFullScreen);
viewer.setFullScreen(true);
});
@ -319,10 +331,11 @@
height: 155
} ]
} );
viewer.addOnceHandler('tile-drawn', function() {
assert.ok(OpenSeadragon.isCanvasTainted(viewer.drawer.context.canvas),
"Canvas should be tainted.");
done();
viewer.addOnceHandler('tiled-image-drawn', function(event) {
event.tiles[0].getCache().getDataAs("context2d", false).then(context =>
assert.ok(OpenSeadragon.isCanvasTainted(context.canvas),
"Canvas should be tainted.")
).then(done);
});
} );
@ -339,10 +352,11 @@
height: 155
} ]
} );
viewer.addOnceHandler('tile-drawn', function() {
assert.ok(!OpenSeadragon.isCanvasTainted(viewer.drawer.context.canvas),
"Canvas should not be tainted.");
done();
viewer.addOnceHandler('tiled-image-drawn', function(event) {
event.tiles[0].getCache().getDataAs("context2d", false).then(context =>
assert.notOk(OpenSeadragon.isCanvasTainted(context.canvas),
"Canvas should be tainted.")
).then(done);
});
} );
@ -363,10 +377,11 @@
},
crossOriginPolicy : false
} );
viewer.addOnceHandler('tile-drawn', function() {
assert.ok(OpenSeadragon.isCanvasTainted(viewer.drawer.context.canvas),
"Canvas should be tainted.");
done();
viewer.addOnceHandler('tiled-image-drawn', function(event) {
event.tiles[0].getCache().getDataAs("context2d", false).then(context =>
assert.ok(OpenSeadragon.isCanvasTainted(context.canvas),
"Canvas should be tainted.")
).then(done);
});
} );
@ -387,10 +402,11 @@
crossOriginPolicy : "Anonymous"
}
} );
viewer.addOnceHandler('tile-drawn', function() {
assert.ok(!OpenSeadragon.isCanvasTainted(viewer.drawer.context.canvas),
"Canvas should not be tainted.");
done();
viewer.addOnceHandler('tiled-image-drawn', function(event) {
event.tiles[0].getCache().getDataAs("context2d", false).then(context =>
assert.notOk(OpenSeadragon.isCanvasTainted(context.canvas),
"Canvas should be tainted.")
).then(done);
});
} );

View file

@ -16,8 +16,8 @@
},
afterEach: function () {
if (viewer && viewer.close) {
viewer.close();
if (viewer){
viewer.destroy();
}
viewer = null;

View file

@ -0,0 +1,260 @@
/* global QUnit, testLog */
(function() {
let viewer;
QUnit.module(`Data Manipulation Across Drawers`, {
beforeEach: function () {
$('<div id="example"></div>').appendTo("#qunit-fixture");
testLog.reset();
},
afterEach: function () {
if (viewer && viewer.close) {
viewer.close();
}
viewer = null;
}
});
const PROMISE_REF_KEY = Symbol("_private_test_ref");
OpenSeadragon.getBuiltInDrawersForTest().forEach(testDrawer);
// If you want to debug a specific drawer, use instead:
// ['webgl'].forEach(testDrawer);
function getPluginCode(overlayColor = "rgba(0,0,255,0.5)") {
return async function(e) {
const ctx = await e.getData('context2d');
if (ctx) {
const canvas = ctx.canvas;
ctx.fillStyle = overlayColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
await e.setData(ctx, 'context2d');
}
};
}
function getResetTileDataCode() {
return async function(e) {
e.resetData();
};
}
function getTileDescription(t) {
return `${t.level}/${t.x}-${t.y}`;
}
function testDrawer(type) {
function whiteViewport() {
viewer = OpenSeadragon({
id: 'example',
prefixUrl: '/build/openseadragon/images/',
maxImageCacheCount: 200,
springStiffness: 100,
drawer: type
});
viewer.open({
width: 24,
height: 24,
tileSize: 24,
minLevel: 1,
// This is a crucial test feature: all tiles share the same URL, so there are plenty collisions
getTileUrl: (x, y, l) => "",
getTilePostData: () => "",
downloadTileStart: (context) => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = context.tile.size.x;
canvas.height = context.tile.size.y;
ctx.fillStyle = "#ffffff";
ctx.fillRect(0, 0, context.tile.size.x, context.tile.size.y);
context.finish(ctx, null, "context2d");
}
});
// Get promise reference to wait for tile ready
viewer.addHandler('tile-loaded', e => {
e.tile[PROMISE_REF_KEY] = e.promise;
});
}
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
// we test middle of the canvas, so that we can test both tiles or the output canvas of canvas drawer :)
async function readTileData(tileRef = null) {
// Get some time for viewer to load data
await sleep(50);
// make sure at least one tile loaded
const tile = tileRef || viewer.world.getItemAt(0).getTilesToDraw()[0];
await tile[PROMISE_REF_KEY];
// Get some time for viewer to load data
await sleep(50);
if (type === "canvas") {
//test with the underlying canvas instead
const canvas = viewer.drawer.canvas;
return viewer.drawer.canvas.getContext("2d").getImageData(canvas.width/2, canvas.height/2, 1, 1);
}
//else incompatible drawer for data getting
const cache = tile.tile.getCache();
if (!cache || !cache.loaded) return null;
const ctx = await cache.getDataAs("context2d");
if (!ctx) return null;
return ctx.getImageData(ctx.canvas.width/2, ctx.canvas.height/2, 1, 1)
}
QUnit.test(type + ' drawer: basic scenario.', function(assert) {
whiteViewport();
const done = assert.async();
const fnA = getPluginCode("rgba(0,0,255,1)");
const fnB = getPluginCode("rgba(255,0,0,1)");
viewer.addHandler('tile-invalidated', fnA);
viewer.addHandler('tile-invalidated', fnB);
viewer.addHandler('open', async () => {
await viewer.waitForFinishedJobsForTest();
let data = await readTileData();
assert.equal(data.data[0], 255);
assert.equal(data.data[1], 0);
assert.equal(data.data[2], 0);
assert.equal(data.data[3], 255);
// Thorough testing of the cache state
for (let tile of viewer.tileCache._tilesLoaded) {
await tile[PROMISE_REF_KEY]; // to be sure all tiles has finished before checking
const caches = Object.entries(tile._caches);
assert.equal(caches.length, 2, `Tile ${getTileDescription(tile)} has only two caches - main & original`);
for (let [key, value] of caches) {
assert.ok(value.loaded, `Attached cache '${key}' is ready.`);
assert.notOk(value._destroyed, `Attached cache '${key}' is not destroyed.`);
assert.ok(value._tiles.includes(tile), `Attached cache '${key}' reference is bidirectional.`);
}
}
done();
});
});
QUnit.test(type + ' drawer: basic scenario with priorities + events addition.', function(assert) {
whiteViewport();
const done = assert.async();
// FNA gets applied last since it has low priority
const fnA = getPluginCode("rgba(0,0,255,1)");
const fnB = getPluginCode("rgba(255,0,0,1)");
viewer.addHandler('tile-invalidated', fnA);
viewer.addHandler('tile-invalidated', fnB, null, 1);
// const promise = viewer.requestInvalidate();
viewer.addHandler('open', async () => {
await viewer.waitForFinishedJobsForTest();
let data = await readTileData();
assert.equal(data.data[0], 0);
assert.equal(data.data[1], 0);
assert.equal(data.data[2], 255);
assert.equal(data.data[3], 255);
// Test swap
viewer.addHandler('tile-invalidated', fnB);
await viewer.requestInvalidate();
data = await readTileData();
// suddenly B is applied since it was added with same priority but later
assert.equal(data.data[0], 255);
assert.equal(data.data[1], 0);
assert.equal(data.data[2], 0);
assert.equal(data.data[3], 255);
// Now B gets applied last! Red
viewer.addHandler('tile-invalidated', fnB, null, -1);
await viewer.requestInvalidate();
// no change
data = await readTileData();
assert.equal(data.data[0], 255);
assert.equal(data.data[1], 0);
assert.equal(data.data[2], 0);
assert.equal(data.data[3], 255);
// Thorough testing of the cache state
for (let tile of viewer.tileCache._tilesLoaded) {
await tile[PROMISE_REF_KEY]; // to be sure all tiles has finished before checking
const caches = Object.entries(tile._caches);
assert.equal(caches.length, 2, `Tile ${getTileDescription(tile)} has only two caches - main & original`);
for (let [key, value] of caches) {
assert.ok(value.loaded, `Attached cache '${key}' is ready.`);
assert.notOk(value._destroyed, `Attached cache '${key}' is not destroyed.`);
assert.ok(value._tiles.includes(tile), `Attached cache '${key}' reference is bidirectional.`);
}
}
done();
});
});
QUnit.test(type + ' drawer: one calls tile restore.', function(assert) {
whiteViewport();
const done = assert.async();
const fnA = getPluginCode("rgba(0,255,0,1)");
const fnB = getResetTileDataCode();
viewer.addHandler('tile-invalidated', fnA);
viewer.addHandler('tile-invalidated', fnB, null, 1);
// const promise = viewer.requestInvalidate();
viewer.addHandler('open', async () => {
await viewer.waitForFinishedJobsForTest();
let data = await readTileData();
assert.equal(data.data[0], 0);
assert.equal(data.data[1], 255);
assert.equal(data.data[2], 0);
assert.equal(data.data[3], 255);
// Test swap - suddenly B applied since it was added later
viewer.addHandler('tile-invalidated', fnB);
await viewer.requestInvalidate();
data = await readTileData();
assert.equal(data.data[0], 255);
assert.equal(data.data[1], 255);
assert.equal(data.data[2], 255);
assert.equal(data.data[3], 255);
viewer.addHandler('tile-invalidated', fnB, null, -1);
await viewer.requestInvalidate();
data = await readTileData();
//Erased!
assert.equal(data.data[0], 255);
assert.equal(data.data[1], 255);
assert.equal(data.data[2], 255);
assert.equal(data.data[3], 255);
// Thorough testing of the cache state
for (let tile of viewer.tileCache._tilesLoaded) {
await tile[PROMISE_REF_KEY]; // to be sure all tiles has finished before checking
const caches = Object.entries(tile._caches);
assert.equal(caches.length, 1, `Tile ${getTileDescription(tile)} has only single, original cache`);
for (let [key, value] of caches) {
assert.ok(value.loaded, `Attached cache '${key}' is ready.`);
assert.notOk(value._destroyed, `Attached cache '${key}' is not destroyed.`);
assert.ok(value._tiles.includes(tile), `Attached cache '${key}' reference is bidirectional.`);
}
}
done();
});
});
}
}());

View file

@ -2,133 +2,153 @@
(function() {
var viewer;
OpenSeadragon.getBuiltInDrawersForTest().forEach(runDrawerTests);
QUnit.module('Drawer', {
beforeEach: function () {
$('<div id="example"></div>').appendTo("#qunit-fixture");
function runDrawerTests(drawerType){
testLog.reset();
},
afterEach: function () {
if (viewer && viewer.close) {
viewer.close();
QUnit.module('Drawer-'+drawerType, {
beforeEach: function () {
$('<div id="example"></div>').appendTo("#qunit-fixture");
testLog.reset();
},
afterEach: function () {
if (viewer){
viewer.destroy();
}
viewer = null;
}
viewer = null;
}
});
// ----------
var createViewer = function(options) {
options = options || {};
// eslint-disable-next-line new-cap
viewer = OpenSeadragon(OpenSeadragon.extend({
id: 'example',
prefixUrl: '/build/openseadragon/images/',
springStiffness: 100 // Faster animation = faster tests
}, options));
};
// ----------
QUnit.test('basics', function(assert) {
var done = assert.async();
createViewer();
assert.ok(viewer.drawer, 'Drawer exists');
assert.equal(viewer.drawer.canRotate(), OpenSeadragon.supportsCanvas, 'we can rotate if we have canvas');
done();
});
// ----------
QUnit.test('rotation', function(assert) {
var done = assert.async();
createViewer({
tileSources: '/test/data/testpattern.dzi'
});
viewer.addHandler('open', function handler(event) {
viewer.viewport.setRotation(30, true);
Util.spyOnce(viewer.drawer.context, 'rotate', function() {
assert.ok(true, 'drawing with new rotation');
// ----------
var createViewer = function(options) {
options = options || {};
// eslint-disable-next-line new-cap
viewer = OpenSeadragon(OpenSeadragon.extend({
id: 'example',
prefixUrl: '/build/openseadragon/images/',
springStiffness: 100, // Faster animation = faster tests
drawer: drawerType,
}, options));
};
// ----------
QUnit.test('basics', function(assert) {
var done = assert.async();
createViewer();
assert.ok(viewer.drawer, 'Drawer exists');
assert.equal(viewer.drawer.canRotate(), ['webgl','canvas'].includes(drawerType), 'we can rotate if we have canvas');
done();
});
// ----------
QUnit.test('rotation', function(assert) {
var done = assert.async();
createViewer({
tileSources: '/test/data/testpattern.dzi'
});
// this test only makes sense for canvas drawer because of how it is
// detected by watching for the canvas context rotate function
// TODO: add test for actual rotation of the drawn image in a way that
// applies to the webgl drawer as well as the canvas drawer.
if(viewer.drawer.getType() !== 'canvas'){
assert.expect(0);
done();
};
viewer.addHandler('open', function handler(event) {
viewer.viewport.setRotation(30, true);
Util.spyOnce(viewer.drawer.context, 'rotate', function() {
assert.ok(true, 'drawing with new rotation');
done();
});
});
});
// ----------
QUnit.test('debug', function(assert) {
var done = assert.async();
createViewer({
tileSources: '/test/data/testpattern.dzi',
debugMode: true
});
// only test this for canvas and webgl drawers
if( !['canvas','webgl'].includes(viewer.drawer.getType() )){
assert.expect(0);
done()
}
Util.spyOnce(viewer.drawer, '_drawDebugInfo', function() {
assert.ok(true, '_drawDebugInfo is called');
done();
});
});
});
// ----------
QUnit.test('debug', function(assert) {
var done = assert.async();
createViewer({
tileSources: '/test/data/testpattern.dzi',
debugMode: true
});
// ----------
QUnit.test('sketchCanvas', function(assert) {
var done = assert.async();
Util.spyOnce(viewer.drawer, 'drawDebugInfo', function() {
assert.ok(true, 'drawDebugInfo is called');
done();
});
});
createViewer({
tileSources: '/test/data/testpattern.dzi',
});
var drawer = viewer.drawer;
// ----------
QUnit.test('sketchCanvas', function(assert) {
var done = assert.async();
createViewer({
tileSources: '/test/data/testpattern.dzi'
});
var drawer = viewer.drawer;
// this test only makes sense for canvas drawer
if(viewer.drawer.getType() !== 'canvas'){
assert.expect(0);
done();
};
viewer.addHandler('tile-drawn', function noOpacityHandler() {
viewer.removeHandler('tile-drawn', noOpacityHandler);
assert.equal(drawer.sketchCanvas, null,
'The sketch canvas should be null if no decimal opacity is used.');
assert.equal(drawer.sketchContext, null,
'The sketch context should be null if no decimal opacity is used.');
testOpacityDecimal();
});
function testOpacityDecimal() {
var tiledImage;
viewer.addTiledImage({
tileSource: '/test/data/testpattern.dzi',
opacity: 0.5,
success: function(event) {
tiledImage = event.item;
}
viewer.addHandler('tile-drawn', function noOpacityHandler() {
viewer.removeHandler('tile-drawn', noOpacityHandler);
assert.equal(drawer.sketchCanvas, null,
'The sketch canvas should be null if no decimal opacity is used.');
assert.equal(drawer.sketchContext, null,
'The sketch context should be null if no decimal opacity is used.');
testOpacityDecimal();
});
viewer.addHandler('tile-drawn', function opacityDecimalHandler(event) {
if (tiledImage !== event.tiledImage) {
return;
}
viewer.removeHandler('tile-drawn', opacityDecimalHandler);
assert.notEqual(drawer.sketchCanvas, null,
'The sketch canvas should not be null once a decimal opacity has been used.');
assert.notEqual(drawer.sketchContext, null,
'The sketch context should not be null once a decimal opacity has been used.');
function testOpacityDecimal() {
var tiledImage;
viewer.addTiledImage({
tileSource: '/test/data/testpattern.dzi',
opacity: 0.5,
success: function(event) {
tiledImage = event.item;
}
});
viewer.addHandler('tile-drawn', function opacityDecimalHandler(event) {
if (tiledImage !== event.tiledImage) {
return;
}
viewer.removeHandler('tile-drawn', opacityDecimalHandler);
assert.notEqual(drawer.sketchCanvas, null,
'The sketch canvas should not be null once a decimal opacity has been used.');
assert.notEqual(drawer.sketchContext, null,
'The sketch context should not be null once a decimal opacity has been used.');
done();
});
}
});
// ----------
QUnit.test('deprecations', function(assert) {
var done = assert.async();
createViewer({
tileSources: '/test/data/testpattern.dzi'
});
viewer.world.addHandler('add-item', function() {
// no current deprecated methods
assert.expect(0);
done();
});
}
});
// ----------
QUnit.test('deprecations', function(assert) {
var done = assert.async();
createViewer({
tileSources: '/test/data/testpattern.dzi'
});
viewer.world.addHandler('add-item', function() {
Util.testDeprecation(assert, viewer.drawer, 'addOverlay', viewer, 'addOverlay');
Util.testDeprecation(assert, viewer.drawer, 'updateOverlay', viewer, 'updateOverlay');
Util.testDeprecation(assert, viewer.drawer, 'removeOverlay', viewer, 'removeOverlay');
Util.testDeprecation(assert, viewer.drawer, 'clearOverlays', viewer, 'clearOverlays');
Util.testDeprecation(assert, viewer.drawer, 'needsUpdate', viewer.world, 'needsDraw');
Util.testDeprecation(assert, viewer.drawer, 'numTilesLoaded', viewer.tileCache, 'numTilesLoaded');
Util.testDeprecation(assert, viewer.drawer, 'reset', viewer.world, 'resetItems');
Util.testDeprecation(assert, viewer.drawer, 'update', viewer.world, 'draw');
Util.testDeprecation(assert, viewer.drawer, 'setOpacity', viewer.world.getItemAt(0), 'setOpacity');
Util.testDeprecation(assert, viewer.drawer, 'getOpacity', viewer.world.getItemAt(0), 'getOpacity');
done();
});
});
}
})();

View file

@ -0,0 +1,143 @@
/* global QUnit, $, TouchUtil, Util, testLog */
(function () {
var context, result=[], eName = "test", eventCounter = 0, finished = false;
function evaluateTest(e) {
if (finished) return;
finished = true;
e.assert.strictEqual(JSON.stringify(result), JSON.stringify(e.expected), e.message);
e.done();
}
function executor(i, ms) {
if (ms === undefined) return function (e) {
eventCounter++;
result.push(i);
if (eventCounter === context.numberOfHandlers(eName)) {
evaluateTest(e);
}
};
return function (e) {
return new Promise(function (resolve) {
setTimeout(function () {
eventCounter++;
result.push(i);
if (eventCounter === context.numberOfHandlers(eName)) {
evaluateTest(e);
}
resolve();
}, ms);
});
}
}
function runTest(e, async=false) {
context.raiseEvent(eName, e);
}
function runTestAwaiting(e, async=false) {
context.raiseEventAwaiting(eName, e);
}
QUnit.module( 'EventSource', {
beforeEach: function () {
context = new OpenSeadragon.EventSource();
eventCounter = 0;
result = [];
finished = false;
}
} );
// ----------
QUnit.test('EventSource: no events', function(assert) {
context.addHandler(eName, evaluateTest);
runTest({
assert: assert,
done: assert.async(),
expected: [],
message: 'No handlers registered - arrays should be empty.'
});
});
QUnit.test('EventSource: simple callbacks order', function(assert) {
context.addHandler(eName, executor(1));
context.addHandler(eName, executor(2));
context.addHandler(eName, executor(3));
runTest({
assert: assert,
done: assert.async(),
expected: [1, 2, 3],
message: 'Simple callback order should follow [1,2,3].'
});
});
QUnit.test('EventSource: priority callbacks order', function(assert) {
context.addHandler(eName, executor(1), undefined, 20);
context.addHandler(eName, executor(2), undefined, 124);
context.addHandler(eName, executor(3), undefined, -5);
context.addHandler(eName, executor(4));
context.addHandler(eName, executor(5), undefined, -2);
runTest({
assert: assert,
done: assert.async(),
expected: [2, 1, 4, 5, 3],
message: 'Prioritized callback order should follow [2,1,4,5,3].'
});
});
QUnit.test('EventSource: async non-synchronized order', function(assert) {
context.addHandler(eName, executor(1, 5));
context.addHandler(eName, executor(2, 50));
context.addHandler(eName, executor(3));
context.addHandler(eName, executor(4));
runTest({
assert: assert,
done: assert.async(),
expected: [3, 4, 1, 2],
message: 'Async callback order should follow [3,4,1,2].'
});
});
QUnit.test('EventSource: async non-synchronized priority order', function(assert) {
context.addHandler(eName, executor(1, 5));
context.addHandler(eName, executor(2, 50), undefined, -100);
context.addHandler(eName, executor(3), undefined, -500);
context.addHandler(eName, executor(4), undefined, 675);
runTest({
assert: assert,
done: assert.async(),
expected: [4, 3, 1, 2],
message: 'Async callback order with priority should follow [4,3,1,2]. Async functions do not respect priority.'
});
});
QUnit.test('EventSource: async synchronized order', function(assert) {
context.addHandler(eName, executor(1, 5));
context.addHandler(eName, executor(2, 50));
context.addHandler(eName, executor(3));
context.addHandler(eName, executor(4));
runTestAwaiting({
waitForPromiseHandlers: true,
assert: assert,
done: assert.async(),
expected: [1, 2, 3, 4],
message: 'Async callback order should follow [1,2,3,4], since it is synchronized.'
});
});
QUnit.test('EventSource: async synchronized priority order', function(assert) {
context.addHandler(eName, executor(1, 5));
context.addHandler(eName, executor(2), undefined, -500);
context.addHandler(eName, executor(3, 50), undefined, -200);
context.addHandler(eName, executor(4), undefined, 675);
runTestAwaiting({
waitForPromiseHandlers: true,
assert: assert,
done: assert.async(),
expected: [4, 1, 3, 2],
message: 'Async callback order with priority should follow [4,1,3,2], since priority is respected when synchronized.'
});
});
} )();

View file

@ -2,6 +2,7 @@
(function () {
var viewer;
var sleep = time => new Promise(res => setTimeout(res, time));
QUnit.module( 'Events', {
beforeEach: function () {
@ -17,10 +18,9 @@
} );
},
afterEach: function () {
if ( viewer && viewer.close ) {
viewer.close();
if (viewer){
viewer.destroy();
}
viewer = null;
}
} );
@ -333,10 +333,10 @@
assert.equal( quickClick, expected.quickClick, expected.description + 'clickHandler event.quick matches expected (' + expected.quickClick + ')' );
}
if ('speed' in expected) {
Util.assessNumericValue(expected.speed, speed, 1.0, expected.description + 'Drag speed ');
Util.assessNumericValue(assert, speed, expected.speed, 1.0, expected.description + 'Drag speed');
}
if ('direction' in expected) {
Util.assessNumericValue(expected.direction, direction, 0.2, expected.description + 'Drag direction ');
Util.assessNumericValue(assert, direction, expected.direction, 0.2, expected.description + 'Drag direction');
}
};
@ -868,7 +868,7 @@
simulateDblTap();
var zoom = viewer.viewport.getZoom();
Util.assessNumericValue(assert, originalZoom, zoom, epsilon,
Util.assessNumericValue(assert, zoom, originalZoom, epsilon,
"Zoom on double tap should be prevented");
// Reset event handler to original
@ -878,7 +878,7 @@
originalZoom *= viewer.zoomPerClick;
zoom = viewer.viewport.getZoom();
Util.assessNumericValue(assert, originalZoom, zoom, epsilon,
Util.assessNumericValue(assert, zoom, originalZoom, epsilon,
"Zoom on double tap should not be prevented");
@ -1155,6 +1155,12 @@
// ----------
QUnit.test( 'Viewer: event count test with \'tile-drawing\'', function (assert) {
var done = assert.async();
if(viewer.drawer.getType() !== 'canvas'){
assert.expect(0);
done();
return;
}
assert.ok(viewer.numberOfHandlers('tile-drawing') === 0,
"'tile-drawing' event is empty by default.");
@ -1162,6 +1168,7 @@
viewer.removeHandler( 'tile-drawing', tileDrawing );
assert.ok(viewer.numberOfHandlers('tile-drawing') === 0,
"'tile-drawing' deleted: count is 0.");
viewer.close();
done();
};
@ -1185,6 +1192,12 @@
QUnit.test( 'Viewer: tile-drawing event', function (assert) {
var done = assert.async();
if(viewer.drawer.getType() !== 'canvas'){
assert.expect(0);
done();
return;
}
var tileDrawing = function ( event ) {
viewer.removeHandler( 'tile-drawing', tileDrawing );
assert.ok( event, 'Event handler should be invoked' );
@ -1210,11 +1223,12 @@
var tile = event.tile;
assert.ok( tile.loading, "The tile should be marked as loading.");
assert.notOk( tile.loaded, "The tile should not be marked as loaded.");
setTimeout(function() {
//make sure we require tile loaded status once the data is ready
event.promise.then(function() {
assert.notOk( tile.loading, "The tile should not be marked as loading.");
assert.ok( tile.loaded, "The tile should be marked as loaded.");
done();
}, 0);
});
}
viewer.addHandler( 'tile-loaded', tileLoaded);
@ -1226,72 +1240,85 @@
function tileLoaded ( event ) {
viewer.removeHandler( 'tile-loaded', tileLoaded);
var tile = event.tile;
var callback = event.getCompletionCallback();
assert.ok( tile.loading, "The tile should be marked as loading.");
assert.notOk( tile.loaded, "The tile should not be marked as loaded.");
assert.ok( callback, "The event should have a callback.");
setTimeout(function() {
assert.ok( tile.loading, "The tile should be marked as loading.");
assert.notOk( tile.loaded, "The tile should not be marked as loaded.");
callback();
event.promise.then( _ => {
assert.notOk( tile.loading, "The tile should not be marked as loading.");
assert.ok( tile.loaded, "The tile should be marked as loaded.");
done();
}, 0);
});
}
viewer.addHandler( 'tile-loaded', tileLoaded);
viewer.open( '/test/data/testpattern.dzi' );
} );
QUnit.test( 'Viewer: tile-loaded event with 2 callbacks.', function (assert) {
var done = assert.async();
function tileLoaded ( event ) {
viewer.removeHandler( 'tile-loaded', tileLoaded);
var tile = event.tile;
var callback1 = event.getCompletionCallback();
var callback2 = event.getCompletionCallback();
QUnit.test( 'Viewer: asynchronous tile processing.', function (assert) {
var done = assert.async(),
handledOnce = false;
const tileLoaded1 = async (event) => {
assert.ok( handledOnce, "tileLoaded1 with priority 5 should be called second.");
const tile = event.tile;
handledOnce = true;
assert.ok( tile.loading, "The tile should be marked as loading.");
assert.notOk( tile.loaded, "The tile should not be marked as loaded.");
setTimeout(function() {
assert.ok( tile.loading, "The tile should be marked as loading.");
assert.notOk( tile.loaded, "The tile should not be marked as loaded.");
callback1();
assert.ok( tile.loading, "The tile should be marked as loading.");
assert.notOk( tile.loaded, "The tile should not be marked as loaded.");
setTimeout(function() {
assert.ok( tile.loading, "The tile should be marked as loading.");
assert.notOk( tile.loaded, "The tile should not be marked as loaded.");
callback2();
assert.notOk( tile.loading, "The tile should not be marked as loading.");
assert.ok( tile.loaded, "The tile should be marked as loaded.");
done();
}, 0);
}, 0);
}
viewer.addHandler( 'tile-loaded', tileLoaded);
event.promise.then(() => {
assert.notOk( tile.loading, "The tile should not be marked as loading.");
assert.ok( tile.loaded, "The tile should be marked as loaded.");
done();
done = null;
});
await sleep(10);
};
const tileLoaded2 = async (event) => {
assert.notOk( handledOnce, "TileLoaded2 with priority 10 should be called first.");
const tile = event.tile;
//remove handlers immediatelly, processing is async -> removing in the second function could
//get after a different tile gets processed
viewer.removeHandler( 'tile-loaded', tileLoaded1);
viewer.removeHandler( 'tile-loaded', tileLoaded2);
handledOnce = true;
assert.ok( tile.loading, "The tile should be marked as loading.");
assert.notOk( tile.loaded, "The tile should not be marked as loaded.");
event.promise.then(() => {
assert.notOk( tile.loading, "The tile should not be marked as loading.");
assert.ok( tile.loaded, "The tile should be marked as loaded.");
});
await sleep(30);
};
//first will get called tileLoaded2 although registered later
viewer.addHandler( 'tile-loaded', tileLoaded1, null, 5);
viewer.addHandler( 'tile-loaded', tileLoaded2, null, 10);
viewer.open( '/test/data/testpattern.dzi' );
} );
QUnit.test( 'Viewer: tile-unloaded event.', function(assert) {
var tiledImage;
var tile;
var tiles = [];
var done = assert.async();
function tileLoaded( event ) {
viewer.removeHandler( 'tile-loaded', tileLoaded);
tiledImage = event.tiledImage;
tile = event.tile;
setTimeout(function() {
tiledImage.reset();
}, 0);
tiles.push(event.tile);
if (tiles.length === 1) {
setTimeout(function() {
tiledImage.reset();
}, 0);
}
}
function tileUnloaded( event ) {
viewer.removeHandler( 'tile-loaded', tileLoaded);
viewer.removeHandler( 'tile-unloaded', tileUnloaded );
assert.equal( tile, event.tile,
"The unloaded tile should be the same than the loaded one." );
assert.equal( tiles.find(t => t === event.tile), event.tile,
"The unloaded tile should be one of the loaded tiles." );
assert.equal( tiledImage, event.tiledImage,
"The tiledImage of the unloaded tile should be the same than the one of the loaded one." );
done();

View file

@ -5,15 +5,23 @@
// This module tests whether our various file formats can be opened.
// TODO: Add more file formats (with corresponding test data).
var viewer = null;
QUnit.module('Formats', {
beforeEach: function () {
var example = document.createElement("div");
example.id = "example";
document.getElementById("qunit-fixture").appendChild(example);
},
afterEach: function () {
if (viewer){
viewer.destroy();
}
viewer = null;
}
});
var viewer = null;
// ----------
var testOpenUrl = function(relativeUrl, assert) {
@ -21,7 +29,7 @@
};
var testOpen = function(tileSource, assert) {
var timeWatcher = Util.timeWatcher(assert, 7000);
const done = assert.async();
viewer = OpenSeadragon({
id: 'example',
@ -34,12 +42,12 @@
var openHandler = function(event) {
viewer.removeHandler('open', openHandler);
assert.ok(true, 'Open event was sent');
viewer.addHandler('tile-drawn', tileDrawnHandler);
viewer.addHandler('tiled-image-drawn', tileDrawnHandler);
};
var tileDrawnHandler = function(event) {
viewer.removeHandler('tile-drawn', tileDrawnHandler);
assert.ok(true, 'A tile has been drawn');
viewer.removeHandler('tiled-image-drawn', tileDrawnHandler);
assert.ok(true, 'A tiled image has been drawn');
viewer.addHandler('close', closeHandler);
viewer.close();
};
@ -48,7 +56,7 @@
viewer.removeHandler('close', closeHandler);
$('#example').empty();
assert.ok(true, 'Close event was sent');
timeWatcher.done();
done();
};
viewer.addHandler('open', openHandler);
};

View file

@ -107,6 +107,21 @@
],
"profile": "level0"
},
infoJson3level0WithTiles = {
"@context": "http://iiif.io/api/image/3/context.json",
"id": id,
"width": 2000,
"height": 1000,
"tiles": [
{ "width": 256, "scaleFactors": [ 2, 4, 1 ] }
],
"sizes": [
{ width: 2000, height: 1000 },
{ width: 1000, height: 500 },
{ width: 500, height: 250 }
],
"profile": "level0"
},
infoJson3level0ContextExtension = {
"@context": [
"http://iiif.io/api/image/3/context.json",
@ -141,6 +156,21 @@
"width": 2000,
"height": 1000,
"profile": "level1"
},
infoJson3DescendingSizeOrder = {
"@context": "http://iiif.io/api/image/3/context.json",
"id": id,
"width": 2000,
"height": 1000,
"tiles": [
{ "width": 512, "scaleFactors": [ 1, 2, 4 ] }
],
"sizes": [
{ width: 2000, height: 1000 },
{ width: 1000, height: 500 },
{ width: 500, height: 250 }
],
"profile": "level1",
};
QUnit.module('IIIF');
@ -213,7 +243,7 @@
/*
* FIXME: following https://iiif.io/api/image/3.0/#47-canonical-uri-syntax and
* https://iiif.io/api/image/2.1/#canonical-uri-syntax, I'd expect 'max' to be required to
* be served by a level 0 compliant service instead of 'w,h', 'full' instead of 'w,' respectivley.
* be served by a level 0 compliant service instead of 'w,h', 'full' instead of 'w,' respectively.
*/
//assert.equal(levelsVersion3[1].url, 'http://example.com/identifier/full/max/0/default.jpg');
});
@ -238,6 +268,11 @@
assert.equal(source2Level0.getTileUrl(0, 0, 0), "http://example.com/identifier/full/1000,/0/default.jpg");
assert.equal(source2Level0.getTileUrl(1, 0, 0), "http://example.com/identifier/full/2000,/0/default.jpg");
var source3Level0WithTiles = getSource(infoJson3level0WithTiles);
assert.equal(source3Level0WithTiles.getTileUrl(0, 0, 0), "http://example.com/identifier/0,0,1024,1000/256,250/0/default.jpg");
assert.equal(source3Level0WithTiles.getTileUrl(1, 1, 0), "http://example.com/identifier/512,0,512,512/256,256/0/default.jpg");
assert.equal(source3Level0WithTiles.getTileUrl(2, 0, 0), "http://example.com/identifier/0,0,256,256/256,256/0/default.jpg");
var source3Level1 = getSource(infoJson3level1);
assert.equal(source3Level1.getTileUrl(0, 0, 0), "http://example.com/identifier/full/8,4/0/default.jpg");
assert.equal(source3Level1.getTileUrl(7, 0, 0), "http://example.com/identifier/0,0,1024,1000/512,500/0/default.jpg");
@ -246,6 +281,11 @@
assert.equal(source3Level1.getTileUrl(8, 3, 0), "http://example.com/identifier/1536,0,464,512/464,512/0/default.jpg");
assert.equal(source3Level1.getTileUrl(8, 0, 1), "http://example.com/identifier/0,512,512,488/512,488/0/default.jpg");
assert.equal(source3Level1.getTileUrl(8, 3, 1), "http://example.com/identifier/1536,512,464,488/464,488/0/default.jpg");
var source3DescendingSizeOrder = getSource(infoJson3DescendingSizeOrder);
assert.equal(source3DescendingSizeOrder.getTileUrl(0, 0, 0), "http://example.com/identifier/full/500,250/0/default.jpg");
assert.equal(source3DescendingSizeOrder.getTileUrl(1, 1, 0), "http://example.com/identifier/1024,0,976,1000/488,500/0/default.jpg");
assert.equal(source3DescendingSizeOrder.getTileUrl(2, 0, 0), "http://example.com/identifier/0,0,512,512/512,512/0/default.jpg");
});
})();

View file

@ -15,8 +15,8 @@
testLog.reset();
},
afterEach: function () {
if (viewer && viewer.close) {
viewer.close();
if (viewer){
viewer.destroy();
}
viewer = null;

View file

@ -2,275 +2,280 @@
( function() {
var viewer;
const drawerTypes = ['webgl','canvas'];
drawerTypes.forEach(runDrawerTests);
QUnit.module( 'Multi-Image', {
beforeEach: function() {
$( '<div id="example"></div>' ).appendTo( "#qunit-fixture" );
function runDrawerTests(drawerType){
testLog.reset();
QUnit.module( 'Multi-Image-'+drawerType, {
beforeEach: function() {
$( '<div id="example"></div>' ).appendTo( "#qunit-fixture" );
viewer = OpenSeadragon( {
id: 'example',
prefixUrl: '/build/openseadragon/images/',
springStiffness: 100 // Faster animation = faster tests
});
},
afterEach: function() {
if ( viewer && viewer.close ) {
viewer.close();
}
testLog.reset();
viewer = null;
$("#example").remove();
}
} );
// ----------
QUnit.test( 'Multi-image operations', function(assert) {
var done = assert.async();
assert.expect( 24 );
viewer.addHandler( "open", function( ) {
assert.equal( 1, viewer.world.getItemCount( ),
"One item should be present after opening." );
var options = {
tileSource: {
type: 'legacy-image-pyramid',
levels: [ {
url: "data/A.png",
width: 1000,
height: 1000
} ]
viewer = OpenSeadragon( {
id: 'example',
prefixUrl: '/build/openseadragon/images/',
springStiffness: 100, // Faster animation = faster tests
drawer: drawerType
});
},
afterEach: function() {
if (viewer){
viewer.destroy();
}
};
viewer.addTiledImage( options );
viewer.world.addHandler( "add-item", function addFirstItemHandler( event ) {
viewer.world.removeHandler( "add-item", addFirstItemHandler );
var item1 = event.item;
assert.equal( viewer.world.getItemCount( ), 2,
"2 items should be present after adding a item." );
assert.equal( viewer.world.getIndexOfItem( item1 ), 1,
"The first added item should have a index of 1" );
assert.equal( viewer.world.getItemAt( 1 ), item1,
"The item at index 1 should be the first added item." );
viewer = null;
$("#example").remove();
}
} );
// ----------
QUnit.test( 'Multi-image operations', function(assert) {
var done = assert.async();
assert.expect( 24 );
viewer.addHandler( "open", function( ) {
assert.equal( 1, viewer.world.getItemCount( ),
"One item should be present after opening." );
var options = {
tileSource: {
type: 'legacy-image-pyramid',
levels: [ {
url: "data/A.png",
width: 1000,
height: 1000
} ]
}
};
viewer.addTiledImage( options );
viewer.world.addHandler( "add-item", function addSecondItemHandler( event ) {
viewer.world.removeHandler( "add-item", addSecondItemHandler );
var item2 = event.item;
assert.equal( viewer.world.getItemCount( ), 3,
"3 items should be present after adding a second item." );
assert.equal( viewer.world.getIndexOfItem( item2 ), 2,
"If not specified, a item should be added with the highest index." );
assert.equal( viewer.world.getItemAt( 2 ), item2,
"The item at index 2 should be the second added item." );
viewer.world.addHandler( "add-item", function addFirstItemHandler( event ) {
viewer.world.removeHandler( "add-item", addFirstItemHandler );
var item1 = event.item;
assert.equal( viewer.world.getItemCount( ), 2,
"2 items should be present after adding a item." );
assert.equal( viewer.world.getIndexOfItem( item1 ), 1,
"The first added item should have a index of 1" );
assert.equal( viewer.world.getItemAt( 1 ), item1,
"The item at index 1 should be the first added item." );
viewer.world.addHandler( "item-index-change",
function itemIndexChangedHandler( event ) {
viewer.world.removeHandler( "item-index-change",
itemIndexChangedHandler );
assert.equal( event.item, item2,
"The item which changed index should be item2" );
assert.equal( event.previousIndex, 2, "Previous index should be 2." );
assert.equal( event.newIndex, 1, "New index should be 1." );
});
viewer.world.setItemIndex( item2, 1 );
assert.equal( viewer.world.getIndexOfItem( item2 ), 1,
"Item2 index should be 1 after setItemIndex." );
assert.equal( viewer.world.getIndexOfItem( item1 ), 2,
"Item1 index should be 2 after setItemIndex." );
assert.equal( viewer.world.getItemAt( 1 ), item2,
"The item at index 1 should be item2." );
assert.equal( viewer.world.getItemAt( 2 ), item1,
"The item at index 2 should be item1." );
options.index = 2;
options.tileSource.levels[0].url = "data/CCyan.png";
viewer.addTiledImage( options );
viewer.world.addHandler( "add-item", function addThirdItemHandler( event ) {
viewer.world.removeHandler( "add-item", addThirdItemHandler );
var item3 = event.item;
assert.equal( viewer.world.getItemCount( ), 4,
"4 items should be present after adding a third item." );
assert.equal( viewer.world.getIndexOfItem( item3 ), 2,
"Item 3 should be added with index 2." );
viewer.world.addHandler( "add-item", function addSecondItemHandler( event ) {
viewer.world.removeHandler( "add-item", addSecondItemHandler );
var item2 = event.item;
assert.equal( viewer.world.getItemCount( ), 3,
"3 items should be present after adding a second item." );
assert.equal( viewer.world.getIndexOfItem( item2 ), 2,
"If not specified, a item should be added with the highest index." );
assert.equal( viewer.world.getItemAt( 2 ), item2,
"The item at index 2 should be the second added item." );
viewer.world.addHandler( "item-index-change",
function itemIndexChangedHandler( event ) {
viewer.world.removeHandler( "item-index-change",
itemIndexChangedHandler );
assert.equal( event.item, item2,
"The item which changed index should be item2" );
assert.equal( event.previousIndex, 2, "Previous index should be 2." );
assert.equal( event.newIndex, 1, "New index should be 1." );
});
viewer.world.setItemIndex( item2, 1 );
assert.equal( viewer.world.getIndexOfItem( item2 ), 1,
"Item 2 should stay at index 1." );
"Item2 index should be 1 after setItemIndex." );
assert.equal( viewer.world.getIndexOfItem( item1 ), 2,
"Item1 index should be 2 after setItemIndex." );
assert.equal( viewer.world.getItemAt( 1 ), item2,
"The item at index 1 should be item2." );
assert.equal( viewer.world.getItemAt( 2 ), item1,
"The item at index 2 should be item1." );
options.index = 2;
options.replace = true;
options.tileSource.levels[0].url = "data/CCyan.png";
viewer.addTiledImage( options );
viewer.world.addHandler( "add-item", function replaceAddItemHandler( event ) {
viewer.world.removeHandler( "add-item", replaceAddItemHandler );
var item4 = event.item;
viewer.world.addHandler( "add-item", function addThirdItemHandler( event ) {
viewer.world.removeHandler( "add-item", addThirdItemHandler );
var item3 = event.item;
assert.equal( viewer.world.getItemCount( ), 4,
"4 items should still be present after replacing the second item." );
assert.equal( viewer.world.getIndexOfItem( item4 ), 2,
"Item 4 should be added with index 2." );
assert.equal( viewer.world.getIndexOfItem( item3 ), -1,
"Item 3 should be at index -1." );
"4 items should be present after adding a third item." );
assert.equal( viewer.world.getIndexOfItem( item3 ), 2,
"Item 3 should be added with index 2." );
assert.equal( viewer.world.getIndexOfItem( item2 ), 1,
"Item 2 should stay at index 1." );
viewer.world.addHandler( "remove-item", function removeItemHandler( event ) {
viewer.world.removeHandler( "remove-item", removeItemHandler );
options.index = 2;
options.replace = true;
viewer.addTiledImage( options );
viewer.world.addHandler( "add-item", function replaceAddItemHandler( event ) {
viewer.world.removeHandler( "add-item", replaceAddItemHandler );
var item4 = event.item;
assert.equal( viewer.world.getItemCount( ), 4,
"4 items should still be present after replacing the second item." );
assert.equal( viewer.world.getIndexOfItem( item4 ), 2,
"Item 4 should be added with index 2." );
assert.equal( viewer.world.getIndexOfItem( item3 ), -1,
"Item 3 should be at index -1." );
assert.equal( item2, event.item, "Removed item should be item2." );
viewer.world.addHandler( "remove-item", function removeItemHandler( event ) {
viewer.world.removeHandler( "remove-item", removeItemHandler );
assert.equal( viewer.world.getIndexOfItem( item1 ), 2,
"Item 1 should be at index 2." );
assert.equal( viewer.world.getIndexOfItem( item2 ), -1,
"Item 2 should be at index -1." );
assert.equal( viewer.world.getIndexOfItem( item4 ), 1,
"Item 4 should be at index 1." );
assert.equal( item2, event.item, "Removed item should be item2." );
done();
assert.equal( viewer.world.getIndexOfItem( item1 ), 2,
"Item 1 should be at index 2." );
assert.equal( viewer.world.getIndexOfItem( item2 ), -1,
"Item 2 should be at index -1." );
assert.equal( viewer.world.getIndexOfItem( item4 ), 1,
"Item 4 should be at index 1." );
done();
});
viewer.world.removeItem( item2 );
});
});
});
});
});
viewer.open( '/test/data/testpattern.dzi' );
});
viewer.world.removeItem( item2 );
// ----------
QUnit.test( 'Sequences as items', function(assert) {
var done = assert.async();
var options = {
tileSource: [{
type: 'legacy-image-pyramid',
levels: [{
url: "data/A.png",
width: 1000,
height: 1000
}]
}, {
type: 'legacy-image-pyramid',
levels: [{
url: "data/BBlue.png",
width: 1000,
height: 1000
}]
}]
};
viewer.addHandler( "open", function openHandler() {
viewer.removeHandler( "open", openHandler );
viewer.addHandler( "add-item-failed",
function addItemFailedHandler( event ) {
viewer.removeHandler( "add-item-failed", addItemFailedHandler );
assert.equal( event.message, "[Viewer.addTiledImage] Sequences can not be added; add them one at a time instead." );
assert.equal( event.options, options, "Item failed event should give the options." );
done();
} );
viewer.addTiledImage( options );
});
viewer.open( '/test/data/testpattern.dzi' );
});
// ----------
QUnit.test('items are added in order', function(assert) {
var done = assert.async();
viewer.addHandler('open', function(event) {
assert.equal(viewer.world.getItemAt(0).getContentSize().y, 2000, 'first image is tall');
assert.equal(viewer.world.getItemAt(0).getBounds().width, 4, 'first image has 4 width');
assert.equal(viewer.world.getItemAt(1).getContentSize().x, 2000, 'second image is wide');
assert.equal(viewer.world.getItemAt(1).getBounds().width, 2, 'second image has 2 width');
done();
});
viewer.open([
{
tileSource: '/test/data/tall.dzi',
width: 4
}, {
tileSource: '/test/data/wide.dzi',
width: 2
}
]);
});
QUnit.test('Viewer.addSimpleImage', function(assert) {
var done = assert.async();
viewer.addHandler("open", function openHandler() {
viewer.removeHandler("open", openHandler);
viewer.world.addHandler('add-item', function itemAdded(event) {
viewer.world.removeHandler('add-item', itemAdded);
assert.equal(event.item.opacity, 0.5,
'Opacity option should be set when using addSimpleImage');
done();
});
viewer.addSimpleImage({
url: '/test/data/A.png',
opacity: 0.5
});
});
viewer.open('/test/data/testpattern.dzi');
});
QUnit.test('Transparent image on top of others', function(assert) {
var done = assert.async();
viewer.open('/test/data/testpattern.dzi');
function getPixelFromViewerScreenCoords(x, y) {
const density = OpenSeadragon.pixelDensityRatio;
const imageData = viewer.drawer.context.getImageData(x * density, y * density, 1, 1);
return {
r: imageData.data[0],
g: imageData.data[1],
b: imageData.data[2],
a: imageData.data[3]
};
}
viewer.addHandler('open', function() {
var firstImage = viewer.world.getItemAt(0);
firstImage.addHandler('fully-loaded-change', function() {
viewer.addOnceHandler('update-viewport', function(){
// Pixel 250,250 will be in the hole of the A
var expectedVal = getPixelFromViewerScreenCoords(250, 250);
assert.notEqual(expectedVal.r, 0, 'Red channel should not be 0');
assert.notEqual(expectedVal.g, 0, 'Green channel should not be 0');
assert.notEqual(expectedVal.b, 0, 'Blue channel should not be 0');
assert.notEqual(expectedVal.a, 0, 'Alpha channel should not be 0');
viewer.addSimpleImage({
url: '/test/data/A.png',
success: function() {
var secondImage = viewer.world.getItemAt(1);
secondImage.addHandler('fully-loaded-change', function() {
viewer.addOnceHandler('update-viewport', function(){
var actualVal = getPixelFromViewerScreenCoords(250, 250);
assert.equal(actualVal.r, expectedVal.r,
'Red channel should not change in transparent part of the A');
assert.equal(actualVal.g, expectedVal.g,
'Green channel should not change in transparent part of the A');
assert.equal(actualVal.b, expectedVal.b,
'Blue channel should not change in transparent part of the A');
assert.equal(actualVal.a, expectedVal.a,
'Alpha channel should not change in transparent part of the A');
var onAVal = getPixelFromViewerScreenCoords(333 , 250);
assert.equal(onAVal.r, 0, 'Red channel should be 0 on the A');
assert.equal(onAVal.g, 0, 'Green channel should be 0 on the A');
assert.equal(onAVal.b, 0, 'Blue channel should be 0 on the A');
assert.equal(onAVal.a, 255, 'Alpha channel should be 255 on the A');
done();
});
// trigger a redraw so the event fires
firstImage.redraw();
});
}
});
});
});
});
});
viewer.open( '/test/data/testpattern.dzi' );
});
// ----------
QUnit.test( 'Sequences as items', function(assert) {
var done = assert.async();
var options = {
tileSource: [{
type: 'legacy-image-pyramid',
levels: [{
url: "data/A.png",
width: 1000,
height: 1000
}]
}, {
type: 'legacy-image-pyramid',
levels: [{
url: "data/BBlue.png",
width: 1000,
height: 1000
}]
}]
};
viewer.addHandler( "open", function openHandler() {
viewer.removeHandler( "open", openHandler );
viewer.addHandler( "add-item-failed",
function addItemFailedHandler( event ) {
viewer.removeHandler( "add-item-failed", addItemFailedHandler );
assert.equal( event.message, "[Viewer.addTiledImage] Sequences can not be added; add them one at a time instead." );
assert.equal( event.options, options, "Item failed event should give the options." );
done();
} );
viewer.addTiledImage( options );
});
viewer.open( '/test/data/testpattern.dzi' );
});
// ----------
QUnit.test('items are added in order', function(assert) {
var done = assert.async();
viewer.addHandler('open', function(event) {
assert.equal(viewer.world.getItemAt(0).getContentSize().y, 2000, 'first image is tall');
assert.equal(viewer.world.getItemAt(0).getBounds().width, 4, 'first image has 4 width');
assert.equal(viewer.world.getItemAt(1).getContentSize().x, 2000, 'second image is wide');
assert.equal(viewer.world.getItemAt(1).getBounds().width, 2, 'second image has 2 width');
done();
});
viewer.open([
{
tileSource: '/test/data/tall.dzi',
width: 4
}, {
tileSource: '/test/data/wide.dzi',
width: 2
}
]);
});
QUnit.test('Viewer.addSimpleImage', function(assert) {
var done = assert.async();
viewer.addHandler("open", function openHandler() {
viewer.removeHandler("open", openHandler);
viewer.world.addHandler('add-item', function itemAdded(event) {
viewer.world.removeHandler('add-item', itemAdded);
assert.equal(event.item.opacity, 0.5,
'Opacity option should be set when using addSimpleImage');
done();
});
viewer.addSimpleImage({
url: '/test/data/A.png',
opacity: 0.5
});
});
viewer.open('/test/data/testpattern.dzi');
});
QUnit.test('Transparent image on top of others', function(assert) {
var done = assert.async();
viewer.open('/test/data/testpattern.dzi');
var density = OpenSeadragon.pixelDensityRatio;
viewer.addHandler('open', function() {
var firstImage = viewer.world.getItemAt(0);
firstImage.addHandler('fully-loaded-change', function() {
var imageData = viewer.drawer.context.getImageData(0, 0,
500 * density, 500 * density);
// Pixel 250,250 will be in the hole of the A
var expectedVal = getPixelValue(imageData, 250 * density, 250 * density);
assert.notEqual(expectedVal.r, 0, 'Red channel should not be 0');
assert.notEqual(expectedVal.g, 0, 'Green channel should not be 0');
assert.notEqual(expectedVal.b, 0, 'Blue channel should not be 0');
assert.notEqual(expectedVal.a, 0, 'Alpha channel should not be 0');
viewer.addSimpleImage({
url: '/test/data/A.png',
success: function() {
var secondImage = viewer.world.getItemAt(1);
secondImage.addHandler('fully-loaded-change', function() {
var imageData = viewer.drawer.context.getImageData(0, 0, 500 * density, 500 * density);
var actualVal = getPixelValue(imageData, 250 * density, 250 * density);
assert.equal(actualVal.r, expectedVal.r,
'Red channel should not change in transparent part of the A');
assert.equal(actualVal.g, expectedVal.g,
'Green channel should not change in transparent part of the A');
assert.equal(actualVal.b, expectedVal.b,
'Blue channel should not change in transparent part of the A');
assert.equal(actualVal.a, expectedVal.a,
'Alpha channel should not change in transparent part of the A');
var onAVal = getPixelValue(imageData, 333 * density, 250 * density);
assert.equal(onAVal.r, 0, 'Red channel should be null on the A');
assert.equal(onAVal.g, 0, 'Green channel should be null on the A');
assert.equal(onAVal.b, 0, 'Blue channel should be null on the A');
assert.equal(onAVal.a, 255, 'Alpha channel should be 255 on the A');
done();
});
}
});
});
});
function getPixelValue(imageData, x, y) {
var offset = 4 * (y * imageData.width + x);
return {
r: imageData.data[offset],
g: imageData.data[offset + 1],
b: imageData.data[offset + 2],
a: imageData.data[offset + 3]
};
}
});
}
})();

View file

@ -41,20 +41,26 @@
}
resetTestVariables();
if (viewer){
viewer.destroy();
}
viewer = null;
}
});
var assessNavigatorLocation = function (assert, expectedX, expectedY) {
navigatorElement = navigatorElement || $(".navigator");
Util.assessNumericValue(assert, expectedX, navigatorElement.offset().left, 10, ' Navigator x Position');
Util.assessNumericValue(assert, expectedY, navigatorElement.offset().top, 10, ' Navigator y Position');
Util.assessNumericValue(assert, navigatorElement.offset().left, expectedX, 10, ' Navigator x Position');
Util.assessNumericValue(assert, navigatorElement.offset().top, expectedY, 10, ' Navigator y Position');
};
var assessNavigatorSize = function (assert, expectedWidth, expectedHeight, msg) {
msg = msg || "";
navigatorElement = navigatorElement || $(".navigator");
Util.assessNumericValue(assert, expectedWidth, navigatorElement.width(), 2, ' Navigator Width ' + msg);
Util.assessNumericValue(assert, expectedHeight, navigatorElement.height(), 2, ' Navigator Height ' + msg);
Util.assessNumericValue(assert, navigatorElement.width(), expectedWidth, 2, ' Navigator Width ' + msg);
Util.assessNumericValue(assert, navigatorElement.height(), expectedHeight, 2, ' Navigator Height ' + msg);
};
var assessNavigatorAspectRatio = function (assert, expectedAspectRatio, variance, msg) {
@ -62,8 +68,8 @@
navigatorElement = navigatorElement || $(".navigator");
Util.assessNumericValue(
assert,
expectedAspectRatio,
navigatorElement.width() / navigatorElement.height(),
expectedAspectRatio,
variance,
' Navigator Aspect Ratio ' + msg
);
@ -74,13 +80,14 @@
navigatorElement = navigatorElement || $(".navigator");
Util.assessNumericValue(
assert,
expectedArea,
navigatorElement.width() * navigatorElement.height(),
expectedArea,
Math.max(navigatorElement.width(), navigatorElement.height()),
' Navigator Area ' + msg
);
};
var navigatorRegionBoundsInPoints = function () {
var regionBoundsInPoints,
expectedDisplayRegionWidth,
@ -136,22 +143,23 @@
var expectedBounds = navigatorRegionBoundsInPoints();
Util.assessNumericValue(
assert,
expectedBounds.width,
displayRegion.width() + viewer.navigator.totalBorderWidths.x,
expectedBounds.width,
2,
status + ' Width synchronization'
);
Util.assessNumericValue(
assert,
expectedBounds.height,
displayRegion.height() + viewer.navigator.totalBorderWidths.y,
expectedBounds.height,
2,
status + ' Height synchronization'
);
Util.assessNumericValue(assert, expectedBounds.x, displayRegion.position().left, 2, status + ' Left synchronization');
Util.assessNumericValue(assert, expectedBounds.y, displayRegion.position().top, 2, status + ' Top synchronization');
Util.assessNumericValue(assert, displayRegion.position().left, expectedBounds.x, 2, status + ' Left synchronization');
Util.assessNumericValue(assert, displayRegion.position().top, expectedBounds.y, 2, status + ' Top synchronization');
};
var waitForViewer = function () {
return function (assert, handler, count, lastDisplayRegionLeft, lastDisplayWidth) {
var viewerAndNavigatorDisplayReady = false,
@ -265,14 +273,15 @@
}
Util.assessNumericValue(
assert,
1 / viewer.source.aspectRatio / 2,
viewer.viewport.getCenter().y,
1 / viewer.source.aspectRatio / 2,
yPositionVariance,
' Viewer at center, y coord'
);
Util.assessNumericValue(assert, 0.5, viewer.viewport.getCenter().x, 0.4, ' Viewer at center, x coord');
Util.assessNumericValue(assert, viewer.viewport.getCenter().x, 0.5, 0.4, ' Viewer at center, x coord');
};
var assessViewerInCorner = function (theContentCorner, assert) {
return function () {
var expectedXCoordinate, expectedYCoordinate;
@ -295,8 +304,8 @@
if (viewer.viewport.getBounds().width < 1) {
Util.assessNumericValue(
assert,
expectedXCoordinate,
viewer.viewport.getBounds().x,
expectedXCoordinate,
0.04,
' Viewer at ' + theContentCorner + ', x coord'
);
@ -304,8 +313,8 @@
if (viewer.viewport.getBounds().height < 1 / viewer.source.aspectRatio) {
Util.assessNumericValue(
assert,
expectedYCoordinate,
viewer.viewport.getBounds().y,
expectedYCoordinate,
0.04,
' Viewer at ' + theContentCorner + ', y coord'
);

Some files were not shown because too many files have changed in this diff Show more