Compare commits
827 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 | ||
|
8e6196a3f3 | ||
|
c93df98363 | ||
|
574f375467 | ||
|
1351ac018a | ||
|
c5588a2313 | ||
|
3342b7880d | ||
|
564121428c | ||
|
67fc9eafe8 | ||
|
9ccf93d767 | ||
|
9daa8feec1 | ||
|
9048abb05e | ||
|
69297c0181 | ||
|
ae70c88e52 | ||
|
e6341ac1e0 | ||
|
2d713783ba | ||
|
e122f372ba | ||
|
48bcd75f1a | ||
|
37a024f023 | ||
|
86c105beaa | ||
|
2afd61b05d | ||
|
7e1b5c5665 | ||
|
54af53cf4e | ||
|
dd2f132577 | ||
|
de4eac5a06 | ||
|
15b3e10727 | ||
|
524b42c778 | ||
|
44431beb13 | ||
|
c5c1fd156f | ||
|
66b4b29424 | ||
|
256514bca6 | ||
|
4f990c74b7 | ||
|
757bf8690e | ||
|
845cbced69 | ||
|
05997d359c | ||
|
f85c8ca4e9 | ||
|
5a11784173 | ||
|
78c6295acf | ||
|
5aca85fe21 | ||
|
1fe1630ab9 | ||
|
dfcfa4c02b | ||
|
77030096e4 | ||
|
8744a429f1 | ||
|
aede32c335 | ||
|
9c42062efb | ||
|
eb85b4498f | ||
|
ac18d5629d | ||
|
8ef5270d7f | ||
|
09544f795f | ||
|
c92fe116c5 | ||
|
bd8e8a9e7f | ||
|
601160e9fb | ||
|
5aa5d7ad56 | ||
|
11f70d54e8 | ||
|
b8cc7e171e | ||
|
554149cf4f | ||
|
968cb96bc4 | ||
|
6d35f6df2c | ||
|
c5639e32b5 | ||
|
da314c067c | ||
|
11b7205ea4 | ||
|
87f90c05b4 | ||
|
9150af8592 | ||
|
c25d15419c | ||
|
7670892ae7 | ||
|
6fa8e6695d | ||
|
27399beffe | ||
|
29384cd5ff | ||
|
d55ccdf557 | ||
|
22d4e62198 | ||
|
cdfd5a454b | ||
|
c3dec09d9c | ||
|
d7cd12679c | ||
|
7454d4323d | ||
|
69c4b31853 | ||
|
89a69ee1e0 | ||
|
733f501a94 | ||
|
5bba14a763 | ||
![]() |
7c44dc64bc | ||
|
2f174ad552 | ||
|
7f2725ba49 | ||
|
aea62de752 | ||
|
84bbc27a17 | ||
|
bfddf7355b | ||
![]() |
6540a57aa9 | ||
|
51807a77f0 | ||
![]() |
cb2fbe9b50 | ||
|
fccab2da56 | ||
|
e4d9dcdd03 | ||
|
3ebc03ab3a | ||
|
05b596ce32 | ||
|
ca88de8e7f | ||
|
d44fc85582 | ||
|
a0ac8666e0 | ||
|
af3801367a | ||
|
ed66c2d151 | ||
|
af564547f0 | ||
|
89ab847ef6 | ||
|
2f28970e32 | ||
|
e12a626efb | ||
|
2f2285711d | ||
|
9a4b802286 | ||
|
8336e8336b | ||
|
3a0bb0cd58 | ||
|
41a07b2250 | ||
|
cd9c5032f8 | ||
|
c470e7effd | ||
|
d5bcbcec3a | ||
|
38027e7588 | ||
|
bbac0f0355 | ||
|
aec24d6dd7 | ||
|
13330c4ede | ||
|
8bb600f6ae | ||
|
98972d22d1 | ||
|
333be9d6ca | ||
|
f87310c082 | ||
|
1a1b8e4510 | ||
|
476657ecad | ||
|
5baa8d3252 | ||
|
39625aacaa | ||
|
f787d941e8 | ||
|
2a90a2cdf4 | ||
|
e212edae54 | ||
|
6b92904178 | ||
|
c48d143fe8 | ||
|
0474d64ba1 | ||
|
38bc89f455 | ||
|
d504a7ac83 | ||
|
9ec91891e6 | ||
|
ce45e34265 | ||
|
6d74b684ec | ||
|
7497b83525 | ||
|
f88a1f0603 | ||
|
d3ef767487 | ||
|
8e55dc7217 | ||
|
5e7ce1afa5 | ||
|
5f50d9e59e | ||
|
9bce0e696c | ||
|
61c77a3440 | ||
|
298f069c7e | ||
|
12402f2f0f | ||
|
dafcf7bc14 | ||
|
45643b94e3 | ||
|
6d5a75bcf7 | ||
|
bdb44d84fb | ||
|
c449a8353d | ||
|
5829a9e892 | ||
|
ddd4a05e5e | ||
|
32af983910 | ||
|
3cdd94e465 | ||
|
1b27e59be8 | ||
|
0335f3f4ab | ||
|
d1f46148a7 | ||
|
1d327d673e | ||
|
2fbaf71448 | ||
|
0aadc23fcb | ||
|
bb1755613e | ||
|
71e9d5775c | ||
|
6494b49d67 | ||
|
ddd375b378 | ||
|
1b777c9e62 | ||
|
d35c0a6385 | ||
|
86dabe624b | ||
|
7a97d524f8 | ||
|
820564dd3e | ||
|
9988fcebc1 | ||
|
0114381ebe | ||
|
650b899de3 | ||
|
6eeccb530b | ||
|
bf395c1633 | ||
|
d956b2ae9d | ||
|
c20b2bbab9 | ||
|
71a1f06e35 | ||
|
3ca17c1508 | ||
|
ff4f88dc64 | ||
|
e8adefbe6e | ||
|
4ce2bdcdd1 | ||
|
f31179c9ce | ||
|
c1c380fa8f | ||
|
7b759558b1 | ||
|
1d99a2d6be | ||
|
df0e598e38 | ||
|
3661d6cae5 | ||
|
9772901d71 | ||
|
4a877d60f7 | ||
|
ca368797b0 | ||
|
d4dfcd2288 | ||
|
21146b6b2c | ||
|
5b7be6be05 | ||
|
325f65fe5c | ||
|
7d6437b2cd | ||
|
a6d1472134 | ||
|
6e80e2d184 | ||
|
8a2c998cb9 | ||
|
150e750ece | ||
|
d82fd35323 | ||
|
45a7a4aaf3 | ||
|
92ad2c5552 | ||
|
62e24ac1ab | ||
|
4f79f29238 | ||
|
eca51850e2 | ||
|
c16d1e3dff | ||
|
d62538f42e | ||
|
c2821b1775 | ||
|
ef8e7ad9c5 | ||
|
fb8bafc6a0 | ||
|
dc2a1534d3 | ||
|
8746e8b243 | ||
|
1cbc2d96c1 | ||
|
65957efe72 | ||
|
8519e30a3a | ||
|
4301254fa0 |
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
.gitignore
vendored
|
@ -8,3 +8,4 @@ instrumented/
|
|||
/nbproject/private/
|
||||
.directory
|
||||
local-test
|
||||
.DS_Store
|
||||
|
|
|
@ -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:
|
||||
- "stable"
|
||||
- lts/*
|
||||
before_install:
|
||||
- npm install -g grunt-cli
|
||||
|
|
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
|
|
@ -3,20 +3,20 @@
|
|||
OpenSeadragon is truly a community project; we welcome your involvement!
|
||||
|
||||
When contributing, please attempt to match the code style already in the codebase.
|
||||
However, we are in the process of changing our code style (see issue [#456](https://github.com/openseadragon/openseadragon/issues/456)), so avoid spaces inside parentheses and square brackets. Note that we use four spaces per indentation stop. For easier setup you can also install [EditorConfig](http://editorconfig.org/) if your IDE is supported. For more thoughts on code style, see [idiomatic.js](https://github.com/rwldrn/idiomatic.js/).
|
||||
However, we are in the process of changing our code style (see issue [#456](https://github.com/openseadragon/openseadragon/issues/456)), so avoid spaces inside parentheses and square brackets. Note that we use four spaces per indentation stop. For easier setup you can also install [EditorConfig](https://editorconfig.org/) if your IDE is supported. For more thoughts on code style, see [idiomatic.js](https://github.com/rwldrn/idiomatic.js/).
|
||||
|
||||
When fixing bugs and adding features, when appropriate please also:
|
||||
|
||||
* Update related doc comments (we use [JSDoc 3](http://usejsdoc.org/))
|
||||
* Update related doc comments (we use [JSDoc 3](https://jsdoc.app/))
|
||||
* Add/update related unit tests
|
||||
|
||||
If you're new to the project, check out our [good first bug](https://github.com/openseadragon/openseadragon/issues?labels=good+first+bug&page=1&state=open) issues for some places to dip your toe in the water.
|
||||
If you're new to the project, check out our [good first issues](https://github.com/openseadragon/openseadragon/issues?labels=good+first+issue&page=1&state=open) for some places to dip your toe in the water.
|
||||
|
||||
If you're new to open source in general, check out [GitHub's open source intro guide](https://guides.github.com/activities/contributing-to-open-source/).
|
||||
|
||||
### First Time Setup
|
||||
|
||||
All command-line operations for building and testing OpenSeadragon are scripted using [Grunt](http://gruntjs.com/) which is based on [Node.js](http://nodejs.org/). To get set up:
|
||||
All command-line operations for building and testing OpenSeadragon are scripted using [Grunt](https://gruntjs.com/) which is based on [Node.js](https://nodejs.org/). To get set up:
|
||||
|
||||
1. Install Node, if you haven't already (available at the link above)
|
||||
1. Install the Grunt command line runner (if you haven't already); on the command line, run `npm install -g grunt-cli`
|
||||
|
@ -57,10 +57,14 @@ You can also publish the built version to the site-build repository. This assume
|
|||
|
||||
### Testing
|
||||
|
||||
Our tests are based on [QUnit](http://qunitjs.com/) and [Puppeteer](https://github.com/GoogleChrome/puppeteer); they're both installed when you run `npm install`. To run on the command line:
|
||||
Our tests are based on [QUnit](https://qunitjs.com/) and [Puppeteer](https://github.com/GoogleChrome/puppeteer); they're both installed when you run `npm install`. To run on the command line:
|
||||
|
||||
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
|
@ -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-2013 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:
|
||||
|
|
31
README.md
|
@ -1,18 +1,39 @@
|
|||
# OpenSeadragon
|
||||
[](https://gitter.im/openseadragon/openseadragon?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](http://travis-ci.org/openseadragon/openseadragon)
|
||||
|
||||
<!-- [![Gitter][gitter-badge]][gitter]
|
||||
[![Build Status][build-badge]][build] -->
|
||||
|
||||
An open-source, web-based viewer for zoomable images, implemented in pure JavaScript.
|
||||
|
||||
See it in action and get started using it at [http://openseadragon.github.io/](http://openseadragon.github.io/).
|
||||
See it in action and get started using it at [https://openseadragon.github.io/][openseadragon].
|
||||
|
||||
## Stable Builds
|
||||
|
||||
See the [GitHub releases page](https://github.com/openseadragon/openseadragon/releases).
|
||||
See the [GitHub releases page][github-releases].
|
||||
|
||||
## Development
|
||||
|
||||
If you want to use OpenSeadragon in your own projects, you can find the latest stable build, API documentation, and example code at [http://openseadragon.github.io/](http://openseadragon.github.io/). If you want to modify OpenSeadragon and/or contribute to its development, read the [contributing guide](https://github.com/openseadragon/openseadragon/blob/master/CONTRIBUTING.md) for instructions.
|
||||
If you want to use OpenSeadragon in your own projects, you can find the latest stable build, API documentation, and example code at [https://openseadragon.github.io/][openseadragon]. If you want to modify OpenSeadragon and/or contribute to its development, read the [contributing guide][github-contributing] for instructions.
|
||||
|
||||
## License
|
||||
|
||||
OpenSeadragon is released under the New BSD license. For details, see the [LICENSE.txt file](https://github.com/openseadragon/openseadragon/blob/master/LICENSE.txt).
|
||||
OpenSeadragon is released under the New BSD license. For details, see the [LICENSE.txt file][github-license].
|
||||
|
||||
[openseadragon]: https://openseadragon.github.io/
|
||||
<!-- [gitter-badge]: https://badges.gitter.im/Join%20Chat.svg
|
||||
[gitter]: https://gitter.im/openseadragon/openseadragon?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
|
||||
[build-badge]: https://secure.travis-ci.org/openseadragon/openseadragon.png?branch=master
|
||||
[build]: https://travis-ci.org/openseadragon/openseadragon -->
|
||||
[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
After Width: | Height: | Size: 35 KiB |
BIN
assets/logos/pitt-logo.png
Normal file
After Width: | Height: | Size: 77 KiB |
BIN
assets/logos/stanford-logo.png
Normal file
After Width: | Height: | Size: 66 KiB |
123
changelog.txt
|
@ -1,6 +1,129 @@
|
|||
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 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)
|
||||
* You can now provide a pivot point when rotating the viewport (#2233 #2253 @pearcetm)
|
||||
* Improved the constraints that keep the image in the viewer, specifically when zoomed out a lot (#2160 @joedf, #2246 @pearcetm)
|
||||
* You can now provide an element for the navigator (as an alternative to an ID) (#1303 @cameronbaney, #2166 #2175 @joedf)
|
||||
* Now supporting IIIF "id" and "identifier" in addition to "@id" (#2173 @ahankinson)
|
||||
* We now delegate tile fetching and caching to the TileSource, to allow for custom tile formats (#2148 @Aiosa)
|
||||
* Added support for dynamic URLs from tile sources (#2247 @JohnReagan)
|
||||
* The viewer now emits before-destroy and destroy events (#2239 @pearcetm)
|
||||
* Auto resize detection is now more efficient (#2256 @pearcetm)
|
||||
* Improved documentation (#2211 @shyamkumaryadav)
|
||||
* Fixed: Cropping tiled images with polygons was broken (#2183 @altert)
|
||||
* Fixed: Boundary constraints were wrong when the viewport was rotated (#2249 @pearcetm)
|
||||
* Fixed: IIIF tile sizes would be calculated wrong on rare occasions (#2206 @filak)
|
||||
* Fixed: Disabling buttons only changed their appearance, but they were still clickable (#2187 @pearcetm)
|
||||
* Fixed: ImageTileSource produced an error having to do with getTileHashKey (#2190 @Aiosa)
|
||||
* Fixed: On startup you would get an unnecessary "Viewer.buttons is deprecated" warning (#2201 @joedf, #2219 @jssullivan, #2212 @joedf)
|
||||
|
||||
3.1.0:
|
||||
|
||||
* Added subPixelRoundingForTransparency Viewer option to address seams that can appear in semi-transparent images (#2075 @TanukiSharp)
|
||||
|
|
17725
package-lock.json
generated
21
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "openseadragon",
|
||||
"version": "3.1.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": "^5.1.1",
|
||||
"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-2013 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 );
|
||||
|
@ -398,6 +391,7 @@ $.extend( $.Button.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.
|
|||
disable: function(){
|
||||
this.notifyGroupExit();
|
||||
this.element.disabled = true;
|
||||
this.tracker.setTracking(false);
|
||||
$.setElementOpacity( this.element, 0.2, true );
|
||||
},
|
||||
|
||||
|
@ -406,6 +400,7 @@ $.extend( $.Button.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.
|
|||
*/
|
||||
enable: function(){
|
||||
this.element.disabled = false;
|
||||
this.tracker.setTracking(true);
|
||||
$.setElementOpacity( this.element, 1.0, true );
|
||||
this.notifyGroupEnter();
|
||||
},
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* OpenSeadragon - ButtonGroup
|
||||
*
|
||||
* Copyright (C) 2009 CodePlex Foundation
|
||||
* Copyright (C) 2010-2013 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
|
@ -2,7 +2,7 @@
|
|||
* OpenSeadragon - Control
|
||||
*
|
||||
* Copyright (C) 2009 CodePlex Foundation
|
||||
* Copyright (C) 2010-2013 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
|
||||
|
@ -74,6 +74,7 @@ $.ControlAnchor = {
|
|||
* @param {Element} container - the element to control will be anchored too.
|
||||
*/
|
||||
$.Control = function ( element, options, container ) {
|
||||
|
||||
var parent = element.parentNode;
|
||||
if (typeof options === 'number')
|
||||
{
|
||||
|
@ -150,6 +151,7 @@ $.Control = function ( element, options, container ) {
|
|||
} else {
|
||||
parent.appendChild( this.wrapper );
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/** @lends OpenSeadragon.Control.prototype */
|
||||
|
@ -169,7 +171,7 @@ $.Control.prototype = {
|
|||
/**
|
||||
* Determines if the control is currently visible.
|
||||
* @function
|
||||
* @return {Boolean} true if currently visible, false otherwise.
|
||||
* @returns {Boolean} true if currently visible, false otherwise.
|
||||
*/
|
||||
isVisible: function() {
|
||||
return this.wrapper.style.display !== "none";
|
||||
|
@ -192,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-2013 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%';
|
||||
}
|
||||
|
@ -149,7 +151,7 @@
|
|||
|
||||
/**
|
||||
* @function
|
||||
* @return {OpenSeadragon.ControlDock} Chainable.
|
||||
* @returns {OpenSeadragon.ControlDock} Chainable.
|
||||
*/
|
||||
removeControl: function ( element ) {
|
||||
element = $.getElement( element );
|
||||
|
@ -165,7 +167,7 @@
|
|||
|
||||
/**
|
||||
* @function
|
||||
* @return {OpenSeadragon.ControlDock} Chainable.
|
||||
* @returns {OpenSeadragon.ControlDock} Chainable.
|
||||
*/
|
||||
clearControls: function () {
|
||||
while ( this.controls.length > 0 ) {
|
||||
|
@ -178,7 +180,7 @@
|
|||
|
||||
/**
|
||||
* @function
|
||||
* @return {Boolean}
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
areControlsEnabled: function () {
|
||||
var i;
|
||||
|
@ -195,7 +197,7 @@
|
|||
|
||||
/**
|
||||
* @function
|
||||
* @return {OpenSeadragon.ControlDock} Chainable.
|
||||
* @returns {OpenSeadragon.ControlDock} Chainable.
|
||||
*/
|
||||
setControlsEnabled: function( enabled ) {
|
||||
var i;
|
||||
|
|
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-2013 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
|
||||
|
|
763
src/drawer.js
|
@ -1,763 +0,0 @@
|
|||
/*
|
||||
* OpenSeadragon - Drawer
|
||||
*
|
||||
* Copyright (C) 2009 CodePlex Foundation
|
||||
* Copyright (C) 2010-2013 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
|
||||
*/
|
||||
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
|
||||
* @return {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;
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {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.
|
||||
* @return {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.
|
||||
*/
|
||||
drawTile: function(tile, drawingHandler, useSketch, scale, translate, shouldRoundPositionAndSize) {
|
||||
$.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);
|
||||
} 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.degrees !== 0 ) {
|
||||
this._offsetForRotation({degrees: this.viewport.degrees});
|
||||
}
|
||||
if (tiledImage.getRotation(true) % 360 !== 0) {
|
||||
this._offsetForRotation({
|
||||
degrees: tiledImage.getRotation(true),
|
||||
point: tiledImage.viewport.pixelFromPointNoRotate(
|
||||
tiledImage._getRotationPoint(true), true)
|
||||
});
|
||||
}
|
||||
if (tiledImage.viewport.degrees === 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.degrees );
|
||||
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.degrees !== 0 ) {
|
||||
this._restoreRotationChanges();
|
||||
}
|
||||
if (tiledImage.getRotation(true) % 360 !== 0) {
|
||||
this._restoreRotationChanges();
|
||||
}
|
||||
|
||||
if (tiledImage.viewport.degrees === 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
|
@ -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-2013 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
|
||||
|
@ -125,7 +125,7 @@ $.extend( $.DziTileSource.prototype, $.TileSource.prototype, /** @lends OpenSead
|
|||
* @param {Object|XMLDocument} data - the raw configuration
|
||||
* @param {String} url - the url the data was retrieved from if any.
|
||||
* @param {String} postData - HTTP POST data in k=v&k2=v2... form or null
|
||||
* @return {Object} options - A dictionary of keyword arguments sufficient
|
||||
* @returns {Object} options - A dictionary of keyword arguments sufficient
|
||||
* to configure this tile sources constructor.
|
||||
*/
|
||||
configure: function( data, url, postData ){
|
||||
|
@ -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-2013 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 ] );
|
||||
}
|
||||
|
@ -125,10 +153,10 @@ $.EventSource.prototype = {
|
|||
/**
|
||||
* Get the amount of handlers registered for a given event.
|
||||
* @param {String} eventName - Name of event to inspect.
|
||||
* @return {number} amount of events
|
||||
* @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-2013 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
|
@ -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-2013 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
|
||||
|
@ -51,8 +51,11 @@ $.IIIFTileSource = function( options ){
|
|||
|
||||
$.extend( true, this, options );
|
||||
|
||||
if ( !( this.height && this.width && this['@id'] ) ) {
|
||||
throw new Error( 'IIIF required parameters not provided.' );
|
||||
/* Normalizes v3-style 'id' keys to an "_id" internal property */
|
||||
this._id = this["@id"] || this["id"] || this['identifier'] || null;
|
||||
|
||||
if ( !( this.height && this.width && this._id) ) {
|
||||
throw new Error( 'IIIF required parameters (width, height, or id) not provided.' );
|
||||
}
|
||||
|
||||
options.tileSizePerScaleFactor = {};
|
||||
|
@ -131,13 +134,25 @@ $.IIIFTileSource = function( options ){
|
|||
|
||||
if (!options.maxLevel && !this.emulateLegacyImagePyramid) {
|
||||
if (!this.scale_factors) {
|
||||
options.maxLevel = Number(Math.ceil(Math.log(Math.max(this.width, this.height), 2)));
|
||||
options.maxLevel = Number(Math.round(Math.log(Math.max(this.width, this.height), 2)));
|
||||
} else {
|
||||
var maxScaleFactor = Math.max.apply(null, this.scale_factors);
|
||||
options.maxLevel = Math.round(Math.log(maxScaleFactor) * Math.LOG2E);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 ] );
|
||||
};
|
||||
|
||||
|
@ -147,7 +162,7 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
|
|||
* this tile source.
|
||||
* @function
|
||||
* @param {Object|Array} data
|
||||
* @param {String} optional - url
|
||||
* @param {String} [url] - url
|
||||
*/
|
||||
|
||||
supports: function( data, url ) {
|
||||
|
@ -180,23 +195,29 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
|
|||
},
|
||||
|
||||
/**
|
||||
* A static function used to prepare an incoming IIIF Image API info.json
|
||||
* response for processing by the tile handler. Normalizes data for all
|
||||
* versions of IIIF (1.0, 1.1, 2.x, 3.x) and returns a data object that
|
||||
* may be passed to the IIIFTileSource.
|
||||
*
|
||||
* @function
|
||||
* @static
|
||||
* @param {Object} data - the raw configuration
|
||||
* @param {String} url - the url configuration was retrieved from
|
||||
* @param {String} postData - HTTP POST data in k=v&k2=v2... form or null
|
||||
* @example <caption>IIIF 1.1 Info Looks like this</caption>
|
||||
* @returns {Object} A normalized IIIF data object
|
||||
* @example <caption>IIIF 2.x Info Looks like this</caption>
|
||||
* {
|
||||
* "@context" : "http://library.stanford.edu/iiif/image-api/1.1/context.json",
|
||||
* "@id" : "http://iiif.example.com/prefix/1E34750D-38DB-4825-A38A-B60A345E591C",
|
||||
* "width" : 6000,
|
||||
* "height" : 4000,
|
||||
* "scale_factors" : [ 1, 2, 4 ],
|
||||
* "tile_width" : 1024,
|
||||
* "tile_height" : 1024,
|
||||
* "formats" : [ "jpg", "png" ],
|
||||
* "qualities" : [ "native", "grey" ],
|
||||
* "profile" : "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level0"
|
||||
* "@context": "http://iiif.io/api/image/2/context.json",
|
||||
* "@id": "http://iiif.example.com/prefix/1E34750D-38DB-4825-A38A-B60A345E591C",
|
||||
* "protocol": "http://iiif.io/api/image",
|
||||
* "height": 1024,
|
||||
* "width": 775,
|
||||
* "tiles" : [{"width":256, "scaleFactors":[1,2,4,8]}],
|
||||
* "profile": ["http://iiif.io/api/image/2/level1.json", {
|
||||
* "qualities": [ "native", "bitonal", "grey", "color" ],
|
||||
* "formats": [ "jpg", "png", "gif" ]
|
||||
* }]
|
||||
* }
|
||||
*/
|
||||
configure: function( data, url, postData ){
|
||||
|
@ -204,13 +225,13 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
|
|||
if ( !$.isPlainObject(data) ) {
|
||||
var options = configureFromXml10( data );
|
||||
options['@context'] = "http://iiif.io/api/image/1.0/context.json";
|
||||
options['@id'] = url.replace('/info.xml', '');
|
||||
options["@id"] = url.replace('/info.xml', '');
|
||||
options.version = 1;
|
||||
return options;
|
||||
} else {
|
||||
if ( !data['@context'] ) {
|
||||
data['@context'] = 'http://iiif.io/api/image/1.0/context.json';
|
||||
data['@id'] = url.replace('/info.json', '');
|
||||
data["@id"] = url.replace('/info.json', '');
|
||||
data.version = 1;
|
||||
} else {
|
||||
var context = data['@context'];
|
||||
|
@ -239,12 +260,10 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
|
|||
$.console.error('Data has a @context property which contains no known IIIF context URI.');
|
||||
}
|
||||
}
|
||||
if ( !data['@id'] && data['id'] ) {
|
||||
data['@id'] = data['id'];
|
||||
}
|
||||
if(data.preferredFormats) {
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -326,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);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
@ -341,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);
|
||||
},
|
||||
|
||||
|
@ -368,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.ceil( this.width * scale ),
|
||||
levelHeight = Math.ceil( this.height * scale ),
|
||||
levelWidth,
|
||||
levelHeight,
|
||||
|
||||
//## iiif region
|
||||
tileWidth,
|
||||
|
@ -389,10 +445,21 @@ $.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.ceil( tileWidth / scale );
|
||||
iiifTileSizeHeight = Math.ceil( tileHeight / scale );
|
||||
iiifTileSizeWidth = Math.round( tileWidth / scale );
|
||||
iiifTileSizeHeight = Math.round( tileHeight / scale );
|
||||
if (this.version === 1) {
|
||||
iiifQuality = "native." + this.tileFormat;
|
||||
} else {
|
||||
|
@ -419,8 +486,8 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
|
|||
} else {
|
||||
iiifRegion = [ iiifTileX, iiifTileY, iiifTileW, iiifTileH ].join( ',' );
|
||||
}
|
||||
iiifSizeW = Math.ceil( iiifTileW * scale );
|
||||
iiifSizeH = Math.ceil( 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 ) {
|
||||
|
@ -431,11 +498,18 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
|
|||
iiifSize = iiifSizeW + ",";
|
||||
}
|
||||
}
|
||||
uri = [ this['@id'], iiifRegion, iiifSize, IIIF_ROTATION, iiifQuality ].join( '/' );
|
||||
uri = [ this._id, iiifRegion, iiifSize, IIIF_ROTATION, iiifQuality ].join( '/' );
|
||||
|
||||
return uri;
|
||||
},
|
||||
|
||||
/**
|
||||
* Equality comparator
|
||||
*/
|
||||
equals: function(otherSource) {
|
||||
return this._id === otherSource._id;
|
||||
},
|
||||
|
||||
__testonly__: {
|
||||
canBeTiled: canBeTiled,
|
||||
constructLevels: constructLevels
|
||||
|
@ -449,7 +523,7 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
|
|||
* @param {Object} options
|
||||
* @param {Array|String} options.profile
|
||||
* @param {Number} options.version
|
||||
* @param {String} options.extraFeatures
|
||||
* @param {String[]} options.extraFeatures
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
function canBeTiled ( options ) {
|
||||
|
@ -482,7 +556,7 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
|
|||
var levels = [];
|
||||
for(var i = 0; i < options.sizes.length; i++) {
|
||||
levels.push({
|
||||
url: options['@id'] + '/full/' + options.sizes[i].width + ',' +
|
||||
url: options._id + '/full/' + options.sizes[i].width + ',' +
|
||||
(options.version === 3 ? options.sizes[i].height : '') +
|
||||
'/0/default.' + options.tileFormat,
|
||||
width: options.sizes[i].width,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* OpenSeadragon - ImageLoader
|
||||
*
|
||||
* Copyright (C) 2009 CodePlex Foundation
|
||||
* Copyright (C) 2010-2013 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
|
||||
|
@ -35,137 +35,130 @@
|
|||
(function($){
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @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.
|
||||
* @param {TileSource} [options.source] - Image loading strategy
|
||||
* @param {String} [options.loadWithAjax] - Whether to load this image with AJAX.
|
||||
* @param {String} [options.ajaxHeaders] - Headers to add to the image request if using AJAX.
|
||||
* @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 TileSrouce::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.
|
||||
*/
|
||||
function ImageJob (options) {
|
||||
$.ImageJob = function(options) {
|
||||
|
||||
$.extend(true, this, {
|
||||
timeout: $.DEFAULT_SETTINGS.timeout,
|
||||
jobId: null
|
||||
jobId: null,
|
||||
tries: 0
|
||||
}, options);
|
||||
|
||||
/**
|
||||
* Image object which will contain downloaded image.
|
||||
* @member {Image} image
|
||||
* Data object which will contain downloaded image data.
|
||||
* @member {Image|*} data data object, by default an Image object (depends on TileSource)
|
||||
* @memberof OpenSeadragon.ImageJob#
|
||||
*/
|
||||
this.image = null;
|
||||
}
|
||||
this.data = null;
|
||||
|
||||
ImageJob.prototype = {
|
||||
errorMsg: null,
|
||||
/**
|
||||
* User workspace to populate with helper variables
|
||||
* @member {*} userData to append custom data and avoid namespace collision
|
||||
* @memberof OpenSeadragon.ImageJob#
|
||||
*/
|
||||
this.userData = {};
|
||||
|
||||
/**
|
||||
* Error message holder
|
||||
* @member {string} error message
|
||||
* @memberof OpenSeadragon.ImageJob#
|
||||
* @private
|
||||
*/
|
||||
this.errorMsg = null;
|
||||
};
|
||||
|
||||
$.ImageJob.prototype = {
|
||||
/**
|
||||
* Starts the image job.
|
||||
* @method
|
||||
* @memberof OpenSeadragon.ImageJob#
|
||||
*/
|
||||
start: function(){
|
||||
start: function() {
|
||||
this.tries++;
|
||||
|
||||
var self = this;
|
||||
var selfAbort = this.abort;
|
||||
|
||||
this.image = new Image();
|
||||
|
||||
this.image.onload = function(){
|
||||
self.finish(true);
|
||||
};
|
||||
this.image.onabort = this.image.onerror = function() {
|
||||
self.errorMsg = "Image load aborted";
|
||||
self.finish(false);
|
||||
};
|
||||
|
||||
this.jobId = window.setTimeout(function(){
|
||||
self.errorMsg = "Image load exceeded timeout (" + self.timeout + " ms)";
|
||||
self.finish(false);
|
||||
this.jobId = window.setTimeout(function () {
|
||||
self.fail("Image load exceeded timeout (" + self.timeout + " ms)", null);
|
||||
}, this.timeout);
|
||||
|
||||
// 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.
|
||||
if (this.loadWithAjax) {
|
||||
this.request = $.makeAjaxRequest({
|
||||
url: this.src,
|
||||
withCredentials: this.ajaxWithCredentials,
|
||||
headers: this.ajaxHeaders,
|
||||
responseType: "arraybuffer",
|
||||
postData: this.postData,
|
||||
success: function(request) {
|
||||
var blb;
|
||||
// Make the raw data into a blob.
|
||||
// BlobBuilder fallback adapted from
|
||||
// http://stackoverflow.com/questions/15293694/blob-constructor-browser-compatibility
|
||||
try {
|
||||
blb = new window.Blob([request.response]);
|
||||
} catch (e) {
|
||||
var BlobBuilder = (
|
||||
window.BlobBuilder ||
|
||||
window.WebKitBlobBuilder ||
|
||||
window.MozBlobBuilder ||
|
||||
window.MSBlobBuilder
|
||||
);
|
||||
if (e.name === 'TypeError' && BlobBuilder) {
|
||||
var 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) {
|
||||
self.errorMsg = "Empty image response.";
|
||||
self.finish(false);
|
||||
}
|
||||
// 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.
|
||||
var url = (window.URL || window.webkitURL).createObjectURL(blb);
|
||||
self.image.src = url;
|
||||
},
|
||||
error: function(request) {
|
||||
self.errorMsg = "Image load aborted - XHR error: Ajax returned " + request.status;
|
||||
self.finish(false);
|
||||
}
|
||||
});
|
||||
|
||||
// Provide a function to properly abort the request.
|
||||
this.abort = function() {
|
||||
self.request.abort();
|
||||
|
||||
// Call the existing abort function if available
|
||||
if (typeof selfAbort === "function") {
|
||||
selfAbort();
|
||||
}
|
||||
};
|
||||
} else {
|
||||
if (this.crossOriginPolicy !== false) {
|
||||
this.image.crossOrigin = this.crossOriginPolicy;
|
||||
this.abort = function() {
|
||||
self.source.downloadTileAbort(self);
|
||||
if (typeof selfAbort === "function") {
|
||||
selfAbort();
|
||||
}
|
||||
};
|
||||
|
||||
this.image.src = this.src;
|
||||
}
|
||||
this.source.downloadTileStart(this);
|
||||
},
|
||||
|
||||
finish: function(successful) {
|
||||
this.image.onload = this.image.onerror = this.image.onabort = null;
|
||||
if (!successful) {
|
||||
this.image = null;
|
||||
/**
|
||||
* Finish this job.
|
||||
* @param {*} data data that has been downloaded
|
||||
* @param {XMLHttpRequest} request reference to the request if used
|
||||
* @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, 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 = 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);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -183,6 +176,7 @@ $.ImageLoader = function(options) {
|
|||
jobLimit: $.DEFAULT_SETTINGS.imageLoaderLimit,
|
||||
timeout: $.DEFAULT_SETTINGS.timeout,
|
||||
jobQueue: [],
|
||||
failedTiles: [],
|
||||
jobsInProgress: 0
|
||||
}, options);
|
||||
|
||||
|
@ -196,41 +190,61 @@ $.ImageLoader.prototype = {
|
|||
* @method
|
||||
* @param {Object} options - Options for this job.
|
||||
* @param {String} [options.src] - URL of image to download.
|
||||
* @param {Tile} [options.tile] - Tile that belongs the data to. The tile instance
|
||||
* is not internally used and serves for custom TileSources implementations.
|
||||
* @param {TileSource} [options.source] - Image loading strategy
|
||||
* @param {String} [options.loadWithAjax] - Whether to load this image with AJAX.
|
||||
* @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 TileSrouce::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) {
|
||||
var _this = this,
|
||||
complete = function(job) {
|
||||
completeJob(_this, job, options.callback);
|
||||
},
|
||||
if (!options.source) {
|
||||
$.console.error('ImageLoader.prototype.addJob() requires [options.source]. ' +
|
||||
'TileSource since new API defines how images are fetched. Creating a dummy TileSource.');
|
||||
var implementation = $.TileSource.prototype;
|
||||
options.source = {
|
||||
downloadTileStart: implementation.downloadTileStart,
|
||||
downloadTileAbort: implementation.downloadTileAbort
|
||||
};
|
||||
}
|
||||
|
||||
const _this = this,
|
||||
jobOptions = {
|
||||
src: options.src,
|
||||
tile: options.tile || {},
|
||||
source: options.source,
|
||||
loadWithAjax: options.loadWithAjax,
|
||||
ajaxHeaders: options.loadWithAjax ? options.ajaxHeaders : null,
|
||||
crossOriginPolicy: options.crossOriginPolicy,
|
||||
ajaxWithCredentials: options.ajaxWithCredentials,
|
||||
postData: options.postData,
|
||||
callback: complete,
|
||||
callback: (job) => completeJob(_this, job, options.callback),
|
||||
abort: options.abort,
|
||||
timeout: this.timeout
|
||||
},
|
||||
newJob = new ImageJob(jobOptions);
|
||||
newJob = new $.ImageJob(jobOptions);
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -250,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.
|
||||
|
@ -258,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.image, 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-2013 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
|
||||
* @return {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-2013 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
|
||||
|
@ -123,7 +123,7 @@ $.extend( $.LegacyTileSource.prototype, $.TileSource.prototype, /** @lends OpenS
|
|||
* @param {Object|XMLDocument} configuration - the raw configuration
|
||||
* @param {String} dataUrl - the url the data was retrieved from if any.
|
||||
* @param {String} postData - HTTP POST data in k=v&k2=v2... form or null
|
||||
* @return {Object} options - A dictionary of keyword arguments sufficient
|
||||
* @returns {Object} options - A dictionary of keyword arguments sufficient
|
||||
* to configure this tile sources constructor.
|
||||
*/
|
||||
configure: function( configuration, dataUrl, postData ){
|
||||
|
@ -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
|
@ -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-2013 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-2013 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,7 +45,9 @@
|
|||
* @memberof OpenSeadragon
|
||||
* @extends OpenSeadragon.Viewer
|
||||
* @extends OpenSeadragon.EventSource
|
||||
* @param {Object} options
|
||||
* @param {Object} options - Navigator options
|
||||
* @param {Element} [options.element] - An element to use for the navigator.
|
||||
* @param {String} [options.id] - Id of the element to use for the navigator. However, this is ignored if {@link options.element} is provided.
|
||||
*/
|
||||
$.Navigator = function( options ){
|
||||
|
||||
|
@ -55,8 +57,31 @@ $.Navigator = function( options ){
|
|||
navigatorSize;
|
||||
|
||||
//We may need to create a new element and id if they did not
|
||||
//provide the id for the existing element
|
||||
if( !options.id ){
|
||||
//provide the id for the existing element or the element itself
|
||||
if( options.element || options.id ){
|
||||
if ( options.element ) {
|
||||
if ( options.id ){
|
||||
$.console.warn("Given option.id for Navigator was ignored since option.element was provided and is being used instead.");
|
||||
}
|
||||
|
||||
// Don't overwrite the element's id if it has one already
|
||||
if ( options.element.id ) {
|
||||
options.id = options.element.id;
|
||||
} else {
|
||||
options.id = 'navigator-' + $.now();
|
||||
}
|
||||
|
||||
this.element = options.element;
|
||||
} else {
|
||||
this.element = document.getElementById( options.id );
|
||||
}
|
||||
|
||||
options.controlOptions = {
|
||||
anchor: $.ControlAnchor.NONE,
|
||||
attachToViewer: false,
|
||||
autoFade: false
|
||||
};
|
||||
} else {
|
||||
options.id = 'navigator-' + $.now();
|
||||
this.element = $.makeNeutralElement( "div" );
|
||||
options.controlOptions = {
|
||||
|
@ -82,14 +107,6 @@ $.Navigator = function( options ){
|
|||
options.controlOptions.width = options.width;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
this.element = document.getElementById( options.id );
|
||||
options.controlOptions = {
|
||||
anchor: $.ControlAnchor.NONE,
|
||||
attachToViewer: false,
|
||||
autoFade: false
|
||||
};
|
||||
}
|
||||
this.element.id = options.id;
|
||||
this.element.className += ' navigator';
|
||||
|
@ -107,8 +124,9 @@ $.Navigator = function( options ){
|
|||
showSequenceControl: false,
|
||||
immediateRender: true,
|
||||
blendTime: 0,
|
||||
animationTime: 0,
|
||||
autoResize: options.autoResize,
|
||||
animationTime: options.animationTime,
|
||||
// disable autoResize since resize behavior is implemented differently by the navigator
|
||||
autoResize: false,
|
||||
// prevent resizing the navigator from adding unwanted space around the image
|
||||
minZoomImageRatio: 1.0,
|
||||
background: options.background,
|
||||
|
@ -152,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.
|
||||
|
@ -163,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 );
|
||||
|
@ -205,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);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -291,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);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -305,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();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -315,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();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -329,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);
|
||||
},
|
||||
|
@ -337,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 ) {
|
||||
|
||||
|
@ -348,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;
|
||||
|
@ -376,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-2013 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
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -332,8 +353,11 @@
|
|||
*
|
||||
* @property {Number} [animationTime=1.2]
|
||||
* Specifies the animation duration per each {@link OpenSeadragon.Spring}
|
||||
* which occur when the image is dragged or zoomed.
|
||||
* 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
|
||||
|
@ -341,6 +365,9 @@
|
|||
* @property {Boolean} [gestureSettingsMouse.clickToZoom=true] - Zoom on click gesture
|
||||
* @property {Boolean} [gestureSettingsMouse.dblClickToZoom=false] - Zoom on double-click gesture. Note: If set to true
|
||||
* then clickToZoom should be set to false to prevent multiple zooms.
|
||||
* @property {Boolean} [gestureSettingsMouse.dblClickDragToZoom=false] - Zoom on dragging through
|
||||
* double-click gesture ( single click and next click to drag). Note: If set to true
|
||||
* then clickToZoom should be set to false to prevent multiple zooms.
|
||||
* @property {Boolean} [gestureSettingsMouse.pinchToZoom=false] - Zoom on pinch gesture
|
||||
* @property {Boolean} [gestureSettingsMouse.zoomToRefPoint=true] - If zoomToRefPoint is true, the zoom is centered at the pointer position. Otherwise,
|
||||
* the zoom is centered at the canvas center.
|
||||
|
@ -356,6 +383,10 @@
|
|||
* @property {Boolean} [gestureSettingsTouch.clickToZoom=false] - Zoom on click gesture
|
||||
* @property {Boolean} [gestureSettingsTouch.dblClickToZoom=true] - Zoom on double-click gesture. Note: If set to true
|
||||
* then clickToZoom should be set to false to prevent multiple zooms.
|
||||
* @property {Boolean} [gestureSettingsTouch.dblClickDragToZoom=true] - Zoom on dragging through
|
||||
* double-click gesture ( single click and next click to drag). Note: If set to true
|
||||
* then clickToZoom should be set to false to prevent multiple zooms.
|
||||
|
||||
* @property {Boolean} [gestureSettingsTouch.pinchToZoom=true] - Zoom on pinch gesture
|
||||
* @property {Boolean} [gestureSettingsTouch.zoomToRefPoint=true] - If zoomToRefPoint is true, the zoom is centered at the pointer position. Otherwise,
|
||||
* the zoom is centered at the canvas center.
|
||||
|
@ -386,6 +417,9 @@
|
|||
* @property {Boolean} [gestureSettingsUnknown.clickToZoom=false] - Zoom on click gesture
|
||||
* @property {Boolean} [gestureSettingsUnknown.dblClickToZoom=true] - Zoom on double-click gesture. Note: If set to true
|
||||
* then clickToZoom should be set to false to prevent multiple zooms.
|
||||
* @property {Boolean} [gestureSettingsUnknown.dblClickDragToZoom=false] - Zoom on dragging through
|
||||
* double-click gesture ( single click and next click to drag). Note: If set to true
|
||||
* then clickToZoom should be set to false to prevent multiple zooms.
|
||||
* @property {Boolean} [gestureSettingsUnknown.pinchToZoom=true] - Zoom on pinch gesture
|
||||
* @property {Boolean} [gestureSettingsUnknown.zoomToRefPoint=true] - If zoomToRefPoint is true, the zoom is centered at the pointer position. Otherwise,
|
||||
* the zoom is centered at the canvas center.
|
||||
|
@ -400,6 +434,9 @@
|
|||
* @property {Number} [zoomPerScroll=1.2]
|
||||
* The "zoom distance" per mouse scroll or touch pinch. <em><strong>Note:</strong> Setting this to 1.0 effectively disables the mouse-wheel zoom feature (also see gestureSettings[Mouse|Touch|Pen].scrollToZoom}).</em>
|
||||
*
|
||||
* @property {Number} [zoomPerDblClickDrag=1.2]
|
||||
* The "zoom distance" per double-click mouse drag. <em><strong>Note:</strong> Setting this to 1.0 effectively disables the double-click-drag-to-Zoom feature (also see gestureSettings[Mouse|Touch|Pen].dblClickDragToZoom).</em>
|
||||
*
|
||||
* @property {Number} [zoomPerSecond=1.0]
|
||||
* Sets the zoom amount per second when zoomIn/zoomOut buttons are pressed and held.
|
||||
* The value is a factor of the current zoom, so 1.0 (the default) disables zooming when the zoomIn/zoomOut buttons
|
||||
|
@ -410,6 +447,11 @@
|
|||
* @property {Boolean} [showNavigator=false]
|
||||
* Set to true to make the navigator minimap appear.
|
||||
*
|
||||
* @property {Element} [navigatorElement=null]
|
||||
* The element to hold the navigator minimap.
|
||||
* If an element is specified, the Id option (see navigatorId) is ignored.
|
||||
* If no element nor ID is specified, a div element will be generated accordingly.
|
||||
*
|
||||
* @property {String} [navigatorId=navigator-GENERATED DATE]
|
||||
* The ID of a div to hold the navigator minimap.
|
||||
* If an ID is specified, the navigatorPosition, navigatorSizeRatio, navigatorMaintainSizeRatio, navigator[Top|Left|Height|Width] and navigatorAutoFade options will be ignored.
|
||||
|
@ -477,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
|
||||
|
@ -541,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}
|
||||
|
@ -677,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.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -714,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.
|
||||
*
|
||||
|
@ -808,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,
|
||||
|
@ -831,7 +905,6 @@ function OpenSeadragon( options ){
|
|||
return $.type(obj) === "function";
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Taken from jQuery 1.6.1
|
||||
* @function isArray
|
||||
|
@ -917,7 +990,7 @@ function OpenSeadragon( options ){
|
|||
/**
|
||||
* Shim around Object.freeze. Does nothing if Object.freeze is not supported.
|
||||
* @param {Object} obj The object to freeze.
|
||||
* @return {Object} obj The frozen object.
|
||||
* @returns {Object} obj The frozen object.
|
||||
*/
|
||||
$.freezeObject = function(obj) {
|
||||
if (Object.freeze) {
|
||||
|
@ -1010,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 ) {
|
||||
|
@ -1032,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
|
||||
*/
|
||||
|
@ -1096,8 +1180,19 @@ function OpenSeadragon( options ){
|
|||
if ( options !== null || options !== undefined ) {
|
||||
// Extend the base object
|
||||
for ( name in options ) {
|
||||
src = target[ name ];
|
||||
copy = options[ name ];
|
||||
var descriptor = Object.getOwnPropertyDescriptor(options, name);
|
||||
|
||||
if (descriptor !== undefined) {
|
||||
if (descriptor.get || descriptor.set) {
|
||||
Object.defineProperty(target, name, descriptor);
|
||||
continue;
|
||||
}
|
||||
|
||||
copy = descriptor.value;
|
||||
} else {
|
||||
$.console.warn('Could not copy inherited property "' + name + '".');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Prevent never-ending loop
|
||||
if ( target === copy ) {
|
||||
|
@ -1106,6 +1201,8 @@ function OpenSeadragon( options ){
|
|||
|
||||
// Recurse if we're merging plain objects or arrays
|
||||
if ( deep && copy && ( OpenSeadragon.isPlainObject( copy ) || ( copyIsArray = OpenSeadragon.isArray( copy ) ) ) ) {
|
||||
src = target[ name ];
|
||||
|
||||
if ( copyIsArray ) {
|
||||
copyIsArray = false;
|
||||
clone = src && OpenSeadragon.isArray( src ) ? src : [];
|
||||
|
@ -1159,6 +1256,7 @@ function OpenSeadragon( options ){
|
|||
loadTilesWithAjax: false,
|
||||
ajaxHeaders: {},
|
||||
splitHashDataForPost: false,
|
||||
callTileLoadedWithCachedData: false,
|
||||
|
||||
//PAN AND ZOOM SETTINGS AND CONSTRAINTS
|
||||
panHorizontal: true,
|
||||
|
@ -1180,11 +1278,13 @@ function OpenSeadragon( options ){
|
|||
dblClickDistThreshold: 20,
|
||||
springStiffness: 6.5,
|
||||
animationTime: 1.2,
|
||||
loadDestinationTilesOnAnimation: true,
|
||||
gestureSettingsMouse: {
|
||||
dragToPan: true,
|
||||
scrollToZoom: true,
|
||||
clickToZoom: true,
|
||||
dblClickToZoom: false,
|
||||
dblClickDragToZoom: false,
|
||||
pinchToZoom: false,
|
||||
zoomToRefPoint: true,
|
||||
flickEnabled: false,
|
||||
|
@ -1197,6 +1297,7 @@ function OpenSeadragon( options ){
|
|||
scrollToZoom: false,
|
||||
clickToZoom: false,
|
||||
dblClickToZoom: true,
|
||||
dblClickDragToZoom: true,
|
||||
pinchToZoom: true,
|
||||
zoomToRefPoint: true,
|
||||
flickEnabled: true,
|
||||
|
@ -1209,6 +1310,7 @@ function OpenSeadragon( options ){
|
|||
scrollToZoom: false,
|
||||
clickToZoom: true,
|
||||
dblClickToZoom: false,
|
||||
dblClickDragToZoom: false,
|
||||
pinchToZoom: false,
|
||||
zoomToRefPoint: true,
|
||||
flickEnabled: false,
|
||||
|
@ -1221,6 +1323,7 @@ function OpenSeadragon( options ){
|
|||
scrollToZoom: false,
|
||||
clickToZoom: false,
|
||||
dblClickToZoom: true,
|
||||
dblClickDragToZoom: false,
|
||||
pinchToZoom: true,
|
||||
zoomToRefPoint: true,
|
||||
flickEnabled: true,
|
||||
|
@ -1230,6 +1333,7 @@ function OpenSeadragon( options ){
|
|||
},
|
||||
zoomPerClick: 2,
|
||||
zoomPerScroll: 1.2,
|
||||
zoomPerDblClickDrag: 1.2,
|
||||
zoomPerSecond: 1.0,
|
||||
blendTime: 0,
|
||||
alwaysBlend: false,
|
||||
|
@ -1245,6 +1349,7 @@ function OpenSeadragon( options ){
|
|||
preserveImageSizeOnResize: false, // requires autoResize=true
|
||||
minScrollDeltaTime: 50,
|
||||
rotationIncrement: 90,
|
||||
maxTilesPerFrame: 1,
|
||||
|
||||
//DEFAULT CONTROL SETTINGS
|
||||
showSequenceControl: true, //SEQUENCE
|
||||
|
@ -1265,6 +1370,7 @@ function OpenSeadragon( options ){
|
|||
|
||||
//VIEWPORT NAVIGATOR SETTINGS
|
||||
showNavigator: false,
|
||||
navigatorElement: null,
|
||||
navigatorId: null,
|
||||
navigatorPosition: null,
|
||||
navigatorSizeRatio: 0.2,
|
||||
|
@ -1285,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,
|
||||
|
@ -1316,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/",
|
||||
|
@ -1384,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
|
||||
|
@ -1634,8 +1752,8 @@ function OpenSeadragon( options ){
|
|||
|
||||
/**
|
||||
* Compute the modulo of a number but makes sure to always return
|
||||
* a positive value.
|
||||
* @param {Number} number the number to computes the modulo of
|
||||
* a positive value (also known as Euclidean modulo).
|
||||
* @param {Number} number the number to compute the modulo of
|
||||
* @param {Number} modulo the modulo
|
||||
* @returns {Number} the result of the modulo of number
|
||||
*/
|
||||
|
@ -1647,6 +1765,7 @@ function OpenSeadragon( options ){
|
|||
return result;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Determines if a point is within the bounding rectangle of the given element (hit-test).
|
||||
* @function
|
||||
|
@ -2095,7 +2214,7 @@ function OpenSeadragon( options ){
|
|||
* @param {Boolean} [options.capture]
|
||||
* @param {Boolean} [options.passive]
|
||||
* @param {Boolean} [options.once]
|
||||
* @return {String} The protocol (http:, https:, file:, ftp: ...)
|
||||
* @returns {String} The protocol (http:, https:, file:, ftp: ...)
|
||||
*/
|
||||
normalizeEventListenerOptions: function (options) {
|
||||
var opts;
|
||||
|
@ -2205,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
|
||||
|
@ -2259,7 +2342,7 @@ function OpenSeadragon( options ){
|
|||
* @function
|
||||
* @private
|
||||
* @param {String} url The url to retrieve the protocol from.
|
||||
* @return {String} The protocol (http:, https:, file:, ftp: ...)
|
||||
* @returns {String} The protocol (http:, https:, file:, ftp: ...)
|
||||
*/
|
||||
getUrlProtocol: function( url ) {
|
||||
var match = url.match(/^([a-z]+:)\/\//i);
|
||||
|
@ -2273,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
|
||||
|
@ -2321,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 TileSrouce::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}
|
||||
|
@ -2342,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" );
|
||||
|
@ -2514,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." );
|
||||
}
|
||||
|
@ -2561,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);
|
||||
}
|
||||
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
@ -2627,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 = {};
|
||||
|
||||
|
@ -2647,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 &&
|
||||
|
@ -2732,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');
|
||||
}
|
||||
})();
|
||||
|
||||
|
@ -2840,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-2013 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
|
||||
|
@ -123,7 +123,7 @@ $.extend( $.OsmTileSource.prototype, $.TileSource.prototype, /** @lends OpenSead
|
|||
* @param {Object} data - the raw configuration
|
||||
* @param {String} url - the url the data was retrieved from if any.
|
||||
* @param {String} postData - HTTP POST data in k=v&k2=v2... form or null
|
||||
* @return {Object} options - A dictionary of keyword arguments sufficient
|
||||
* @returns {Object} options - A dictionary of keyword arguments sufficient
|
||||
* to configure this tile sources constructor.
|
||||
*/
|
||||
configure: function( data, url, postData ){
|
||||
|
@ -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-2013 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';
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -299,21 +323,24 @@
|
|||
this.adjust(position, size);
|
||||
|
||||
var rotate = 0;
|
||||
if (viewport.degrees &&
|
||||
if (viewport.getRotation(true) &&
|
||||
this.rotationMode !== $.OverlayRotationMode.NO_ROTATION) {
|
||||
// BOUNDING_BOX is only valid if both directions get scaled.
|
||||
// Get replaced by EXACT otherwise.
|
||||
if (this.rotationMode === $.OverlayRotationMode.BOUNDING_BOX &&
|
||||
this.width !== null && this.height !== null) {
|
||||
var rect = new $.Rect(position.x, position.y, size.x, size.y);
|
||||
var boundingBox = this._getBoundingBox(rect, viewport.degrees);
|
||||
var boundingBox = this._getBoundingBox(rect, viewport.getRotation(true));
|
||||
position = boundingBox.getTopLeft();
|
||||
size = boundingBox.getSize();
|
||||
} else {
|
||||
rotate = viewport.degrees;
|
||||
rotate = viewport.getRotation(true);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -447,7 +474,7 @@
|
|||
// private
|
||||
_adjustBoundsForRotation: function(viewport, bounds) {
|
||||
if (!viewport ||
|
||||
viewport.degrees === 0 ||
|
||||
viewport.getRotation(true) === 0 ||
|
||||
this.rotationMode === $.OverlayRotationMode.EXACT) {
|
||||
return bounds;
|
||||
}
|
||||
|
@ -467,7 +494,7 @@
|
|||
}
|
||||
|
||||
// NO_ROTATION case
|
||||
return bounds.rotate(-viewport.degrees,
|
||||
return bounds.rotate(-viewport.getRotation(true),
|
||||
this._getPlacementPoint(bounds));
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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-2013 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
|
@ -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-2013 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-2013 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
|
||||
|
@ -240,7 +240,7 @@ $.Rect.prototype = {
|
|||
* Determines if two Rectangles have equivalent components.
|
||||
* @function
|
||||
* @param {OpenSeadragon.Rect} rectangle The Rectangle to compare to.
|
||||
* @return {Boolean} 'true' if all components are equal, otherwise 'false'.
|
||||
* @returns {Boolean} 'true' if all components are equal, otherwise 'false'.
|
||||
*/
|
||||
equals: function(other) {
|
||||
return (other instanceof $.Rect) &&
|
||||
|
@ -287,7 +287,7 @@ $.Rect.prototype = {
|
|||
* Returns the smallest rectangle that will contain this and the given
|
||||
* rectangle bounding boxes.
|
||||
* @param {OpenSeadragon.Rect} rect
|
||||
* @return {OpenSeadragon.Rect} The new rectangle.
|
||||
* @returns {OpenSeadragon.Rect} The new rectangle.
|
||||
*/
|
||||
union: function(rect) {
|
||||
var thisBoundingBox = this.getBoundingBox();
|
||||
|
@ -313,7 +313,7 @@ $.Rect.prototype = {
|
|||
* Returns the bounding box of the intersection of this rectangle with the
|
||||
* given rectangle.
|
||||
* @param {OpenSeadragon.Rect} rect
|
||||
* @return {OpenSeadragon.Rect} the bounding box of the intersection
|
||||
* @returns {OpenSeadragon.Rect} the bounding box of the intersection
|
||||
* or null if the rectangles don't intersect.
|
||||
*/
|
||||
intersection: function(rect) {
|
||||
|
@ -441,7 +441,7 @@ $.Rect.prototype = {
|
|||
* @param {Number} degrees The angle in degrees to rotate.
|
||||
* @param {OpenSeadragon.Point} [pivot] The point about which to rotate.
|
||||
* Defaults to the center of the rectangle.
|
||||
* @return {OpenSeadragon.Rect}
|
||||
* @returns {OpenSeadragon.Rect}
|
||||
*/
|
||||
rotate: function(degrees, pivot) {
|
||||
degrees = $.positiveModulo(degrees, 360);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* OpenSeadragon - ReferenceStrip
|
||||
*
|
||||
* Copyright (C) 2009 CodePlex Foundation
|
||||
* Copyright (C) 2010-2013 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-2013 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-2013 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
|
||||
|
|
721
src/tile.js
|
@ -2,7 +2,7 @@
|
|||
* OpenSeadragon - Tile
|
||||
*
|
||||
* Copyright (C) 2009 CodePlex Foundation
|
||||
* Copyright (C) 2010-2013 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
|
||||
|
@ -44,16 +44,16 @@
|
|||
* coordinates.
|
||||
* @param {Boolean} exists Is this tile a part of a sparse image? ( Also has
|
||||
* this tile failed to load? )
|
||||
* @param {String} url The URL of this tile's image.
|
||||
* @param {CanvasRenderingContext2D} context2D The context2D of this tile if it
|
||||
* is provided directly by the tile source.
|
||||
* @param {String|Function} url The URL of this tile's image or a function that returns a url.
|
||||
* @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 TileSrouce::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) {
|
||||
|
@ -82,11 +82,17 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, aja
|
|||
*/
|
||||
this.bounds = bounds;
|
||||
/**
|
||||
* 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.
|
||||
* @member {OpenSeadragon.Rect} sourceBounds
|
||||
* @memberof OpenSeadragon.Tile#
|
||||
*/
|
||||
* 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.
|
||||
* @member {OpenSeadragon.Rect} sourceBounds
|
||||
* @memberof OpenSeadragon.Tile#
|
||||
*/
|
||||
this.sourceBounds = sourceBounds;
|
||||
/**
|
||||
* Is this tile a part of a sparse image? Also has this tile failed to load?
|
||||
|
@ -95,16 +101,18 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, aja
|
|||
*/
|
||||
this.exists = exists;
|
||||
/**
|
||||
* The URL of this tile's image.
|
||||
* @member {String} url
|
||||
* Private property to hold string url or url retriever function.
|
||||
* Consumers should access via Tile.getUrl()
|
||||
* @member {String|Function} url
|
||||
* @memberof OpenSeadragon.Tile#
|
||||
* @private
|
||||
*/
|
||||
this.url = url;
|
||||
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 TileSrouce::getPostData) or null
|
||||
* see TileSource::getTilePostData) or null
|
||||
* @memberof OpenSeadragon.Tile#
|
||||
*/
|
||||
this.postData = postData;
|
||||
|
@ -113,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
|
||||
|
@ -127,17 +137,15 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, aja
|
|||
* @memberof OpenSeadragon.Tile#
|
||||
*/
|
||||
this.ajaxHeaders = ajaxHeaders;
|
||||
/**
|
||||
* The unique cache key for this tile.
|
||||
* @member {String} cacheKey
|
||||
* @memberof OpenSeadragon.Tile#
|
||||
*/
|
||||
|
||||
if (cacheKey === undefined) {
|
||||
$.console.error("Tile constructor needs 'cacheKey' variable: creation tile cache" +
|
||||
$.console.warn("Tile constructor needs 'cacheKey' variable: creation tile cache" +
|
||||
" in Tile class is deprecated. TileSource.prototype.getTileHashKey will be used.");
|
||||
cacheKey = $.TileSource.prototype.getTileHashKey(level, x, y, url, ajaxHeaders, postData);
|
||||
}
|
||||
this.cacheKey = cacheKey;
|
||||
|
||||
this._cKey = cacheKey || "";
|
||||
this._ocKey = cacheKey || "";
|
||||
|
||||
/**
|
||||
* Is this tile loaded?
|
||||
|
@ -151,32 +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 Image object for this tile.
|
||||
* @member {Object} image
|
||||
* @memberof OpenSeadragon.Tile#
|
||||
*/
|
||||
this.image = 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
|
||||
|
@ -210,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;
|
||||
/**
|
||||
|
@ -222,6 +204,13 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, aja
|
|||
*/
|
||||
this.visibility = null;
|
||||
|
||||
/**
|
||||
* The transparency indicator of this tile.
|
||||
* @member {Boolean} hasTransparency true if tile contains transparency for correct rendering
|
||||
* @memberof OpenSeadragon.Tile#
|
||||
*/
|
||||
this.hasTransparency = false;
|
||||
|
||||
/**
|
||||
* Whether this tile is currently being drawn.
|
||||
* @member {Boolean} beingDrawn
|
||||
|
@ -249,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 */
|
||||
|
@ -264,183 +279,439 @@ $.Tile.prototype = {
|
|||
return this.level + "/" + this.x + "_" + this.y;
|
||||
},
|
||||
|
||||
// private
|
||||
_hasTransparencyChannel: function() {
|
||||
return !!this.context2D || this.url.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 ) {
|
||||
this.element = $.makeNeutralElement( "div" );
|
||||
this.imgElement = this.cacheImageRecord.getImage().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;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* The Image object for this tile.
|
||||
* @member {Object} image
|
||||
* @memberof OpenSeadragon.Tile#
|
||||
* @deprecated
|
||||
* @returns {Image}
|
||||
*/
|
||||
drawCanvas: function( context, drawingHandler, scale, translate, shouldRoundPositionAndSize ) {
|
||||
get image() {
|
||||
$.console.error("[Tile.image] property has been deprecated. Use [Tile.getData] instead.");
|
||||
return this.getImage();
|
||||
},
|
||||
|
||||
var position = this.position.times($.pixelDensityRatio),
|
||||
size = this.size.times($.pixelDensityRatio),
|
||||
rendered;
|
||||
/**
|
||||
* The URL of this tile's image.
|
||||
* @member {String} url
|
||||
* @memberof OpenSeadragon.Tile#
|
||||
* @deprecated
|
||||
* @returns {String}
|
||||
*/
|
||||
get url() {
|
||||
$.console.error("[Tile.url] property has been deprecated. Use [Tile.getUrl] instead.");
|
||||
return this.getUrl();
|
||||
},
|
||||
|
||||
if (!this.context2D && !this.cacheImageRecord) {
|
||||
$.console.warn(
|
||||
'[Tile.drawCanvas] attempting to draw tile %s when it\'s not cached',
|
||||
this.toString());
|
||||
return;
|
||||
/**
|
||||
* 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}
|
||||
*/
|
||||
getImage: function() {
|
||||
$.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;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the url string for this tile.
|
||||
* @returns {String}
|
||||
*/
|
||||
getUrl: function() {
|
||||
if (typeof this._url === 'function') {
|
||||
return this._url();
|
||||
}
|
||||
|
||||
rendered = this.context2D || this.cacheImageRecord.getRenderedContext();
|
||||
return this._url;
|
||||
},
|
||||
|
||||
if ( !this.loaded || !rendered ){
|
||||
$.console.warn(
|
||||
"Attempting to draw tile %s when it's not yet loaded.",
|
||||
this.toString()
|
||||
);
|
||||
/**
|
||||
* Get the CanvasRenderingContext2D instance for tile image data drawn
|
||||
* onto Canvas if enabled and available
|
||||
* @returns {CanvasRenderingContext2D|undefined}
|
||||
*/
|
||||
getCanvasContext: function() {
|
||||
$.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;
|
||||
},
|
||||
|
||||
return;
|
||||
/**
|
||||
* The context2D of this tile if it is provided directly by the tile source.
|
||||
* @deprecated
|
||||
* @type {CanvasRenderingContext2D}
|
||||
*/
|
||||
get context2D() {
|
||||
$.console.error("[Tile.context2D] property has been deprecated. Use 'tile-invalidated' routine event instead.");
|
||||
return this.getCanvasContext();
|
||||
},
|
||||
|
||||
/**
|
||||
* 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);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
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._hasTransparencyChannel()) {
|
||||
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
|
||||
* @return {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());
|
||||
|
@ -454,15 +725,15 @@ $.Tile.prototype = {
|
|||
* Needed to avoid swimming and twitching.
|
||||
* @function
|
||||
* @param {Number} [scale=1] - Scale to be applied to position.
|
||||
* @return {OpenSeadragon.Point}
|
||||
* @returns {OpenSeadragon.Point}
|
||||
*/
|
||||
getTranslationForEdgeSmoothing: function(scale, canvasSize, sketchCanvasSize) {
|
||||
// The translation vector must have positive values, otherwise the image goes a bit off
|
||||
// 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)
|
||||
|
@ -474,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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
1541
src/tilecache.js
1940
src/tiledimage.js
|
@ -2,7 +2,7 @@
|
|||
* OpenSeadragon - TileSource
|
||||
*
|
||||
* Copyright (C) 2009 CodePlex Foundation
|
||||
* Copyright (C) 2010-2013 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 TileSrouce::getPostData) or null
|
||||
* see TileSource::getTilePostData) or null
|
||||
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
||||
*/
|
||||
_this.raiseEvent( 'open-failed', {
|
||||
|
@ -568,23 +586,34 @@ $.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
|
||||
* from if any.
|
||||
* @return {Boolean}
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
supports: function( data, url ) {
|
||||
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.
|
||||
|
@ -598,7 +627,7 @@ $.TileSource.prototype = {
|
|||
* from if any.
|
||||
* @param {String} postData - HTTP POST data in k=v&k2=v2... form or null value obtained from
|
||||
* the protocol URL after '#' sign if flag splitHashDataForPost set to 'true'
|
||||
* @return {Object} options - A dictionary of keyword arguments sufficient
|
||||
* @returns {Object} options - A dictionary of keyword arguments sufficient
|
||||
* to configure the tile source constructor (include all values you want to
|
||||
* instantiate the TileSource subclass with - what _options_ object should contain).
|
||||
* @throws {Error}
|
||||
|
@ -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.
|
||||
|
@ -618,6 +657,7 @@ $.TileSource.prototype = {
|
|||
* @param {Number} level
|
||||
* @param {Number} x
|
||||
* @param {Number} y
|
||||
* @returns {String|Function} url - A string for the url or a function that returns a url string.
|
||||
* @throws {Error}
|
||||
*/
|
||||
getTileUrl: function( level, x, y ) {
|
||||
|
@ -649,7 +689,7 @@ $.TileSource.prototype = {
|
|||
* @param {Number} level
|
||||
* @param {Number} x
|
||||
* @param {Number} y
|
||||
* @return {*|null} post data to send with tile configuration request
|
||||
* @returns {*|null} post data to send with tile configuration request
|
||||
*/
|
||||
getTilePostData: function( level, x, y ) {
|
||||
return null;
|
||||
|
@ -662,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
|
||||
|
@ -676,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
|
||||
|
@ -686,13 +734,19 @@ $.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) {
|
||||
if (ajaxHeaders) {
|
||||
return url + "+" + JSON.stringify(ajaxHeaders);
|
||||
} else {
|
||||
return url;
|
||||
function withHeaders(hash) {
|
||||
return ajaxHeaders ? hash + "+" + JSON.stringify(ajaxHeaders) : hash;
|
||||
}
|
||||
|
||||
if (typeof url !== "string") {
|
||||
return withHeaders(level + "/" + x + "_" + y);
|
||||
}
|
||||
return withHeaders(url);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -709,6 +763,211 @@ $.TileSource.prototype = {
|
|||
y >= 0 &&
|
||||
x < numTiles.x &&
|
||||
y < numTiles.y;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 url.match('.png');
|
||||
},
|
||||
|
||||
/**
|
||||
* Download tile data.
|
||||
* Note that if you override this function, you should override also downloadTileAbort().
|
||||
* @param {ImageJob} context job context that you have to call finish(...) on.
|
||||
* @param {String} [context.src] - URL of image to download.
|
||||
* @param {String} [context.loadWithAjax] - Whether to load this image with AJAX.
|
||||
* @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|?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 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: 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 parameter. The max number of milliseconds that
|
||||
* this image job may take to complete.
|
||||
* @param {string} [context.errorMsg] Private parameter. The final error message, default null (set by finish).
|
||||
*/
|
||||
downloadTileStart: function (context) {
|
||||
const dataStore = context.userData,
|
||||
image = new Image();
|
||||
|
||||
dataStore.image = image;
|
||||
dataStore.request = null;
|
||||
|
||||
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(image, dataStore.request, "image");
|
||||
};
|
||||
image.onload = function () {
|
||||
finalize();
|
||||
};
|
||||
image.onabort = image.onerror = function() {
|
||||
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 property of the image object.
|
||||
if (context.loadWithAjax) {
|
||||
dataStore.request = $.makeAjaxRequest({
|
||||
url: context.src,
|
||||
withCredentials: context.ajaxWithCredentials,
|
||||
headers: context.ajaxHeaders,
|
||||
responseType: "arraybuffer",
|
||||
postData: context.postData,
|
||||
success: function(request) {
|
||||
var blb;
|
||||
// Make the raw data into a blob.
|
||||
// BlobBuilder fallback adapted from
|
||||
// http://stackoverflow.com/questions/15293694/blob-constructor-browser-compatibility
|
||||
try {
|
||||
blb = new window.Blob([request.response]);
|
||||
} catch (e) {
|
||||
const BlobBuilder = (
|
||||
window.BlobBuilder ||
|
||||
window.WebKitBlobBuilder ||
|
||||
window.MozBlobBuilder ||
|
||||
window.MSBlobBuilder
|
||||
);
|
||||
if (e.name === 'TypeError' && 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) {
|
||||
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.
|
||||
image.src = (window.URL || window.webkitURL).createObjectURL(blb);
|
||||
}
|
||||
},
|
||||
error: function(request) {
|
||||
finalize("[downloadTileStart] Image load aborted - XHR error");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (context.crossOriginPolicy !== false) {
|
||||
image.crossOrigin = context.crossOriginPolicy;
|
||||
}
|
||||
image.src = context.src;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
downloadTileAbort: function (context) {
|
||||
if (context.userData.request) {
|
||||
context.userData.request.abort();
|
||||
}
|
||||
var image = context.userData.image;
|
||||
if (context.userData.image) {
|
||||
image.onload = image.onerror = image.onabort = null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Create cache object from the result of the download process. The
|
||||
* 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
|
||||
* - 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 {OpenSeadragon.Tile} tile instance the cache was created with
|
||||
* @deprecated
|
||||
*/
|
||||
createTileCache: function(cacheObject, data, tile) {
|
||||
$.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.
|
||||
* 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) {
|
||||
$.console.error("[TileSource.destroyTileCache] has been deprecated. Use cache API of a tile instead.");
|
||||
//no-op, handled internally
|
||||
},
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
$.console.error("[TileSource.getTileCacheData] has been deprecated. Use cache API of a tile instead.");
|
||||
return cacheObject.getDataAs(undefined, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Compatibility image element getter
|
||||
* - 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.
|
||||
* 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) {
|
||||
$.console.error("[TileSource.getTileCacheDataAsImage] has been deprecated. Use cache API of a tile instead.");
|
||||
return cacheObject.getImage();
|
||||
},
|
||||
|
||||
/**
|
||||
* Compatibility context 2D getter
|
||||
* - 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 {OpenSeadragon.CacheRecord} cacheObject context cache object
|
||||
* @returns {CanvasRenderingContext2D} context of the canvas representation of the cache data
|
||||
* @deprecated
|
||||
*/
|
||||
getTileCacheDataAsContext2D: function(cacheObject) {
|
||||
$.console.error("[TileSource.getTileCacheDataAsContext2D] has been deprecated. Use cache API of a tile instead.");
|
||||
return cacheObject.getRenderedContext();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -740,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-2013 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-2013 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,7 +112,7 @@ $.extend( $.TmsTileSource.prototype, $.TileSource.prototype, /** @lends OpenSead
|
|||
* @param {Object} data - the raw configuration
|
||||
* @param {String} url - the url the data was retrieved from if any.
|
||||
* @param {String} postData - HTTP POST data in k=v&k2=v2... form or null
|
||||
* @return {Object} options - A dictionary of keyword arguments sufficient
|
||||
* @returns {Object} options - A dictionary of keyword arguments sufficient
|
||||
* to configure this tile sources constructor.
|
||||
*/
|
||||
configure: function( data, url, postData ){
|
||||
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
918
src/viewer.js
446
src/viewport.js
|
@ -2,7 +2,7 @@
|
|||
* OpenSeadragon - Viewport
|
||||
*
|
||||
* Copyright (C) 2009 CodePlex Foundation
|
||||
* Copyright (C) 2010-2013 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
|
||||
|
@ -86,6 +86,9 @@ $.Viewport = function( options ) {
|
|||
|
||||
delete options.margins;
|
||||
|
||||
options.initialDegrees = options.degrees;
|
||||
delete options.degrees;
|
||||
|
||||
$.extend( true, this, {
|
||||
|
||||
//required settings
|
||||
|
@ -94,7 +97,8 @@ $.Viewport = function( options ) {
|
|||
|
||||
//internal state properties
|
||||
zoomPoint: null,
|
||||
viewer: null,
|
||||
rotationPivot: null,
|
||||
viewer: null,
|
||||
|
||||
//configurable options
|
||||
springStiffness: $.DEFAULT_SETTINGS.springStiffness,
|
||||
|
@ -107,7 +111,7 @@ $.Viewport = function( options ) {
|
|||
defaultZoomLevel: $.DEFAULT_SETTINGS.defaultZoomLevel,
|
||||
minZoomLevel: $.DEFAULT_SETTINGS.minZoomLevel,
|
||||
maxZoomLevel: $.DEFAULT_SETTINGS.maxZoomLevel,
|
||||
degrees: $.DEFAULT_SETTINGS.degrees,
|
||||
initialDegrees: $.DEFAULT_SETTINGS.degrees,
|
||||
flipped: $.DEFAULT_SETTINGS.flipped,
|
||||
homeFillsViewer: $.DEFAULT_SETTINGS.homeFillsViewer,
|
||||
silenceMultiImageWarnings: $.DEFAULT_SETTINGS.silenceMultiImageWarnings
|
||||
|
@ -133,9 +137,16 @@ $.Viewport = function( options ) {
|
|||
animationTime: this.animationTime
|
||||
});
|
||||
|
||||
this.degreesSpring = new $.Spring({
|
||||
initial: options.initialDegrees,
|
||||
springStiffness: this.springStiffness,
|
||||
animationTime: this.animationTime
|
||||
});
|
||||
|
||||
this._oldCenterX = this.centerSpringX.current.value;
|
||||
this._oldCenterY = this.centerSpringY.current.value;
|
||||
this._oldZoom = this.zoomSpring.current.value;
|
||||
this._oldDegrees = this.degreesSpring.current.value;
|
||||
|
||||
this._setContentBounds(new $.Rect(0, 0, 1, 1), 1);
|
||||
|
||||
|
@ -145,11 +156,24 @@ $.Viewport = function( options ) {
|
|||
|
||||
/** @lends OpenSeadragon.Viewport.prototype */
|
||||
$.Viewport.prototype = {
|
||||
|
||||
// deprecated
|
||||
get degrees () {
|
||||
$.console.warn('Accessing [Viewport.degrees] is deprecated. Use viewport.getRotation instead.');
|
||||
return this.getRotation();
|
||||
},
|
||||
|
||||
// deprecated
|
||||
set degrees (degrees) {
|
||||
$.console.warn('Setting [Viewport.degrees] is deprecated. Use viewport.rotateTo, viewport.rotateBy, or viewport.setRotation instead.');
|
||||
this.rotateTo(degrees);
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the viewport's home bounds and constraints for the given content size.
|
||||
* @function
|
||||
* @param {OpenSeadragon.Point} contentSize - size of the content in content units
|
||||
* @return {OpenSeadragon.Viewport} Chainable.
|
||||
* @returns {OpenSeadragon.Viewport} Chainable.
|
||||
* @fires OpenSeadragon.Viewer.event:reset-size
|
||||
*/
|
||||
resetContentSize: function(contentSize) {
|
||||
|
@ -184,7 +208,7 @@ $.Viewport.prototype = {
|
|||
this._contentSizeNoRotate = this._contentBoundsNoRotate.getSize().times(
|
||||
contentFactor);
|
||||
|
||||
this._contentBounds = bounds.rotate(this.degrees).getBoundingBox();
|
||||
this._contentBounds = bounds.rotate(this.getRotation()).getBoundingBox();
|
||||
this._contentSize = this._contentBounds.getSize().times(contentFactor);
|
||||
this._contentAspectRatio = this._contentSize.x / this._contentSize.y;
|
||||
|
||||
|
@ -367,7 +391,7 @@ $.Viewport.prototype = {
|
|||
* @returns {OpenSeadragon.Rect} The location you are zoomed/panned to, in viewport coordinates.
|
||||
*/
|
||||
getBounds: function(current) {
|
||||
return this.getBoundsNoRotate(current).rotate(-this.getRotation());
|
||||
return this.getBoundsNoRotate(current).rotate(-this.getRotation(current));
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -399,7 +423,7 @@ $.Viewport.prototype = {
|
|||
*/
|
||||
getBoundsWithMargins: function(current) {
|
||||
return this.getBoundsNoRotateWithMargins(current).rotate(
|
||||
-this.getRotation(), this.getCenter(current));
|
||||
-this.getRotation(current), this.getCenter(current));
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -459,7 +483,7 @@ $.Viewport.prototype = {
|
|||
);
|
||||
|
||||
newZoomPixel = this._pixelFromPoint(this.zoomPoint, bounds);
|
||||
deltaZoomPixels = newZoomPixel.minus( oldZoomPixel );
|
||||
deltaZoomPixels = newZoomPixel.minus( oldZoomPixel ).rotate(-this.getRotation(true));
|
||||
deltaZoomPoints = deltaZoomPixels.divide( this._containerInnerSize.x * zoom );
|
||||
|
||||
return centerTarget.plus( deltaZoomPoints );
|
||||
|
@ -488,52 +512,78 @@ $.Viewport.prototype = {
|
|||
* @function
|
||||
* @private
|
||||
* @param {OpenSeadragon.Rect} bounds
|
||||
* @return {OpenSeadragon.Rect} constrained bounds.
|
||||
* @returns {OpenSeadragon.Rect} constrained bounds.
|
||||
*/
|
||||
_applyBoundaryConstraints: function(bounds) {
|
||||
var newBounds = new $.Rect(
|
||||
bounds.x,
|
||||
bounds.y,
|
||||
bounds.width,
|
||||
bounds.height);
|
||||
var newBounds = this.viewportToViewerElementRectangle(bounds).getBoundingBox();
|
||||
var cb = this.viewportToViewerElementRectangle(this._contentBoundsNoRotate).getBoundingBox();
|
||||
|
||||
var xConstrained = false;
|
||||
var yConstrained = false;
|
||||
|
||||
if (this.wrapHorizontal) {
|
||||
//do nothing
|
||||
} else {
|
||||
var horizontalThreshold = this.visibilityRatio * newBounds.width;
|
||||
var boundsRight = newBounds.x + newBounds.width;
|
||||
var contentRight = this._contentBoundsNoRotate.x + this._contentBoundsNoRotate.width;
|
||||
var leftDx = this._contentBoundsNoRotate.x - boundsRight + horizontalThreshold;
|
||||
var rightDx = contentRight - newBounds.x - horizontalThreshold;
|
||||
var contentRight = cb.x + cb.width;
|
||||
|
||||
if (horizontalThreshold > this._contentBoundsNoRotate.width) {
|
||||
var horizontalThreshold, leftDx, rightDx;
|
||||
if (newBounds.width > cb.width) {
|
||||
horizontalThreshold = this.visibilityRatio * cb.width;
|
||||
} else {
|
||||
horizontalThreshold = this.visibilityRatio * newBounds.width;
|
||||
}
|
||||
|
||||
leftDx = cb.x - boundsRight + horizontalThreshold;
|
||||
rightDx = contentRight - newBounds.x - horizontalThreshold;
|
||||
if (horizontalThreshold > cb.width) {
|
||||
newBounds.x += (leftDx + rightDx) / 2;
|
||||
xConstrained = true;
|
||||
} else if (rightDx < 0) {
|
||||
newBounds.x += rightDx;
|
||||
xConstrained = true;
|
||||
} else if (leftDx > 0) {
|
||||
newBounds.x += leftDx;
|
||||
xConstrained = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (this.wrapVertical) {
|
||||
//do nothing
|
||||
} else {
|
||||
var verticalThreshold = this.visibilityRatio * newBounds.height;
|
||||
var boundsBottom = newBounds.y + newBounds.height;
|
||||
var contentBottom = this._contentBoundsNoRotate.y + this._contentBoundsNoRotate.height;
|
||||
var topDy = this._contentBoundsNoRotate.y - boundsBottom + verticalThreshold;
|
||||
var bottomDy = contentBottom - newBounds.y - verticalThreshold;
|
||||
var contentBottom = cb.y + cb.height;
|
||||
|
||||
if (verticalThreshold > this._contentBoundsNoRotate.height) {
|
||||
var verticalThreshold, topDy, bottomDy;
|
||||
if (newBounds.height > cb.height) {
|
||||
verticalThreshold = this.visibilityRatio * cb.height;
|
||||
} else{
|
||||
verticalThreshold = this.visibilityRatio * newBounds.height;
|
||||
}
|
||||
|
||||
topDy = cb.y - boundsBottom + verticalThreshold;
|
||||
bottomDy = contentBottom - newBounds.y - verticalThreshold;
|
||||
if (verticalThreshold > cb.height) {
|
||||
newBounds.y += (topDy + bottomDy) / 2;
|
||||
yConstrained = true;
|
||||
} else if (bottomDy < 0) {
|
||||
newBounds.y += bottomDy;
|
||||
yConstrained = true;
|
||||
} else if (topDy > 0) {
|
||||
newBounds.y += topDy;
|
||||
yConstrained = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return newBounds;
|
||||
var constraintApplied = xConstrained || yConstrained;
|
||||
var newViewportBounds = constraintApplied ? this.viewerElementToViewportRectangle(newBounds) : bounds.clone();
|
||||
newViewportBounds.xConstrained = xConstrained;
|
||||
newViewportBounds.yConstrained = yConstrained;
|
||||
newViewportBounds.constraintApplied = constraintApplied;
|
||||
|
||||
return newViewportBounds;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -566,8 +616,8 @@ $.Viewport.prototype = {
|
|||
* zooming and panning to the closest acceptable zoom and location.
|
||||
* @function
|
||||
* @param {Boolean} [immediately=false]
|
||||
* @return {OpenSeadragon.Viewport} Chainable.
|
||||
* @fires OpenSeadragon.Viewer.event:constrain
|
||||
* @returns {OpenSeadragon.Viewport} Chainable.
|
||||
* @fires OpenSeadragon.Viewer.event:constrain if constraints were applied
|
||||
*/
|
||||
applyConstraints: function(immediately) {
|
||||
var actualZoom = this.getZoom();
|
||||
|
@ -577,17 +627,13 @@ $.Viewport.prototype = {
|
|||
this.zoomTo(constrainedZoom, this.zoomPoint, immediately);
|
||||
}
|
||||
|
||||
var bounds = this.getBoundsNoRotate();
|
||||
var constrainedBounds = this._applyBoundaryConstraints(bounds);
|
||||
this._raiseConstraintsEvent(immediately);
|
||||
var constrainedBounds = this.getConstrainedBounds(false);
|
||||
|
||||
if (bounds.x !== constrainedBounds.x ||
|
||||
bounds.y !== constrainedBounds.y ||
|
||||
immediately) {
|
||||
this.fitBounds(
|
||||
constrainedBounds.rotate(-this.getRotation()),
|
||||
immediately);
|
||||
if(constrainedBounds.constraintApplied){
|
||||
this.fitBounds(constrainedBounds, immediately);
|
||||
this._raiseConstraintsEvent(immediately);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
|
@ -595,7 +641,7 @@ $.Viewport.prototype = {
|
|||
* Equivalent to {@link OpenSeadragon.Viewport#applyConstraints}
|
||||
* @function
|
||||
* @param {Boolean} [immediately=false]
|
||||
* @return {OpenSeadragon.Viewport} Chainable.
|
||||
* @returns {OpenSeadragon.Viewport} Chainable.
|
||||
* @fires OpenSeadragon.Viewer.event:constrain
|
||||
*/
|
||||
ensureVisible: function(immediately) {
|
||||
|
@ -607,7 +653,7 @@ $.Viewport.prototype = {
|
|||
* @private
|
||||
* @param {OpenSeadragon.Rect} bounds
|
||||
* @param {Object} options (immediately=false, constraints=false)
|
||||
* @return {OpenSeadragon.Viewport} Chainable.
|
||||
* @returns {OpenSeadragon.Viewport} Chainable.
|
||||
*/
|
||||
_fitBounds: function(bounds, options) {
|
||||
options = options || {};
|
||||
|
@ -637,45 +683,53 @@ $.Viewport.prototype = {
|
|||
newBounds.y = center.y - newBounds.height / 2;
|
||||
var newZoom = 1.0 / newBounds.width;
|
||||
|
||||
if (constraints) {
|
||||
var newBoundsAspectRatio = newBounds.getAspectRatio();
|
||||
var newConstrainedZoom = this._applyZoomConstraints(newZoom);
|
||||
|
||||
if (newZoom !== newConstrainedZoom) {
|
||||
newZoom = newConstrainedZoom;
|
||||
newBounds.width = 1.0 / newZoom;
|
||||
newBounds.x = center.x - newBounds.width / 2;
|
||||
newBounds.height = newBounds.width / newBoundsAspectRatio;
|
||||
newBounds.y = center.y - newBounds.height / 2;
|
||||
}
|
||||
|
||||
newBounds = this._applyBoundaryConstraints(newBounds);
|
||||
center = newBounds.getCenter();
|
||||
this._raiseConstraintsEvent(immediately);
|
||||
}
|
||||
|
||||
if (immediately) {
|
||||
this.panTo(center, true);
|
||||
return this.zoomTo(newZoom, null, true);
|
||||
this.zoomTo(newZoom, null, true);
|
||||
if(constraints){
|
||||
this.applyConstraints(true);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
this.panTo(this.getCenter(true), true);
|
||||
this.zoomTo(this.getZoom(true), null, true);
|
||||
var currentCenter = this.getCenter(true);
|
||||
var currentZoom = this.getZoom(true);
|
||||
this.panTo(currentCenter, true);
|
||||
this.zoomTo(currentZoom, null, true);
|
||||
|
||||
var oldBounds = this.getBounds();
|
||||
var oldZoom = this.getZoom();
|
||||
|
||||
if (oldZoom === 0 || Math.abs(newZoom / oldZoom - 1) < 0.00000001) {
|
||||
this.zoomTo(newZoom, true);
|
||||
return this.panTo(center, immediately);
|
||||
this.zoomTo(newZoom, null, true);
|
||||
this.panTo(center, immediately);
|
||||
if(constraints){
|
||||
this.applyConstraints(false);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
newBounds = newBounds.rotate(-this.getRotation());
|
||||
var referencePoint = newBounds.getTopLeft().times(newZoom)
|
||||
.minus(oldBounds.getTopLeft().times(oldZoom))
|
||||
.divide(newZoom - oldZoom);
|
||||
if(constraints){
|
||||
this.panTo(center, false);
|
||||
|
||||
return this.zoomTo(newZoom, referencePoint, immediately);
|
||||
newZoom = this._applyZoomConstraints(newZoom);
|
||||
this.zoomTo(newZoom, null, false);
|
||||
|
||||
var constrainedBounds = this.getConstrainedBounds();
|
||||
|
||||
this.panTo(currentCenter, true);
|
||||
this.zoomTo(currentZoom, null, true);
|
||||
|
||||
this.fitBounds(constrainedBounds);
|
||||
} else {
|
||||
var rotatedNewBounds = newBounds.rotate(-this.getRotation());
|
||||
var referencePoint = rotatedNewBounds.getTopLeft().times(newZoom)
|
||||
.minus(oldBounds.getTopLeft().times(oldZoom))
|
||||
.divide(newZoom - oldZoom);
|
||||
|
||||
this.zoomTo(newZoom, referencePoint, immediately);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -688,7 +742,7 @@ $.Viewport.prototype = {
|
|||
* @function
|
||||
* @param {OpenSeadragon.Rect} bounds
|
||||
* @param {Boolean} [immediately=false]
|
||||
* @return {OpenSeadragon.Viewport} Chainable.
|
||||
* @returns {OpenSeadragon.Viewport} Chainable.
|
||||
*/
|
||||
fitBounds: function(bounds, immediately) {
|
||||
return this._fitBounds(bounds, {
|
||||
|
@ -707,7 +761,7 @@ $.Viewport.prototype = {
|
|||
* @function
|
||||
* @param {OpenSeadragon.Rect} bounds
|
||||
* @param {Boolean} [immediately=false]
|
||||
* @return {OpenSeadragon.Viewport} Chainable.
|
||||
* @returns {OpenSeadragon.Viewport} Chainable.
|
||||
*/
|
||||
fitBoundsWithConstraints: function(bounds, immediately) {
|
||||
return this._fitBounds(bounds, {
|
||||
|
@ -719,7 +773,7 @@ $.Viewport.prototype = {
|
|||
/**
|
||||
* Zooms so the image just fills the viewer vertically.
|
||||
* @param {Boolean} immediately
|
||||
* @return {OpenSeadragon.Viewport} Chainable.
|
||||
* @returns {OpenSeadragon.Viewport} Chainable.
|
||||
*/
|
||||
fitVertically: function(immediately) {
|
||||
var box = new $.Rect(
|
||||
|
@ -733,7 +787,7 @@ $.Viewport.prototype = {
|
|||
/**
|
||||
* Zooms so the image just fills the viewer horizontally.
|
||||
* @param {Boolean} immediately
|
||||
* @return {OpenSeadragon.Viewport} Chainable.
|
||||
* @returns {OpenSeadragon.Viewport} Chainable.
|
||||
*/
|
||||
fitHorizontally: function(immediately) {
|
||||
var box = new $.Rect(
|
||||
|
@ -749,7 +803,10 @@ $.Viewport.prototype = {
|
|||
* Returns bounds taking constraints into account
|
||||
* Added to improve constrained panning
|
||||
* @param {Boolean} current - Pass true for the current location; defaults to false (target location).
|
||||
* @return {OpenSeadragon.Viewport} Chainable.
|
||||
* @returns {OpenSeadragon.Rect} The bounds in viewport coordinates after applying constraints. The returned $.Rect
|
||||
* contains additional properties constraintsApplied, xConstrained and yConstrained.
|
||||
* These flags indicate whether the viewport bounds were modified by the constraints
|
||||
* of the viewer rectangle, and in which dimension(s).
|
||||
*/
|
||||
getConstrainedBounds: function(current) {
|
||||
var bounds,
|
||||
|
@ -766,7 +823,7 @@ $.Viewport.prototype = {
|
|||
* @function
|
||||
* @param {OpenSeadragon.Point} delta
|
||||
* @param {Boolean} immediately
|
||||
* @return {OpenSeadragon.Viewport} Chainable.
|
||||
* @returns {OpenSeadragon.Viewport} Chainable.
|
||||
* @fires OpenSeadragon.Viewer.event:pan
|
||||
*/
|
||||
panBy: function( delta, immediately ) {
|
||||
|
@ -781,7 +838,7 @@ $.Viewport.prototype = {
|
|||
* @function
|
||||
* @param {OpenSeadragon.Point} center
|
||||
* @param {Boolean} immediately
|
||||
* @return {OpenSeadragon.Viewport} Chainable.
|
||||
* @returns {OpenSeadragon.Viewport} Chainable.
|
||||
* @fires OpenSeadragon.Viewer.event:pan
|
||||
*/
|
||||
panTo: function( center, immediately ) {
|
||||
|
@ -816,7 +873,7 @@ $.Viewport.prototype = {
|
|||
|
||||
/**
|
||||
* @function
|
||||
* @return {OpenSeadragon.Viewport} Chainable.
|
||||
* @returns {OpenSeadragon.Viewport} Chainable.
|
||||
* @fires OpenSeadragon.Viewer.event:zoom
|
||||
*/
|
||||
zoomBy: function(factor, refPoint, immediately) {
|
||||
|
@ -831,7 +888,7 @@ $.Viewport.prototype = {
|
|||
* @param {OpenSeadragon.Point} [refPoint] The point which will stay at
|
||||
* the same screen location. Defaults to the viewport center.
|
||||
* @param {Boolean} [immediately=false]
|
||||
* @return {OpenSeadragon.Viewport} Chainable.
|
||||
* @returns {OpenSeadragon.Viewport} Chainable.
|
||||
* @fires OpenSeadragon.Viewer.event:zoom
|
||||
*/
|
||||
zoomTo: function(zoom, refPoint, immediately) {
|
||||
|
@ -878,13 +935,90 @@ $.Viewport.prototype = {
|
|||
* Rotates this viewport to the angle specified.
|
||||
* @function
|
||||
* @param {Number} degrees The degrees to set the rotation to.
|
||||
* @return {OpenSeadragon.Viewport} Chainable.
|
||||
* @param {Boolean} [immediately=false] Whether to animate to the new angle
|
||||
* or rotate immediately.
|
||||
* * @returns {OpenSeadragon.Viewport} Chainable.
|
||||
*/
|
||||
setRotation: function(degrees) {
|
||||
setRotation: function(degrees, immediately) {
|
||||
return this.rotateTo(degrees, null, immediately);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the current rotation in degrees.
|
||||
* @function
|
||||
* @param {Boolean} [current=false] True for current rotation, false for target.
|
||||
* @returns {Number} The current rotation in degrees.
|
||||
*/
|
||||
getRotation: function(current) {
|
||||
return current ?
|
||||
this.degreesSpring.current.value :
|
||||
this.degreesSpring.target.value;
|
||||
},
|
||||
|
||||
/**
|
||||
* Rotates this viewport to the angle specified around a pivot point. Alias for rotateTo.
|
||||
* @function
|
||||
* @param {Number} degrees The degrees to set the rotation to.
|
||||
* @param {OpenSeadragon.Point} [pivot] (Optional) point in viewport coordinates
|
||||
* around which the rotation should be performed. Defaults to the center of the viewport.
|
||||
* @param {Boolean} [immediately=false] Whether to animate to the new angle
|
||||
* or rotate immediately.
|
||||
* * @returns {OpenSeadragon.Viewport} Chainable.
|
||||
*/
|
||||
setRotationWithPivot: function(degrees, pivot, immediately) {
|
||||
return this.rotateTo(degrees, pivot, immediately);
|
||||
},
|
||||
|
||||
/**
|
||||
* Rotates this viewport to the angle specified.
|
||||
* @function
|
||||
* @param {Number} degrees The degrees to set the rotation to.
|
||||
* @param {OpenSeadragon.Point} [pivot] (Optional) point in viewport coordinates
|
||||
* around which the rotation should be performed. Defaults to the center of the viewport.
|
||||
* @param {Boolean} [immediately=false] Whether to animate to the new angle
|
||||
* or rotate immediately.
|
||||
* @returns {OpenSeadragon.Viewport} Chainable.
|
||||
*/
|
||||
rotateTo: function(degrees, pivot, immediately){
|
||||
if (!this.viewer || !this.viewer.drawer.canRotate()) {
|
||||
return this;
|
||||
}
|
||||
this.degrees = $.positiveModulo(degrees, 360);
|
||||
|
||||
if (this.degreesSpring.target.value === degrees &&
|
||||
this.degreesSpring.isAtTargetValue()) {
|
||||
return this;
|
||||
}
|
||||
this.rotationPivot = pivot instanceof $.Point &&
|
||||
!isNaN(pivot.x) &&
|
||||
!isNaN(pivot.y) ?
|
||||
pivot :
|
||||
null;
|
||||
if (immediately) {
|
||||
if(this.rotationPivot){
|
||||
var changeInDegrees = degrees - this._oldDegrees;
|
||||
if(!changeInDegrees){
|
||||
this.rotationPivot = null;
|
||||
return this;
|
||||
}
|
||||
this._rotateAboutPivot(degrees);
|
||||
} else{
|
||||
this.degreesSpring.resetTo(degrees);
|
||||
}
|
||||
} else {
|
||||
var normalizedFrom = $.positiveModulo(this.degreesSpring.current.value, 360);
|
||||
var normalizedTo = $.positiveModulo(degrees, 360);
|
||||
var diff = normalizedTo - normalizedFrom;
|
||||
if (diff > 180) {
|
||||
normalizedTo -= 360;
|
||||
} else if (diff < -180) {
|
||||
normalizedTo += 360;
|
||||
}
|
||||
|
||||
var reverseDiff = normalizedFrom - normalizedTo;
|
||||
this.degreesSpring.resetTo(degrees + reverseDiff);
|
||||
this.degreesSpring.springTo(degrees);
|
||||
}
|
||||
|
||||
this._setContentBounds(
|
||||
this.viewer.world.getHomeBounds(),
|
||||
this.viewer.world.getContentFactor());
|
||||
|
@ -898,24 +1032,31 @@ $.Viewport.prototype = {
|
|||
* @type {object}
|
||||
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
||||
* @property {Number} degrees - The number of degrees the rotation was set to.
|
||||
* @property {Boolean} immediately - Whether the rotation happened immediately or was animated
|
||||
* @property {OpenSeadragon.Point} pivot - The point in viewport coordinates around which the rotation (if any) happened
|
||||
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
||||
*/
|
||||
this.viewer.raiseEvent('rotate', {degrees: degrees});
|
||||
this.viewer.raiseEvent('rotate', {degrees: degrees, immediately: !!immediately, pivot: this.rotationPivot || this.getCenter()});
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the current rotation in degrees.
|
||||
* Rotates this viewport by the angle specified.
|
||||
* @function
|
||||
* @return {Number} The current rotation in degrees.
|
||||
* @param {Number} degrees The degrees by which to rotate the viewport.
|
||||
* @param {OpenSeadragon.Point} [pivot] (Optional) point in viewport coordinates
|
||||
* around which the rotation should be performed. Defaults to the center of the viewport.
|
||||
* * @param {Boolean} [immediately=false] Whether to animate to the new angle
|
||||
* or rotate immediately.
|
||||
* @returns {OpenSeadragon.Viewport} Chainable.
|
||||
*/
|
||||
getRotation: function() {
|
||||
return this.degrees;
|
||||
rotateBy: function(degrees, pivot, immediately){
|
||||
return this.rotateTo(this.degreesSpring.target.value + degrees, pivot, immediately);
|
||||
},
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @return {OpenSeadragon.Viewport} Chainable.
|
||||
* @returns {OpenSeadragon.Viewport} Chainable.
|
||||
* @fires OpenSeadragon.Viewer.event:resize
|
||||
*/
|
||||
resize: function( newContainerSize, maintain ) {
|
||||
|
@ -937,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
|
||||
|
@ -953,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
|
||||
|
@ -965,30 +1131,70 @@ $.Viewport.prototype = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Update the zoom and center (X and Y) springs.
|
||||
* 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;
|
||||
this._adjustCenterSpringsForZoomPoint(function() {
|
||||
_this.zoomSpring.update();
|
||||
});
|
||||
|
||||
if(this.degreesSpring.isAtTargetValue()){
|
||||
this.rotationPivot = null;
|
||||
}
|
||||
this.centerSpringX.update();
|
||||
this.centerSpringY.update();
|
||||
|
||||
if(this.rotationPivot){
|
||||
this._rotateAboutPivot(true);
|
||||
}
|
||||
else{
|
||||
this.degreesSpring.update();
|
||||
}
|
||||
|
||||
|
||||
var changed = this.centerSpringX.current.value !== this._oldCenterX ||
|
||||
this.centerSpringY.current.value !== this._oldCenterY ||
|
||||
this.zoomSpring.current.value !== this._oldZoom;
|
||||
this.zoomSpring.current.value !== this._oldZoom ||
|
||||
this.degreesSpring.current.value !== this._oldDegrees;
|
||||
|
||||
|
||||
this._oldCenterX = this.centerSpringX.current.value;
|
||||
this._oldCenterY = this.centerSpringY.current.value;
|
||||
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
|
||||
_rotateAboutPivot: function(degreesOrUseSpring){
|
||||
var useSpring = degreesOrUseSpring === true;
|
||||
|
||||
var delta = this.rotationPivot.minus(this.getCenter());
|
||||
this.centerSpringX.shiftBy(delta.x);
|
||||
this.centerSpringY.shiftBy(delta.y);
|
||||
|
||||
if(useSpring){
|
||||
this.degreesSpring.update();
|
||||
} else {
|
||||
this.degreesSpring.resetTo(degreesOrUseSpring);
|
||||
}
|
||||
|
||||
var changeInDegrees = this.degreesSpring.current.value - this._oldDegrees;
|
||||
var rdelta = delta.rotate(changeInDegrees * -1).times(-1);
|
||||
this.centerSpringX.shiftBy(rdelta.x);
|
||||
this.centerSpringY.shiftBy(rdelta.y);
|
||||
},
|
||||
|
||||
// private
|
||||
_adjustCenterSpringsForZoomPoint: function(zoomSpringHandler) {
|
||||
if (this.zoomPoint) {
|
||||
var oldZoomPixel = this.pixelFromPoint(this.zoomPoint, true);
|
||||
|
@ -1035,7 +1241,7 @@ $.Viewport.prototype = {
|
|||
*/
|
||||
deltaPixelsFromPoints: function(deltaPoints, current) {
|
||||
return this.deltaPixelsFromPointsNoRotate(
|
||||
deltaPoints.rotate(this.getRotation()),
|
||||
deltaPoints.rotate(this.getRotation(current)),
|
||||
current);
|
||||
},
|
||||
|
||||
|
@ -1064,7 +1270,7 @@ $.Viewport.prototype = {
|
|||
*/
|
||||
deltaPointsFromPixels: function(deltaPixels, current) {
|
||||
return this.deltaPointsFromPixelsNoRotate(deltaPixels, current)
|
||||
.rotate(-this.getRotation());
|
||||
.rotate(-this.getRotation(current));
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1106,7 +1312,7 @@ $.Viewport.prototype = {
|
|||
// private
|
||||
_pixelFromPoint: function(point, bounds) {
|
||||
return this._pixelFromPointNoRotate(
|
||||
point.rotate(this.getRotation(), this.getCenter(true)),
|
||||
point.rotate(this.getRotation(true), this.getCenter(true)),
|
||||
bounds);
|
||||
},
|
||||
|
||||
|
@ -1139,8 +1345,8 @@ $.Viewport.prototype = {
|
|||
*/
|
||||
pointFromPixel: function(pixel, current) {
|
||||
return this.pointFromPixelNoRotate(pixel, current).rotate(
|
||||
-this.getRotation(),
|
||||
this.getCenter(true)
|
||||
-this.getRotation(current),
|
||||
this.getCenter(current)
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -1161,7 +1367,7 @@ $.Viewport.prototype = {
|
|||
* @param {(OpenSeadragon.Point|Number)} viewerX either a point or the X
|
||||
* coordinate in viewport coordinate system.
|
||||
* @param {Number} [viewerY] Y coordinate in viewport coordinate system.
|
||||
* @return {OpenSeadragon.Point} a point representing the coordinates in the image.
|
||||
* @returns {OpenSeadragon.Point} a point representing the coordinates in the image.
|
||||
*/
|
||||
viewportToImageCoordinates: function(viewerX, viewerY) {
|
||||
if (viewerX instanceof $.Point) {
|
||||
|
@ -1207,7 +1413,7 @@ $.Viewport.prototype = {
|
|||
* @param {(OpenSeadragon.Point | Number)} imageX the point or the
|
||||
* X coordinate in image coordinate system.
|
||||
* @param {Number} [imageY] Y coordinate in image coordinate system.
|
||||
* @return {OpenSeadragon.Point} a point representing the coordinates in the viewport.
|
||||
* @returns {OpenSeadragon.Point} a point representing the coordinates in the viewport.
|
||||
*/
|
||||
imageToViewportCoordinates: function(imageX, imageY) {
|
||||
if (imageX instanceof $.Point) {
|
||||
|
@ -1505,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.
|
||||
|
@ -1517,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
|
||||
|
@ -1538,7 +1744,7 @@ $.Viewport.prototype = {
|
|||
/**
|
||||
* Toggles flip state and demands a new drawing on navigator and viewer objects.
|
||||
* @function
|
||||
* @return {OpenSeadragon.Viewport} Chainable.
|
||||
* @returns {OpenSeadragon.Viewport} Chainable.
|
||||
*/
|
||||
toggleFlip: function() {
|
||||
this.setFlip(!this.getFlip());
|
||||
|
@ -1548,7 +1754,7 @@ $.Viewport.prototype = {
|
|||
/**
|
||||
* Get flip state stored on viewport.
|
||||
* @function
|
||||
* @return {Boolean} Flip state.
|
||||
* @returns {Boolean} Flip state.
|
||||
*/
|
||||
getFlip: function() {
|
||||
return this.flipped;
|
||||
|
@ -1558,7 +1764,7 @@ $.Viewport.prototype = {
|
|||
* Sets flip state according to the state input argument.
|
||||
* @function
|
||||
* @param {Boolean} state - Flip state to set.
|
||||
* @return {OpenSeadragon.Viewport} Chainable.
|
||||
* @returns {OpenSeadragon.Viewport} Chainable.
|
||||
*/
|
||||
setFlip: function( state ) {
|
||||
if ( this.flipped === state ) {
|
||||
|
@ -1583,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
218
src/world.js
|
@ -2,7 +2,7 @@
|
|||
* OpenSeadragon - World
|
||||
*
|
||||
* Copyright (C) 2009 CodePlex Foundation
|
||||
* Copyright (C) 2010-2013 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 */ {
|
||||
|
@ -123,7 +123,7 @@
|
|||
* @param {Object} data - the raw configuration
|
||||
* @param {String} url - the url the data was retrieved from if any.
|
||||
* @param {String} postData - HTTP POST data in k=v&k2=v2... form or null
|
||||
* @return {Object} options - A dictionary of keyword arguments sufficient
|
||||
* @returns {Object} options - A dictionary of keyword arguments sufficient
|
||||
* to configure this tile sources constructor.
|
||||
*/
|
||||
configure: function(data, url, postData) {
|
||||
|
@ -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
|
@ -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>"
|
||||
}
|
||||
|
|
BIN
test/data/iiif_3_0_sizes/full/1600,1164/0/default.jpg
Normal file
After Width: | Height: | Size: 208 KiB |
BIN
test/data/iiif_3_0_sizes/full/3200,2328/0/default.jpg
Normal file
After Width: | Height: | Size: 678 KiB |
BIN
test/data/iiif_3_0_sizes/full/400,291/0/default.jpg
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
test/data/iiif_3_0_sizes/full/6976,5074/0/default.jpg
Normal file
After Width: | Height: | Size: 1.8 MiB |
BIN
test/data/iiif_3_0_sizes/full/800,582/0/default.jpg
Normal file
After Width: | Height: | Size: 67 KiB |
15
test/data/iiif_3_0_sizes/info.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"@context": "http://iiif.io/api/image/3/context.json",
|
||||
"id": "http://localhost:8000/test/data/iiif_3_0_sizes",
|
||||
"protocol": "http://iiif.io/api/image",
|
||||
"width": 6976,
|
||||
"height": 5074,
|
||||
"profile": "level0",
|
||||
"sizes" : [
|
||||
{"width" : 400, "height" : 291},
|
||||
{"width" : 800, "height" : 582},
|
||||
{"width" : 1600, "height" : 1164},
|
||||
{"width" : 3200, "height": 2328},
|
||||
{"width" : 6976, "height": 5074}
|
||||
]
|
||||
}
|
BIN
test/data/iiif_3_0_tiled/0,0,256,256/256,256/0/default.jpg
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
test/data/iiif_3_0_tiled/0,0,512,512/256,256/0/default.jpg
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
test/data/iiif_3_0_tiled/0,0,775,1024/194,256/0/default.jpg
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
test/data/iiif_3_0_tiled/0,256,256,256/256,256/0/default.jpg
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
test/data/iiif_3_0_tiled/0,512,256,256/256,256/0/default.jpg
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
test/data/iiif_3_0_tiled/0,512,512,512/256,256/0/default.jpg
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
test/data/iiif_3_0_tiled/0,768,256,256/256,256/0/default.jpg
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
test/data/iiif_3_0_tiled/256,0,256,256/256,256/0/default.jpg
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
test/data/iiif_3_0_tiled/256,256,256,256/256,256/0/default.jpg
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
test/data/iiif_3_0_tiled/256,512,256,256/256,256/0/default.jpg
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
test/data/iiif_3_0_tiled/256,768,256,256/256,256/0/default.jpg
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
test/data/iiif_3_0_tiled/512,0,256,256/256,256/0/default.jpg
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
test/data/iiif_3_0_tiled/512,0,263,512/132,256/0/default.jpg
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
test/data/iiif_3_0_tiled/512,256,256,256/256,256/0/default.jpg
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
test/data/iiif_3_0_tiled/512,512,256,256/256,256/0/default.jpg
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
test/data/iiif_3_0_tiled/512,512,263,512/132,256/0/default.jpg
Normal file
After Width: | Height: | Size: 9.9 KiB |
BIN
test/data/iiif_3_0_tiled/512,768,256,256/256,256/0/default.jpg
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
test/data/iiif_3_0_tiled/768,0,7,256/7,256/0/default.jpg
Normal file
After Width: | Height: | Size: 717 B |
BIN
test/data/iiif_3_0_tiled/768,256,7,256/7,256/0/default.jpg
Normal file
After Width: | Height: | Size: 716 B |
BIN
test/data/iiif_3_0_tiled/768,512,7,256/7,256/0/default.jpg
Normal file
After Width: | Height: | Size: 717 B |
BIN
test/data/iiif_3_0_tiled/768,768,7,256/7,256/0/default.jpg
Normal file
After Width: | Height: | Size: 712 B |
BIN
test/data/iiif_3_0_tiled/full/1,/0/default.jpg
Normal file
After Width: | Height: | Size: 633 B |
BIN
test/data/iiif_3_0_tiled/full/13,/0/default.jpg
Normal file
After Width: | Height: | Size: 810 B |
BIN
test/data/iiif_3_0_tiled/full/2,/0/default.jpg
Normal file
After Width: | Height: | Size: 663 B |
BIN
test/data/iiif_3_0_tiled/full/25,/0/default.jpg
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
test/data/iiif_3_0_tiled/full/4,/0/default.jpg
Normal file
After Width: | Height: | Size: 675 B |
BIN
test/data/iiif_3_0_tiled/full/49,/0/default.jpg
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
test/data/iiif_3_0_tiled/full/7,/0/default.jpg
Normal file
After Width: | Height: | Size: 683 B |
BIN
test/data/iiif_3_0_tiled/full/97,/0/default.jpg
Normal file
After Width: | Height: | Size: 7.1 KiB |
11
test/data/iiif_3_0_tiled/info.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"@context": "http://iiif.io/api/image/3/context.json",
|
||||
"id": "http://localhost:8000/test/data/iiif_3_0_tiled",
|
||||
"protocol": "http://iiif.io/api/image",
|
||||
"height": 1024,
|
||||
"width": 775,
|
||||
"tiles" : [{"width":256, "scaleFactors":[1,2,4,8]}],
|
||||
"profile": "level2",
|
||||
"extraQualities": ["bitonal", "grey", "color"],
|
||||
"extraFormats": ["jpg", "png", "gif" ]
|
||||
}
|
BIN
test/data/iiif_3_0_tiled_sf1/0,0,256,256/256,256/0/default.jpg
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
test/data/iiif_3_0_tiled_sf1/0,256,256,256/256,256/0/default.jpg
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
test/data/iiif_3_0_tiled_sf1/0,512,256,256/256,256/0/default.jpg
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
test/data/iiif_3_0_tiled_sf1/0,768,256,256/256,256/0/default.jpg
Normal file
After Width: | Height: | Size: 11 KiB |