mirror of
https://github.com/openseadragon/openseadragon.git
synced 2025-04-03 22:03:31 +03:00
Compare commits
617 commits
Author | SHA1 | Date | |
---|---|---|---|
|
99fcb408fe | ||
|
479ef0ec82 | ||
|
9e01a1e18b | ||
|
2e3f0923f5 | ||
|
3550dfafd3 | ||
|
12de02938a | ||
|
8f093f54df | ||
|
6bdd186219 | ||
|
c54c292873 | ||
|
4c7d9a2ffa | ||
|
595066abd1 | ||
|
b4e14d4804 | ||
|
be87b47469 | ||
|
62fa378f51 | ||
|
c386eca94d | ||
|
8c6d1f4f88 | ||
|
3cb5152905 | ||
|
5cc76737b4 | ||
|
f312bae614 | ||
|
97d388be7d | ||
|
fd27327737 | ||
|
5c60f669ec | ||
|
6ceac75b7a | ||
|
2387c3afd5 | ||
|
5184ed6c9f | ||
|
b22b689dae | ||
|
138215e22f | ||
|
22e766bd1a | ||
|
337af91373 | ||
|
ced1bde338 | ||
|
dbc72d4d98 | ||
|
994f0e25a4 | ||
|
71c3c8437a | ||
|
6e81bedc39 | ||
|
a9562fbb64 | ||
|
b4c7e75109 | ||
|
301191a0b8 | ||
|
edb422007c | ||
|
f8aad5f237 | ||
|
1286fa4549 | ||
|
03b7c5b9a6 | ||
|
7dfde9da3a | ||
|
ffe12888cf | ||
|
8b30e7a307 | ||
|
306adb9b6a | ||
|
226a44c498 | ||
|
b1feb367b8 | ||
|
cb06a5c0fb | ||
|
9b8b4c2728 | ||
|
d6740dab25 | ||
|
003dd3a9da | ||
|
a1fa790e19 | ||
|
3aaa8fa721 | ||
|
39671f4d78 | ||
|
426700b1c6 | ||
|
6315662078 | ||
|
e193557e56 | ||
|
b8b6a9656a | ||
|
f5f9eab200 | ||
|
3f22b6aa3a | ||
|
641e085259 | ||
|
9527f15f52 | ||
|
865c3884b2 | ||
|
56d0d982da | ||
|
58f4e6a36e | ||
|
ef0f14a03d | ||
|
d31562e34f | ||
|
ad943e5472 | ||
|
4c2b0715af | ||
|
a5c569ab5d | ||
|
8fdd639bf0 | ||
|
a0fcfc20ad | ||
|
15627ee18e | ||
|
33c3d4b380 | ||
|
74ffb66f40 | ||
|
55f53e8369 | ||
|
acb7563257 | ||
|
c06e719198 | ||
|
64bb7e25c7 | ||
|
c392f2d205 | ||
|
45bd3f48f9 | ||
|
bee3c243fb | ||
|
563c7674c7 | ||
|
fec033f9d2 | ||
|
e12a349f52 | ||
|
bee5cb2471 | ||
|
c8c7b481b8 | ||
|
480de92d0a | ||
|
e9dd9e9dfc | ||
|
07502e6d58 | ||
|
1843bbd89c | ||
|
a5987db8d5 | ||
|
039ffbd37a | ||
|
1a7a5ee1d6 | ||
|
9b96707dec | ||
|
3ce307f309 | ||
|
d6bb8d3bd1 | ||
|
271f437568 | ||
|
5438fd65e1 | ||
|
85e8b381b8 | ||
|
ef7628f098 | ||
|
17f13885c7 | ||
|
6b4c0f873a | ||
|
8b16628950 | ||
|
04211fbb27 | ||
|
3885fa7bfc | ||
|
f03f2a5d31 | ||
|
ce4b16616d | ||
|
af9bf9e07f | ||
|
1851405fcf | ||
|
0bc7deccd7 | ||
|
2dc0e29647 | ||
|
0e3abe16f4 | ||
|
cd9e4e8c4e | ||
|
02805226d4 | ||
|
4163a46a24 | ||
|
16dbfc62be | ||
|
e24f7d1358 | ||
|
541fe2e4df | ||
|
e059b8982e | ||
|
3b1b2d6d23 | ||
|
535507568f | ||
|
cc7474ec9b | ||
|
9bfdd55b2e | ||
|
5fdeb382ea | ||
|
cf65f1a4f4 | ||
|
f127014f0f | ||
|
e1e0ec6b24 | ||
|
faf6d19db5 | ||
|
fab607584c | ||
|
cd60aff5dc | ||
|
207bc88aab | ||
|
6cbe359398 | ||
|
82314dd4bf | ||
|
12e4607a30 | ||
|
20177116e7 | ||
|
e403e29312 | ||
|
82e1160508 | ||
|
d5cdf59993 | ||
|
3c6c7e0ab7 | ||
|
68f0ed8901 | ||
|
b3cdeabf02 | ||
|
0cd17abafd | ||
|
1e47bd6add | ||
|
6c20101c8b | ||
|
bf25e2f069 | ||
|
06ac68d00e | ||
|
1b6fea72d8 | ||
|
0b63a943b6 | ||
|
f8e5cff117 | ||
|
b6693ee50d | ||
|
8bdc0a4146 | ||
|
3e0e39900e | ||
|
263a664e58 | ||
|
3d21ec897b | ||
|
2033814227 | ||
|
17c34a7985 | ||
|
53e56932bd | ||
|
2b010a9a59 | ||
|
4d4ccb99ba | ||
|
c0eae951c6 | ||
|
b28b77fdbd | ||
|
3b99665b32 | ||
|
50d7db595b | ||
|
fe7e9a3315 | ||
|
e3af370832 | ||
|
63180a1589 | ||
|
c04b6af937 | ||
|
29b01cf1bd | ||
|
a10b9086ab | ||
|
a9f81190dd | ||
|
f3b92e0562 | ||
|
cba40f4db8 | ||
|
1b6f79661b | ||
|
999ff30e74 | ||
|
d97ba581b0 | ||
|
f28b7fc116 | ||
|
a803e826a3 | ||
|
8d2fd759cc | ||
|
d4c9b601c8 | ||
|
7740accfaf | ||
|
8f6771ba91 | ||
|
980deca107 | ||
|
2db1c2f1da | ||
|
b87eaf3040 | ||
|
37ce7cc256 | ||
|
74b69d99de | ||
|
819d4cb20c | ||
|
c1c1d480dd | ||
|
2c5d2eb7c6 | ||
|
396fcb33a5 | ||
|
176fae11e5 | ||
|
fd9b701272 | ||
|
c0655ba370 | ||
|
c08ef0ee36 | ||
|
fc7b942bc2 | ||
|
2290e5d08d | ||
|
ee6751e02f | ||
|
3ed100d218 | ||
|
2c6b970f29 | ||
|
263dc85fbd | ||
|
79eecdcc76 | ||
|
44347fb2d0 | ||
|
72d7c5353e | ||
|
88d7eb8c60 | ||
|
db0ab10428 | ||
|
fd74ea3dca | ||
|
a96d186650 | ||
|
35d468c4db | ||
|
65c727079c | ||
|
feb5e13c32 | ||
|
94954a699c | ||
|
3f5735be67 | ||
|
834ed6ede5 | ||
|
0a035afc2d | ||
|
cdb89ff5ad | ||
|
65d30e7ce1 | ||
|
f7c12a716b | ||
|
be30b429f8 | ||
|
3f03bd6e20 | ||
|
9ef1c5952e | ||
|
5be44521b5 | ||
|
8d66acde53 | ||
|
f3a942c522 | ||
|
3d54f8f686 | ||
|
441ea71f40 | ||
|
d68a4024f8 | ||
|
8b401e65e3 | ||
|
9d6a785aac | ||
|
f2c8db5db0 | ||
|
f8ef6818de | ||
|
4f2bcbb1e3 | ||
|
7ce5499868 | ||
|
e809c9f198 | ||
|
f7c8e4cf33 | ||
|
41c3abb12a | ||
|
3d97ac8ae7 | ||
|
86395dfc78 | ||
|
49e2d7a987 | ||
|
5fefb8f976 | ||
|
6a4c3838c8 | ||
|
9f7b18adb6 | ||
|
6ea7779318 | ||
|
0ba2f75213 | ||
|
31c2c85c4f | ||
|
0621958ca2 | ||
|
4451131999 | ||
|
b0926b3e69 | ||
|
63d8d63c1f | ||
|
a2f6da52c0 | ||
|
5990e5649b | ||
|
6a47104b4f | ||
|
9b7fc464ca | ||
|
262d04c6c6 | ||
|
9a92d9e9ad | ||
|
553795e8fa | ||
|
afeea19403 | ||
|
029a40aa21 | ||
|
3e04c8854e | ||
|
e0f442209b | ||
|
2cb1835533 | ||
|
c177246675 | ||
|
e2c633a23b | ||
|
47419a090a | ||
|
52ef8156c0 | ||
|
a9b50a8fdb | ||
|
135fa76fde | ||
|
360f0d6796 | ||
|
4d26c800ba | ||
|
a42a0d1822 | ||
|
e4fd005fb0 | ||
|
1c0797e800 | ||
|
01e70ab7d8 | ||
|
b6501a3786 | ||
|
7dfc60a97f | ||
|
da5abc1b39 | ||
|
5df791fc82 | ||
|
ae5f08b9bd | ||
|
a0bcbc4d21 | ||
|
c6e3e06194 | ||
|
1442e5d4e4 | ||
|
99c35aa3cb | ||
|
02898cfc17 | ||
|
59645e3a0d | ||
|
4c331a2000 | ||
|
b62fba80d6 | ||
|
1c60e45a7f | ||
|
6e4914ada3 | ||
|
8967e2bb03 | ||
|
0a154a3b21 | ||
|
63f0adbc15 | ||
|
d91df0126b | ||
|
3f21f84df4 | ||
|
c734de531f | ||
|
d4e82d374e | ||
|
5b2c6d7ed9 | ||
|
8f483a3ba0 | ||
|
cae6ec6bee | ||
|
a97fe34d74 | ||
|
9ef2d46e75 | ||
|
fcf20be8ea | ||
|
3fa13570ef | ||
|
f7d86ac244 | ||
|
e3c3634266 | ||
|
3ced918fb4 | ||
|
23e50b6038 | ||
|
fdaa3e2f16 | ||
|
2e0868023a | ||
|
98769af678 | ||
|
e08274e2b0 | ||
|
68623ace26 | ||
|
921171ed2b | ||
|
84a55968ee | ||
|
1b44de3b68 | ||
|
f9a8b97cf9 | ||
|
52fc10ffa2 | ||
|
59519e0e96 | ||
|
d490592b99 | ||
|
024cee42f5 | ||
|
a55071f67b | ||
|
2add108539 | ||
|
c0104c7480 | ||
|
5be1ab6f7d | ||
|
4811dd0320 | ||
|
362a060512 | ||
|
d2376b571f | ||
|
4e9134f66a | ||
|
dc03989bbe | ||
|
3cdd5fd711 | ||
|
f6c35b44ed | ||
|
43de595c24 | ||
|
a2d31e9610 | ||
|
8271b6d8b2 | ||
|
aae82ab16a | ||
|
525d9b52fe | ||
|
bb676a8699 | ||
|
fd648c26e9 | ||
|
f6c67b2159 | ||
|
0a2657cb5b | ||
|
44b66be0a9 | ||
|
db11611d7d | ||
|
b902256a59 | ||
|
f563600635 | ||
|
e0fd78344b | ||
|
3bab3c2190 | ||
|
500c22feb1 | ||
|
8be2ca2dfb | ||
|
834795b4b8 | ||
|
9725ac0398 | ||
|
58f918aaa1 | ||
|
f3a90159c1 | ||
|
1239b22c30 | ||
|
1a1e2c20c0 | ||
|
22ecdfd75d | ||
|
bbee6e9605 | ||
|
40a4e42835 | ||
|
1588f6b715 | ||
|
55882b9215 | ||
|
d5a3cabf13 | ||
|
9616e26dd2 | ||
|
074b65bfcd | ||
|
3f7e7141b9 | ||
|
2d8c6fe4c3 | ||
|
a364c3f168 | ||
|
3d6eb1b91c | ||
|
cf2413e0c9 | ||
|
a690b50eee | ||
|
e50d3639ce | ||
|
1200f0b081 | ||
|
32f12b3ba5 | ||
|
9dc96390fc | ||
|
6b995c018b | ||
|
36d8b3d9ad | ||
|
c86158f0ca | ||
|
90ce0669c5 | ||
|
2c67860c61 | ||
|
a578b97d96 | ||
|
191ba39cbb | ||
|
23496cb049 | ||
|
f4efe2970a | ||
|
2a1090ffa8 | ||
|
219049976c | ||
|
c3ab9a08e7 | ||
|
023a864a36 | ||
|
6abcc76a93 | ||
|
c92f8ad65d | ||
|
c1038af37d | ||
|
89ae9c1376 | ||
|
a8d37d892e | ||
|
e836ffcc61 | ||
|
810987210c | ||
|
6447009c18 | ||
|
cd9d340038 | ||
|
5272cf092c | ||
|
b92a428e88 | ||
|
31f9a71109 | ||
|
174c4c709a | ||
|
e3024deb46 | ||
|
f796925ae5 | ||
|
750d45be81 | ||
|
5542ead48b | ||
|
f01a7a4b3c | ||
|
640526b444 | ||
|
8d2f8449ba | ||
|
ed49d46237 | ||
|
9b051f55de | ||
|
b15ba75551 | ||
|
17cd6b51ce | ||
|
cca81a37d5 | ||
|
0bdd807d9d | ||
|
f8ad0acfa4 | ||
|
55a05963a2 | ||
|
e07745d790 | ||
|
4a3fc5b325 | ||
|
2fcdcc3f6a | ||
|
fa8196ef4f | ||
|
1f47f5def1 | ||
|
77b5766fa2 | ||
|
13955b0bf6 | ||
|
c77a7e7386 | ||
|
17873001b8 | ||
|
054ad0d53b | ||
|
a907aaa160 | ||
|
b6273b95b8 | ||
|
d912ff3196 | ||
|
ca45e96142 | ||
|
046612f8da | ||
|
1e37745764 | ||
|
f4ded1630f | ||
|
3c3842bdce | ||
|
ccb4ae9f86 | ||
|
9684a83b8c | ||
|
7ad748799e | ||
|
f510301922 | ||
|
f50df84287 | ||
|
9f31919dc1 | ||
|
e51053a6a3 | ||
|
eec052f054 | ||
|
be4d5ad7f7 | ||
|
01a4ea6f2c | ||
|
6feed0a68e | ||
|
0a3aa6172d | ||
|
048b43e196 | ||
|
cc412f0a20 | ||
|
3a5a738508 | ||
|
83ec2bb1f0 | ||
|
24c4d2d2bc | ||
|
2ec2c0f2c7 | ||
|
386ca85db8 | ||
|
ffbd8f985a | ||
|
88f419330e | ||
|
3386a73e44 | ||
|
eaa41f7f05 | ||
|
128975ea0f | ||
|
0584ca7be9 | ||
|
37b8ef9519 | ||
|
7bb02d51d0 | ||
|
7dec7b9aba | ||
|
2e248baf91 | ||
|
354590a17a | ||
|
8fda8ceae7 | ||
|
8fe6a5f6e6 | ||
|
1bc93be3f2 | ||
|
36084703f0 | ||
|
1089f79a5b | ||
|
2a0af48bc1 | ||
|
dab8a9a3cd | ||
|
2f3bef0865 | ||
|
467597e2c2 | ||
|
5eece7d73a | ||
|
69c4ccf33e | ||
|
5328761877 | ||
|
f9ab63944b | ||
|
3fc156d513 | ||
|
c9cd7352c4 | ||
|
ebab356c20 | ||
|
f96db3cf0f | ||
|
f004ecf323 | ||
|
2c03c1bbe9 | ||
|
b405a904a6 | ||
|
c2a1df9ae0 | ||
|
c69950c6e4 | ||
|
c7df7be7f1 | ||
|
ff67cf1cae | ||
|
ad1179b9db | ||
|
ea54427f42 | ||
|
b9cb52a184 | ||
|
3d57722b9f | ||
|
ae9ad111c4 | ||
|
160cae7a4a | ||
|
884968139d | ||
|
324dc4ed30 | ||
|
dedd85e184 | ||
|
7fb5744495 | ||
|
d897e5454f | ||
|
01d940b938 | ||
|
e6f2d3e07b | ||
|
4060dee4f9 | ||
|
e983bd8108 | ||
|
857a825ed8 | ||
|
6fd828974d | ||
|
e51aa4a9c5 | ||
|
182c11481e | ||
|
6981cfed01 | ||
|
d4057aca12 | ||
|
528df37335 | ||
|
b23e8295d3 | ||
|
13603dd5f4 | ||
|
fe7a5eb01b | ||
|
3c2628f182 | ||
|
b1274515aa | ||
|
b088fc0da3 | ||
|
d36c76127c | ||
|
c5404006b2 | ||
|
5fd125dc92 | ||
|
877c3b68ed | ||
|
0c358c140d | ||
|
60a6a610e0 | ||
|
f91dbdd2c7 | ||
|
d18a4c3fd7 | ||
|
ebda5a909b | ||
|
4f737f1714 | ||
|
e6b9c79bc1 | ||
|
8b305546ed | ||
|
5b56d6fa16 | ||
|
2e99e48603 | ||
|
b45d500f1e | ||
|
cec0acafe1 | ||
|
073b2e0b1f | ||
|
3488d18e4c | ||
|
bff93af17e | ||
|
84d2dcf11d | ||
|
244e54da69 | ||
|
292b62ba21 | ||
|
dec2afabb2 | ||
|
2ae9fb0e13 | ||
|
56ae09fc8a | ||
|
5f816246e4 | ||
|
fb32cc7198 | ||
|
a01c230b6e | ||
|
8a4c95ef4b | ||
|
9821b4589d | ||
|
6d1895364f | ||
|
ade59513df | ||
|
641951aaed | ||
|
6159ca7c7c | ||
|
bf06f271c6 | ||
|
cbb0a009e7 | ||
|
25b4d7a3ca | ||
|
624609d800 | ||
|
afae0ec5ec | ||
|
a6e621b562 | ||
|
7c81d068e6 | ||
|
d1b8001adc | ||
|
2764e3ad9a | ||
|
5c12e1ce1c | ||
|
7d2ceac61a | ||
|
8a201c12b4 | ||
|
f393547d9e | ||
|
b0233f6a24 | ||
|
2932b0a800 | ||
|
59083b7669 | ||
|
f0d15e3e79 | ||
|
795e85bebb | ||
|
376ee38d3c | ||
|
c05af1d38b | ||
|
ab0ddcae3d | ||
|
09a119afe3 | ||
|
25aabbe766 | ||
|
f2fa9dbb41 | ||
|
9026c3a71d | ||
|
e765860aeb | ||
|
8ec6e55304 | ||
|
5a5420972c | ||
|
c7992411a5 | ||
|
cf3bf91b8d | ||
|
8442cc2b2b | ||
|
0ab81df9b8 | ||
|
6be459e451 | ||
|
78928e3510 | ||
|
4909320089 | ||
|
3a8738fd43 | ||
|
dac697c74e | ||
|
4bc9ea4573 | ||
|
cfa88b701f | ||
|
16fc72d8fb | ||
|
f36d44dc67 | ||
|
ead728dc35 | ||
|
3cf3fb50b2 | ||
|
37d4f62ce9 | ||
|
57486732b1 | ||
|
55e7d2439a | ||
|
81d86570da | ||
|
947109718c | ||
|
ca5cc84988 | ||
|
dbb60cf026 | ||
|
2484de5010 | ||
|
8550b4fea5 | ||
|
6f7e8c7a49 | ||
|
e6f7c78626 | ||
|
d22eceed66 | ||
|
b4700d28bd | ||
|
f0f12c459e | ||
|
5d70a807da | ||
|
77bc130636 | ||
|
377f2bd04f | ||
|
7a7acdbe57 | ||
|
de00939d8d | ||
|
a1004bac86 | ||
|
5a8e7ddcf8 | ||
|
f3a76a267c | ||
|
c8dbb2c757 | ||
|
d80b6ad4ce | ||
|
3d9ae49948 | ||
|
a9f0523b62 | ||
|
a111d0f616 | ||
|
c3dec09d9c |
113 changed files with 16404 additions and 10268 deletions
1
.browserslistrc
Normal file
1
.browserslistrc
Normal file
|
@ -0,0 +1 @@
|
|||
defaults
|
|
@ -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,
|
||||
|
|
|
@ -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
39
CITATION.cff
Normal 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
|
|
@ -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
|
||||
|
|
33
Gruntfile.js
33
Gruntfile.js
|
@ -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'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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:
|
||||
|
|
10
README.md
10
README.md
|
@ -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>
|
||||
|
||||
<a href="https://www.pitt.edu/"><img alt="University of Pittsburgh Logo" src="assets/logos/pitt-logo.png" height="70" /></a>
|
||||
|
||||
<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
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
BIN
assets/logos/pitt-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 77 KiB |
BIN
assets/logos/stanford-logo.png
Normal file
BIN
assets/logos/stanford-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 66 KiB |
103
changelog.txt
103
changelog.txt
|
@ -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
7587
package-lock.json
generated
File diff suppressed because it is too large
Load diff
21
package.json
21
package.json
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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
1054
src/canvasdrawer.js
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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 );
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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
488
src/datatypeconvertor.js
Normal 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));
|
|
@ -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
|
||||
|
|
767
src/drawer.js
767
src/drawer.js
|
@ -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 <canvas> element if the browser supports them, otherwise a <div> 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 <canvas> 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
426
src/drawerbase.js
Normal 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 ));
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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
288
src/htmldrawer.js
Normal 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 ));
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
209
src/matrix3.js
Normal 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 ));
|
|
@ -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));
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
362
src/priorityqueue.js
Normal 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));
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
660
src/tile.js
660
src/tile.js
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
1533
src/tilecache.js
1533
src/tilecache.js
File diff suppressed because it is too large
Load diff
1927
src/tiledimage.js
1927
src/tiledimage.js
File diff suppressed because it is too large
Load diff
|
@ -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 :
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
642
src/viewer.js
642
src/viewer.js
|
@ -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 ));
|
||||
|
|
|
@ -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
1301
src/webgldrawer.js
Normal file
File diff suppressed because it is too large
Load diff
218
src/world.js
218
src/world.js
|
@ -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;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
300
style.css
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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>"
|
||||
}
|
||||
|
|
33
test/demo/basic-html-drawer.html
Normal file
33
test/demo/basic-html-drawer.html
Normal 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>
|
|
@ -25,7 +25,7 @@
|
|||
id: "contentDiv",
|
||||
prefixUrl: "../../build/openseadragon/images/",
|
||||
tileSources: "../data/testpattern.dzi",
|
||||
showNavigator:true
|
||||
showNavigator: true,
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -41,8 +41,9 @@
|
|||
constrainDuringPan: true,
|
||||
visibilityRatio: 1,
|
||||
prefixUrl: "../../build/openseadragon/images/",
|
||||
minZoomImageRatio: 1
|
||||
minZoomImageRatio: 1,
|
||||
crossOriginPolicy: 'Anonymous',
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
114
test/demo/drawercomparison.html
Normal file
114
test/demo/drawercomparison.html
Normal 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 <img> 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>
|
||||
|
371
test/demo/drawercomparison.js
Normal file
371
test/demo/drawercomparison.js
Normal 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 <= 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));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
44
test/demo/drawerperformance.html
Normal file
44
test/demo/drawerperformance.html
Normal 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>
|
||||
|
1002
test/demo/drawerperformance.js
Normal file
1002
test/demo/drawerperformance.js
Normal file
File diff suppressed because it is too large
Load diff
843
test/demo/filtering-plugin/demo.js
Normal file
843
test/demo/filtering-plugin/demo.js
Normal 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 + '"> ? </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;
|
||||
});
|
82
test/demo/filtering-plugin/index.html
Normal file
82
test/demo/filtering-plugin/index.html
Normal 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>
|
337
test/demo/filtering-plugin/plugin.js
Normal file
337
test/demo/filtering-plugin/plugin.js
Normal 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);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
}());
|
BIN
test/demo/filtering-plugin/static/minus.png
Normal file
BIN
test/demo/filtering-plugin/static/minus.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 171 B |
BIN
test/demo/filtering-plugin/static/plus.png
Normal file
BIN
test/demo/filtering-plugin/static/plus.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 240 B |
81
test/demo/filtering-plugin/style.css
Normal file
81
test/demo/filtering-plugin/style.css
Normal 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;
|
||||
}
|
|
@ -59,7 +59,8 @@
|
|||
this.viewer = OpenSeadragon({
|
||||
id: "contentDiv",
|
||||
prefixUrl: "../../build/openseadragon/images/",
|
||||
tileSources: tileSources
|
||||
tileSources: tileSources,
|
||||
crossOriginPolicy: 'Anonymous',
|
||||
});
|
||||
|
||||
this.viewer.addHandler('open', function() {
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
|
|
35
test/demo/max-tiles-per-frame.html
Normal file
35
test/demo/max-tiles-per-frame.html
Normal 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>
|
|
@ -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>
|
143
test/demo/plugin-data-modification-interaction.html
Normal file
143
test/demo/plugin-data-modification-interaction.html
Normal 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>
|
34
test/demo/rotating-navigator-region.html
Normal file
34
test/demo/rotating-navigator-region.html
Normal 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
87
test/demo/rstats.css
Normal 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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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
|
||||
});
|
||||
|
||||
|
|
|
@ -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
|
||||
});
|
||||
|
||||
|
|
76
test/helpers/drawer-switcher.js
Normal file
76
test/helpers/drawer-switcher.js
Normal 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 <= 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}
|
||||
<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;
|
||||
}
|
||||
}
|
|
@ -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
95
test/helpers/mocks.js
Normal 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);
|
||||
}
|
||||
};
|
||||
|
|
@ -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);
|
||||
};
|
||||
} )();
|
||||
|
||||
|
|
|
@ -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
|
||||
});
|
||||
|
||||
})();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}),
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
||||
} );
|
||||
|
|
4
test/modules/controls.js
vendored
4
test/modules/controls.js
vendored
|
@ -16,8 +16,8 @@
|
|||
|
||||
},
|
||||
afterEach: function () {
|
||||
if (viewer && viewer.close) {
|
||||
viewer.close();
|
||||
if (viewer){
|
||||
viewer.destroy();
|
||||
}
|
||||
|
||||
viewer = null;
|
||||
|
|
260
test/modules/data-manipulation.js
Normal file
260
test/modules/data-manipulation.js
Normal 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();
|
||||
});
|
||||
});
|
||||
}
|
||||
}());
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
})();
|
||||
|
|
143
test/modules/event-source.js
Normal file
143
test/modules/event-source.js
Normal 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.'
|
||||
});
|
||||
});
|
||||
} )();
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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");
|
||||
});
|
||||
|
||||
})();
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
testLog.reset();
|
||||
},
|
||||
afterEach: function () {
|
||||
if (viewer && viewer.close) {
|
||||
viewer.close();
|
||||
if (viewer){
|
||||
viewer.destroy();
|
||||
}
|
||||
|
||||
viewer = null;
|
||||
|
|
|
@ -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]
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
})();
|
||||
|
|
|
@ -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
Loading…
Add table
Reference in a new issue