mirror of
https://github.com/lovell/sharp.git
synced 2026-02-04 13:46:19 +01:00
Compare commits
1692 Commits
v0.8.4
...
v0.33.0-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d033ae341 | ||
|
|
f7da2e5970 | ||
|
|
51e56f994c | ||
|
|
68ac12292c | ||
|
|
a9aa7339ce | ||
|
|
08108f5fad | ||
|
|
58e3c4c70e | ||
|
|
f8cf25ca56 | ||
|
|
ca95979ecc | ||
|
|
392f6afb5e | ||
|
|
7c97aabaf8 | ||
|
|
fcc7e84bee | ||
|
|
226a9a13ef | ||
|
|
4d3c9ae3d1 | ||
|
|
f31011d759 | ||
|
|
7cf4ae5648 | ||
|
|
9161c605e1 | ||
|
|
70ac6905c7 | ||
|
|
265d70111a | ||
|
|
59327bdd53 | ||
|
|
3043e01171 | ||
|
|
36feb7551b | ||
|
|
a41c62be2b | ||
|
|
854ed65016 | ||
|
|
8f63d131a4 | ||
|
|
70a3067963 | ||
|
|
aabbe1fa08 | ||
|
|
0f8bb9196e | ||
|
|
efee9f1779 | ||
|
|
61c5cb4669 | ||
|
|
e618c17bd0 | ||
|
|
6578118f32 | ||
|
|
ba20b8ada4 | ||
|
|
eefaa99872 | ||
|
|
dbce6fab79 | ||
|
|
af0fcb37c2 | ||
|
|
c6f54e59da | ||
|
|
846563e45f | ||
|
|
9c217ab580 | ||
|
|
e7381e522e | ||
|
|
4340d60ccf | ||
|
|
7f64d464de | ||
|
|
67e927bdb6 | ||
|
|
9c7713ed54 | ||
|
|
8be6da1def | ||
|
|
95635683ac | ||
|
|
44a0ee3fd3 | ||
|
|
ccd51c8cbf | ||
|
|
bb7469b2d1 | ||
|
|
a2cac61209 | ||
|
|
5c19f6dd9b | ||
|
|
3d01775972 | ||
|
|
87562a5111 | ||
|
|
2829e17743 | ||
|
|
ffefbd2ecc | ||
|
|
bc8f983329 | ||
|
|
440936a699 | ||
|
|
0bc79cdb95 | ||
|
|
9a66e25f53 | ||
|
|
8370935ccf | ||
|
|
f908987f35 | ||
|
|
aea368a3a0 | ||
|
|
7ecbc20d3d | ||
|
|
cb0e2a91c4 | ||
|
|
739b317a6f | ||
|
|
a0e1c39785 | ||
|
|
85b26dab68 | ||
|
|
66f7cef253 | ||
|
|
863174f201 | ||
|
|
bcd865cc96 | ||
|
|
16ea04fe80 | ||
|
|
9c547dc321 | ||
|
|
5522060e9e | ||
|
|
d2f0fa855b | ||
|
|
2bb3ea8170 | ||
|
|
3434eef5b9 | ||
|
|
2f67823c3d | ||
|
|
38c760cdd7 | ||
|
|
14c3346800 | ||
|
|
0da55bab7e | ||
|
|
cfb659f576 | ||
|
|
cc5ac5385f | ||
|
|
93fafb0c18 | ||
|
|
41e3c8ca09 | ||
|
|
da61ea0199 | ||
|
|
7e6a70af44 | ||
|
|
f5845c7e61 | ||
|
|
eb1e53db83 | ||
|
|
3340120aea | ||
|
|
de0fc07092 | ||
|
|
dc4b39f73f | ||
|
|
e873978e53 | ||
|
|
5255964c79 | ||
|
|
dea319daf6 | ||
|
|
a2ca678854 | ||
|
|
e98993a6e2 | ||
|
|
90abd927c9 | ||
|
|
4d7957a043 | ||
|
|
bf9bb56367 | ||
|
|
8408e99aa3 | ||
|
|
a39f959dcc | ||
|
|
d08baa20e6 | ||
|
|
391018ad3d | ||
|
|
afed876f90 | ||
|
|
d6b60a60c6 | ||
|
|
5f8646d937 | ||
|
|
b763801d68 | ||
|
|
2e0f789c9b | ||
|
|
a8645f0f38 | ||
|
|
7b58ad9360 | ||
|
|
9ebbcc3701 | ||
|
|
e87204b92c | ||
|
|
a4c6eba7d4 | ||
|
|
b9c3851515 | ||
|
|
97cf69c26a | ||
|
|
d5be024bfd | ||
|
|
de01fc44e7 | ||
|
|
ca102ebd6c | ||
|
|
b9d4c30a9f | ||
|
|
148760fe55 | ||
|
|
98ed237734 | ||
|
|
b55e58f31e | ||
|
|
0af070ed93 | ||
|
|
9fbb4fcaef | ||
|
|
6008ff8a08 | ||
|
|
cd5e11bd50 | ||
|
|
08d6822265 | ||
|
|
8b8a815fbb | ||
|
|
a44da850c1 | ||
|
|
c5ef4677b1 | ||
|
|
f8a430bdd3 | ||
|
|
cd419a261b | ||
|
|
d7776e3b98 | ||
|
|
1eefd4e562 | ||
|
|
0a16d26ec7 | ||
|
|
fc03fba602 | ||
|
|
c87fe512b4 | ||
|
|
2eaab59c48 | ||
|
|
4ec883eaa0 | ||
|
|
0063df4d4f | ||
|
|
6c61ad256f | ||
|
|
b90474affa | ||
|
|
34cbc6dec3 | ||
|
|
bb8de0cc26 | ||
|
|
863e37455a | ||
|
|
6f0e6f2e65 | ||
|
|
ebf4ccd124 | ||
|
|
b96c8e8ba4 | ||
|
|
42d2f07e44 | ||
|
|
a2988c9edc | ||
|
|
24b3344937 | ||
|
|
9608f219bd | ||
|
|
4798d9da64 | ||
|
|
8d8c6b70eb | ||
|
|
9e2207f376 | ||
|
|
802f560b9b | ||
|
|
a532659b0f | ||
|
|
25c6da2bcd | ||
|
|
02f855d57a | ||
|
|
c150263ef1 | ||
|
|
9f79f80a93 | ||
|
|
069803b83d | ||
|
|
f79760b4f2 | ||
|
|
aa5f0f4e40 | ||
|
|
286a322622 | ||
|
|
6d404f4d2c | ||
|
|
bdc50e1d6e | ||
|
|
a9bd0e79f8 | ||
|
|
a1e464cc5e | ||
|
|
081debd055 | ||
|
|
ef849fd639 | ||
|
|
a42a975c46 | ||
|
|
e8273580af | ||
|
|
5be36c2deb | ||
|
|
6cda090ce2 | ||
|
|
eac6e8b261 | ||
|
|
844deaf480 | ||
|
|
efbb0c22fd | ||
|
|
da0b594900 | ||
|
|
78dada9126 | ||
|
|
15f5cd4671 | ||
|
|
9eb2e94404 | ||
|
|
e40b068628 | ||
|
|
2c46528269 | ||
|
|
584807b4f5 | ||
|
|
a7fa7014ef | ||
|
|
f92e33fbff | ||
|
|
0f1e7ef6f6 | ||
|
|
89e204d824 | ||
|
|
2a71f1830f | ||
|
|
def99a294a | ||
|
|
9d760f3958 | ||
|
|
0265d305fe | ||
|
|
a472aea025 | ||
|
|
01ffa80338 | ||
|
|
789d4851ea | ||
|
|
4490a93430 | ||
|
|
ac0dc10bd5 | ||
|
|
5740f4545e | ||
|
|
a9d692fb43 | ||
|
|
df971207b8 | ||
|
|
3a64a0529a | ||
|
|
76cda885fb | ||
|
|
1a563360c6 | ||
|
|
ca22af203f | ||
|
|
9fa516e849 | ||
|
|
12f472126d | ||
|
|
18be09f1d7 | ||
|
|
b3c3290f90 | ||
|
|
123f95c85a | ||
|
|
5b0fba4c01 | ||
|
|
37f7ccfff4 | ||
|
|
51811d06e2 | ||
|
|
181731f8f4 | ||
|
|
ae79d26ead | ||
|
|
eacb8337fa | ||
|
|
99bf279de8 | ||
|
|
1b0eb6ab53 | ||
|
|
891cf67d0b | ||
|
|
eaf8a86bf2 | ||
|
|
3400976d61 | ||
|
|
2d49f0e93e | ||
|
|
b0c69f1ee9 | ||
|
|
27d0c35a01 | ||
|
|
32a22b5420 | ||
|
|
d1004eed02 | ||
|
|
70e6bb0162 | ||
|
|
32aa3b4b20 | ||
|
|
df24b30755 | ||
|
|
4de74bea94 | ||
|
|
28b87db760 | ||
|
|
fbd4970b57 | ||
|
|
f5da147a58 | ||
|
|
eee0dd36d9 | ||
|
|
f7b29d7b59 | ||
|
|
0b806187fc | ||
|
|
c1393daa70 | ||
|
|
31c1cfb049 | ||
|
|
afc35c2208 | ||
|
|
6eb2add3bf | ||
|
|
5cdb2b83d5 | ||
|
|
1eb66c0944 | ||
|
|
55c4d8807c | ||
|
|
9a54a034e1 | ||
|
|
f5109560d6 | ||
|
|
953a94885b | ||
|
|
0e3bd46ca3 | ||
|
|
4b38f56d02 | ||
|
|
0fe857c5ac | ||
|
|
1bf06bd5b4 | ||
|
|
6e3f4c3c92 | ||
|
|
8583eb1235 | ||
|
|
c3a852eecf | ||
|
|
3a44748f49 | ||
|
|
e1bc8674fd | ||
|
|
a618ce7a15 | ||
|
|
a44168c8c7 | ||
|
|
74e3f73934 | ||
|
|
b9261c243c | ||
|
|
cc9f91f37c | ||
|
|
212a6e7519 | ||
|
|
e547eaa180 | ||
|
|
9a0d9eed74 | ||
|
|
dd56a9699e | ||
|
|
ea7cf2a2ef | ||
|
|
76c4c51e2a | ||
|
|
b46ab510da | ||
|
|
3e327a586c | ||
|
|
974fab946e | ||
|
|
f998a8f249 | ||
|
|
be331e958e | ||
|
|
254944f8ab | ||
|
|
f1e640d231 | ||
|
|
c295f06a6f | ||
|
|
6288c7bced | ||
|
|
d247c02762 | ||
|
|
1b84ccbbe9 | ||
|
|
e4160c684d | ||
|
|
905518fab0 | ||
|
|
8ff33763ce | ||
|
|
cbf741cac7 | ||
|
|
6c2e2be41d | ||
|
|
e0d3c6e05d | ||
|
|
e3cab7f10f | ||
|
|
204463ffbb | ||
|
|
1bcd3700c5 | ||
|
|
c99a11cff5 | ||
|
|
81c74f57e0 | ||
|
|
7a8ab452c5 | ||
|
|
bb91912883 | ||
|
|
afc4c5bf79 | ||
|
|
e40a881ab4 | ||
|
|
c1b13adac3 | ||
|
|
29e09898f7 | ||
|
|
853a20358e | ||
|
|
8bb30d7801 | ||
|
|
a333b87f5d | ||
|
|
4662527a17 | ||
|
|
b10d8f89ca | ||
|
|
f903e1465e | ||
|
|
a75718565c | ||
|
|
4d82331bf6 | ||
|
|
b91875d3d9 | ||
|
|
a0568ec0c3 | ||
|
|
48e3ea5e29 | ||
|
|
93b29057e4 | ||
|
|
db654de385 | ||
|
|
a6aeef612b | ||
|
|
7bf6cbd669 | ||
|
|
04c31b35a7 | ||
|
|
ee9cdb6598 | ||
|
|
8960eb8309 | ||
|
|
54d9dc46f5 | ||
|
|
51b4a7c564 | ||
|
|
5b03579e5c | ||
|
|
58c2af3251 | ||
|
|
ee948ac6fa | ||
|
|
66a3ce5e55 | ||
|
|
75e5afcd42 | ||
|
|
d396a4e65d | ||
|
|
ae1dbcdf4e | ||
|
|
4c29368b51 | ||
|
|
36e55969d2 | ||
|
|
985e881e7a | ||
|
|
0b116715aa | ||
|
|
9deac83322 | ||
|
|
5d36f5f699 | ||
|
|
926572b41e | ||
|
|
d0c8e95641 | ||
|
|
b0ca23c3e7 | ||
|
|
c3a0d5f5d0 | ||
|
|
1d36936954 | ||
|
|
b609df4b48 | ||
|
|
c567d3b9ab | ||
|
|
27d9fe2a4e | ||
|
|
ac883c5215 | ||
|
|
e8720c9374 | ||
|
|
42e45d842a | ||
|
|
72fd8abe2c | ||
|
|
ac18bbbc7c | ||
|
|
fcbe4e1e01 | ||
|
|
9280742385 | ||
|
|
7a1a1cf9e8 | ||
|
|
ea599ade10 | ||
|
|
1de49f3ed8 | ||
|
|
4ac65054bc | ||
|
|
23033e2050 | ||
|
|
dd3b78272a | ||
|
|
80d169b7c2 | ||
|
|
003279a0b0 | ||
|
|
af80d7e389 | ||
|
|
21a960796c | ||
|
|
fc3b4a683d | ||
|
|
808133e7dc | ||
|
|
801b6fea6c | ||
|
|
c2ecde6a16 | ||
|
|
55efe5602b | ||
|
|
c62002554b | ||
|
|
7f83ecd255 | ||
|
|
dc5f4dcd28 | ||
|
|
735793ba99 | ||
|
|
47792df689 | ||
|
|
5c6cdfaece | ||
|
|
115a6b10f6 | ||
|
|
4feee506cf | ||
|
|
83db5f8a2a | ||
|
|
7eb5efa3a3 | ||
|
|
5a9f89fe06 | ||
|
|
02e0c2dfc9 | ||
|
|
968d9d7008 | ||
|
|
7faacd91b0 | ||
|
|
154eaff4ec | ||
|
|
424660278d | ||
|
|
2b01951306 | ||
|
|
83bb6a4554 | ||
|
|
c72d42816d | ||
|
|
35a81a7af2 | ||
|
|
9dc8db4370 | ||
|
|
47ae1f52db | ||
|
|
ec17d7f580 | ||
|
|
da5453a7c0 | ||
|
|
f7bed69ffb | ||
|
|
7aa340232e | ||
|
|
68823a5edb | ||
|
|
1c3ba303ea | ||
|
|
76f8112952 | ||
|
|
24150eac89 | ||
|
|
51121a1440 | ||
|
|
3b370b6c01 | ||
|
|
884947a069 | ||
|
|
f8340e1a82 | ||
|
|
5101f4e79c | ||
|
|
161d127bf3 | ||
|
|
638d540371 | ||
|
|
d8f1298511 | ||
|
|
d67e09ba7c | ||
|
|
4c3a8a7007 | ||
|
|
92399ee5e2 | ||
|
|
96992845ed | ||
|
|
2b8e4d20de | ||
|
|
dd6583044b | ||
|
|
446e4e3c3a | ||
|
|
3b492ea423 | ||
|
|
3da258f6fb | ||
|
|
9755629cfd | ||
|
|
5bb6702717 | ||
|
|
8c0660d71e | ||
|
|
513fb40f40 | ||
|
|
659cdabd8e | ||
|
|
add4c7928f | ||
|
|
336856dfc2 | ||
|
|
e1ba2a7fd8 | ||
|
|
2b1f5cbe07 | ||
|
|
72025051c5 | ||
|
|
140eeebb3d | ||
|
|
407bfcb42a | ||
|
|
549219f32a | ||
|
|
afab0d34dd | ||
|
|
2deced0fb9 | ||
|
|
2a0077c481 | ||
|
|
f7f3e43490 | ||
|
|
b876abaf88 | ||
|
|
dcd9a3c558 | ||
|
|
a06b8c296a | ||
|
|
602f988aba | ||
|
|
079bd7b9f5 | ||
|
|
1ff84b20b7 | ||
|
|
97655d2dfd | ||
|
|
d10d7b02d4 | ||
|
|
2ffdae2914 | ||
|
|
342de36973 | ||
|
|
b33231d4bd | ||
|
|
319db21f29 | ||
|
|
d359331426 | ||
|
|
7ae151362b | ||
|
|
648a1e05da | ||
|
|
b9f211fe34 | ||
|
|
e475d9e47f | ||
|
|
f37ca8249a | ||
|
|
1dd4be670d | ||
|
|
197d4cf835 | ||
|
|
83eed86b53 | ||
|
|
bbf612cb9e | ||
|
|
2679bb567b | ||
|
|
481e350f39 | ||
|
|
50c7a08754 | ||
|
|
9a0bb60737 | ||
|
|
deb5d81221 | ||
|
|
916b04dbac | ||
|
|
52307fad5d | ||
|
|
afb21135c2 | ||
|
|
b7fbffb3f7 | ||
|
|
5d98bcd8d8 | ||
|
|
e044788f63 | ||
|
|
4a9267ce12 | ||
|
|
104464c2e0 | ||
|
|
60adc110f5 | ||
|
|
2031d7d112 | ||
|
|
3402656ec5 | ||
|
|
4e84f743e4 | ||
|
|
17e50de5f0 | ||
|
|
978a788f40 | ||
|
|
6e91d55971 | ||
|
|
d4ce0a1e36 | ||
|
|
148608b377 | ||
|
|
f725f4acb7 | ||
|
|
d07a549438 | ||
|
|
551441cedd | ||
|
|
46c14e939b | ||
|
|
6084647795 | ||
|
|
e0a598ae62 | ||
|
|
28833eb04a | ||
|
|
b24c9c86d1 | ||
|
|
b7add480c7 | ||
|
|
eabb671b10 | ||
|
|
513ed02b76 | ||
|
|
b7ddbe71f7 | ||
|
|
21d1a7ca62 | ||
|
|
4c2d28a7ad | ||
|
|
2afec9e3ed | ||
|
|
69790421b7 | ||
|
|
3f08f6a359 | ||
|
|
719c2db8da | ||
|
|
a9aa55c32d | ||
|
|
3f6d9d6ee9 | ||
|
|
b32568159f | ||
|
|
bb48d0d857 | ||
|
|
536412515f | ||
|
|
fcc6eaadd3 | ||
|
|
7dc78e8796 | ||
|
|
c65de3fe6d | ||
|
|
d000f57773 | ||
|
|
75cddbdb6d | ||
|
|
e418d91511 | ||
|
|
6c2e6c5432 | ||
|
|
8c6d9fdc62 | ||
|
|
468e95427e | ||
|
|
6d7a5ace6b | ||
|
|
cbaec198a5 | ||
|
|
7467fa8b50 | ||
|
|
61640fb5c7 | ||
|
|
19cb4b62b0 | ||
|
|
81ee8bc30f | ||
|
|
9f384e1c6c | ||
|
|
35e8c8b25e | ||
|
|
dc53f1baff | ||
|
|
70139600b5 | ||
|
|
1b4d1521e0 | ||
|
|
ed3377cb2d | ||
|
|
d72852b3aa | ||
|
|
4b6b6189bf | ||
|
|
b69a54fc75 | ||
|
|
81e388a4cc | ||
|
|
5bd5e5052a | ||
|
|
a2d3fa729f | ||
|
|
cb6811bc47 | ||
|
|
53c6e80869 | ||
|
|
e71dca586c | ||
|
|
b3cd48db5f | ||
|
|
476448b9d4 | ||
|
|
070534df5b | ||
|
|
9a1e8ed574 | ||
|
|
309918a878 | ||
|
|
cac83b94c1 | ||
|
|
9c06df08a1 | ||
|
|
52e4543d31 | ||
|
|
a688468378 | ||
|
|
e1760d64fb | ||
|
|
84d4e3cf8f | ||
|
|
f8a76372ad | ||
|
|
4237f5520f | ||
|
|
8c0c01c702 | ||
|
|
9c100830e0 | ||
|
|
ed5d753b89 | ||
|
|
d1ca756bd8 | ||
|
|
16cf9f0ef2 | ||
|
|
133f69d66c | ||
|
|
bc60daff9e | ||
|
|
43a085d1ae | ||
|
|
8c33d0aa56 | ||
|
|
fe0767df13 | ||
|
|
08a25a0c8f | ||
|
|
cd410080bd | ||
|
|
7555378e3b | ||
|
|
80c95ee66a | ||
|
|
31563b210d | ||
|
|
861cd93324 | ||
|
|
abb344bb1a | ||
|
|
6147491d9e | ||
|
|
f1f18fbb4a | ||
|
|
9fc611f257 | ||
|
|
34a2e14a14 | ||
|
|
83fe65b9e9 | ||
|
|
ec26c8aa49 | ||
|
|
da43a3055f | ||
|
|
a38126c82f | ||
|
|
cb592ce588 | ||
|
|
d69c58a6da | ||
|
|
bdb1986e08 | ||
|
|
55356c78a8 | ||
|
|
a0f55252b1 | ||
|
|
013f5cffa9 | ||
|
|
d5d008f568 | ||
|
|
3b02134cdc | ||
|
|
a57d7b51b1 | ||
|
|
1a3c38d35f | ||
|
|
00aece0538 | ||
|
|
5a9cc835b3 | ||
|
|
58526cc849 | ||
|
|
955b5f43a5 | ||
|
|
447aec3fde | ||
|
|
473260a836 | ||
|
|
4d2784c10c | ||
|
|
d9af897595 | ||
|
|
23a48be315 | ||
|
|
ce8f48e5d1 | ||
|
|
6aaf839662 | ||
|
|
984a9e653e | ||
|
|
8dffa28b4d | ||
|
|
b05a4bdadd | ||
|
|
36087fe518 | ||
|
|
5eed87ec4d | ||
|
|
af66a73225 | ||
|
|
dcf913c17e | ||
|
|
68ccba8f74 | ||
|
|
956f7e29db | ||
|
|
4264c0577e | ||
|
|
cc37b59309 | ||
|
|
9f2f92095d | ||
|
|
0c1075c089 | ||
|
|
9c64710c8b | ||
|
|
f6f16b91db | ||
|
|
1986b5cfe6 | ||
|
|
6445b72d41 | ||
|
|
df7b8ba738 | ||
|
|
202083999e | ||
|
|
315f519e1d | ||
|
|
d7d580ae6f | ||
|
|
7017af303d | ||
|
|
0dc325daa4 | ||
|
|
6dffb47973 | ||
|
|
b19dad69d6 | ||
|
|
94c5ac64e9 | ||
|
|
c4bcd088fb | ||
|
|
aeecbe9396 | ||
|
|
171aade9cd | ||
|
|
67213ae86c | ||
|
|
24d9e53c3f | ||
|
|
573ed5f4b5 | ||
|
|
ceff628add | ||
|
|
0bb8cb9203 | ||
|
|
98349bde28 | ||
|
|
f09be932eb | ||
|
|
4c57ac2bbe | ||
|
|
1dd93c1b6b | ||
|
|
c9f85fe27f | ||
|
|
419cbe50f6 | ||
|
|
5031c8323f | ||
|
|
762d5913ce | ||
|
|
290df1b1c7 | ||
|
|
79170afc51 | ||
|
|
bba00c2bfe | ||
|
|
f7e2b3688f | ||
|
|
8d49b7dde1 | ||
|
|
138e60adb3 | ||
|
|
d6376c31e0 | ||
|
|
a7003e93c8 | ||
|
|
4821a11223 | ||
|
|
bf1b326988 | ||
|
|
39ddb6a175 | ||
|
|
b2a0b8c0f0 | ||
|
|
4debc46d0e | ||
|
|
f4e259d10f | ||
|
|
774d78228e | ||
|
|
0e62bde5c3 | ||
|
|
2bbd9b23e6 | ||
|
|
02676140e8 | ||
|
|
182beaa4a1 | ||
|
|
7c08a09529 | ||
|
|
ef964b5472 | ||
|
|
ee54ce9913 | ||
|
|
e59e146887 | ||
|
|
103ec0d58f | ||
|
|
a0d89ed514 | ||
|
|
c10888e6fe | ||
|
|
93455f8eb5 | ||
|
|
65acd96c8d | ||
|
|
fabe720b9b | ||
|
|
53dd313e97 | ||
|
|
2678d7a660 | ||
|
|
46718102c6 | ||
|
|
0f473fe3b1 | ||
|
|
2872602c9e | ||
|
|
ab653cae33 | ||
|
|
e6a035e575 | ||
|
|
fbe48d75dd | ||
|
|
20ba0f49dd | ||
|
|
c213e9878d | ||
|
|
9704ca4c18 | ||
|
|
49dce6219e | ||
|
|
260ff6c94f | ||
|
|
3ec281d104 | ||
|
|
c4c43d525b | ||
|
|
6c5cde363a | ||
|
|
d46b4d950f | ||
|
|
b369c4bb8a | ||
|
|
c3898487c4 | ||
|
|
ca3c5b400f | ||
|
|
97772b176c | ||
|
|
08a2965f1c | ||
|
|
76dcddfa3d | ||
|
|
79f476ae4d | ||
|
|
d406cb619c | ||
|
|
4f3890f1e4 | ||
|
|
7a9d58cc51 | ||
|
|
eef87da0e1 | ||
|
|
00e65f6f14 | ||
|
|
866e9824d1 | ||
|
|
482e6078e2 | ||
|
|
bc7ab296ef | ||
|
|
a5f4f53b56 | ||
|
|
b1227f526d | ||
|
|
78b42c8306 | ||
|
|
4b6d45ab8e | ||
|
|
7980341923 | ||
|
|
3917efdebd | ||
|
|
eaecb7347b | ||
|
|
05ca7d3129 | ||
|
|
4beae0de71 | ||
|
|
b711661784 | ||
|
|
0c4a45b1f4 | ||
|
|
ec2beb0039 | ||
|
|
cafb3e8bac | ||
|
|
e896d5e920 | ||
|
|
42d4228595 | ||
|
|
341ea3e4ea | ||
|
|
cb1baede87 | ||
|
|
45653ca2e7 | ||
|
|
77c861b74b | ||
|
|
b586c2146f | ||
|
|
4d42bed4f8 | ||
|
|
ea54c119ab | ||
|
|
d95b124771 | ||
|
|
61ccf09e6f | ||
|
|
8da30f0b41 | ||
|
|
90a57e7664 | ||
|
|
c42de19d2a | ||
|
|
dcc42f8514 | ||
|
|
3150fad909 | ||
|
|
ba17db3ab3 | ||
|
|
7c1c48327e | ||
|
|
85459e0ec6 | ||
|
|
13bdb68366 | ||
|
|
c91373fba3 | ||
|
|
e8149f5295 | ||
|
|
ab015ef90f | ||
|
|
fdfc89e577 | ||
|
|
e647e1f7bd | ||
|
|
a05d735e31 | ||
|
|
4470048ba4 | ||
|
|
0f58f956a7 | ||
|
|
20284757a6 | ||
|
|
df6efa0285 | ||
|
|
3c10e118e3 | ||
|
|
19980190f7 | ||
|
|
8f5495a446 | ||
|
|
9431029917 | ||
|
|
17ea70a102 | ||
|
|
7f142bddb3 | ||
|
|
98e0516ac1 | ||
|
|
7717516d1d | ||
|
|
760550ca0d | ||
|
|
f8144dd89c | ||
|
|
ac4070cb49 | ||
|
|
25b964e3df | ||
|
|
17ec6c72df | ||
|
|
a7b1185602 | ||
|
|
c76ae06fd1 | ||
|
|
b3dd54d550 | ||
|
|
d248eadb06 | ||
|
|
507eef3053 | ||
|
|
fc2f672337 | ||
|
|
6f49be8f26 | ||
|
|
dad7f1e1f6 | ||
|
|
c3a9384dcf | ||
|
|
0ee08bfe46 | ||
|
|
0872f495f9 | ||
|
|
33ac8b93a9 | ||
|
|
bbff1c222d | ||
|
|
b534f99870 | ||
|
|
86acf2460e | ||
|
|
40c00035ee | ||
|
|
4673abae7d | ||
|
|
ef8a705957 | ||
|
|
48255fa009 | ||
|
|
ed6269bc9c | ||
|
|
16b8d9afe5 | ||
|
|
f29fcb1f73 | ||
|
|
b36ad25030 | ||
|
|
edf3fe24c9 | ||
|
|
5a1bdf0eb1 | ||
|
|
d8e24d7e76 | ||
|
|
786c5330e9 | ||
|
|
70e730bb67 | ||
|
|
1d6ef630a5 | ||
|
|
1eedb22ef5 | ||
|
|
6ed1f49ad3 | ||
|
|
ecd01afad3 | ||
|
|
8bd8709f2b | ||
|
|
e82a585cec | ||
|
|
c1d4a68558 | ||
|
|
eff36dc09f | ||
|
|
031a58f817 | ||
|
|
cf39fc4fb1 | ||
|
|
c9bff94e17 | ||
|
|
e78e919925 | ||
|
|
76bb25262e | ||
|
|
d8426b1ed5 | ||
|
|
4894c10dd9 | ||
|
|
24285bb0e0 | ||
|
|
bf75501262 | ||
|
|
9e214a17f0 | ||
|
|
0aae1ecd9b | ||
|
|
062f990315 | ||
|
|
22685e44c0 | ||
|
|
8d66433e7c | ||
|
|
82863f48f6 | ||
|
|
51b14329d6 | ||
|
|
14dc7681ed | ||
|
|
258c9e86eb | ||
|
|
03dad5f2b2 | ||
|
|
86264e7733 | ||
|
|
95eae905f0 | ||
|
|
a90dbcc808 | ||
|
|
e9b21f2211 | ||
|
|
409e5174bb | ||
|
|
8b3c0daab2 | ||
|
|
c17807c995 | ||
|
|
4abb4edf64 | ||
|
|
d5ecc537af | ||
|
|
c7a49054fd | ||
|
|
a2314c4aa0 | ||
|
|
1717173f17 | ||
|
|
e44c12f029 | ||
|
|
1a98c390fc | ||
|
|
91902740e4 | ||
|
|
6aa6a93b44 | ||
|
|
b4135ac9b3 | ||
|
|
78906e6551 | ||
|
|
ba29ba1ab7 | ||
|
|
e0fa15f5cb | ||
|
|
82a1ff359d | ||
|
|
18e1f10a94 | ||
|
|
4828a17643 | ||
|
|
3b4f95597a | ||
|
|
bd52e93fca | ||
|
|
6fdc79d569 | ||
|
|
7dbad7206e | ||
|
|
a8a0c1e935 | ||
|
|
00bcf60566 | ||
|
|
4a745f2d2e | ||
|
|
057074b238 | ||
|
|
4b8cc13a05 | ||
|
|
a4e8586b59 | ||
|
|
095b37db6a | ||
|
|
efff650314 | ||
|
|
0764ca8fde | ||
|
|
403160434b | ||
|
|
730ab14faa | ||
|
|
51d70c4574 | ||
|
|
765ccd2aaa | ||
|
|
5399332f81 | ||
|
|
43b7f9fc0e | ||
|
|
3925689435 | ||
|
|
96a994a4c0 | ||
|
|
84c20373ec | ||
|
|
a216d2945b | ||
|
|
755a0caf3d | ||
|
|
703d90e663 | ||
|
|
e230ce41d7 | ||
|
|
596b38a3bb | ||
|
|
d31a91a599 | ||
|
|
400ef71b6f | ||
|
|
bb15cd9067 | ||
|
|
6ee6a226e1 | ||
|
|
94d51a94c8 | ||
|
|
d0feb4156c | ||
|
|
0d1278dade | ||
|
|
1b401b1195 | ||
|
|
11daa3b4d1 | ||
|
|
88a3919ce0 | ||
|
|
c41b87303d | ||
|
|
833aaead56 | ||
|
|
ff98d2e44a | ||
|
|
fcf05f608a | ||
|
|
9baf38db44 | ||
|
|
69050ef1c8 | ||
|
|
b35b9f7850 | ||
|
|
500ae97cac | ||
|
|
d5b7040557 | ||
|
|
ca52894651 | ||
|
|
4009acdd30 | ||
|
|
147c93ecd3 | ||
|
|
8e04e4b07f | ||
|
|
e7413ea1e5 | ||
|
|
220bb03a32 | ||
|
|
20f512fe5f | ||
|
|
efb3523eaa | ||
|
|
2f2276e091 | ||
|
|
08a6597626 | ||
|
|
d82a6ee4fc | ||
|
|
e627f6d68d | ||
|
|
e650f58bd8 | ||
|
|
5a9b6c8afd | ||
|
|
075771d1e9 | ||
|
|
4fcf091fef | ||
|
|
0e66454fe4 | ||
|
|
aa3ce760bb | ||
|
|
ba46ad1fd9 | ||
|
|
11214bab5d | ||
|
|
d87c289b4a | ||
|
|
af45c03b6f | ||
|
|
7d6fadce6b | ||
|
|
6b560f7a85 | ||
|
|
5d4221460d | ||
|
|
d42c383992 | ||
|
|
9c7f6fcb2b | ||
|
|
14af0bda61 | ||
|
|
fb5c393fbd | ||
|
|
69fe21a7ec | ||
|
|
da4e05c118 | ||
|
|
e4333ff6b0 | ||
|
|
4ae8999f62 | ||
|
|
3fa91bb4ce | ||
|
|
23b2e541ab | ||
|
|
5bfcf61a6f | ||
|
|
0778c112a9 | ||
|
|
2c300754a7 | ||
|
|
c610e306df | ||
|
|
417cca6e0d | ||
|
|
2ed4b5ae83 | ||
|
|
16e429cf2c | ||
|
|
6b7ce8a605 | ||
|
|
ba4ce75377 | ||
|
|
76ded7fd28 | ||
|
|
a0d1a7be50 | ||
|
|
690bc43abe | ||
|
|
50b461024d | ||
|
|
6cf0b3240d | ||
|
|
233b015d77 | ||
|
|
28de243c11 | ||
|
|
36e8a3da88 | ||
|
|
119d16cad3 | ||
|
|
38402d3185 | ||
|
|
6c02949fc1 | ||
|
|
b737d4601e | ||
|
|
3ff3353550 | ||
|
|
946d3c81a5 | ||
|
|
628996846d | ||
|
|
631a3597c7 | ||
|
|
cfa4f7d45c | ||
|
|
05d76eeadf | ||
|
|
28a6c53da0 | ||
|
|
6fcd2153c5 | ||
|
|
7ae0512b9b | ||
|
|
0890b59c32 | ||
|
|
df3ce450d9 | ||
|
|
bb0257b318 | ||
|
|
9c3597670d | ||
|
|
aa9b328778 | ||
|
|
159e8dace2 | ||
|
|
3be4d5bb45 | ||
|
|
af7caa7b25 | ||
|
|
b4ede75522 | ||
|
|
9d98114074 | ||
|
|
4ac51899c3 | ||
|
|
90a0382317 | ||
|
|
687795c801 | ||
|
|
2e0fbbb942 | ||
|
|
0a3512d066 | ||
|
|
6032171f91 | ||
|
|
fc178de309 | ||
|
|
3f4398457f | ||
|
|
b494b2e872 | ||
|
|
18afcf5f90 | ||
|
|
87a422942d | ||
|
|
ac515121e5 | ||
|
|
2bfea0ad76 | ||
|
|
83cdb558f6 | ||
|
|
9ee377963e | ||
|
|
9cc06c887b | ||
|
|
7457b50373 | ||
|
|
6387fb79b1 | ||
|
|
54e5514b9a | ||
|
|
1e4597c284 | ||
|
|
7cafd4386c | ||
|
|
e3549ba28c | ||
|
|
d1bbe62e52 | ||
|
|
36af74a09b | ||
|
|
5afe02be60 | ||
|
|
2262959673 | ||
|
|
ba3f914445 | ||
|
|
770be35c44 | ||
|
|
cc9f2b90fd | ||
|
|
4aff57b071 | ||
|
|
1df8d82fe0 | ||
|
|
98e90784f4 | ||
|
|
87ea54cc66 | ||
|
|
d5e98bc8ad | ||
|
|
fa69ff773a | ||
|
|
a183bb1cac | ||
|
|
cf62372cab | ||
|
|
56fa9c95a1 | ||
|
|
32a34a8841 | ||
|
|
98797445de | ||
|
|
bd377438b6 | ||
|
|
9dd6510de6 | ||
|
|
93ad9d4a4a | ||
|
|
4c01a099ea | ||
|
|
8e70579e47 | ||
|
|
ee8bfa3980 | ||
|
|
c5dfa49cae | ||
|
|
0822404129 | ||
|
|
144f39cd45 | ||
|
|
87f191fd05 | ||
|
|
37ed436202 | ||
|
|
88e490356d | ||
|
|
7c631c0787 | ||
|
|
f5d3721fe0 | ||
|
|
cc633589d9 | ||
|
|
cc1d4c1a6d | ||
|
|
30ca424942 | ||
|
|
813831acf0 | ||
|
|
a54fe9f77c | ||
|
|
8c6da5548a | ||
|
|
a2aa7d69e7 | ||
|
|
34d5252242 | ||
|
|
f31e4d2869 | ||
|
|
c695c40abc | ||
|
|
fd1ca1dbb2 | ||
|
|
f25dbd5f61 | ||
|
|
541e7104fd | ||
|
|
94945cf6ac | ||
|
|
db76e655f8 | ||
|
|
d43c7b581d | ||
|
|
383b933e26 | ||
|
|
d26ccf6294 | ||
|
|
6f9699f605 | ||
|
|
1e9093d781 | ||
|
|
9dc6492e52 | ||
|
|
d22f7cae6a | ||
|
|
473afaab45 | ||
|
|
dcd68303a4 | ||
|
|
03394556b5 | ||
|
|
1c4f6f75f3 | ||
|
|
f00928dedb | ||
|
|
a48f8fbb61 | ||
|
|
1fa388370e | ||
|
|
95ef6b3f71 | ||
|
|
de11d36d00 | ||
|
|
d77c2adabe | ||
|
|
c89c055ae0 | ||
|
|
dac8117f32 | ||
|
|
937b091bab | ||
|
|
019e6a1bfe | ||
|
|
1565e58fcf | ||
|
|
c22e2a17ef | ||
|
|
fd2a10ccea | ||
|
|
0725378257 | ||
|
|
c431909f35 | ||
|
|
db4df6f0b2 | ||
|
|
17f942c802 | ||
|
|
60438ebfe5 | ||
|
|
21fbe546b8 | ||
|
|
11900945eb | ||
|
|
ea5270221b | ||
|
|
a64844689e | ||
|
|
6007e13a22 | ||
|
|
c3274e480b | ||
|
|
3c54eeda5b | ||
|
|
6236e4b97d | ||
|
|
796738da65 | ||
|
|
37d385fafa | ||
|
|
db2af42ee7 | ||
|
|
24b42ef192 | ||
|
|
2ce166ab0a | ||
|
|
71755b69e4 | ||
|
|
1106aac2d8 | ||
|
|
93aac660a3 | ||
|
|
0ce8ad7130 | ||
|
|
deacd553bf | ||
|
|
c8ff7e11a9 | ||
|
|
4cff62258c | ||
|
|
0144358afb | ||
|
|
136097efe7 | ||
|
|
374c6959d7 | ||
|
|
7d48a5ccf4 | ||
|
|
bf3254cb16 | ||
|
|
5bed3a7d52 | ||
|
|
ece111280b | ||
|
|
a15a9b956b | ||
|
|
42860c2f83 | ||
|
|
b5b95e5ae1 | ||
|
|
d705cffdd6 | ||
|
|
23a4bc103e | ||
|
|
c14434f9e7 | ||
|
|
25bd2cea3e | ||
|
|
532de4ecab | ||
|
|
bfdd27eeef | ||
|
|
bd9f238ab4 | ||
|
|
75556bb57c | ||
|
|
2de062a34a | ||
|
|
4589b15dea | ||
|
|
8b75ce6786 | ||
|
|
7bbc5176a1 | ||
|
|
5cb35485f1 | ||
|
|
80189ed689 | ||
|
|
3d7e8ef432 | ||
|
|
1999c7103c | ||
|
|
9c20ae383e | ||
|
|
76c41eaf05 | ||
|
|
873aa6700f | ||
|
|
0d9590a9a0 | ||
|
|
94607b585a | ||
|
|
da0b0348a2 | ||
|
|
09263455b5 | ||
|
|
ddc23493d4 | ||
|
|
54a71fc142 | ||
|
|
b1a9bf10a2 | ||
|
|
97cfbe1b63 | ||
|
|
0ee8c63551 | ||
|
|
0ac5a9ad82 | ||
|
|
6e51f2d608 | ||
|
|
f23a8dc9dc | ||
|
|
d09fe6178c | ||
|
|
ae2cfcc4f3 | ||
|
|
853cc65e32 | ||
|
|
5d140d949f | ||
|
|
6d2da2b3ba | ||
|
|
165e337e44 | ||
|
|
b154cd0418 | ||
|
|
8ef1532691 | ||
|
|
771e44f2a7 | ||
|
|
8933f1128d | ||
|
|
dbac4b9a63 | ||
|
|
bdac5b5807 | ||
|
|
b0961b5213 | ||
|
|
0d7c3fc4d8 | ||
|
|
8dac256096 | ||
|
|
8320da39c3 | ||
|
|
875937e3d8 | ||
|
|
f880adbaac | ||
|
|
48c5f86adb | ||
|
|
f60f7dab12 | ||
|
|
8f690236ed | ||
|
|
430a4297aa | ||
|
|
69e3ab02c4 | ||
|
|
b87a20b881 | ||
|
|
0e6f2d16ab | ||
|
|
498b061819 | ||
|
|
5ab6f599fb | ||
|
|
8b80626035 | ||
|
|
f86ae79fdb | ||
|
|
1a4e68096f | ||
|
|
d599d1f29e | ||
|
|
73edfb3d2c | ||
|
|
ebae68d579 | ||
|
|
573836e2b8 | ||
|
|
da5deb8177 | ||
|
|
8fca89e876 | ||
|
|
d5295a2d0c | ||
|
|
c4df115948 | ||
|
|
8afcb16d8e | ||
|
|
3764d63244 | ||
|
|
c82914df30 | ||
|
|
84ba921f5b | ||
|
|
8e74668e3c | ||
|
|
707c05b5f5 | ||
|
|
9bd2cec199 | ||
|
|
358b8fe8b6 | ||
|
|
7115ae5375 | ||
|
|
13997ca653 | ||
|
|
9fa04a0b93 | ||
|
|
0894145284 | ||
|
|
927b77700d | ||
|
|
1d7a0ea99e | ||
|
|
d6aee8e5ba | ||
|
|
dfaa39fa5d | ||
|
|
8fe3b59efe | ||
|
|
47237b1f15 | ||
|
|
053e727bd5 | ||
|
|
2ce9e81d80 | ||
|
|
50848ee462 | ||
|
|
ef06d560cd | ||
|
|
2abf9f96c7 | ||
|
|
a91686b4cd | ||
|
|
1be424cd47 | ||
|
|
929ea10f76 | ||
|
|
11d1f39e3d | ||
|
|
fc233ed4ff | ||
|
|
efd2e893cf | ||
|
|
2a18b9a8f7 | ||
|
|
6aa214181b | ||
|
|
7067beda99 | ||
|
|
e0f0baf164 | ||
|
|
1fec132dee | ||
|
|
ba521fccb4 | ||
|
|
965a97105e | ||
|
|
3c88c84998 | ||
|
|
d0f66c3734 | ||
|
|
ebc2a741f6 | ||
|
|
d46512af1c | ||
|
|
b4d72bd544 | ||
|
|
382d476271 | ||
|
|
80c240b54a | ||
|
|
21f99d88ab | ||
|
|
d5873a00d5 | ||
|
|
99076edc89 | ||
|
|
7b6c80327e | ||
|
|
57946ed672 | ||
|
|
aad16ac50d | ||
|
|
bbe897e607 | ||
|
|
eca0e66e23 | ||
|
|
3511723914 | ||
|
|
6a1c7b7588 | ||
|
|
18fd6ef119 | ||
|
|
0004f5d2ff | ||
|
|
5f29d1ba9c | ||
|
|
791fd35c35 | ||
|
|
e0d622d347 | ||
|
|
6b34e8a804 | ||
|
|
eb8773fe3e | ||
|
|
b40e3fa1f1 | ||
|
|
d25d761b55 | ||
|
|
d6051dd714 | ||
|
|
53ff061efa | ||
|
|
72b0efd393 | ||
|
|
df97ef23d9 | ||
|
|
f6373971bd | ||
|
|
ec617f2489 | ||
|
|
502ae78579 | ||
|
|
49297d6afb | ||
|
|
29354badd8 | ||
|
|
3c4de796c8 | ||
|
|
c7f4488e77 | ||
|
|
d8765f955d | ||
|
|
9f20037dad | ||
|
|
2ebb090df2 | ||
|
|
110fff3ab9 | ||
|
|
f42a1ceab7 | ||
|
|
9e39a7fa95 | ||
|
|
c879df3b31 | ||
|
|
361ed98353 | ||
|
|
d45f8ef2d3 | ||
|
|
d6a63d11d7 | ||
|
|
4c6804eadc | ||
|
|
99810c0311 | ||
|
|
d15fb1ab1b | ||
|
|
0a6d8b37ad | ||
|
|
f78ffdb9ce | ||
|
|
b7b6fdbdf5 | ||
|
|
e398b471e1 | ||
|
|
48f69f3d88 | ||
|
|
95850d75f6 | ||
|
|
c41d755441 | ||
|
|
39a21787b7 | ||
|
|
36078f9903 | ||
|
|
2f534dc01c | ||
|
|
c8e59f08ec | ||
|
|
19dd6a997f | ||
|
|
4d1a1694cd | ||
|
|
52bea15ad7 | ||
|
|
6592361c5a | ||
|
|
f3f83494f5 | ||
|
|
1169afbe90 | ||
|
|
301bfbd271 | ||
|
|
46aec7eabc | ||
|
|
4cd3b66761 | ||
|
|
567e3dd258 | ||
|
|
fcf853712c | ||
|
|
088d36b47b | ||
|
|
27fb864ac4 | ||
|
|
4001c4a48a | ||
|
|
f64c18ef15 | ||
|
|
f8e72f443d | ||
|
|
5e015cc3ca | ||
|
|
9707f8c5d2 | ||
|
|
6b1d698448 | ||
|
|
72f69dda30 | ||
|
|
8b5d8a0577 | ||
|
|
1aa053ce6f | ||
|
|
701b1c4216 | ||
|
|
f1c4cef781 | ||
|
|
6fe5b307b1 | ||
|
|
679ce08998 | ||
|
|
eeb923eb5b | ||
|
|
142c431745 | ||
|
|
81f5589411 | ||
|
|
04f5c884a4 | ||
|
|
d8df503404 | ||
|
|
d241efcdbe | ||
|
|
a1b8efe721 | ||
|
|
815d076b35 | ||
|
|
86b4816b3f | ||
|
|
473055468a | ||
|
|
c6a28db8b1 | ||
|
|
971f567571 | ||
|
|
7e2eca3d1e | ||
|
|
86b0053bf0 | ||
|
|
cfc4b282f0 | ||
|
|
b85d2aa565 | ||
|
|
70a3d4fb5e | ||
|
|
d9b667e346 | ||
|
|
3a1db53d5a | ||
|
|
4858ebe051 | ||
|
|
d2455267a8 | ||
|
|
61721bb086 | ||
|
|
3e76ee25e3 | ||
|
|
a71e562ff7 | ||
|
|
850fc9adf9 | ||
|
|
d3c78f825c | ||
|
|
7231d92d1f | ||
|
|
93e14484da | ||
|
|
d7d03b1ca2 | ||
|
|
dfd6d95209 | ||
|
|
e4e7384f99 | ||
|
|
effa77afee | ||
|
|
6ccccf8c39 | ||
|
|
dd9d66ef20 | ||
|
|
bc84d1e47a | ||
|
|
6b426014ad | ||
|
|
c6f12fe033 | ||
|
|
bb096ac617 | ||
|
|
734df539dd | ||
|
|
27b9481452 | ||
|
|
945706c2a4 | ||
|
|
a7b024d4fa | ||
|
|
9911863441 | ||
|
|
deb978bf57 | ||
|
|
55998707a5 | ||
|
|
4af702ee11 | ||
|
|
8717ecc429 | ||
|
|
552cfd6ff1 | ||
|
|
928edfd1dd | ||
|
|
98fb2e73f9 | ||
|
|
de09577342 | ||
|
|
cbdbbe535a | ||
|
|
36e636dca1 | ||
|
|
3f5e38bb62 | ||
|
|
eb30f6ceff | ||
|
|
1051fcd278 | ||
|
|
1a0030e086 | ||
|
|
114ce370ed | ||
|
|
207dcbeaa4 | ||
|
|
d4a1722863 | ||
|
|
18b9991fe7 | ||
|
|
739178dd74 | ||
|
|
dcd1392a85 | ||
|
|
07d66da57b | ||
|
|
28ce33feb3 | ||
|
|
86039a3f2b | ||
|
|
af9d09f8ae | ||
|
|
7c06a48ec0 | ||
|
|
7ada9dbd0d | ||
|
|
5c5d74a903 | ||
|
|
72354d55a8 | ||
|
|
fc2002fbd0 | ||
|
|
82ec2715f1 | ||
|
|
ef6e90fb3c | ||
|
|
475f0bf120 | ||
|
|
e68a14c94c | ||
|
|
da0dc28bc4 | ||
|
|
e6bfa52b0b | ||
|
|
36bfbdee0d | ||
|
|
7a9a4127a0 | ||
|
|
4f1472d4ff | ||
|
|
032bb7e96b | ||
|
|
9ddc817a09 | ||
|
|
a5bd68ef8c | ||
|
|
a2ec3642bf | ||
|
|
9647fe1b9f | ||
|
|
762cda75a9 | ||
|
|
c39a9b8de9 | ||
|
|
15a577863a | ||
|
|
2d500554c1 | ||
|
|
c42fb97419 | ||
|
|
d1d6155fd1 | ||
|
|
ff8c42e894 | ||
|
|
e10aeb29eb | ||
|
|
fee3d882c7 | ||
|
|
d17e8d3450 | ||
|
|
99f960bf56 | ||
|
|
83d8847f57 | ||
|
|
f672f86b53 | ||
|
|
b69627891d | ||
|
|
673d8278b5 | ||
|
|
8dd554b935 | ||
|
|
65b7f7d7d5 | ||
|
|
a982cfdb20 | ||
|
|
7689fbe54d | ||
|
|
c9d32e22d3 | ||
|
|
278273b5c3 | ||
|
|
a5d85b8a54 | ||
|
|
4c172d25f6 | ||
|
|
b70a7d9a3b | ||
|
|
ba5a8b44ed | ||
|
|
91e1ed1314 | ||
|
|
85f20c6e1b | ||
|
|
4b98dbb454 | ||
|
|
c3ad4fbdaa | ||
|
|
2e9cd83ed2 | ||
|
|
f1ead06645 | ||
|
|
d486eaad03 | ||
|
|
7d261a147d | ||
|
|
61038888c4 | ||
|
|
39040fb9a0 | ||
|
|
4f3262c328 | ||
|
|
69126a7c5f | ||
|
|
62554b766f | ||
|
|
e699e36270 | ||
|
|
331926dc3c | ||
|
|
8a3b660bbc | ||
|
|
933989c87d | ||
|
|
e3cbcb98c0 | ||
|
|
32a2787254 | ||
|
|
fccfc27de0 | ||
|
|
cdb2894bd9 | ||
|
|
051d022fc2 | ||
|
|
7388d97502 | ||
|
|
1bece3a792 | ||
|
|
1de0038516 | ||
|
|
b7a098fb28 | ||
|
|
ee21d2991c | ||
|
|
f8eab49962 | ||
|
|
c9b3847a69 | ||
|
|
dce3840537 | ||
|
|
b6030c161b | ||
|
|
c920180cb3 | ||
|
|
531a0402f7 | ||
|
|
cb10f9a9c8 | ||
|
|
c808139b02 | ||
|
|
e0d58266be | ||
|
|
1b7c5816fc | ||
|
|
b224874332 | ||
|
|
ef61da3051 | ||
|
|
f214269aa1 | ||
|
|
6bc2ea8dc7 | ||
|
|
71fb839e2b | ||
|
|
8c9c070caf | ||
|
|
b2d7d4c4a9 | ||
|
|
0ac7fbfc07 | ||
|
|
ebfc897bcf | ||
|
|
c66495b66c | ||
|
|
24fb0c33c2 | ||
|
|
25b63a2fb4 | ||
|
|
e576165cf1 | ||
|
|
fe2eccef39 | ||
|
|
0e0e746a0d | ||
|
|
5b4f4b0672 | ||
|
|
185fcfe635 | ||
|
|
2034efcf55 | ||
|
|
38ddb3b866 | ||
|
|
f950294f70 | ||
|
|
86815bc9c4 | ||
|
|
bb37dc1ea6 | ||
|
|
d92ea31858 | ||
|
|
55f204c6f9 | ||
|
|
e97909f776 | ||
|
|
c210ac73cc | ||
|
|
962c91daf0 | ||
|
|
df33c3024a | ||
|
|
62e04f7784 | ||
|
|
32fcb771ca | ||
|
|
a21760b374 | ||
|
|
cd05c7814a | ||
|
|
7b12f091e8 | ||
|
|
e149e60c7a | ||
|
|
bdac84059d | ||
|
|
2d05804fc3 | ||
|
|
2a56de69cc | ||
|
|
6ca2a4a9cd | ||
|
|
a9eb65c462 | ||
|
|
afb30b3695 | ||
|
|
09b019ed13 | ||
|
|
d46ac3a478 | ||
|
|
677b2b9089 | ||
|
|
5c1067c63f | ||
|
|
736c04a7a4 | ||
|
|
0e29c55d13 | ||
|
|
ca49e6079c | ||
|
|
320a7464c7 | ||
|
|
da74cd078f | ||
|
|
322aa60891 | ||
|
|
e380576da2 | ||
|
|
cf7664a854 | ||
|
|
56508e8d79 | ||
|
|
2656c69d99 | ||
|
|
57c1e3ae26 | ||
|
|
2675b2265b | ||
|
|
41e50770d1 | ||
|
|
b3d6e94984 | ||
|
|
5c9c17f1f6 | ||
|
|
11329d5e09 | ||
|
|
8843211e12 | ||
|
|
20e75dc50b | ||
|
|
d2e5441d6e | ||
|
|
0ffa1e72d0 | ||
|
|
a0e034a9e9 | ||
|
|
3c7cbf8685 | ||
|
|
7541dfcab2 | ||
|
|
dc2b79ac9a | ||
|
|
6d62051877 | ||
|
|
61b86744d7 | ||
|
|
fd5b4a131f | ||
|
|
32c4b9eff1 | ||
|
|
95cf35efc5 | ||
|
|
58e6368525 | ||
|
|
16e0d54b15 | ||
|
|
be381e4440 | ||
|
|
9982182926 | ||
|
|
607d157b76 | ||
|
|
e21277ceba | ||
|
|
8012733a52 | ||
|
|
01a1377972 | ||
|
|
37e4b9b5ba | ||
|
|
8a3098604c | ||
|
|
5febce7a59 | ||
|
|
3dbedf1fb6 | ||
|
|
0ae619dfc5 | ||
|
|
05dd191e17 | ||
|
|
c9ecc7a517 | ||
|
|
434a433a09 | ||
|
|
3de54d897c | ||
|
|
530c2a9fcf | ||
|
|
60b8b92630 | ||
|
|
5842da22d8 | ||
|
|
9850e3dae0 | ||
|
|
3af62446fc | ||
|
|
1f71dade67 | ||
|
|
8be664b66f | ||
|
|
c0be4f1307 | ||
|
|
d9c754f5c1 | ||
|
|
33a175eafb | ||
|
|
7c990b3ab3 | ||
|
|
5dfeaa9fd1 | ||
|
|
84fd1caa46 | ||
|
|
2678d761ba | ||
|
|
ede2ee9ce3 | ||
|
|
20f468991f | ||
|
|
58d9e0fef7 | ||
|
|
d7278f022b | ||
|
|
7383596f8c | ||
|
|
f6831ab46b | ||
|
|
7cf0f95ed1 | ||
|
|
bf6b894480 | ||
|
|
ee8fcb6109 | ||
|
|
05cec013fe | ||
|
|
f4cbbd7b79 | ||
|
|
2129adfcc3 | ||
|
|
9f59a2aebf | ||
|
|
26fb75bf3f | ||
|
|
25e5f27785 | ||
|
|
ef62daccf9 | ||
|
|
9067c0a000 | ||
|
|
79470d2e07 | ||
|
|
3cefa6f2bf | ||
|
|
75d954a6bc | ||
|
|
1b7e3746cc | ||
|
|
29252d9dbb | ||
|
|
23e14861be | ||
|
|
97960b5f91 | ||
|
|
18c4ad9adf | ||
|
|
b240c53633 | ||
|
|
660f3d58be | ||
|
|
b6d75cda8e | ||
|
|
e07356c11c | ||
|
|
82e215a42e | ||
|
|
cc1c36d891 | ||
|
|
a1a2d7de5c | ||
|
|
6dce2deb82 | ||
|
|
cdad84edc6 | ||
|
|
de842a67d8 | ||
|
|
918bbe88c6 | ||
|
|
7d3891989c | ||
|
|
168fe7c8d9 | ||
|
|
29ab8408fb | ||
|
|
692e2d4df4 | ||
|
|
85d86dbede | ||
|
|
ce2d7b8efc | ||
|
|
be00d72d82 | ||
|
|
5b376364f5 | ||
|
|
409d15c624 | ||
|
|
ce22388c3b | ||
|
|
30143cf509 | ||
|
|
78f31d2b0d | ||
|
|
4e67a5025a | ||
|
|
b7e0a6f277 | ||
|
|
045680fba5 | ||
|
|
692347cc6b | ||
|
|
faa515d969 | ||
|
|
c4a278ec9c | ||
|
|
658a541f49 | ||
|
|
01435977de | ||
|
|
36ac8828f2 | ||
|
|
9c83d98bbb | ||
|
|
dee9ca3ec2 | ||
|
|
d375327d20 | ||
|
|
09244192e9 | ||
|
|
de333eb02d | ||
|
|
8b50f15a44 | ||
|
|
f853fa3e23 | ||
|
|
d26f6b3b89 | ||
|
|
022a2b1ade | ||
|
|
4f1ac5717e | ||
|
|
d303703dc5 | ||
|
|
642e5687b6 | ||
|
|
2ec845b083 | ||
|
|
c40cd1aa50 | ||
|
|
804162c69a | ||
|
|
08b2a647d0 | ||
|
|
3a058c0c27 | ||
|
|
b8885c1faa | ||
|
|
321e0f2bfe | ||
|
|
cff8b45420 | ||
|
|
6ac47c1ef8 | ||
|
|
86490bedfb | ||
|
|
1091be374e | ||
|
|
36be0453dd | ||
|
|
e2c53b59ce | ||
|
|
f19b6c48ca | ||
|
|
d2a2654ace | ||
|
|
8832ae0bf9 | ||
|
|
ef8db1eebf | ||
|
|
c792a047b1 | ||
|
|
64f7f1d662 | ||
|
|
c886eaa6b0 | ||
|
|
b50fb53f27 | ||
|
|
75d72cfded | ||
|
|
21b0d8c7f7 | ||
|
|
fa8f06f07d | ||
|
|
e07a105b7c | ||
|
|
4f72dcbf54 | ||
|
|
b77877c83d | ||
|
|
8fd3520257 | ||
|
|
f15e64039c | ||
|
|
3ffe2ba17f | ||
|
|
33782d3c83 | ||
|
|
783826aa26 | ||
|
|
c2ef16eac2 | ||
|
|
e999fb6e30 | ||
|
|
d1fc0591a5 | ||
|
|
fb1c9cf3d3 | ||
|
|
21ba1dfc26 | ||
|
|
dacd62428e | ||
|
|
1e52c2dbe6 | ||
|
|
8926ebc56c | ||
|
|
9da87ce868 | ||
|
|
46cc45c186 | ||
|
|
54f2243386 | ||
|
|
8ac33aad69 | ||
|
|
6fc62d39c9 | ||
|
|
a0655806de | ||
|
|
3614d14f83 | ||
|
|
f6fd45cc90 | ||
|
|
be39297f3b | ||
|
|
dce36e0074 | ||
|
|
ba034a8164 | ||
|
|
3dfc7bea3a | ||
|
|
f72435c750 | ||
|
|
3810f642d3 | ||
|
|
ae968142ee | ||
|
|
ccb7887cb9 | ||
|
|
f1ad1216ca | ||
|
|
ce6813329b | ||
|
|
7ad7193b1e | ||
|
|
bd96a49de6 | ||
|
|
81c710eaa3 | ||
|
|
711f0fefb6 | ||
|
|
33ca86e4f2 | ||
|
|
9b5229f2dd | ||
|
|
5781a23a4d | ||
|
|
2d1e6f2644 | ||
|
|
5240eeb518 | ||
|
|
125ee836fe | ||
|
|
3ca2f009f4 | ||
|
|
a900c28f7c | ||
|
|
77bbbb9715 | ||
|
|
88753a6333 | ||
|
|
bcd82f4893 | ||
|
|
749dc61f85 | ||
|
|
c7ccf6801d | ||
|
|
1565522ecc | ||
|
|
a44df2f533 | ||
|
|
317510746f | ||
|
|
ef54e327b7 | ||
|
|
d8d0158774 | ||
|
|
4d75f27a25 | ||
|
|
f89e9d726d | ||
|
|
5194b37460 | ||
|
|
55ea432711 | ||
|
|
ab7408c96f | ||
|
|
1f7e80e581 | ||
|
|
0e91ca90d6 | ||
|
|
8f41fed9c2 | ||
|
|
96dd40cee1 | ||
|
|
62767d072b | ||
|
|
33880ce19e | ||
|
|
988176846d | ||
|
|
657d436a0f | ||
|
|
e5549e3063 | ||
|
|
0b2fb967b8 | ||
|
|
f57478c1aa | ||
|
|
e5a5e2ca7e | ||
|
|
797d503a99 | ||
|
|
512a281986 | ||
|
|
37c5ca7166 | ||
|
|
cda700ef73 | ||
|
|
d32901da8d | ||
|
|
83ebe12061 | ||
|
|
fe34548bad | ||
|
|
855945bef2 | ||
|
|
8421e3aa5f | ||
|
|
c93f79daa7 | ||
|
|
35c53f78c8 | ||
|
|
c158d51f8b | ||
|
|
8e9a8dfede | ||
|
|
67dc694cfb | ||
|
|
74704a132c | ||
|
|
b86674f91f | ||
|
|
5dab3c8482 | ||
|
|
a190ae6b08 | ||
|
|
464fb1726d |
105
.circleci/config.yml
Normal file
105
.circleci/config.yml
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
version: 2.1
|
||||||
|
|
||||||
|
workflows:
|
||||||
|
build:
|
||||||
|
jobs:
|
||||||
|
- linux-arm64-glibc-node-18:
|
||||||
|
filters:
|
||||||
|
tags:
|
||||||
|
only: /^v.*/
|
||||||
|
- linux-arm64-musl-node-18:
|
||||||
|
filters:
|
||||||
|
tags:
|
||||||
|
only: /^v.*/
|
||||||
|
- linux-arm64-glibc-node-20:
|
||||||
|
filters:
|
||||||
|
tags:
|
||||||
|
only: /^v.*/
|
||||||
|
- linux-arm64-musl-node-20:
|
||||||
|
filters:
|
||||||
|
tags:
|
||||||
|
only: /^v.*/
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
linux-arm64-glibc-node-18:
|
||||||
|
resource_class: arm.medium
|
||||||
|
machine:
|
||||||
|
image: ubuntu-2004:current
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run: |
|
||||||
|
sudo docker run -dit --name sharp --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp arm64v8/debian:bullseye
|
||||||
|
sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python3 curl fonts-noto-core"
|
||||||
|
sudo docker exec sharp sh -c "mkdir -p /etc/apt/keyrings"
|
||||||
|
sudo docker exec sharp sh -c "curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg"
|
||||||
|
sudo docker exec sharp sh -c "echo 'deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_18.x nodistro main' | tee /etc/apt/sources.list.d/nodesource.list"
|
||||||
|
sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs"
|
||||||
|
- run: sudo docker exec sharp sh -c "npm install --build-from-source"
|
||||||
|
- run: sudo docker exec sharp sh -c "npm test"
|
||||||
|
- run: |
|
||||||
|
sudo docker exec sharp sh -c "npm run package-from-local-build"
|
||||||
|
sudo docker exec sharp sh -c "npm pkg set \"optionalDependencies.@sharpen/sharp-linux-arm64=file:./npm/linux-arm64\""
|
||||||
|
sudo docker exec sharp sh -c "npm run clean"
|
||||||
|
sudo docker exec sharp sh -c "npm install --ignore-scripts"
|
||||||
|
sudo docker exec sharp sh -c "npm test"
|
||||||
|
- run: "[[ -n $CIRCLE_TAG ]] && sudo docker exec --env prebuild_upload sharp sh -c \"npx prebuild --upload=$prebuild_upload\" || true"
|
||||||
|
linux-arm64-glibc-node-20:
|
||||||
|
resource_class: arm.medium
|
||||||
|
machine:
|
||||||
|
image: ubuntu-2004:current
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run: |
|
||||||
|
sudo docker run -dit --name sharp --workdir /mnt/sharp arm64v8/debian:bullseye
|
||||||
|
sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python3 curl fonts-noto-core"
|
||||||
|
sudo docker exec sharp sh -c "mkdir -p /etc/apt/keyrings"
|
||||||
|
sudo docker exec sharp sh -c "curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg"
|
||||||
|
sudo docker exec sharp sh -c "echo 'deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main' | tee /etc/apt/sources.list.d/nodesource.list"
|
||||||
|
sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs"
|
||||||
|
sudo docker exec sharp sh -c "mkdir -p /mnt/sharp"
|
||||||
|
sudo docker cp . sharp:/mnt/sharp/.
|
||||||
|
- run: sudo docker exec sharp sh -c "npm install --build-from-source"
|
||||||
|
- run: sudo docker exec sharp sh -c "npm test"
|
||||||
|
- run: |
|
||||||
|
sudo docker exec sharp sh -c "npm run package-from-local-build"
|
||||||
|
sudo docker exec sharp sh -c "npm pkg set \"optionalDependencies.@sharpen/sharp-linux-arm64=file:./npm/linux-arm64\""
|
||||||
|
sudo docker exec sharp sh -c "npm run clean"
|
||||||
|
sudo docker exec sharp sh -c "npm install --ignore-scripts"
|
||||||
|
sudo docker exec sharp sh -c "npm test"
|
||||||
|
linux-arm64-musl-node-18:
|
||||||
|
resource_class: arm.medium
|
||||||
|
machine:
|
||||||
|
image: ubuntu-2004:current
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run: |
|
||||||
|
sudo docker run -dit --name sharp --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:18-alpine3.17
|
||||||
|
sudo docker exec sharp sh -c "apk add build-base git python3 font-noto --update-cache"
|
||||||
|
- run: sudo docker exec sharp sh -c "npm install --build-from-source"
|
||||||
|
- run: sudo docker exec sharp sh -c "npm test"
|
||||||
|
- run: |
|
||||||
|
sudo docker exec sharp sh -c "npm run package-from-local-build"
|
||||||
|
sudo docker exec sharp sh -c "npm pkg set \"optionalDependencies.@sharpen/sharp-linuxmusl-arm64=file:./npm/linuxmusl-arm64\""
|
||||||
|
sudo docker exec sharp sh -c "npm run clean"
|
||||||
|
sudo docker exec sharp sh -c "npm install --ignore-scripts"
|
||||||
|
sudo docker exec sharp sh -c "npm test"
|
||||||
|
- run: "[[ -n $CIRCLE_TAG ]] && sudo docker exec --env prebuild_upload sharp sh -c \"npx prebuild --upload=$prebuild_upload\" || true"
|
||||||
|
linux-arm64-musl-node-20:
|
||||||
|
resource_class: arm.medium
|
||||||
|
machine:
|
||||||
|
image: ubuntu-2004:current
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run: |
|
||||||
|
sudo docker run -dit --name sharp --workdir /mnt/sharp node:20-alpine3.18
|
||||||
|
sudo docker exec sharp sh -c "apk add build-base git python3 font-noto --update-cache"
|
||||||
|
sudo docker exec sharp sh -c "mkdir -p /mnt/sharp"
|
||||||
|
sudo docker cp . sharp:/mnt/sharp/.
|
||||||
|
- run: sudo docker exec sharp sh -c "npm install --build-from-source"
|
||||||
|
- run: sudo docker exec sharp sh -c "npm test"
|
||||||
|
- run: |
|
||||||
|
sudo docker exec sharp sh -c "npm run package-from-local-build"
|
||||||
|
sudo docker exec sharp sh -c "npm pkg set \"optionalDependencies.@sharpen/sharp-linuxmusl-arm64=file:./npm/linuxmusl-arm64\""
|
||||||
|
sudo docker exec sharp sh -c "npm run clean"
|
||||||
|
sudo docker exec sharp sh -c "npm install --ignore-scripts"
|
||||||
|
sudo docker exec sharp sh -c "npm test"
|
||||||
16
.cirrus.yml
Normal file
16
.cirrus.yml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
freebsd_instance:
|
||||||
|
image_family: freebsd-13-2
|
||||||
|
|
||||||
|
task:
|
||||||
|
name: FreeBSD
|
||||||
|
env:
|
||||||
|
IGNORE_OSVERSION: yes
|
||||||
|
skip_notifications: true
|
||||||
|
prerequisites_script:
|
||||||
|
- pkg update -f
|
||||||
|
- pkg upgrade -y
|
||||||
|
- pkg install -y devel/git devel/pkgconf graphics/vips www/node20 www/npm
|
||||||
|
install_script:
|
||||||
|
- npm install --build-from-source
|
||||||
|
test_script:
|
||||||
|
- npm test
|
||||||
12
.editorconfig
Normal file
12
.editorconfig
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# http://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
src/libvips/* linguist-vendored
|
||||||
98
.github/CONTRIBUTING.md
vendored
Normal file
98
.github/CONTRIBUTING.md
vendored
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
# Contributing to sharp
|
||||||
|
|
||||||
|
Hello, thank you for your interest in helping!
|
||||||
|
|
||||||
|
## Submit a new bug report
|
||||||
|
|
||||||
|
Please create a [new issue](https://github.com/lovell/sharp/issues/new) containing the steps to reproduce the problem.
|
||||||
|
|
||||||
|
If you're having installation problems, please include the output of running `npm install --verbose sharp`.
|
||||||
|
|
||||||
|
New bugs are assigned a `triage` label whilst under investigation.
|
||||||
|
|
||||||
|
## Submit a new feature request
|
||||||
|
|
||||||
|
If a [similar request](https://github.com/lovell/sharp/labels/enhancement) exists,
|
||||||
|
it's probably fastest to add a comment to it about your requirement.
|
||||||
|
|
||||||
|
Implementation is usually straightforward if libvips
|
||||||
|
[already supports](https://www.libvips.org/API/current/func-list.html)
|
||||||
|
the feature you need.
|
||||||
|
|
||||||
|
## Submit a Pull Request to fix a bug
|
||||||
|
|
||||||
|
Thank you! To prevent the problem occurring again, please add unit tests that would have failed.
|
||||||
|
|
||||||
|
Please select the `main` branch as the destination for your Pull Request so your fix can be included in the next minor release.
|
||||||
|
|
||||||
|
Please squash your changes into a single commit using a command like `git rebase -i upstream/main`.
|
||||||
|
|
||||||
|
To test C++ changes, you can compile the module using `npm install --build-from-source` and then run the tests using `npm test`.
|
||||||
|
|
||||||
|
## Submit a Pull Request with a new feature
|
||||||
|
|
||||||
|
Please add JavaScript [unit tests](https://github.com/lovell/sharp/tree/main/test/unit) to cover your new feature.
|
||||||
|
A test coverage report for the JavaScript code is generated in the `coverage/lcov-report` directory.
|
||||||
|
Please also update the [TypeScript definitions](https://github.com/lovell/sharp/tree/main/lib/index.d.ts), along with the [type definition tests](https://github.com/lovell/sharp/tree/main/test/types/sharp.test-d.ts).
|
||||||
|
|
||||||
|
Where possible, the functional tests use gradient-based perceptual hashes
|
||||||
|
based on [dHash](http://www.hackerfactor.com/blog/index.php?/archives/529-Kind-of-Like-That.html)
|
||||||
|
to compare expected vs actual images.
|
||||||
|
|
||||||
|
You deserve to add your details to the [list of contributors](https://github.com/lovell/sharp/blob/main/package.json#L5).
|
||||||
|
|
||||||
|
Any change that modifies the existing public API should be added to the relevant work-in-progress branch for inclusion in the next major release.
|
||||||
|
|
||||||
|
Please squash your changes into a single commit using a command like `git rebase -i upstream/<wip-branch>`.
|
||||||
|
|
||||||
|
### Add a new public method
|
||||||
|
|
||||||
|
The API tries to be as fluent as possible.
|
||||||
|
Image processing concepts follow the naming conventions from libvips and, to a lesser extent, ImageMagick.
|
||||||
|
|
||||||
|
Most methods have optional parameters and assume sensible defaults.
|
||||||
|
Please ensure backwards compatibility where possible.
|
||||||
|
|
||||||
|
Feel free to create a [new issue](https://github.com/lovell/sharp/issues/new) to gather feedback on a potential API change.
|
||||||
|
|
||||||
|
### Remove an existing public method
|
||||||
|
|
||||||
|
A method to be removed should be deprecated in the next major version then removed in the following major version.
|
||||||
|
|
||||||
|
By way of example, the `background()` method present in v0.20.0 was deprecated in v0.21.0 and removed in v0.22.0.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
The public API is documented with [JSDoc](http://usejsdoc.org/) annotated comments.
|
||||||
|
|
||||||
|
These can be converted to Markdown by running:
|
||||||
|
```sh
|
||||||
|
npm run docs-build
|
||||||
|
```
|
||||||
|
|
||||||
|
Please include documentation updates in any Pull Request that modifies the public API.
|
||||||
|
|
||||||
|
## Run the tests
|
||||||
|
|
||||||
|
### Functional tests and static code analysis
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm test
|
||||||
|
```
|
||||||
|
|
||||||
|
### Memory leak tests
|
||||||
|
|
||||||
|
Requires [Valgrind](http://valgrind.org/).
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run test-leak
|
||||||
|
```
|
||||||
|
|
||||||
|
## Finally
|
||||||
|
|
||||||
|
Please feel free to ask any questions via a
|
||||||
|
[new issue](https://github.com/lovell/sharp/issues/new).
|
||||||
|
|
||||||
|
If you're unable to post details publicly, please
|
||||||
|
[e-mail](https://github.com/lovell/sharp/blob/main/package.json#L5)
|
||||||
|
for private, paid consulting.
|
||||||
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
open_collective: libvips
|
||||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: Documentation
|
||||||
|
url: https://sharp.pixelplumbing.com/
|
||||||
|
about: Installation instructions, complete API documentation with examples, changelog
|
||||||
28
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
28
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea
|
||||||
|
labels: enhancement
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature request
|
||||||
|
|
||||||
|
### What are you trying to achieve?
|
||||||
|
|
||||||
|
<!-- Please provide context here. -->
|
||||||
|
|
||||||
|
### When you searched for similar feature requests, what did you find that might be related?
|
||||||
|
|
||||||
|
<!-- Please demonstrate your research here. -->
|
||||||
|
|
||||||
|
### What would you expect the API to look like?
|
||||||
|
|
||||||
|
<!-- Please provide your suggestions here. -->
|
||||||
|
|
||||||
|
### What alternatives have you considered?
|
||||||
|
|
||||||
|
<!-- Please provide your ideas here. -->
|
||||||
|
|
||||||
|
### Please provide sample image(s) that help explain this feature
|
||||||
|
|
||||||
|
<!-- Please provide links to one or more images here. -->
|
||||||
56
.github/ISSUE_TEMPLATE/installation.md
vendored
Normal file
56
.github/ISSUE_TEMPLATE/installation.md
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
---
|
||||||
|
name: Installation
|
||||||
|
about: Something went wrong during either 'npm install sharp' or 'require("sharp")'
|
||||||
|
labels: installation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- Please try to answer as many of these questions as possible. -->
|
||||||
|
|
||||||
|
## Possible install-time or require-time problem
|
||||||
|
|
||||||
|
<!-- Please place an [x] in the box to confirm. -->
|
||||||
|
|
||||||
|
- [ ] I have read the [documentation relating to installation](https://sharp.pixelplumbing.com/install).
|
||||||
|
|
||||||
|
### Are you using the latest version of sharp?
|
||||||
|
|
||||||
|
<!-- Please place an [x] in the box to confirm. -->
|
||||||
|
|
||||||
|
- [ ] I am using the latest version of `sharp` as reported by `npm view sharp dist-tags.latest`.
|
||||||
|
|
||||||
|
If you cannot confirm this, please upgrade to the latest version and try again before opening an issue.
|
||||||
|
|
||||||
|
If you are using another package which depends on a version of `sharp` that is not the latest, please open an issue against that package instead.
|
||||||
|
|
||||||
|
### Are you using a supported runtime?
|
||||||
|
|
||||||
|
<!-- Please place an [x] in the relevant box to confirm. -->
|
||||||
|
|
||||||
|
- [ ] I am using Node.js 18 with a version >= 18.17.0
|
||||||
|
- [ ] I am using Node.js 20 with a version >= 20.3.0
|
||||||
|
- [ ] I am using Node.js 21 or later
|
||||||
|
|
||||||
|
If you cannot confirm any of these, please upgrade to the latest version and try again before opening an issue.
|
||||||
|
|
||||||
|
### Are you using a supported package manager?
|
||||||
|
|
||||||
|
<!-- Please place an [x] in the relevant box to confirm. -->
|
||||||
|
|
||||||
|
- [ ] I am using npm >= 9.6.5
|
||||||
|
- [ ] I am using yarn >= 3.2.0
|
||||||
|
- [ ] I am using pnpm >= 7.1.0
|
||||||
|
|
||||||
|
If you cannot confirm any of these, please upgrade to the latest version and try again before opening an issue.
|
||||||
|
|
||||||
|
### What is the complete output of running `npm install --verbose --foreground-scripts sharp` in an empty directory?
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
<!-- Please provide output of `npm install --verbose --foreground-scripts sharp` in an empty directory here. -->
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### What is the output of running `npx envinfo --binaries --system --npmPackages=sharp --npmGlobalPackages=sharp`?
|
||||||
|
|
||||||
|
<!-- Please provide output of `npx envinfo --binaries --system --npmPackages=sharp --npmGlobalPackages=sharp` here. -->
|
||||||
49
.github/ISSUE_TEMPLATE/possible-bug.md
vendored
Normal file
49
.github/ISSUE_TEMPLATE/possible-bug.md
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
---
|
||||||
|
name: Possible bug
|
||||||
|
about: Installation of sharp was successful but then something unexpected occurred using one of its features
|
||||||
|
labels: triage
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- If this issue relates to installation, please use https://github.com/lovell/sharp/issues/new?labels=installation&template=installation.md instead. -->
|
||||||
|
|
||||||
|
## Possible bug
|
||||||
|
|
||||||
|
### Is this a possible bug in a feature of sharp, unrelated to installation?
|
||||||
|
|
||||||
|
<!-- Please place an [x] in the box to confirm. -->
|
||||||
|
|
||||||
|
- [ ] Running `npm install sharp` completes without error.
|
||||||
|
- [ ] Running `node -e "require('sharp')"` completes without error.
|
||||||
|
|
||||||
|
If you cannot confirm both of these, please open an [installation issue](https://github.com/lovell/sharp/issues/new?labels=installation&template=installation.md) instead.
|
||||||
|
|
||||||
|
### Are you using the latest version of sharp?
|
||||||
|
|
||||||
|
<!-- Please place an [x] in the box to confirm. -->
|
||||||
|
|
||||||
|
- [ ] I am using the latest version of `sharp` as reported by `npm view sharp dist-tags.latest`.
|
||||||
|
|
||||||
|
If you cannot confirm this, please upgrade to the latest version and try again before opening an issue.
|
||||||
|
|
||||||
|
If you are using another package which depends on a version of `sharp` that is not the latest, please open an issue against that package instead.
|
||||||
|
|
||||||
|
### What is the output of running `npx envinfo --binaries --system --npmPackages=sharp --npmGlobalPackages=sharp`?
|
||||||
|
|
||||||
|
<!-- Please provide output of the above command here. -->
|
||||||
|
|
||||||
|
### What are the steps to reproduce?
|
||||||
|
|
||||||
|
<!-- Please enter steps to reproduce here. -->
|
||||||
|
|
||||||
|
### What is the expected behaviour?
|
||||||
|
|
||||||
|
<!-- Please enter the expected behaviour here. -->
|
||||||
|
|
||||||
|
### Please provide a minimal, standalone code sample, without other dependencies, that demonstrates this problem
|
||||||
|
|
||||||
|
<!-- Please provide either formatted code or a link to a repo/gist that allows someone else to reproduce here. -->
|
||||||
|
|
||||||
|
### Please provide sample image(s) that help explain this problem
|
||||||
|
|
||||||
|
<!-- Please provide links to one or more images here. -->
|
||||||
26
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
26
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
name: Question
|
||||||
|
about: For help understanding an existing feature
|
||||||
|
labels: question
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- If this issue relates to installation, please use https://github.com/lovell/sharp/issues/new?labels=installation&template=installation.md instead. -->
|
||||||
|
|
||||||
|
## Question about an existing feature
|
||||||
|
|
||||||
|
### What are you trying to achieve?
|
||||||
|
|
||||||
|
<!-- Please provide context here. -->
|
||||||
|
|
||||||
|
### When you searched for similar issues, what did you find that might be related?
|
||||||
|
|
||||||
|
<!-- Please demonstrate your research here. -->
|
||||||
|
|
||||||
|
### Please provide a minimal, standalone code sample, without other dependencies, that demonstrates this question
|
||||||
|
|
||||||
|
<!-- Please provide either formatted code or a link to a repo/gist that helps someone else understand here. -->
|
||||||
|
|
||||||
|
### Please provide sample image(s) that help explain this question
|
||||||
|
|
||||||
|
<!-- Please provide links to one or more images here. -->
|
||||||
18
.github/SECURITY.md
vendored
Normal file
18
.github/SECURITY.md
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
The latest version of `sharp` as published to npm
|
||||||
|
and reported by `npm view sharp dist-tags.latest`
|
||||||
|
is supported with security updates.
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
Please use
|
||||||
|
[e-mail](https://github.com/lovell/sharp/blob/main/package.json#L5)
|
||||||
|
to report a vulnerability.
|
||||||
|
|
||||||
|
You can expect a response within 48 hours
|
||||||
|
if you are a human reporting a genuine issue.
|
||||||
|
|
||||||
|
Thank you in advance.
|
||||||
190
.github/workflows/ci.yml
vendored
Normal file
190
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
name: CI
|
||||||
|
on:
|
||||||
|
- push
|
||||||
|
- pull_request
|
||||||
|
permissions: {}
|
||||||
|
jobs:
|
||||||
|
github-runner:
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
name: ${{ matrix.platform }} - Node.js ${{ matrix.nodejs_version_major }} ${{ matrix.prebuild && '- prebuild' }}
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
container: ${{ matrix.container }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: ubuntu-22.04
|
||||||
|
container: rockylinux:8
|
||||||
|
nodejs_version: "^18.17.0"
|
||||||
|
nodejs_version_major: 18
|
||||||
|
platform: linux-x64
|
||||||
|
prebuild: true
|
||||||
|
- os: ubuntu-22.04
|
||||||
|
container: rockylinux:8
|
||||||
|
nodejs_version: "^20.3.0"
|
||||||
|
nodejs_version_major: 20
|
||||||
|
platform: linux-x64
|
||||||
|
- os: ubuntu-22.04
|
||||||
|
container: node:18-alpine3.17
|
||||||
|
nodejs_version_major: 18
|
||||||
|
platform: linuxmusl-x64
|
||||||
|
prebuild: true
|
||||||
|
- os: ubuntu-22.04
|
||||||
|
container: node:20-alpine3.18
|
||||||
|
nodejs_version_major: 20
|
||||||
|
platform: linuxmusl-x64
|
||||||
|
- os: macos-11
|
||||||
|
nodejs_arch: x64
|
||||||
|
nodejs_version: "^18.17.0"
|
||||||
|
nodejs_version_major: 18
|
||||||
|
platform: darwin-x64
|
||||||
|
prebuild: true
|
||||||
|
- os: macos-11
|
||||||
|
nodejs_arch: x64
|
||||||
|
nodejs_version: "^20.3.0"
|
||||||
|
nodejs_version_major: 20
|
||||||
|
platform: darwin-x64
|
||||||
|
- os: windows-2019
|
||||||
|
nodejs_arch: x86
|
||||||
|
nodejs_version: "^18.17.0"
|
||||||
|
nodejs_version_major: 18
|
||||||
|
platform: win32-ia32
|
||||||
|
prebuild: true
|
||||||
|
- os: windows-2019
|
||||||
|
nodejs_arch: x86
|
||||||
|
nodejs_version: "^20.3.0"
|
||||||
|
nodejs_version_major: 20
|
||||||
|
platform: win32-ia32
|
||||||
|
- os: windows-2019
|
||||||
|
nodejs_arch: x64
|
||||||
|
nodejs_version: "^18.17.0"
|
||||||
|
nodejs_version_major: 18
|
||||||
|
platform: win32-x64
|
||||||
|
prebuild: true
|
||||||
|
- os: windows-2019
|
||||||
|
nodejs_arch: x64
|
||||||
|
nodejs_version: "^20.3.0"
|
||||||
|
nodejs_version_major: 20
|
||||||
|
platform: win32-x64
|
||||||
|
steps:
|
||||||
|
- name: Dependencies (Linux glibc)
|
||||||
|
if: contains(matrix.container, 'centos')
|
||||||
|
run: |
|
||||||
|
yum install -y https://rpm.nodesource.com/pub_${{ matrix.nodejs_version_major }}.x/nodistro/repo/nodesource-release-nodistro-1.noarch.rpm
|
||||||
|
yum install -y https://packages.endpointdev.com/rhel/7/os/x86_64/endpoint-repo.x86_64.rpm
|
||||||
|
yum install -y centos-release-scl
|
||||||
|
yum install -y devtoolset-11-gcc-c++ make git python3 nodejs fontconfig google-noto-sans-fonts
|
||||||
|
echo "/opt/rh/devtoolset-11/root/usr/bin" >> $GITHUB_PATH
|
||||||
|
- name: Dependencies (Rocky Linux glibc)
|
||||||
|
if: contains(matrix.container, 'rockylinux')
|
||||||
|
run: |
|
||||||
|
dnf install -y https://rpm.nodesource.com/pub_${{ matrix.nodejs_version_major }}.x/nodistro/repo/nodesource-release-nodistro-1.noarch.rpm
|
||||||
|
dnf install -y --setopt=nodesource-nodejs.module_hotfixes=1 nodejs
|
||||||
|
dnf install -y gcc-toolset-11-gcc-c++ make git python3 fontconfig google-noto-sans-fonts
|
||||||
|
echo "/opt/rh/gcc-toolset-11/root/usr/bin" >> $GITHUB_PATH
|
||||||
|
- name: Dependencies (Linux musl)
|
||||||
|
if: contains(matrix.container, 'alpine')
|
||||||
|
run: apk add build-base git python3 font-noto --update-cache
|
||||||
|
- name: Dependencies (Python 3.11 - macOS, Windows)
|
||||||
|
if: contains(matrix.os, 'macos') || contains(matrix.os, 'windows')
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
- name: Dependencies (Node.js - macOS, Windows)
|
||||||
|
if: contains(matrix.os, 'macos') || contains(matrix.os, 'windows')
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.nodejs_version }}
|
||||||
|
architecture: ${{ matrix.nodejs_arch }}
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Install
|
||||||
|
run: npm install --build-from-source
|
||||||
|
- name: Test
|
||||||
|
run: npm test
|
||||||
|
- name: Test packaging
|
||||||
|
run: |
|
||||||
|
npm run package-from-local-build
|
||||||
|
npm pkg set "optionalDependencies.@sharpen/sharp-${{ matrix.platform }}=file:./npm/${{ matrix.platform }}"
|
||||||
|
npm run clean
|
||||||
|
npm install --ignore-scripts
|
||||||
|
npm test
|
||||||
|
- name: Prebuild
|
||||||
|
if: matrix.prebuild && startsWith(github.ref, 'refs/tags/')
|
||||||
|
env:
|
||||||
|
prebuild_upload: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: npx prebuild
|
||||||
|
github-runner-qemu:
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
name: linux-arm - Node.js 18 - prebuild
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: uraimo/run-on-arch-action@v2
|
||||||
|
with:
|
||||||
|
arch: armv6
|
||||||
|
distro: buster
|
||||||
|
env: |
|
||||||
|
nodejs_version: "18.17.0"
|
||||||
|
prebuild_upload: "${{ startsWith(github.ref, 'refs/tags/') && secrets.GITHUB_TOKEN || '' }}"
|
||||||
|
run: |
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y fontconfig fonts-noto-core g++ git libatomic1 make python3 xz-utils
|
||||||
|
mkdir /opt/nodejs
|
||||||
|
curl --silent https://unofficial-builds.nodejs.org/download/release/v${nodejs_version}/node-v${nodejs_version}-linux-armv6l.tar.xz | tar xJC /opt/nodejs --strip-components=1
|
||||||
|
export PATH=$PATH:/opt/nodejs/bin
|
||||||
|
npm install --build-from-source
|
||||||
|
npx mocha --no-config --spec=test/unit/io.js
|
||||||
|
npm run package-from-local-build
|
||||||
|
npm pkg set "optionalDependencies.@sharpen/sharp-linux-arm=file:./npm/linux-arm"
|
||||||
|
npm run clean
|
||||||
|
npm install --ignore-scripts
|
||||||
|
npx mocha --no-config --spec=test/unit/io.js --timeout=30000
|
||||||
|
[[ -n $prebuild_upload ]] && npx prebuild || true
|
||||||
|
macstadium-runner:
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
name: ${{ matrix.platform }} - Node.js ${{ matrix.nodejs_version_major }} ${{ matrix.prebuild && '- prebuild' }}
|
||||||
|
runs-on: macos-m1
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- nodejs_arch: x64
|
||||||
|
nodejs_version: "^18.17.0"
|
||||||
|
nodejs_version_major: 18
|
||||||
|
platform: darwin-x64
|
||||||
|
- nodejs_arch: arm64
|
||||||
|
nodejs_version: "^18.17.0"
|
||||||
|
nodejs_version_major: 18
|
||||||
|
platform: darwin-arm64
|
||||||
|
prebuild: true
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: /usr/bin/arch -arch arm64e /bin/bash -l {0}
|
||||||
|
steps:
|
||||||
|
- name: Dependencies (Node.js)
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.nodejs_version }}
|
||||||
|
architecture: ${{ matrix.nodejs_arch }}
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Install
|
||||||
|
run: npm install --build-from-source
|
||||||
|
- name: Test
|
||||||
|
run: npm test
|
||||||
|
- name: Test packaging
|
||||||
|
run: |
|
||||||
|
npm run package-from-local-build
|
||||||
|
npm pkg set "optionalDependencies.@sharpen/sharp-${{ matrix.platform }}=file:./npm/${{ matrix.platform }}"
|
||||||
|
npm run clean
|
||||||
|
npm install --ignore-scripts
|
||||||
|
npm test
|
||||||
|
- name: Prebuild
|
||||||
|
if: matrix.prebuild && startsWith(github.ref, 'refs/tags/')
|
||||||
|
env:
|
||||||
|
prebuild_upload: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: npx prebuild
|
||||||
22
.gitignore
vendored
22
.gitignore
vendored
@@ -1,9 +1,21 @@
|
|||||||
build
|
src/build
|
||||||
|
src/node_modules
|
||||||
node_modules
|
node_modules
|
||||||
coverage
|
/coverage
|
||||||
|
npm/*/*
|
||||||
|
!npm/*/package.json
|
||||||
test/bench/node_modules
|
test/bench/node_modules
|
||||||
test/fixtures/output.*
|
test/fixtures/output*
|
||||||
|
test/fixtures/vips-properties.xml
|
||||||
test/leak/libvips.supp
|
test/leak/libvips.supp
|
||||||
|
test/saliency/report.json
|
||||||
# Mac OS X
|
test/saliency/Image*
|
||||||
|
test/saliency/[Uu]serData*
|
||||||
|
!test/saliency/userData.js
|
||||||
|
.gitattributes
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.nyc_output
|
||||||
|
.vscode/
|
||||||
|
package-lock.json
|
||||||
|
.idea
|
||||||
|
.firebase
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
node_modules
|
|
||||||
test/bench/node_modules
|
|
||||||
coverage
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"strict": true,
|
|
||||||
"node": true,
|
|
||||||
"globals": {
|
|
||||||
"describe": true,
|
|
||||||
"it": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
7
.mocharc.jsonc
Normal file
7
.mocharc.jsonc
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"parallel": true,
|
||||||
|
"slow": 1000,
|
||||||
|
"timeout": 30000,
|
||||||
|
"require": "./test/beforeEach.js",
|
||||||
|
"spec": "./test/unit/*.js"
|
||||||
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
build
|
|
||||||
node_modules
|
|
||||||
coverage
|
|
||||||
.jshintignore
|
|
||||||
.jshintrc
|
|
||||||
.gitignore
|
|
||||||
test
|
|
||||||
.travis.yml
|
|
||||||
6
.prebuildrc
Normal file
6
.prebuildrc
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"runtime": "napi",
|
||||||
|
"include-regex": "(sharp-.+\\.node|libvips-.+\\.dll|libglib-.+\\.dll|libgobject-.+\\.dll)",
|
||||||
|
"prerelease": true,
|
||||||
|
"strip": true
|
||||||
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
language: node_js
|
|
||||||
node_js:
|
|
||||||
- "0.10"
|
|
||||||
- "0.11"
|
|
||||||
before_install:
|
|
||||||
- curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash -
|
|
||||||
after_success:
|
|
||||||
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
|
|
||||||
653
README.md
Executable file → Normal file
653
README.md
Executable file → Normal file
@@ -1,622 +1,111 @@
|
|||||||
# sharp
|
# sharp
|
||||||
|
|
||||||
* [Installation](https://github.com/lovell/sharp#installation)
|
<img src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
|
||||||
* [Usage examples](https://github.com/lovell/sharp#usage-examples)
|
|
||||||
* [API](https://github.com/lovell/sharp#api)
|
|
||||||
* [Testing](https://github.com/lovell/sharp#testing)
|
|
||||||
* [Performance](https://github.com/lovell/sharp#performance)
|
|
||||||
* [Thanks](https://github.com/lovell/sharp#thanks)
|
|
||||||
* [Licence](https://github.com/lovell/sharp#licence)
|
|
||||||
|
|
||||||
The typical use case for this high speed Node.js module is to convert large images of many formats to smaller, web-friendly JPEG, PNG and WebP images of varying dimensions.
|
The typical use case for this high speed Node.js module
|
||||||
|
is to convert large images in common formats to
|
||||||
|
smaller, web-friendly JPEG, PNG, WebP, GIF and AVIF images of varying dimensions.
|
||||||
|
|
||||||
|
Resizing an image is typically 4x-5x faster than using the
|
||||||
|
quickest ImageMagick and GraphicsMagick settings
|
||||||
|
due to its use of [libvips](https://github.com/libvips/libvips).
|
||||||
|
|
||||||
This module supports reading and writing JPEG, PNG and WebP images to and from Streams, Buffer objects and the filesystem.
|
|
||||||
It also supports reading images of many other types from the filesystem via libmagick++ or libgraphicsmagick++ if present.
|
|
||||||
Colour spaces, embedded ICC profiles and alpha transparency channels are all handled correctly.
|
Colour spaces, embedded ICC profiles and alpha transparency channels are all handled correctly.
|
||||||
|
Lanczos resampling ensures quality is not sacrificed for speed.
|
||||||
|
|
||||||
Only small regions of uncompressed image data are held in memory and processed at a time, taking full advantage of multiple CPU cores and L1/L2/L3 cache. Resizing an image is typically 4x faster than using the quickest ImageMagick and GraphicsMagick settings.
|
As well as image resizing, operations such as
|
||||||
|
rotation, extraction, compositing and gamma correction are available.
|
||||||
|
|
||||||
Huffman tables are optimised when generating JPEG output images without having to use separate command line tools like [jpegoptim](https://github.com/tjko/jpegoptim) and [jpegtran](http://jpegclub.org/jpegtran/). PNG filtering can be disabled, which for diagrams and line art often produces the same result as [pngcrush](http://pmt.sourceforge.net/pngcrush/).
|
Most modern macOS, Windows and Linux systems running Node.js >= 18.17.0
|
||||||
|
do not require any additional install or runtime dependencies.
|
||||||
|
|
||||||
Everything remains non-blocking thanks to _libuv_, no child processes are spawned and Promises/A+ are supported.
|
## Documentation
|
||||||
|
|
||||||
Anyone who has used the Node.js bindings for [GraphicsMagick](https://github.com/aheckmann/gm) will find the API similarly fluent.
|
Visit [sharp.pixelplumbing.com](https://sharp.pixelplumbing.com/) for complete
|
||||||
|
[installation instructions](https://sharp.pixelplumbing.com/install),
|
||||||
|
[API documentation](https://sharp.pixelplumbing.com/api-constructor),
|
||||||
|
[benchmark tests](https://sharp.pixelplumbing.com/performance) and
|
||||||
|
[changelog](https://sharp.pixelplumbing.com/changelog).
|
||||||
|
|
||||||
This module is powered by the blazingly fast [libvips](https://github.com/jcupitt/libvips) image processing library, originally created in 1989 at Birkbeck College and currently maintained by [John Cupitt](https://github.com/jcupitt).
|
## Examples
|
||||||
|
|
||||||
## Installation
|
```sh
|
||||||
|
npm install sharp
|
||||||
npm install sharp
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
* Node.js v0.10+
|
|
||||||
* [libvips](https://github.com/jcupitt/libvips) v7.40.0+ (7.42.0+ recommended)
|
|
||||||
|
|
||||||
To install the most suitable version of libvips on the following Operating Systems:
|
|
||||||
|
|
||||||
* Mac OS
|
|
||||||
* Homebrew
|
|
||||||
* MacPorts
|
|
||||||
* Debian Linux
|
|
||||||
* Debian 7, 8
|
|
||||||
* Ubuntu 12.04, 14.04, 14.10, 15.04
|
|
||||||
* Mint 13, 17
|
|
||||||
* Red Hat Linux
|
|
||||||
* RHEL/Centos/Scientific 6, 7
|
|
||||||
* Fedora 21, 22
|
|
||||||
* Amazon Linux 2014.09
|
|
||||||
|
|
||||||
run the following as a user with `sudo` access:
|
|
||||||
|
|
||||||
curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash -
|
|
||||||
|
|
||||||
or run the following as `root`:
|
|
||||||
|
|
||||||
curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | bash -
|
|
||||||
|
|
||||||
The [preinstall.sh](https://github.com/lovell/sharp/blob/master/preinstall.sh) script requires `curl` and `pkg-config`.
|
|
||||||
|
|
||||||
### Mac OS tips
|
|
||||||
|
|
||||||
Manual install via homebrew:
|
|
||||||
|
|
||||||
brew install homebrew/science/vips --with-webp --with-graphicsmagick
|
|
||||||
|
|
||||||
A missing or incorrectly configured _Xcode Command Line Tools_ installation [can lead](https://github.com/lovell/sharp/issues/80) to a `library not found for -ljpeg` error. If so, please try:
|
|
||||||
|
|
||||||
xcode-select --install
|
|
||||||
|
|
||||||
The _gettext_ dependency of _libvips_ [can lead](https://github.com/lovell/sharp/issues/9) to a `library not found for -lintl` error. If so, please try:
|
|
||||||
|
|
||||||
brew link gettext --force
|
|
||||||
|
|
||||||
### Heroku
|
|
||||||
|
|
||||||
[Alessandro Tagliapietra](https://github.com/alex88) maintains an [Heroku buildpack for libvips](https://github.com/alex88/heroku-buildpack-vips) and its dependencies.
|
|
||||||
|
|
||||||
### Docker
|
|
||||||
|
|
||||||
[Marc Bachmann](https://github.com/marcbachmann) maintains a [Dockerfile for libvips](https://github.com/marcbachmann/dockerfile-libvips).
|
|
||||||
|
|
||||||
docker pull marcbachmann/libvips
|
|
||||||
|
|
||||||
### gulp.js
|
|
||||||
|
|
||||||
[Eugeny Vlasenko](https://github.com/mahnunchik) maintains [gulp-responsive](https://www.npmjs.org/package/gulp-responsive) and [Mohammad Prabowo](https://github.com/rizalp) maintains [gulp-sharp](https://www.npmjs.org/package/gulp-sharp).
|
|
||||||
|
|
||||||
## Usage examples
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
var sharp = require('sharp');
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
sharp('input.jpg').resize(300, 200).toFile('output.jpg', function(err) {
|
const sharp = require('sharp');
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
// output.jpg is a 300 pixels wide and 200 pixels high image
|
|
||||||
// containing a scaled and cropped version of input.jpg
|
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```javascript
|
### Callback
|
||||||
var transformer = sharp().resize(300, 200).crop(sharp.gravity.north);
|
|
||||||
readableStream.pipe(transformer).pipe(writableStream);
|
|
||||||
// Read image data from readableStream, resize and write image data to writableStream
|
|
||||||
```
|
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
var image = sharp(inputJpg);
|
sharp(inputBuffer)
|
||||||
image.metadata(function(err, metadata) {
|
.resize(320, 240)
|
||||||
image.resize(metadata.width / 2).webp().toBuffer(function(err, outputBuffer, info) {
|
.toFile('output.webp', (err, info) => { ... });
|
||||||
// outputBuffer contains a WebP image half the width and height of the original JPEG
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Promise
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
var pipeline = sharp()
|
sharp('input.jpg')
|
||||||
.rotate()
|
.rotate()
|
||||||
.resize(null, 200)
|
.resize(200)
|
||||||
.progressive()
|
.jpeg({ mozjpeg: true })
|
||||||
.toBuffer(function(err, outputBuffer, info) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
// outputBuffer contains 200px high progressive JPEG image data,
|
|
||||||
// auto-rotated using EXIF Orientation tag
|
|
||||||
// info.width and info.height contain the dimensions of the resized image
|
|
||||||
});
|
|
||||||
readableStream.pipe(pipeline);
|
|
||||||
```
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
sharp('input.png')
|
|
||||||
.rotate(180)
|
|
||||||
.resize(300)
|
|
||||||
.flatten()
|
|
||||||
.background('#ff6600')
|
|
||||||
.sharpen()
|
|
||||||
.withMetadata()
|
|
||||||
.quality(90)
|
|
||||||
.webp()
|
|
||||||
.toBuffer()
|
.toBuffer()
|
||||||
.then(function(outputBuffer) {
|
.then( data => { ... })
|
||||||
// outputBuffer contains upside down, 300px wide, alpha channel flattened
|
.catch( err => { ... });
|
||||||
// onto orange background, sharpened, with metadata, 90% quality WebP image
|
|
||||||
// data
|
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Async/await
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
http.createServer(function(request, response) {
|
const semiTransparentRedPng = await sharp({
|
||||||
response.writeHead(200, {'Content-Type': 'image/webp'});
|
create: {
|
||||||
sharp('input.jpg').rotate().resize(200).webp().pipe(response);
|
width: 48,
|
||||||
}).listen(8000);
|
height: 48,
|
||||||
// Create HTTP server that always returns auto-rotated 'input.jpg',
|
channels: 4,
|
||||||
// resized to 200 pixels wide, in WebP format
|
background: { r: 255, g: 0, b: 0, alpha: 0.5 }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.png()
|
||||||
|
.toBuffer();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Stream
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
sharp(input)
|
const roundedCorners = Buffer.from(
|
||||||
.extract(top, left, width, height)
|
'<svg><rect x="0" y="0" width="200" height="200" rx="50" ry="50"/></svg>'
|
||||||
.toFile(output);
|
);
|
||||||
// Extract a region of the input image, saving in the same format.
|
|
||||||
|
const roundedCornerResizer =
|
||||||
|
sharp()
|
||||||
|
.resize(200, 200)
|
||||||
|
.composite([{
|
||||||
|
input: roundedCorners,
|
||||||
|
blend: 'dest-in'
|
||||||
|
}])
|
||||||
|
.png();
|
||||||
|
|
||||||
|
readableStream
|
||||||
|
.pipe(roundedCornerResizer)
|
||||||
|
.pipe(writableStream);
|
||||||
```
|
```
|
||||||
|
|
||||||
```javascript
|
## Contributing
|
||||||
sharp(input)
|
|
||||||
.extract(topOffsetPre, leftOffsetPre, widthPre, heightPre)
|
|
||||||
.resize(width, height)
|
|
||||||
.extract(topOffsetPost, leftOffsetPost, widthPost, heightPost)
|
|
||||||
.toFile(output);
|
|
||||||
// Extract a region, resize, then extract from the resized image
|
|
||||||
```
|
|
||||||
|
|
||||||
```javascript
|
A [guide for contributors](https://github.com/lovell/sharp/blob/main/.github/CONTRIBUTING.md)
|
||||||
sharp(inputBuffer)
|
covers reporting bugs, requesting features and submitting code changes.
|
||||||
.resize(200, 300)
|
|
||||||
.interpolateWith(sharp.interpolator.nohalo)
|
|
||||||
.background('white')
|
|
||||||
.embed()
|
|
||||||
.toFile('output.tiff')
|
|
||||||
.then(function() {
|
|
||||||
// output.tiff is a 200 pixels wide and 300 pixels high image
|
|
||||||
// containing a bicubic scaled version, embedded on a white canvas,
|
|
||||||
// of the image data in inputBuffer
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
```javascript
|
## Licensing
|
||||||
sharp('input.gif')
|
|
||||||
.resize(200, 300)
|
|
||||||
.background({r: 0, g: 0, b: 0, a: 0})
|
|
||||||
.embed()
|
|
||||||
.webp()
|
|
||||||
.toBuffer(function(err, outputBuffer) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
// outputBuffer contains WebP image data of a 200 pixels wide and 300 pixels high
|
|
||||||
// containing a scaled version, embedded on a transparent canvas, of input.gif
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
```javascript
|
Copyright 2013 Lovell Fuller and others.
|
||||||
sharp(inputBuffer)
|
|
||||||
.resize(200, 200)
|
|
||||||
.max()
|
|
||||||
.jpeg()
|
|
||||||
.toBuffer().then(function(outputBuffer) {
|
|
||||||
// outputBuffer contains JPEG image data no wider than 200 pixels and no higher
|
|
||||||
// than 200 pixels regardless of the inputBuffer image dimensions
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
### Input methods
|
|
||||||
|
|
||||||
#### sharp([input])
|
|
||||||
|
|
||||||
Constructor to which further methods are chained. `input`, if present, can be one of:
|
|
||||||
|
|
||||||
* Buffer containing JPEG, PNG, WebP or TIFF image data, or
|
|
||||||
* String containing the filename of an image, with most major formats supported.
|
|
||||||
|
|
||||||
The object returned implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.
|
|
||||||
|
|
||||||
JPEG, PNG, WebP or TIFF format image data can be streamed into the object when `input` is not provided.
|
|
||||||
|
|
||||||
JPEG, PNG or WebP format image data can be streamed out from this object.
|
|
||||||
|
|
||||||
#### metadata([callback])
|
|
||||||
|
|
||||||
Fast access to image metadata without decoding any compressed image data.
|
|
||||||
|
|
||||||
`callback`, if present, gets the arguments `(err, metadata)` where `metadata` has the attributes:
|
|
||||||
|
|
||||||
* `format`: Name of decoder to be used to decompress image data e.g. `jpeg`, `png`, `webp` (for file-based input additionally `tiff` and `magick`)
|
|
||||||
* `width`: Number of pixels wide
|
|
||||||
* `height`: Number of pixels high
|
|
||||||
* `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `scrgb`, `cmyk`, `lab`, `xyz`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L522)
|
|
||||||
* `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
|
|
||||||
* `hasProfile`: Boolean indicating the presence of an embedded ICC profile
|
|
||||||
* `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
|
|
||||||
* `orientation`: Number value of the EXIF Orientation header, if present
|
|
||||||
|
|
||||||
A Promises/A+ promise is returned when `callback` is not provided.
|
|
||||||
|
|
||||||
#### sequentialRead()
|
|
||||||
|
|
||||||
An advanced setting that switches the libvips access method to `VIPS_ACCESS_SEQUENTIAL`. This will reduce memory usage and can improve performance on some systems.
|
|
||||||
|
|
||||||
### Image transformation options
|
|
||||||
|
|
||||||
#### resize(width, [height])
|
|
||||||
|
|
||||||
Scale output to `width` x `height`. By default, the resized image is cropped to the exact size specified.
|
|
||||||
|
|
||||||
`width` is the Number of pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height.
|
|
||||||
|
|
||||||
`height` is the Number of pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
|
|
||||||
|
|
||||||
#### extract(top, left, width, height)
|
|
||||||
|
|
||||||
Extract a region of the image. Can be used with or without a `resize` operation.
|
|
||||||
|
|
||||||
`top` and `left` are the offset, in pixels, from the top-left corner.
|
|
||||||
|
|
||||||
`width` and `height` are the dimensions of the extracted image.
|
|
||||||
|
|
||||||
Use `extract` before `resize` for pre-resize extraction. Use `extract` after `resize` for post-resize extraction. Use `extract` before and after for both.
|
|
||||||
|
|
||||||
#### crop([gravity])
|
|
||||||
|
|
||||||
Crop the resized image to the exact size specified, the default behaviour.
|
|
||||||
|
|
||||||
`gravity`, if present, is an attribute of the `sharp.gravity` Object e.g. `sharp.gravity.north`.
|
|
||||||
|
|
||||||
Possible values are `north`, `east`, `south`, `west`, `center` and `centre`. The default gravity is `center`/`centre`.
|
|
||||||
|
|
||||||
#### max()
|
|
||||||
|
|
||||||
Preserving aspect ratio, resize the image to the maximum `width` or `height` specified.
|
|
||||||
|
|
||||||
Both `width` and `height` must be provided via `resize` otherwise the behaviour will default to `crop`.
|
|
||||||
|
|
||||||
#### background(rgba)
|
|
||||||
|
|
||||||
Set the background for the `embed` and `flatten` operations.
|
|
||||||
|
|
||||||
`rgba` is parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
|
|
||||||
|
|
||||||
The alpha value is a float between `0` (transparent) and `1` (opaque).
|
|
||||||
|
|
||||||
The default background is `{r: 0, g: 0, b: 0, a: 1}`, black without transparency.
|
|
||||||
|
|
||||||
#### embed()
|
|
||||||
|
|
||||||
Preserving aspect ratio, resize the image to the maximum `width` or `height` specified then embed on a background of the exact `width` and `height` specified.
|
|
||||||
|
|
||||||
If the background contains an alpha value then WebP and PNG format output images will contain an alpha channel, even when the input image does not.
|
|
||||||
|
|
||||||
#### flatten()
|
|
||||||
|
|
||||||
Merge alpha transparency channel, if any, with `background`.
|
|
||||||
|
|
||||||
#### rotate([angle])
|
|
||||||
|
|
||||||
Rotate the output image by either an explicit angle or auto-orient based on the EXIF `Orientation` tag.
|
|
||||||
|
|
||||||
`angle`, if present, is a Number with a value of `0`, `90`, `180` or `270`.
|
|
||||||
|
|
||||||
Use this method without `angle` to determine the angle from EXIF data. Mirroring is supported and may infer the use of a `flip` operation.
|
|
||||||
|
|
||||||
#### flip()
|
|
||||||
|
|
||||||
Flip the image about the vertical Y axis. This always occurs after rotation, if any.
|
|
||||||
|
|
||||||
#### flop()
|
|
||||||
|
|
||||||
Flop the image about the horizontal X axis. This always occurs after rotation, if any.
|
|
||||||
|
|
||||||
#### withoutEnlargement()
|
|
||||||
|
|
||||||
Do not enlarge the output image if the input image width *or* height are already less than the required dimensions.
|
|
||||||
|
|
||||||
This is equivalent to GraphicsMagick's `>` geometry option: "change the dimensions of the image only if its width or height exceeds the geometry specification".
|
|
||||||
|
|
||||||
#### blur([sigma])
|
|
||||||
|
|
||||||
When used without parameters, performs a fast, mild blur of the output image. This typically reduces performance by 10%.
|
|
||||||
|
|
||||||
When a `sigma` is provided, performs a slower, more accurate Gaussian blur. This typically reduces performance by 25%.
|
|
||||||
|
|
||||||
* `sigma`, if present, is a Number between 0.3 and 1000 representing the approximate blur radius in pixels.
|
|
||||||
|
|
||||||
#### sharpen([radius], [flat], [jagged])
|
|
||||||
|
|
||||||
When used without parameters, performs a fast, mild sharpen of the output image. This typically reduces performance by 10%.
|
|
||||||
|
|
||||||
When a `radius` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space. Separate control over the level of sharpening in "flat" and "jagged" areas is available. This typically reduces performance by 50%.
|
|
||||||
|
|
||||||
* `radius`, if present, is an integral Number representing the sharpen mask radius in pixels.
|
|
||||||
* `flat`, if present, is a Number representing the level of sharpening to apply to "flat" areas, defaulting to a value of 1.0.
|
|
||||||
* `jagged`, if present, is a Number representing the level of sharpening to apply to "jagged" areas, defaulting to a value of 2.0.
|
|
||||||
|
|
||||||
#### interpolateWith(interpolator)
|
|
||||||
|
|
||||||
Use the given interpolator for image resizing, where `interpolator` is an attribute of the `sharp.interpolator` Object e.g. `sharp.interpolator.bicubic`.
|
|
||||||
|
|
||||||
Possible interpolators, in order of performance, are:
|
|
||||||
|
|
||||||
* `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation), suitable for image enlargement only.
|
|
||||||
* `bilinear`: Use [bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation), the default and fastest image reduction interpolation.
|
|
||||||
* `bicubic`: Use [bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation), which typically reduces performance by 5%.
|
|
||||||
* `vertexSplitQuadraticBasisSpline`: Use [VSQBS interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48), which prevents "staircasing" and typically reduces performance by 5%.
|
|
||||||
* `locallyBoundedBicubic`: Use [LBB interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100), which prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" and typically reduces performance by a factor of 2.
|
|
||||||
* `nohalo`: Use [Nohalo interpolation](http://eprints.soton.ac.uk/268086/), which prevents acutance and typically reduces performance by a factor of 3.
|
|
||||||
|
|
||||||
#### gamma([gamma])
|
|
||||||
|
|
||||||
Apply a gamma correction by reducing the encoding (darken) pre-resize at a factor of `1/gamma` then increasing the encoding (brighten) post-resize at a factor of `gamma`.
|
|
||||||
|
|
||||||
`gamma`, if present, is a Number betweem 1 and 3. The default value is `2.2`, a suitable approximation for sRGB images.
|
|
||||||
|
|
||||||
This can improve the perceived brightness of a resized image in non-linear colour spaces.
|
|
||||||
|
|
||||||
JPEG input images will not take advantage of the shrink-on-load performance optimisation when applying a gamma correction.
|
|
||||||
|
|
||||||
#### grayscale() / greyscale()
|
|
||||||
|
|
||||||
Convert to 8-bit greyscale; 256 shades of grey.
|
|
||||||
|
|
||||||
This is a linear operation. If the input image is in a non-linear colour space such as sRGB, use `gamma()` with `greyscale()` for the best results.
|
|
||||||
|
|
||||||
The output image will still be web-friendly sRGB and contain three (identical) channels.
|
|
||||||
|
|
||||||
### Output options
|
|
||||||
|
|
||||||
#### jpeg()
|
|
||||||
|
|
||||||
Use JPEG format for the output image.
|
|
||||||
|
|
||||||
#### png()
|
|
||||||
|
|
||||||
Use PNG format for the output image.
|
|
||||||
|
|
||||||
#### webp()
|
|
||||||
|
|
||||||
Use WebP format for the output image.
|
|
||||||
|
|
||||||
#### quality(quality)
|
|
||||||
|
|
||||||
The output quality to use for lossy JPEG, WebP and TIFF output formats. The default quality is `80`.
|
|
||||||
|
|
||||||
`quality` is a Number between 1 and 100.
|
|
||||||
|
|
||||||
#### progressive()
|
|
||||||
|
|
||||||
Use progressive (interlace) scan for JPEG and PNG output. This typically reduces compression performance by 30% but results in an image that can be rendered sooner when decompressed.
|
|
||||||
|
|
||||||
#### withMetadata()
|
|
||||||
|
|
||||||
Include all metadata (EXIF, XMP, IPTC) from the input image in the output image. This will also convert to and add the latest web-friendly v2 sRGB ICC profile.
|
|
||||||
|
|
||||||
The default behaviour is to strip all metadata and convert to the device-independent sRGB colour space.
|
|
||||||
|
|
||||||
#### compressionLevel(compressionLevel)
|
|
||||||
|
|
||||||
An advanced setting for the _zlib_ compression level of the lossless PNG output format. The default level is `6`.
|
|
||||||
|
|
||||||
`compressionLevel` is a Number between 0 and 9.
|
|
||||||
|
|
||||||
#### withoutAdaptiveFiltering()
|
|
||||||
|
|
||||||
_Requires libvips 7.42.0+_
|
|
||||||
|
|
||||||
An advanced setting to disable adaptive row filtering for the lossless PNG output format.
|
|
||||||
|
|
||||||
### Output methods
|
|
||||||
|
|
||||||
#### toFile(filename, [callback])
|
|
||||||
|
|
||||||
`filename` is a String containing the filename to write the image data to. The format is inferred from the extension, with JPEG, PNG, WebP and TIFF supported.
|
|
||||||
|
|
||||||
`callback`, if present, is called with two arguments `(err, info)` where:
|
|
||||||
|
|
||||||
* `err` contains an error message, if any.
|
|
||||||
* `info` contains the output image `format`, `size` (bytes), `width` and `height`.
|
|
||||||
|
|
||||||
A Promises/A+ promise is returned when `callback` is not provided.
|
|
||||||
|
|
||||||
#### toBuffer([callback])
|
|
||||||
|
|
||||||
Write image data to a Buffer, the format of which will match the input image by default. JPEG, PNG and WebP are supported.
|
|
||||||
|
|
||||||
`callback`, if present, gets three arguments `(err, buffer, info)` where:
|
|
||||||
|
|
||||||
* `err` is an error message, if any.
|
|
||||||
* `buffer` is the output image data.
|
|
||||||
* `info` contains the output image `format`, `size` (bytes), `width` and `height`.
|
|
||||||
|
|
||||||
A Promises/A+ promise is returned when `callback` is not provided.
|
|
||||||
|
|
||||||
### Utility methods
|
|
||||||
|
|
||||||
#### sharp.cache([memory], [items])
|
|
||||||
|
|
||||||
If `memory` or `items` are provided, set the limits of _libvips'_ operation cache.
|
|
||||||
|
|
||||||
* `memory` is the maximum memory in MB to use for this cache, with a default value of 100
|
|
||||||
* `items` is the maximum number of operations to cache, with a default value of 500
|
|
||||||
|
|
||||||
This method always returns cache statistics, useful for determining how much working memory is required for a particular task.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
var stats = sharp.cache(); // { current: 75, high: 99, memory: 100, items: 500 }
|
|
||||||
sharp.cache(200); // { current: 75, high: 99, memory: 200, items: 500 }
|
|
||||||
sharp.cache(50, 200); // { current: 49, high: 99, memory: 50, items: 200}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### sharp.concurrency([threads])
|
|
||||||
|
|
||||||
`threads`, if provided, is the Number of threads _libvips'_ should create for processing each image. The default value is the number of CPU cores. A value of `0` will reset to this default.
|
|
||||||
|
|
||||||
This method always returns the current concurrency.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
var threads = sharp.concurrency(); // 4
|
|
||||||
sharp.concurrency(2); // 2
|
|
||||||
sharp.concurrency(0); // 4
|
|
||||||
```
|
|
||||||
|
|
||||||
The maximum number of images that can be processed in parallel is limited by libuv's `UV_THREADPOOL_SIZE` environment variable.
|
|
||||||
|
|
||||||
#### sharp.counters()
|
|
||||||
|
|
||||||
Provides access to internal task counters.
|
|
||||||
|
|
||||||
* `queue` is the number of tasks this module has queued waiting for _libuv_ to provide a worker thread from its pool.
|
|
||||||
* `process` is the number of resize tasks currently being processed.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
var counters = sharp.counters(); // { queue: 2, process: 4 }
|
|
||||||
```
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
### Functional tests
|
|
||||||
|
|
||||||
#### Coverage
|
|
||||||
|
|
||||||
[](https://coveralls.io/r/lovell/sharp?branch=master)
|
|
||||||
|
|
||||||
#### Ubuntu 12.04
|
|
||||||
|
|
||||||
[](https://travis-ci.org/lovell/sharp)
|
|
||||||
|
|
||||||
#### Centos 6.5
|
|
||||||
|
|
||||||
[](https://snap-ci.com/lovell/sharp/branch/master)
|
|
||||||
|
|
||||||
#### It worked on my machine
|
|
||||||
|
|
||||||
```
|
|
||||||
npm test
|
|
||||||
```
|
|
||||||
|
|
||||||
### Memory leak tests
|
|
||||||
|
|
||||||
```
|
|
||||||
cd sharp/test/leak
|
|
||||||
./leak.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
Requires _valgrind_:
|
|
||||||
|
|
||||||
```
|
|
||||||
brew install valgrind
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
sudo apt-get install -qq valgrind
|
|
||||||
```
|
|
||||||
|
|
||||||
### Benchmark tests
|
|
||||||
|
|
||||||
```
|
|
||||||
cd sharp/test/bench
|
|
||||||
npm install
|
|
||||||
npm test
|
|
||||||
```
|
|
||||||
|
|
||||||
Requires both _ImageMagick_ and _GraphicsMagick_:
|
|
||||||
|
|
||||||
```
|
|
||||||
brew install imagemagick
|
|
||||||
brew install graphicsmagick
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
sudo apt-get install -qq imagemagick graphicsmagick libmagick++-dev
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
sudo yum install ImageMagick
|
|
||||||
sudo yum install -y http://download.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm
|
|
||||||
sudo yum install -y --enablerepo=epel GraphicsMagick
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance
|
|
||||||
|
|
||||||
### Test environment
|
|
||||||
|
|
||||||
* AWS EC2 [c3.xlarge](http://aws.amazon.com/ec2/instance-types/#Compute_Optimized)
|
|
||||||
* Ubuntu 14.04
|
|
||||||
* libvips 7.40.8
|
|
||||||
* liborc 0.4.22
|
|
||||||
|
|
||||||
### The contenders
|
|
||||||
|
|
||||||
* [imagemagick-native](https://github.com/mash/node-imagemagick-native) v1.2.2 - Supports Buffers only
|
|
||||||
* [imagemagick](https://github.com/yourdeveloper/node-imagemagick) v0.1.3 - Supports filesystem only and "has been unmaintained for a long time".
|
|
||||||
* [gm](https://github.com/aheckmann/gm) v1.16.0 - Fully featured wrapper around GraphicsMagick.
|
|
||||||
* sharp v0.6.2 - Caching within libvips disabled to ensure a fair comparison.
|
|
||||||
|
|
||||||
### The task
|
|
||||||
|
|
||||||
Decompress a 2725x2225 JPEG image, resize and crop to 720x480, then compress to JPEG.
|
|
||||||
|
|
||||||
### Results
|
|
||||||
|
|
||||||
| Module | Input | Output | Ops/sec | Speed-up |
|
|
||||||
| :-------------------- | :----- | :----- | ------: | -------: |
|
|
||||||
| imagemagick-native | buffer | buffer | 1.58 | 1 |
|
|
||||||
| imagemagick | file | file | 6.23 | 3.9 |
|
|
||||||
| gm | buffer | file | 5.32 | 3.4 |
|
|
||||||
| gm | buffer | buffer | 5.32 | 3.4 |
|
|
||||||
| gm | file | file | 5.36 | 3.4 |
|
|
||||||
| gm | file | buffer | 5.36 | 3.4 |
|
|
||||||
| sharp | buffer | file | 22.05 | 14.0 |
|
|
||||||
| sharp | buffer | buffer | 22.14 | 14.0 |
|
|
||||||
| sharp | file | file | 21.79 | 13.8 |
|
|
||||||
| sharp | file | buffer | 21.90 | 13.9 |
|
|
||||||
| sharp | stream | stream | 20.87 | 13.2 |
|
|
||||||
| sharp +promise | file | buffer | 21.89 | 13.9 |
|
|
||||||
| sharp +sharpen | file | buffer | 19.69 | 12.5 |
|
|
||||||
| sharp +progressive | file | buffer | 16.93 | 10.7 |
|
|
||||||
| sharp +sequentialRead | file | buffer | 21.60 | 13.7 |
|
|
||||||
|
|
||||||
You can expect greater performance with caching enabled (default) and using 8+ core machines.
|
|
||||||
|
|
||||||
## Thanks
|
|
||||||
|
|
||||||
This module would never have been possible without the help and code contributions of the following people:
|
|
||||||
|
|
||||||
* [John Cupitt](https://github.com/jcupitt)
|
|
||||||
* [Pierre Inglebert](https://github.com/pierreinglebert)
|
|
||||||
* [Jonathan Ong](https://github.com/jonathanong)
|
|
||||||
* [Chanon Sajjamanochai](https://github.com/chanon)
|
|
||||||
* [Juliano Julio](https://github.com/julianojulio)
|
|
||||||
* [Daniel Gasienica](https://github.com/gasi)
|
|
||||||
* [Julian Walker](https://github.com/julianwa)
|
|
||||||
* [Amit Pitaru](https://github.com/apitaru)
|
|
||||||
* [Brandon Aaron](https://github.com/brandonaaron)
|
|
||||||
* [Andreas Lind](https://github.com/papandreou)
|
|
||||||
|
|
||||||
Thank you!
|
|
||||||
|
|
||||||
## Licence
|
|
||||||
|
|
||||||
Copyright 2013, 2014 Lovell Fuller and contributors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0.html)
|
You may obtain a copy of the License at
|
||||||
|
[https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
|||||||
38
binding.gyp
38
binding.gyp
@@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
'targets': [{
|
|
||||||
'target_name': 'sharp',
|
|
||||||
'sources': [
|
|
||||||
'src/common.cc',
|
|
||||||
'src/utilities.cc',
|
|
||||||
'src/metadata.cc',
|
|
||||||
'src/resize.cc',
|
|
||||||
'src/sharp.cc'
|
|
||||||
],
|
|
||||||
'variables': {
|
|
||||||
'PKG_CONFIG_PATH': '<!(which brew >/dev/null 2>&1 && eval $(brew --env) && echo $PKG_CONFIG_LIBDIR || true):$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig'
|
|
||||||
},
|
|
||||||
'libraries': [
|
|
||||||
'<!(PKG_CONFIG_PATH="<(PKG_CONFIG_PATH)" pkg-config --libs vips)'
|
|
||||||
],
|
|
||||||
'include_dirs': [
|
|
||||||
'<!(PKG_CONFIG_PATH="<(PKG_CONFIG_PATH)" pkg-config --cflags vips glib-2.0)',
|
|
||||||
'<!(node -e "require(\'nan\')")'
|
|
||||||
],
|
|
||||||
'cflags_cc': [
|
|
||||||
'-std=c++0x',
|
|
||||||
'-fexceptions',
|
|
||||||
'-Wall',
|
|
||||||
'-O3'
|
|
||||||
],
|
|
||||||
'xcode_settings': {
|
|
||||||
'OTHER_CPLUSPLUSFLAGS': [
|
|
||||||
'-std=c++11',
|
|
||||||
'-stdlib=libc++',
|
|
||||||
'-fexceptions',
|
|
||||||
'-Wall',
|
|
||||||
'-O3'
|
|
||||||
],
|
|
||||||
'MACOSX_DEPLOYMENT_TARGET': '10.7'
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
88
docs/README.md
Normal file
88
docs/README.md
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# sharp
|
||||||
|
|
||||||
|
<img src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
|
||||||
|
|
||||||
|
The typical use case for this high speed Node.js module
|
||||||
|
is to convert large images in common formats to
|
||||||
|
smaller, web-friendly JPEG, PNG, WebP, GIF and AVIF images of varying dimensions.
|
||||||
|
|
||||||
|
Resizing an image is typically 4x-5x faster than using the
|
||||||
|
quickest ImageMagick and GraphicsMagick settings
|
||||||
|
due to its use of [libvips](https://github.com/libvips/libvips).
|
||||||
|
|
||||||
|
Colour spaces, embedded ICC profiles and alpha transparency channels are all handled correctly.
|
||||||
|
Lanczos resampling ensures quality is not sacrificed for speed.
|
||||||
|
|
||||||
|
As well as image resizing, operations such as
|
||||||
|
rotation, extraction, compositing and gamma correction are available.
|
||||||
|
|
||||||
|
Most modern macOS, Windows and Linux systems running Node.js >= 14.15.0
|
||||||
|
do not require any additional install or runtime dependencies.
|
||||||
|
|
||||||
|
### Formats
|
||||||
|
|
||||||
|
This module supports reading JPEG, PNG, WebP, GIF, AVIF, TIFF and SVG images.
|
||||||
|
|
||||||
|
Output images can be in JPEG, PNG, WebP, GIF, AVIF and TIFF formats as well as uncompressed raw pixel data.
|
||||||
|
|
||||||
|
Streams, Buffer objects and the filesystem can be used for input and output.
|
||||||
|
|
||||||
|
A single input Stream can be split into multiple processing pipelines and output Streams.
|
||||||
|
|
||||||
|
Deep Zoom image pyramids can be generated,
|
||||||
|
suitable for use with "slippy map" tile viewers like
|
||||||
|
[OpenSeadragon](https://github.com/openseadragon/openseadragon).
|
||||||
|
|
||||||
|
### Fast
|
||||||
|
|
||||||
|
This module is powered by the blazingly fast
|
||||||
|
[libvips](https://github.com/libvips/libvips) image processing library,
|
||||||
|
originally created in 1989 at Birkbeck College
|
||||||
|
and currently maintained by a small team led by
|
||||||
|
[John Cupitt](https://github.com/jcupitt).
|
||||||
|
|
||||||
|
Only small regions of uncompressed image data
|
||||||
|
are held in memory and processed at a time,
|
||||||
|
taking full advantage of multiple CPU cores and L1/L2/L3 cache.
|
||||||
|
|
||||||
|
Everything remains non-blocking thanks to _libuv_,
|
||||||
|
no child processes are spawned and Promises/async/await are supported.
|
||||||
|
|
||||||
|
### Optimal
|
||||||
|
|
||||||
|
The features of `mozjpeg` and `pngquant` can be used
|
||||||
|
to optimise the file size of JPEG and PNG images respectively,
|
||||||
|
without having to invoke separate `imagemin` processes.
|
||||||
|
|
||||||
|
Huffman tables are optimised when generating JPEG output images
|
||||||
|
without having to use separate command line tools like
|
||||||
|
[jpegoptim](https://github.com/tjko/jpegoptim) and
|
||||||
|
[jpegtran](http://jpegclub.org/jpegtran/).
|
||||||
|
|
||||||
|
PNG filtering is disabled by default,
|
||||||
|
which for diagrams and line art often produces the same result
|
||||||
|
as [pngcrush](https://pmt.sourceforge.io/pngcrush/).
|
||||||
|
|
||||||
|
The file size of animated GIF output is optimised
|
||||||
|
without having to use separate command line tools such as
|
||||||
|
[gifsicle](https://www.lcdf.org/gifsicle/).
|
||||||
|
|
||||||
|
### Contributing
|
||||||
|
|
||||||
|
A [guide for contributors](https://github.com/lovell/sharp/blob/main/.github/CONTRIBUTING.md)
|
||||||
|
covers reporting bugs, requesting features and submitting code changes.
|
||||||
|
|
||||||
|
### Licensing
|
||||||
|
|
||||||
|
Copyright 2013 Lovell Fuller and others.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
[https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
136
docs/api-channel.md
Normal file
136
docs/api-channel.md
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
## removeAlpha
|
||||||
|
> removeAlpha() ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Remove alpha channel, if any. This is a no-op if the image does not have an alpha channel.
|
||||||
|
|
||||||
|
See also [flatten](/api-operation#flatten).
|
||||||
|
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
sharp('rgba.png')
|
||||||
|
.removeAlpha()
|
||||||
|
.toFile('rgb.png', function(err, info) {
|
||||||
|
// rgb.png is a 3 channel image without an alpha channel
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## ensureAlpha
|
||||||
|
> ensureAlpha([alpha]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Ensure the output image has an alpha transparency channel.
|
||||||
|
If missing, the added alpha channel will have the specified
|
||||||
|
transparency level, defaulting to fully-opaque (1).
|
||||||
|
This is a no-op if the image already has an alpha channel.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid alpha transparency level
|
||||||
|
|
||||||
|
**Since**: 0.21.2
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [alpha] | <code>number</code> | <code>1</code> | alpha transparency level (0=fully-transparent, 1=fully-opaque) |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// rgba.png will be a 4 channel image with a fully-opaque alpha channel
|
||||||
|
await sharp('rgb.jpg')
|
||||||
|
.ensureAlpha()
|
||||||
|
.toFile('rgba.png')
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// rgba is a 4 channel image with a fully-transparent alpha channel
|
||||||
|
const rgba = await sharp(rgb)
|
||||||
|
.ensureAlpha(0)
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## extractChannel
|
||||||
|
> extractChannel(channel) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Extract a single channel from a multi-channel image.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid channel
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| channel | <code>number</code> \| <code>string</code> | zero-indexed channel/band number to extract, or `red`, `green`, `blue` or `alpha`. |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// green.jpg is a greyscale image containing the green channel of the input
|
||||||
|
await sharp(input)
|
||||||
|
.extractChannel('green')
|
||||||
|
.toFile('green.jpg');
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// red1 is the red value of the first pixel, red2 the second pixel etc.
|
||||||
|
const [red1, red2, ...] = await sharp(input)
|
||||||
|
.extractChannel(0)
|
||||||
|
.raw()
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## joinChannel
|
||||||
|
> joinChannel(images, options) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Join one or more channels to the image.
|
||||||
|
The meaning of the added channels depends on the output colourspace, set with `toColourspace()`.
|
||||||
|
By default the output image will be web-friendly sRGB, with additional channels interpreted as alpha channels.
|
||||||
|
Channel ordering follows vips convention:
|
||||||
|
- sRGB: 0: Red, 1: Green, 2: Blue, 3: Alpha.
|
||||||
|
- CMYK: 0: Magenta, 1: Cyan, 2: Yellow, 3: Black, 4: Alpha.
|
||||||
|
|
||||||
|
Buffers may be any of the image formats supported by sharp.
|
||||||
|
For raw pixel input, the `options` object should contain a `raw` attribute, which follows the format of the attribute of the same name in the `sharp()` constructor.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| images | <code>Array.<(string\|Buffer)></code> \| <code>string</code> \| <code>Buffer</code> | one or more images (file paths, Buffers). |
|
||||||
|
| options | <code>Object</code> | image options, see `sharp()` constructor. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## bandbool
|
||||||
|
> bandbool(boolOp) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Perform a bitwise boolean operation on all input image channels (bands) to produce a single channel output image.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| boolOp | <code>string</code> | one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively. |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
sharp('3-channel-rgb-input.png')
|
||||||
|
.bandbool(sharp.bool.and)
|
||||||
|
.toFile('1-channel-output.png', function (err, info) {
|
||||||
|
// The output will be a single channel image where each pixel `P = R & G & B`.
|
||||||
|
// If `I(1,1) = [247, 170, 14] = [0b11110111, 0b10101010, 0b00001111]`
|
||||||
|
// then `O(1,1) = 0b11110111 & 0b10101010 & 0b00001111 = 0b00000010 = 2`.
|
||||||
|
});
|
||||||
|
```
|
||||||
147
docs/api-colour.md
Normal file
147
docs/api-colour.md
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
## tint
|
||||||
|
> tint(rgb) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Tint the image using the provided chroma while preserving the image luminance.
|
||||||
|
An alpha channel may be present and will be unchanged by the operation.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameter
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| rgb | <code>string</code> \| <code>Object</code> | parsed by the [color](https://www.npmjs.org/package/color) module to extract chroma values. |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const output = await sharp(input)
|
||||||
|
.tint({ r: 255, g: 240, b: 16 })
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## greyscale
|
||||||
|
> greyscale([greyscale]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Convert to 8-bit greyscale; 256 shades of grey.
|
||||||
|
This is a linear operation. If the input image is in a non-linear colour space such as sRGB, use `gamma()` with `greyscale()` for the best results.
|
||||||
|
By default the output image will be web-friendly sRGB and contain three (identical) color channels.
|
||||||
|
This may be overridden by other sharp operations such as `toColourspace('b-w')`,
|
||||||
|
which will produce an output image containing one color channel.
|
||||||
|
An alpha channel may be present, and will be unchanged by the operation.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Default |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| [greyscale] | <code>Boolean</code> | <code>true</code> |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const output = await sharp(input).greyscale().toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## grayscale
|
||||||
|
> grayscale([grayscale]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Alternative spelling of `greyscale`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Default |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| [grayscale] | <code>Boolean</code> | <code>true</code> |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## pipelineColourspace
|
||||||
|
> pipelineColourspace([colourspace]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Set the pipeline colourspace.
|
||||||
|
|
||||||
|
The input image will be converted to the provided colourspace at the start of the pipeline.
|
||||||
|
All operations will use this colourspace before converting to the output colourspace,
|
||||||
|
as defined by [toColourspace](#tocolourspace).
|
||||||
|
|
||||||
|
This feature is experimental and has not yet been fully-tested with all operations.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
**Since**: 0.29.0
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| [colourspace] | <code>string</code> | pipeline colourspace e.g. `rgb16`, `scrgb`, `lab`, `grey16` [...](https://github.com/libvips/libvips/blob/41cff4e9d0838498487a00623462204eb10ee5b8/libvips/iofuncs/enumtypes.c#L774) |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Run pipeline in 16 bits per channel RGB while converting final result to 8 bits per channel sRGB.
|
||||||
|
await sharp(input)
|
||||||
|
.pipelineColourspace('rgb16')
|
||||||
|
.toColourspace('srgb')
|
||||||
|
.toFile('16bpc-pipeline-to-8bpc-output.png')
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## pipelineColorspace
|
||||||
|
> pipelineColorspace([colorspace]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Alternative spelling of `pipelineColourspace`.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| [colorspace] | <code>string</code> | pipeline colorspace. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## toColourspace
|
||||||
|
> toColourspace([colourspace]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Set the output colourspace.
|
||||||
|
By default output image will be web-friendly sRGB, with additional channels interpreted as alpha channels.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| [colourspace] | <code>string</code> | output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/libvips/libvips/blob/3c0bfdf74ce1dc37a6429bed47fa76f16e2cd70a/libvips/iofuncs/enumtypes.c#L777-L794) |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Output 16 bits per pixel RGB
|
||||||
|
await sharp(input)
|
||||||
|
.toColourspace('rgb16')
|
||||||
|
.toFile('16-bpp.png')
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## toColorspace
|
||||||
|
> toColorspace([colorspace]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Alternative spelling of `toColourspace`.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| [colorspace] | <code>string</code> | output colorspace. |
|
||||||
97
docs/api-composite.md
Normal file
97
docs/api-composite.md
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
## composite
|
||||||
|
> composite(images) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Composite image(s) over the processed (resized, extracted etc.) image.
|
||||||
|
|
||||||
|
The images to composite must be the same size or smaller than the processed image.
|
||||||
|
If both `top` and `left` options are provided, they take precedence over `gravity`.
|
||||||
|
|
||||||
|
Any resize, rotate or extract operations in the same processing pipeline
|
||||||
|
will always be applied to the input image before composition.
|
||||||
|
|
||||||
|
The `blend` option can be one of `clear`, `source`, `over`, `in`, `out`, `atop`,
|
||||||
|
`dest`, `dest-over`, `dest-in`, `dest-out`, `dest-atop`,
|
||||||
|
`xor`, `add`, `saturate`, `multiply`, `screen`, `overlay`, `darken`, `lighten`,
|
||||||
|
`colour-dodge`, `color-dodge`, `colour-burn`,`color-burn`,
|
||||||
|
`hard-light`, `soft-light`, `difference`, `exclusion`.
|
||||||
|
|
||||||
|
More information about blend modes can be found at
|
||||||
|
https://www.libvips.org/API/current/libvips-conversion.html#VipsBlendMode
|
||||||
|
and https://www.cairographics.org/operators/
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
**Since**: 0.22.0
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| images | <code>Array.<Object></code> | | Ordered list of images to composite |
|
||||||
|
| [images[].input] | <code>Buffer</code> \| <code>String</code> | | Buffer containing image data, String containing the path to an image file, or Create object (see below) |
|
||||||
|
| [images[].input.create] | <code>Object</code> | | describes a blank overlay to be created. |
|
||||||
|
| [images[].input.create.width] | <code>Number</code> | | |
|
||||||
|
| [images[].input.create.height] | <code>Number</code> | | |
|
||||||
|
| [images[].input.create.channels] | <code>Number</code> | | 3-4 |
|
||||||
|
| [images[].input.create.background] | <code>String</code> \| <code>Object</code> | | parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. |
|
||||||
|
| [images[].input.text] | <code>Object</code> | | describes a new text image to be created. |
|
||||||
|
| [images[].input.text.text] | <code>string</code> | | text to render as a UTF-8 string. It can contain Pango markup, for example `<i>Le</i>Monde`. |
|
||||||
|
| [images[].input.text.font] | <code>string</code> | | font name to render with. |
|
||||||
|
| [images[].input.text.fontfile] | <code>string</code> | | absolute filesystem path to a font file that can be used by `font`. |
|
||||||
|
| [images[].input.text.width] | <code>number</code> | <code>0</code> | integral number of pixels to word-wrap at. Lines of text wider than this will be broken at word boundaries. |
|
||||||
|
| [images[].input.text.height] | <code>number</code> | <code>0</code> | integral number of pixels high. When defined, `dpi` will be ignored and the text will automatically fit the pixel resolution defined by `width` and `height`. Will be ignored if `width` is not specified or set to 0. |
|
||||||
|
| [images[].input.text.align] | <code>string</code> | <code>"'left'"</code> | text alignment (`'left'`, `'centre'`, `'center'`, `'right'`). |
|
||||||
|
| [images[].input.text.justify] | <code>boolean</code> | <code>false</code> | set this to true to apply justification to the text. |
|
||||||
|
| [images[].input.text.dpi] | <code>number</code> | <code>72</code> | the resolution (size) at which to render the text. Does not take effect if `height` is specified. |
|
||||||
|
| [images[].input.text.rgba] | <code>boolean</code> | <code>false</code> | set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for Pango markup features like `<span foreground="red">Red!</span>`. |
|
||||||
|
| [images[].input.text.spacing] | <code>number</code> | <code>0</code> | text line height in points. Will use the font line height if none is specified. |
|
||||||
|
| [images[].blend] | <code>String</code> | <code>'over'</code> | how to blend this image with the image below. |
|
||||||
|
| [images[].gravity] | <code>String</code> | <code>'centre'</code> | gravity at which to place the overlay. |
|
||||||
|
| [images[].top] | <code>Number</code> | | the pixel offset from the top edge. |
|
||||||
|
| [images[].left] | <code>Number</code> | | the pixel offset from the left edge. |
|
||||||
|
| [images[].tile] | <code>Boolean</code> | <code>false</code> | set to true to repeat the overlay image across the entire image with the given `gravity`. |
|
||||||
|
| [images[].premultiplied] | <code>Boolean</code> | <code>false</code> | set to true to avoid premultiplying the image below. Equivalent to the `--premultiplied` vips option. |
|
||||||
|
| [images[].density] | <code>Number</code> | <code>72</code> | number representing the DPI for vector overlay image. |
|
||||||
|
| [images[].raw] | <code>Object</code> | | describes overlay when using raw pixel data. |
|
||||||
|
| [images[].raw.width] | <code>Number</code> | | |
|
||||||
|
| [images[].raw.height] | <code>Number</code> | | |
|
||||||
|
| [images[].raw.channels] | <code>Number</code> | | |
|
||||||
|
| [images[].animated] | <code>boolean</code> | <code>false</code> | Set to `true` to read all frames/pages of an animated image. |
|
||||||
|
| [images[].failOn] | <code>string</code> | <code>"'warning'"</code> | @see [constructor parameters](/api-constructor#parameters) |
|
||||||
|
| [images[].limitInputPixels] | <code>number</code> \| <code>boolean</code> | <code>268402689</code> | @see [constructor parameters](/api-constructor#parameters) |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
await sharp(background)
|
||||||
|
.composite([
|
||||||
|
{ input: layer1, gravity: 'northwest' },
|
||||||
|
{ input: layer2, gravity: 'southeast' },
|
||||||
|
])
|
||||||
|
.toFile('combined.png');
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const output = await sharp('input.gif', { animated: true })
|
||||||
|
.composite([
|
||||||
|
{ input: 'overlay.png', tile: true, blend: 'saturate' }
|
||||||
|
])
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
sharp('input.png')
|
||||||
|
.rotate(180)
|
||||||
|
.resize(300)
|
||||||
|
.flatten( { background: '#ff6600' } )
|
||||||
|
.composite([{ input: 'overlay.png', gravity: 'southeast' }])
|
||||||
|
.sharpen()
|
||||||
|
.withMetadata()
|
||||||
|
.webp( { quality: 90 } )
|
||||||
|
.toBuffer()
|
||||||
|
.then(function(outputBuffer) {
|
||||||
|
// outputBuffer contains upside down, 300px wide, alpha channel flattened
|
||||||
|
// onto orange background, composited with overlay.png with SE gravity,
|
||||||
|
// sharpened, with metadata, 90% quality WebP image data. Phew!
|
||||||
|
});
|
||||||
|
```
|
||||||
228
docs/api-constructor.md
Normal file
228
docs/api-constructor.md
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
## Sharp
|
||||||
|
> Sharp
|
||||||
|
|
||||||
|
|
||||||
|
**Emits**: <code>Sharp#event:info</code>, <code>Sharp#event:warning</code>
|
||||||
|
<a name="new_Sharp_new"></a>
|
||||||
|
|
||||||
|
### new
|
||||||
|
> new Sharp([input], [options])
|
||||||
|
|
||||||
|
Constructor factory to create an instance of `sharp`, to which further methods are chained.
|
||||||
|
|
||||||
|
JPEG, PNG, WebP, GIF, AVIF or TIFF format image data can be streamed out from this object.
|
||||||
|
When using Stream based output, derived attributes are available from the `info` event.
|
||||||
|
|
||||||
|
Non-critical problems encountered during processing are emitted as `warning` events.
|
||||||
|
|
||||||
|
Implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [input] | <code>Buffer</code> \| <code>ArrayBuffer</code> \| <code>Uint8Array</code> \| <code>Uint8ClampedArray</code> \| <code>Int8Array</code> \| <code>Uint16Array</code> \| <code>Int16Array</code> \| <code>Uint32Array</code> \| <code>Int32Array</code> \| <code>Float32Array</code> \| <code>Float64Array</code> \| <code>string</code> | | if present, can be a Buffer / ArrayBuffer / Uint8Array / Uint8ClampedArray containing JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image data, or a TypedArray containing raw pixel image data, or a String containing the filesystem path to an JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image file. JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present. |
|
||||||
|
| [options] | <code>Object</code> | | if present, is an Object with optional attributes. |
|
||||||
|
| [options.failOn] | <code>string</code> | <code>"'warning'"</code> | when to abort processing of invalid pixel data, one of (in order of sensitivity): 'none' (least), 'truncated', 'error' or 'warning' (most), higher levels imply lower levels, invalid metadata will always abort. |
|
||||||
|
| [options.limitInputPixels] | <code>number</code> \| <code>boolean</code> | <code>268402689</code> | Do not process input images where the number of pixels (width x height) exceeds this limit. Assumes image dimensions contained in the input metadata can be trusted. An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF). |
|
||||||
|
| [options.unlimited] | <code>boolean</code> | <code>false</code> | Set this to `true` to remove safety features that help prevent memory exhaustion (JPEG, PNG, SVG, HEIF). |
|
||||||
|
| [options.sequentialRead] | <code>boolean</code> | <code>true</code> | Set this to `false` to use random access rather than sequential read. Some operations will do this automatically. |
|
||||||
|
| [options.density] | <code>number</code> | <code>72</code> | number representing the DPI for vector images in the range 1 to 100000. |
|
||||||
|
| [options.ignoreIcc] | <code>number</code> | <code>false</code> | should the embedded ICC profile, if any, be ignored. |
|
||||||
|
| [options.pages] | <code>number</code> | <code>1</code> | Number of pages to extract for multi-page input (GIF, WebP, TIFF), use -1 for all pages. |
|
||||||
|
| [options.page] | <code>number</code> | <code>0</code> | Page number to start extracting from for multi-page input (GIF, WebP, TIFF), zero based. |
|
||||||
|
| [options.subifd] | <code>number</code> | <code>-1</code> | subIFD (Sub Image File Directory) to extract for OME-TIFF, defaults to main image. |
|
||||||
|
| [options.level] | <code>number</code> | <code>0</code> | level to extract from a multi-level input (OpenSlide), zero based. |
|
||||||
|
| [options.animated] | <code>boolean</code> | <code>false</code> | Set to `true` to read all frames/pages of an animated image (GIF, WebP, TIFF), equivalent of setting `pages` to `-1`. |
|
||||||
|
| [options.raw] | <code>Object</code> | | describes raw pixel input image data. See `raw()` for pixel ordering. |
|
||||||
|
| [options.raw.width] | <code>number</code> | | integral number of pixels wide. |
|
||||||
|
| [options.raw.height] | <code>number</code> | | integral number of pixels high. |
|
||||||
|
| [options.raw.channels] | <code>number</code> | | integral number of channels, between 1 and 4. |
|
||||||
|
| [options.raw.premultiplied] | <code>boolean</code> | | specifies that the raw input has already been premultiplied, set to `true` to avoid sharp premultiplying the image. (optional, default `false`) |
|
||||||
|
| [options.create] | <code>Object</code> | | describes a new image to be created. |
|
||||||
|
| [options.create.width] | <code>number</code> | | integral number of pixels wide. |
|
||||||
|
| [options.create.height] | <code>number</code> | | integral number of pixels high. |
|
||||||
|
| [options.create.channels] | <code>number</code> | | integral number of channels, either 3 (RGB) or 4 (RGBA). |
|
||||||
|
| [options.create.background] | <code>string</code> \| <code>Object</code> | | parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. |
|
||||||
|
| [options.create.noise] | <code>Object</code> | | describes a noise to be created. |
|
||||||
|
| [options.create.noise.type] | <code>string</code> | | type of generated noise, currently only `gaussian` is supported. |
|
||||||
|
| [options.create.noise.mean] | <code>number</code> | | mean of pixels in generated noise. |
|
||||||
|
| [options.create.noise.sigma] | <code>number</code> | | standard deviation of pixels in generated noise. |
|
||||||
|
| [options.text] | <code>Object</code> | | describes a new text image to be created. |
|
||||||
|
| [options.text.text] | <code>string</code> | | text to render as a UTF-8 string. It can contain Pango markup, for example `<i>Le</i>Monde`. |
|
||||||
|
| [options.text.font] | <code>string</code> | | font name to render with. |
|
||||||
|
| [options.text.fontfile] | <code>string</code> | | absolute filesystem path to a font file that can be used by `font`. |
|
||||||
|
| [options.text.width] | <code>number</code> | <code>0</code> | Integral number of pixels to word-wrap at. Lines of text wider than this will be broken at word boundaries. |
|
||||||
|
| [options.text.height] | <code>number</code> | <code>0</code> | Maximum integral number of pixels high. When defined, `dpi` will be ignored and the text will automatically fit the pixel resolution defined by `width` and `height`. Will be ignored if `width` is not specified or set to 0. |
|
||||||
|
| [options.text.align] | <code>string</code> | <code>"'left'"</code> | Alignment style for multi-line text (`'left'`, `'centre'`, `'center'`, `'right'`). |
|
||||||
|
| [options.text.justify] | <code>boolean</code> | <code>false</code> | set this to true to apply justification to the text. |
|
||||||
|
| [options.text.dpi] | <code>number</code> | <code>72</code> | the resolution (size) at which to render the text. Does not take effect if `height` is specified. |
|
||||||
|
| [options.text.rgba] | <code>boolean</code> | <code>false</code> | set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for pango markup features like `<span foreground="red">Red!</span>`. |
|
||||||
|
| [options.text.spacing] | <code>number</code> | <code>0</code> | text line height in points. Will use the font line height if none is specified. |
|
||||||
|
| [options.text.wrap] | <code>string</code> | <code>"'word'"</code> | word wrapping style when width is provided, one of: 'word', 'char', 'charWord' (prefer char, fallback to word) or 'none'. |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
sharp('input.jpg')
|
||||||
|
.resize(300, 200)
|
||||||
|
.toFile('output.jpg', function(err) {
|
||||||
|
// output.jpg is a 300 pixels wide and 200 pixels high image
|
||||||
|
// containing a scaled and cropped version of input.jpg
|
||||||
|
});
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Read image data from readableStream,
|
||||||
|
// resize to 300 pixels wide,
|
||||||
|
// emit an 'info' event with calculated dimensions
|
||||||
|
// and finally write image data to writableStream
|
||||||
|
var transformer = sharp()
|
||||||
|
.resize(300)
|
||||||
|
.on('info', function(info) {
|
||||||
|
console.log('Image height is ' + info.height);
|
||||||
|
});
|
||||||
|
readableStream.pipe(transformer).pipe(writableStream);
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Create a blank 300x200 PNG image of semi-translucent red pixels
|
||||||
|
sharp({
|
||||||
|
create: {
|
||||||
|
width: 300,
|
||||||
|
height: 200,
|
||||||
|
channels: 4,
|
||||||
|
background: { r: 255, g: 0, b: 0, alpha: 0.5 }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.png()
|
||||||
|
.toBuffer()
|
||||||
|
.then( ... );
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Convert an animated GIF to an animated WebP
|
||||||
|
await sharp('in.gif', { animated: true }).toFile('out.webp');
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Read a raw array of pixels and save it to a png
|
||||||
|
const input = Uint8Array.from([255, 255, 255, 0, 0, 0]); // or Uint8ClampedArray
|
||||||
|
const image = sharp(input, {
|
||||||
|
// because the input does not contain its dimensions or how many channels it has
|
||||||
|
// we need to specify it in the constructor options
|
||||||
|
raw: {
|
||||||
|
width: 2,
|
||||||
|
height: 1,
|
||||||
|
channels: 3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await image.toFile('my-two-pixels.png');
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Generate RGB Gaussian noise
|
||||||
|
await sharp({
|
||||||
|
create: {
|
||||||
|
width: 300,
|
||||||
|
height: 200,
|
||||||
|
channels: 3,
|
||||||
|
noise: {
|
||||||
|
type: 'gaussian',
|
||||||
|
mean: 128,
|
||||||
|
sigma: 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).toFile('noise.png');
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Generate an image from text
|
||||||
|
await sharp({
|
||||||
|
text: {
|
||||||
|
text: 'Hello, world!',
|
||||||
|
width: 400, // max width
|
||||||
|
height: 300 // max height
|
||||||
|
}
|
||||||
|
}).toFile('text_bw.png');
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Generate an rgba image from text using pango markup and font
|
||||||
|
await sharp({
|
||||||
|
text: {
|
||||||
|
text: '<span foreground="red">Red!</span><span background="cyan">blue</span>',
|
||||||
|
font: 'sans',
|
||||||
|
rgba: true,
|
||||||
|
dpi: 300
|
||||||
|
}
|
||||||
|
}).toFile('text_rgba.png');
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## clone
|
||||||
|
> clone() ⇒ [<code>Sharp</code>](#Sharp)
|
||||||
|
|
||||||
|
Take a "snapshot" of the Sharp instance, returning a new instance.
|
||||||
|
Cloned instances inherit the input of their parent instance.
|
||||||
|
This allows multiple output Streams and therefore multiple processing pipelines to share a single input Stream.
|
||||||
|
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const pipeline = sharp().rotate();
|
||||||
|
pipeline.clone().resize(800, 600).pipe(firstWritableStream);
|
||||||
|
pipeline.clone().extract({ left: 20, top: 20, width: 100, height: 100 }).pipe(secondWritableStream);
|
||||||
|
readableStream.pipe(pipeline);
|
||||||
|
// firstWritableStream receives auto-rotated, resized readableStream
|
||||||
|
// secondWritableStream receives auto-rotated, extracted region of readableStream
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Create a pipeline that will download an image, resize it and format it to different files
|
||||||
|
// Using Promises to know when the pipeline is complete
|
||||||
|
const fs = require("fs");
|
||||||
|
const got = require("got");
|
||||||
|
const sharpStream = sharp({ failOn: 'none' });
|
||||||
|
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
promises.push(
|
||||||
|
sharpStream
|
||||||
|
.clone()
|
||||||
|
.jpeg({ quality: 100 })
|
||||||
|
.toFile("originalFile.jpg")
|
||||||
|
);
|
||||||
|
|
||||||
|
promises.push(
|
||||||
|
sharpStream
|
||||||
|
.clone()
|
||||||
|
.resize({ width: 500 })
|
||||||
|
.jpeg({ quality: 80 })
|
||||||
|
.toFile("optimized-500.jpg")
|
||||||
|
);
|
||||||
|
|
||||||
|
promises.push(
|
||||||
|
sharpStream
|
||||||
|
.clone()
|
||||||
|
.resize({ width: 500 })
|
||||||
|
.webp({ quality: 80 })
|
||||||
|
.toFile("optimized-500.webp")
|
||||||
|
);
|
||||||
|
|
||||||
|
// https://github.com/sindresorhus/got/blob/main/documentation/3-streams.md
|
||||||
|
got.stream("https://www.example.com/some-file.jpg").pipe(sharpStream);
|
||||||
|
|
||||||
|
Promise.all(promises)
|
||||||
|
.then(res => { console.log("Done!", res); })
|
||||||
|
.catch(err => {
|
||||||
|
console.error("Error processing files, let's clean it up", err);
|
||||||
|
try {
|
||||||
|
fs.unlinkSync("originalFile.jpg");
|
||||||
|
fs.unlinkSync("optimized-500.jpg");
|
||||||
|
fs.unlinkSync("optimized-500.webp");
|
||||||
|
} catch (e) {}
|
||||||
|
});
|
||||||
|
```
|
||||||
137
docs/api-input.md
Normal file
137
docs/api-input.md
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
## metadata
|
||||||
|
> metadata([callback]) ⇒ <code>Promise.<Object></code> \| <code>Sharp</code>
|
||||||
|
|
||||||
|
Fast access to (uncached) image metadata without decoding any compressed pixel data.
|
||||||
|
|
||||||
|
This is read from the header of the input image.
|
||||||
|
It does not take into consideration any operations to be applied to the output image,
|
||||||
|
such as resize or rotate.
|
||||||
|
|
||||||
|
Dimensions in the response will respect the `page` and `pages` properties of the
|
||||||
|
[constructor parameters](/api-constructor#parameters).
|
||||||
|
|
||||||
|
A `Promise` is returned when `callback` is not provided.
|
||||||
|
|
||||||
|
- `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
|
||||||
|
- `size`: Total size of image in bytes, for Stream and Buffer input only
|
||||||
|
- `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below)
|
||||||
|
- `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below)
|
||||||
|
- `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://www.libvips.org/API/current/VipsImage.html#VipsInterpretation)
|
||||||
|
- `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
|
||||||
|
- `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://www.libvips.org/API/current/VipsImage.html#VipsBandFormat)
|
||||||
|
- `density`: Number of pixels per inch (DPI), if present
|
||||||
|
- `chromaSubsampling`: String containing JPEG chroma subsampling, `4:2:0` or `4:4:4` for RGB, `4:2:0:4` or `4:4:4:4` for CMYK
|
||||||
|
- `isProgressive`: Boolean indicating whether the image is interlaced using a progressive scan
|
||||||
|
- `pages`: Number of pages/frames contained within the image, with support for TIFF, HEIF, PDF, animated GIF and animated WebP
|
||||||
|
- `pageHeight`: Number of pixels high each page in a multi-page image will be.
|
||||||
|
- `paletteBitDepth`: Bit depth of palette-based image (GIF, PNG).
|
||||||
|
- `loop`: Number of times to loop an animated image, zero refers to a continuous loop.
|
||||||
|
- `delay`: Delay in ms between each page in an animated image, provided as an array of integers.
|
||||||
|
- `pagePrimary`: Number of the primary page in a HEIF image
|
||||||
|
- `levels`: Details of each level in a multi-level image provided as an array of objects, requires libvips compiled with support for OpenSlide
|
||||||
|
- `subifds`: Number of Sub Image File Directories in an OME-TIFF image
|
||||||
|
- `background`: Default background colour, if present, for PNG (bKGD) and GIF images, either an RGB Object or a single greyscale value
|
||||||
|
- `compression`: The encoder used to compress an HEIF file, `av1` (AVIF) or `hevc` (HEIC)
|
||||||
|
- `resolutionUnit`: The unit of resolution (density), either `inch` or `cm`, if present
|
||||||
|
- `hasProfile`: Boolean indicating the presence of an embedded ICC profile
|
||||||
|
- `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
|
||||||
|
- `orientation`: Number value of the EXIF Orientation header, if present
|
||||||
|
- `exif`: Buffer containing raw EXIF data, if present
|
||||||
|
- `icc`: Buffer containing raw [ICC](https://www.npmjs.com/package/icc) profile data, if present
|
||||||
|
- `iptc`: Buffer containing raw IPTC data, if present
|
||||||
|
- `xmp`: Buffer containing raw XMP data, if present
|
||||||
|
- `tifftagPhotoshop`: Buffer containing raw TIFFTAG_PHOTOSHOP data, if present
|
||||||
|
- `formatMagick`: String containing format for images loaded via *magick
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| [callback] | <code>function</code> | called with the arguments `(err, metadata)` |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const metadata = await sharp(input).metadata();
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const image = sharp(inputJpg);
|
||||||
|
image
|
||||||
|
.metadata()
|
||||||
|
.then(function(metadata) {
|
||||||
|
return image
|
||||||
|
.resize(Math.round(metadata.width / 2))
|
||||||
|
.webp()
|
||||||
|
.toBuffer();
|
||||||
|
})
|
||||||
|
.then(function(data) {
|
||||||
|
// data contains a WebP image half the width and height of the original JPEG
|
||||||
|
});
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Based on EXIF rotation metadata, get the right-side-up width and height:
|
||||||
|
|
||||||
|
const size = getNormalSize(await sharp(input).metadata());
|
||||||
|
|
||||||
|
function getNormalSize({ width, height, orientation }) {
|
||||||
|
return (orientation || 0) >= 5
|
||||||
|
? { width: height, height: width }
|
||||||
|
: { width, height };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## stats
|
||||||
|
> stats([callback]) ⇒ <code>Promise.<Object></code>
|
||||||
|
|
||||||
|
Access to pixel-derived image statistics for every channel in the image.
|
||||||
|
A `Promise` is returned when `callback` is not provided.
|
||||||
|
|
||||||
|
- `channels`: Array of channel statistics for each channel in the image. Each channel statistic contains
|
||||||
|
- `min` (minimum value in the channel)
|
||||||
|
- `max` (maximum value in the channel)
|
||||||
|
- `sum` (sum of all values in a channel)
|
||||||
|
- `squaresSum` (sum of squared values in a channel)
|
||||||
|
- `mean` (mean of the values in a channel)
|
||||||
|
- `stdev` (standard deviation for the values in a channel)
|
||||||
|
- `minX` (x-coordinate of one of the pixel where the minimum lies)
|
||||||
|
- `minY` (y-coordinate of one of the pixel where the minimum lies)
|
||||||
|
- `maxX` (x-coordinate of one of the pixel where the maximum lies)
|
||||||
|
- `maxY` (y-coordinate of one of the pixel where the maximum lies)
|
||||||
|
- `isOpaque`: Is the image fully opaque? Will be `true` if the image has no alpha channel or if every pixel is fully opaque.
|
||||||
|
- `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any.
|
||||||
|
- `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any.
|
||||||
|
- `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram.
|
||||||
|
|
||||||
|
**Note**: Statistics are derived from the original input image. Any operations performed on the image must first be
|
||||||
|
written to a buffer in order to run `stats` on the result (see third example).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| [callback] | <code>function</code> | called with the arguments `(err, stats)` |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const image = sharp(inputJpg);
|
||||||
|
image
|
||||||
|
.stats()
|
||||||
|
.then(function(stats) {
|
||||||
|
// stats contains the channel-wise statistics array and the isOpaque value
|
||||||
|
});
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const { entropy, sharpness, dominant } = await sharp(input).stats();
|
||||||
|
const { r, g, b } = dominant;
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const image = sharp(input);
|
||||||
|
// store intermediate result
|
||||||
|
const part = await image.extract(region).toBuffer();
|
||||||
|
// create new instance to obtain statistics of extracted region
|
||||||
|
const stats = await sharp(part).stats();
|
||||||
|
```
|
||||||
654
docs/api-operation.md
Normal file
654
docs/api-operation.md
Normal file
@@ -0,0 +1,654 @@
|
|||||||
|
## rotate
|
||||||
|
> rotate([angle], [options]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Rotate the output image by either an explicit angle
|
||||||
|
or auto-orient based on the EXIF `Orientation` tag.
|
||||||
|
|
||||||
|
If an angle is provided, it is converted to a valid positive degree rotation.
|
||||||
|
For example, `-450` will produce a 270 degree rotation.
|
||||||
|
|
||||||
|
When rotating by an angle other than a multiple of 90,
|
||||||
|
the background colour can be provided with the `background` option.
|
||||||
|
|
||||||
|
If no angle is provided, it is determined from the EXIF data.
|
||||||
|
Mirroring is supported and may infer the use of a flip operation.
|
||||||
|
|
||||||
|
The use of `rotate` without an angle will remove the EXIF `Orientation` tag, if any.
|
||||||
|
|
||||||
|
Only one rotation can occur per pipeline.
|
||||||
|
Previous calls to `rotate` in the same pipeline will be ignored.
|
||||||
|
|
||||||
|
Method order is important when rotating, resizing and/or extracting regions,
|
||||||
|
for example `.rotate(x).extract(y)` will produce a different result to `.extract(y).rotate(x)`.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [angle] | <code>number</code> | <code>auto</code> | angle of rotation. |
|
||||||
|
| [options] | <code>Object</code> | | if present, is an Object with optional attributes. |
|
||||||
|
| [options.background] | <code>string</code> \| <code>Object</code> | <code>"\"#000000\""</code> | parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const pipeline = sharp()
|
||||||
|
.rotate()
|
||||||
|
.resize(null, 200)
|
||||||
|
.toBuffer(function (err, outputBuffer, info) {
|
||||||
|
// outputBuffer contains 200px high JPEG image data,
|
||||||
|
// auto-rotated using EXIF Orientation tag
|
||||||
|
// info.width and info.height contain the dimensions of the resized image
|
||||||
|
});
|
||||||
|
readableStream.pipe(pipeline);
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const rotateThenResize = await sharp(input)
|
||||||
|
.rotate(90)
|
||||||
|
.resize({ width: 16, height: 8, fit: 'fill' })
|
||||||
|
.toBuffer();
|
||||||
|
const resizeThenRotate = await sharp(input)
|
||||||
|
.resize({ width: 16, height: 8, fit: 'fill' })
|
||||||
|
.rotate(90)
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## flip
|
||||||
|
> flip([flip]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Mirror the image vertically (up-down) about the x-axis.
|
||||||
|
This always occurs before rotation, if any.
|
||||||
|
|
||||||
|
This operation does not work correctly with multi-page images.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Default |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| [flip] | <code>Boolean</code> | <code>true</code> |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const output = await sharp(input).flip().toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## flop
|
||||||
|
> flop([flop]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Mirror the image horizontally (left-right) about the y-axis.
|
||||||
|
This always occurs before rotation, if any.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Default |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| [flop] | <code>Boolean</code> | <code>true</code> |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const output = await sharp(input).flop().toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## affine
|
||||||
|
> affine(matrix, [options]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Perform an affine transform on an image. This operation will always occur after resizing, extraction and rotation, if any.
|
||||||
|
|
||||||
|
You must provide an array of length 4 or a 2x2 affine transformation matrix.
|
||||||
|
By default, new pixels are filled with a black background. You can provide a background color with the `background` option.
|
||||||
|
A particular interpolator may also be specified. Set the `interpolator` option to an attribute of the `sharp.interpolators` Object e.g. `sharp.interpolators.nohalo`.
|
||||||
|
|
||||||
|
In the case of a 2x2 matrix, the transform is:
|
||||||
|
- X = `matrix[0, 0]` \* (x + `idx`) + `matrix[0, 1]` \* (y + `idy`) + `odx`
|
||||||
|
- Y = `matrix[1, 0]` \* (x + `idx`) + `matrix[1, 1]` \* (y + `idy`) + `ody`
|
||||||
|
|
||||||
|
where:
|
||||||
|
- x and y are the coordinates in input image.
|
||||||
|
- X and Y are the coordinates in output image.
|
||||||
|
- (0,0) is the upper left corner.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
**Since**: 0.27.0
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| matrix | <code>Array.<Array.<number>></code> \| <code>Array.<number></code> | | affine transformation matrix |
|
||||||
|
| [options] | <code>Object</code> | | if present, is an Object with optional attributes. |
|
||||||
|
| [options.background] | <code>String</code> \| <code>Object</code> | <code>"#000000"</code> | parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. |
|
||||||
|
| [options.idx] | <code>Number</code> | <code>0</code> | input horizontal offset |
|
||||||
|
| [options.idy] | <code>Number</code> | <code>0</code> | input vertical offset |
|
||||||
|
| [options.odx] | <code>Number</code> | <code>0</code> | output horizontal offset |
|
||||||
|
| [options.ody] | <code>Number</code> | <code>0</code> | output vertical offset |
|
||||||
|
| [options.interpolator] | <code>String</code> | <code>sharp.interpolators.bicubic</code> | interpolator |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const pipeline = sharp()
|
||||||
|
.affine([[1, 0.3], [0.1, 0.7]], {
|
||||||
|
background: 'white',
|
||||||
|
interpolator: sharp.interpolators.nohalo
|
||||||
|
})
|
||||||
|
.toBuffer((err, outputBuffer, info) => {
|
||||||
|
// outputBuffer contains the transformed image
|
||||||
|
// info.width and info.height contain the new dimensions
|
||||||
|
});
|
||||||
|
|
||||||
|
inputStream
|
||||||
|
.pipe(pipeline);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## sharpen
|
||||||
|
> sharpen([options], [flat], [jagged]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Sharpen the image.
|
||||||
|
|
||||||
|
When used without parameters, performs a fast, mild sharpen of the output image.
|
||||||
|
|
||||||
|
When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space.
|
||||||
|
Fine-grained control over the level of sharpening in "flat" (m1) and "jagged" (m2) areas is available.
|
||||||
|
|
||||||
|
See [libvips sharpen](https://www.libvips.org/API/current/libvips-convolution.html#vips-sharpen) operation.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [options] | <code>Object</code> \| <code>number</code> | | if present, is an Object with attributes |
|
||||||
|
| [options.sigma] | <code>number</code> | | the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`, between 0.000001 and 10 |
|
||||||
|
| [options.m1] | <code>number</code> | <code>1.0</code> | the level of sharpening to apply to "flat" areas, between 0 and 1000000 |
|
||||||
|
| [options.m2] | <code>number</code> | <code>2.0</code> | the level of sharpening to apply to "jagged" areas, between 0 and 1000000 |
|
||||||
|
| [options.x1] | <code>number</code> | <code>2.0</code> | threshold between "flat" and "jagged", between 0 and 1000000 |
|
||||||
|
| [options.y2] | <code>number</code> | <code>10.0</code> | maximum amount of brightening, between 0 and 1000000 |
|
||||||
|
| [options.y3] | <code>number</code> | <code>20.0</code> | maximum amount of darkening, between 0 and 1000000 |
|
||||||
|
| [flat] | <code>number</code> | | (deprecated) see `options.m1`. |
|
||||||
|
| [jagged] | <code>number</code> | | (deprecated) see `options.m2`. |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const data = await sharp(input).sharpen().toBuffer();
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const data = await sharp(input).sharpen({ sigma: 2 }).toBuffer();
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const data = await sharp(input)
|
||||||
|
.sharpen({
|
||||||
|
sigma: 2,
|
||||||
|
m1: 0,
|
||||||
|
m2: 3,
|
||||||
|
x1: 3,
|
||||||
|
y2: 15,
|
||||||
|
y3: 15,
|
||||||
|
})
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## median
|
||||||
|
> median([size]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Apply median filter.
|
||||||
|
When used without parameters the default window is 3x3.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [size] | <code>number</code> | <code>3</code> | square mask size: size x size |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const output = await sharp(input).median().toBuffer();
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const output = await sharp(input).median(5).toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## blur
|
||||||
|
> blur([sigma]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Blur the image.
|
||||||
|
|
||||||
|
When used without parameters, performs a fast 3x3 box blur (equivalent to a box linear filter).
|
||||||
|
|
||||||
|
When a `sigma` is provided, performs a slower, more accurate Gaussian blur.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| [sigma] | <code>number</code> | a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`. |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const boxBlurred = await sharp(input)
|
||||||
|
.blur()
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const gaussianBlurred = await sharp(input)
|
||||||
|
.blur(5)
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## flatten
|
||||||
|
> flatten([options]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Merge alpha transparency channel, if any, with a background, then remove the alpha channel.
|
||||||
|
|
||||||
|
See also [removeAlpha](/api-channel#removealpha).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [options] | <code>Object</code> | | |
|
||||||
|
| [options.background] | <code>string</code> \| <code>Object</code> | <code>"{r: 0, g: 0, b: 0}"</code> | background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black. |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
await sharp(rgbaInput)
|
||||||
|
.flatten({ background: '#F0A703' })
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## unflatten
|
||||||
|
> unflatten()
|
||||||
|
|
||||||
|
Ensure the image has an alpha channel
|
||||||
|
with all white pixel values made fully transparent.
|
||||||
|
|
||||||
|
Existing alpha channel values for non-white pixels remain unchanged.
|
||||||
|
|
||||||
|
This feature is experimental and the API may change.
|
||||||
|
|
||||||
|
|
||||||
|
**Since**: 0.32.1
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
await sharp(rgbInput)
|
||||||
|
.unflatten()
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
await sharp(rgbInput)
|
||||||
|
.threshold(128, { grayscale: false }) // converter bright pixels to white
|
||||||
|
.unflatten()
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## gamma
|
||||||
|
> gamma([gamma], [gammaOut]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Apply a gamma correction by reducing the encoding (darken) pre-resize at a factor of `1/gamma`
|
||||||
|
then increasing the encoding (brighten) post-resize at a factor of `gamma`.
|
||||||
|
This can improve the perceived brightness of a resized image in non-linear colour spaces.
|
||||||
|
JPEG and WebP input images will not take advantage of the shrink-on-load performance optimisation
|
||||||
|
when applying a gamma correction.
|
||||||
|
|
||||||
|
Supply a second argument to use a different output gamma value, otherwise the first value is used in both cases.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [gamma] | <code>number</code> | <code>2.2</code> | value between 1.0 and 3.0. |
|
||||||
|
| [gammaOut] | <code>number</code> | | value between 1.0 and 3.0. (optional, defaults to same as `gamma`) |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## negate
|
||||||
|
> negate([options]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Produce the "negative" of the image.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [options] | <code>Object</code> | | |
|
||||||
|
| [options.alpha] | <code>Boolean</code> | <code>true</code> | Whether or not to negate any alpha channel |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const output = await sharp(input)
|
||||||
|
.negate()
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const output = await sharp(input)
|
||||||
|
.negate({ alpha: false })
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## normalise
|
||||||
|
> normalise([options]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Enhance output image contrast by stretching its luminance to cover a full dynamic range.
|
||||||
|
|
||||||
|
Uses a histogram-based approach, taking a default range of 1% to 99% to reduce sensitivity to noise at the extremes.
|
||||||
|
|
||||||
|
Luminance values below the `lower` percentile will be underexposed by clipping to zero.
|
||||||
|
Luminance values above the `upper` percentile will be overexposed by clipping to the max pixel value.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [options] | <code>Object</code> | | |
|
||||||
|
| [options.lower] | <code>number</code> | <code>1</code> | Percentile below which luminance values will be underexposed. |
|
||||||
|
| [options.upper] | <code>number</code> | <code>99</code> | Percentile above which luminance values will be overexposed. |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const output = await sharp(input)
|
||||||
|
.normalise()
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const output = await sharp(input)
|
||||||
|
.normalise({ lower: 0, upper: 100 })
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## normalize
|
||||||
|
> normalize([options]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Alternative spelling of normalise.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [options] | <code>Object</code> | | |
|
||||||
|
| [options.lower] | <code>number</code> | <code>1</code> | Percentile below which luminance values will be underexposed. |
|
||||||
|
| [options.upper] | <code>number</code> | <code>99</code> | Percentile above which luminance values will be overexposed. |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const output = await sharp(input)
|
||||||
|
.normalize()
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## clahe
|
||||||
|
> clahe(options) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Perform contrast limiting adaptive histogram equalization
|
||||||
|
[CLAHE](https://en.wikipedia.org/wiki/Adaptive_histogram_equalization#Contrast_Limited_AHE).
|
||||||
|
|
||||||
|
This will, in general, enhance the clarity of the image by bringing out darker details.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
**Since**: 0.28.3
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| options | <code>Object</code> | | |
|
||||||
|
| options.width | <code>number</code> | | Integral width of the search window, in pixels. |
|
||||||
|
| options.height | <code>number</code> | | Integral height of the search window, in pixels. |
|
||||||
|
| [options.maxSlope] | <code>number</code> | <code>3</code> | Integral level of brightening, between 0 and 100, where 0 disables contrast limiting. |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const output = await sharp(input)
|
||||||
|
.clahe({
|
||||||
|
width: 3,
|
||||||
|
height: 3,
|
||||||
|
})
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## convolve
|
||||||
|
> convolve(kernel) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Convolve the image with the specified kernel.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| kernel | <code>Object</code> | | |
|
||||||
|
| kernel.width | <code>number</code> | | width of the kernel in pixels. |
|
||||||
|
| kernel.height | <code>number</code> | | height of the kernel in pixels. |
|
||||||
|
| kernel.kernel | <code>Array.<number></code> | | Array of length `width*height` containing the kernel values. |
|
||||||
|
| [kernel.scale] | <code>number</code> | <code>sum</code> | the scale of the kernel in pixels. |
|
||||||
|
| [kernel.offset] | <code>number</code> | <code>0</code> | the offset of the kernel in pixels. |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
sharp(input)
|
||||||
|
.convolve({
|
||||||
|
width: 3,
|
||||||
|
height: 3,
|
||||||
|
kernel: [-1, 0, 1, -2, 0, 2, -1, 0, 1]
|
||||||
|
})
|
||||||
|
.raw()
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
// data contains the raw pixel data representing the convolution
|
||||||
|
// of the input image with the horizontal Sobel operator
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## threshold
|
||||||
|
> threshold([threshold], [options]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Any pixel value greater than or equal to the threshold value will be set to 255, otherwise it will be set to 0.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [threshold] | <code>number</code> | <code>128</code> | a value in the range 0-255 representing the level at which the threshold will be applied. |
|
||||||
|
| [options] | <code>Object</code> | | |
|
||||||
|
| [options.greyscale] | <code>Boolean</code> | <code>true</code> | convert to single channel greyscale. |
|
||||||
|
| [options.grayscale] | <code>Boolean</code> | <code>true</code> | alternative spelling for greyscale. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## boolean
|
||||||
|
> boolean(operand, operator, [options]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Perform a bitwise boolean operation with operand image.
|
||||||
|
|
||||||
|
This operation creates an output image where each pixel is the result of
|
||||||
|
the selected bitwise boolean `operation` between the corresponding pixels of the input images.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| operand | <code>Buffer</code> \| <code>string</code> | Buffer containing image data or string containing the path to an image file. |
|
||||||
|
| operator | <code>string</code> | one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively. |
|
||||||
|
| [options] | <code>Object</code> | |
|
||||||
|
| [options.raw] | <code>Object</code> | describes operand when using raw pixel data. |
|
||||||
|
| [options.raw.width] | <code>number</code> | |
|
||||||
|
| [options.raw.height] | <code>number</code> | |
|
||||||
|
| [options.raw.channels] | <code>number</code> | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## linear
|
||||||
|
> linear([a], [b]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Apply the linear formula `a` * input + `b` to the image to adjust image levels.
|
||||||
|
|
||||||
|
When a single number is provided, it will be used for all image channels.
|
||||||
|
When an array of numbers is provided, the array length must match the number of channels.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [a] | <code>number</code> \| <code>Array.<number></code> | <code>[]</code> | multiplier |
|
||||||
|
| [b] | <code>number</code> \| <code>Array.<number></code> | <code>[]</code> | offset |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
await sharp(input)
|
||||||
|
.linear(0.5, 2)
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
await sharp(rgbInput)
|
||||||
|
.linear(
|
||||||
|
[0.25, 0.5, 0.75],
|
||||||
|
[150, 100, 50]
|
||||||
|
)
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## recomb
|
||||||
|
> recomb(inputMatrix) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Recombine the image with the specified matrix.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
**Since**: 0.21.1
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| inputMatrix | <code>Array.<Array.<number>></code> | 3x3 Recombination matrix |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
sharp(input)
|
||||||
|
.recomb([
|
||||||
|
[0.3588, 0.7044, 0.1368],
|
||||||
|
[0.2990, 0.5870, 0.1140],
|
||||||
|
[0.2392, 0.4696, 0.0912],
|
||||||
|
])
|
||||||
|
.raw()
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
// data contains the raw pixel data after applying the matrix
|
||||||
|
// With this example input, a sepia filter has been applied
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## modulate
|
||||||
|
> modulate([options]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Transforms the image using brightness, saturation, hue rotation, and lightness.
|
||||||
|
Brightness and lightness both operate on luminance, with the difference being that
|
||||||
|
brightness is multiplicative whereas lightness is additive.
|
||||||
|
|
||||||
|
|
||||||
|
**Since**: 0.22.1
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| [options] | <code>Object</code> | |
|
||||||
|
| [options.brightness] | <code>number</code> | Brightness multiplier |
|
||||||
|
| [options.saturation] | <code>number</code> | Saturation multiplier |
|
||||||
|
| [options.hue] | <code>number</code> | Degrees for hue rotation |
|
||||||
|
| [options.lightness] | <code>number</code> | Lightness addend |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// increase brightness by a factor of 2
|
||||||
|
const output = await sharp(input)
|
||||||
|
.modulate({
|
||||||
|
brightness: 2
|
||||||
|
})
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// hue-rotate by 180 degrees
|
||||||
|
const output = await sharp(input)
|
||||||
|
.modulate({
|
||||||
|
hue: 180
|
||||||
|
})
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// increase lightness by +50
|
||||||
|
const output = await sharp(input)
|
||||||
|
.modulate({
|
||||||
|
lightness: 50
|
||||||
|
})
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// decrease brightness and saturation while also hue-rotating by 90 degrees
|
||||||
|
const output = await sharp(input)
|
||||||
|
.modulate({
|
||||||
|
brightness: 0.5,
|
||||||
|
saturation: 0.5,
|
||||||
|
hue: 90,
|
||||||
|
})
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
710
docs/api-output.md
Normal file
710
docs/api-output.md
Normal file
@@ -0,0 +1,710 @@
|
|||||||
|
## toFile
|
||||||
|
> toFile(fileOut, [callback]) ⇒ <code>Promise.<Object></code>
|
||||||
|
|
||||||
|
Write output image data to a file.
|
||||||
|
|
||||||
|
If an explicit output format is not selected, it will be inferred from the extension,
|
||||||
|
with JPEG, PNG, WebP, AVIF, TIFF, GIF, DZI, and libvips' V format supported.
|
||||||
|
Note that raw pixel data is only supported for buffer output.
|
||||||
|
|
||||||
|
By default all metadata will be removed, which includes EXIF-based orientation.
|
||||||
|
See [withMetadata](#withmetadata) for control over this.
|
||||||
|
|
||||||
|
The caller is responsible for ensuring directory structures and permissions exist.
|
||||||
|
|
||||||
|
A `Promise` is returned when `callback` is not provided.
|
||||||
|
|
||||||
|
|
||||||
|
**Returns**: <code>Promise.<Object></code> - - when no callback is provided
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| fileOut | <code>string</code> | the path to write the image data to. |
|
||||||
|
| [callback] | <code>function</code> | called on completion with two arguments `(err, info)`. `info` contains the output image `format`, `size` (bytes), `width`, `height`, `channels` and `premultiplied` (indicating if premultiplication was used). When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`. When using the attention crop strategy also contains `attentionX` and `attentionY`, the focal point of the cropped region. May also contain `textAutofitDpi` (dpi the font was rendered at) if image was created from text. |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
sharp(input)
|
||||||
|
.toFile('output.png', (err, info) => { ... });
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
sharp(input)
|
||||||
|
.toFile('output.png')
|
||||||
|
.then(info => { ... })
|
||||||
|
.catch(err => { ... });
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## toBuffer
|
||||||
|
> toBuffer([options], [callback]) ⇒ <code>Promise.<Buffer></code>
|
||||||
|
|
||||||
|
Write output to a Buffer.
|
||||||
|
JPEG, PNG, WebP, AVIF, TIFF, GIF and raw pixel data output are supported.
|
||||||
|
|
||||||
|
Use [toFormat](#toformat) or one of the format-specific functions such as [jpeg](#jpeg), [png](#png) etc. to set the output format.
|
||||||
|
|
||||||
|
If no explicit format is set, the output format will match the input image, except SVG input which becomes PNG output.
|
||||||
|
|
||||||
|
By default all metadata will be removed, which includes EXIF-based orientation.
|
||||||
|
See [withMetadata](#withmetadata) for control over this.
|
||||||
|
|
||||||
|
`callback`, if present, gets three arguments `(err, data, info)` where:
|
||||||
|
- `err` is an error, if any.
|
||||||
|
- `data` is the output image data.
|
||||||
|
- `info` contains the output image `format`, `size` (bytes), `width`, `height`,
|
||||||
|
`channels` and `premultiplied` (indicating if premultiplication was used).
|
||||||
|
When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
|
||||||
|
May also contain `textAutofitDpi` (dpi the font was rendered at) if image was created from text.
|
||||||
|
|
||||||
|
A `Promise` is returned when `callback` is not provided.
|
||||||
|
|
||||||
|
|
||||||
|
**Returns**: <code>Promise.<Buffer></code> - - when no callback is provided
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| [options] | <code>Object</code> | |
|
||||||
|
| [options.resolveWithObject] | <code>boolean</code> | Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`. |
|
||||||
|
| [callback] | <code>function</code> | |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
sharp(input)
|
||||||
|
.toBuffer((err, data, info) => { ... });
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
sharp(input)
|
||||||
|
.toBuffer()
|
||||||
|
.then(data => { ... })
|
||||||
|
.catch(err => { ... });
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
sharp(input)
|
||||||
|
.png()
|
||||||
|
.toBuffer({ resolveWithObject: true })
|
||||||
|
.then(({ data, info }) => { ... })
|
||||||
|
.catch(err => { ... });
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const { data, info } = await sharp('my-image.jpg')
|
||||||
|
// output the raw pixels
|
||||||
|
.raw()
|
||||||
|
.toBuffer({ resolveWithObject: true });
|
||||||
|
|
||||||
|
// create a more type safe way to work with the raw pixel data
|
||||||
|
// this will not copy the data, instead it will change `data`s underlying ArrayBuffer
|
||||||
|
// so `data` and `pixelArray` point to the same memory location
|
||||||
|
const pixelArray = new Uint8ClampedArray(data.buffer);
|
||||||
|
|
||||||
|
// When you are done changing the pixelArray, sharp takes the `pixelArray` as an input
|
||||||
|
const { width, height, channels } = info;
|
||||||
|
await sharp(pixelArray, { raw: { width, height, channels } })
|
||||||
|
.toFile('my-changed-image.jpg');
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## withMetadata
|
||||||
|
> withMetadata([options]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
|
||||||
|
This will also convert to and add a web-friendly sRGB ICC profile if appropriate,
|
||||||
|
unless a custom output profile is provided.
|
||||||
|
|
||||||
|
The default behaviour, when `withMetadata` is not used, is to convert to the device-independent
|
||||||
|
sRGB colour space and strip all metadata, including the removal of any ICC profile.
|
||||||
|
|
||||||
|
EXIF metadata is unsupported for TIFF output.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [options] | <code>Object</code> | | |
|
||||||
|
| [options.orientation] | <code>number</code> | | value between 1 and 8, used to update the EXIF `Orientation` tag. |
|
||||||
|
| [options.icc] | <code>string</code> | <code>"'srgb'"</code> | Filesystem path to output ICC profile, relative to `process.cwd()`, defaults to built-in sRGB. |
|
||||||
|
| [options.exif] | <code>Object.<Object></code> | <code>{}</code> | Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data. |
|
||||||
|
| [options.density] | <code>number</code> | | Number of pixels per inch (DPI). |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
sharp('input.jpg')
|
||||||
|
.withMetadata()
|
||||||
|
.toFile('output-with-metadata.jpg')
|
||||||
|
.then(info => { ... });
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Set output EXIF metadata
|
||||||
|
const data = await sharp(input)
|
||||||
|
.withMetadata({
|
||||||
|
exif: {
|
||||||
|
IFD0: {
|
||||||
|
Copyright: 'The National Gallery'
|
||||||
|
},
|
||||||
|
IFD3: {
|
||||||
|
GPSLatitudeRef: 'N',
|
||||||
|
GPSLatitude: '51/1 30/1 3230/100',
|
||||||
|
GPSLongitudeRef: 'W',
|
||||||
|
GPSLongitude: '0/1 7/1 4366/100'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Set output metadata to 96 DPI
|
||||||
|
const data = await sharp(input)
|
||||||
|
.withMetadata({ density: 96 })
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## toFormat
|
||||||
|
> toFormat(format, options) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Force output to a given format.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> unsupported format or options
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| format | <code>string</code> \| <code>Object</code> | as a string or an Object with an 'id' attribute |
|
||||||
|
| options | <code>Object</code> | output options |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Convert any input to PNG output
|
||||||
|
const data = await sharp(input)
|
||||||
|
.toFormat('png')
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## jpeg
|
||||||
|
> jpeg([options]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Use these JPEG options for output image.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid options
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [options] | <code>Object</code> | | output options |
|
||||||
|
| [options.quality] | <code>number</code> | <code>80</code> | quality, integer 1-100 |
|
||||||
|
| [options.progressive] | <code>boolean</code> | <code>false</code> | use progressive (interlace) scan |
|
||||||
|
| [options.chromaSubsampling] | <code>string</code> | <code>"'4:2:0'"</code> | set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling |
|
||||||
|
| [options.optimiseCoding] | <code>boolean</code> | <code>true</code> | optimise Huffman coding tables |
|
||||||
|
| [options.optimizeCoding] | <code>boolean</code> | <code>true</code> | alternative spelling of optimiseCoding |
|
||||||
|
| [options.mozjpeg] | <code>boolean</code> | <code>false</code> | use mozjpeg defaults, equivalent to `{ trellisQuantisation: true, overshootDeringing: true, optimiseScans: true, quantisationTable: 3 }` |
|
||||||
|
| [options.trellisQuantisation] | <code>boolean</code> | <code>false</code> | apply trellis quantisation |
|
||||||
|
| [options.overshootDeringing] | <code>boolean</code> | <code>false</code> | apply overshoot deringing |
|
||||||
|
| [options.optimiseScans] | <code>boolean</code> | <code>false</code> | optimise progressive scans, forces progressive |
|
||||||
|
| [options.optimizeScans] | <code>boolean</code> | <code>false</code> | alternative spelling of optimiseScans |
|
||||||
|
| [options.quantisationTable] | <code>number</code> | <code>0</code> | quantization table to use, integer 0-8 |
|
||||||
|
| [options.quantizationTable] | <code>number</code> | <code>0</code> | alternative spelling of quantisationTable |
|
||||||
|
| [options.force] | <code>boolean</code> | <code>true</code> | force JPEG output, otherwise attempt to use input format |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Convert any input to very high quality JPEG output
|
||||||
|
const data = await sharp(input)
|
||||||
|
.jpeg({
|
||||||
|
quality: 100,
|
||||||
|
chromaSubsampling: '4:4:4'
|
||||||
|
})
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Use mozjpeg to reduce output JPEG file size (slower)
|
||||||
|
const data = await sharp(input)
|
||||||
|
.jpeg({ mozjpeg: true })
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## png
|
||||||
|
> png([options]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Use these PNG options for output image.
|
||||||
|
|
||||||
|
By default, PNG output is full colour at 8 or 16 bits per pixel.
|
||||||
|
Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel.
|
||||||
|
Set `palette` to `true` for slower, indexed PNG output.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid options
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [options] | <code>Object</code> | | |
|
||||||
|
| [options.progressive] | <code>boolean</code> | <code>false</code> | use progressive (interlace) scan |
|
||||||
|
| [options.compressionLevel] | <code>number</code> | <code>6</code> | zlib compression level, 0 (fastest, largest) to 9 (slowest, smallest) |
|
||||||
|
| [options.adaptiveFiltering] | <code>boolean</code> | <code>false</code> | use adaptive row filtering |
|
||||||
|
| [options.palette] | <code>boolean</code> | <code>false</code> | quantise to a palette-based image with alpha transparency support |
|
||||||
|
| [options.quality] | <code>number</code> | <code>100</code> | use the lowest number of colours needed to achieve given quality, sets `palette` to `true` |
|
||||||
|
| [options.effort] | <code>number</code> | <code>7</code> | CPU effort, between 1 (fastest) and 10 (slowest), sets `palette` to `true` |
|
||||||
|
| [options.colours] | <code>number</code> | <code>256</code> | maximum number of palette entries, sets `palette` to `true` |
|
||||||
|
| [options.colors] | <code>number</code> | <code>256</code> | alternative spelling of `options.colours`, sets `palette` to `true` |
|
||||||
|
| [options.dither] | <code>number</code> | <code>1.0</code> | level of Floyd-Steinberg error diffusion, sets `palette` to `true` |
|
||||||
|
| [options.force] | <code>boolean</code> | <code>true</code> | force PNG output, otherwise attempt to use input format |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Convert any input to full colour PNG output
|
||||||
|
const data = await sharp(input)
|
||||||
|
.png()
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Convert any input to indexed PNG output (slower)
|
||||||
|
const data = await sharp(input)
|
||||||
|
.png({ palette: true })
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## webp
|
||||||
|
> webp([options]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Use these WebP options for output image.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid options
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [options] | <code>Object</code> | | output options |
|
||||||
|
| [options.quality] | <code>number</code> | <code>80</code> | quality, integer 1-100 |
|
||||||
|
| [options.alphaQuality] | <code>number</code> | <code>100</code> | quality of alpha layer, integer 0-100 |
|
||||||
|
| [options.lossless] | <code>boolean</code> | <code>false</code> | use lossless compression mode |
|
||||||
|
| [options.nearLossless] | <code>boolean</code> | <code>false</code> | use near_lossless compression mode |
|
||||||
|
| [options.smartSubsample] | <code>boolean</code> | <code>false</code> | use high quality chroma subsampling |
|
||||||
|
| [options.preset] | <code>string</code> | <code>"'default'"</code> | named preset for preprocessing/filtering, one of: default, photo, picture, drawing, icon, text |
|
||||||
|
| [options.effort] | <code>number</code> | <code>4</code> | CPU effort, between 0 (fastest) and 6 (slowest) |
|
||||||
|
| [options.loop] | <code>number</code> | <code>0</code> | number of animation iterations, use 0 for infinite animation |
|
||||||
|
| [options.delay] | <code>number</code> \| <code>Array.<number></code> | | delay(s) between animation frames (in milliseconds) |
|
||||||
|
| [options.minSize] | <code>boolean</code> | <code>false</code> | prevent use of animation key frames to minimise file size (slow) |
|
||||||
|
| [options.mixed] | <code>boolean</code> | <code>false</code> | allow mixture of lossy and lossless animation frames (slow) |
|
||||||
|
| [options.force] | <code>boolean</code> | <code>true</code> | force WebP output, otherwise attempt to use input format |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Convert any input to lossless WebP output
|
||||||
|
const data = await sharp(input)
|
||||||
|
.webp({ lossless: true })
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Optimise the file size of an animated WebP
|
||||||
|
const outputWebp = await sharp(inputWebp, { animated: true })
|
||||||
|
.webp({ effort: 6 })
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## gif
|
||||||
|
> gif([options]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Use these GIF options for the output image.
|
||||||
|
|
||||||
|
The first entry in the palette is reserved for transparency.
|
||||||
|
|
||||||
|
The palette of the input image will be re-used if possible.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid options
|
||||||
|
|
||||||
|
**Since**: 0.30.0
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [options] | <code>Object</code> | | output options |
|
||||||
|
| [options.reuse] | <code>boolean</code> | <code>true</code> | re-use existing palette, otherwise generate new (slow) |
|
||||||
|
| [options.progressive] | <code>boolean</code> | <code>false</code> | use progressive (interlace) scan |
|
||||||
|
| [options.colours] | <code>number</code> | <code>256</code> | maximum number of palette entries, including transparency, between 2 and 256 |
|
||||||
|
| [options.colors] | <code>number</code> | <code>256</code> | alternative spelling of `options.colours` |
|
||||||
|
| [options.effort] | <code>number</code> | <code>7</code> | CPU effort, between 1 (fastest) and 10 (slowest) |
|
||||||
|
| [options.dither] | <code>number</code> | <code>1.0</code> | level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most) |
|
||||||
|
| [options.interFrameMaxError] | <code>number</code> | <code>0</code> | maximum inter-frame error for transparency, between 0 (lossless) and 32 |
|
||||||
|
| [options.interPaletteMaxError] | <code>number</code> | <code>3</code> | maximum inter-palette error for palette reuse, between 0 and 256 |
|
||||||
|
| [options.loop] | <code>number</code> | <code>0</code> | number of animation iterations, use 0 for infinite animation |
|
||||||
|
| [options.delay] | <code>number</code> \| <code>Array.<number></code> | | delay(s) between animation frames (in milliseconds) |
|
||||||
|
| [options.force] | <code>boolean</code> | <code>true</code> | force GIF output, otherwise attempt to use input format |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Convert PNG to GIF
|
||||||
|
await sharp(pngBuffer)
|
||||||
|
.gif()
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Convert animated WebP to animated GIF
|
||||||
|
await sharp('animated.webp', { animated: true })
|
||||||
|
.toFile('animated.gif');
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Create a 128x128, cropped, non-dithered, animated thumbnail of an animated GIF
|
||||||
|
const out = await sharp('in.gif', { animated: true })
|
||||||
|
.resize({ width: 128, height: 128 })
|
||||||
|
.gif({ dither: 0 })
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Lossy file size reduction of animated GIF
|
||||||
|
await sharp('in.gif', { animated: true })
|
||||||
|
.gif({ interFrameMaxError: 8 })
|
||||||
|
.toFile('optim.gif');
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## jp2
|
||||||
|
> jp2([options]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Use these JP2 options for output image.
|
||||||
|
|
||||||
|
Requires libvips compiled with support for OpenJPEG.
|
||||||
|
The prebuilt binaries do not include this - see
|
||||||
|
[installing a custom libvips](https://sharp.pixelplumbing.com/install#custom-libvips).
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid options
|
||||||
|
|
||||||
|
**Since**: 0.29.1
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [options] | <code>Object</code> | | output options |
|
||||||
|
| [options.quality] | <code>number</code> | <code>80</code> | quality, integer 1-100 |
|
||||||
|
| [options.lossless] | <code>boolean</code> | <code>false</code> | use lossless compression mode |
|
||||||
|
| [options.tileWidth] | <code>number</code> | <code>512</code> | horizontal tile size |
|
||||||
|
| [options.tileHeight] | <code>number</code> | <code>512</code> | vertical tile size |
|
||||||
|
| [options.chromaSubsampling] | <code>string</code> | <code>"'4:4:4'"</code> | set to '4:2:0' to use chroma subsampling |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Convert any input to lossless JP2 output
|
||||||
|
const data = await sharp(input)
|
||||||
|
.jp2({ lossless: true })
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Convert any input to very high quality JP2 output
|
||||||
|
const data = await sharp(input)
|
||||||
|
.jp2({
|
||||||
|
quality: 100,
|
||||||
|
chromaSubsampling: '4:4:4'
|
||||||
|
})
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## tiff
|
||||||
|
> tiff([options]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Use these TIFF options for output image.
|
||||||
|
|
||||||
|
The `density` can be set in pixels/inch via [withMetadata](#withmetadata)
|
||||||
|
instead of providing `xres` and `yres` in pixels/mm.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid options
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [options] | <code>Object</code> | | output options |
|
||||||
|
| [options.quality] | <code>number</code> | <code>80</code> | quality, integer 1-100 |
|
||||||
|
| [options.force] | <code>boolean</code> | <code>true</code> | force TIFF output, otherwise attempt to use input format |
|
||||||
|
| [options.compression] | <code>string</code> | <code>"'jpeg'"</code> | compression options: none, jpeg, deflate, packbits, ccittfax4, lzw, webp, zstd, jp2k |
|
||||||
|
| [options.predictor] | <code>string</code> | <code>"'horizontal'"</code> | compression predictor options: none, horizontal, float |
|
||||||
|
| [options.pyramid] | <code>boolean</code> | <code>false</code> | write an image pyramid |
|
||||||
|
| [options.tile] | <code>boolean</code> | <code>false</code> | write a tiled tiff |
|
||||||
|
| [options.tileWidth] | <code>number</code> | <code>256</code> | horizontal tile size |
|
||||||
|
| [options.tileHeight] | <code>number</code> | <code>256</code> | vertical tile size |
|
||||||
|
| [options.xres] | <code>number</code> | <code>1.0</code> | horizontal resolution in pixels/mm |
|
||||||
|
| [options.yres] | <code>number</code> | <code>1.0</code> | vertical resolution in pixels/mm |
|
||||||
|
| [options.resolutionUnit] | <code>string</code> | <code>"'inch'"</code> | resolution unit options: inch, cm |
|
||||||
|
| [options.bitdepth] | <code>number</code> | <code>8</code> | reduce bitdepth to 1, 2 or 4 bit |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Convert SVG input to LZW-compressed, 1 bit per pixel TIFF output
|
||||||
|
sharp('input.svg')
|
||||||
|
.tiff({
|
||||||
|
compression: 'lzw',
|
||||||
|
bitdepth: 1
|
||||||
|
})
|
||||||
|
.toFile('1-bpp-output.tiff')
|
||||||
|
.then(info => { ... });
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## avif
|
||||||
|
> avif([options]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Use these AVIF options for output image.
|
||||||
|
|
||||||
|
AVIF image sequences are not supported.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid options
|
||||||
|
|
||||||
|
**Since**: 0.27.0
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [options] | <code>Object</code> | | output options |
|
||||||
|
| [options.quality] | <code>number</code> | <code>50</code> | quality, integer 1-100 |
|
||||||
|
| [options.lossless] | <code>boolean</code> | <code>false</code> | use lossless compression |
|
||||||
|
| [options.effort] | <code>number</code> | <code>4</code> | CPU effort, between 0 (fastest) and 9 (slowest) |
|
||||||
|
| [options.chromaSubsampling] | <code>string</code> | <code>"'4:4:4'"</code> | set to '4:2:0' to use chroma subsampling |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const data = await sharp(input)
|
||||||
|
.avif({ effort: 2 })
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const data = await sharp(input)
|
||||||
|
.avif({ lossless: true })
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## heif
|
||||||
|
> heif(options) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Use these HEIF options for output image.
|
||||||
|
|
||||||
|
Support for patent-encumbered HEIC images using `hevc` compression requires the use of a
|
||||||
|
globally-installed libvips compiled with support for libheif, libde265 and x265.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid options
|
||||||
|
|
||||||
|
**Since**: 0.23.0
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| options | <code>Object</code> | | output options |
|
||||||
|
| options.compression | <code>string</code> | | compression format: av1, hevc |
|
||||||
|
| [options.quality] | <code>number</code> | <code>50</code> | quality, integer 1-100 |
|
||||||
|
| [options.lossless] | <code>boolean</code> | <code>false</code> | use lossless compression |
|
||||||
|
| [options.effort] | <code>number</code> | <code>4</code> | CPU effort, between 0 (fastest) and 9 (slowest) |
|
||||||
|
| [options.chromaSubsampling] | <code>string</code> | <code>"'4:4:4'"</code> | set to '4:2:0' to use chroma subsampling |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const data = await sharp(input)
|
||||||
|
.heif({ compression: 'hevc' })
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## jxl
|
||||||
|
> jxl([options]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Use these JPEG-XL (JXL) options for output image.
|
||||||
|
|
||||||
|
This feature is experimental, please do not use in production systems.
|
||||||
|
|
||||||
|
Requires libvips compiled with support for libjxl.
|
||||||
|
The prebuilt binaries do not include this - see
|
||||||
|
[installing a custom libvips](https://sharp.pixelplumbing.com/install#custom-libvips).
|
||||||
|
|
||||||
|
Image metadata (EXIF, XMP) is unsupported.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid options
|
||||||
|
|
||||||
|
**Since**: 0.31.3
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [options] | <code>Object</code> | | output options |
|
||||||
|
| [options.distance] | <code>number</code> | <code>1.0</code> | maximum encoding error, between 0 (highest quality) and 15 (lowest quality) |
|
||||||
|
| [options.quality] | <code>number</code> | | calculate `distance` based on JPEG-like quality, between 1 and 100, overrides distance if specified |
|
||||||
|
| [options.decodingTier] | <code>number</code> | <code>0</code> | target decode speed tier, between 0 (highest quality) and 4 (lowest quality) |
|
||||||
|
| [options.lossless] | <code>boolean</code> | <code>false</code> | use lossless compression |
|
||||||
|
| [options.effort] | <code>number</code> | <code>7</code> | CPU effort, between 3 (fastest) and 9 (slowest) |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## raw
|
||||||
|
> raw([options]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Force output to be raw, uncompressed pixel data.
|
||||||
|
Pixel ordering is left-to-right, top-to-bottom, without padding.
|
||||||
|
Channel ordering will be RGB or RGBA for non-greyscale colourspaces.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid options
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [options] | <code>Object</code> | | output options |
|
||||||
|
| [options.depth] | <code>string</code> | <code>"'uchar'"</code> | bit depth, one of: char, uchar (default), short, ushort, int, uint, float, complex, double, dpcomplex |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Extract raw, unsigned 8-bit RGB pixel data from JPEG input
|
||||||
|
const { data, info } = await sharp('input.jpg')
|
||||||
|
.raw()
|
||||||
|
.toBuffer({ resolveWithObject: true });
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Extract alpha channel as raw, unsigned 16-bit pixel data from PNG input
|
||||||
|
const data = await sharp('input.png')
|
||||||
|
.ensureAlpha()
|
||||||
|
.extractChannel(3)
|
||||||
|
.toColourspace('b-w')
|
||||||
|
.raw({ depth: 'ushort' })
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## tile
|
||||||
|
> tile([options]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Use tile-based deep zoom (image pyramid) output.
|
||||||
|
|
||||||
|
Set the format and options for tile images via the `toFormat`, `jpeg`, `png` or `webp` functions.
|
||||||
|
Use a `.zip` or `.szi` file extension with `toFile` to write to a compressed archive file format.
|
||||||
|
|
||||||
|
The container will be set to `zip` when the output is a Buffer or Stream, otherwise it will default to `fs`.
|
||||||
|
|
||||||
|
Requires libvips compiled with support for libgsf.
|
||||||
|
The prebuilt binaries do not include this - see
|
||||||
|
[installing a custom libvips](https://sharp.pixelplumbing.com/install#custom-libvips).
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [options] | <code>Object</code> | | |
|
||||||
|
| [options.size] | <code>number</code> | <code>256</code> | tile size in pixels, a value between 1 and 8192. |
|
||||||
|
| [options.overlap] | <code>number</code> | <code>0</code> | tile overlap in pixels, a value between 0 and 8192. |
|
||||||
|
| [options.angle] | <code>number</code> | <code>0</code> | tile angle of rotation, must be a multiple of 90. |
|
||||||
|
| [options.background] | <code>string</code> \| <code>Object</code> | <code>"{r: 255, g: 255, b: 255, alpha: 1}"</code> | background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to white without transparency. |
|
||||||
|
| [options.depth] | <code>string</code> | | how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout. |
|
||||||
|
| [options.skipBlanks] | <code>number</code> | <code>-1</code> | threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images |
|
||||||
|
| [options.container] | <code>string</code> | <code>"'fs'"</code> | tile container, with value `fs` (filesystem) or `zip` (compressed file). |
|
||||||
|
| [options.layout] | <code>string</code> | <code>"'dz'"</code> | filesystem layout, possible values are `dz`, `iiif`, `iiif3`, `zoomify` or `google`. |
|
||||||
|
| [options.centre] | <code>boolean</code> | <code>false</code> | centre image in tile. |
|
||||||
|
| [options.center] | <code>boolean</code> | <code>false</code> | alternative spelling of centre. |
|
||||||
|
| [options.id] | <code>string</code> | <code>"'https://example.com/iiif'"</code> | when `layout` is `iiif`/`iiif3`, sets the `@id`/`id` attribute of `info.json` |
|
||||||
|
| [options.basename] | <code>string</code> | | the name of the directory within the zip file when container is `zip`. |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
sharp('input.tiff')
|
||||||
|
.png()
|
||||||
|
.tile({
|
||||||
|
size: 512
|
||||||
|
})
|
||||||
|
.toFile('output.dz', function(err, info) {
|
||||||
|
// output.dzi is the Deep Zoom XML definition
|
||||||
|
// output_files contains 512x512 tiles grouped by zoom level
|
||||||
|
});
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const zipFileWithTiles = await sharp(input)
|
||||||
|
.tile({ basename: "tiles" })
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const iiififier = sharp().tile({ layout: "iiif" });
|
||||||
|
readableStream
|
||||||
|
.pipe(iiififier)
|
||||||
|
.pipe(writeableStream);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## timeout
|
||||||
|
> timeout(options) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Set a timeout for processing, in seconds.
|
||||||
|
Use a value of zero to continue processing indefinitely, the default behaviour.
|
||||||
|
|
||||||
|
The clock starts when libvips opens an input image for processing.
|
||||||
|
Time spent waiting for a libuv thread to become available is not included.
|
||||||
|
|
||||||
|
|
||||||
|
**Since**: 0.29.2
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| options | <code>Object</code> | |
|
||||||
|
| options.seconds | <code>number</code> | Number of seconds after which processing will be stopped |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Ensure processing takes no longer than 3 seconds
|
||||||
|
try {
|
||||||
|
const data = await sharp(input)
|
||||||
|
.blur(1000)
|
||||||
|
.timeout({ seconds: 3 })
|
||||||
|
.toBuffer();
|
||||||
|
} catch (err) {
|
||||||
|
if (err.message.includes('timeout')) { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
315
docs/api-resize.md
Normal file
315
docs/api-resize.md
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
## resize
|
||||||
|
> resize([width], [height], [options]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Resize image to `width`, `height` or `width x height`.
|
||||||
|
|
||||||
|
When both a `width` and `height` are provided, the possible methods by which the image should **fit** these are:
|
||||||
|
- `cover`: (default) Preserving aspect ratio, attempt to ensure the image covers both provided dimensions by cropping/clipping to fit.
|
||||||
|
- `contain`: Preserving aspect ratio, contain within both provided dimensions using "letterboxing" where necessary.
|
||||||
|
- `fill`: Ignore the aspect ratio of the input and stretch to both provided dimensions.
|
||||||
|
- `inside`: Preserving aspect ratio, resize the image to be as large as possible while ensuring its dimensions are less than or equal to both those specified.
|
||||||
|
- `outside`: Preserving aspect ratio, resize the image to be as small as possible while ensuring its dimensions are greater than or equal to both those specified.
|
||||||
|
|
||||||
|
Some of these values are based on the [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) CSS property.
|
||||||
|
|
||||||
|
<img alt="Examples of various values for the fit property when resizing" width="100%" style="aspect-ratio: 998/243" src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/api-resize-fit.svg">
|
||||||
|
|
||||||
|
When using a **fit** of `cover` or `contain`, the default **position** is `centre`. Other options are:
|
||||||
|
- `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`.
|
||||||
|
- `sharp.gravity`: `north`, `northeast`, `east`, `southeast`, `south`, `southwest`, `west`, `northwest`, `center` or `centre`.
|
||||||
|
- `sharp.strategy`: `cover` only, dynamically crop using either the `entropy` or `attention` strategy.
|
||||||
|
|
||||||
|
Some of these values are based on the [object-position](https://developer.mozilla.org/en-US/docs/Web/CSS/object-position) CSS property.
|
||||||
|
|
||||||
|
The experimental strategy-based approach resizes so one dimension is at its target length
|
||||||
|
then repeatedly ranks edge regions, discarding the edge with the lowest score based on the selected strategy.
|
||||||
|
- `entropy`: focus on the region with the highest [Shannon entropy](https://en.wikipedia.org/wiki/Entropy_%28information_theory%29).
|
||||||
|
- `attention`: focus on the region with the highest luminance frequency, colour saturation and presence of skin tones.
|
||||||
|
|
||||||
|
Possible interpolation kernels are:
|
||||||
|
- `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation).
|
||||||
|
- `cubic`: Use a [Catmull-Rom spline](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline).
|
||||||
|
- `mitchell`: Use a [Mitchell-Netravali spline](https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf).
|
||||||
|
- `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`.
|
||||||
|
- `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
|
||||||
|
|
||||||
|
Only one resize can occur per pipeline.
|
||||||
|
Previous calls to `resize` in the same pipeline will be ignored.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [width] | <code>number</code> | | How many pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height. |
|
||||||
|
| [height] | <code>number</code> | | How many pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width. |
|
||||||
|
| [options] | <code>Object</code> | | |
|
||||||
|
| [options.width] | <code>number</code> | | An alternative means of specifying `width`. If both are present this takes priority. |
|
||||||
|
| [options.height] | <code>number</code> | | An alternative means of specifying `height`. If both are present this takes priority. |
|
||||||
|
| [options.fit] | <code>String</code> | <code>'cover'</code> | How the image should be resized/cropped to fit the target dimension(s), one of `cover`, `contain`, `fill`, `inside` or `outside`. |
|
||||||
|
| [options.position] | <code>String</code> | <code>'centre'</code> | A position, gravity or strategy to use when `fit` is `cover` or `contain`. |
|
||||||
|
| [options.background] | <code>String</code> \| <code>Object</code> | <code>{r: 0, g: 0, b: 0, alpha: 1}</code> | background colour when `fit` is `contain`, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency. |
|
||||||
|
| [options.kernel] | <code>String</code> | <code>'lanczos3'</code> | The kernel to use for image reduction. Use the `fastShrinkOnLoad` option to control kernel vs shrink-on-load. |
|
||||||
|
| [options.withoutEnlargement] | <code>Boolean</code> | <code>false</code> | Do not scale up if the width *or* height are already less than the target dimensions, equivalent to GraphicsMagick's `>` geometry option. This may result in output dimensions smaller than the target dimensions. |
|
||||||
|
| [options.withoutReduction] | <code>Boolean</code> | <code>false</code> | Do not scale down if the width *or* height are already greater than the target dimensions, equivalent to GraphicsMagick's `<` geometry option. This may still result in a crop to reach the target dimensions. |
|
||||||
|
| [options.fastShrinkOnLoad] | <code>Boolean</code> | <code>true</code> | Take greater advantage of the JPEG and WebP shrink-on-load feature, which can lead to a slight moiré pattern or round-down of an auto-scaled dimension. |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
sharp(input)
|
||||||
|
.resize({ width: 100 })
|
||||||
|
.toBuffer()
|
||||||
|
.then(data => {
|
||||||
|
// 100 pixels wide, auto-scaled height
|
||||||
|
});
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
sharp(input)
|
||||||
|
.resize({ height: 100 })
|
||||||
|
.toBuffer()
|
||||||
|
.then(data => {
|
||||||
|
// 100 pixels high, auto-scaled width
|
||||||
|
});
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
sharp(input)
|
||||||
|
.resize(200, 300, {
|
||||||
|
kernel: sharp.kernel.nearest,
|
||||||
|
fit: 'contain',
|
||||||
|
position: 'right top',
|
||||||
|
background: { r: 255, g: 255, b: 255, alpha: 0.5 }
|
||||||
|
})
|
||||||
|
.toFile('output.png')
|
||||||
|
.then(() => {
|
||||||
|
// output.png is a 200 pixels wide and 300 pixels high image
|
||||||
|
// containing a nearest-neighbour scaled version
|
||||||
|
// contained within the north-east corner of a semi-transparent white canvas
|
||||||
|
});
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const transformer = sharp()
|
||||||
|
.resize({
|
||||||
|
width: 200,
|
||||||
|
height: 200,
|
||||||
|
fit: sharp.fit.cover,
|
||||||
|
position: sharp.strategy.entropy
|
||||||
|
});
|
||||||
|
// Read image data from readableStream
|
||||||
|
// Write 200px square auto-cropped image data to writableStream
|
||||||
|
readableStream
|
||||||
|
.pipe(transformer)
|
||||||
|
.pipe(writableStream);
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
sharp(input)
|
||||||
|
.resize(200, 200, {
|
||||||
|
fit: sharp.fit.inside,
|
||||||
|
withoutEnlargement: true
|
||||||
|
})
|
||||||
|
.toFormat('jpeg')
|
||||||
|
.toBuffer()
|
||||||
|
.then(function(outputBuffer) {
|
||||||
|
// outputBuffer contains JPEG image data
|
||||||
|
// no wider and no higher than 200 pixels
|
||||||
|
// and no larger than the input image
|
||||||
|
});
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
sharp(input)
|
||||||
|
.resize(200, 200, {
|
||||||
|
fit: sharp.fit.outside,
|
||||||
|
withoutReduction: true
|
||||||
|
})
|
||||||
|
.toFormat('jpeg')
|
||||||
|
.toBuffer()
|
||||||
|
.then(function(outputBuffer) {
|
||||||
|
// outputBuffer contains JPEG image data
|
||||||
|
// of at least 200 pixels wide and 200 pixels high while maintaining aspect ratio
|
||||||
|
// and no smaller than the input image
|
||||||
|
});
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const scaleByHalf = await sharp(input)
|
||||||
|
.metadata()
|
||||||
|
.then(({ width }) => sharp(input)
|
||||||
|
.resize(Math.round(width * 0.5))
|
||||||
|
.toBuffer()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## extend
|
||||||
|
> extend(extend) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Extend / pad / extrude one or more edges of the image with either
|
||||||
|
the provided background colour or pixels derived from the image.
|
||||||
|
This operation will always occur after resizing and extraction, if any.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| extend | <code>number</code> \| <code>Object</code> | | single pixel count to add to all edges or an Object with per-edge counts |
|
||||||
|
| [extend.top] | <code>number</code> | <code>0</code> | |
|
||||||
|
| [extend.left] | <code>number</code> | <code>0</code> | |
|
||||||
|
| [extend.bottom] | <code>number</code> | <code>0</code> | |
|
||||||
|
| [extend.right] | <code>number</code> | <code>0</code> | |
|
||||||
|
| [extend.extendWith] | <code>String</code> | <code>'background'</code> | populate new pixels using this method, one of: background, copy, repeat, mirror. |
|
||||||
|
| [extend.background] | <code>String</code> \| <code>Object</code> | <code>{r: 0, g: 0, b: 0, alpha: 1}</code> | background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency. |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Resize to 140 pixels wide, then add 10 transparent pixels
|
||||||
|
// to the top, left and right edges and 20 to the bottom edge
|
||||||
|
sharp(input)
|
||||||
|
.resize(140)
|
||||||
|
.extend({
|
||||||
|
top: 10,
|
||||||
|
bottom: 20,
|
||||||
|
left: 10,
|
||||||
|
right: 10,
|
||||||
|
background: { r: 0, g: 0, b: 0, alpha: 0 }
|
||||||
|
})
|
||||||
|
...
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Add a row of 10 red pixels to the bottom
|
||||||
|
sharp(input)
|
||||||
|
.extend({
|
||||||
|
bottom: 10,
|
||||||
|
background: 'red'
|
||||||
|
})
|
||||||
|
...
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Extrude image by 8 pixels to the right, mirroring existing right hand edge
|
||||||
|
sharp(input)
|
||||||
|
.extend({
|
||||||
|
right: 8,
|
||||||
|
background: 'mirror'
|
||||||
|
})
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## extract
|
||||||
|
> extract(options) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Extract/crop a region of the image.
|
||||||
|
|
||||||
|
- Use `extract` before `resize` for pre-resize extraction.
|
||||||
|
- Use `extract` after `resize` for post-resize extraction.
|
||||||
|
- Use `extract` twice and `resize` once for extract-then-resize-then-extract in a fixed operation order.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| options | <code>Object</code> | describes the region to extract using integral pixel values |
|
||||||
|
| options.left | <code>number</code> | zero-indexed offset from left edge |
|
||||||
|
| options.top | <code>number</code> | zero-indexed offset from top edge |
|
||||||
|
| options.width | <code>number</code> | width of region to extract |
|
||||||
|
| options.height | <code>number</code> | height of region to extract |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
sharp(input)
|
||||||
|
.extract({ left: left, top: top, width: width, height: height })
|
||||||
|
.toFile(output, function(err) {
|
||||||
|
// Extract a region of the input image, saving in the same format.
|
||||||
|
});
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
sharp(input)
|
||||||
|
.extract({ left: leftOffsetPre, top: topOffsetPre, width: widthPre, height: heightPre })
|
||||||
|
.resize(width, height)
|
||||||
|
.extract({ left: leftOffsetPost, top: topOffsetPost, width: widthPost, height: heightPost })
|
||||||
|
.toFile(output, function(err) {
|
||||||
|
// Extract a region, resize, then extract from the resized image
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## trim
|
||||||
|
> trim(trim) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Trim pixels from all edges that contain values similar to the given background colour, which defaults to that of the top-left pixel.
|
||||||
|
|
||||||
|
Images with an alpha channel will use the combined bounding box of alpha and non-alpha channels.
|
||||||
|
|
||||||
|
If the result of this operation would trim an image to nothing then no change is made.
|
||||||
|
|
||||||
|
The `info` response Object, obtained from callback of `.toFile()` or `.toBuffer()`,
|
||||||
|
will contain `trimOffsetLeft` and `trimOffsetTop` properties.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| trim | <code>string</code> \| <code>number</code> \| <code>Object</code> | | the specific background colour to trim, the threshold for doing so or an Object with both. |
|
||||||
|
| [trim.background] | <code>string</code> \| <code>Object</code> | <code>"'top-left pixel'"</code> | background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to that of the top-left pixel. |
|
||||||
|
| [trim.threshold] | <code>number</code> | <code>10</code> | the allowed difference from the above colour, a positive number. |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Trim pixels with a colour similar to that of the top-left pixel.
|
||||||
|
sharp(input)
|
||||||
|
.trim()
|
||||||
|
.toFile(output, function(err, info) {
|
||||||
|
...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Trim pixels with the exact same colour as that of the top-left pixel.
|
||||||
|
sharp(input)
|
||||||
|
.trim(0)
|
||||||
|
.toFile(output, function(err, info) {
|
||||||
|
...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Trim only pixels with a similar colour to red.
|
||||||
|
sharp(input)
|
||||||
|
.trim("#FF0000")
|
||||||
|
.toFile(output, function(err, info) {
|
||||||
|
...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Trim all "yellow-ish" pixels, being more lenient with the higher threshold.
|
||||||
|
sharp(input)
|
||||||
|
.trim({
|
||||||
|
background: "yellow",
|
||||||
|
threshold: 42,
|
||||||
|
})
|
||||||
|
.toFile(output, function(err, info) {
|
||||||
|
...
|
||||||
|
});
|
||||||
|
```
|
||||||
232
docs/api-utility.md
Normal file
232
docs/api-utility.md
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
## versions
|
||||||
|
> versions
|
||||||
|
|
||||||
|
An Object containing the version numbers of sharp, libvips and its dependencies.
|
||||||
|
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
console.log(sharp.versions);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## interpolators
|
||||||
|
> interpolators : <code>enum</code>
|
||||||
|
|
||||||
|
An Object containing the available interpolators and their proper values
|
||||||
|
|
||||||
|
|
||||||
|
**Read only**: true
|
||||||
|
**Properties**
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| nearest | <code>string</code> | <code>"nearest"</code> | [Nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation). Suitable for image enlargement only. |
|
||||||
|
| bilinear | <code>string</code> | <code>"bilinear"</code> | [Bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation). Faster than bicubic but with less smooth results. |
|
||||||
|
| bicubic | <code>string</code> | <code>"bicubic"</code> | [Bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) (the default). |
|
||||||
|
| locallyBoundedBicubic | <code>string</code> | <code>"lbb"</code> | [LBB interpolation](https://github.com/libvips/libvips/blob/master/libvips/resample/lbb.cpp#L100). Prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" but typically reduces performance by a factor of 2. |
|
||||||
|
| nohalo | <code>string</code> | <code>"nohalo"</code> | [Nohalo interpolation](http://eprints.soton.ac.uk/268086/). Prevents acutance but typically reduces performance by a factor of 3. |
|
||||||
|
| vertexSplitQuadraticBasisSpline | <code>string</code> | <code>"vsqbs"</code> | [VSQBS interpolation](https://github.com/libvips/libvips/blob/master/libvips/resample/vsqbs.cpp#L48). Prevents "staircasing" when enlarging. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## format
|
||||||
|
> format ⇒ <code>Object</code>
|
||||||
|
|
||||||
|
An Object containing nested boolean values representing the available input and output formats/methods.
|
||||||
|
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
console.log(sharp.format);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## queue
|
||||||
|
> queue
|
||||||
|
|
||||||
|
An EventEmitter that emits a `change` event when a task is either:
|
||||||
|
- queued, waiting for _libuv_ to provide a worker thread
|
||||||
|
- complete
|
||||||
|
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
sharp.queue.on('change', function(queueLength) {
|
||||||
|
console.log('Queue contains ' + queueLength + ' task(s)');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## cache
|
||||||
|
> cache([options]) ⇒ <code>Object</code>
|
||||||
|
|
||||||
|
Gets or, when options are provided, sets the limits of _libvips'_ operation cache.
|
||||||
|
Existing entries in the cache will be trimmed after any change in limits.
|
||||||
|
This method always returns cache statistics,
|
||||||
|
useful for determining how much working memory is required for a particular task.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [options] | <code>Object</code> \| <code>boolean</code> | <code>true</code> | Object with the following attributes, or boolean where true uses default cache settings and false removes all caching |
|
||||||
|
| [options.memory] | <code>number</code> | <code>50</code> | is the maximum memory in MB to use for this cache |
|
||||||
|
| [options.files] | <code>number</code> | <code>20</code> | is the maximum number of files to hold open |
|
||||||
|
| [options.items] | <code>number</code> | <code>100</code> | is the maximum number of operations to cache |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const stats = sharp.cache();
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
sharp.cache( { items: 200 } );
|
||||||
|
sharp.cache( { files: 0 } );
|
||||||
|
sharp.cache(false);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## concurrency
|
||||||
|
> concurrency([concurrency]) ⇒ <code>number</code>
|
||||||
|
|
||||||
|
Gets or, when a concurrency is provided, sets
|
||||||
|
the maximum number of threads _libvips_ should use to process _each image_.
|
||||||
|
These are from a thread pool managed by glib,
|
||||||
|
which helps avoid the overhead of creating new threads.
|
||||||
|
|
||||||
|
This method always returns the current concurrency.
|
||||||
|
|
||||||
|
The default value is the number of CPU cores,
|
||||||
|
except when using glibc-based Linux without jemalloc,
|
||||||
|
where the default is `1` to help reduce memory fragmentation.
|
||||||
|
|
||||||
|
A value of `0` will reset this to the number of CPU cores.
|
||||||
|
|
||||||
|
Some image format libraries spawn additional threads,
|
||||||
|
e.g. libaom manages its own 4 threads when encoding AVIF images,
|
||||||
|
and these are independent of the value set here.
|
||||||
|
|
||||||
|
The maximum number of images that sharp can process in parallel
|
||||||
|
is controlled by libuv's `UV_THREADPOOL_SIZE` environment variable,
|
||||||
|
which defaults to 4.
|
||||||
|
|
||||||
|
https://nodejs.org/api/cli.html#uv_threadpool_sizesize
|
||||||
|
|
||||||
|
For example, by default, a machine with 8 CPU cores will process
|
||||||
|
4 images in parallel and use up to 8 threads per image,
|
||||||
|
so there will be up to 32 concurrent threads.
|
||||||
|
|
||||||
|
|
||||||
|
**Returns**: <code>number</code> - concurrency
|
||||||
|
|
||||||
|
| Param | Type |
|
||||||
|
| --- | --- |
|
||||||
|
| [concurrency] | <code>number</code> |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const threads = sharp.concurrency(); // 4
|
||||||
|
sharp.concurrency(2); // 2
|
||||||
|
sharp.concurrency(0); // 4
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## counters
|
||||||
|
> counters() ⇒ <code>Object</code>
|
||||||
|
|
||||||
|
Provides access to internal task counters.
|
||||||
|
- queue is the number of tasks this module has queued waiting for _libuv_ to provide a worker thread from its pool.
|
||||||
|
- process is the number of resize tasks currently being processed.
|
||||||
|
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const counters = sharp.counters(); // { queue: 2, process: 4 }
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## simd
|
||||||
|
> simd([simd]) ⇒ <code>boolean</code>
|
||||||
|
|
||||||
|
Get and set use of SIMD vector unit instructions.
|
||||||
|
Requires libvips to have been compiled with liborc support.
|
||||||
|
|
||||||
|
Improves the performance of `resize`, `blur` and `sharpen` operations
|
||||||
|
by taking advantage of the SIMD vector unit of the CPU, e.g. Intel SSE and ARM NEON.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Param | Type | Default |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| [simd] | <code>boolean</code> | <code>true</code> |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const simd = sharp.simd();
|
||||||
|
// simd is `true` if the runtime use of liborc is currently enabled
|
||||||
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const simd = sharp.simd(false);
|
||||||
|
// prevent libvips from using liborc at runtime
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## block
|
||||||
|
> block(options)
|
||||||
|
|
||||||
|
Block libvips operations at runtime.
|
||||||
|
|
||||||
|
This is in addition to the `VIPS_BLOCK_UNTRUSTED` environment variable,
|
||||||
|
which when set will block all "untrusted" operations.
|
||||||
|
|
||||||
|
|
||||||
|
**Since**: 0.32.4
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| options | <code>Object</code> | |
|
||||||
|
| options.operation | <code>Array.<string></code> | List of libvips low-level operation names to block. |
|
||||||
|
|
||||||
|
**Example** *(Block all TIFF input.)*
|
||||||
|
```js
|
||||||
|
sharp.block({
|
||||||
|
operation: ['VipsForeignLoadTiff']
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## unblock
|
||||||
|
> unblock(options)
|
||||||
|
|
||||||
|
Unblock libvips operations at runtime.
|
||||||
|
|
||||||
|
This is useful for defining a list of allowed operations.
|
||||||
|
|
||||||
|
|
||||||
|
**Since**: 0.32.4
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| options | <code>Object</code> | |
|
||||||
|
| options.operation | <code>Array.<string></code> | List of libvips low-level operation names to unblock. |
|
||||||
|
|
||||||
|
**Example** *(Block all input except WebP from the filesystem.)*
|
||||||
|
```js
|
||||||
|
sharp.block({
|
||||||
|
operation: ['VipsForeignLoad']
|
||||||
|
});
|
||||||
|
sharp.unblock({
|
||||||
|
operation: ['VipsForeignLoadWebpFile']
|
||||||
|
});
|
||||||
|
```
|
||||||
|
**Example** *(Block all input except JPEG and PNG from a Buffer or Stream.)*
|
||||||
|
```js
|
||||||
|
sharp.block({
|
||||||
|
operation: ['VipsForeignLoad']
|
||||||
|
});
|
||||||
|
sharp.unblock({
|
||||||
|
operation: ['VipsForeignLoadJpegBuffer', 'VipsForeignLoadPngBuffer']
|
||||||
|
});
|
||||||
|
```
|
||||||
38
docs/build.js
Normal file
38
docs/build.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs').promises;
|
||||||
|
const path = require('path');
|
||||||
|
const jsdoc2md = require('jsdoc-to-markdown');
|
||||||
|
|
||||||
|
[
|
||||||
|
'constructor',
|
||||||
|
'input',
|
||||||
|
'resize',
|
||||||
|
'composite',
|
||||||
|
'operation',
|
||||||
|
'colour',
|
||||||
|
'channel',
|
||||||
|
'output',
|
||||||
|
'utility'
|
||||||
|
].forEach(async (m) => {
|
||||||
|
const input = path.join('lib', `${m}.js`);
|
||||||
|
const output = path.join('docs', `api-${m}.md`);
|
||||||
|
|
||||||
|
const ast = await jsdoc2md.getTemplateData({ files: input });
|
||||||
|
const markdown = await jsdoc2md.render({
|
||||||
|
data: ast,
|
||||||
|
'global-index-format': 'none',
|
||||||
|
'module-index-format': 'none'
|
||||||
|
});
|
||||||
|
|
||||||
|
const cleanMarkdown = markdown
|
||||||
|
.replace(/(## )([A-Za-z0-9]+)([^\n]*)/g, '$1$2\n> $2$3\n') // simplify headings to match those of documentationjs, ensures existing URLs work
|
||||||
|
.replace(/<a name="[A-Za-z0-9+]+"><\/a>/g, '') // remove anchors, let docute add these (at markdown to HTML render time)
|
||||||
|
.replace(/\*\*Kind\*\*: global[^\n]+/g, '') // remove all "global" Kind labels (requires JSDoc refactoring)
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
await fs.writeFile(output, cleanMarkdown);
|
||||||
|
});
|
||||||
1918
docs/changelog.md
Normal file
1918
docs/changelog.md
Normal file
File diff suppressed because it is too large
Load Diff
1
docs/docute.min.js
vendored
Normal file
1
docs/docute.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
116
docs/firebase.json
Normal file
116
docs/firebase.json
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
{
|
||||||
|
"hosting": {
|
||||||
|
"site": "pixelplumbing-sharp",
|
||||||
|
"public": ".",
|
||||||
|
"ignore": [
|
||||||
|
".*",
|
||||||
|
"build.js",
|
||||||
|
"firebase.json",
|
||||||
|
"image/**",
|
||||||
|
"search-index/**"
|
||||||
|
],
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"source": "**",
|
||||||
|
"headers": [
|
||||||
|
{ "key": "Cache-Control", "value": "max-age=86400" },
|
||||||
|
{ "key": "X-Content-Type-Options", "value": "nosniff" },
|
||||||
|
{ "key": "X-Frame-Options", "value": "SAMEORIGIN" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"redirects": [
|
||||||
|
{
|
||||||
|
"source": "**/install/**",
|
||||||
|
"destination": "/install",
|
||||||
|
"type": 301
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/page/install",
|
||||||
|
"destination": "/install",
|
||||||
|
"type": 301
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "**/api-constructor/**",
|
||||||
|
"destination": "/api-constructor",
|
||||||
|
"type": 301
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "**/api-input/**",
|
||||||
|
"destination": "/api-input",
|
||||||
|
"type": 301
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "**/api-output/**",
|
||||||
|
"destination": "/api-output",
|
||||||
|
"type": 301
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "**/api-resize/**",
|
||||||
|
"destination": "/api-resize",
|
||||||
|
"type": 301
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "**/api-compsite/**",
|
||||||
|
"destination": "/api-compsite",
|
||||||
|
"type": 301
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "**/api-operation/**",
|
||||||
|
"destination": "/api-operation",
|
||||||
|
"type": 301
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "**/api-colour/**",
|
||||||
|
"destination": "/api-colour",
|
||||||
|
"type": 301
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "**/api-channel/**",
|
||||||
|
"destination": "/api-channel",
|
||||||
|
"type": 301
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "**/api-utility/**",
|
||||||
|
"destination": "/api-utility",
|
||||||
|
"type": 301
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/page/api",
|
||||||
|
"destination": "/api-constructor",
|
||||||
|
"type": 301
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "**/performance/**",
|
||||||
|
"destination": "/performance",
|
||||||
|
"type": 301
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/page/performance",
|
||||||
|
"destination": "/performance",
|
||||||
|
"type": 301
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "**/changelog/**",
|
||||||
|
"destination": "/changelog",
|
||||||
|
"type": 301
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/page/changelog",
|
||||||
|
"destination": "/changelog",
|
||||||
|
"type": 301
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/en/**",
|
||||||
|
"destination": "/",
|
||||||
|
"type": 301
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rewrites": [
|
||||||
|
{
|
||||||
|
"source": "**",
|
||||||
|
"destination": "/index.html"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
277
docs/humans.txt
Normal file
277
docs/humans.txt
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
/* THANKS */
|
||||||
|
|
||||||
|
Name: John Cupitt
|
||||||
|
GitHub: https://github.com/jcupitt
|
||||||
|
|
||||||
|
Name: Pierre Inglebert
|
||||||
|
GitHub: https://github.com/pierreinglebert
|
||||||
|
|
||||||
|
Name: Jonathan Ong
|
||||||
|
GitHub: https://github.com/jonathanong
|
||||||
|
|
||||||
|
Name: Chanon Sajjamanochai
|
||||||
|
GitHub: https://github.com/chanon
|
||||||
|
|
||||||
|
Name: Juliano Julio
|
||||||
|
GitHub: https://github.com/julianojulio
|
||||||
|
|
||||||
|
Name: Daniel Gasienica
|
||||||
|
GitHub: https://github.com/gasi
|
||||||
|
|
||||||
|
Name: Julian Walker
|
||||||
|
GitHub: https://github.com/julianwa
|
||||||
|
|
||||||
|
Name: Amit Pitaru
|
||||||
|
GitHub: https://github.com/apitaru
|
||||||
|
|
||||||
|
Name: Brandon Aaron
|
||||||
|
GitHub: https://github.com/brandonaaron
|
||||||
|
|
||||||
|
Name: Andreas Lind
|
||||||
|
GitHub: https://github.com/papandreouGitHub:
|
||||||
|
|
||||||
|
Name: Maurus Cuelenaere
|
||||||
|
GitHub: https://github.com/mcuelenaere
|
||||||
|
|
||||||
|
Name: Linus Unnebäck
|
||||||
|
GitHub: https://github.com/LinusU
|
||||||
|
|
||||||
|
Name: Victor Mateevitsi
|
||||||
|
GitHub: https://github.com/mvictoras
|
||||||
|
|
||||||
|
Name: Alaric Holloway
|
||||||
|
GitHub: https://github.com/skedastik
|
||||||
|
|
||||||
|
Name: Bernhard K. Weisshuhn
|
||||||
|
GitHub: https://github.com/bkw
|
||||||
|
|
||||||
|
Name: David A. Carley
|
||||||
|
GitHub: https://github.com/dacarley
|
||||||
|
|
||||||
|
Name: John Tobin
|
||||||
|
GitHub: https://github.com/jtobinisaniceguy
|
||||||
|
|
||||||
|
Name: Kenton Gray
|
||||||
|
GitHub: https://github.com/kentongray
|
||||||
|
|
||||||
|
Name: Felix Bünemann
|
||||||
|
GitHub: https://github.com/felixbuenemann
|
||||||
|
|
||||||
|
Name: Samy Al Zahrani
|
||||||
|
GitHub: https://github.com/salzhrani
|
||||||
|
|
||||||
|
Name: Chintan Thakkar
|
||||||
|
GitHub: https://github.com/lemnisk8
|
||||||
|
|
||||||
|
Name: F. Orlando Galashan
|
||||||
|
GitHub: https://github.com/frulo
|
||||||
|
|
||||||
|
Name: Kleis Auke Wolthuizen
|
||||||
|
GitHub: https://github.com/kleisauke
|
||||||
|
|
||||||
|
Name: Matt Hirsch
|
||||||
|
GitHub: https://github.com/mhirsch
|
||||||
|
|
||||||
|
Name: Rahul Nanwani
|
||||||
|
GitHub: https://github.com/rnanwani
|
||||||
|
|
||||||
|
Name: Matthias Thoemmes
|
||||||
|
GitHub: https://github.com/cmtt
|
||||||
|
|
||||||
|
Name: Patrick Paskaris
|
||||||
|
GitHub: https://github.com/ppaskaris
|
||||||
|
|
||||||
|
Name: Jérémy Lal
|
||||||
|
GitHub: https://github.com/kapouer
|
||||||
|
|
||||||
|
Name: Alice Monday
|
||||||
|
GitHub: https://github.com/alice0meta
|
||||||
|
|
||||||
|
Name: Kristo Jorgenson
|
||||||
|
GitHub: https://github.com/kristojorg
|
||||||
|
|
||||||
|
Name: Yves Bos
|
||||||
|
GitHub: https://github.com/YvesBos
|
||||||
|
|
||||||
|
Name: Nicolas Coden
|
||||||
|
GitHub: https://github.com/ncoden
|
||||||
|
|
||||||
|
Name: Matt Parrish
|
||||||
|
GitHub: https://github.com/pbomb
|
||||||
|
|
||||||
|
Name: Matthew McEachen
|
||||||
|
GitHub: https://github.com/mceachen
|
||||||
|
|
||||||
|
Name: Jarda Kotěšovec
|
||||||
|
GitHub: https://github.com/jardakotesovec
|
||||||
|
|
||||||
|
Name: Kenric D'Souza
|
||||||
|
GitHub: https://github.com/AzureByte
|
||||||
|
|
||||||
|
Name: Oleh Aleinyk
|
||||||
|
GitHub: https://github.com/oaleynik
|
||||||
|
|
||||||
|
Name: Marcel Bretschneider
|
||||||
|
GitHub: https://github.com/3epnm
|
||||||
|
|
||||||
|
Name: Andrea Bianco
|
||||||
|
GitHub: https://github.com/BiancoA
|
||||||
|
|
||||||
|
Name: Rik Heywood
|
||||||
|
GitHub: https://github.com/rikh42
|
||||||
|
|
||||||
|
Name: Thomas Parisot
|
||||||
|
GitHub: https://github.com/oncletom
|
||||||
|
|
||||||
|
Name: Nathan Graves
|
||||||
|
GitHub: https://github.com/woolite64
|
||||||
|
|
||||||
|
Name: Tom Lokhorst
|
||||||
|
GitHub: https://github.com/tomlokhorst
|
||||||
|
|
||||||
|
Name: Espen Hovlandsdal
|
||||||
|
GitHub: https://github.com/rexxars
|
||||||
|
|
||||||
|
Name: Sylvain Dumont
|
||||||
|
GitHub: https://github.com/sylvaindumont
|
||||||
|
|
||||||
|
Name: Alun Davies
|
||||||
|
GitHub: https://github.com/alundavies
|
||||||
|
|
||||||
|
Name: Aidan Hoolachan
|
||||||
|
GitHub: https://github.com/ajhool
|
||||||
|
|
||||||
|
Name: Axel Eirola
|
||||||
|
GitHub: https://github.com/aeirola
|
||||||
|
|
||||||
|
Name: Freezy
|
||||||
|
GitHub: https://github.com/freezy
|
||||||
|
|
||||||
|
Name: Julian Aubourg
|
||||||
|
GitHub: https://github.com/jaubourg
|
||||||
|
|
||||||
|
Name: Keith Belovay
|
||||||
|
GitHub: https://github.com/fromkeith
|
||||||
|
|
||||||
|
Name: Michael B. Klein
|
||||||
|
GitHub: https://github.com/mbklein
|
||||||
|
|
||||||
|
Name: Jakub Michálek
|
||||||
|
GitHub: https://github.com/Goues
|
||||||
|
|
||||||
|
Name: Ilya Ovdin
|
||||||
|
GitHub: https://github.com/iovdin
|
||||||
|
|
||||||
|
Name: Andargor
|
||||||
|
GitHub: https://github.com/Andargor
|
||||||
|
|
||||||
|
Name: Nicolas Stepien
|
||||||
|
GitHub: https://github.com/MayhemYDG
|
||||||
|
|
||||||
|
Name: Paul Neave
|
||||||
|
GitHub: https://github.com/neave
|
||||||
|
|
||||||
|
Name: Brendan Kennedy
|
||||||
|
GitHub: https://github.com/rustyguts
|
||||||
|
|
||||||
|
Name: Brychan Bennett-Odlum
|
||||||
|
GitHub: https://github.com/BrychanOdlum
|
||||||
|
|
||||||
|
Name: Edward Silverton
|
||||||
|
GitHub: https://github.com/edsilv
|
||||||
|
|
||||||
|
Name: Dumitru Deveatii
|
||||||
|
GitHub: https://github.com/dimadeveatii
|
||||||
|
|
||||||
|
Name: Roland Asmann
|
||||||
|
GitHub: https://github.com/malice00
|
||||||
|
|
||||||
|
Name: Roman Malieiev
|
||||||
|
GitHub: https://github.com/romaleev
|
||||||
|
|
||||||
|
Name: Jerome Vouillon
|
||||||
|
GitHub: https://github.com/vouillon
|
||||||
|
|
||||||
|
Name: Tomáš Szabo
|
||||||
|
GitHub: https://github.com/deftomat
|
||||||
|
|
||||||
|
Name: Robert O'Rourke
|
||||||
|
GitHub: https://github.com/roborourke
|
||||||
|
|
||||||
|
Name: Denis Soldatov
|
||||||
|
GitHub: https://github.com/derom
|
||||||
|
|
||||||
|
Name: Stefan Probst
|
||||||
|
GitHub: https://github.com/stefanprobst
|
||||||
|
|
||||||
|
Name: Thomas Beiganz
|
||||||
|
GitHub: https://github.com/beig
|
||||||
|
|
||||||
|
Name: Florian Busch
|
||||||
|
GitHub: https://github.com/florian-busch
|
||||||
|
|
||||||
|
Name: Matthieu Salettes
|
||||||
|
GitHub: https://github.com/msalettes
|
||||||
|
|
||||||
|
Name: Taneli Vatanen
|
||||||
|
GitHub: https://github.com/Daiz
|
||||||
|
|
||||||
|
Name: Mart Jansink
|
||||||
|
GitHub: https://github.com/mart-jansink
|
||||||
|
|
||||||
|
Name: Tenpi
|
||||||
|
GitHub: https://github.com/Tenpi
|
||||||
|
|
||||||
|
Name: Zaruike
|
||||||
|
GitHub: https://github.com/Zaruike
|
||||||
|
|
||||||
|
Name: Erlend F
|
||||||
|
GitHub: https://github.com/erf
|
||||||
|
|
||||||
|
Name: Drian Naude
|
||||||
|
GitHub: https://github.com/driannaude
|
||||||
|
|
||||||
|
Name: Max Gordon
|
||||||
|
GitHub: https://github.com/gforge
|
||||||
|
|
||||||
|
Name: Chris Banks
|
||||||
|
GitHub: https://github.com/christopherbradleybanks
|
||||||
|
|
||||||
|
Name: codepage949
|
||||||
|
GitHub: https://github.com/codepage949
|
||||||
|
|
||||||
|
Name: Chris Hranj
|
||||||
|
GitHub: https://github.com/Brodan
|
||||||
|
|
||||||
|
Name: Ankur Parihar
|
||||||
|
GitHub: https://github.com/ankurparihar
|
||||||
|
|
||||||
|
Name: Joona Heinikoski
|
||||||
|
GitHub: https://github.com/joonamo
|
||||||
|
|
||||||
|
Name: AlexanderTheGrey
|
||||||
|
GitHub: https://github.com/AlexanderTheGrey
|
||||||
|
|
||||||
|
Name: Blayne Chard
|
||||||
|
GitHub: https://github.com/blacha
|
||||||
|
|
||||||
|
Name: Brahim
|
||||||
|
GitHub: https://github.com/brahima
|
||||||
|
|
||||||
|
Name: Anton Marsden
|
||||||
|
GitHub: https://github.com/antonmarsden
|
||||||
|
|
||||||
|
Name: Marcos Casagrande
|
||||||
|
GitHub: https://github.com/marcosc90
|
||||||
|
|
||||||
|
Name: Emanuel Jöbstl
|
||||||
|
GitHub: https://github.com/ejoebstl
|
||||||
|
|
||||||
|
Name: Tomasz Janowski
|
||||||
|
GitHub: https://github.com/janaz
|
||||||
|
|
||||||
|
Name: Lachlan Newman
|
||||||
|
GitHub: https://github.com/LachlanNewman
|
||||||
|
|
||||||
|
Name: BJJ
|
||||||
|
GitHub: https://github.com/bianjunjie1981
|
||||||
61
docs/image/api-resize-fit.svg
Normal file
61
docs/image/api-resize-fit.svg
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="998" height="243" viewBox="0 0 998 243">
|
||||||
|
<defs>
|
||||||
|
<g id="placeholder">
|
||||||
|
<rect width="180" height="128" fill="#64bed8"/>
|
||||||
|
<circle cx="61.1" cy="36.8" r="19.3" fill="#ffefa9"/>
|
||||||
|
<circle cx="61.1" cy="36.8" r="18.1" fill="#fdda42"/>
|
||||||
|
<path d="m67.2 34.7 15.2 46L90 57.9l30.4 38 7.8-15.2 7.5 15.4H44z" fill="#6a696f"/>
|
||||||
|
<path d="m82.4 80.7-15.2-46-.3 69h22.9z" fill="#474749"/>
|
||||||
|
<path d="m90.1 58 12.2 15 18.2 23-13.9.1z" fill="#474749"/>
|
||||||
|
<path d="M135.8 96H131l-2.8-15.3z" fill="#474749"/>
|
||||||
|
<path d="M35.2 96h107.1c0 1.7-1.4 3.2-3.2 3.2H38.4a3.2 3.2 0 0 1-3.2-3.2z" fill="#b9c861"/>
|
||||||
|
<path d="m67.2 34.7-.1 31-6.2-3-5.3 2.7z" fill="#fff"/>
|
||||||
|
<path d="m67.2 34.7 7.6 23-7.7 8z" fill="#b3b1b4"/>
|
||||||
|
<rect width="30.8" height="7.7" x="71.1" y="27.2" rx="2.8" ry="4.1" fill="#fff"/>
|
||||||
|
<rect width="30.8" height="7.7" x="82.2" y="34.8" rx="2.8" ry="4.1" fill="#fff"/>
|
||||||
|
<rect width="30.8" height="7.7" x="36.2" y="19.6" rx="2.8" ry="4.1" fill="#fff"/>
|
||||||
|
<path d="m89.6 72.8-7.2 7.9L90 57.9l10 23z" fill="#fff"/>
|
||||||
|
<path d="m90.1 58 10 23 2.2-8z" fill="#b3b1b4"/>
|
||||||
|
<path d="M131.2 85.2 137 68l9 17.2-8 6z" fill="#8da128"/>
|
||||||
|
<rect width="109.4" height="6.8" x="33.9" y="99.1" rx="13.2" ry="11.4" fill="#22b0d6"/>
|
||||||
|
<path d="m137 68-5.8 17.2 6.8 6.1.3-13.7z" fill="#727d2e"/>
|
||||||
|
<rect width="83.3" height="6.8" x="50.8" y="103.6" rx="10" ry="11.4" fill="#22b0d6"/>
|
||||||
|
<rect width=".7" height="18.4" x="138" y="77.6" fill="#585657"/>
|
||||||
|
<rect width=".5" height="5.2" x="2" y="-161.3" fill="#585657" transform="rotate(120)"/>
|
||||||
|
<rect width=".5" height="5.3" x="5.5" y="-163.3" fill="#585657" transform="rotate(120)"/>
|
||||||
|
<rect width=".5" height="4.8" x="-142.4" y="77.7" fill="#585657" transform="rotate(240)"/>
|
||||||
|
<rect width=".5" height="5.1" x="-146" y="75.6" fill="#585657" transform="rotate(240)"/>
|
||||||
|
</g>
|
||||||
|
<pattern id="img" height="100%" width="100%" viewBox="0 0 180 128">
|
||||||
|
<use xlink:href="#placeholder"/>
|
||||||
|
</pattern>
|
||||||
|
<pattern id="img-fill" width="100%" height="100%" viewBox="0 0 180 128" preserveAspectRatio="none">
|
||||||
|
<use xlink:href="#placeholder"/>
|
||||||
|
</pattern>
|
||||||
|
</defs>
|
||||||
|
<rect x="0" y="0" width="998" height="243" fill="#ddd"/>
|
||||||
|
<g id="cover">
|
||||||
|
<rect x="22" y="28" width="180" height="132" fill="url(#img)"/>
|
||||||
|
<rect x="48" y="30" width="128" height="128" fill="none" stroke="#000" stroke-width="4"/>
|
||||||
|
<text x="112" y="85%" dominant-baseline="middle" text-anchor="middle" font-family="sans" font-size="32" font-weight="bold">cover</text>
|
||||||
|
</g>
|
||||||
|
<g id="contain">
|
||||||
|
<rect x="240" y="30" width="128" height="128" fill="url(#img)" stroke="#000" stroke-width="4"/>
|
||||||
|
<text x="304" y="85%" dominant-baseline="middle" text-anchor="middle" font-family="sans" font-size="32" font-weight="bold" fill="#555">contain</text>
|
||||||
|
</g>
|
||||||
|
<g id="fill">
|
||||||
|
<rect x="432" y="30" width="128" height="128" fill="url(#img-fill)" stroke="#000" stroke-width="4"/>
|
||||||
|
<text x="496" y="85%" dominant-baseline="middle" text-anchor="middle" font-family="sans" font-size="32" font-weight="bold">fill</text>
|
||||||
|
</g>
|
||||||
|
<g id="inside">
|
||||||
|
<rect x="624" y="48" width="128" height="92" fill="url(#img)" stroke="#000" stroke-width="4"/>
|
||||||
|
<rect x="624" y="30" width="128" height="128" fill="none" stroke="#000" stroke-width="4" stroke-dasharray="12 4" stroke-dashoffset="6"/>
|
||||||
|
<text x="688" y="85%" dominant-baseline="middle" text-anchor="middle" font-family="sans" font-size="32" font-weight="bold" fill="#555">inside</text>
|
||||||
|
</g>
|
||||||
|
<g id="outside">
|
||||||
|
<rect x="792" y="30" width="176" height="128" fill="url(#img)" stroke="#000" stroke-width="4"/>
|
||||||
|
<rect x="816" y="30" width="128" height="128" fill="none" stroke="#000" stroke-width="4" stroke-dasharray="12 4" stroke-dashoffset="-2"/>
|
||||||
|
<text x="880" y="85%" dominant-baseline="middle" text-anchor="middle" font-family="sans" font-size="32" font-weight="bold">outside</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.0 KiB |
BIN
docs/image/sharp-logo-32.png
Normal file
BIN
docs/image/sharp-logo-32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 652 B |
BIN
docs/image/sharp-logo-600.png
Normal file
BIN
docs/image/sharp-logo-600.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
11
docs/image/sharp-logo-mono.svg
Normal file
11
docs/image/sharp-logo-mono.svg
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="86 86 550 550">
|
||||||
|
<!-- Creative Commons CC0 1.0 Universal Public Domain Dedication -->
|
||||||
|
<defs>
|
||||||
|
<mask id="c">
|
||||||
|
<path fill="none" stroke="#fff" stroke-width="80" d="M258.411 285.777l200.176-26.8M244.113 466.413L451.44 438.66m.001 0V238.484m0-150.121v171.572l178.725-23.917m-359.843 19.584V477.22m2.387 156.95V462.591L93.984 486.515"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width="112" d="M451.441 610.246V438.66l178.725-23.91M269.688 112.59v171.58L90.964 308.093"/>
|
||||||
|
</mask>
|
||||||
|
</defs>
|
||||||
|
<path mask="url(#c)" fill="none" stroke="#000" stroke-width="80" d="M258.411 285.777l200.176-26.8M244.113 466.413L451.44 438.66m.001 0V238.484m0-150.121v171.572l178.725-23.917m-359.843 19.584V477.22m2.387 156.95V462.591L93.984 486.515"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width="80" d="M451.441 610.246V438.66l178.725-23.91M269.688 112.59v171.58L90.964 308.093"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 929 B |
5
docs/image/sharp-logo.svg
Normal file
5
docs/image/sharp-logo.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="86 86 550 550">
|
||||||
|
<!-- Creative Commons CC0 1.0 Universal Public Domain Dedication -->
|
||||||
|
<path fill="none" stroke="#9c0" stroke-width="80" d="M258.411 285.777l200.176-26.8M244.113 466.413L451.44 438.66M451.441 438.66V238.484M451.441 88.363v171.572l178.725-23.917M270.323 255.602V477.22M272.71 634.17V462.591L93.984 486.515"/>
|
||||||
|
<path fill="none" stroke="#090" stroke-width="80" d="M451.441 610.246V438.66l178.725-23.91M269.688 112.59v171.58L90.964 308.093"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 508 B |
206
docs/index.html
Normal file
206
docs/index.html
Normal file
File diff suppressed because one or more lines are too long
225
docs/install.md
Normal file
225
docs/install.md
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
# Installation
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install sharp
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yarn add sharp
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm add sharp
|
||||||
|
```
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
* Node.js >= 18.17.0
|
||||||
|
|
||||||
|
## Prebuilt binaries
|
||||||
|
|
||||||
|
Ready-compiled sharp and libvips binaries are provided for use on the most common platforms:
|
||||||
|
|
||||||
|
* macOS x64 (>= 10.13)
|
||||||
|
* macOS ARM64
|
||||||
|
* Linux x64 (glibc >= 2.26, musl >= 1.2.2, CPU with SSE4.2)
|
||||||
|
* Linux ARM64 (glibc >= 2.26, musl >= 1.2.2)
|
||||||
|
* Linux ARM (glibc >= 2.28)
|
||||||
|
* Windows x64
|
||||||
|
* Windows x86
|
||||||
|
|
||||||
|
This provides support for the
|
||||||
|
JPEG, PNG, WebP, AVIF (limited to 8-bit depth), TIFF, GIF and SVG (input) image formats.
|
||||||
|
|
||||||
|
## Custom libvips
|
||||||
|
|
||||||
|
To use a custom, globally-installed version of libvips instead of the provided binaries,
|
||||||
|
make sure it is at least the version listed under `engines.libvips` in the `package.json` file
|
||||||
|
and that it can be located using `pkg-config --modversion vips-cpp`.
|
||||||
|
|
||||||
|
For help compiling libvips and its dependencies, please see
|
||||||
|
[building libvips from source](https://www.libvips.org/install.html#building-libvips-from-source).
|
||||||
|
|
||||||
|
The use of a globally-installed libvips is unsupported on Windows
|
||||||
|
and on macOS when running Node.js under Rosetta.
|
||||||
|
|
||||||
|
## Building from source
|
||||||
|
|
||||||
|
This module will be compiled from source at `npm install` time when:
|
||||||
|
|
||||||
|
* a globally-installed libvips is detected (set the `SHARP_IGNORE_GLOBAL_LIBVIPS` environment variable to skip this), or
|
||||||
|
* when the `npm install --build-from-source` flag is used.
|
||||||
|
|
||||||
|
Building from source requires:
|
||||||
|
|
||||||
|
* C++11 compiler
|
||||||
|
* [node-addon-api](https://www.npmjs.com/package/node-addon-api)
|
||||||
|
* [node-gyp](https://github.com/nodejs/node-gyp#installation) and its dependencies
|
||||||
|
|
||||||
|
There is an install-time check for these dependencies.
|
||||||
|
If `node-addon-api` or `node-gyp` cannot be found, try adding them via:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install --save node-addon-api node-gyp
|
||||||
|
```
|
||||||
|
|
||||||
|
For cross-compiling, the `--platform`, `--arch` and `--libc` npm flags
|
||||||
|
(or the `npm_config_platform`, `npm_config_arch` and `npm_config_libc` environment variables)
|
||||||
|
can be used to configure the target environment.
|
||||||
|
|
||||||
|
## FreeBSD
|
||||||
|
|
||||||
|
The `vips` package must be installed before `npm install` is run.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pkg install -y pkgconf vips
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd /usr/ports/graphics/vips/ && make install clean
|
||||||
|
```
|
||||||
|
|
||||||
|
## Linux memory allocator
|
||||||
|
|
||||||
|
The default memory allocator on most glibc-based Linux systems
|
||||||
|
(e.g. Debian, Red Hat) is unsuitable for long-running, multi-threaded
|
||||||
|
processes that involve lots of small memory allocations.
|
||||||
|
|
||||||
|
For this reason, by default, sharp will limit the use of thread-based
|
||||||
|
[concurrency](api-utility#concurrency) when the glibc allocator is
|
||||||
|
detected at runtime.
|
||||||
|
|
||||||
|
To help avoid fragmentation and improve performance on these systems,
|
||||||
|
the use of an alternative memory allocator such as
|
||||||
|
[jemalloc](https://github.com/jemalloc/jemalloc) is recommended.
|
||||||
|
|
||||||
|
Those using musl-based Linux (e.g. Alpine) and non-Linux systems are
|
||||||
|
unaffected.
|
||||||
|
|
||||||
|
## AWS Lambda
|
||||||
|
|
||||||
|
The `node_modules` directory of the
|
||||||
|
[deployment package](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-package.html)
|
||||||
|
must include binaries for either the linux-x64 or linux-arm64 platforms
|
||||||
|
depending on the chosen architecture.
|
||||||
|
|
||||||
|
When building your deployment package on a machine that differs from the target architecture,
|
||||||
|
you will need to install either `@sharpen/sharp-linux-x64` or `@sharpen/sharp-linux-arm64` package.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install --force @sharpen/sharp-linux-x64
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install --force @sharpen/sharp-linux-arm64
|
||||||
|
```
|
||||||
|
|
||||||
|
To get the best performance select the largest memory available.
|
||||||
|
A 1536 MB function provides ~12x more CPU time than a 128 MB function.
|
||||||
|
|
||||||
|
When integrating with AWS API Gateway, ensure it is configured with the relevant
|
||||||
|
[binary media types](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-payload-encodings.html).
|
||||||
|
|
||||||
|
## Bundlers
|
||||||
|
|
||||||
|
### webpack
|
||||||
|
|
||||||
|
Ensure sharp is excluded from bundling via the
|
||||||
|
[externals](https://webpack.js.org/configuration/externals/)
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
```js
|
||||||
|
externals: {
|
||||||
|
'sharp': 'commonjs sharp'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### esbuild
|
||||||
|
|
||||||
|
Ensure sharp is excluded from bundling via the
|
||||||
|
[external](https://esbuild.github.io/api/#external)
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
```js
|
||||||
|
buildSync({
|
||||||
|
entryPoints: ['app.js'],
|
||||||
|
bundle: true,
|
||||||
|
platform: 'node',
|
||||||
|
external: ['sharp'],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
esbuild app.js --bundle --platform=node --external:sharp
|
||||||
|
```
|
||||||
|
|
||||||
|
For `serverless-esbuild`, ensure platform-specific binaries are installed
|
||||||
|
via the `serverless.yml` configuration.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
custom:
|
||||||
|
esbuild:
|
||||||
|
external:
|
||||||
|
- sharp
|
||||||
|
packagerOptions:
|
||||||
|
scripts:
|
||||||
|
- npm install --force @sharpen/sharp-linux-x64
|
||||||
|
```
|
||||||
|
|
||||||
|
## TypeScript
|
||||||
|
|
||||||
|
TypeScript definitions are published as part of
|
||||||
|
the `sharp` package from v0.32.0.
|
||||||
|
|
||||||
|
Previously these were available via the `@types/sharp` package,
|
||||||
|
which is now deprecated.
|
||||||
|
|
||||||
|
When using Typescript, please ensure `devDependencies` includes
|
||||||
|
the `@types/node` package.
|
||||||
|
|
||||||
|
## Fonts
|
||||||
|
|
||||||
|
When creating text images or rendering SVG images that contain text elements,
|
||||||
|
`fontconfig` is used to find the relevant fonts.
|
||||||
|
|
||||||
|
On Windows and macOS systems, all system fonts are available for use.
|
||||||
|
|
||||||
|
On macOS systems using Homebrew, you may need to set the
|
||||||
|
`PANGOCAIRO_BACKEND` environment variable to a value of `fontconfig`
|
||||||
|
to ensure it is used for font discovery instead of Core Text.
|
||||||
|
|
||||||
|
On Linux systems, fonts that include the relevant
|
||||||
|
[`fontconfig` configuration](https://www.freedesktop.org/software/fontconfig/fontconfig-user.html)
|
||||||
|
when installed via package manager are available for use.
|
||||||
|
|
||||||
|
If `fontconfig` configuration is not found, the following error will occur:
|
||||||
|
```
|
||||||
|
Fontconfig error: Cannot load default config file
|
||||||
|
```
|
||||||
|
|
||||||
|
In serverless environments where there is no control over font packages,
|
||||||
|
use the `FONTCONFIG_PATH` environment variable to point to a custom location.
|
||||||
|
|
||||||
|
Embedded SVG fonts are unsupported.
|
||||||
|
|
||||||
|
## Worker threads
|
||||||
|
|
||||||
|
On some platforms, including glibc-based Linux,
|
||||||
|
the main thread must call `require('sharp')`
|
||||||
|
_before_ worker threads are created.
|
||||||
|
This is to ensure shared libraries remain loaded in memory
|
||||||
|
until after all threads are complete.
|
||||||
|
|
||||||
|
Without this, the following error may occur:
|
||||||
|
```
|
||||||
|
Module did not self-register
|
||||||
|
```
|
||||||
|
|
||||||
|
## Known conflicts
|
||||||
|
|
||||||
|
### Canvas and Windows
|
||||||
|
|
||||||
|
If both `canvas` and `sharp` modules are used in the same Windows process, the following error may occur:
|
||||||
|
```
|
||||||
|
The specified procedure could not be found.
|
||||||
|
```
|
||||||
111
docs/performance.md
Normal file
111
docs/performance.md
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
# Performance
|
||||||
|
|
||||||
|
A test to benchmark the performance of this module relative to alternatives.
|
||||||
|
|
||||||
|
Greater libvips performance can be expected with caching enabled (default)
|
||||||
|
and using 8+ core machines, especially those with larger L1/L2 CPU caches.
|
||||||
|
|
||||||
|
The I/O limits of the relevant (de)compression library will generally determine maximum throughput.
|
||||||
|
|
||||||
|
## Contenders
|
||||||
|
|
||||||
|
* [jimp](https://www.npmjs.com/package/jimp) v0.22.7 - Image processing in pure JavaScript.
|
||||||
|
* [imagemagick](https://www.npmjs.com/package/imagemagick) v0.1.3 - Supports filesystem only and "*has been unmaintained for a long time*".
|
||||||
|
* [gm](https://www.npmjs.com/package/gm) v1.25.0 - Fully featured wrapper around GraphicsMagick's `gm` command line utility.
|
||||||
|
* [@squoosh/lib](https://www.npmjs.com/package/@squoosh/lib) v0.4.0 - Image libraries transpiled to WebAssembly, includes GPLv3 code, but "*Project no longer maintained*".
|
||||||
|
* [@squoosh/cli](https://www.npmjs.com/package/@squoosh/cli) v0.7.3 - Command line wrapper around `@squoosh/lib`, avoids GPLv3 by spawning process, but "*Project no longer maintained*".
|
||||||
|
* sharp v0.32.0 / libvips v8.14.2 - Caching within libvips disabled to ensure a fair comparison.
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
|
||||||
|
### AMD64
|
||||||
|
|
||||||
|
* AWS EC2 us-east-2 [c6a.xlarge](https://aws.amazon.com/ec2/instance-types/c6a/) (4x AMD EPYC 7R13)
|
||||||
|
* Ubuntu 22.04 20230303 (ami-0122295b0eb922138)
|
||||||
|
* Node.js 16.19.1
|
||||||
|
|
||||||
|
### ARM64
|
||||||
|
|
||||||
|
* AWS EC2 us-east-2 [c7g.xlarge](https://aws.amazon.com/ec2/instance-types/c7g/) (4x ARM Graviton3)
|
||||||
|
* Ubuntu 22.04 20230303 (ami-0af198159897e7a29)
|
||||||
|
* Node.js 16.19.1
|
||||||
|
|
||||||
|
## Task: JPEG
|
||||||
|
|
||||||
|
Decompress a 2725x2225 JPEG image,
|
||||||
|
resize to 720x588 using Lanczos 3 resampling (where available),
|
||||||
|
then compress to JPEG at a "quality" setting of 80.
|
||||||
|
|
||||||
|
Note: jimp does not support Lanczos 3, bicubic resampling used instead.
|
||||||
|
|
||||||
|
#### Results: JPEG (AMD64)
|
||||||
|
|
||||||
|
| Module | Input | Output | Ops/sec | Speed-up |
|
||||||
|
| :----------------- | :----- | :----- | ------: | -------: |
|
||||||
|
| jimp | buffer | buffer | 0.84 | 1.0 |
|
||||||
|
| squoosh-cli | file | file | 1.07 | 1.3 |
|
||||||
|
| squoosh-lib | buffer | buffer | 1.82 | 2.2 |
|
||||||
|
| gm | buffer | buffer | 8.41 | 10.0 |
|
||||||
|
| gm | file | file | 8.45 | 10.0 |
|
||||||
|
| imagemagick | file | file | 8.77 | 10.4 |
|
||||||
|
| sharp | stream | stream | 36.36 | 43.3 |
|
||||||
|
| sharp | file | file | 38.67 | 46.0 |
|
||||||
|
| sharp | buffer | buffer | 39.44 | 47.0 |
|
||||||
|
|
||||||
|
#### Results: JPEG (ARM64)
|
||||||
|
|
||||||
|
| Module | Input | Output | Ops/sec | Speed-up |
|
||||||
|
| :----------------- | :----- | :----- | ------: | -------: |
|
||||||
|
| jimp | buffer | buffer | 1.02 | 1.0 |
|
||||||
|
| squoosh-cli | file | file | 1.11 | 1.1 |
|
||||||
|
| squoosh-lib | buffer | buffer | 2.08 | 2.0 |
|
||||||
|
| gm | buffer | buffer | 8.80 | 8.6 |
|
||||||
|
| gm | file | file | 10.05 | 9.9 |
|
||||||
|
| imagemagick | file | file | 10.28 | 10.1 |
|
||||||
|
| sharp | stream | stream | 26.87 | 26.3 |
|
||||||
|
| sharp | file | file | 27.88 | 27.3 |
|
||||||
|
| sharp | buffer | buffer | 28.40 | 27.8 |
|
||||||
|
|
||||||
|
## Task: PNG
|
||||||
|
|
||||||
|
Decompress a 2048x1536 RGBA PNG image,
|
||||||
|
premultiply the alpha channel,
|
||||||
|
resize to 720x540 using Lanczos 3 resampling (where available),
|
||||||
|
unpremultiply then compress as PNG with a "default" zlib compression level of 6
|
||||||
|
and without adaptive filtering.
|
||||||
|
|
||||||
|
Note: jimp does not support premultiply/unpremultiply.
|
||||||
|
|
||||||
|
### Results: PNG (AMD64)
|
||||||
|
|
||||||
|
| Module | Input | Output | Ops/sec | Speed-up |
|
||||||
|
| :----------------- | :----- | :----- | ------: | -------: |
|
||||||
|
| squoosh-cli | file | file | 0.40 | 1.0 |
|
||||||
|
| squoosh-lib | buffer | buffer | 0.47 | 1.2 |
|
||||||
|
| gm | file | file | 6.47 | 16.2 |
|
||||||
|
| jimp | buffer | buffer | 6.60 | 16.5 |
|
||||||
|
| imagemagick | file | file | 7.08 | 17.7 |
|
||||||
|
| sharp | file | file | 17.80 | 44.5 |
|
||||||
|
| sharp | buffer | buffer | 18.02 | 45.0 |
|
||||||
|
|
||||||
|
### Results: PNG (ARM64)
|
||||||
|
|
||||||
|
| Module | Input | Output | Ops/sec | Speed-up |
|
||||||
|
| :----------------- | :----- | :----- | ------: | -------: |
|
||||||
|
| squoosh-cli | file | file | 0.40 | 1.0 |
|
||||||
|
| squoosh-lib | buffer | buffer | 0.48 | 1.2 |
|
||||||
|
| gm | file | file | 7.20 | 18.0 |
|
||||||
|
| jimp | buffer | buffer | 7.62 | 19.1 |
|
||||||
|
| imagemagick | file | file | 7.96 | 19.9 |
|
||||||
|
| sharp | file | file | 12.97 | 32.4 |
|
||||||
|
| sharp | buffer | buffer | 13.12 | 32.8 |
|
||||||
|
|
||||||
|
## Running the benchmark test
|
||||||
|
|
||||||
|
Requires Docker.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/lovell/sharp.git
|
||||||
|
cd sharp/test/bench
|
||||||
|
./run-with-docker.sh
|
||||||
|
```
|
||||||
2
docs/robots.txt
Normal file
2
docs/robots.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
User-agent: *
|
||||||
|
Disallow:
|
||||||
1
docs/search-index.json
Normal file
1
docs/search-index.json
Normal file
File diff suppressed because one or more lines are too long
64
docs/search-index/build.js
Normal file
64
docs/search-index/build.js
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { extractDescription, extractKeywords, extractParameters } = require('./extract');
|
||||||
|
|
||||||
|
const searchIndex = [];
|
||||||
|
|
||||||
|
// Install
|
||||||
|
const contents = fs.readFileSync(path.join(__dirname, '..', 'install.md'), 'utf8');
|
||||||
|
const matches = contents.matchAll(
|
||||||
|
/## (?<title>[A-Za-z0-9 ]+)\n\n(?<body>[^#]+)/gs
|
||||||
|
);
|
||||||
|
for (const match of matches) {
|
||||||
|
const { title, body } = match.groups;
|
||||||
|
const description = extractDescription(body);
|
||||||
|
|
||||||
|
searchIndex.push({
|
||||||
|
t: title,
|
||||||
|
d: description,
|
||||||
|
k: extractKeywords(`${title} ${description}`),
|
||||||
|
l: `/install#${title.toLowerCase().replace(/ /g, '-')}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// API
|
||||||
|
[
|
||||||
|
'constructor',
|
||||||
|
'input',
|
||||||
|
'output',
|
||||||
|
'resize',
|
||||||
|
'composite',
|
||||||
|
'operation',
|
||||||
|
'channel',
|
||||||
|
'colour',
|
||||||
|
'utility'
|
||||||
|
].forEach((section) => {
|
||||||
|
const contents = fs.readFileSync(path.join(__dirname, '..', `api-${section}.md`), 'utf8');
|
||||||
|
const matches = contents.matchAll(
|
||||||
|
/## (?<title>[A-Za-z]+)\n[^\n]+\n(?<firstparagraph>.+?)\n\n.+?(?<parameters>\| Param .+?\n\n)?\*\*Example/gs
|
||||||
|
);
|
||||||
|
for (const match of matches) {
|
||||||
|
const { title, firstparagraph, parameters } = match.groups;
|
||||||
|
const description = firstparagraph.startsWith('###')
|
||||||
|
? 'Constructor'
|
||||||
|
: extractDescription(firstparagraph);
|
||||||
|
const parameterNames = parameters ? extractParameters(parameters) : '';
|
||||||
|
|
||||||
|
searchIndex.push({
|
||||||
|
t: title,
|
||||||
|
d: description,
|
||||||
|
k: extractKeywords(`${title} ${description} ${parameterNames}`),
|
||||||
|
l: `/api-${section}#${title.toLowerCase()}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(__dirname, '..', 'search-index.json'),
|
||||||
|
JSON.stringify(searchIndex)
|
||||||
|
);
|
||||||
34
docs/search-index/extract.js
Normal file
34
docs/search-index/extract.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const stopWords = require('./stop-words');
|
||||||
|
|
||||||
|
const extractDescription = (str) =>
|
||||||
|
str
|
||||||
|
.replace(/### Examples.*/sg, '')
|
||||||
|
.replace(/\(http[^)]+/g, '')
|
||||||
|
.replace(/\s+/g, ' ')
|
||||||
|
.replace(/[^A-Za-z0-9_/\-,. ]/g, '')
|
||||||
|
.replace(/\s+/g, ' ')
|
||||||
|
.substring(0, 200)
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
const extractParameters = (str) =>
|
||||||
|
[...str.matchAll(/options\.(?<name>[^.`\] ]+)/gs)]
|
||||||
|
.map((match) => match.groups.name)
|
||||||
|
.map((name) => name.replace(/([A-Z])/g, ' $1').toLowerCase())
|
||||||
|
.join(' ');
|
||||||
|
|
||||||
|
const extractKeywords = (str) =>
|
||||||
|
[
|
||||||
|
...new Set(
|
||||||
|
str
|
||||||
|
.split(/[ -/]/)
|
||||||
|
.map((word) => word.toLowerCase().replace(/[^a-z]/g, ''))
|
||||||
|
.filter((word) => word.length > 2 && word.length < 15 && !stopWords.includes(word))
|
||||||
|
)
|
||||||
|
].join(' ');
|
||||||
|
|
||||||
|
module.exports = { extractDescription, extractKeywords, extractParameters };
|
||||||
135
docs/search-index/stop-words.js
Normal file
135
docs/search-index/stop-words.js
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
'about',
|
||||||
|
'after',
|
||||||
|
'all',
|
||||||
|
'allows',
|
||||||
|
'already',
|
||||||
|
'also',
|
||||||
|
'alternative',
|
||||||
|
'always',
|
||||||
|
'and',
|
||||||
|
'any',
|
||||||
|
'are',
|
||||||
|
'available',
|
||||||
|
'based',
|
||||||
|
'been',
|
||||||
|
'before',
|
||||||
|
'best',
|
||||||
|
'both',
|
||||||
|
'call',
|
||||||
|
'callback',
|
||||||
|
'can',
|
||||||
|
'containing',
|
||||||
|
'contains',
|
||||||
|
'created',
|
||||||
|
'current',
|
||||||
|
'date',
|
||||||
|
'default',
|
||||||
|
'does',
|
||||||
|
'each',
|
||||||
|
'either',
|
||||||
|
'ensure',
|
||||||
|
'entirely',
|
||||||
|
'etc',
|
||||||
|
'every',
|
||||||
|
'except',
|
||||||
|
'following',
|
||||||
|
'for',
|
||||||
|
'from',
|
||||||
|
'get',
|
||||||
|
'gets',
|
||||||
|
'given',
|
||||||
|
'has',
|
||||||
|
'have',
|
||||||
|
'how',
|
||||||
|
'image',
|
||||||
|
'implies',
|
||||||
|
'include',
|
||||||
|
'including',
|
||||||
|
'involve',
|
||||||
|
'its',
|
||||||
|
'last',
|
||||||
|
'least',
|
||||||
|
'lots',
|
||||||
|
'make',
|
||||||
|
'may',
|
||||||
|
'more',
|
||||||
|
'most',
|
||||||
|
'much',
|
||||||
|
'must',
|
||||||
|
'non',
|
||||||
|
'not',
|
||||||
|
'now',
|
||||||
|
'occur',
|
||||||
|
'occurs',
|
||||||
|
'one',
|
||||||
|
'options',
|
||||||
|
'other',
|
||||||
|
'out',
|
||||||
|
'over',
|
||||||
|
'part',
|
||||||
|
'perform',
|
||||||
|
'performs',
|
||||||
|
'please',
|
||||||
|
'pre',
|
||||||
|
'previously',
|
||||||
|
'produce',
|
||||||
|
'provide',
|
||||||
|
'provided',
|
||||||
|
'ready',
|
||||||
|
'requires',
|
||||||
|
'requiresharp',
|
||||||
|
'returned',
|
||||||
|
'run',
|
||||||
|
'same',
|
||||||
|
'see',
|
||||||
|
'set',
|
||||||
|
'sets',
|
||||||
|
'sharp',
|
||||||
|
'should',
|
||||||
|
'since',
|
||||||
|
'site',
|
||||||
|
'some',
|
||||||
|
'specified',
|
||||||
|
'spelling',
|
||||||
|
'such',
|
||||||
|
'support',
|
||||||
|
'supported',
|
||||||
|
'sure',
|
||||||
|
'take',
|
||||||
|
'task',
|
||||||
|
'than',
|
||||||
|
'that',
|
||||||
|
'the',
|
||||||
|
'their',
|
||||||
|
'then',
|
||||||
|
'there',
|
||||||
|
'therefore',
|
||||||
|
'these',
|
||||||
|
'this',
|
||||||
|
'under',
|
||||||
|
'unless',
|
||||||
|
'unmaintained',
|
||||||
|
'unsuitable',
|
||||||
|
'until',
|
||||||
|
'use',
|
||||||
|
'used',
|
||||||
|
'using',
|
||||||
|
'value',
|
||||||
|
'values',
|
||||||
|
'via',
|
||||||
|
'were',
|
||||||
|
'when',
|
||||||
|
'which',
|
||||||
|
'while',
|
||||||
|
'will',
|
||||||
|
'with',
|
||||||
|
'without',
|
||||||
|
'you',
|
||||||
|
'your'
|
||||||
|
];
|
||||||
Binary file not shown.
Binary file not shown.
589
index.js
589
index.js
@@ -1,589 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var path = require('path');
|
|
||||||
var util = require('util');
|
|
||||||
var stream = require('stream');
|
|
||||||
|
|
||||||
var semver = require('semver');
|
|
||||||
var color = require('color');
|
|
||||||
var BluebirdPromise = require('bluebird');
|
|
||||||
|
|
||||||
var sharp = require('./build/Release/sharp');
|
|
||||||
var libvipsVersion = sharp.libvipsVersion();
|
|
||||||
|
|
||||||
var Sharp = function(input) {
|
|
||||||
if (!(this instanceof Sharp)) {
|
|
||||||
return new Sharp(input);
|
|
||||||
}
|
|
||||||
stream.Duplex.call(this);
|
|
||||||
this.options = {
|
|
||||||
// input options
|
|
||||||
bufferIn: null,
|
|
||||||
streamIn: false,
|
|
||||||
sequentialRead: false,
|
|
||||||
// ICC profiles
|
|
||||||
iccProfilePath: path.join(__dirname, 'icc') + path.sep,
|
|
||||||
// resize options
|
|
||||||
topOffsetPre: -1,
|
|
||||||
leftOffsetPre: -1,
|
|
||||||
widthPre: -1,
|
|
||||||
heightPre: -1,
|
|
||||||
topOffsetPost: -1,
|
|
||||||
leftOffsetPost: -1,
|
|
||||||
widthPost: -1,
|
|
||||||
heightPost: -1,
|
|
||||||
width: -1,
|
|
||||||
height: -1,
|
|
||||||
canvas: 'c',
|
|
||||||
gravity: 0,
|
|
||||||
angle: 0,
|
|
||||||
flip: false,
|
|
||||||
flop: false,
|
|
||||||
withoutEnlargement: false,
|
|
||||||
interpolator: 'bilinear',
|
|
||||||
// operations
|
|
||||||
background: [0, 0, 0, 255],
|
|
||||||
flatten: false,
|
|
||||||
blurSigma: 0,
|
|
||||||
sharpenRadius: 0,
|
|
||||||
sharpenFlat: 1,
|
|
||||||
sharpenJagged: 2,
|
|
||||||
gamma: 0,
|
|
||||||
greyscale: false,
|
|
||||||
// output options
|
|
||||||
output: '__input',
|
|
||||||
progressive: false,
|
|
||||||
quality: 80,
|
|
||||||
compressionLevel: 6,
|
|
||||||
withoutAdaptiveFiltering: false,
|
|
||||||
streamOut: false,
|
|
||||||
withMetadata: false
|
|
||||||
};
|
|
||||||
if (typeof input === 'string') {
|
|
||||||
// input=file
|
|
||||||
this.options.fileIn = input;
|
|
||||||
} else if (typeof input === 'object' && input instanceof Buffer) {
|
|
||||||
// input=buffer
|
|
||||||
if (
|
|
||||||
(input.length > 3) &&
|
|
||||||
// JPEG
|
|
||||||
(input[0] === 0xFF && input[1] === 0xD8) ||
|
|
||||||
// PNG
|
|
||||||
(input[0] === 0x89 && input[1] === 0x50) ||
|
|
||||||
// WebP
|
|
||||||
(input[0] === 0x52 && input[1] === 0x49) ||
|
|
||||||
// TIFF
|
|
||||||
(input[0] === 0x4D && input[1] === 0x4D && input[2] === 0x00 && (input[3] === 0x2A || input[3] === 0x2B)) ||
|
|
||||||
(input[0] === 0x49 && input[1] === 0x49 && (input[2] === 0x2A || input[2] === 0x2B) && input[3] === 0x00)
|
|
||||||
) {
|
|
||||||
this.options.bufferIn = input;
|
|
||||||
} else {
|
|
||||||
throw new Error('Buffer contains an unsupported image format. JPEG, PNG, WebP and TIFF are currently supported.');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// input=stream
|
|
||||||
this.options.streamIn = true;
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
module.exports = Sharp;
|
|
||||||
util.inherits(Sharp, stream.Duplex);
|
|
||||||
|
|
||||||
/*
|
|
||||||
Handle incoming chunk on Writable Stream
|
|
||||||
*/
|
|
||||||
Sharp.prototype._write = function(chunk, encoding, callback) {
|
|
||||||
/*jslint unused: false */
|
|
||||||
if (this.options.streamIn) {
|
|
||||||
if (typeof chunk === 'object' && chunk instanceof Buffer) {
|
|
||||||
if (this.options.bufferIn instanceof Buffer) {
|
|
||||||
// Append to existing Buffer
|
|
||||||
this.options.bufferIn = Buffer.concat(
|
|
||||||
[this.options.bufferIn, chunk],
|
|
||||||
this.options.bufferIn.length + chunk.length
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Create new Buffer
|
|
||||||
this.options.bufferIn = new Buffer(chunk.length);
|
|
||||||
chunk.copy(this.options.bufferIn);
|
|
||||||
}
|
|
||||||
callback();
|
|
||||||
} else {
|
|
||||||
callback(new Error('Non-Buffer data on Writable Stream'));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
callback(new Error('Unexpected data on Writable Stream'));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Crop this part of the resized image (Center/Centre, North, East, South, West)
|
|
||||||
module.exports.gravity = {'center': 0, 'centre': 0, 'north': 1, 'east': 2, 'south': 3, 'west': 4};
|
|
||||||
|
|
||||||
Sharp.prototype.crop = function(gravity) {
|
|
||||||
this.options.canvas = 'c';
|
|
||||||
if (typeof gravity === 'number' && !Number.isNaN(gravity) && gravity >= 0 && gravity <= 4) {
|
|
||||||
this.options.gravity = gravity;
|
|
||||||
} else {
|
|
||||||
throw new Error('Unsupported crop gravity ' + gravity);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
Sharp.prototype.extract = function(topOffset, leftOffset, width, height) {
|
|
||||||
/*jslint unused: false */
|
|
||||||
var suffix = this.options.width === -1 && this.options.height === -1 ? 'Pre' : 'Post';
|
|
||||||
var values = arguments;
|
|
||||||
['topOffset', 'leftOffset', 'width', 'height'].forEach(function(name, index) {
|
|
||||||
this.options[name + suffix] = values[index];
|
|
||||||
}.bind(this));
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Set the background colour for embed and flatten operations.
|
|
||||||
Delegates to the 'Color' module, which can throw an Error
|
|
||||||
but is liberal in what it accepts, clamping values to sensible min/max.
|
|
||||||
*/
|
|
||||||
Sharp.prototype.background = function(rgba) {
|
|
||||||
var colour = color(rgba);
|
|
||||||
this.options.background = colour.rgbArray();
|
|
||||||
this.options.background.push(colour.alpha() * 255);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
Sharp.prototype.embed = function() {
|
|
||||||
this.options.canvas = 'e';
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
Sharp.prototype.max = function() {
|
|
||||||
this.options.canvas = 'm';
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
Sharp.prototype.flatten = function(flatten) {
|
|
||||||
this.options.flatten = (typeof flatten === 'boolean') ? flatten : true;
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Rotate output image by 0, 90, 180 or 270 degrees
|
|
||||||
Auto-rotation based on the EXIF Orientation tag is represented by an angle of -1
|
|
||||||
*/
|
|
||||||
Sharp.prototype.rotate = function(angle) {
|
|
||||||
if (typeof angle === 'undefined') {
|
|
||||||
this.options.angle = -1;
|
|
||||||
} else if (!Number.isNaN(angle) && [0, 90, 180, 270].indexOf(angle) !== -1) {
|
|
||||||
this.options.angle = angle;
|
|
||||||
} else {
|
|
||||||
throw new Error('Unsupported angle (0, 90, 180, 270) ' + angle);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Flip the image vertically, about the Y axis
|
|
||||||
*/
|
|
||||||
Sharp.prototype.flip = function(flip) {
|
|
||||||
this.options.flip = (typeof flip === 'boolean') ? flip : true;
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Flop the image horizontally, about the X axis
|
|
||||||
*/
|
|
||||||
Sharp.prototype.flop = function(flop) {
|
|
||||||
this.options.flop = (typeof flop === 'boolean') ? flop : true;
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Do not enlarge the output if the input width *or* height are already less than the required dimensions
|
|
||||||
This is equivalent to GraphicsMagick's ">" geometry option:
|
|
||||||
"change the dimensions of the image only if its width or height exceeds the geometry specification"
|
|
||||||
*/
|
|
||||||
Sharp.prototype.withoutEnlargement = function(withoutEnlargement) {
|
|
||||||
this.options.withoutEnlargement = (typeof withoutEnlargement === 'boolean') ? withoutEnlargement : true;
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Blur the output image.
|
|
||||||
Call without a sigma to use a fast, mild blur.
|
|
||||||
Call with a sigma to use a slower, more accurate Gaussian blur.
|
|
||||||
*/
|
|
||||||
Sharp.prototype.blur = function(sigma) {
|
|
||||||
if (typeof sigma === 'undefined') {
|
|
||||||
// No arguments: default to mild blur
|
|
||||||
this.options.blurSigma = -1;
|
|
||||||
} else if (typeof sigma === 'boolean') {
|
|
||||||
// Boolean argument: apply mild blur?
|
|
||||||
this.options.blurSigma = sigma ? -1 : 0;
|
|
||||||
} else if (typeof sigma === 'number' && !Number.isNaN(sigma) && sigma >= 0.3 && sigma <= 1000) {
|
|
||||||
// Numeric argument: specific sigma
|
|
||||||
this.options.blurSigma = sigma;
|
|
||||||
} else {
|
|
||||||
throw new Error('Invalid blur sigma (0.3 to 1000.0) ' + sigma);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Sharpen the output image.
|
|
||||||
Call without a radius to use a fast, mild sharpen.
|
|
||||||
Call with a radius to use a slow, accurate sharpen using the L of LAB colour space.
|
|
||||||
radius - size of mask in pixels, must be integer
|
|
||||||
flat - level of "flat" area sharpen, default 1
|
|
||||||
jagged - level of "jagged" area sharpen, default 2
|
|
||||||
*/
|
|
||||||
Sharp.prototype.sharpen = function(radius, flat, jagged) {
|
|
||||||
if (typeof radius === 'undefined') {
|
|
||||||
// No arguments: default to mild sharpen
|
|
||||||
this.options.sharpenRadius = -1;
|
|
||||||
} else if (typeof radius === 'boolean') {
|
|
||||||
// Boolean argument: apply mild sharpen?
|
|
||||||
this.options.sharpenRadius = radius ? -1 : 0;
|
|
||||||
} else if (typeof radius === 'number' && !Number.isNaN(radius) && (radius % 1 === 0) && radius >= 1) {
|
|
||||||
// Numeric argument: specific radius
|
|
||||||
this.options.sharpenRadius = radius;
|
|
||||||
// Control over flat areas
|
|
||||||
if (typeof flat !== 'undefined' && flat !== null) {
|
|
||||||
if (typeof flat === 'number' && !Number.isNaN(flat) && flat >= 0) {
|
|
||||||
this.options.sharpenFlat = flat;
|
|
||||||
} else {
|
|
||||||
throw new Error('Invalid sharpen level for flat areas ' + flat + ' (expected >= 0)');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Control over jagged areas
|
|
||||||
if (typeof jagged !== 'undefined' && jagged !== null) {
|
|
||||||
if (typeof jagged === 'number' && !Number.isNaN(jagged) && jagged >= 0) {
|
|
||||||
this.options.sharpenJagged = jagged;
|
|
||||||
} else {
|
|
||||||
throw new Error('Invalid sharpen level for jagged areas ' + jagged + ' (expected >= 0)');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error('Invalid sharpen radius ' + radius + ' (expected integer >= 1)');
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Set the interpolator to use for the affine transformation
|
|
||||||
*/
|
|
||||||
module.exports.interpolator = {
|
|
||||||
nearest: 'nearest',
|
|
||||||
bilinear: 'bilinear',
|
|
||||||
bicubic: 'bicubic',
|
|
||||||
nohalo: 'nohalo',
|
|
||||||
locallyBoundedBicubic: 'lbb',
|
|
||||||
vertexSplitQuadraticBasisSpline: 'vsqbs'
|
|
||||||
};
|
|
||||||
Sharp.prototype.interpolateWith = function(interpolator) {
|
|
||||||
this.options.interpolator = interpolator;
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Darken image pre-resize (1/gamma) and brighten post-resize (gamma).
|
|
||||||
Improves brightness of resized image in non-linear colour spaces.
|
|
||||||
*/
|
|
||||||
Sharp.prototype.gamma = function(gamma) {
|
|
||||||
if (typeof gamma === 'undefined') {
|
|
||||||
// Default gamma correction of 2.2 (sRGB)
|
|
||||||
this.options.gamma = 2.2;
|
|
||||||
} else if (!Number.isNaN(gamma) && gamma >= 1 && gamma <= 3) {
|
|
||||||
this.options.gamma = gamma;
|
|
||||||
} else {
|
|
||||||
throw new Error('Invalid gamma correction (1.0 to 3.0) ' + gamma);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Convert to greyscale
|
|
||||||
*/
|
|
||||||
Sharp.prototype.greyscale = function(greyscale) {
|
|
||||||
this.options.greyscale = (typeof greyscale === 'boolean') ? greyscale : true;
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
Sharp.prototype.grayscale = Sharp.prototype.greyscale;
|
|
||||||
|
|
||||||
Sharp.prototype.progressive = function(progressive) {
|
|
||||||
this.options.progressive = (typeof progressive === 'boolean') ? progressive : true;
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
Sharp.prototype.sequentialRead = function(sequentialRead) {
|
|
||||||
this.options.sequentialRead = (typeof sequentialRead === 'boolean') ? sequentialRead : true;
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
Sharp.prototype.quality = function(quality) {
|
|
||||||
if (!Number.isNaN(quality) && quality >= 1 && quality <= 100) {
|
|
||||||
this.options.quality = quality;
|
|
||||||
} else {
|
|
||||||
throw new Error('Invalid quality (1 to 100) ' + quality);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
zlib compression level for PNG output
|
|
||||||
*/
|
|
||||||
Sharp.prototype.compressionLevel = function(compressionLevel) {
|
|
||||||
if (!Number.isNaN(compressionLevel) && compressionLevel >= 0 && compressionLevel <= 9) {
|
|
||||||
this.options.compressionLevel = compressionLevel;
|
|
||||||
} else {
|
|
||||||
throw new Error('Invalid compressionLevel (0 to 9) ' + compressionLevel);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Disable the use of adaptive row filtering for PNG output - requires libvips 7.41.0+
|
|
||||||
*/
|
|
||||||
Sharp.prototype.withoutAdaptiveFiltering = function(withoutAdaptiveFiltering) {
|
|
||||||
if (semver.gte(libvipsVersion, '7.41.0')) {
|
|
||||||
this.options.withoutAdaptiveFiltering = (typeof withoutAdaptiveFiltering === 'boolean') ? withoutAdaptiveFiltering : true;
|
|
||||||
} else {
|
|
||||||
console.error('withoutAdaptiveFiltering requires libvips 7.41.0+');
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
Sharp.prototype.withMetadata = function(withMetadata) {
|
|
||||||
this.options.withMetadata = (typeof withMetadata === 'boolean') ? withMetadata : true;
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
Sharp.prototype.resize = function(width, height) {
|
|
||||||
if (!width) {
|
|
||||||
this.options.width = -1;
|
|
||||||
} else {
|
|
||||||
if (typeof width === 'number' && !Number.isNaN(width)) {
|
|
||||||
this.options.width = width;
|
|
||||||
} else {
|
|
||||||
throw new Error('Invalid width ' + width);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!height) {
|
|
||||||
this.options.height = -1;
|
|
||||||
} else {
|
|
||||||
if (typeof height === 'number' && !Number.isNaN(height)) {
|
|
||||||
this.options.height = height;
|
|
||||||
} else {
|
|
||||||
throw new Error('Invalid height ' + height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Write output image data to a file
|
|
||||||
*/
|
|
||||||
Sharp.prototype.toFile = function(output, callback) {
|
|
||||||
if (!output || output.length === 0) {
|
|
||||||
var errOutputInvalid = new Error('Invalid output');
|
|
||||||
if (typeof callback === 'function') {
|
|
||||||
callback(errOutputInvalid);
|
|
||||||
} else {
|
|
||||||
return BluebirdPromise.reject(errOutputInvalid);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (this.options.fileIn === output) {
|
|
||||||
var errOutputIsInput = new Error('Cannot use same file for input and output');
|
|
||||||
if (typeof callback === 'function') {
|
|
||||||
callback(errOutputIsInput);
|
|
||||||
} else {
|
|
||||||
return BluebirdPromise.reject(errOutputIsInput);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.options.output = output;
|
|
||||||
return this._sharp(callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
Sharp.prototype.toBuffer = function(callback) {
|
|
||||||
return this._sharp(callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
Sharp.prototype.jpeg = function() {
|
|
||||||
this.options.output = '__jpeg';
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
Sharp.prototype.png = function() {
|
|
||||||
this.options.output = '__png';
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
Sharp.prototype.webp = function() {
|
|
||||||
this.options.output = '__webp';
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Used by a Writable Stream to notify that it is ready for data
|
|
||||||
*/
|
|
||||||
Sharp.prototype._read = function() {
|
|
||||||
if (!this.options.streamOut) {
|
|
||||||
this.options.streamOut = true;
|
|
||||||
this._sharp();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Invoke the C++ image processing pipeline
|
|
||||||
Supports callback, stream and promise variants
|
|
||||||
*/
|
|
||||||
Sharp.prototype._sharp = function(callback) {
|
|
||||||
var that = this;
|
|
||||||
if (typeof callback === 'function') {
|
|
||||||
// output=file/buffer
|
|
||||||
if (this.options.streamIn) {
|
|
||||||
// output=file/buffer, input=stream
|
|
||||||
this.on('finish', function() {
|
|
||||||
sharp.resize(that.options, callback);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// output=file/buffer, input=file/buffer
|
|
||||||
sharp.resize(this.options, callback);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
} else if (this.options.streamOut) {
|
|
||||||
// output=stream
|
|
||||||
if (this.options.streamIn) {
|
|
||||||
// output=stream, input=stream
|
|
||||||
this.on('finish', function() {
|
|
||||||
sharp.resize(that.options, function(err, data) {
|
|
||||||
if (err) {
|
|
||||||
that.emit('error', new Error(err));
|
|
||||||
} else {
|
|
||||||
that.push(data);
|
|
||||||
}
|
|
||||||
that.push(null);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// output=stream, input=file/buffer
|
|
||||||
sharp.resize(this.options, function(err, data) {
|
|
||||||
if (err) {
|
|
||||||
that.emit('error', new Error(err));
|
|
||||||
} else {
|
|
||||||
that.push(data);
|
|
||||||
}
|
|
||||||
that.push(null);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
} else {
|
|
||||||
// output=promise
|
|
||||||
if (this.options.streamIn) {
|
|
||||||
// output=promise, input=stream
|
|
||||||
return new BluebirdPromise(function(resolve, reject) {
|
|
||||||
that.on('finish', function() {
|
|
||||||
sharp.resize(that.options, function(err, data) {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
} else {
|
|
||||||
resolve(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// output=promise, input=file/buffer
|
|
||||||
return new BluebirdPromise(function(resolve, reject) {
|
|
||||||
sharp.resize(that.options, function(err, data) {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
} else {
|
|
||||||
resolve(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Reads the image header and returns metadata
|
|
||||||
Supports callback, stream and promise variants
|
|
||||||
*/
|
|
||||||
Sharp.prototype.metadata = function(callback) {
|
|
||||||
var that = this;
|
|
||||||
if (typeof callback === 'function') {
|
|
||||||
if (this.options.streamIn) {
|
|
||||||
this.on('finish', function() {
|
|
||||||
sharp.metadata(that.options, callback);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
sharp.metadata(this.options, callback);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
} else {
|
|
||||||
if (this.options.streamIn) {
|
|
||||||
return new BluebirdPromise(function(resolve, reject) {
|
|
||||||
that.on('finish', function() {
|
|
||||||
sharp.metadata(that.options, function(err, data) {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
} else {
|
|
||||||
resolve(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return new BluebirdPromise(function(resolve, reject) {
|
|
||||||
sharp.metadata(that.options, function(err, data) {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
} else {
|
|
||||||
resolve(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Get and set cache memory and item limits
|
|
||||||
*/
|
|
||||||
module.exports.cache = function(memory, items) {
|
|
||||||
if (typeof memory !== 'number' || Number.isNaN(memory)) {
|
|
||||||
memory = null;
|
|
||||||
}
|
|
||||||
if (typeof items !== 'number' || Number.isNaN(items)) {
|
|
||||||
items = null;
|
|
||||||
}
|
|
||||||
return sharp.cache(memory, items);
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Get and set size of thread pool
|
|
||||||
*/
|
|
||||||
module.exports.concurrency = function(concurrency) {
|
|
||||||
if (typeof concurrency !== 'number' || Number.isNaN(concurrency)) {
|
|
||||||
concurrency = null;
|
|
||||||
}
|
|
||||||
return sharp.concurrency(concurrency);
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Get internal counters
|
|
||||||
*/
|
|
||||||
module.exports.counters = function() {
|
|
||||||
return sharp.counters();
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Get the version of the libvips library
|
|
||||||
*/
|
|
||||||
module.exports.libvipsVersion = function() {
|
|
||||||
return libvipsVersion;
|
|
||||||
};
|
|
||||||
36
install/check.js
Normal file
36
install/check.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { useGlobalLibvips, globalLibvipsVersion, log, spawnRebuild } = require('../lib/libvips');
|
||||||
|
|
||||||
|
const buildFromSource = (msg) => {
|
||||||
|
log(msg);
|
||||||
|
log('Attempting to build from source via node-gyp');
|
||||||
|
try {
|
||||||
|
require('node-addon-api');
|
||||||
|
log('Found node-addon-api');
|
||||||
|
} catch (err) {
|
||||||
|
log('Please add node-addon-api to your dependencies');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const gyp = require('node-gyp');
|
||||||
|
log(`Found node-gyp version ${gyp().version}`);
|
||||||
|
} catch (err) {
|
||||||
|
log('Please add node-gyp to your dependencies');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log('See https://sharp.pixelplumbing.com/install#building-from-source');
|
||||||
|
const status = spawnRebuild();
|
||||||
|
if (status !== 0) {
|
||||||
|
process.exit(status);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (useGlobalLibvips()) {
|
||||||
|
buildFromSource(`Detected globally-installed libvips v${globalLibvipsVersion()}`);
|
||||||
|
} else if (process.env.npm_config_build_from_source) {
|
||||||
|
buildFromSource('Detected --build-from-source flag');
|
||||||
|
}
|
||||||
174
lib/channel.js
Normal file
174
lib/channel.js
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const is = require('./is');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boolean operations for bandbool.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const bool = {
|
||||||
|
and: 'and',
|
||||||
|
or: 'or',
|
||||||
|
eor: 'eor'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove alpha channel, if any. This is a no-op if the image does not have an alpha channel.
|
||||||
|
*
|
||||||
|
* See also {@link /api-operation#flatten|flatten}.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* sharp('rgba.png')
|
||||||
|
* .removeAlpha()
|
||||||
|
* .toFile('rgb.png', function(err, info) {
|
||||||
|
* // rgb.png is a 3 channel image without an alpha channel
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* @returns {Sharp}
|
||||||
|
*/
|
||||||
|
function removeAlpha () {
|
||||||
|
this.options.removeAlpha = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure the output image has an alpha transparency channel.
|
||||||
|
* If missing, the added alpha channel will have the specified
|
||||||
|
* transparency level, defaulting to fully-opaque (1).
|
||||||
|
* This is a no-op if the image already has an alpha channel.
|
||||||
|
*
|
||||||
|
* @since 0.21.2
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // rgba.png will be a 4 channel image with a fully-opaque alpha channel
|
||||||
|
* await sharp('rgb.jpg')
|
||||||
|
* .ensureAlpha()
|
||||||
|
* .toFile('rgba.png')
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // rgba is a 4 channel image with a fully-transparent alpha channel
|
||||||
|
* const rgba = await sharp(rgb)
|
||||||
|
* .ensureAlpha(0)
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @param {number} [alpha=1] - alpha transparency level (0=fully-transparent, 1=fully-opaque)
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid alpha transparency level
|
||||||
|
*/
|
||||||
|
function ensureAlpha (alpha) {
|
||||||
|
if (is.defined(alpha)) {
|
||||||
|
if (is.number(alpha) && is.inRange(alpha, 0, 1)) {
|
||||||
|
this.options.ensureAlpha = alpha;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('alpha', 'number between 0 and 1', alpha);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.options.ensureAlpha = 1;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract a single channel from a multi-channel image.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // green.jpg is a greyscale image containing the green channel of the input
|
||||||
|
* await sharp(input)
|
||||||
|
* .extractChannel('green')
|
||||||
|
* .toFile('green.jpg');
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // red1 is the red value of the first pixel, red2 the second pixel etc.
|
||||||
|
* const [red1, red2, ...] = await sharp(input)
|
||||||
|
* .extractChannel(0)
|
||||||
|
* .raw()
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @param {number|string} channel - zero-indexed channel/band number to extract, or `red`, `green`, `blue` or `alpha`.
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid channel
|
||||||
|
*/
|
||||||
|
function extractChannel (channel) {
|
||||||
|
const channelMap = { red: 0, green: 1, blue: 2, alpha: 3 };
|
||||||
|
if (Object.keys(channelMap).includes(channel)) {
|
||||||
|
channel = channelMap[channel];
|
||||||
|
}
|
||||||
|
if (is.integer(channel) && is.inRange(channel, 0, 4)) {
|
||||||
|
this.options.extractChannel = channel;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('channel', 'integer or one of: red, green, blue, alpha', channel);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Join one or more channels to the image.
|
||||||
|
* The meaning of the added channels depends on the output colourspace, set with `toColourspace()`.
|
||||||
|
* By default the output image will be web-friendly sRGB, with additional channels interpreted as alpha channels.
|
||||||
|
* Channel ordering follows vips convention:
|
||||||
|
* - sRGB: 0: Red, 1: Green, 2: Blue, 3: Alpha.
|
||||||
|
* - CMYK: 0: Magenta, 1: Cyan, 2: Yellow, 3: Black, 4: Alpha.
|
||||||
|
*
|
||||||
|
* Buffers may be any of the image formats supported by sharp.
|
||||||
|
* For raw pixel input, the `options` object should contain a `raw` attribute, which follows the format of the attribute of the same name in the `sharp()` constructor.
|
||||||
|
*
|
||||||
|
* @param {Array<string|Buffer>|string|Buffer} images - one or more images (file paths, Buffers).
|
||||||
|
* @param {Object} options - image options, see `sharp()` constructor.
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
function joinChannel (images, options) {
|
||||||
|
if (Array.isArray(images)) {
|
||||||
|
images.forEach(function (image) {
|
||||||
|
this.options.joinChannelIn.push(this._createInputDescriptor(image, options));
|
||||||
|
}, this);
|
||||||
|
} else {
|
||||||
|
this.options.joinChannelIn.push(this._createInputDescriptor(images, options));
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a bitwise boolean operation on all input image channels (bands) to produce a single channel output image.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* sharp('3-channel-rgb-input.png')
|
||||||
|
* .bandbool(sharp.bool.and)
|
||||||
|
* .toFile('1-channel-output.png', function (err, info) {
|
||||||
|
* // The output will be a single channel image where each pixel `P = R & G & B`.
|
||||||
|
* // If `I(1,1) = [247, 170, 14] = [0b11110111, 0b10101010, 0b00001111]`
|
||||||
|
* // then `O(1,1) = 0b11110111 & 0b10101010 & 0b00001111 = 0b00000010 = 2`.
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* @param {string} boolOp - one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
function bandbool (boolOp) {
|
||||||
|
if (is.string(boolOp) && is.inArray(boolOp, ['and', 'or', 'eor'])) {
|
||||||
|
this.options.bandBoolOp = boolOp;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('boolOp', 'one of: and, or, eor', boolOp);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorate the Sharp prototype with channel-related functions.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
module.exports = function (Sharp) {
|
||||||
|
Object.assign(Sharp.prototype, {
|
||||||
|
// Public instance functions
|
||||||
|
removeAlpha,
|
||||||
|
ensureAlpha,
|
||||||
|
extractChannel,
|
||||||
|
joinChannel,
|
||||||
|
bandbool
|
||||||
|
});
|
||||||
|
// Class attributes
|
||||||
|
Sharp.bool = bool;
|
||||||
|
};
|
||||||
184
lib/colour.js
Normal file
184
lib/colour.js
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const color = require('color');
|
||||||
|
const is = require('./is');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Colourspaces.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const colourspace = {
|
||||||
|
multiband: 'multiband',
|
||||||
|
'b-w': 'b-w',
|
||||||
|
bw: 'b-w',
|
||||||
|
cmyk: 'cmyk',
|
||||||
|
srgb: 'srgb'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tint the image using the provided chroma while preserving the image luminance.
|
||||||
|
* An alpha channel may be present and will be unchanged by the operation.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const output = await sharp(input)
|
||||||
|
* .tint({ r: 255, g: 240, b: 16 })
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @param {string|Object} rgb - parsed by the [color](https://www.npmjs.org/package/color) module to extract chroma values.
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid parameter
|
||||||
|
*/
|
||||||
|
function tint (rgb) {
|
||||||
|
const colour = color(rgb);
|
||||||
|
this.options.tintA = colour.a();
|
||||||
|
this.options.tintB = colour.b();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert to 8-bit greyscale; 256 shades of grey.
|
||||||
|
* This is a linear operation. If the input image is in a non-linear colour space such as sRGB, use `gamma()` with `greyscale()` for the best results.
|
||||||
|
* By default the output image will be web-friendly sRGB and contain three (identical) color channels.
|
||||||
|
* This may be overridden by other sharp operations such as `toColourspace('b-w')`,
|
||||||
|
* which will produce an output image containing one color channel.
|
||||||
|
* An alpha channel may be present, and will be unchanged by the operation.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const output = await sharp(input).greyscale().toBuffer();
|
||||||
|
*
|
||||||
|
* @param {Boolean} [greyscale=true]
|
||||||
|
* @returns {Sharp}
|
||||||
|
*/
|
||||||
|
function greyscale (greyscale) {
|
||||||
|
this.options.greyscale = is.bool(greyscale) ? greyscale : true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alternative spelling of `greyscale`.
|
||||||
|
* @param {Boolean} [grayscale=true]
|
||||||
|
* @returns {Sharp}
|
||||||
|
*/
|
||||||
|
function grayscale (grayscale) {
|
||||||
|
return this.greyscale(grayscale);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the pipeline colourspace.
|
||||||
|
*
|
||||||
|
* The input image will be converted to the provided colourspace at the start of the pipeline.
|
||||||
|
* All operations will use this colourspace before converting to the output colourspace,
|
||||||
|
* as defined by {@link #tocolourspace|toColourspace}.
|
||||||
|
*
|
||||||
|
* This feature is experimental and has not yet been fully-tested with all operations.
|
||||||
|
*
|
||||||
|
* @since 0.29.0
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Run pipeline in 16 bits per channel RGB while converting final result to 8 bits per channel sRGB.
|
||||||
|
* await sharp(input)
|
||||||
|
* .pipelineColourspace('rgb16')
|
||||||
|
* .toColourspace('srgb')
|
||||||
|
* .toFile('16bpc-pipeline-to-8bpc-output.png')
|
||||||
|
*
|
||||||
|
* @param {string} [colourspace] - pipeline colourspace e.g. `rgb16`, `scrgb`, `lab`, `grey16` [...](https://github.com/libvips/libvips/blob/41cff4e9d0838498487a00623462204eb10ee5b8/libvips/iofuncs/enumtypes.c#L774)
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
function pipelineColourspace (colourspace) {
|
||||||
|
if (!is.string(colourspace)) {
|
||||||
|
throw is.invalidParameterError('colourspace', 'string', colourspace);
|
||||||
|
}
|
||||||
|
this.options.colourspaceInput = colourspace;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alternative spelling of `pipelineColourspace`.
|
||||||
|
* @param {string} [colorspace] - pipeline colorspace.
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
function pipelineColorspace (colorspace) {
|
||||||
|
return this.pipelineColourspace(colorspace);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the output colourspace.
|
||||||
|
* By default output image will be web-friendly sRGB, with additional channels interpreted as alpha channels.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Output 16 bits per pixel RGB
|
||||||
|
* await sharp(input)
|
||||||
|
* .toColourspace('rgb16')
|
||||||
|
* .toFile('16-bpp.png')
|
||||||
|
*
|
||||||
|
* @param {string} [colourspace] - output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/libvips/libvips/blob/3c0bfdf74ce1dc37a6429bed47fa76f16e2cd70a/libvips/iofuncs/enumtypes.c#L777-L794)
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
function toColourspace (colourspace) {
|
||||||
|
if (!is.string(colourspace)) {
|
||||||
|
throw is.invalidParameterError('colourspace', 'string', colourspace);
|
||||||
|
}
|
||||||
|
this.options.colourspace = colourspace;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alternative spelling of `toColourspace`.
|
||||||
|
* @param {string} [colorspace] - output colorspace.
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
function toColorspace (colorspace) {
|
||||||
|
return this.toColourspace(colorspace);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a colour attribute of the this.options Object.
|
||||||
|
* @private
|
||||||
|
* @param {string} key
|
||||||
|
* @param {string|Object} value
|
||||||
|
* @throws {Error} Invalid value
|
||||||
|
*/
|
||||||
|
function _setBackgroundColourOption (key, value) {
|
||||||
|
if (is.defined(value)) {
|
||||||
|
if (is.object(value) || is.string(value)) {
|
||||||
|
const colour = color(value);
|
||||||
|
this.options[key] = [
|
||||||
|
colour.red(),
|
||||||
|
colour.green(),
|
||||||
|
colour.blue(),
|
||||||
|
Math.round(colour.alpha() * 255)
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('background', 'object or string', value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorate the Sharp prototype with colour-related functions.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
module.exports = function (Sharp) {
|
||||||
|
Object.assign(Sharp.prototype, {
|
||||||
|
// Public
|
||||||
|
tint,
|
||||||
|
greyscale,
|
||||||
|
grayscale,
|
||||||
|
pipelineColourspace,
|
||||||
|
pipelineColorspace,
|
||||||
|
toColourspace,
|
||||||
|
toColorspace,
|
||||||
|
// Private
|
||||||
|
_setBackgroundColourOption
|
||||||
|
});
|
||||||
|
// Class attributes
|
||||||
|
Sharp.colourspace = colourspace;
|
||||||
|
Sharp.colorspace = colourspace;
|
||||||
|
};
|
||||||
210
lib/composite.js
Normal file
210
lib/composite.js
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const is = require('./is');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blend modes.
|
||||||
|
* @member
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const blend = {
|
||||||
|
clear: 'clear',
|
||||||
|
source: 'source',
|
||||||
|
over: 'over',
|
||||||
|
in: 'in',
|
||||||
|
out: 'out',
|
||||||
|
atop: 'atop',
|
||||||
|
dest: 'dest',
|
||||||
|
'dest-over': 'dest-over',
|
||||||
|
'dest-in': 'dest-in',
|
||||||
|
'dest-out': 'dest-out',
|
||||||
|
'dest-atop': 'dest-atop',
|
||||||
|
xor: 'xor',
|
||||||
|
add: 'add',
|
||||||
|
saturate: 'saturate',
|
||||||
|
multiply: 'multiply',
|
||||||
|
screen: 'screen',
|
||||||
|
overlay: 'overlay',
|
||||||
|
darken: 'darken',
|
||||||
|
lighten: 'lighten',
|
||||||
|
'colour-dodge': 'colour-dodge',
|
||||||
|
'color-dodge': 'colour-dodge',
|
||||||
|
'colour-burn': 'colour-burn',
|
||||||
|
'color-burn': 'colour-burn',
|
||||||
|
'hard-light': 'hard-light',
|
||||||
|
'soft-light': 'soft-light',
|
||||||
|
difference: 'difference',
|
||||||
|
exclusion: 'exclusion'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composite image(s) over the processed (resized, extracted etc.) image.
|
||||||
|
*
|
||||||
|
* The images to composite must be the same size or smaller than the processed image.
|
||||||
|
* If both `top` and `left` options are provided, they take precedence over `gravity`.
|
||||||
|
*
|
||||||
|
* Any resize, rotate or extract operations in the same processing pipeline
|
||||||
|
* will always be applied to the input image before composition.
|
||||||
|
*
|
||||||
|
* The `blend` option can be one of `clear`, `source`, `over`, `in`, `out`, `atop`,
|
||||||
|
* `dest`, `dest-over`, `dest-in`, `dest-out`, `dest-atop`,
|
||||||
|
* `xor`, `add`, `saturate`, `multiply`, `screen`, `overlay`, `darken`, `lighten`,
|
||||||
|
* `colour-dodge`, `color-dodge`, `colour-burn`,`color-burn`,
|
||||||
|
* `hard-light`, `soft-light`, `difference`, `exclusion`.
|
||||||
|
*
|
||||||
|
* More information about blend modes can be found at
|
||||||
|
* https://www.libvips.org/API/current/libvips-conversion.html#VipsBlendMode
|
||||||
|
* and https://www.cairographics.org/operators/
|
||||||
|
*
|
||||||
|
* @since 0.22.0
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* await sharp(background)
|
||||||
|
* .composite([
|
||||||
|
* { input: layer1, gravity: 'northwest' },
|
||||||
|
* { input: layer2, gravity: 'southeast' },
|
||||||
|
* ])
|
||||||
|
* .toFile('combined.png');
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const output = await sharp('input.gif', { animated: true })
|
||||||
|
* .composite([
|
||||||
|
* { input: 'overlay.png', tile: true, blend: 'saturate' }
|
||||||
|
* ])
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* sharp('input.png')
|
||||||
|
* .rotate(180)
|
||||||
|
* .resize(300)
|
||||||
|
* .flatten( { background: '#ff6600' } )
|
||||||
|
* .composite([{ input: 'overlay.png', gravity: 'southeast' }])
|
||||||
|
* .sharpen()
|
||||||
|
* .withMetadata()
|
||||||
|
* .webp( { quality: 90 } )
|
||||||
|
* .toBuffer()
|
||||||
|
* .then(function(outputBuffer) {
|
||||||
|
* // outputBuffer contains upside down, 300px wide, alpha channel flattened
|
||||||
|
* // onto orange background, composited with overlay.png with SE gravity,
|
||||||
|
* // sharpened, with metadata, 90% quality WebP image data. Phew!
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* @param {Object[]} images - Ordered list of images to composite
|
||||||
|
* @param {Buffer|String} [images[].input] - Buffer containing image data, String containing the path to an image file, or Create object (see below)
|
||||||
|
* @param {Object} [images[].input.create] - describes a blank overlay to be created.
|
||||||
|
* @param {Number} [images[].input.create.width]
|
||||||
|
* @param {Number} [images[].input.create.height]
|
||||||
|
* @param {Number} [images[].input.create.channels] - 3-4
|
||||||
|
* @param {String|Object} [images[].input.create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
|
||||||
|
* @param {Object} [images[].input.text] - describes a new text image to be created.
|
||||||
|
* @param {string} [images[].input.text.text] - text to render as a UTF-8 string. It can contain Pango markup, for example `<i>Le</i>Monde`.
|
||||||
|
* @param {string} [images[].input.text.font] - font name to render with.
|
||||||
|
* @param {string} [images[].input.text.fontfile] - absolute filesystem path to a font file that can be used by `font`.
|
||||||
|
* @param {number} [images[].input.text.width=0] - integral number of pixels to word-wrap at. Lines of text wider than this will be broken at word boundaries.
|
||||||
|
* @param {number} [images[].input.text.height=0] - integral number of pixels high. When defined, `dpi` will be ignored and the text will automatically fit the pixel resolution defined by `width` and `height`. Will be ignored if `width` is not specified or set to 0.
|
||||||
|
* @param {string} [images[].input.text.align='left'] - text alignment (`'left'`, `'centre'`, `'center'`, `'right'`).
|
||||||
|
* @param {boolean} [images[].input.text.justify=false] - set this to true to apply justification to the text.
|
||||||
|
* @param {number} [images[].input.text.dpi=72] - the resolution (size) at which to render the text. Does not take effect if `height` is specified.
|
||||||
|
* @param {boolean} [images[].input.text.rgba=false] - set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for Pango markup features like `<span foreground="red">Red!</span>`.
|
||||||
|
* @param {number} [images[].input.text.spacing=0] - text line height in points. Will use the font line height if none is specified.
|
||||||
|
* @param {String} [images[].blend='over'] - how to blend this image with the image below.
|
||||||
|
* @param {String} [images[].gravity='centre'] - gravity at which to place the overlay.
|
||||||
|
* @param {Number} [images[].top] - the pixel offset from the top edge.
|
||||||
|
* @param {Number} [images[].left] - the pixel offset from the left edge.
|
||||||
|
* @param {Boolean} [images[].tile=false] - set to true to repeat the overlay image across the entire image with the given `gravity`.
|
||||||
|
* @param {Boolean} [images[].premultiplied=false] - set to true to avoid premultiplying the image below. Equivalent to the `--premultiplied` vips option.
|
||||||
|
* @param {Number} [images[].density=72] - number representing the DPI for vector overlay image.
|
||||||
|
* @param {Object} [images[].raw] - describes overlay when using raw pixel data.
|
||||||
|
* @param {Number} [images[].raw.width]
|
||||||
|
* @param {Number} [images[].raw.height]
|
||||||
|
* @param {Number} [images[].raw.channels]
|
||||||
|
* @param {boolean} [images[].animated=false] - Set to `true` to read all frames/pages of an animated image.
|
||||||
|
* @param {string} [images[].failOn='warning'] - @see {@link /api-constructor#parameters|constructor parameters}
|
||||||
|
* @param {number|boolean} [images[].limitInputPixels=268402689] - @see {@link /api-constructor#parameters|constructor parameters}
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
function composite (images) {
|
||||||
|
if (!Array.isArray(images)) {
|
||||||
|
throw is.invalidParameterError('images to composite', 'array', images);
|
||||||
|
}
|
||||||
|
this.options.composite = images.map(image => {
|
||||||
|
if (!is.object(image)) {
|
||||||
|
throw is.invalidParameterError('image to composite', 'object', image);
|
||||||
|
}
|
||||||
|
const inputOptions = this._inputOptionsFromObject(image);
|
||||||
|
const composite = {
|
||||||
|
input: this._createInputDescriptor(image.input, inputOptions, { allowStream: false }),
|
||||||
|
blend: 'over',
|
||||||
|
tile: false,
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
hasOffset: false,
|
||||||
|
gravity: 0,
|
||||||
|
premultiplied: false
|
||||||
|
};
|
||||||
|
if (is.defined(image.blend)) {
|
||||||
|
if (is.string(blend[image.blend])) {
|
||||||
|
composite.blend = blend[image.blend];
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('blend', 'valid blend name', image.blend);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(image.tile)) {
|
||||||
|
if (is.bool(image.tile)) {
|
||||||
|
composite.tile = image.tile;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('tile', 'boolean', image.tile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(image.left)) {
|
||||||
|
if (is.integer(image.left)) {
|
||||||
|
composite.left = image.left;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('left', 'integer', image.left);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(image.top)) {
|
||||||
|
if (is.integer(image.top)) {
|
||||||
|
composite.top = image.top;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('top', 'integer', image.top);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(image.top) !== is.defined(image.left)) {
|
||||||
|
throw new Error('Expected both left and top to be set');
|
||||||
|
} else {
|
||||||
|
composite.hasOffset = is.integer(image.top) && is.integer(image.left);
|
||||||
|
}
|
||||||
|
if (is.defined(image.gravity)) {
|
||||||
|
if (is.integer(image.gravity) && is.inRange(image.gravity, 0, 8)) {
|
||||||
|
composite.gravity = image.gravity;
|
||||||
|
} else if (is.string(image.gravity) && is.integer(this.constructor.gravity[image.gravity])) {
|
||||||
|
composite.gravity = this.constructor.gravity[image.gravity];
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('gravity', 'valid gravity', image.gravity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(image.premultiplied)) {
|
||||||
|
if (is.bool(image.premultiplied)) {
|
||||||
|
composite.premultiplied = image.premultiplied;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('premultiplied', 'boolean', image.premultiplied);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return composite;
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorate the Sharp prototype with composite-related functions.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
module.exports = function (Sharp) {
|
||||||
|
Sharp.prototype.composite = composite;
|
||||||
|
Sharp.blend = blend;
|
||||||
|
};
|
||||||
438
lib/constructor.js
Normal file
438
lib/constructor.js
Normal file
@@ -0,0 +1,438 @@
|
|||||||
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const util = require('node:util');
|
||||||
|
const stream = require('node:stream');
|
||||||
|
const is = require('./is');
|
||||||
|
|
||||||
|
require('./sharp');
|
||||||
|
|
||||||
|
// Use NODE_DEBUG=sharp to enable libvips warnings
|
||||||
|
const debuglog = util.debuglog('sharp');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor factory to create an instance of `sharp`, to which further methods are chained.
|
||||||
|
*
|
||||||
|
* JPEG, PNG, WebP, GIF, AVIF or TIFF format image data can be streamed out from this object.
|
||||||
|
* When using Stream based output, derived attributes are available from the `info` event.
|
||||||
|
*
|
||||||
|
* Non-critical problems encountered during processing are emitted as `warning` events.
|
||||||
|
*
|
||||||
|
* Implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.
|
||||||
|
*
|
||||||
|
* @constructs Sharp
|
||||||
|
*
|
||||||
|
* @emits Sharp#info
|
||||||
|
* @emits Sharp#warning
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* sharp('input.jpg')
|
||||||
|
* .resize(300, 200)
|
||||||
|
* .toFile('output.jpg', function(err) {
|
||||||
|
* // output.jpg is a 300 pixels wide and 200 pixels high image
|
||||||
|
* // containing a scaled and cropped version of input.jpg
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Read image data from readableStream,
|
||||||
|
* // resize to 300 pixels wide,
|
||||||
|
* // emit an 'info' event with calculated dimensions
|
||||||
|
* // and finally write image data to writableStream
|
||||||
|
* var transformer = sharp()
|
||||||
|
* .resize(300)
|
||||||
|
* .on('info', function(info) {
|
||||||
|
* console.log('Image height is ' + info.height);
|
||||||
|
* });
|
||||||
|
* readableStream.pipe(transformer).pipe(writableStream);
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Create a blank 300x200 PNG image of semi-translucent red pixels
|
||||||
|
* sharp({
|
||||||
|
* create: {
|
||||||
|
* width: 300,
|
||||||
|
* height: 200,
|
||||||
|
* channels: 4,
|
||||||
|
* background: { r: 255, g: 0, b: 0, alpha: 0.5 }
|
||||||
|
* }
|
||||||
|
* })
|
||||||
|
* .png()
|
||||||
|
* .toBuffer()
|
||||||
|
* .then( ... );
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Convert an animated GIF to an animated WebP
|
||||||
|
* await sharp('in.gif', { animated: true }).toFile('out.webp');
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Read a raw array of pixels and save it to a png
|
||||||
|
* const input = Uint8Array.from([255, 255, 255, 0, 0, 0]); // or Uint8ClampedArray
|
||||||
|
* const image = sharp(input, {
|
||||||
|
* // because the input does not contain its dimensions or how many channels it has
|
||||||
|
* // we need to specify it in the constructor options
|
||||||
|
* raw: {
|
||||||
|
* width: 2,
|
||||||
|
* height: 1,
|
||||||
|
* channels: 3
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
* await image.toFile('my-two-pixels.png');
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Generate RGB Gaussian noise
|
||||||
|
* await sharp({
|
||||||
|
* create: {
|
||||||
|
* width: 300,
|
||||||
|
* height: 200,
|
||||||
|
* channels: 3,
|
||||||
|
* noise: {
|
||||||
|
* type: 'gaussian',
|
||||||
|
* mean: 128,
|
||||||
|
* sigma: 30
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }).toFile('noise.png');
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Generate an image from text
|
||||||
|
* await sharp({
|
||||||
|
* text: {
|
||||||
|
* text: 'Hello, world!',
|
||||||
|
* width: 400, // max width
|
||||||
|
* height: 300 // max height
|
||||||
|
* }
|
||||||
|
* }).toFile('text_bw.png');
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Generate an rgba image from text using pango markup and font
|
||||||
|
* await sharp({
|
||||||
|
* text: {
|
||||||
|
* text: '<span foreground="red">Red!</span><span background="cyan">blue</span>',
|
||||||
|
* font: 'sans',
|
||||||
|
* rgba: true,
|
||||||
|
* dpi: 300
|
||||||
|
* }
|
||||||
|
* }).toFile('text_rgba.png');
|
||||||
|
*
|
||||||
|
* @param {(Buffer|ArrayBuffer|Uint8Array|Uint8ClampedArray|Int8Array|Uint16Array|Int16Array|Uint32Array|Int32Array|Float32Array|Float64Array|string)} [input] - if present, can be
|
||||||
|
* a Buffer / ArrayBuffer / Uint8Array / Uint8ClampedArray containing JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image data, or
|
||||||
|
* a TypedArray containing raw pixel image data, or
|
||||||
|
* a String containing the filesystem path to an JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image file.
|
||||||
|
* JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
|
||||||
|
* @param {Object} [options] - if present, is an Object with optional attributes.
|
||||||
|
* @param {string} [options.failOn='warning'] - when to abort processing of invalid pixel data, one of (in order of sensitivity): 'none' (least), 'truncated', 'error' or 'warning' (most), higher levels imply lower levels, invalid metadata will always abort.
|
||||||
|
* @param {number|boolean} [options.limitInputPixels=268402689] - Do not process input images where the number of pixels
|
||||||
|
* (width x height) exceeds this limit. Assumes image dimensions contained in the input metadata can be trusted.
|
||||||
|
* An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF).
|
||||||
|
* @param {boolean} [options.unlimited=false] - Set this to `true` to remove safety features that help prevent memory exhaustion (JPEG, PNG, SVG, HEIF).
|
||||||
|
* @param {boolean} [options.sequentialRead=true] - Set this to `false` to use random access rather than sequential read. Some operations will do this automatically.
|
||||||
|
* @param {number} [options.density=72] - number representing the DPI for vector images in the range 1 to 100000.
|
||||||
|
* @param {number} [options.ignoreIcc=false] - should the embedded ICC profile, if any, be ignored.
|
||||||
|
* @param {number} [options.pages=1] - Number of pages to extract for multi-page input (GIF, WebP, TIFF), use -1 for all pages.
|
||||||
|
* @param {number} [options.page=0] - Page number to start extracting from for multi-page input (GIF, WebP, TIFF), zero based.
|
||||||
|
* @param {number} [options.subifd=-1] - subIFD (Sub Image File Directory) to extract for OME-TIFF, defaults to main image.
|
||||||
|
* @param {number} [options.level=0] - level to extract from a multi-level input (OpenSlide), zero based.
|
||||||
|
* @param {boolean} [options.animated=false] - Set to `true` to read all frames/pages of an animated image (GIF, WebP, TIFF), equivalent of setting `pages` to `-1`.
|
||||||
|
* @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering.
|
||||||
|
* @param {number} [options.raw.width] - integral number of pixels wide.
|
||||||
|
* @param {number} [options.raw.height] - integral number of pixels high.
|
||||||
|
* @param {number} [options.raw.channels] - integral number of channels, between 1 and 4.
|
||||||
|
* @param {boolean} [options.raw.premultiplied] - specifies that the raw input has already been premultiplied, set to `true`
|
||||||
|
* to avoid sharp premultiplying the image. (optional, default `false`)
|
||||||
|
* @param {Object} [options.create] - describes a new image to be created.
|
||||||
|
* @param {number} [options.create.width] - integral number of pixels wide.
|
||||||
|
* @param {number} [options.create.height] - integral number of pixels high.
|
||||||
|
* @param {number} [options.create.channels] - integral number of channels, either 3 (RGB) or 4 (RGBA).
|
||||||
|
* @param {string|Object} [options.create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
|
||||||
|
* @param {Object} [options.create.noise] - describes a noise to be created.
|
||||||
|
* @param {string} [options.create.noise.type] - type of generated noise, currently only `gaussian` is supported.
|
||||||
|
* @param {number} [options.create.noise.mean] - mean of pixels in generated noise.
|
||||||
|
* @param {number} [options.create.noise.sigma] - standard deviation of pixels in generated noise.
|
||||||
|
* @param {Object} [options.text] - describes a new text image to be created.
|
||||||
|
* @param {string} [options.text.text] - text to render as a UTF-8 string. It can contain Pango markup, for example `<i>Le</i>Monde`.
|
||||||
|
* @param {string} [options.text.font] - font name to render with.
|
||||||
|
* @param {string} [options.text.fontfile] - absolute filesystem path to a font file that can be used by `font`.
|
||||||
|
* @param {number} [options.text.width=0] - Integral number of pixels to word-wrap at. Lines of text wider than this will be broken at word boundaries.
|
||||||
|
* @param {number} [options.text.height=0] - Maximum integral number of pixels high. When defined, `dpi` will be ignored and the text will automatically fit the pixel resolution defined by `width` and `height`. Will be ignored if `width` is not specified or set to 0.
|
||||||
|
* @param {string} [options.text.align='left'] - Alignment style for multi-line text (`'left'`, `'centre'`, `'center'`, `'right'`).
|
||||||
|
* @param {boolean} [options.text.justify=false] - set this to true to apply justification to the text.
|
||||||
|
* @param {number} [options.text.dpi=72] - the resolution (size) at which to render the text. Does not take effect if `height` is specified.
|
||||||
|
* @param {boolean} [options.text.rgba=false] - set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for pango markup features like `<span foreground="red">Red!</span>`.
|
||||||
|
* @param {number} [options.text.spacing=0] - text line height in points. Will use the font line height if none is specified.
|
||||||
|
* @param {string} [options.text.wrap='word'] - word wrapping style when width is provided, one of: 'word', 'char', 'charWord' (prefer char, fallback to word) or 'none'.
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
const Sharp = function (input, options) {
|
||||||
|
if (arguments.length === 1 && !is.defined(input)) {
|
||||||
|
throw new Error('Invalid input');
|
||||||
|
}
|
||||||
|
if (!(this instanceof Sharp)) {
|
||||||
|
return new Sharp(input, options);
|
||||||
|
}
|
||||||
|
stream.Duplex.call(this);
|
||||||
|
this.options = {
|
||||||
|
// resize options
|
||||||
|
topOffsetPre: -1,
|
||||||
|
leftOffsetPre: -1,
|
||||||
|
widthPre: -1,
|
||||||
|
heightPre: -1,
|
||||||
|
topOffsetPost: -1,
|
||||||
|
leftOffsetPost: -1,
|
||||||
|
widthPost: -1,
|
||||||
|
heightPost: -1,
|
||||||
|
width: -1,
|
||||||
|
height: -1,
|
||||||
|
canvas: 'crop',
|
||||||
|
position: 0,
|
||||||
|
resizeBackground: [0, 0, 0, 255],
|
||||||
|
useExifOrientation: false,
|
||||||
|
angle: 0,
|
||||||
|
rotationAngle: 0,
|
||||||
|
rotationBackground: [0, 0, 0, 255],
|
||||||
|
rotateBeforePreExtract: false,
|
||||||
|
flip: false,
|
||||||
|
flop: false,
|
||||||
|
extendTop: 0,
|
||||||
|
extendBottom: 0,
|
||||||
|
extendLeft: 0,
|
||||||
|
extendRight: 0,
|
||||||
|
extendBackground: [0, 0, 0, 255],
|
||||||
|
extendWith: 'background',
|
||||||
|
withoutEnlargement: false,
|
||||||
|
withoutReduction: false,
|
||||||
|
affineMatrix: [],
|
||||||
|
affineBackground: [0, 0, 0, 255],
|
||||||
|
affineIdx: 0,
|
||||||
|
affineIdy: 0,
|
||||||
|
affineOdx: 0,
|
||||||
|
affineOdy: 0,
|
||||||
|
affineInterpolator: this.constructor.interpolators.bilinear,
|
||||||
|
kernel: 'lanczos3',
|
||||||
|
fastShrinkOnLoad: true,
|
||||||
|
// operations
|
||||||
|
tintA: 128,
|
||||||
|
tintB: 128,
|
||||||
|
flatten: false,
|
||||||
|
flattenBackground: [0, 0, 0],
|
||||||
|
unflatten: false,
|
||||||
|
negate: false,
|
||||||
|
negateAlpha: true,
|
||||||
|
medianSize: 0,
|
||||||
|
blurSigma: 0,
|
||||||
|
sharpenSigma: 0,
|
||||||
|
sharpenM1: 1,
|
||||||
|
sharpenM2: 2,
|
||||||
|
sharpenX1: 2,
|
||||||
|
sharpenY2: 10,
|
||||||
|
sharpenY3: 20,
|
||||||
|
threshold: 0,
|
||||||
|
thresholdGrayscale: true,
|
||||||
|
trimBackground: [],
|
||||||
|
trimThreshold: 0,
|
||||||
|
gamma: 0,
|
||||||
|
gammaOut: 0,
|
||||||
|
greyscale: false,
|
||||||
|
normalise: false,
|
||||||
|
normaliseLower: 1,
|
||||||
|
normaliseUpper: 99,
|
||||||
|
claheWidth: 0,
|
||||||
|
claheHeight: 0,
|
||||||
|
claheMaxSlope: 3,
|
||||||
|
brightness: 1,
|
||||||
|
saturation: 1,
|
||||||
|
hue: 0,
|
||||||
|
lightness: 0,
|
||||||
|
booleanBufferIn: null,
|
||||||
|
booleanFileIn: '',
|
||||||
|
joinChannelIn: [],
|
||||||
|
extractChannel: -1,
|
||||||
|
removeAlpha: false,
|
||||||
|
ensureAlpha: -1,
|
||||||
|
colourspace: 'srgb',
|
||||||
|
colourspaceInput: 'last',
|
||||||
|
composite: [],
|
||||||
|
// output
|
||||||
|
fileOut: '',
|
||||||
|
formatOut: 'input',
|
||||||
|
streamOut: false,
|
||||||
|
withMetadata: false,
|
||||||
|
withMetadataOrientation: -1,
|
||||||
|
withMetadataDensity: 0,
|
||||||
|
withMetadataIcc: '',
|
||||||
|
withMetadataStrs: {},
|
||||||
|
resolveWithObject: false,
|
||||||
|
// output format
|
||||||
|
jpegQuality: 80,
|
||||||
|
jpegProgressive: false,
|
||||||
|
jpegChromaSubsampling: '4:2:0',
|
||||||
|
jpegTrellisQuantisation: false,
|
||||||
|
jpegOvershootDeringing: false,
|
||||||
|
jpegOptimiseScans: false,
|
||||||
|
jpegOptimiseCoding: true,
|
||||||
|
jpegQuantisationTable: 0,
|
||||||
|
pngProgressive: false,
|
||||||
|
pngCompressionLevel: 6,
|
||||||
|
pngAdaptiveFiltering: false,
|
||||||
|
pngPalette: false,
|
||||||
|
pngQuality: 100,
|
||||||
|
pngEffort: 7,
|
||||||
|
pngBitdepth: 8,
|
||||||
|
pngDither: 1,
|
||||||
|
jp2Quality: 80,
|
||||||
|
jp2TileHeight: 512,
|
||||||
|
jp2TileWidth: 512,
|
||||||
|
jp2Lossless: false,
|
||||||
|
jp2ChromaSubsampling: '4:4:4',
|
||||||
|
webpQuality: 80,
|
||||||
|
webpAlphaQuality: 100,
|
||||||
|
webpLossless: false,
|
||||||
|
webpNearLossless: false,
|
||||||
|
webpSmartSubsample: false,
|
||||||
|
webpPreset: 'default',
|
||||||
|
webpEffort: 4,
|
||||||
|
webpMinSize: false,
|
||||||
|
webpMixed: false,
|
||||||
|
gifBitdepth: 8,
|
||||||
|
gifEffort: 7,
|
||||||
|
gifDither: 1,
|
||||||
|
gifInterFrameMaxError: 0,
|
||||||
|
gifInterPaletteMaxError: 3,
|
||||||
|
gifReuse: true,
|
||||||
|
gifProgressive: false,
|
||||||
|
tiffQuality: 80,
|
||||||
|
tiffCompression: 'jpeg',
|
||||||
|
tiffPredictor: 'horizontal',
|
||||||
|
tiffPyramid: false,
|
||||||
|
tiffBitdepth: 8,
|
||||||
|
tiffTile: false,
|
||||||
|
tiffTileHeight: 256,
|
||||||
|
tiffTileWidth: 256,
|
||||||
|
tiffXres: 1.0,
|
||||||
|
tiffYres: 1.0,
|
||||||
|
tiffResolutionUnit: 'inch',
|
||||||
|
heifQuality: 50,
|
||||||
|
heifLossless: false,
|
||||||
|
heifCompression: 'av1',
|
||||||
|
heifEffort: 4,
|
||||||
|
heifChromaSubsampling: '4:4:4',
|
||||||
|
jxlDistance: 1,
|
||||||
|
jxlDecodingTier: 0,
|
||||||
|
jxlEffort: 7,
|
||||||
|
jxlLossless: false,
|
||||||
|
rawDepth: 'uchar',
|
||||||
|
tileSize: 256,
|
||||||
|
tileOverlap: 0,
|
||||||
|
tileContainer: 'fs',
|
||||||
|
tileLayout: 'dz',
|
||||||
|
tileFormat: 'last',
|
||||||
|
tileDepth: 'last',
|
||||||
|
tileAngle: 0,
|
||||||
|
tileSkipBlanks: -1,
|
||||||
|
tileBackground: [255, 255, 255, 255],
|
||||||
|
tileCentre: false,
|
||||||
|
tileId: 'https://example.com/iiif',
|
||||||
|
tileBasename: '',
|
||||||
|
timeoutSeconds: 0,
|
||||||
|
linearA: [],
|
||||||
|
linearB: [],
|
||||||
|
// Function to notify of libvips warnings
|
||||||
|
debuglog: warning => {
|
||||||
|
this.emit('warning', warning);
|
||||||
|
debuglog(warning);
|
||||||
|
},
|
||||||
|
// Function to notify of queue length changes
|
||||||
|
queueListener: function (queueLength) {
|
||||||
|
Sharp.queue.emit('change', queueLength);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.options.input = this._createInputDescriptor(input, options, { allowStream: true });
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
Object.setPrototypeOf(Sharp.prototype, stream.Duplex.prototype);
|
||||||
|
Object.setPrototypeOf(Sharp, stream.Duplex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take a "snapshot" of the Sharp instance, returning a new instance.
|
||||||
|
* Cloned instances inherit the input of their parent instance.
|
||||||
|
* This allows multiple output Streams and therefore multiple processing pipelines to share a single input Stream.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const pipeline = sharp().rotate();
|
||||||
|
* pipeline.clone().resize(800, 600).pipe(firstWritableStream);
|
||||||
|
* pipeline.clone().extract({ left: 20, top: 20, width: 100, height: 100 }).pipe(secondWritableStream);
|
||||||
|
* readableStream.pipe(pipeline);
|
||||||
|
* // firstWritableStream receives auto-rotated, resized readableStream
|
||||||
|
* // secondWritableStream receives auto-rotated, extracted region of readableStream
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Create a pipeline that will download an image, resize it and format it to different files
|
||||||
|
* // Using Promises to know when the pipeline is complete
|
||||||
|
* const fs = require("fs");
|
||||||
|
* const got = require("got");
|
||||||
|
* const sharpStream = sharp({ failOn: 'none' });
|
||||||
|
*
|
||||||
|
* const promises = [];
|
||||||
|
*
|
||||||
|
* promises.push(
|
||||||
|
* sharpStream
|
||||||
|
* .clone()
|
||||||
|
* .jpeg({ quality: 100 })
|
||||||
|
* .toFile("originalFile.jpg")
|
||||||
|
* );
|
||||||
|
*
|
||||||
|
* promises.push(
|
||||||
|
* sharpStream
|
||||||
|
* .clone()
|
||||||
|
* .resize({ width: 500 })
|
||||||
|
* .jpeg({ quality: 80 })
|
||||||
|
* .toFile("optimized-500.jpg")
|
||||||
|
* );
|
||||||
|
*
|
||||||
|
* promises.push(
|
||||||
|
* sharpStream
|
||||||
|
* .clone()
|
||||||
|
* .resize({ width: 500 })
|
||||||
|
* .webp({ quality: 80 })
|
||||||
|
* .toFile("optimized-500.webp")
|
||||||
|
* );
|
||||||
|
*
|
||||||
|
* // https://github.com/sindresorhus/got/blob/main/documentation/3-streams.md
|
||||||
|
* got.stream("https://www.example.com/some-file.jpg").pipe(sharpStream);
|
||||||
|
*
|
||||||
|
* Promise.all(promises)
|
||||||
|
* .then(res => { console.log("Done!", res); })
|
||||||
|
* .catch(err => {
|
||||||
|
* console.error("Error processing files, let's clean it up", err);
|
||||||
|
* try {
|
||||||
|
* fs.unlinkSync("originalFile.jpg");
|
||||||
|
* fs.unlinkSync("optimized-500.jpg");
|
||||||
|
* fs.unlinkSync("optimized-500.webp");
|
||||||
|
* } catch (e) {}
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* @returns {Sharp}
|
||||||
|
*/
|
||||||
|
function clone () {
|
||||||
|
// Clone existing options
|
||||||
|
const clone = this.constructor.call();
|
||||||
|
clone.options = Object.assign({}, this.options);
|
||||||
|
// Pass 'finish' event to clone for Stream-based input
|
||||||
|
if (this._isStreamInput()) {
|
||||||
|
this.on('finish', () => {
|
||||||
|
// Clone inherits input data
|
||||||
|
this._flattenBufferIn();
|
||||||
|
clone.options.bufferIn = this.options.bufferIn;
|
||||||
|
clone.emit('finish');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
Object.assign(Sharp.prototype, { clone });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export constructor.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
module.exports = Sharp;
|
||||||
1654
lib/index.d.ts
vendored
Normal file
1654
lib/index.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
16
lib/index.js
Normal file
16
lib/index.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Sharp = require('./constructor');
|
||||||
|
require('./input')(Sharp);
|
||||||
|
require('./resize')(Sharp);
|
||||||
|
require('./composite')(Sharp);
|
||||||
|
require('./operation')(Sharp);
|
||||||
|
require('./colour')(Sharp);
|
||||||
|
require('./channel')(Sharp);
|
||||||
|
require('./output')(Sharp);
|
||||||
|
require('./utility')(Sharp);
|
||||||
|
|
||||||
|
module.exports = Sharp;
|
||||||
631
lib/input.js
Normal file
631
lib/input.js
Normal file
@@ -0,0 +1,631 @@
|
|||||||
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const color = require('color');
|
||||||
|
const is = require('./is');
|
||||||
|
const sharp = require('./sharp');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Justication alignment
|
||||||
|
* @member
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const align = {
|
||||||
|
left: 'low',
|
||||||
|
center: 'centre',
|
||||||
|
centre: 'centre',
|
||||||
|
right: 'high'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract input options, if any, from an object.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function _inputOptionsFromObject (obj) {
|
||||||
|
const { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd } = obj;
|
||||||
|
return [raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd].some(is.defined)
|
||||||
|
? { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd }
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create Object containing input and input-related options.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function _createInputDescriptor (input, inputOptions, containerOptions) {
|
||||||
|
const inputDescriptor = {
|
||||||
|
failOn: 'warning',
|
||||||
|
limitInputPixels: Math.pow(0x3FFF, 2),
|
||||||
|
ignoreIcc: false,
|
||||||
|
unlimited: false,
|
||||||
|
sequentialRead: true
|
||||||
|
};
|
||||||
|
if (is.string(input)) {
|
||||||
|
// filesystem
|
||||||
|
inputDescriptor.file = input;
|
||||||
|
} else if (is.buffer(input)) {
|
||||||
|
// Buffer
|
||||||
|
if (input.length === 0) {
|
||||||
|
throw Error('Input Buffer is empty');
|
||||||
|
}
|
||||||
|
inputDescriptor.buffer = input;
|
||||||
|
} else if (is.arrayBuffer(input)) {
|
||||||
|
if (input.byteLength === 0) {
|
||||||
|
throw Error('Input bit Array is empty');
|
||||||
|
}
|
||||||
|
inputDescriptor.buffer = Buffer.from(input, 0, input.byteLength);
|
||||||
|
} else if (is.typedArray(input)) {
|
||||||
|
if (input.length === 0) {
|
||||||
|
throw Error('Input Bit Array is empty');
|
||||||
|
}
|
||||||
|
inputDescriptor.buffer = Buffer.from(input.buffer, input.byteOffset, input.byteLength);
|
||||||
|
} else if (is.plainObject(input) && !is.defined(inputOptions)) {
|
||||||
|
// Plain Object descriptor, e.g. create
|
||||||
|
inputOptions = input;
|
||||||
|
if (_inputOptionsFromObject(inputOptions)) {
|
||||||
|
// Stream with options
|
||||||
|
inputDescriptor.buffer = [];
|
||||||
|
}
|
||||||
|
} else if (!is.defined(input) && !is.defined(inputOptions) && is.object(containerOptions) && containerOptions.allowStream) {
|
||||||
|
// Stream without options
|
||||||
|
inputDescriptor.buffer = [];
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unsupported input '${input}' of type ${typeof input}${
|
||||||
|
is.defined(inputOptions) ? ` when also providing options of type ${typeof inputOptions}` : ''
|
||||||
|
}`);
|
||||||
|
}
|
||||||
|
if (is.object(inputOptions)) {
|
||||||
|
// Deprecated: failOnError
|
||||||
|
if (is.defined(inputOptions.failOnError)) {
|
||||||
|
if (is.bool(inputOptions.failOnError)) {
|
||||||
|
inputDescriptor.failOn = inputOptions.failOnError ? 'warning' : 'none';
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('failOnError', 'boolean', inputOptions.failOnError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// failOn
|
||||||
|
if (is.defined(inputOptions.failOn)) {
|
||||||
|
if (is.string(inputOptions.failOn) && is.inArray(inputOptions.failOn, ['none', 'truncated', 'error', 'warning'])) {
|
||||||
|
inputDescriptor.failOn = inputOptions.failOn;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('failOn', 'one of: none, truncated, error, warning', inputOptions.failOn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Density
|
||||||
|
if (is.defined(inputOptions.density)) {
|
||||||
|
if (is.inRange(inputOptions.density, 1, 100000)) {
|
||||||
|
inputDescriptor.density = inputOptions.density;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('density', 'number between 1 and 100000', inputOptions.density);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Ignore embeddded ICC profile
|
||||||
|
if (is.defined(inputOptions.ignoreIcc)) {
|
||||||
|
if (is.bool(inputOptions.ignoreIcc)) {
|
||||||
|
inputDescriptor.ignoreIcc = inputOptions.ignoreIcc;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('ignoreIcc', 'boolean', inputOptions.ignoreIcc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// limitInputPixels
|
||||||
|
if (is.defined(inputOptions.limitInputPixels)) {
|
||||||
|
if (is.bool(inputOptions.limitInputPixels)) {
|
||||||
|
inputDescriptor.limitInputPixels = inputOptions.limitInputPixels
|
||||||
|
? Math.pow(0x3FFF, 2)
|
||||||
|
: 0;
|
||||||
|
} else if (is.integer(inputOptions.limitInputPixels) && is.inRange(inputOptions.limitInputPixels, 0, Number.MAX_SAFE_INTEGER)) {
|
||||||
|
inputDescriptor.limitInputPixels = inputOptions.limitInputPixels;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('limitInputPixels', 'positive integer', inputOptions.limitInputPixels);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// unlimited
|
||||||
|
if (is.defined(inputOptions.unlimited)) {
|
||||||
|
if (is.bool(inputOptions.unlimited)) {
|
||||||
|
inputDescriptor.unlimited = inputOptions.unlimited;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('unlimited', 'boolean', inputOptions.unlimited);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// sequentialRead
|
||||||
|
if (is.defined(inputOptions.sequentialRead)) {
|
||||||
|
if (is.bool(inputOptions.sequentialRead)) {
|
||||||
|
inputDescriptor.sequentialRead = inputOptions.sequentialRead;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('sequentialRead', 'boolean', inputOptions.sequentialRead);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Raw pixel input
|
||||||
|
if (is.defined(inputOptions.raw)) {
|
||||||
|
if (
|
||||||
|
is.object(inputOptions.raw) &&
|
||||||
|
is.integer(inputOptions.raw.width) && inputOptions.raw.width > 0 &&
|
||||||
|
is.integer(inputOptions.raw.height) && inputOptions.raw.height > 0 &&
|
||||||
|
is.integer(inputOptions.raw.channels) && is.inRange(inputOptions.raw.channels, 1, 4)
|
||||||
|
) {
|
||||||
|
inputDescriptor.rawWidth = inputOptions.raw.width;
|
||||||
|
inputDescriptor.rawHeight = inputOptions.raw.height;
|
||||||
|
inputDescriptor.rawChannels = inputOptions.raw.channels;
|
||||||
|
inputDescriptor.rawPremultiplied = !!inputOptions.raw.premultiplied;
|
||||||
|
|
||||||
|
switch (input.constructor) {
|
||||||
|
case Uint8Array:
|
||||||
|
case Uint8ClampedArray:
|
||||||
|
inputDescriptor.rawDepth = 'uchar';
|
||||||
|
break;
|
||||||
|
case Int8Array:
|
||||||
|
inputDescriptor.rawDepth = 'char';
|
||||||
|
break;
|
||||||
|
case Uint16Array:
|
||||||
|
inputDescriptor.rawDepth = 'ushort';
|
||||||
|
break;
|
||||||
|
case Int16Array:
|
||||||
|
inputDescriptor.rawDepth = 'short';
|
||||||
|
break;
|
||||||
|
case Uint32Array:
|
||||||
|
inputDescriptor.rawDepth = 'uint';
|
||||||
|
break;
|
||||||
|
case Int32Array:
|
||||||
|
inputDescriptor.rawDepth = 'int';
|
||||||
|
break;
|
||||||
|
case Float32Array:
|
||||||
|
inputDescriptor.rawDepth = 'float';
|
||||||
|
break;
|
||||||
|
case Float64Array:
|
||||||
|
inputDescriptor.rawDepth = 'double';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
inputDescriptor.rawDepth = 'uchar';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('Expected width, height and channels for raw pixel input');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Multi-page input (GIF, TIFF, PDF)
|
||||||
|
if (is.defined(inputOptions.animated)) {
|
||||||
|
if (is.bool(inputOptions.animated)) {
|
||||||
|
inputDescriptor.pages = inputOptions.animated ? -1 : 1;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('animated', 'boolean', inputOptions.animated);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(inputOptions.pages)) {
|
||||||
|
if (is.integer(inputOptions.pages) && is.inRange(inputOptions.pages, -1, 100000)) {
|
||||||
|
inputDescriptor.pages = inputOptions.pages;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('pages', 'integer between -1 and 100000', inputOptions.pages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(inputOptions.page)) {
|
||||||
|
if (is.integer(inputOptions.page) && is.inRange(inputOptions.page, 0, 100000)) {
|
||||||
|
inputDescriptor.page = inputOptions.page;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('page', 'integer between 0 and 100000', inputOptions.page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Multi-level input (OpenSlide)
|
||||||
|
if (is.defined(inputOptions.level)) {
|
||||||
|
if (is.integer(inputOptions.level) && is.inRange(inputOptions.level, 0, 256)) {
|
||||||
|
inputDescriptor.level = inputOptions.level;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('level', 'integer between 0 and 256', inputOptions.level);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sub Image File Directory (TIFF)
|
||||||
|
if (is.defined(inputOptions.subifd)) {
|
||||||
|
if (is.integer(inputOptions.subifd) && is.inRange(inputOptions.subifd, -1, 100000)) {
|
||||||
|
inputDescriptor.subifd = inputOptions.subifd;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('subifd', 'integer between -1 and 100000', inputOptions.subifd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Create new image
|
||||||
|
if (is.defined(inputOptions.create)) {
|
||||||
|
if (
|
||||||
|
is.object(inputOptions.create) &&
|
||||||
|
is.integer(inputOptions.create.width) && inputOptions.create.width > 0 &&
|
||||||
|
is.integer(inputOptions.create.height) && inputOptions.create.height > 0 &&
|
||||||
|
is.integer(inputOptions.create.channels)
|
||||||
|
) {
|
||||||
|
inputDescriptor.createWidth = inputOptions.create.width;
|
||||||
|
inputDescriptor.createHeight = inputOptions.create.height;
|
||||||
|
inputDescriptor.createChannels = inputOptions.create.channels;
|
||||||
|
// Noise
|
||||||
|
if (is.defined(inputOptions.create.noise)) {
|
||||||
|
if (!is.object(inputOptions.create.noise)) {
|
||||||
|
throw new Error('Expected noise to be an object');
|
||||||
|
}
|
||||||
|
if (!is.inArray(inputOptions.create.noise.type, ['gaussian'])) {
|
||||||
|
throw new Error('Only gaussian noise is supported at the moment');
|
||||||
|
}
|
||||||
|
if (!is.inRange(inputOptions.create.channels, 1, 4)) {
|
||||||
|
throw is.invalidParameterError('create.channels', 'number between 1 and 4', inputOptions.create.channels);
|
||||||
|
}
|
||||||
|
inputDescriptor.createNoiseType = inputOptions.create.noise.type;
|
||||||
|
if (is.number(inputOptions.create.noise.mean) && is.inRange(inputOptions.create.noise.mean, 0, 10000)) {
|
||||||
|
inputDescriptor.createNoiseMean = inputOptions.create.noise.mean;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('create.noise.mean', 'number between 0 and 10000', inputOptions.create.noise.mean);
|
||||||
|
}
|
||||||
|
if (is.number(inputOptions.create.noise.sigma) && is.inRange(inputOptions.create.noise.sigma, 0, 10000)) {
|
||||||
|
inputDescriptor.createNoiseSigma = inputOptions.create.noise.sigma;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('create.noise.sigma', 'number between 0 and 10000', inputOptions.create.noise.sigma);
|
||||||
|
}
|
||||||
|
} else if (is.defined(inputOptions.create.background)) {
|
||||||
|
if (!is.inRange(inputOptions.create.channels, 3, 4)) {
|
||||||
|
throw is.invalidParameterError('create.channels', 'number between 3 and 4', inputOptions.create.channels);
|
||||||
|
}
|
||||||
|
const background = color(inputOptions.create.background);
|
||||||
|
inputDescriptor.createBackground = [
|
||||||
|
background.red(),
|
||||||
|
background.green(),
|
||||||
|
background.blue(),
|
||||||
|
Math.round(background.alpha() * 255)
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
throw new Error('Expected valid noise or background to create a new input image');
|
||||||
|
}
|
||||||
|
delete inputDescriptor.buffer;
|
||||||
|
} else {
|
||||||
|
throw new Error('Expected valid width, height and channels to create a new input image');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Create a new image with text
|
||||||
|
if (is.defined(inputOptions.text)) {
|
||||||
|
if (is.object(inputOptions.text) && is.string(inputOptions.text.text)) {
|
||||||
|
inputDescriptor.textValue = inputOptions.text.text;
|
||||||
|
if (is.defined(inputOptions.text.height) && is.defined(inputOptions.text.dpi)) {
|
||||||
|
throw new Error('Expected only one of dpi or height');
|
||||||
|
}
|
||||||
|
if (is.defined(inputOptions.text.font)) {
|
||||||
|
if (is.string(inputOptions.text.font)) {
|
||||||
|
inputDescriptor.textFont = inputOptions.text.font;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('text.font', 'string', inputOptions.text.font);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(inputOptions.text.fontfile)) {
|
||||||
|
if (is.string(inputOptions.text.fontfile)) {
|
||||||
|
inputDescriptor.textFontfile = inputOptions.text.fontfile;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('text.fontfile', 'string', inputOptions.text.fontfile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(inputOptions.text.width)) {
|
||||||
|
if (is.number(inputOptions.text.width)) {
|
||||||
|
inputDescriptor.textWidth = inputOptions.text.width;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('text.textWidth', 'number', inputOptions.text.width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(inputOptions.text.height)) {
|
||||||
|
if (is.number(inputOptions.text.height)) {
|
||||||
|
inputDescriptor.textHeight = inputOptions.text.height;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('text.height', 'number', inputOptions.text.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(inputOptions.text.align)) {
|
||||||
|
if (is.string(inputOptions.text.align) && is.string(this.constructor.align[inputOptions.text.align])) {
|
||||||
|
inputDescriptor.textAlign = this.constructor.align[inputOptions.text.align];
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('text.align', 'valid alignment', inputOptions.text.align);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(inputOptions.text.justify)) {
|
||||||
|
if (is.bool(inputOptions.text.justify)) {
|
||||||
|
inputDescriptor.textJustify = inputOptions.text.justify;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('text.justify', 'boolean', inputOptions.text.justify);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(inputOptions.text.dpi)) {
|
||||||
|
if (is.number(inputOptions.text.dpi) && is.inRange(inputOptions.text.dpi, 1, 100000)) {
|
||||||
|
inputDescriptor.textDpi = inputOptions.text.dpi;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('text.dpi', 'number between 1 and 100000', inputOptions.text.dpi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(inputOptions.text.rgba)) {
|
||||||
|
if (is.bool(inputOptions.text.rgba)) {
|
||||||
|
inputDescriptor.textRgba = inputOptions.text.rgba;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('text.rgba', 'bool', inputOptions.text.rgba);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(inputOptions.text.spacing)) {
|
||||||
|
if (is.number(inputOptions.text.spacing)) {
|
||||||
|
inputDescriptor.textSpacing = inputOptions.text.spacing;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('text.spacing', 'number', inputOptions.text.spacing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(inputOptions.text.wrap)) {
|
||||||
|
if (is.string(inputOptions.text.wrap) && is.inArray(inputOptions.text.wrap, ['word', 'char', 'wordChar', 'none'])) {
|
||||||
|
inputDescriptor.textWrap = inputOptions.text.wrap;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('text.wrap', 'one of: word, char, wordChar, none', inputOptions.text.wrap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete inputDescriptor.buffer;
|
||||||
|
} else {
|
||||||
|
throw new Error('Expected a valid string to create an image with text.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (is.defined(inputOptions)) {
|
||||||
|
throw new Error('Invalid input options ' + inputOptions);
|
||||||
|
}
|
||||||
|
return inputDescriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle incoming Buffer chunk on Writable Stream.
|
||||||
|
* @private
|
||||||
|
* @param {Buffer} chunk
|
||||||
|
* @param {string} encoding - unused
|
||||||
|
* @param {Function} callback
|
||||||
|
*/
|
||||||
|
function _write (chunk, encoding, callback) {
|
||||||
|
/* istanbul ignore else */
|
||||||
|
if (Array.isArray(this.options.input.buffer)) {
|
||||||
|
/* istanbul ignore else */
|
||||||
|
if (is.buffer(chunk)) {
|
||||||
|
if (this.options.input.buffer.length === 0) {
|
||||||
|
this.on('finish', () => {
|
||||||
|
this.streamInFinished = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.options.input.buffer.push(chunk);
|
||||||
|
callback();
|
||||||
|
} else {
|
||||||
|
callback(new Error('Non-Buffer data on Writable Stream'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
callback(new Error('Unexpected data on Writable Stream'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flattens the array of chunks accumulated in input.buffer.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function _flattenBufferIn () {
|
||||||
|
if (this._isStreamInput()) {
|
||||||
|
this.options.input.buffer = Buffer.concat(this.options.input.buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Are we expecting Stream-based input?
|
||||||
|
* @private
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function _isStreamInput () {
|
||||||
|
return Array.isArray(this.options.input.buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fast access to (uncached) image metadata without decoding any compressed pixel data.
|
||||||
|
*
|
||||||
|
* This is read from the header of the input image.
|
||||||
|
* It does not take into consideration any operations to be applied to the output image,
|
||||||
|
* such as resize or rotate.
|
||||||
|
*
|
||||||
|
* Dimensions in the response will respect the `page` and `pages` properties of the
|
||||||
|
* {@link /api-constructor#parameters|constructor parameters}.
|
||||||
|
*
|
||||||
|
* A `Promise` is returned when `callback` is not provided.
|
||||||
|
*
|
||||||
|
* - `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
|
||||||
|
* - `size`: Total size of image in bytes, for Stream and Buffer input only
|
||||||
|
* - `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below)
|
||||||
|
* - `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below)
|
||||||
|
* - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://www.libvips.org/API/current/VipsImage.html#VipsInterpretation)
|
||||||
|
* - `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
|
||||||
|
* - `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://www.libvips.org/API/current/VipsImage.html#VipsBandFormat)
|
||||||
|
* - `density`: Number of pixels per inch (DPI), if present
|
||||||
|
* - `chromaSubsampling`: String containing JPEG chroma subsampling, `4:2:0` or `4:4:4` for RGB, `4:2:0:4` or `4:4:4:4` for CMYK
|
||||||
|
* - `isProgressive`: Boolean indicating whether the image is interlaced using a progressive scan
|
||||||
|
* - `pages`: Number of pages/frames contained within the image, with support for TIFF, HEIF, PDF, animated GIF and animated WebP
|
||||||
|
* - `pageHeight`: Number of pixels high each page in a multi-page image will be.
|
||||||
|
* - `paletteBitDepth`: Bit depth of palette-based image (GIF, PNG).
|
||||||
|
* - `loop`: Number of times to loop an animated image, zero refers to a continuous loop.
|
||||||
|
* - `delay`: Delay in ms between each page in an animated image, provided as an array of integers.
|
||||||
|
* - `pagePrimary`: Number of the primary page in a HEIF image
|
||||||
|
* - `levels`: Details of each level in a multi-level image provided as an array of objects, requires libvips compiled with support for OpenSlide
|
||||||
|
* - `subifds`: Number of Sub Image File Directories in an OME-TIFF image
|
||||||
|
* - `background`: Default background colour, if present, for PNG (bKGD) and GIF images, either an RGB Object or a single greyscale value
|
||||||
|
* - `compression`: The encoder used to compress an HEIF file, `av1` (AVIF) or `hevc` (HEIC)
|
||||||
|
* - `resolutionUnit`: The unit of resolution (density), either `inch` or `cm`, if present
|
||||||
|
* - `hasProfile`: Boolean indicating the presence of an embedded ICC profile
|
||||||
|
* - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
|
||||||
|
* - `orientation`: Number value of the EXIF Orientation header, if present
|
||||||
|
* - `exif`: Buffer containing raw EXIF data, if present
|
||||||
|
* - `icc`: Buffer containing raw [ICC](https://www.npmjs.com/package/icc) profile data, if present
|
||||||
|
* - `iptc`: Buffer containing raw IPTC data, if present
|
||||||
|
* - `xmp`: Buffer containing raw XMP data, if present
|
||||||
|
* - `tifftagPhotoshop`: Buffer containing raw TIFFTAG_PHOTOSHOP data, if present
|
||||||
|
* - `formatMagick`: String containing format for images loaded via *magick
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const metadata = await sharp(input).metadata();
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const image = sharp(inputJpg);
|
||||||
|
* image
|
||||||
|
* .metadata()
|
||||||
|
* .then(function(metadata) {
|
||||||
|
* return image
|
||||||
|
* .resize(Math.round(metadata.width / 2))
|
||||||
|
* .webp()
|
||||||
|
* .toBuffer();
|
||||||
|
* })
|
||||||
|
* .then(function(data) {
|
||||||
|
* // data contains a WebP image half the width and height of the original JPEG
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Based on EXIF rotation metadata, get the right-side-up width and height:
|
||||||
|
*
|
||||||
|
* const size = getNormalSize(await sharp(input).metadata());
|
||||||
|
*
|
||||||
|
* function getNormalSize({ width, height, orientation }) {
|
||||||
|
* return (orientation || 0) >= 5
|
||||||
|
* ? { width: height, height: width }
|
||||||
|
* : { width, height };
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @param {Function} [callback] - called with the arguments `(err, metadata)`
|
||||||
|
* @returns {Promise<Object>|Sharp}
|
||||||
|
*/
|
||||||
|
function metadata (callback) {
|
||||||
|
if (is.fn(callback)) {
|
||||||
|
if (this._isStreamInput()) {
|
||||||
|
this.on('finish', () => {
|
||||||
|
this._flattenBufferIn();
|
||||||
|
sharp.metadata(this.options, callback);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
sharp.metadata(this.options, callback);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
} else {
|
||||||
|
if (this._isStreamInput()) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const finished = () => {
|
||||||
|
this._flattenBufferIn();
|
||||||
|
sharp.metadata(this.options, (err, metadata) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(metadata);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
if (this.writableFinished) {
|
||||||
|
finished();
|
||||||
|
} else {
|
||||||
|
this.once('finish', finished);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
sharp.metadata(this.options, (err, metadata) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(metadata);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access to pixel-derived image statistics for every channel in the image.
|
||||||
|
* A `Promise` is returned when `callback` is not provided.
|
||||||
|
*
|
||||||
|
* - `channels`: Array of channel statistics for each channel in the image. Each channel statistic contains
|
||||||
|
* - `min` (minimum value in the channel)
|
||||||
|
* - `max` (maximum value in the channel)
|
||||||
|
* - `sum` (sum of all values in a channel)
|
||||||
|
* - `squaresSum` (sum of squared values in a channel)
|
||||||
|
* - `mean` (mean of the values in a channel)
|
||||||
|
* - `stdev` (standard deviation for the values in a channel)
|
||||||
|
* - `minX` (x-coordinate of one of the pixel where the minimum lies)
|
||||||
|
* - `minY` (y-coordinate of one of the pixel where the minimum lies)
|
||||||
|
* - `maxX` (x-coordinate of one of the pixel where the maximum lies)
|
||||||
|
* - `maxY` (y-coordinate of one of the pixel where the maximum lies)
|
||||||
|
* - `isOpaque`: Is the image fully opaque? Will be `true` if the image has no alpha channel or if every pixel is fully opaque.
|
||||||
|
* - `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any.
|
||||||
|
* - `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any.
|
||||||
|
* - `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram.
|
||||||
|
*
|
||||||
|
* **Note**: Statistics are derived from the original input image. Any operations performed on the image must first be
|
||||||
|
* written to a buffer in order to run `stats` on the result (see third example).
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const image = sharp(inputJpg);
|
||||||
|
* image
|
||||||
|
* .stats()
|
||||||
|
* .then(function(stats) {
|
||||||
|
* // stats contains the channel-wise statistics array and the isOpaque value
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const { entropy, sharpness, dominant } = await sharp(input).stats();
|
||||||
|
* const { r, g, b } = dominant;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const image = sharp(input);
|
||||||
|
* // store intermediate result
|
||||||
|
* const part = await image.extract(region).toBuffer();
|
||||||
|
* // create new instance to obtain statistics of extracted region
|
||||||
|
* const stats = await sharp(part).stats();
|
||||||
|
*
|
||||||
|
* @param {Function} [callback] - called with the arguments `(err, stats)`
|
||||||
|
* @returns {Promise<Object>}
|
||||||
|
*/
|
||||||
|
function stats (callback) {
|
||||||
|
if (is.fn(callback)) {
|
||||||
|
if (this._isStreamInput()) {
|
||||||
|
this.on('finish', () => {
|
||||||
|
this._flattenBufferIn();
|
||||||
|
sharp.stats(this.options, callback);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
sharp.stats(this.options, callback);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
} else {
|
||||||
|
if (this._isStreamInput()) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.on('finish', function () {
|
||||||
|
this._flattenBufferIn();
|
||||||
|
sharp.stats(this.options, (err, stats) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(stats);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
sharp.stats(this.options, (err, stats) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(stats);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorate the Sharp prototype with input-related functions.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
module.exports = function (Sharp) {
|
||||||
|
Object.assign(Sharp.prototype, {
|
||||||
|
// Private
|
||||||
|
_inputOptionsFromObject,
|
||||||
|
_createInputDescriptor,
|
||||||
|
_write,
|
||||||
|
_flattenBufferIn,
|
||||||
|
_isStreamInput,
|
||||||
|
// Public
|
||||||
|
metadata,
|
||||||
|
stats
|
||||||
|
});
|
||||||
|
// Class attributes
|
||||||
|
Sharp.align = align;
|
||||||
|
};
|
||||||
155
lib/is.js
Normal file
155
lib/is.js
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this value defined and not null?
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const defined = function (val) {
|
||||||
|
return typeof val !== 'undefined' && val !== null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this value an object?
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const object = function (val) {
|
||||||
|
return typeof val === 'object';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this value a plain object?
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const plainObject = function (val) {
|
||||||
|
return Object.prototype.toString.call(val) === '[object Object]';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this value a function?
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const fn = function (val) {
|
||||||
|
return typeof val === 'function';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this value a boolean?
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const bool = function (val) {
|
||||||
|
return typeof val === 'boolean';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this value a Buffer object?
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const buffer = function (val) {
|
||||||
|
return val instanceof Buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this value a typed array object?. E.g. Uint8Array or Uint8ClampedArray?
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const typedArray = function (val) {
|
||||||
|
if (defined(val)) {
|
||||||
|
switch (val.constructor) {
|
||||||
|
case Uint8Array:
|
||||||
|
case Uint8ClampedArray:
|
||||||
|
case Int8Array:
|
||||||
|
case Uint16Array:
|
||||||
|
case Int16Array:
|
||||||
|
case Uint32Array:
|
||||||
|
case Int32Array:
|
||||||
|
case Float32Array:
|
||||||
|
case Float64Array:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this value an ArrayBuffer object?
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const arrayBuffer = function (val) {
|
||||||
|
return val instanceof ArrayBuffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this value a non-empty string?
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const string = function (val) {
|
||||||
|
return typeof val === 'string' && val.length > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this value a real number?
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const number = function (val) {
|
||||||
|
return typeof val === 'number' && !Number.isNaN(val);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this value an integer?
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const integer = function (val) {
|
||||||
|
return Number.isInteger(val);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this value within an inclusive given range?
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const inRange = function (val, min, max) {
|
||||||
|
return val >= min && val <= max;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this value within the elements of an array?
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const inArray = function (val, list) {
|
||||||
|
return list.includes(val);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an Error with a message relating to an invalid parameter.
|
||||||
|
*
|
||||||
|
* @param {string} name - parameter name.
|
||||||
|
* @param {string} expected - description of the type/value/range expected.
|
||||||
|
* @param {*} actual - the value received.
|
||||||
|
* @returns {Error} Containing the formatted message.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const invalidParameterError = function (name, expected, actual) {
|
||||||
|
return new Error(
|
||||||
|
`Expected ${expected} for ${name} but received ${actual} of type ${typeof actual}`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
defined,
|
||||||
|
object,
|
||||||
|
plainObject,
|
||||||
|
fn,
|
||||||
|
bool,
|
||||||
|
buffer,
|
||||||
|
typedArray,
|
||||||
|
arrayBuffer,
|
||||||
|
string,
|
||||||
|
number,
|
||||||
|
integer,
|
||||||
|
inRange,
|
||||||
|
inArray,
|
||||||
|
invalidParameterError
|
||||||
|
};
|
||||||
153
lib/libvips.js
Normal file
153
lib/libvips.js
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { spawnSync } = require('node:child_process');
|
||||||
|
const semverCoerce = require('semver/functions/coerce');
|
||||||
|
const semverGreaterThanOrEqualTo = require('semver/functions/gte');
|
||||||
|
const detectLibc = require('detect-libc');
|
||||||
|
|
||||||
|
const { engines } = require('../package.json');
|
||||||
|
|
||||||
|
const minimumLibvipsVersionLabelled = process.env.npm_package_config_libvips || /* istanbul ignore next */
|
||||||
|
engines.libvips;
|
||||||
|
const minimumLibvipsVersion = semverCoerce(minimumLibvipsVersionLabelled).version;
|
||||||
|
|
||||||
|
const prebuiltPlatforms = [
|
||||||
|
'darwin-arm64', 'darwin-x64',
|
||||||
|
'linux-arm', 'linux-arm64', 'linux-x64',
|
||||||
|
'linuxmusl-arm64', 'linuxmusl-x64',
|
||||||
|
'win32-ia32', 'win32-x64'
|
||||||
|
];
|
||||||
|
|
||||||
|
const spawnSyncOptions = {
|
||||||
|
encoding: 'utf8',
|
||||||
|
shell: true
|
||||||
|
};
|
||||||
|
|
||||||
|
const log = (item) => {
|
||||||
|
if (item instanceof Error) {
|
||||||
|
console.error(`sharp: Installation error: ${item.message}`);
|
||||||
|
} else {
|
||||||
|
console.log(`sharp: ${item}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* istanbul ignore next */
|
||||||
|
const runtimeLibc = () => detectLibc.isNonGlibcLinuxSync() ? detectLibc.familySync() : '';
|
||||||
|
|
||||||
|
const runtimePlatformArch = () => `${process.platform}${runtimeLibc()}-${process.arch}`;
|
||||||
|
|
||||||
|
/* istanbul ignore next */
|
||||||
|
const buildPlatformArch = () => {
|
||||||
|
/* eslint camelcase: ["error", { allow: ["^npm_config_"] }] */
|
||||||
|
const { npm_config_arch, npm_config_platform, npm_config_libc } = process.env;
|
||||||
|
return `${npm_config_platform || process.platform}${npm_config_libc || runtimeLibc()}-${npm_config_arch || process.arch}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildSharpLibvipsIncludeDir = () => {
|
||||||
|
try {
|
||||||
|
return require('@sharpen/sharp-libvips-dev/include');
|
||||||
|
} catch {}
|
||||||
|
/* istanbul ignore next */
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildSharpLibvipsCPlusPlusDir = () => {
|
||||||
|
try {
|
||||||
|
return require('@sharpen/sharp-libvips-dev/cplusplus');
|
||||||
|
} catch {}
|
||||||
|
/* istanbul ignore next */
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildSharpLibvipsLibDir = () => {
|
||||||
|
try {
|
||||||
|
return require(`@sharpen/sharp-libvips-${buildPlatformArch()}/lib`);
|
||||||
|
} catch {}
|
||||||
|
/* istanbul ignore next */
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const isRosetta = () => {
|
||||||
|
/* istanbul ignore next */
|
||||||
|
if (process.platform === 'darwin' && process.arch === 'x64') {
|
||||||
|
const translated = spawnSync('sysctl sysctl.proc_translated', spawnSyncOptions).stdout;
|
||||||
|
return (translated || '').trim() === 'sysctl.proc_translated: 1';
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* istanbul ignore next */
|
||||||
|
const spawnRebuild = () =>
|
||||||
|
spawnSync('node-gyp rebuild --directory=src', {
|
||||||
|
...spawnSyncOptions,
|
||||||
|
stdio: 'inherit'
|
||||||
|
}).status;
|
||||||
|
|
||||||
|
const globalLibvipsVersion = () => {
|
||||||
|
if (process.platform !== 'win32') {
|
||||||
|
const globalLibvipsVersion = spawnSync('pkg-config --modversion vips-cpp', {
|
||||||
|
...spawnSyncOptions,
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
PKG_CONFIG_PATH: pkgConfigPath()
|
||||||
|
}
|
||||||
|
}).stdout;
|
||||||
|
/* istanbul ignore next */
|
||||||
|
return (globalLibvipsVersion || '').trim();
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* istanbul ignore next */
|
||||||
|
const pkgConfigPath = () => {
|
||||||
|
if (process.platform !== 'win32') {
|
||||||
|
const brewPkgConfigPath = spawnSync(
|
||||||
|
'which brew >/dev/null 2>&1 && brew environment --plain | grep PKG_CONFIG_LIBDIR | cut -d" " -f2',
|
||||||
|
spawnSyncOptions
|
||||||
|
).stdout || '';
|
||||||
|
return [
|
||||||
|
brewPkgConfigPath.trim(),
|
||||||
|
process.env.PKG_CONFIG_PATH,
|
||||||
|
'/usr/local/lib/pkgconfig',
|
||||||
|
'/usr/lib/pkgconfig',
|
||||||
|
'/usr/local/libdata/pkgconfig',
|
||||||
|
'/usr/libdata/pkgconfig'
|
||||||
|
].filter(Boolean).join(':');
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const useGlobalLibvips = () => {
|
||||||
|
if (Boolean(process.env.SHARP_IGNORE_GLOBAL_LIBVIPS) === true) {
|
||||||
|
log('Detected SHARP_IGNORE_GLOBAL_LIBVIPS, skipping search for globally-installed libvips');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/* istanbul ignore next */
|
||||||
|
if (isRosetta()) {
|
||||||
|
log('Detected Rosetta, skipping search for globally-installed libvips');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const globalVipsVersion = globalLibvipsVersion();
|
||||||
|
return !!globalVipsVersion && /* istanbul ignore next */
|
||||||
|
semverGreaterThanOrEqualTo(globalVipsVersion, minimumLibvipsVersion);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
minimumLibvipsVersion,
|
||||||
|
prebuiltPlatforms,
|
||||||
|
buildPlatformArch,
|
||||||
|
buildSharpLibvipsIncludeDir,
|
||||||
|
buildSharpLibvipsCPlusPlusDir,
|
||||||
|
buildSharpLibvipsLibDir,
|
||||||
|
runtimePlatformArch,
|
||||||
|
log,
|
||||||
|
spawnRebuild,
|
||||||
|
globalLibvipsVersion,
|
||||||
|
pkgConfigPath,
|
||||||
|
useGlobalLibvips
|
||||||
|
};
|
||||||
919
lib/operation.js
Normal file
919
lib/operation.js
Normal file
@@ -0,0 +1,919 @@
|
|||||||
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const color = require('color');
|
||||||
|
const is = require('./is');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotate the output image by either an explicit angle
|
||||||
|
* or auto-orient based on the EXIF `Orientation` tag.
|
||||||
|
*
|
||||||
|
* If an angle is provided, it is converted to a valid positive degree rotation.
|
||||||
|
* For example, `-450` will produce a 270 degree rotation.
|
||||||
|
*
|
||||||
|
* When rotating by an angle other than a multiple of 90,
|
||||||
|
* the background colour can be provided with the `background` option.
|
||||||
|
*
|
||||||
|
* If no angle is provided, it is determined from the EXIF data.
|
||||||
|
* Mirroring is supported and may infer the use of a flip operation.
|
||||||
|
*
|
||||||
|
* The use of `rotate` without an angle will remove the EXIF `Orientation` tag, if any.
|
||||||
|
*
|
||||||
|
* Only one rotation can occur per pipeline.
|
||||||
|
* Previous calls to `rotate` in the same pipeline will be ignored.
|
||||||
|
*
|
||||||
|
* Method order is important when rotating, resizing and/or extracting regions,
|
||||||
|
* for example `.rotate(x).extract(y)` will produce a different result to `.extract(y).rotate(x)`.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const pipeline = sharp()
|
||||||
|
* .rotate()
|
||||||
|
* .resize(null, 200)
|
||||||
|
* .toBuffer(function (err, outputBuffer, info) {
|
||||||
|
* // outputBuffer contains 200px high JPEG image data,
|
||||||
|
* // auto-rotated using EXIF Orientation tag
|
||||||
|
* // info.width and info.height contain the dimensions of the resized image
|
||||||
|
* });
|
||||||
|
* readableStream.pipe(pipeline);
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const rotateThenResize = await sharp(input)
|
||||||
|
* .rotate(90)
|
||||||
|
* .resize({ width: 16, height: 8, fit: 'fill' })
|
||||||
|
* .toBuffer();
|
||||||
|
* const resizeThenRotate = await sharp(input)
|
||||||
|
* .resize({ width: 16, height: 8, fit: 'fill' })
|
||||||
|
* .rotate(90)
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @param {number} [angle=auto] angle of rotation.
|
||||||
|
* @param {Object} [options] - if present, is an Object with optional attributes.
|
||||||
|
* @param {string|Object} [options.background="#000000"] parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
function rotate (angle, options) {
|
||||||
|
if (this.options.useExifOrientation || this.options.angle || this.options.rotationAngle) {
|
||||||
|
this.options.debuglog('ignoring previous rotate options');
|
||||||
|
}
|
||||||
|
if (!is.defined(angle)) {
|
||||||
|
this.options.useExifOrientation = true;
|
||||||
|
} else if (is.integer(angle) && !(angle % 90)) {
|
||||||
|
this.options.angle = angle;
|
||||||
|
} else if (is.number(angle)) {
|
||||||
|
this.options.rotationAngle = angle;
|
||||||
|
if (is.object(options) && options.background) {
|
||||||
|
const backgroundColour = color(options.background);
|
||||||
|
this.options.rotationBackground = [
|
||||||
|
backgroundColour.red(),
|
||||||
|
backgroundColour.green(),
|
||||||
|
backgroundColour.blue(),
|
||||||
|
Math.round(backgroundColour.alpha() * 255)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('angle', 'numeric', angle);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mirror the image vertically (up-down) about the x-axis.
|
||||||
|
* This always occurs before rotation, if any.
|
||||||
|
*
|
||||||
|
* This operation does not work correctly with multi-page images.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const output = await sharp(input).flip().toBuffer();
|
||||||
|
*
|
||||||
|
* @param {Boolean} [flip=true]
|
||||||
|
* @returns {Sharp}
|
||||||
|
*/
|
||||||
|
function flip (flip) {
|
||||||
|
this.options.flip = is.bool(flip) ? flip : true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mirror the image horizontally (left-right) about the y-axis.
|
||||||
|
* This always occurs before rotation, if any.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const output = await sharp(input).flop().toBuffer();
|
||||||
|
*
|
||||||
|
* @param {Boolean} [flop=true]
|
||||||
|
* @returns {Sharp}
|
||||||
|
*/
|
||||||
|
function flop (flop) {
|
||||||
|
this.options.flop = is.bool(flop) ? flop : true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform an affine transform on an image. This operation will always occur after resizing, extraction and rotation, if any.
|
||||||
|
*
|
||||||
|
* You must provide an array of length 4 or a 2x2 affine transformation matrix.
|
||||||
|
* By default, new pixels are filled with a black background. You can provide a background color with the `background` option.
|
||||||
|
* A particular interpolator may also be specified. Set the `interpolator` option to an attribute of the `sharp.interpolators` Object e.g. `sharp.interpolators.nohalo`.
|
||||||
|
*
|
||||||
|
* In the case of a 2x2 matrix, the transform is:
|
||||||
|
* - X = `matrix[0, 0]` \* (x + `idx`) + `matrix[0, 1]` \* (y + `idy`) + `odx`
|
||||||
|
* - Y = `matrix[1, 0]` \* (x + `idx`) + `matrix[1, 1]` \* (y + `idy`) + `ody`
|
||||||
|
*
|
||||||
|
* where:
|
||||||
|
* - x and y are the coordinates in input image.
|
||||||
|
* - X and Y are the coordinates in output image.
|
||||||
|
* - (0,0) is the upper left corner.
|
||||||
|
*
|
||||||
|
* @since 0.27.0
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const pipeline = sharp()
|
||||||
|
* .affine([[1, 0.3], [0.1, 0.7]], {
|
||||||
|
* background: 'white',
|
||||||
|
* interpolator: sharp.interpolators.nohalo
|
||||||
|
* })
|
||||||
|
* .toBuffer((err, outputBuffer, info) => {
|
||||||
|
* // outputBuffer contains the transformed image
|
||||||
|
* // info.width and info.height contain the new dimensions
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* inputStream
|
||||||
|
* .pipe(pipeline);
|
||||||
|
*
|
||||||
|
* @param {Array<Array<number>>|Array<number>} matrix - affine transformation matrix
|
||||||
|
* @param {Object} [options] - if present, is an Object with optional attributes.
|
||||||
|
* @param {String|Object} [options.background="#000000"] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
|
||||||
|
* @param {Number} [options.idx=0] - input horizontal offset
|
||||||
|
* @param {Number} [options.idy=0] - input vertical offset
|
||||||
|
* @param {Number} [options.odx=0] - output horizontal offset
|
||||||
|
* @param {Number} [options.ody=0] - output vertical offset
|
||||||
|
* @param {String} [options.interpolator=sharp.interpolators.bicubic] - interpolator
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
function affine (matrix, options) {
|
||||||
|
const flatMatrix = [].concat(...matrix);
|
||||||
|
if (flatMatrix.length === 4 && flatMatrix.every(is.number)) {
|
||||||
|
this.options.affineMatrix = flatMatrix;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('matrix', '1x4 or 2x2 array', matrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is.defined(options)) {
|
||||||
|
if (is.object(options)) {
|
||||||
|
this._setBackgroundColourOption('affineBackground', options.background);
|
||||||
|
if (is.defined(options.idx)) {
|
||||||
|
if (is.number(options.idx)) {
|
||||||
|
this.options.affineIdx = options.idx;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('options.idx', 'number', options.idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(options.idy)) {
|
||||||
|
if (is.number(options.idy)) {
|
||||||
|
this.options.affineIdy = options.idy;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('options.idy', 'number', options.idy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(options.odx)) {
|
||||||
|
if (is.number(options.odx)) {
|
||||||
|
this.options.affineOdx = options.odx;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('options.odx', 'number', options.odx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(options.ody)) {
|
||||||
|
if (is.number(options.ody)) {
|
||||||
|
this.options.affineOdy = options.ody;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('options.ody', 'number', options.ody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(options.interpolator)) {
|
||||||
|
if (is.inArray(options.interpolator, Object.values(this.constructor.interpolators))) {
|
||||||
|
this.options.affineInterpolator = options.interpolator;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('options.interpolator', 'valid interpolator name', options.interpolator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('options', 'object', options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sharpen the image.
|
||||||
|
*
|
||||||
|
* When used without parameters, performs a fast, mild sharpen of the output image.
|
||||||
|
*
|
||||||
|
* When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space.
|
||||||
|
* Fine-grained control over the level of sharpening in "flat" (m1) and "jagged" (m2) areas is available.
|
||||||
|
*
|
||||||
|
* See {@link https://www.libvips.org/API/current/libvips-convolution.html#vips-sharpen|libvips sharpen} operation.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const data = await sharp(input).sharpen().toBuffer();
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const data = await sharp(input).sharpen({ sigma: 2 }).toBuffer();
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const data = await sharp(input)
|
||||||
|
* .sharpen({
|
||||||
|
* sigma: 2,
|
||||||
|
* m1: 0,
|
||||||
|
* m2: 3,
|
||||||
|
* x1: 3,
|
||||||
|
* y2: 15,
|
||||||
|
* y3: 15,
|
||||||
|
* })
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @param {Object|number} [options] - if present, is an Object with attributes
|
||||||
|
* @param {number} [options.sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`, between 0.000001 and 10
|
||||||
|
* @param {number} [options.m1=1.0] - the level of sharpening to apply to "flat" areas, between 0 and 1000000
|
||||||
|
* @param {number} [options.m2=2.0] - the level of sharpening to apply to "jagged" areas, between 0 and 1000000
|
||||||
|
* @param {number} [options.x1=2.0] - threshold between "flat" and "jagged", between 0 and 1000000
|
||||||
|
* @param {number} [options.y2=10.0] - maximum amount of brightening, between 0 and 1000000
|
||||||
|
* @param {number} [options.y3=20.0] - maximum amount of darkening, between 0 and 1000000
|
||||||
|
* @param {number} [flat] - (deprecated) see `options.m1`.
|
||||||
|
* @param {number} [jagged] - (deprecated) see `options.m2`.
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
function sharpen (options, flat, jagged) {
|
||||||
|
if (!is.defined(options)) {
|
||||||
|
// No arguments: default to mild sharpen
|
||||||
|
this.options.sharpenSigma = -1;
|
||||||
|
} else if (is.bool(options)) {
|
||||||
|
// Deprecated boolean argument: apply mild sharpen?
|
||||||
|
this.options.sharpenSigma = options ? -1 : 0;
|
||||||
|
} else if (is.number(options) && is.inRange(options, 0.01, 10000)) {
|
||||||
|
// Deprecated numeric argument: specific sigma
|
||||||
|
this.options.sharpenSigma = options;
|
||||||
|
// Deprecated control over flat areas
|
||||||
|
if (is.defined(flat)) {
|
||||||
|
if (is.number(flat) && is.inRange(flat, 0, 10000)) {
|
||||||
|
this.options.sharpenM1 = flat;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('flat', 'number between 0 and 10000', flat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Deprecated control over jagged areas
|
||||||
|
if (is.defined(jagged)) {
|
||||||
|
if (is.number(jagged) && is.inRange(jagged, 0, 10000)) {
|
||||||
|
this.options.sharpenM2 = jagged;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('jagged', 'number between 0 and 10000', jagged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (is.plainObject(options)) {
|
||||||
|
if (is.number(options.sigma) && is.inRange(options.sigma, 0.000001, 10)) {
|
||||||
|
this.options.sharpenSigma = options.sigma;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('options.sigma', 'number between 0.000001 and 10', options.sigma);
|
||||||
|
}
|
||||||
|
if (is.defined(options.m1)) {
|
||||||
|
if (is.number(options.m1) && is.inRange(options.m1, 0, 1000000)) {
|
||||||
|
this.options.sharpenM1 = options.m1;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('options.m1', 'number between 0 and 1000000', options.m1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(options.m2)) {
|
||||||
|
if (is.number(options.m2) && is.inRange(options.m2, 0, 1000000)) {
|
||||||
|
this.options.sharpenM2 = options.m2;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('options.m2', 'number between 0 and 1000000', options.m2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(options.x1)) {
|
||||||
|
if (is.number(options.x1) && is.inRange(options.x1, 0, 1000000)) {
|
||||||
|
this.options.sharpenX1 = options.x1;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('options.x1', 'number between 0 and 1000000', options.x1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(options.y2)) {
|
||||||
|
if (is.number(options.y2) && is.inRange(options.y2, 0, 1000000)) {
|
||||||
|
this.options.sharpenY2 = options.y2;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('options.y2', 'number between 0 and 1000000', options.y2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(options.y3)) {
|
||||||
|
if (is.number(options.y3) && is.inRange(options.y3, 0, 1000000)) {
|
||||||
|
this.options.sharpenY3 = options.y3;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('options.y3', 'number between 0 and 1000000', options.y3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('sigma', 'number between 0.01 and 10000', options);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply median filter.
|
||||||
|
* When used without parameters the default window is 3x3.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const output = await sharp(input).median().toBuffer();
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const output = await sharp(input).median(5).toBuffer();
|
||||||
|
*
|
||||||
|
* @param {number} [size=3] square mask size: size x size
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
function median (size) {
|
||||||
|
if (!is.defined(size)) {
|
||||||
|
// No arguments: default to 3x3
|
||||||
|
this.options.medianSize = 3;
|
||||||
|
} else if (is.integer(size) && is.inRange(size, 1, 1000)) {
|
||||||
|
// Numeric argument: specific sigma
|
||||||
|
this.options.medianSize = size;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('size', 'integer between 1 and 1000', size);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blur the image.
|
||||||
|
*
|
||||||
|
* When used without parameters, performs a fast 3x3 box blur (equivalent to a box linear filter).
|
||||||
|
*
|
||||||
|
* When a `sigma` is provided, performs a slower, more accurate Gaussian blur.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const boxBlurred = await sharp(input)
|
||||||
|
* .blur()
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const gaussianBlurred = await sharp(input)
|
||||||
|
* .blur(5)
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @param {number} [sigma] a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
function blur (sigma) {
|
||||||
|
if (!is.defined(sigma)) {
|
||||||
|
// No arguments: default to mild blur
|
||||||
|
this.options.blurSigma = -1;
|
||||||
|
} else if (is.bool(sigma)) {
|
||||||
|
// Boolean argument: apply mild blur?
|
||||||
|
this.options.blurSigma = sigma ? -1 : 0;
|
||||||
|
} else if (is.number(sigma) && is.inRange(sigma, 0.3, 1000)) {
|
||||||
|
// Numeric argument: specific sigma
|
||||||
|
this.options.blurSigma = sigma;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('sigma', 'number between 0.3 and 1000', sigma);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge alpha transparency channel, if any, with a background, then remove the alpha channel.
|
||||||
|
*
|
||||||
|
* See also {@link /api-channel#removealpha|removeAlpha}.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* await sharp(rgbaInput)
|
||||||
|
* .flatten({ background: '#F0A703' })
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @param {Object} [options]
|
||||||
|
* @param {string|Object} [options.background={r: 0, g: 0, b: 0}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black.
|
||||||
|
* @returns {Sharp}
|
||||||
|
*/
|
||||||
|
function flatten (options) {
|
||||||
|
this.options.flatten = is.bool(options) ? options : true;
|
||||||
|
if (is.object(options)) {
|
||||||
|
this._setBackgroundColourOption('flattenBackground', options.background);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure the image has an alpha channel
|
||||||
|
* with all white pixel values made fully transparent.
|
||||||
|
*
|
||||||
|
* Existing alpha channel values for non-white pixels remain unchanged.
|
||||||
|
*
|
||||||
|
* This feature is experimental and the API may change.
|
||||||
|
*
|
||||||
|
* @since 0.32.1
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* await sharp(rgbInput)
|
||||||
|
* .unflatten()
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* await sharp(rgbInput)
|
||||||
|
* .threshold(128, { grayscale: false }) // converter bright pixels to white
|
||||||
|
* .unflatten()
|
||||||
|
* .toBuffer();
|
||||||
|
*/
|
||||||
|
function unflatten () {
|
||||||
|
this.options.unflatten = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a gamma correction by reducing the encoding (darken) pre-resize at a factor of `1/gamma`
|
||||||
|
* then increasing the encoding (brighten) post-resize at a factor of `gamma`.
|
||||||
|
* This can improve the perceived brightness of a resized image in non-linear colour spaces.
|
||||||
|
* JPEG and WebP input images will not take advantage of the shrink-on-load performance optimisation
|
||||||
|
* when applying a gamma correction.
|
||||||
|
*
|
||||||
|
* Supply a second argument to use a different output gamma value, otherwise the first value is used in both cases.
|
||||||
|
*
|
||||||
|
* @param {number} [gamma=2.2] value between 1.0 and 3.0.
|
||||||
|
* @param {number} [gammaOut] value between 1.0 and 3.0. (optional, defaults to same as `gamma`)
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
function gamma (gamma, gammaOut) {
|
||||||
|
if (!is.defined(gamma)) {
|
||||||
|
// Default gamma correction of 2.2 (sRGB)
|
||||||
|
this.options.gamma = 2.2;
|
||||||
|
} else if (is.number(gamma) && is.inRange(gamma, 1, 3)) {
|
||||||
|
this.options.gamma = gamma;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('gamma', 'number between 1.0 and 3.0', gamma);
|
||||||
|
}
|
||||||
|
if (!is.defined(gammaOut)) {
|
||||||
|
// Default gamma correction for output is same as input
|
||||||
|
this.options.gammaOut = this.options.gamma;
|
||||||
|
} else if (is.number(gammaOut) && is.inRange(gammaOut, 1, 3)) {
|
||||||
|
this.options.gammaOut = gammaOut;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('gammaOut', 'number between 1.0 and 3.0', gammaOut);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produce the "negative" of the image.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const output = await sharp(input)
|
||||||
|
* .negate()
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const output = await sharp(input)
|
||||||
|
* .negate({ alpha: false })
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @param {Object} [options]
|
||||||
|
* @param {Boolean} [options.alpha=true] Whether or not to negate any alpha channel
|
||||||
|
* @returns {Sharp}
|
||||||
|
*/
|
||||||
|
function negate (options) {
|
||||||
|
this.options.negate = is.bool(options) ? options : true;
|
||||||
|
if (is.plainObject(options) && 'alpha' in options) {
|
||||||
|
if (!is.bool(options.alpha)) {
|
||||||
|
throw is.invalidParameterError('alpha', 'should be boolean value', options.alpha);
|
||||||
|
} else {
|
||||||
|
this.options.negateAlpha = options.alpha;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enhance output image contrast by stretching its luminance to cover a full dynamic range.
|
||||||
|
*
|
||||||
|
* Uses a histogram-based approach, taking a default range of 1% to 99% to reduce sensitivity to noise at the extremes.
|
||||||
|
*
|
||||||
|
* Luminance values below the `lower` percentile will be underexposed by clipping to zero.
|
||||||
|
* Luminance values above the `upper` percentile will be overexposed by clipping to the max pixel value.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const output = await sharp(input)
|
||||||
|
* .normalise()
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const output = await sharp(input)
|
||||||
|
* .normalise({ lower: 0, upper: 100 })
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @param {Object} [options]
|
||||||
|
* @param {number} [options.lower=1] - Percentile below which luminance values will be underexposed.
|
||||||
|
* @param {number} [options.upper=99] - Percentile above which luminance values will be overexposed.
|
||||||
|
* @returns {Sharp}
|
||||||
|
*/
|
||||||
|
function normalise (options) {
|
||||||
|
if (is.plainObject(options)) {
|
||||||
|
if (is.defined(options.lower)) {
|
||||||
|
if (is.number(options.lower) && is.inRange(options.lower, 0, 99)) {
|
||||||
|
this.options.normaliseLower = options.lower;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('lower', 'number between 0 and 99', options.lower);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(options.upper)) {
|
||||||
|
if (is.number(options.upper) && is.inRange(options.upper, 1, 100)) {
|
||||||
|
this.options.normaliseUpper = options.upper;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('upper', 'number between 1 and 100', options.upper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.options.normaliseLower >= this.options.normaliseUpper) {
|
||||||
|
throw is.invalidParameterError('range', 'lower to be less than upper',
|
||||||
|
`${this.options.normaliseLower} >= ${this.options.normaliseUpper}`);
|
||||||
|
}
|
||||||
|
this.options.normalise = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alternative spelling of normalise.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const output = await sharp(input)
|
||||||
|
* .normalize()
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @param {Object} [options]
|
||||||
|
* @param {number} [options.lower=1] - Percentile below which luminance values will be underexposed.
|
||||||
|
* @param {number} [options.upper=99] - Percentile above which luminance values will be overexposed.
|
||||||
|
* @returns {Sharp}
|
||||||
|
*/
|
||||||
|
function normalize (options) {
|
||||||
|
return this.normalise(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform contrast limiting adaptive histogram equalization
|
||||||
|
* {@link https://en.wikipedia.org/wiki/Adaptive_histogram_equalization#Contrast_Limited_AHE|CLAHE}.
|
||||||
|
*
|
||||||
|
* This will, in general, enhance the clarity of the image by bringing out darker details.
|
||||||
|
*
|
||||||
|
* @since 0.28.3
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const output = await sharp(input)
|
||||||
|
* .clahe({
|
||||||
|
* width: 3,
|
||||||
|
* height: 3,
|
||||||
|
* })
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @param {Object} options
|
||||||
|
* @param {number} options.width - Integral width of the search window, in pixels.
|
||||||
|
* @param {number} options.height - Integral height of the search window, in pixels.
|
||||||
|
* @param {number} [options.maxSlope=3] - Integral level of brightening, between 0 and 100, where 0 disables contrast limiting.
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
function clahe (options) {
|
||||||
|
if (is.plainObject(options)) {
|
||||||
|
if (is.integer(options.width) && options.width > 0) {
|
||||||
|
this.options.claheWidth = options.width;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('width', 'integer greater than zero', options.width);
|
||||||
|
}
|
||||||
|
if (is.integer(options.height) && options.height > 0) {
|
||||||
|
this.options.claheHeight = options.height;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('height', 'integer greater than zero', options.height);
|
||||||
|
}
|
||||||
|
if (is.defined(options.maxSlope)) {
|
||||||
|
if (is.integer(options.maxSlope) && is.inRange(options.maxSlope, 0, 100)) {
|
||||||
|
this.options.claheMaxSlope = options.maxSlope;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('maxSlope', 'integer between 0 and 100', options.maxSlope);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('options', 'plain object', options);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convolve the image with the specified kernel.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* sharp(input)
|
||||||
|
* .convolve({
|
||||||
|
* width: 3,
|
||||||
|
* height: 3,
|
||||||
|
* kernel: [-1, 0, 1, -2, 0, 2, -1, 0, 1]
|
||||||
|
* })
|
||||||
|
* .raw()
|
||||||
|
* .toBuffer(function(err, data, info) {
|
||||||
|
* // data contains the raw pixel data representing the convolution
|
||||||
|
* // of the input image with the horizontal Sobel operator
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* @param {Object} kernel
|
||||||
|
* @param {number} kernel.width - width of the kernel in pixels.
|
||||||
|
* @param {number} kernel.height - height of the kernel in pixels.
|
||||||
|
* @param {Array<number>} kernel.kernel - Array of length `width*height` containing the kernel values.
|
||||||
|
* @param {number} [kernel.scale=sum] - the scale of the kernel in pixels.
|
||||||
|
* @param {number} [kernel.offset=0] - the offset of the kernel in pixels.
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
function convolve (kernel) {
|
||||||
|
if (!is.object(kernel) || !Array.isArray(kernel.kernel) ||
|
||||||
|
!is.integer(kernel.width) || !is.integer(kernel.height) ||
|
||||||
|
!is.inRange(kernel.width, 3, 1001) || !is.inRange(kernel.height, 3, 1001) ||
|
||||||
|
kernel.height * kernel.width !== kernel.kernel.length
|
||||||
|
) {
|
||||||
|
// must pass in a kernel
|
||||||
|
throw new Error('Invalid convolution kernel');
|
||||||
|
}
|
||||||
|
// Default scale is sum of kernel values
|
||||||
|
if (!is.integer(kernel.scale)) {
|
||||||
|
kernel.scale = kernel.kernel.reduce(function (a, b) {
|
||||||
|
return a + b;
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
// Clip scale to a minimum value of 1
|
||||||
|
if (kernel.scale < 1) {
|
||||||
|
kernel.scale = 1;
|
||||||
|
}
|
||||||
|
if (!is.integer(kernel.offset)) {
|
||||||
|
kernel.offset = 0;
|
||||||
|
}
|
||||||
|
this.options.convKernel = kernel;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any pixel value greater than or equal to the threshold value will be set to 255, otherwise it will be set to 0.
|
||||||
|
* @param {number} [threshold=128] - a value in the range 0-255 representing the level at which the threshold will be applied.
|
||||||
|
* @param {Object} [options]
|
||||||
|
* @param {Boolean} [options.greyscale=true] - convert to single channel greyscale.
|
||||||
|
* @param {Boolean} [options.grayscale=true] - alternative spelling for greyscale.
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
function threshold (threshold, options) {
|
||||||
|
if (!is.defined(threshold)) {
|
||||||
|
this.options.threshold = 128;
|
||||||
|
} else if (is.bool(threshold)) {
|
||||||
|
this.options.threshold = threshold ? 128 : 0;
|
||||||
|
} else if (is.integer(threshold) && is.inRange(threshold, 0, 255)) {
|
||||||
|
this.options.threshold = threshold;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('threshold', 'integer between 0 and 255', threshold);
|
||||||
|
}
|
||||||
|
if (!is.object(options) || options.greyscale === true || options.grayscale === true) {
|
||||||
|
this.options.thresholdGrayscale = true;
|
||||||
|
} else {
|
||||||
|
this.options.thresholdGrayscale = false;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a bitwise boolean operation with operand image.
|
||||||
|
*
|
||||||
|
* This operation creates an output image where each pixel is the result of
|
||||||
|
* the selected bitwise boolean `operation` between the corresponding pixels of the input images.
|
||||||
|
*
|
||||||
|
* @param {Buffer|string} operand - Buffer containing image data or string containing the path to an image file.
|
||||||
|
* @param {string} operator - one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
|
||||||
|
* @param {Object} [options]
|
||||||
|
* @param {Object} [options.raw] - describes operand when using raw pixel data.
|
||||||
|
* @param {number} [options.raw.width]
|
||||||
|
* @param {number} [options.raw.height]
|
||||||
|
* @param {number} [options.raw.channels]
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
function boolean (operand, operator, options) {
|
||||||
|
this.options.boolean = this._createInputDescriptor(operand, options);
|
||||||
|
if (is.string(operator) && is.inArray(operator, ['and', 'or', 'eor'])) {
|
||||||
|
this.options.booleanOp = operator;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('operator', 'one of: and, or, eor', operator);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the linear formula `a` * input + `b` to the image to adjust image levels.
|
||||||
|
*
|
||||||
|
* When a single number is provided, it will be used for all image channels.
|
||||||
|
* When an array of numbers is provided, the array length must match the number of channels.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* await sharp(input)
|
||||||
|
* .linear(0.5, 2)
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* await sharp(rgbInput)
|
||||||
|
* .linear(
|
||||||
|
* [0.25, 0.5, 0.75],
|
||||||
|
* [150, 100, 50]
|
||||||
|
* )
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @param {(number|number[])} [a=[]] multiplier
|
||||||
|
* @param {(number|number[])} [b=[]] offset
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
function linear (a, b) {
|
||||||
|
if (!is.defined(a) && is.number(b)) {
|
||||||
|
a = 1.0;
|
||||||
|
} else if (is.number(a) && !is.defined(b)) {
|
||||||
|
b = 0.0;
|
||||||
|
}
|
||||||
|
if (!is.defined(a)) {
|
||||||
|
this.options.linearA = [];
|
||||||
|
} else if (is.number(a)) {
|
||||||
|
this.options.linearA = [a];
|
||||||
|
} else if (Array.isArray(a) && a.length && a.every(is.number)) {
|
||||||
|
this.options.linearA = a;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('a', 'number or array of numbers', a);
|
||||||
|
}
|
||||||
|
if (!is.defined(b)) {
|
||||||
|
this.options.linearB = [];
|
||||||
|
} else if (is.number(b)) {
|
||||||
|
this.options.linearB = [b];
|
||||||
|
} else if (Array.isArray(b) && b.length && b.every(is.number)) {
|
||||||
|
this.options.linearB = b;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('b', 'number or array of numbers', b);
|
||||||
|
}
|
||||||
|
if (this.options.linearA.length !== this.options.linearB.length) {
|
||||||
|
throw new Error('Expected a and b to be arrays of the same length');
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recombine the image with the specified matrix.
|
||||||
|
*
|
||||||
|
* @since 0.21.1
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* sharp(input)
|
||||||
|
* .recomb([
|
||||||
|
* [0.3588, 0.7044, 0.1368],
|
||||||
|
* [0.2990, 0.5870, 0.1140],
|
||||||
|
* [0.2392, 0.4696, 0.0912],
|
||||||
|
* ])
|
||||||
|
* .raw()
|
||||||
|
* .toBuffer(function(err, data, info) {
|
||||||
|
* // data contains the raw pixel data after applying the matrix
|
||||||
|
* // With this example input, a sepia filter has been applied
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* @param {Array<Array<number>>} inputMatrix - 3x3 Recombination matrix
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
function recomb (inputMatrix) {
|
||||||
|
if (!Array.isArray(inputMatrix) || inputMatrix.length !== 3 ||
|
||||||
|
inputMatrix[0].length !== 3 ||
|
||||||
|
inputMatrix[1].length !== 3 ||
|
||||||
|
inputMatrix[2].length !== 3
|
||||||
|
) {
|
||||||
|
// must pass in a kernel
|
||||||
|
throw new Error('Invalid recombination matrix');
|
||||||
|
}
|
||||||
|
this.options.recombMatrix = [
|
||||||
|
inputMatrix[0][0], inputMatrix[0][1], inputMatrix[0][2],
|
||||||
|
inputMatrix[1][0], inputMatrix[1][1], inputMatrix[1][2],
|
||||||
|
inputMatrix[2][0], inputMatrix[2][1], inputMatrix[2][2]
|
||||||
|
].map(Number);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms the image using brightness, saturation, hue rotation, and lightness.
|
||||||
|
* Brightness and lightness both operate on luminance, with the difference being that
|
||||||
|
* brightness is multiplicative whereas lightness is additive.
|
||||||
|
*
|
||||||
|
* @since 0.22.1
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // increase brightness by a factor of 2
|
||||||
|
* const output = await sharp(input)
|
||||||
|
* .modulate({
|
||||||
|
* brightness: 2
|
||||||
|
* })
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // hue-rotate by 180 degrees
|
||||||
|
* const output = await sharp(input)
|
||||||
|
* .modulate({
|
||||||
|
* hue: 180
|
||||||
|
* })
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // increase lightness by +50
|
||||||
|
* const output = await sharp(input)
|
||||||
|
* .modulate({
|
||||||
|
* lightness: 50
|
||||||
|
* })
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // decrease brightness and saturation while also hue-rotating by 90 degrees
|
||||||
|
* const output = await sharp(input)
|
||||||
|
* .modulate({
|
||||||
|
* brightness: 0.5,
|
||||||
|
* saturation: 0.5,
|
||||||
|
* hue: 90,
|
||||||
|
* })
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @param {Object} [options]
|
||||||
|
* @param {number} [options.brightness] Brightness multiplier
|
||||||
|
* @param {number} [options.saturation] Saturation multiplier
|
||||||
|
* @param {number} [options.hue] Degrees for hue rotation
|
||||||
|
* @param {number} [options.lightness] Lightness addend
|
||||||
|
* @returns {Sharp}
|
||||||
|
*/
|
||||||
|
function modulate (options) {
|
||||||
|
if (!is.plainObject(options)) {
|
||||||
|
throw is.invalidParameterError('options', 'plain object', options);
|
||||||
|
}
|
||||||
|
if ('brightness' in options) {
|
||||||
|
if (is.number(options.brightness) && options.brightness >= 0) {
|
||||||
|
this.options.brightness = options.brightness;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('brightness', 'number above zero', options.brightness);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ('saturation' in options) {
|
||||||
|
if (is.number(options.saturation) && options.saturation >= 0) {
|
||||||
|
this.options.saturation = options.saturation;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('saturation', 'number above zero', options.saturation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ('hue' in options) {
|
||||||
|
if (is.integer(options.hue)) {
|
||||||
|
this.options.hue = options.hue % 360;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('hue', 'number', options.hue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ('lightness' in options) {
|
||||||
|
if (is.number(options.lightness)) {
|
||||||
|
this.options.lightness = options.lightness;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('lightness', 'number', options.lightness);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorate the Sharp prototype with operation-related functions.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
module.exports = function (Sharp) {
|
||||||
|
Object.assign(Sharp.prototype, {
|
||||||
|
rotate,
|
||||||
|
flip,
|
||||||
|
flop,
|
||||||
|
affine,
|
||||||
|
sharpen,
|
||||||
|
median,
|
||||||
|
blur,
|
||||||
|
flatten,
|
||||||
|
unflatten,
|
||||||
|
gamma,
|
||||||
|
negate,
|
||||||
|
normalise,
|
||||||
|
normalize,
|
||||||
|
clahe,
|
||||||
|
convolve,
|
||||||
|
threshold,
|
||||||
|
boolean,
|
||||||
|
linear,
|
||||||
|
recomb,
|
||||||
|
modulate
|
||||||
|
});
|
||||||
|
};
|
||||||
1410
lib/output.js
Normal file
1410
lib/output.js
Normal file
File diff suppressed because it is too large
Load Diff
585
lib/resize.js
Normal file
585
lib/resize.js
Normal file
@@ -0,0 +1,585 @@
|
|||||||
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const is = require('./is');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Weighting to apply when using contain/cover fit.
|
||||||
|
* @member
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const gravity = {
|
||||||
|
center: 0,
|
||||||
|
centre: 0,
|
||||||
|
north: 1,
|
||||||
|
east: 2,
|
||||||
|
south: 3,
|
||||||
|
west: 4,
|
||||||
|
northeast: 5,
|
||||||
|
southeast: 6,
|
||||||
|
southwest: 7,
|
||||||
|
northwest: 8
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Position to apply when using contain/cover fit.
|
||||||
|
* @member
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const position = {
|
||||||
|
top: 1,
|
||||||
|
right: 2,
|
||||||
|
bottom: 3,
|
||||||
|
left: 4,
|
||||||
|
'right top': 5,
|
||||||
|
'right bottom': 6,
|
||||||
|
'left bottom': 7,
|
||||||
|
'left top': 8
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How to extend the image.
|
||||||
|
* @member
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const extendWith = {
|
||||||
|
background: 'background',
|
||||||
|
copy: 'copy',
|
||||||
|
repeat: 'repeat',
|
||||||
|
mirror: 'mirror'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strategies for automagic cover behaviour.
|
||||||
|
* @member
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const strategy = {
|
||||||
|
entropy: 16,
|
||||||
|
attention: 17
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reduction kernels.
|
||||||
|
* @member
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const kernel = {
|
||||||
|
nearest: 'nearest',
|
||||||
|
cubic: 'cubic',
|
||||||
|
mitchell: 'mitchell',
|
||||||
|
lanczos2: 'lanczos2',
|
||||||
|
lanczos3: 'lanczos3'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Methods by which an image can be resized to fit the provided dimensions.
|
||||||
|
* @member
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const fit = {
|
||||||
|
contain: 'contain',
|
||||||
|
cover: 'cover',
|
||||||
|
fill: 'fill',
|
||||||
|
inside: 'inside',
|
||||||
|
outside: 'outside'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map external fit property to internal canvas property.
|
||||||
|
* @member
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const mapFitToCanvas = {
|
||||||
|
contain: 'embed',
|
||||||
|
cover: 'crop',
|
||||||
|
fill: 'ignore_aspect',
|
||||||
|
inside: 'max',
|
||||||
|
outside: 'min'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function isRotationExpected (options) {
|
||||||
|
return (options.angle % 360) !== 0 || options.useExifOrientation === true || options.rotationAngle !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function isResizeExpected (options) {
|
||||||
|
return options.width !== -1 || options.height !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resize image to `width`, `height` or `width x height`.
|
||||||
|
*
|
||||||
|
* When both a `width` and `height` are provided, the possible methods by which the image should **fit** these are:
|
||||||
|
* - `cover`: (default) Preserving aspect ratio, attempt to ensure the image covers both provided dimensions by cropping/clipping to fit.
|
||||||
|
* - `contain`: Preserving aspect ratio, contain within both provided dimensions using "letterboxing" where necessary.
|
||||||
|
* - `fill`: Ignore the aspect ratio of the input and stretch to both provided dimensions.
|
||||||
|
* - `inside`: Preserving aspect ratio, resize the image to be as large as possible while ensuring its dimensions are less than or equal to both those specified.
|
||||||
|
* - `outside`: Preserving aspect ratio, resize the image to be as small as possible while ensuring its dimensions are greater than or equal to both those specified.
|
||||||
|
*
|
||||||
|
* Some of these values are based on the [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) CSS property.
|
||||||
|
*
|
||||||
|
* <img alt="Examples of various values for the fit property when resizing" width="100%" style="aspect-ratio: 998/243" src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/api-resize-fit.svg">
|
||||||
|
*
|
||||||
|
* When using a **fit** of `cover` or `contain`, the default **position** is `centre`. Other options are:
|
||||||
|
* - `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`.
|
||||||
|
* - `sharp.gravity`: `north`, `northeast`, `east`, `southeast`, `south`, `southwest`, `west`, `northwest`, `center` or `centre`.
|
||||||
|
* - `sharp.strategy`: `cover` only, dynamically crop using either the `entropy` or `attention` strategy.
|
||||||
|
*
|
||||||
|
* Some of these values are based on the [object-position](https://developer.mozilla.org/en-US/docs/Web/CSS/object-position) CSS property.
|
||||||
|
*
|
||||||
|
* The experimental strategy-based approach resizes so one dimension is at its target length
|
||||||
|
* then repeatedly ranks edge regions, discarding the edge with the lowest score based on the selected strategy.
|
||||||
|
* - `entropy`: focus on the region with the highest [Shannon entropy](https://en.wikipedia.org/wiki/Entropy_%28information_theory%29).
|
||||||
|
* - `attention`: focus on the region with the highest luminance frequency, colour saturation and presence of skin tones.
|
||||||
|
*
|
||||||
|
* Possible interpolation kernels are:
|
||||||
|
* - `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation).
|
||||||
|
* - `cubic`: Use a [Catmull-Rom spline](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline).
|
||||||
|
* - `mitchell`: Use a [Mitchell-Netravali spline](https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf).
|
||||||
|
* - `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`.
|
||||||
|
* - `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
|
||||||
|
*
|
||||||
|
* Only one resize can occur per pipeline.
|
||||||
|
* Previous calls to `resize` in the same pipeline will be ignored.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* sharp(input)
|
||||||
|
* .resize({ width: 100 })
|
||||||
|
* .toBuffer()
|
||||||
|
* .then(data => {
|
||||||
|
* // 100 pixels wide, auto-scaled height
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* sharp(input)
|
||||||
|
* .resize({ height: 100 })
|
||||||
|
* .toBuffer()
|
||||||
|
* .then(data => {
|
||||||
|
* // 100 pixels high, auto-scaled width
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* sharp(input)
|
||||||
|
* .resize(200, 300, {
|
||||||
|
* kernel: sharp.kernel.nearest,
|
||||||
|
* fit: 'contain',
|
||||||
|
* position: 'right top',
|
||||||
|
* background: { r: 255, g: 255, b: 255, alpha: 0.5 }
|
||||||
|
* })
|
||||||
|
* .toFile('output.png')
|
||||||
|
* .then(() => {
|
||||||
|
* // output.png is a 200 pixels wide and 300 pixels high image
|
||||||
|
* // containing a nearest-neighbour scaled version
|
||||||
|
* // contained within the north-east corner of a semi-transparent white canvas
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const transformer = sharp()
|
||||||
|
* .resize({
|
||||||
|
* width: 200,
|
||||||
|
* height: 200,
|
||||||
|
* fit: sharp.fit.cover,
|
||||||
|
* position: sharp.strategy.entropy
|
||||||
|
* });
|
||||||
|
* // Read image data from readableStream
|
||||||
|
* // Write 200px square auto-cropped image data to writableStream
|
||||||
|
* readableStream
|
||||||
|
* .pipe(transformer)
|
||||||
|
* .pipe(writableStream);
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* sharp(input)
|
||||||
|
* .resize(200, 200, {
|
||||||
|
* fit: sharp.fit.inside,
|
||||||
|
* withoutEnlargement: true
|
||||||
|
* })
|
||||||
|
* .toFormat('jpeg')
|
||||||
|
* .toBuffer()
|
||||||
|
* .then(function(outputBuffer) {
|
||||||
|
* // outputBuffer contains JPEG image data
|
||||||
|
* // no wider and no higher than 200 pixels
|
||||||
|
* // and no larger than the input image
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* sharp(input)
|
||||||
|
* .resize(200, 200, {
|
||||||
|
* fit: sharp.fit.outside,
|
||||||
|
* withoutReduction: true
|
||||||
|
* })
|
||||||
|
* .toFormat('jpeg')
|
||||||
|
* .toBuffer()
|
||||||
|
* .then(function(outputBuffer) {
|
||||||
|
* // outputBuffer contains JPEG image data
|
||||||
|
* // of at least 200 pixels wide and 200 pixels high while maintaining aspect ratio
|
||||||
|
* // and no smaller than the input image
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const scaleByHalf = await sharp(input)
|
||||||
|
* .metadata()
|
||||||
|
* .then(({ width }) => sharp(input)
|
||||||
|
* .resize(Math.round(width * 0.5))
|
||||||
|
* .toBuffer()
|
||||||
|
* );
|
||||||
|
*
|
||||||
|
* @param {number} [width] - How many pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height.
|
||||||
|
* @param {number} [height] - How many pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
|
||||||
|
* @param {Object} [options]
|
||||||
|
* @param {number} [options.width] - An alternative means of specifying `width`. If both are present this takes priority.
|
||||||
|
* @param {number} [options.height] - An alternative means of specifying `height`. If both are present this takes priority.
|
||||||
|
* @param {String} [options.fit='cover'] - How the image should be resized/cropped to fit the target dimension(s), one of `cover`, `contain`, `fill`, `inside` or `outside`.
|
||||||
|
* @param {String} [options.position='centre'] - A position, gravity or strategy to use when `fit` is `cover` or `contain`.
|
||||||
|
* @param {String|Object} [options.background={r: 0, g: 0, b: 0, alpha: 1}] - background colour when `fit` is `contain`, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency.
|
||||||
|
* @param {String} [options.kernel='lanczos3'] - The kernel to use for image reduction. Use the `fastShrinkOnLoad` option to control kernel vs shrink-on-load.
|
||||||
|
* @param {Boolean} [options.withoutEnlargement=false] - Do not scale up if the width *or* height are already less than the target dimensions, equivalent to GraphicsMagick's `>` geometry option. This may result in output dimensions smaller than the target dimensions.
|
||||||
|
* @param {Boolean} [options.withoutReduction=false] - Do not scale down if the width *or* height are already greater than the target dimensions, equivalent to GraphicsMagick's `<` geometry option. This may still result in a crop to reach the target dimensions.
|
||||||
|
* @param {Boolean} [options.fastShrinkOnLoad=true] - Take greater advantage of the JPEG and WebP shrink-on-load feature, which can lead to a slight moiré pattern or round-down of an auto-scaled dimension.
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
function resize (widthOrOptions, height, options) {
|
||||||
|
if (isResizeExpected(this.options)) {
|
||||||
|
this.options.debuglog('ignoring previous resize options');
|
||||||
|
}
|
||||||
|
if (this.options.widthPost !== -1) {
|
||||||
|
this.options.debuglog('operation order will be: extract, resize, extract');
|
||||||
|
}
|
||||||
|
if (is.defined(widthOrOptions)) {
|
||||||
|
if (is.object(widthOrOptions) && !is.defined(options)) {
|
||||||
|
options = widthOrOptions;
|
||||||
|
} else if (is.integer(widthOrOptions) && widthOrOptions > 0) {
|
||||||
|
this.options.width = widthOrOptions;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('width', 'positive integer', widthOrOptions);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.options.width = -1;
|
||||||
|
}
|
||||||
|
if (is.defined(height)) {
|
||||||
|
if (is.integer(height) && height > 0) {
|
||||||
|
this.options.height = height;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('height', 'positive integer', height);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.options.height = -1;
|
||||||
|
}
|
||||||
|
if (is.object(options)) {
|
||||||
|
// Width
|
||||||
|
if (is.defined(options.width)) {
|
||||||
|
if (is.integer(options.width) && options.width > 0) {
|
||||||
|
this.options.width = options.width;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('width', 'positive integer', options.width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Height
|
||||||
|
if (is.defined(options.height)) {
|
||||||
|
if (is.integer(options.height) && options.height > 0) {
|
||||||
|
this.options.height = options.height;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('height', 'positive integer', options.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fit
|
||||||
|
if (is.defined(options.fit)) {
|
||||||
|
const canvas = mapFitToCanvas[options.fit];
|
||||||
|
if (is.string(canvas)) {
|
||||||
|
this.options.canvas = canvas;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('fit', 'valid fit', options.fit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Position
|
||||||
|
if (is.defined(options.position)) {
|
||||||
|
const pos = is.integer(options.position)
|
||||||
|
? options.position
|
||||||
|
: strategy[options.position] || position[options.position] || gravity[options.position];
|
||||||
|
if (is.integer(pos) && (is.inRange(pos, 0, 8) || is.inRange(pos, 16, 17))) {
|
||||||
|
this.options.position = pos;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('position', 'valid position/gravity/strategy', options.position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Background
|
||||||
|
this._setBackgroundColourOption('resizeBackground', options.background);
|
||||||
|
// Kernel
|
||||||
|
if (is.defined(options.kernel)) {
|
||||||
|
if (is.string(kernel[options.kernel])) {
|
||||||
|
this.options.kernel = kernel[options.kernel];
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('kernel', 'valid kernel name', options.kernel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Without enlargement
|
||||||
|
if (is.defined(options.withoutEnlargement)) {
|
||||||
|
this._setBooleanOption('withoutEnlargement', options.withoutEnlargement);
|
||||||
|
}
|
||||||
|
// Without reduction
|
||||||
|
if (is.defined(options.withoutReduction)) {
|
||||||
|
this._setBooleanOption('withoutReduction', options.withoutReduction);
|
||||||
|
}
|
||||||
|
// Shrink on load
|
||||||
|
if (is.defined(options.fastShrinkOnLoad)) {
|
||||||
|
this._setBooleanOption('fastShrinkOnLoad', options.fastShrinkOnLoad);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isRotationExpected(this.options) && isResizeExpected(this.options)) {
|
||||||
|
this.options.rotateBeforePreExtract = true;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extend / pad / extrude one or more edges of the image with either
|
||||||
|
* the provided background colour or pixels derived from the image.
|
||||||
|
* This operation will always occur after resizing and extraction, if any.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Resize to 140 pixels wide, then add 10 transparent pixels
|
||||||
|
* // to the top, left and right edges and 20 to the bottom edge
|
||||||
|
* sharp(input)
|
||||||
|
* .resize(140)
|
||||||
|
* .extend({
|
||||||
|
* top: 10,
|
||||||
|
* bottom: 20,
|
||||||
|
* left: 10,
|
||||||
|
* right: 10,
|
||||||
|
* background: { r: 0, g: 0, b: 0, alpha: 0 }
|
||||||
|
* })
|
||||||
|
* ...
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Add a row of 10 red pixels to the bottom
|
||||||
|
* sharp(input)
|
||||||
|
* .extend({
|
||||||
|
* bottom: 10,
|
||||||
|
* background: 'red'
|
||||||
|
* })
|
||||||
|
* ...
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Extrude image by 8 pixels to the right, mirroring existing right hand edge
|
||||||
|
* sharp(input)
|
||||||
|
* .extend({
|
||||||
|
* right: 8,
|
||||||
|
* background: 'mirror'
|
||||||
|
* })
|
||||||
|
* ...
|
||||||
|
*
|
||||||
|
* @param {(number|Object)} extend - single pixel count to add to all edges or an Object with per-edge counts
|
||||||
|
* @param {number} [extend.top=0]
|
||||||
|
* @param {number} [extend.left=0]
|
||||||
|
* @param {number} [extend.bottom=0]
|
||||||
|
* @param {number} [extend.right=0]
|
||||||
|
* @param {String} [extend.extendWith='background'] - populate new pixels using this method, one of: background, copy, repeat, mirror.
|
||||||
|
* @param {String|Object} [extend.background={r: 0, g: 0, b: 0, alpha: 1}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency.
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
function extend (extend) {
|
||||||
|
if (is.integer(extend) && extend > 0) {
|
||||||
|
this.options.extendTop = extend;
|
||||||
|
this.options.extendBottom = extend;
|
||||||
|
this.options.extendLeft = extend;
|
||||||
|
this.options.extendRight = extend;
|
||||||
|
} else if (is.object(extend)) {
|
||||||
|
if (is.defined(extend.top)) {
|
||||||
|
if (is.integer(extend.top) && extend.top >= 0) {
|
||||||
|
this.options.extendTop = extend.top;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('top', 'positive integer', extend.top);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(extend.bottom)) {
|
||||||
|
if (is.integer(extend.bottom) && extend.bottom >= 0) {
|
||||||
|
this.options.extendBottom = extend.bottom;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('bottom', 'positive integer', extend.bottom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(extend.left)) {
|
||||||
|
if (is.integer(extend.left) && extend.left >= 0) {
|
||||||
|
this.options.extendLeft = extend.left;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('left', 'positive integer', extend.left);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(extend.right)) {
|
||||||
|
if (is.integer(extend.right) && extend.right >= 0) {
|
||||||
|
this.options.extendRight = extend.right;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('right', 'positive integer', extend.right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._setBackgroundColourOption('extendBackground', extend.background);
|
||||||
|
if (is.defined(extend.extendWith)) {
|
||||||
|
if (is.string(extendWith[extend.extendWith])) {
|
||||||
|
this.options.extendWith = extendWith[extend.extendWith];
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('extendWith', 'one of: background, copy, repeat, mirror', extend.extendWith);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('extend', 'integer or object', extend);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract/crop a region of the image.
|
||||||
|
*
|
||||||
|
* - Use `extract` before `resize` for pre-resize extraction.
|
||||||
|
* - Use `extract` after `resize` for post-resize extraction.
|
||||||
|
* - Use `extract` twice and `resize` once for extract-then-resize-then-extract in a fixed operation order.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* sharp(input)
|
||||||
|
* .extract({ left: left, top: top, width: width, height: height })
|
||||||
|
* .toFile(output, function(err) {
|
||||||
|
* // Extract a region of the input image, saving in the same format.
|
||||||
|
* });
|
||||||
|
* @example
|
||||||
|
* sharp(input)
|
||||||
|
* .extract({ left: leftOffsetPre, top: topOffsetPre, width: widthPre, height: heightPre })
|
||||||
|
* .resize(width, height)
|
||||||
|
* .extract({ left: leftOffsetPost, top: topOffsetPost, width: widthPost, height: heightPost })
|
||||||
|
* .toFile(output, function(err) {
|
||||||
|
* // Extract a region, resize, then extract from the resized image
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* @param {Object} options - describes the region to extract using integral pixel values
|
||||||
|
* @param {number} options.left - zero-indexed offset from left edge
|
||||||
|
* @param {number} options.top - zero-indexed offset from top edge
|
||||||
|
* @param {number} options.width - width of region to extract
|
||||||
|
* @param {number} options.height - height of region to extract
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
function extract (options) {
|
||||||
|
const suffix = isResizeExpected(this.options) || this.options.widthPre !== -1 ? 'Post' : 'Pre';
|
||||||
|
if (this.options[`width${suffix}`] !== -1) {
|
||||||
|
this.options.debuglog('ignoring previous extract options');
|
||||||
|
}
|
||||||
|
['left', 'top', 'width', 'height'].forEach(function (name) {
|
||||||
|
const value = options[name];
|
||||||
|
if (is.integer(value) && value >= 0) {
|
||||||
|
this.options[name + (name === 'left' || name === 'top' ? 'Offset' : '') + suffix] = value;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError(name, 'integer', value);
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
// Ensure existing rotation occurs before pre-resize extraction
|
||||||
|
if (isRotationExpected(this.options) && !isResizeExpected(this.options)) {
|
||||||
|
if (this.options.widthPre === -1 || this.options.widthPost === -1) {
|
||||||
|
this.options.rotateBeforePreExtract = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trim pixels from all edges that contain values similar to the given background colour, which defaults to that of the top-left pixel.
|
||||||
|
*
|
||||||
|
* Images with an alpha channel will use the combined bounding box of alpha and non-alpha channels.
|
||||||
|
*
|
||||||
|
* If the result of this operation would trim an image to nothing then no change is made.
|
||||||
|
*
|
||||||
|
* The `info` response Object, obtained from callback of `.toFile()` or `.toBuffer()`,
|
||||||
|
* will contain `trimOffsetLeft` and `trimOffsetTop` properties.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Trim pixels with a colour similar to that of the top-left pixel.
|
||||||
|
* sharp(input)
|
||||||
|
* .trim()
|
||||||
|
* .toFile(output, function(err, info) {
|
||||||
|
* ...
|
||||||
|
* });
|
||||||
|
* @example
|
||||||
|
* // Trim pixels with the exact same colour as that of the top-left pixel.
|
||||||
|
* sharp(input)
|
||||||
|
* .trim(0)
|
||||||
|
* .toFile(output, function(err, info) {
|
||||||
|
* ...
|
||||||
|
* });
|
||||||
|
* @example
|
||||||
|
* // Trim only pixels with a similar colour to red.
|
||||||
|
* sharp(input)
|
||||||
|
* .trim("#FF0000")
|
||||||
|
* .toFile(output, function(err, info) {
|
||||||
|
* ...
|
||||||
|
* });
|
||||||
|
* @example
|
||||||
|
* // Trim all "yellow-ish" pixels, being more lenient with the higher threshold.
|
||||||
|
* sharp(input)
|
||||||
|
* .trim({
|
||||||
|
* background: "yellow",
|
||||||
|
* threshold: 42,
|
||||||
|
* })
|
||||||
|
* .toFile(output, function(err, info) {
|
||||||
|
* ...
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* @param {string|number|Object} trim - the specific background colour to trim, the threshold for doing so or an Object with both.
|
||||||
|
* @param {string|Object} [trim.background='top-left pixel'] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to that of the top-left pixel.
|
||||||
|
* @param {number} [trim.threshold=10] - the allowed difference from the above colour, a positive number.
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
function trim (trim) {
|
||||||
|
if (!is.defined(trim)) {
|
||||||
|
this.options.trimThreshold = 10;
|
||||||
|
} else if (is.string(trim)) {
|
||||||
|
this._setBackgroundColourOption('trimBackground', trim);
|
||||||
|
this.options.trimThreshold = 10;
|
||||||
|
} else if (is.number(trim)) {
|
||||||
|
if (trim >= 0) {
|
||||||
|
this.options.trimThreshold = trim;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('threshold', 'positive number', trim);
|
||||||
|
}
|
||||||
|
} else if (is.object(trim)) {
|
||||||
|
this._setBackgroundColourOption('trimBackground', trim.background);
|
||||||
|
if (!is.defined(trim.threshold)) {
|
||||||
|
this.options.trimThreshold = 10;
|
||||||
|
} else if (is.number(trim.threshold) && trim.threshold >= 0) {
|
||||||
|
this.options.trimThreshold = trim.threshold;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('threshold', 'positive number', trim);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('trim', 'string, number or object', trim);
|
||||||
|
}
|
||||||
|
if (isRotationExpected(this.options)) {
|
||||||
|
this.options.rotateBeforePreExtract = true;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorate the Sharp prototype with resize-related functions.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
module.exports = function (Sharp) {
|
||||||
|
Object.assign(Sharp.prototype, {
|
||||||
|
resize,
|
||||||
|
extend,
|
||||||
|
extract,
|
||||||
|
trim
|
||||||
|
});
|
||||||
|
// Class attributes
|
||||||
|
Sharp.gravity = gravity;
|
||||||
|
Sharp.strategy = strategy;
|
||||||
|
Sharp.kernel = kernel;
|
||||||
|
Sharp.fit = fit;
|
||||||
|
Sharp.position = position;
|
||||||
|
};
|
||||||
66
lib/sharp.js
Normal file
66
lib/sharp.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Inspects the runtime environment and exports the relevant sharp.node binary
|
||||||
|
|
||||||
|
const { familySync, versionSync } = require('detect-libc');
|
||||||
|
|
||||||
|
const { runtimePlatformArch, prebuiltPlatforms, minimumLibvipsVersion } = require('./libvips');
|
||||||
|
const runtimePlatform = runtimePlatformArch();
|
||||||
|
const [isLinux, isMacOs, isWindows] = ['linux', 'darwin', 'win32'].map(os => runtimePlatform.startsWith(os));
|
||||||
|
|
||||||
|
/* istanbul ignore next */
|
||||||
|
try {
|
||||||
|
// Check for local build
|
||||||
|
module.exports = require(`../src/build/Release/sharp-${runtimePlatform}.node`);
|
||||||
|
} catch (errLocal) {
|
||||||
|
try {
|
||||||
|
// Check for runtime package
|
||||||
|
module.exports = require(`@sharpen/sharp-${runtimePlatform}/sharp.node`);
|
||||||
|
} catch (errPackage) {
|
||||||
|
const help = ['Could not load the "sharp" module at runtime'];
|
||||||
|
if (errLocal.code !== 'MODULE_NOT_FOUND') {
|
||||||
|
help.push(`${errLocal.code}: ${errLocal.message}`);
|
||||||
|
}
|
||||||
|
if (errPackage.code !== 'MODULE_NOT_FOUND') {
|
||||||
|
help.push(`${errPackage.code}: ${errPackage.message}`);
|
||||||
|
}
|
||||||
|
help.push('Possible solutions:');
|
||||||
|
// Common error messages
|
||||||
|
if (prebuiltPlatforms.includes(runtimePlatform)) {
|
||||||
|
help.push('- Add explicit dependencies for the runtime platform:');
|
||||||
|
help.push(` npm install --force @sharpen/sharp-${runtimePlatform}`);
|
||||||
|
if (!isWindows) {
|
||||||
|
help.push(` npm install --force @sharpen/sharp-libvips-${runtimePlatform}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
help.push(`- The ${runtimePlatform} platform requires manual installation of libvips >= ${minimumLibvipsVersion}`);
|
||||||
|
}
|
||||||
|
if (isLinux && /symbol not found/i.test(errPackage)) {
|
||||||
|
try {
|
||||||
|
const { engines } = require(`@sharpen/sharp-libvips-${runtimePlatform}/package`);
|
||||||
|
const libcFound = `${familySync()} ${versionSync()}`;
|
||||||
|
const libcRequires = `${engines.musl ? 'musl' : 'glibc'} ${engines.musl || engines.glibc}`;
|
||||||
|
help.push(`- Update your OS: found ${libcFound}, requires ${libcRequires}`);
|
||||||
|
} catch (errEngines) {}
|
||||||
|
}
|
||||||
|
if (isMacOs && /Incompatible library version/.test(errLocal.message)) {
|
||||||
|
help.push('- Update Homebrew:');
|
||||||
|
help.push(' brew update && brew upgrade vips');
|
||||||
|
}
|
||||||
|
if (errPackage.code === 'ERR_DLOPEN_DISABLED') {
|
||||||
|
help.push('- Run Node.js without using the --no-addons flag');
|
||||||
|
}
|
||||||
|
// Link to installation docs
|
||||||
|
if (isLinux && /Module did not self-register/.test(errLocal.message + errPackage.message)) {
|
||||||
|
help.push('- Using worker threads on Linux? See https://sharp.pixelplumbing.com/install#worker-threads');
|
||||||
|
} else if (isWindows && /The specified procedure could not be found/.test(errPackage.message)) {
|
||||||
|
help.push('- Using the canvas package on Windows? See https://sharp.pixelplumbing.com/install#canvas-and-windows');
|
||||||
|
} else {
|
||||||
|
help.push('- Consult the installation documentation: https://sharp.pixelplumbing.com/install');
|
||||||
|
}
|
||||||
|
throw new Error(help.join('\n'));
|
||||||
|
}
|
||||||
|
}
|
||||||
273
lib/utility.js
Normal file
273
lib/utility.js
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const events = require('node:events');
|
||||||
|
const detectLibc = require('detect-libc');
|
||||||
|
|
||||||
|
const is = require('./is');
|
||||||
|
const { runtimePlatformArch } = require('./libvips');
|
||||||
|
const sharp = require('./sharp');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An Object containing nested boolean values representing the available input and output formats/methods.
|
||||||
|
* @member
|
||||||
|
* @example
|
||||||
|
* console.log(sharp.format);
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
const format = sharp.format();
|
||||||
|
format.heif.output.alias = ['avif', 'heic'];
|
||||||
|
format.jpeg.output.alias = ['jpe', 'jpg'];
|
||||||
|
format.tiff.output.alias = ['tif'];
|
||||||
|
format.jp2k.output.alias = ['j2c', 'j2k', 'jp2', 'jpx'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An Object containing the available interpolators and their proper values
|
||||||
|
* @readonly
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
const interpolators = {
|
||||||
|
/** [Nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation). Suitable for image enlargement only. */
|
||||||
|
nearest: 'nearest',
|
||||||
|
/** [Bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation). Faster than bicubic but with less smooth results. */
|
||||||
|
bilinear: 'bilinear',
|
||||||
|
/** [Bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) (the default). */
|
||||||
|
bicubic: 'bicubic',
|
||||||
|
/** [LBB interpolation](https://github.com/libvips/libvips/blob/master/libvips/resample/lbb.cpp#L100). Prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" but typically reduces performance by a factor of 2. */
|
||||||
|
locallyBoundedBicubic: 'lbb',
|
||||||
|
/** [Nohalo interpolation](http://eprints.soton.ac.uk/268086/). Prevents acutance but typically reduces performance by a factor of 3. */
|
||||||
|
nohalo: 'nohalo',
|
||||||
|
/** [VSQBS interpolation](https://github.com/libvips/libvips/blob/master/libvips/resample/vsqbs.cpp#L48). Prevents "staircasing" when enlarging. */
|
||||||
|
vertexSplitQuadraticBasisSpline: 'vsqbs'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An Object containing the version numbers of sharp, libvips and its dependencies.
|
||||||
|
* @member
|
||||||
|
* @example
|
||||||
|
* console.log(sharp.versions);
|
||||||
|
*/
|
||||||
|
let versions = {
|
||||||
|
vips: sharp.libvipsVersion()
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
versions = require(`@sharpen/sharp-${runtimePlatformArch()}/versions`);
|
||||||
|
} catch (_) {
|
||||||
|
try {
|
||||||
|
versions = require(`@sharpen/sharp-libvips-${runtimePlatformArch()}/versions`);
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
versions.sharp = require('../package.json').version;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets or, when options are provided, sets the limits of _libvips'_ operation cache.
|
||||||
|
* Existing entries in the cache will be trimmed after any change in limits.
|
||||||
|
* This method always returns cache statistics,
|
||||||
|
* useful for determining how much working memory is required for a particular task.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const stats = sharp.cache();
|
||||||
|
* @example
|
||||||
|
* sharp.cache( { items: 200 } );
|
||||||
|
* sharp.cache( { files: 0 } );
|
||||||
|
* sharp.cache(false);
|
||||||
|
*
|
||||||
|
* @param {Object|boolean} [options=true] - Object with the following attributes, or boolean where true uses default cache settings and false removes all caching
|
||||||
|
* @param {number} [options.memory=50] - is the maximum memory in MB to use for this cache
|
||||||
|
* @param {number} [options.files=20] - is the maximum number of files to hold open
|
||||||
|
* @param {number} [options.items=100] - is the maximum number of operations to cache
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
function cache (options) {
|
||||||
|
if (is.bool(options)) {
|
||||||
|
if (options) {
|
||||||
|
// Default cache settings of 50MB, 20 files, 100 items
|
||||||
|
return sharp.cache(50, 20, 100);
|
||||||
|
} else {
|
||||||
|
return sharp.cache(0, 0, 0);
|
||||||
|
}
|
||||||
|
} else if (is.object(options)) {
|
||||||
|
return sharp.cache(options.memory, options.files, options.items);
|
||||||
|
} else {
|
||||||
|
return sharp.cache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cache(true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets or, when a concurrency is provided, sets
|
||||||
|
* the maximum number of threads _libvips_ should use to process _each image_.
|
||||||
|
* These are from a thread pool managed by glib,
|
||||||
|
* which helps avoid the overhead of creating new threads.
|
||||||
|
*
|
||||||
|
* This method always returns the current concurrency.
|
||||||
|
*
|
||||||
|
* The default value is the number of CPU cores,
|
||||||
|
* except when using glibc-based Linux without jemalloc,
|
||||||
|
* where the default is `1` to help reduce memory fragmentation.
|
||||||
|
*
|
||||||
|
* A value of `0` will reset this to the number of CPU cores.
|
||||||
|
*
|
||||||
|
* Some image format libraries spawn additional threads,
|
||||||
|
* e.g. libaom manages its own 4 threads when encoding AVIF images,
|
||||||
|
* and these are independent of the value set here.
|
||||||
|
*
|
||||||
|
* The maximum number of images that sharp can process in parallel
|
||||||
|
* is controlled by libuv's `UV_THREADPOOL_SIZE` environment variable,
|
||||||
|
* which defaults to 4.
|
||||||
|
*
|
||||||
|
* https://nodejs.org/api/cli.html#uv_threadpool_sizesize
|
||||||
|
*
|
||||||
|
* For example, by default, a machine with 8 CPU cores will process
|
||||||
|
* 4 images in parallel and use up to 8 threads per image,
|
||||||
|
* so there will be up to 32 concurrent threads.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const threads = sharp.concurrency(); // 4
|
||||||
|
* sharp.concurrency(2); // 2
|
||||||
|
* sharp.concurrency(0); // 4
|
||||||
|
*
|
||||||
|
* @param {number} [concurrency]
|
||||||
|
* @returns {number} concurrency
|
||||||
|
*/
|
||||||
|
function concurrency (concurrency) {
|
||||||
|
return sharp.concurrency(is.integer(concurrency) ? concurrency : null);
|
||||||
|
}
|
||||||
|
/* istanbul ignore next */
|
||||||
|
if (detectLibc.familySync() === detectLibc.GLIBC && !sharp._isUsingJemalloc()) {
|
||||||
|
// Reduce default concurrency to 1 when using glibc memory allocator
|
||||||
|
sharp.concurrency(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An EventEmitter that emits a `change` event when a task is either:
|
||||||
|
* - queued, waiting for _libuv_ to provide a worker thread
|
||||||
|
* - complete
|
||||||
|
* @member
|
||||||
|
* @example
|
||||||
|
* sharp.queue.on('change', function(queueLength) {
|
||||||
|
* console.log('Queue contains ' + queueLength + ' task(s)');
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
const queue = new events.EventEmitter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides access to internal task counters.
|
||||||
|
* - queue is the number of tasks this module has queued waiting for _libuv_ to provide a worker thread from its pool.
|
||||||
|
* - process is the number of resize tasks currently being processed.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const counters = sharp.counters(); // { queue: 2, process: 4 }
|
||||||
|
*
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
function counters () {
|
||||||
|
return sharp.counters();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get and set use of SIMD vector unit instructions.
|
||||||
|
* Requires libvips to have been compiled with liborc support.
|
||||||
|
*
|
||||||
|
* Improves the performance of `resize`, `blur` and `sharpen` operations
|
||||||
|
* by taking advantage of the SIMD vector unit of the CPU, e.g. Intel SSE and ARM NEON.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const simd = sharp.simd();
|
||||||
|
* // simd is `true` if the runtime use of liborc is currently enabled
|
||||||
|
* @example
|
||||||
|
* const simd = sharp.simd(false);
|
||||||
|
* // prevent libvips from using liborc at runtime
|
||||||
|
*
|
||||||
|
* @param {boolean} [simd=true]
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function simd (simd) {
|
||||||
|
return sharp.simd(is.bool(simd) ? simd : null);
|
||||||
|
}
|
||||||
|
simd(true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block libvips operations at runtime.
|
||||||
|
*
|
||||||
|
* This is in addition to the `VIPS_BLOCK_UNTRUSTED` environment variable,
|
||||||
|
* which when set will block all "untrusted" operations.
|
||||||
|
*
|
||||||
|
* @since 0.32.4
|
||||||
|
*
|
||||||
|
* @example <caption>Block all TIFF input.</caption>
|
||||||
|
* sharp.block({
|
||||||
|
* operation: ['VipsForeignLoadTiff']
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* @param {Object} options
|
||||||
|
* @param {Array<string>} options.operation - List of libvips low-level operation names to block.
|
||||||
|
*/
|
||||||
|
function block (options) {
|
||||||
|
if (is.object(options)) {
|
||||||
|
if (Array.isArray(options.operation) && options.operation.every(is.string)) {
|
||||||
|
sharp.block(options.operation, true);
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('operation', 'Array<string>', options.operation);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('options', 'object', options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unblock libvips operations at runtime.
|
||||||
|
*
|
||||||
|
* This is useful for defining a list of allowed operations.
|
||||||
|
*
|
||||||
|
* @since 0.32.4
|
||||||
|
*
|
||||||
|
* @example <caption>Block all input except WebP from the filesystem.</caption>
|
||||||
|
* sharp.block({
|
||||||
|
* operation: ['VipsForeignLoad']
|
||||||
|
* });
|
||||||
|
* sharp.unblock({
|
||||||
|
* operation: ['VipsForeignLoadWebpFile']
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* @example <caption>Block all input except JPEG and PNG from a Buffer or Stream.</caption>
|
||||||
|
* sharp.block({
|
||||||
|
* operation: ['VipsForeignLoad']
|
||||||
|
* });
|
||||||
|
* sharp.unblock({
|
||||||
|
* operation: ['VipsForeignLoadJpegBuffer', 'VipsForeignLoadPngBuffer']
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* @param {Object} options
|
||||||
|
* @param {Array<string>} options.operation - List of libvips low-level operation names to unblock.
|
||||||
|
*/
|
||||||
|
function unblock (options) {
|
||||||
|
if (is.object(options)) {
|
||||||
|
if (Array.isArray(options.operation) && options.operation.every(is.string)) {
|
||||||
|
sharp.block(options.operation, false);
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('operation', 'Array<string>', options.operation);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('options', 'object', options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorate the Sharp class with utility-related functions.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
module.exports = function (Sharp) {
|
||||||
|
Sharp.cache = cache;
|
||||||
|
Sharp.concurrency = concurrency;
|
||||||
|
Sharp.counters = counters;
|
||||||
|
Sharp.simd = simd;
|
||||||
|
Sharp.format = format;
|
||||||
|
Sharp.interpolators = interpolators;
|
||||||
|
Sharp.versions = versions;
|
||||||
|
Sharp.queue = queue;
|
||||||
|
Sharp.block = block;
|
||||||
|
Sharp.unblock = unblock;
|
||||||
|
};
|
||||||
40
npm/darwin-arm64/package.json
Normal file
40
npm/darwin-arm64/package.json
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"name": "@sharpen/sharp-darwin-arm64",
|
||||||
|
"version": "0.0.1-alpha.5",
|
||||||
|
"description": "Prebuilt sharp for use with macOS ARM64",
|
||||||
|
"homepage": "https://sharp.pixelplumbing.com",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/lovell/sharp.git",
|
||||||
|
"directory": "npm/darwin-arm64"
|
||||||
|
},
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"preferUnplugged": true,
|
||||||
|
"files": [
|
||||||
|
"lib"
|
||||||
|
],
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"type": "commonjs",
|
||||||
|
"exports": {
|
||||||
|
"./sharp.node": "./lib/sharp-darwin-arm64.node",
|
||||||
|
"./package": "./package.json"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||||
|
"npm": ">=9.6.5",
|
||||||
|
"yarn": ">=3.2.0",
|
||||||
|
"pnpm": ">=7.1.0",
|
||||||
|
"glibc": ">=2.26"
|
||||||
|
},
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
]
|
||||||
|
}
|
||||||
40
npm/darwin-x64/package.json
Normal file
40
npm/darwin-x64/package.json
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"name": "@sharpen/sharp-darwin-x64",
|
||||||
|
"version": "0.0.1-alpha.5",
|
||||||
|
"description": "Prebuilt sharp for use with macOS x64",
|
||||||
|
"homepage": "https://sharp.pixelplumbing.com",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/lovell/sharp.git",
|
||||||
|
"directory": "npm/darwin-x64"
|
||||||
|
},
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"preferUnplugged": true,
|
||||||
|
"files": [
|
||||||
|
"lib"
|
||||||
|
],
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"type": "commonjs",
|
||||||
|
"exports": {
|
||||||
|
"./sharp.node": "./lib/sharp-darwin-x64.node",
|
||||||
|
"./package": "./package.json"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||||
|
"npm": ">=9.6.5",
|
||||||
|
"yarn": ">=3.2.0",
|
||||||
|
"pnpm": ">=7.1.0",
|
||||||
|
"glibc": ">=2.26"
|
||||||
|
},
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
]
|
||||||
|
}
|
||||||
70
npm/from-github-release.js
Normal file
70
npm/from-github-release.js
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Populate contents of all packages with the current GitHub release
|
||||||
|
|
||||||
|
const { writeFile, copyFile, rm } = require('node:fs/promises');
|
||||||
|
const path = require('node:path');
|
||||||
|
const { Readable } = require('node:stream');
|
||||||
|
const { pipeline } = require('node:stream/promises');
|
||||||
|
const { createGunzip } = require('node:zlib');
|
||||||
|
const { extract } = require('tar-fs');
|
||||||
|
|
||||||
|
const { workspaces } = require('./package.json');
|
||||||
|
const { version } = require('../package.json');
|
||||||
|
|
||||||
|
const mapTarballEntry = (header) => {
|
||||||
|
header.name = path.basename(header.name);
|
||||||
|
return header;
|
||||||
|
};
|
||||||
|
|
||||||
|
const licensing = `
|
||||||
|
## Licensing
|
||||||
|
|
||||||
|
Copyright 2013 Lovell Fuller and others.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
[https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
`;
|
||||||
|
|
||||||
|
workspaces.map(async platform => {
|
||||||
|
const url = `https://github.com/lovell/sharp/releases/download/v${version}/sharp-v${version}-napi-v9-${platform}.tar.gz`;
|
||||||
|
const dir = path.join(__dirname, platform);
|
||||||
|
const response = await fetch(url);
|
||||||
|
if (!response.ok) {
|
||||||
|
console.log(`Skipping ${platform}: ${response.statusText}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Extract prebuild tarball
|
||||||
|
const lib = path.join(dir, 'lib');
|
||||||
|
await rm(lib, { recursive: true });
|
||||||
|
await pipeline(
|
||||||
|
Readable.fromWeb(response.body),
|
||||||
|
createGunzip(),
|
||||||
|
extract(lib, { map: mapTarballEntry })
|
||||||
|
);
|
||||||
|
// Generate README
|
||||||
|
const { name, description } = require(`./${platform}/package.json`);
|
||||||
|
await writeFile(path.join(dir, 'README.md'), `# \`${name}\`\n\n${description}.\n${licensing}`);
|
||||||
|
// Copy Apache-2.0 LICENSE
|
||||||
|
await copyFile(path.join(__dirname, '..', 'LICENSE'), path.join(dir, 'LICENSE'));
|
||||||
|
// Copy Windows-specific files
|
||||||
|
if (platform.startsWith('win32-')) {
|
||||||
|
const sharpLibvipsDir = path.join(require(`@sharpen/sharp-libvips-${platform}/lib`), '..');
|
||||||
|
await Promise.all(
|
||||||
|
['versions.json', 'THIRD-PARTY-NOTICES.md'].map(
|
||||||
|
filename => copyFile(path.join(sharpLibvipsDir, filename), path.join(dir, filename))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
26
npm/from-local-build.js
Normal file
26
npm/from-local-build.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Populate contents of a single npm/sharpen-sharp-<build-platform> package
|
||||||
|
// with the local/CI build directory for local/CI prebuild testing
|
||||||
|
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const path = require('node:path');
|
||||||
|
|
||||||
|
const { buildPlatformArch } = require('../lib/libvips');
|
||||||
|
const platform = buildPlatformArch();
|
||||||
|
const dest = path.join(__dirname, platform);
|
||||||
|
|
||||||
|
// Use same config as prebuild to copy binary files
|
||||||
|
const release = path.join(__dirname, '..', 'src', 'build', 'Release');
|
||||||
|
const prebuildrc = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '.prebuildrc'), 'utf8'));
|
||||||
|
const include = new RegExp(prebuildrc['include-regex'], 'i');
|
||||||
|
fs.cpSync(release, path.join(dest, 'lib'), {
|
||||||
|
recursive: true,
|
||||||
|
filter: (file) => {
|
||||||
|
const name = path.basename(file);
|
||||||
|
return name === 'Release' || include.test(name);
|
||||||
|
}
|
||||||
|
});
|
||||||
43
npm/linux-arm/package.json
Normal file
43
npm/linux-arm/package.json
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"name": "@sharpen/sharp-linux-arm",
|
||||||
|
"version": "0.0.1-alpha.5",
|
||||||
|
"description": "Prebuilt sharp for use with Linux (glibc) ARM (32-bit)",
|
||||||
|
"homepage": "https://sharp.pixelplumbing.com",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/lovell/sharp.git",
|
||||||
|
"directory": "npm/linux-arm"
|
||||||
|
},
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"preferUnplugged": true,
|
||||||
|
"files": [
|
||||||
|
"lib"
|
||||||
|
],
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"type": "commonjs",
|
||||||
|
"exports": {
|
||||||
|
"./sharp.node": "./lib/sharp-linux-arm.node",
|
||||||
|
"./package": "./package.json"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||||
|
"npm": ">=9.6.5",
|
||||||
|
"yarn": ">=3.2.0",
|
||||||
|
"pnpm": ">=7.1.0",
|
||||||
|
"glibc": ">=2.28"
|
||||||
|
},
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
]
|
||||||
|
}
|
||||||
43
npm/linux-arm64/package.json
Normal file
43
npm/linux-arm64/package.json
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"name": "@sharpen/sharp-linux-arm64",
|
||||||
|
"version": "0.0.1-alpha.5",
|
||||||
|
"description": "Prebuilt sharp for use with Linux (glibc) ARM64",
|
||||||
|
"homepage": "https://sharp.pixelplumbing.com",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/lovell/sharp.git",
|
||||||
|
"directory": "npm/linux-arm64"
|
||||||
|
},
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"preferUnplugged": true,
|
||||||
|
"files": [
|
||||||
|
"lib"
|
||||||
|
],
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"type": "commonjs",
|
||||||
|
"exports": {
|
||||||
|
"./sharp.node": "./lib/sharp-linux-arm64.node",
|
||||||
|
"./package": "./package.json"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||||
|
"npm": ">=9.6.5",
|
||||||
|
"yarn": ">=3.2.0",
|
||||||
|
"pnpm": ">=7.1.0",
|
||||||
|
"glibc": ">=2.26"
|
||||||
|
},
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
]
|
||||||
|
}
|
||||||
43
npm/linux-x64/package.json
Normal file
43
npm/linux-x64/package.json
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"name": "@sharpen/sharp-linux-x64",
|
||||||
|
"version": "0.0.1-alpha.5",
|
||||||
|
"description": "Prebuilt sharp for use with Linux (glibc) x64",
|
||||||
|
"homepage": "https://sharp.pixelplumbing.com",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/lovell/sharp.git",
|
||||||
|
"directory": "npm/linux-x64"
|
||||||
|
},
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"preferUnplugged": true,
|
||||||
|
"files": [
|
||||||
|
"lib"
|
||||||
|
],
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"type": "commonjs",
|
||||||
|
"exports": {
|
||||||
|
"./sharp.node": "./lib/sharp-linux-x64.node",
|
||||||
|
"./package": "./package.json"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||||
|
"npm": ">=9.6.5",
|
||||||
|
"yarn": ">=3.2.0",
|
||||||
|
"pnpm": ">=7.1.0",
|
||||||
|
"glibc": ">=2.26"
|
||||||
|
},
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
]
|
||||||
|
}
|
||||||
43
npm/linuxmusl-arm64/package.json
Normal file
43
npm/linuxmusl-arm64/package.json
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"name": "@sharpen/sharp-linuxmusl-arm64",
|
||||||
|
"version": "0.0.1-alpha.5",
|
||||||
|
"description": "Prebuilt sharp for use with Linux (musl) ARM64",
|
||||||
|
"homepage": "https://sharp.pixelplumbing.com",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/lovell/sharp.git",
|
||||||
|
"directory": "npm/linuxmusl-arm64"
|
||||||
|
},
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"preferUnplugged": true,
|
||||||
|
"files": [
|
||||||
|
"lib"
|
||||||
|
],
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"type": "commonjs",
|
||||||
|
"exports": {
|
||||||
|
"./sharp.node": "./lib/sharp-linuxmusl-arm64.node",
|
||||||
|
"./package": "./package.json"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||||
|
"npm": ">=9.6.5",
|
||||||
|
"yarn": ">=3.2.0",
|
||||||
|
"pnpm": ">=7.1.0",
|
||||||
|
"musl": ">=1.2.2"
|
||||||
|
},
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
]
|
||||||
|
}
|
||||||
43
npm/linuxmusl-x64/package.json
Normal file
43
npm/linuxmusl-x64/package.json
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"name": "@sharpen/sharp-linuxmusl-x64",
|
||||||
|
"version": "0.0.1-alpha.5",
|
||||||
|
"description": "Prebuilt sharp for use with Linux (musl) x64",
|
||||||
|
"homepage": "https://sharp.pixelplumbing.com",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/lovell/sharp.git",
|
||||||
|
"directory": "npm/linuxmusl-x64"
|
||||||
|
},
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"preferUnplugged": true,
|
||||||
|
"files": [
|
||||||
|
"lib"
|
||||||
|
],
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"type": "commonjs",
|
||||||
|
"exports": {
|
||||||
|
"./sharp.node": "./lib/sharp-linuxmusl-x64.node",
|
||||||
|
"./package": "./package.json"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||||
|
"npm": ">=9.6.5",
|
||||||
|
"yarn": ">=3.2.0",
|
||||||
|
"pnpm": ">=7.1.0",
|
||||||
|
"musl": ">=1.2.2"
|
||||||
|
},
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
]
|
||||||
|
}
|
||||||
16
npm/package.json
Normal file
16
npm/package.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "@sharpen/sharp",
|
||||||
|
"version": "0.0.1-alpha.1",
|
||||||
|
"private": "true",
|
||||||
|
"workspaces": [
|
||||||
|
"darwin-x64",
|
||||||
|
"darwin-arm64",
|
||||||
|
"linux-arm",
|
||||||
|
"linux-arm64",
|
||||||
|
"linuxmusl-arm64",
|
||||||
|
"linuxmusl-x64",
|
||||||
|
"linux-x64",
|
||||||
|
"win32-ia32",
|
||||||
|
"win32-x64"
|
||||||
|
]
|
||||||
|
}
|
||||||
42
npm/win32-ia32/package.json
Normal file
42
npm/win32-ia32/package.json
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"name": "@sharpen/sharp-win32-ia32",
|
||||||
|
"version": "0.0.1-alpha.5",
|
||||||
|
"description": "Prebuilt sharp for use with Windows x86 (32-bit)",
|
||||||
|
"homepage": "https://sharp.pixelplumbing.com",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/lovell/sharp.git",
|
||||||
|
"directory": "npm/win32-ia32"
|
||||||
|
},
|
||||||
|
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"preferUnplugged": true,
|
||||||
|
"files": [
|
||||||
|
"lib",
|
||||||
|
"versions.json",
|
||||||
|
"THIRD-PARTY-NOTICES.md"
|
||||||
|
],
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"type": "commonjs",
|
||||||
|
"exports": {
|
||||||
|
"./sharp.node": "./lib/sharp-win32-ia32.node",
|
||||||
|
"./package": "./package.json",
|
||||||
|
"./versions": "./versions.json"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||||
|
"npm": ">=9.6.5",
|
||||||
|
"yarn": ">=3.2.0",
|
||||||
|
"pnpm": ">=7.1.0"
|
||||||
|
},
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
]
|
||||||
|
}
|
||||||
41
npm/win32-x64/package.json
Normal file
41
npm/win32-x64/package.json
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"name": "@sharpen/sharp-win32-x64",
|
||||||
|
"version": "0.0.1-alpha.5",
|
||||||
|
"description": "Prebuilt sharp for use with Windows x64",
|
||||||
|
"homepage": "https://sharp.pixelplumbing.com",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/lovell/sharp.git",
|
||||||
|
"directory": "npm/win32-x64"
|
||||||
|
},
|
||||||
|
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"preferUnplugged": true,
|
||||||
|
"files": [
|
||||||
|
"lib",
|
||||||
|
"versions.json",
|
||||||
|
"THIRD-PARTY-NOTICES.md"
|
||||||
|
],
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"type": "commonjs",
|
||||||
|
"exports": {
|
||||||
|
"./sharp.node": "./lib/sharp-win32-x64.node",
|
||||||
|
"./package": "./package.json"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||||
|
"npm": ">=9.6.5",
|
||||||
|
"yarn": ">=3.2.0",
|
||||||
|
"pnpm": ">=7.1.0"
|
||||||
|
},
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
]
|
||||||
|
}
|
||||||
186
package.json
Executable file → Normal file
186
package.json
Executable file → Normal file
@@ -1,7 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "sharp",
|
"name": "sharp",
|
||||||
"version": "0.8.4",
|
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, GIF, AVIF and TIFF images",
|
||||||
|
"version": "0.33.0-alpha.5",
|
||||||
"author": "Lovell Fuller <npm@lovell.info>",
|
"author": "Lovell Fuller <npm@lovell.info>",
|
||||||
|
"homepage": "https://github.com/lovell/sharp",
|
||||||
"contributors": [
|
"contributors": [
|
||||||
"Pierre Inglebert <pierre.inglebert@gmail.com>",
|
"Pierre Inglebert <pierre.inglebert@gmail.com>",
|
||||||
"Jonathan Ong <jonathanrichardong@gmail.com>",
|
"Jonathan Ong <jonathanrichardong@gmail.com>",
|
||||||
@@ -11,42 +13,194 @@
|
|||||||
"Julian Walker <julian@fiftythree.com>",
|
"Julian Walker <julian@fiftythree.com>",
|
||||||
"Amit Pitaru <pitaru.amit@gmail.com>",
|
"Amit Pitaru <pitaru.amit@gmail.com>",
|
||||||
"Brandon Aaron <hello.brandon@aaron.sh>",
|
"Brandon Aaron <hello.brandon@aaron.sh>",
|
||||||
"Andreas Lind <andreas@one.com>"
|
"Andreas Lind <andreas@one.com>",
|
||||||
|
"Maurus Cuelenaere <mcuelenaere@gmail.com>",
|
||||||
|
"Linus Unnebäck <linus@folkdatorn.se>",
|
||||||
|
"Victor Mateevitsi <mvictoras@gmail.com>",
|
||||||
|
"Alaric Holloway <alaric.holloway@gmail.com>",
|
||||||
|
"Bernhard K. Weisshuhn <bkw@codingforce.com>",
|
||||||
|
"Chris Riley <criley@primedia.com>",
|
||||||
|
"David Carley <dacarley@gmail.com>",
|
||||||
|
"John Tobin <john@limelightmobileinc.com>",
|
||||||
|
"Kenton Gray <kentongray@gmail.com>",
|
||||||
|
"Felix Bünemann <Felix.Buenemann@gmail.com>",
|
||||||
|
"Samy Al Zahrani <samyalzahrany@gmail.com>",
|
||||||
|
"Chintan Thakkar <lemnisk8@gmail.com>",
|
||||||
|
"F. Orlando Galashan <frulo@gmx.de>",
|
||||||
|
"Kleis Auke Wolthuizen <info@kleisauke.nl>",
|
||||||
|
"Matt Hirsch <mhirsch@media.mit.edu>",
|
||||||
|
"Matthias Thoemmes <thoemmes@gmail.com>",
|
||||||
|
"Patrick Paskaris <patrick@paskaris.gr>",
|
||||||
|
"Jérémy Lal <kapouer@melix.org>",
|
||||||
|
"Rahul Nanwani <r.nanwani@gmail.com>",
|
||||||
|
"Alice Monday <alice0meta@gmail.com>",
|
||||||
|
"Kristo Jorgenson <kristo.jorgenson@gmail.com>",
|
||||||
|
"YvesBos <yves_bos@outlook.com>",
|
||||||
|
"Guy Maliar <guy@tailorbrands.com>",
|
||||||
|
"Nicolas Coden <nicolas@ncoden.fr>",
|
||||||
|
"Matt Parrish <matt.r.parrish@gmail.com>",
|
||||||
|
"Marcel Bretschneider <marcel.bretschneider@gmail.com>",
|
||||||
|
"Matthew McEachen <matthew+github@mceachen.org>",
|
||||||
|
"Jarda Kotěšovec <jarda.kotesovec@gmail.com>",
|
||||||
|
"Kenric D'Souza <kenric.dsouza@gmail.com>",
|
||||||
|
"Oleh Aleinyk <oleg.aleynik@gmail.com>",
|
||||||
|
"Marcel Bretschneider <marcel.bretschneider@gmail.com>",
|
||||||
|
"Andrea Bianco <andrea.bianco@unibas.ch>",
|
||||||
|
"Rik Heywood <rik@rik.org>",
|
||||||
|
"Thomas Parisot <hi@oncletom.io>",
|
||||||
|
"Nathan Graves <nathanrgraves+github@gmail.com>",
|
||||||
|
"Tom Lokhorst <tom@lokhorst.eu>",
|
||||||
|
"Espen Hovlandsdal <espen@hovlandsdal.com>",
|
||||||
|
"Sylvain Dumont <sylvain.dumont35@gmail.com>",
|
||||||
|
"Alun Davies <alun.owain.davies@googlemail.com>",
|
||||||
|
"Aidan Hoolachan <ajhoolachan21@gmail.com>",
|
||||||
|
"Axel Eirola <axel.eirola@iki.fi>",
|
||||||
|
"Freezy <freezy@xbmc.org>",
|
||||||
|
"Daiz <taneli.vatanen@gmail.com>",
|
||||||
|
"Julian Aubourg <j@ubourg.net>",
|
||||||
|
"Keith Belovay <keith@picthrive.com>",
|
||||||
|
"Michael B. Klein <mbklein@gmail.com>",
|
||||||
|
"Jordan Prudhomme <jordan@raboland.fr>",
|
||||||
|
"Ilya Ovdin <iovdin@gmail.com>",
|
||||||
|
"Andargor <andargor@yahoo.com>",
|
||||||
|
"Paul Neave <paul.neave@gmail.com>",
|
||||||
|
"Brendan Kennedy <brenwken@gmail.com>",
|
||||||
|
"Brychan Bennett-Odlum <git@brychan.io>",
|
||||||
|
"Edward Silverton <e.silverton@gmail.com>",
|
||||||
|
"Roman Malieiev <aromaleev@gmail.com>",
|
||||||
|
"Tomas Szabo <tomas.szabo@deftomat.com>",
|
||||||
|
"Robert O'Rourke <robert@o-rourke.org>",
|
||||||
|
"Guillermo Alfonso Varela Chouciño <guillevch@gmail.com>",
|
||||||
|
"Christian Flintrup <chr@gigahost.dk>",
|
||||||
|
"Manan Jadhav <manan@motionden.com>",
|
||||||
|
"Leon Radley <leon@radley.se>",
|
||||||
|
"alza54 <alza54@thiocod.in>",
|
||||||
|
"Jacob Smith <jacob@frende.me>",
|
||||||
|
"Michael Nutt <michael@nutt.im>",
|
||||||
|
"Brad Parham <baparham@gmail.com>",
|
||||||
|
"Taneli Vatanen <taneli.vatanen@gmail.com>",
|
||||||
|
"Joris Dugué <zaruike10@gmail.com>",
|
||||||
|
"Chris Banks <christopher.bradley.banks@gmail.com>",
|
||||||
|
"Ompal Singh <ompal.hitm09@gmail.com>",
|
||||||
|
"Brodan <christopher.hranj@gmail.com",
|
||||||
|
"Ankur Parihar <ankur.github@gmail.com>",
|
||||||
|
"Brahim Ait elhaj <brahima@gmail.com>",
|
||||||
|
"Mart Jansink <m.jansink@gmail.com>",
|
||||||
|
"Lachlan Newman <lachnewman007@gmail.com>"
|
||||||
],
|
],
|
||||||
"description": "High performance Node.js module to resize JPEG, PNG, WebP and TIFF images using the libvips library",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "node ./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha -- --slow=5000 --timeout=10000 ./test/unit/*.js"
|
"install": "node install/check",
|
||||||
|
"clean": "rm -rf src/build/ .nyc_output/ coverage/ test/fixtures/output.*",
|
||||||
|
"test": "npm run test-lint && npm run test-unit && npm run test-licensing && npm run test-types",
|
||||||
|
"test-lint": "semistandard && cpplint",
|
||||||
|
"test-unit": "nyc --reporter=lcov --reporter=text --check-coverage --branches=100 mocha",
|
||||||
|
"test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;LGPL-3.0-or-later;MIT\"",
|
||||||
|
"test-leak": "./test/leak/leak.sh",
|
||||||
|
"test-types": "tsd",
|
||||||
|
"package-from-local-build": "node npm/from-local-build",
|
||||||
|
"package-from-github-release": "node npm/from-github-release",
|
||||||
|
"docs-build": "node docs/build && node docs/search-index/build",
|
||||||
|
"docs-serve": "cd docs && npx serve",
|
||||||
|
"docs-publish": "cd docs && npx firebase-tools deploy --project pixelplumbing --only hosting:pixelplumbing-sharp"
|
||||||
},
|
},
|
||||||
"main": "index.js",
|
"type": "commonjs",
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"types": "lib/index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"install",
|
||||||
|
"lib",
|
||||||
|
"src/*.{cc,h,gyp}"
|
||||||
|
],
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git://github.com/lovell/sharp"
|
"url": "git://github.com/lovell/sharp.git"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"jpeg",
|
"jpeg",
|
||||||
"png",
|
"png",
|
||||||
"webp",
|
"webp",
|
||||||
|
"avif",
|
||||||
"tiff",
|
"tiff",
|
||||||
|
"gif",
|
||||||
|
"svg",
|
||||||
|
"jp2",
|
||||||
|
"dzi",
|
||||||
|
"image",
|
||||||
"resize",
|
"resize",
|
||||||
"thumbnail",
|
"thumbnail",
|
||||||
"crop",
|
"crop",
|
||||||
|
"embed",
|
||||||
"libvips",
|
"libvips",
|
||||||
"vips"
|
"vips"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bluebird": "^2.6.4",
|
"color": "^4.2.3",
|
||||||
"color": "^0.7.3",
|
"detect-libc": "^2.0.2",
|
||||||
"nan": "^1.5.1",
|
"semver": "^7.5.4"
|
||||||
"semver": "^4.1.0"
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@sharpen/sharp-darwin-arm64": "0.0.1-alpha.5",
|
||||||
|
"@sharpen/sharp-darwin-x64": "0.0.1-alpha.5",
|
||||||
|
"@sharpen/sharp-libvips-darwin-arm64": "0.0.1-alpha.2",
|
||||||
|
"@sharpen/sharp-libvips-darwin-x64": "0.0.1-alpha.2",
|
||||||
|
"@sharpen/sharp-libvips-linux-arm": "0.0.1-alpha.2",
|
||||||
|
"@sharpen/sharp-libvips-linux-arm64": "0.0.1-alpha.2",
|
||||||
|
"@sharpen/sharp-libvips-linux-x64": "0.0.1-alpha.2",
|
||||||
|
"@sharpen/sharp-libvips-linuxmusl-arm64": "0.0.1-alpha.2",
|
||||||
|
"@sharpen/sharp-libvips-linuxmusl-x64": "0.0.1-alpha.2",
|
||||||
|
"@sharpen/sharp-linux-arm": "0.0.1-alpha.5",
|
||||||
|
"@sharpen/sharp-linux-arm64": "0.0.1-alpha.5",
|
||||||
|
"@sharpen/sharp-linux-x64": "0.0.1-alpha.5",
|
||||||
|
"@sharpen/sharp-linuxmusl-arm64": "0.0.1-alpha.5",
|
||||||
|
"@sharpen/sharp-linuxmusl-x64": "0.0.1-alpha.5",
|
||||||
|
"@sharpen/sharp-win32-ia32": "0.0.1-alpha.5",
|
||||||
|
"@sharpen/sharp-win32-x64": "0.0.1-alpha.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"mocha": "^2.0.1",
|
"@sharpen/sharp-libvips-dev": "0.0.1-alpha.2",
|
||||||
"mocha-jshint": "^0.0.9",
|
"@sharpen/sharp-libvips-win32-ia32": "0.0.1-alpha.2",
|
||||||
"istanbul": "^0.3.5",
|
"@sharpen/sharp-libvips-win32-x64": "0.0.1-alpha.2",
|
||||||
"coveralls": "^2.11.2"
|
"@types/node": "*",
|
||||||
|
"async": "^3.2.4",
|
||||||
|
"cc": "^3.0.1",
|
||||||
|
"exif-reader": "^2.0.0",
|
||||||
|
"extract-zip": "^2.0.1",
|
||||||
|
"icc": "^3.0.0",
|
||||||
|
"jsdoc-to-markdown": "^8.0.0",
|
||||||
|
"license-checker": "^25.0.1",
|
||||||
|
"mocha": "^10.2.0",
|
||||||
|
"node-addon-api": "^7.0.0",
|
||||||
|
"nyc": "^15.1.0",
|
||||||
|
"prebuild": "^12.1.0",
|
||||||
|
"semistandard": "^17.0.0",
|
||||||
|
"tar-fs": "^3.0.4",
|
||||||
|
"tsd": "^0.29.0"
|
||||||
},
|
},
|
||||||
"license": "Apache 2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10"
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||||
|
"libvips": ">=8.14.5"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"binary": {
|
||||||
|
"napi_versions": [
|
||||||
|
9
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"semistandard": {
|
||||||
|
"env": [
|
||||||
|
"mocha"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"cc": {
|
||||||
|
"linelength": "120",
|
||||||
|
"filter": [
|
||||||
|
"build/include"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"tsd": {
|
||||||
|
"directory": "test/types/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
165
preinstall.sh
165
preinstall.sh
@@ -1,165 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Ensures libvips is installed and attempts to install it if not
|
|
||||||
# Currently supports:
|
|
||||||
# * Mac OS
|
|
||||||
# * Debian Linux
|
|
||||||
# * Debian 7, 8
|
|
||||||
# * Ubuntu 12.04, 14.04, 14.10, 15.04
|
|
||||||
# * Mint 13, 17
|
|
||||||
# * Red Hat Linux
|
|
||||||
# * RHEL/Centos/Scientific 6, 7
|
|
||||||
# * Fedora 21, 22
|
|
||||||
# * Amazon Linux 2014.09
|
|
||||||
|
|
||||||
vips_version_minimum=7.40.0
|
|
||||||
vips_version_latest_major=7.42
|
|
||||||
vips_version_latest_minor=0
|
|
||||||
|
|
||||||
install_libvips_from_source() {
|
|
||||||
echo "Compiling libvips $vips_version_latest_major.$vips_version_latest_minor from source"
|
|
||||||
curl -O http://www.vips.ecs.soton.ac.uk/supported/$vips_version_latest_major/vips-$vips_version_latest_major.$vips_version_latest_minor.tar.gz
|
|
||||||
tar zvxf vips-$vips_version_latest_major.$vips_version_latest_minor.tar.gz
|
|
||||||
cd vips-$vips_version_latest_major.$vips_version_latest_minor
|
|
||||||
./configure --enable-debug=no --enable-docs=no --enable-cxx=yes --without-python --without-orc --without-fftw --without-gsf $1
|
|
||||||
make
|
|
||||||
make install
|
|
||||||
cd ..
|
|
||||||
rm -rf vips-$vips_version_latest_major.$vips_version_latest_minor
|
|
||||||
rm vips-$vips_version_latest_major.$vips_version_latest_minor.tar.gz
|
|
||||||
ldconfig
|
|
||||||
echo "Installed libvips $vips_version_latest_major.$vips_version_latest_minor"
|
|
||||||
}
|
|
||||||
|
|
||||||
sorry() {
|
|
||||||
echo "Sorry, I don't yet know how to install libvips on $1"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Is libvips already installed, and is it at least the minimum required version?
|
|
||||||
|
|
||||||
if ! type pkg-config >/dev/null; then
|
|
||||||
sorry "a system without pkg-config"
|
|
||||||
fi
|
|
||||||
|
|
||||||
pkg_config_path_homebrew=`which brew >/dev/null 2>&1 && eval $(brew --env) && echo $PKG_CONFIG_LIBDIR || true`
|
|
||||||
pkg_config_path="$pkg_config_path_homebrew:$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig"
|
|
||||||
|
|
||||||
PKG_CONFIG_PATH=$pkg_config_path pkg-config --exists vips
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
vips_version_found=$(PKG_CONFIG_PATH=$pkg_config_path pkg-config --modversion vips)
|
|
||||||
pkg-config --atleast-version=$vips_version_minimum vips
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
# Found suitable version of libvips
|
|
||||||
echo "Found libvips $vips_version_found"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
echo "Found libvips $vips_version_found but require $vips_version_minimum"
|
|
||||||
else
|
|
||||||
echo "Could not find libvips using a PKG_CONFIG_PATH of '$pkg_config_path'"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Verify root/sudo access
|
|
||||||
if [ "$(id -u)" -ne "0" ]; then
|
|
||||||
echo "Sorry, I need root/sudo access to continue"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# OS-specific installations of libvips follows
|
|
||||||
|
|
||||||
case $(uname -s) in
|
|
||||||
*[Dd]arwin*)
|
|
||||||
# Mac OS
|
|
||||||
echo "Detected Mac OS"
|
|
||||||
if type "brew" > /dev/null; then
|
|
||||||
echo "Installing libvips via homebrew"
|
|
||||||
brew install homebrew/science/vips --with-webp --with-graphicsmagick
|
|
||||||
elif type "port" > /dev/null; then
|
|
||||||
echo "Installing libvips via MacPorts"
|
|
||||||
port install vips
|
|
||||||
else
|
|
||||||
sorry "Mac OS without homebrew or MacPorts"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
if [ -f /etc/debian_version ]; then
|
|
||||||
# Debian Linux
|
|
||||||
DISTRO=$(lsb_release -c -s)
|
|
||||||
echo "Detected Debian Linux '$DISTRO'"
|
|
||||||
case "$DISTRO" in
|
|
||||||
jessie|vivid)
|
|
||||||
# Debian 8, Ubuntu 15
|
|
||||||
echo "Installing libvips via apt-get"
|
|
||||||
apt-get install -y libvips-dev
|
|
||||||
;;
|
|
||||||
trusty|utopic|qiana|rebecca)
|
|
||||||
# Ubuntu 14, Mint 17
|
|
||||||
echo "Installing libvips dependencies via apt-get"
|
|
||||||
apt-get install -y automake build-essential gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg-turbo8-dev libpng12-dev libwebp-dev libtiff5-dev libexif-dev liblcms2-dev libxml2-dev swig libmagickwand-dev curl
|
|
||||||
install_libvips_from_source
|
|
||||||
;;
|
|
||||||
precise|wheezy|maya)
|
|
||||||
# Debian 7, Ubuntu 12.04, Mint 13
|
|
||||||
echo "Installing libvips dependencies via apt-get"
|
|
||||||
add-apt-repository -y ppa:lyrasis/precise-backports
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y automake build-essential gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg-turbo8-dev libpng12-dev libwebp-dev libtiff4-dev libexif-dev liblcms2-dev libxml2-dev swig libmagickwand-dev curl
|
|
||||||
install_libvips_from_source
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
# Unsupported Debian-based OS
|
|
||||||
sorry "Debian-based $DISTRO"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
elif [ -f /etc/redhat-release ]; then
|
|
||||||
# Red Hat Linux
|
|
||||||
RELEASE=$(cat /etc/redhat-release)
|
|
||||||
echo "Detected Red Hat Linux '$RELEASE'"
|
|
||||||
case $RELEASE in
|
|
||||||
"Red Hat Enterprise Linux release 7."*|"CentOS Linux release 7."*|"Scientific Linux release 7."*)
|
|
||||||
# RHEL/CentOS 7
|
|
||||||
echo "Installing libvips dependencies via yum"
|
|
||||||
yum groupinstall -y "Development Tools"
|
|
||||||
yum install -y gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel lcms-devel ImageMagick-devel gobject-introspection-devel libwebp-devel curl
|
|
||||||
install_libvips_from_source "--prefix=/usr"
|
|
||||||
;;
|
|
||||||
"Red Hat Enterprise Linux release 6."*|"CentOS release 6."*|"Scientific Linux release 6."*)
|
|
||||||
# RHEL/CentOS 6
|
|
||||||
echo "Installing libvips dependencies via yum"
|
|
||||||
yum groupinstall -y "Development Tools"
|
|
||||||
yum install -y gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel lcms-devel ImageMagick-devel curl
|
|
||||||
yum install -y http://li.nux.ro/download/nux/dextop/el6/x86_64/nux-dextop-release-0-2.el6.nux.noarch.rpm
|
|
||||||
yum install -y --enablerepo=nux-dextop gobject-introspection-devel
|
|
||||||
yum install -y http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
|
|
||||||
yum install -y --enablerepo=remi libwebp-devel
|
|
||||||
install_libvips_from_source "--prefix=/usr"
|
|
||||||
;;
|
|
||||||
"Fedora release 21 "*|"Fedora release 22 "*)
|
|
||||||
# Fedora 21, 22
|
|
||||||
echo "Installing libvips via yum"
|
|
||||||
yum install vips-devel
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
# Unsupported RHEL-based OS
|
|
||||||
sorry "$RELEASE"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
elif [ -f /etc/system-release ]; then
|
|
||||||
# Probably Amazon Linux
|
|
||||||
RELEASE=$(cat /etc/system-release)
|
|
||||||
case $RELEASE in
|
|
||||||
"Amazon Linux AMI release 2014.09")
|
|
||||||
# Amazon Linux
|
|
||||||
echo "Detected '$RELEASE'"
|
|
||||||
echo "Installing libvips dependencies via yum"
|
|
||||||
yum groupinstall -y "Development Tools"
|
|
||||||
yum install -y gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel lcms-devel ImageMagick-devel gobject-introspection-devel libwebp-devel curl
|
|
||||||
install_libvips_from_source "--prefix=/usr"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
else
|
|
||||||
# Unsupported OS
|
|
||||||
sorry "$(uname -a)"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
264
src/binding.gyp
Normal file
264
src/binding.gyp
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
# Copyright 2013 Lovell Fuller and others.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
{
|
||||||
|
'variables': {
|
||||||
|
'vips_version': '<!(node -p "require(\'../lib/libvips\').minimumLibvipsVersion")',
|
||||||
|
'platform_and_arch': '<!(node -p "require(\'../lib/libvips\').buildPlatformArch()")',
|
||||||
|
'sharp_libvips_include_dir': '<!(node -p "require(\'../lib/libvips\').buildSharpLibvipsIncludeDir()")',
|
||||||
|
'sharp_libvips_cplusplus_dir': '<!(node -p "require(\'../lib/libvips\').buildSharpLibvipsCPlusPlusDir()")',
|
||||||
|
'sharp_libvips_lib_dir': '<!(node -p "require(\'../lib/libvips\').buildSharpLibvipsLibDir()")'
|
||||||
|
},
|
||||||
|
'targets': [{
|
||||||
|
'target_name': 'libvips-cpp',
|
||||||
|
'conditions': [
|
||||||
|
['OS == "win"', {
|
||||||
|
# Build libvips C++ binding for Windows due to MSVC std library ABI changes
|
||||||
|
'type': 'shared_library',
|
||||||
|
'defines': [
|
||||||
|
'VIPS_CPLUSPLUS_EXPORTS',
|
||||||
|
'_ALLOW_KEYWORD_MACROS'
|
||||||
|
],
|
||||||
|
'sources': [
|
||||||
|
'<(sharp_libvips_cplusplus_dir)/VConnection.cpp',
|
||||||
|
'<(sharp_libvips_cplusplus_dir)/VError.cpp',
|
||||||
|
'<(sharp_libvips_cplusplus_dir)/VImage.cpp',
|
||||||
|
'<(sharp_libvips_cplusplus_dir)/VInterpolate.cpp',
|
||||||
|
'<(sharp_libvips_cplusplus_dir)/VRegion.cpp'
|
||||||
|
],
|
||||||
|
'include_dirs': [
|
||||||
|
'<(sharp_libvips_include_dir)',
|
||||||
|
'<(sharp_libvips_include_dir)/glib-2.0',
|
||||||
|
'<(sharp_libvips_lib_dir)/glib-2.0/include'
|
||||||
|
],
|
||||||
|
'link_settings': {
|
||||||
|
'library_dirs': ['<(sharp_libvips_lib_dir)'],
|
||||||
|
'libraries': [
|
||||||
|
'libvips.lib',
|
||||||
|
'libglib-2.0.lib',
|
||||||
|
'libgobject-2.0.lib'
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'configurations': {
|
||||||
|
'Release': {
|
||||||
|
'msvs_settings': {
|
||||||
|
'VCCLCompilerTool': {
|
||||||
|
'ExceptionHandling': 1,
|
||||||
|
'Optimization': 1,
|
||||||
|
'WholeProgramOptimization': 'true'
|
||||||
|
},
|
||||||
|
'VCLibrarianTool': {
|
||||||
|
'AdditionalOptions': [
|
||||||
|
'/LTCG:INCREMENTAL'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'VCLinkerTool': {
|
||||||
|
'ImageHasSafeExceptionHandlers': 'false',
|
||||||
|
'OptimizeReferences': 2,
|
||||||
|
'EnableCOMDATFolding': 2,
|
||||||
|
'LinkIncremental': 1,
|
||||||
|
'AdditionalOptions': [
|
||||||
|
'/LTCG:INCREMENTAL'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'msvs_disabled_warnings': [
|
||||||
|
4275
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
# Ignore this target for non-Windows
|
||||||
|
'type': 'none'
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
'target_name': 'sharp-<(platform_and_arch)',
|
||||||
|
'defines': [
|
||||||
|
'NAPI_VERSION=9',
|
||||||
|
'NODE_ADDON_API_DISABLE_DEPRECATED',
|
||||||
|
'NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS'
|
||||||
|
],
|
||||||
|
'dependencies': [
|
||||||
|
'<!(node -p "require(\'node-addon-api\').gyp")',
|
||||||
|
'libvips-cpp'
|
||||||
|
],
|
||||||
|
'variables': {
|
||||||
|
'conditions': [
|
||||||
|
['OS != "win"', {
|
||||||
|
'pkg_config_path': '<!(node -p "require(\'../lib/libvips\').pkgConfigPath()")',
|
||||||
|
'use_global_libvips': '<!(node -p "Boolean(require(\'../lib/libvips\').useGlobalLibvips()).toString()")'
|
||||||
|
}, {
|
||||||
|
'pkg_config_path': '',
|
||||||
|
'use_global_libvips': ''
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'sources': [
|
||||||
|
'common.cc',
|
||||||
|
'metadata.cc',
|
||||||
|
'stats.cc',
|
||||||
|
'operations.cc',
|
||||||
|
'pipeline.cc',
|
||||||
|
'utilities.cc',
|
||||||
|
'sharp.cc'
|
||||||
|
],
|
||||||
|
'include_dirs': [
|
||||||
|
'<!(node -p "require(\'node-addon-api\').include_dir")',
|
||||||
|
],
|
||||||
|
'conditions': [
|
||||||
|
['use_global_libvips == "true"', {
|
||||||
|
# Use pkg-config for include and lib
|
||||||
|
'include_dirs': ['<!@(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --cflags-only-I vips-cpp vips glib-2.0 | sed s\/-I//g)'],
|
||||||
|
'libraries': ['<!@(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --libs vips-cpp)'],
|
||||||
|
'conditions': [
|
||||||
|
['OS == "linux"', {
|
||||||
|
'defines': [
|
||||||
|
# Inspect libvips-cpp.so to determine which C++11 ABI version was used and set _GLIBCXX_USE_CXX11_ABI accordingly. This is quite horrible.
|
||||||
|
'_GLIBCXX_USE_CXX11_ABI=<!(if readelf -Ws "$(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --variable libdir vips-cpp)/libvips-cpp.so" | c++filt | grep -qF __cxx11;then echo "1";else echo "0";fi)'
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
# Use pre-built libvips stored locally within node_modules
|
||||||
|
'include_dirs': [
|
||||||
|
'<(sharp_libvips_include_dir)',
|
||||||
|
'<(sharp_libvips_include_dir)/glib-2.0',
|
||||||
|
'<(sharp_libvips_lib_dir)/glib-2.0/include'
|
||||||
|
],
|
||||||
|
'conditions': [
|
||||||
|
['OS == "win"', {
|
||||||
|
'defines': [
|
||||||
|
'_ALLOW_KEYWORD_MACROS',
|
||||||
|
'_FILE_OFFSET_BITS=64'
|
||||||
|
],
|
||||||
|
'link_settings': {
|
||||||
|
'library_dirs': ['<(sharp_libvips_lib_dir)'],
|
||||||
|
'libraries': [
|
||||||
|
'libvips.lib',
|
||||||
|
'libglib-2.0.lib',
|
||||||
|
'libgobject-2.0.lib'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
['OS == "mac"', {
|
||||||
|
'link_settings': {
|
||||||
|
'library_dirs': [
|
||||||
|
'<(sharp_libvips_lib_dir)'
|
||||||
|
],
|
||||||
|
'libraries': [
|
||||||
|
'libvips-cpp.42.dylib'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'xcode_settings': {
|
||||||
|
'OTHER_LDFLAGS': [
|
||||||
|
# Ensure runtime linking is relative to sharp.node
|
||||||
|
'-Wl,-rpath,\'@loader_path/../../sharp-libvips-<(platform_and_arch)/lib\'',
|
||||||
|
'-Wl,-rpath,\'@loader_path/../../node_modules/@sharpen/sharp-libvips-<(platform_and_arch)/lib\'',
|
||||||
|
'-Wl,-rpath,\'@loader_path/../../../node_modules/@sharpen/sharp-libvips-<(platform_and_arch)/lib\''
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
['OS == "linux"', {
|
||||||
|
'defines': [
|
||||||
|
'_GLIBCXX_USE_CXX11_ABI=1'
|
||||||
|
],
|
||||||
|
'link_settings': {
|
||||||
|
'library_dirs': [
|
||||||
|
'<(sharp_libvips_lib_dir)'
|
||||||
|
],
|
||||||
|
'libraries': [
|
||||||
|
'-l:libvips-cpp.so.42'
|
||||||
|
],
|
||||||
|
'ldflags': [
|
||||||
|
# Ensure runtime linking is relative to sharp.node
|
||||||
|
'-Wl,-s',
|
||||||
|
'-Wl,--disable-new-dtags',
|
||||||
|
'-Wl,-rpath=\'$$ORIGIN/../../sharp-libvips-<(platform_and_arch)/lib\'',
|
||||||
|
'-Wl,-rpath=\'$$ORIGIN/../../node_modules/@sharpen/sharp-libvips-<(platform_and_arch)/lib\'',
|
||||||
|
'-Wl,-rpath=\'$$ORIGIN/../../../node_modules/@sharpen/sharp-libvips-<(platform_and_arch)/lib\''
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
],
|
||||||
|
'cflags_cc': [
|
||||||
|
'-std=c++0x',
|
||||||
|
'-fexceptions',
|
||||||
|
'-Wall',
|
||||||
|
'-Os'
|
||||||
|
],
|
||||||
|
'xcode_settings': {
|
||||||
|
'CLANG_CXX_LANGUAGE_STANDARD': 'c++11',
|
||||||
|
'MACOSX_DEPLOYMENT_TARGET': '10.13',
|
||||||
|
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
|
||||||
|
'GCC_ENABLE_CPP_RTTI': 'YES',
|
||||||
|
'OTHER_CPLUSPLUSFLAGS': [
|
||||||
|
'-fexceptions',
|
||||||
|
'-Wall',
|
||||||
|
'-Oz'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'configurations': {
|
||||||
|
'Release': {
|
||||||
|
'conditions': [
|
||||||
|
['OS == "linux"', {
|
||||||
|
'cflags_cc': [
|
||||||
|
'-Wno-cast-function-type'
|
||||||
|
]
|
||||||
|
}],
|
||||||
|
['target_arch == "arm"', {
|
||||||
|
'cflags_cc': [
|
||||||
|
'-Wno-psabi'
|
||||||
|
]
|
||||||
|
}],
|
||||||
|
['OS == "win"', {
|
||||||
|
'msvs_settings': {
|
||||||
|
'VCCLCompilerTool': {
|
||||||
|
'ExceptionHandling': 1,
|
||||||
|
'Optimization': 1,
|
||||||
|
'WholeProgramOptimization': 'true'
|
||||||
|
},
|
||||||
|
'VCLibrarianTool': {
|
||||||
|
'AdditionalOptions': [
|
||||||
|
'/LTCG:INCREMENTAL'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'VCLinkerTool': {
|
||||||
|
'ImageHasSafeExceptionHandlers': 'false',
|
||||||
|
'OptimizeReferences': 2,
|
||||||
|
'EnableCOMDATFolding': 2,
|
||||||
|
'LinkIncremental': 1,
|
||||||
|
'AdditionalOptions': [
|
||||||
|
'/LTCG:INCREMENTAL'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'msvs_disabled_warnings': [
|
||||||
|
4275
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'target_name': 'copy-dll',
|
||||||
|
'type': 'none',
|
||||||
|
'dependencies': [
|
||||||
|
'sharp-<(platform_and_arch)'
|
||||||
|
],
|
||||||
|
'conditions': [
|
||||||
|
['OS == "win"', {
|
||||||
|
'copies': [{
|
||||||
|
'destination': 'build/Release',
|
||||||
|
'files': [
|
||||||
|
'<(sharp_libvips_lib_dir)/libvips-42.dll',
|
||||||
|
'<(sharp_libvips_lib_dir)/libglib-2.0-0.dll',
|
||||||
|
'<(sharp_libvips_lib_dir)/libgobject-2.0-0.dll'
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}
|
||||||
1058
src/common.cc
Executable file → Normal file
1058
src/common.cc
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
343
src/common.h
Executable file → Normal file
343
src/common.h
Executable file → Normal file
@@ -1,28 +1,196 @@
|
|||||||
#ifndef SHARP_COMMON_H
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
#define SHARP_COMMON_H
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
#ifndef SRC_COMMON_H_
|
||||||
|
#define SRC_COMMON_H_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <tuple>
|
||||||
|
#include <vector>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
#include <napi.h>
|
||||||
|
#include <vips/vips8>
|
||||||
|
|
||||||
|
// Verify platform and compiler compatibility
|
||||||
|
|
||||||
|
#if (VIPS_MAJOR_VERSION < 8) || \
|
||||||
|
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 14) || \
|
||||||
|
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 14 && VIPS_MICRO_VERSION < 5)
|
||||||
|
#error "libvips version 8.14.5+ is required - please see https://sharp.pixelplumbing.com/install"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
|
||||||
|
#error "GCC version 4.6+ is required for C++11 features - please see https://sharp.pixelplumbing.com/install"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if (defined(__clang__) && defined(__has_feature))
|
||||||
|
#if (!__has_feature(cxx_range_for))
|
||||||
|
#error "clang version 3.0+ is required for C++11 features - please see https://sharp.pixelplumbing.com/install"
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using vips::VImage;
|
||||||
|
|
||||||
namespace sharp {
|
namespace sharp {
|
||||||
|
|
||||||
|
struct InputDescriptor { // NOLINT(runtime/indentation_namespace)
|
||||||
|
std::string name;
|
||||||
|
std::string file;
|
||||||
|
char *buffer;
|
||||||
|
VipsFailOn failOn;
|
||||||
|
uint64_t limitInputPixels;
|
||||||
|
bool unlimited;
|
||||||
|
VipsAccess access;
|
||||||
|
size_t bufferLength;
|
||||||
|
bool isBuffer;
|
||||||
|
double density;
|
||||||
|
bool ignoreIcc;
|
||||||
|
VipsBandFormat rawDepth;
|
||||||
|
int rawChannels;
|
||||||
|
int rawWidth;
|
||||||
|
int rawHeight;
|
||||||
|
bool rawPremultiplied;
|
||||||
|
int pages;
|
||||||
|
int page;
|
||||||
|
int level;
|
||||||
|
int subifd;
|
||||||
|
int createChannels;
|
||||||
|
int createWidth;
|
||||||
|
int createHeight;
|
||||||
|
std::vector<double> createBackground;
|
||||||
|
std::string createNoiseType;
|
||||||
|
double createNoiseMean;
|
||||||
|
double createNoiseSigma;
|
||||||
|
std::string textValue;
|
||||||
|
std::string textFont;
|
||||||
|
std::string textFontfile;
|
||||||
|
int textWidth;
|
||||||
|
int textHeight;
|
||||||
|
VipsAlign textAlign;
|
||||||
|
bool textJustify;
|
||||||
|
int textDpi;
|
||||||
|
bool textRgba;
|
||||||
|
int textSpacing;
|
||||||
|
VipsTextWrap textWrap;
|
||||||
|
int textAutofitDpi;
|
||||||
|
|
||||||
|
InputDescriptor():
|
||||||
|
buffer(nullptr),
|
||||||
|
failOn(VIPS_FAIL_ON_WARNING),
|
||||||
|
limitInputPixels(0x3FFF * 0x3FFF),
|
||||||
|
unlimited(FALSE),
|
||||||
|
access(VIPS_ACCESS_RANDOM),
|
||||||
|
bufferLength(0),
|
||||||
|
isBuffer(FALSE),
|
||||||
|
density(72.0),
|
||||||
|
ignoreIcc(FALSE),
|
||||||
|
rawDepth(VIPS_FORMAT_UCHAR),
|
||||||
|
rawChannels(0),
|
||||||
|
rawWidth(0),
|
||||||
|
rawHeight(0),
|
||||||
|
rawPremultiplied(false),
|
||||||
|
pages(1),
|
||||||
|
page(0),
|
||||||
|
level(0),
|
||||||
|
subifd(-1),
|
||||||
|
createChannels(0),
|
||||||
|
createWidth(0),
|
||||||
|
createHeight(0),
|
||||||
|
createBackground{ 0.0, 0.0, 0.0, 255.0 },
|
||||||
|
createNoiseMean(0.0),
|
||||||
|
createNoiseSigma(0.0),
|
||||||
|
textWidth(0),
|
||||||
|
textHeight(0),
|
||||||
|
textAlign(VIPS_ALIGN_LOW),
|
||||||
|
textJustify(FALSE),
|
||||||
|
textDpi(72),
|
||||||
|
textRgba(FALSE),
|
||||||
|
textSpacing(0),
|
||||||
|
textWrap(VIPS_TEXT_WRAP_WORD),
|
||||||
|
textAutofitDpi(0) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convenience methods to access the attributes of a Napi::Object
|
||||||
|
bool HasAttr(Napi::Object obj, std::string attr);
|
||||||
|
std::string AttrAsStr(Napi::Object obj, std::string attr);
|
||||||
|
std::string AttrAsStr(Napi::Object obj, unsigned int const attr);
|
||||||
|
uint32_t AttrAsUint32(Napi::Object obj, std::string attr);
|
||||||
|
int32_t AttrAsInt32(Napi::Object obj, std::string attr);
|
||||||
|
int32_t AttrAsInt32(Napi::Object obj, unsigned int const attr);
|
||||||
|
double AttrAsDouble(Napi::Object obj, std::string attr);
|
||||||
|
double AttrAsDouble(Napi::Object obj, unsigned int const attr);
|
||||||
|
bool AttrAsBool(Napi::Object obj, std::string attr);
|
||||||
|
std::vector<double> AttrAsVectorOfDouble(Napi::Object obj, std::string attr);
|
||||||
|
std::vector<int32_t> AttrAsInt32Vector(Napi::Object obj, std::string attr);
|
||||||
|
template <class T> T AttrAsEnum(Napi::Object obj, std::string attr, GType type) {
|
||||||
|
return static_cast<T>(
|
||||||
|
vips_enum_from_nick(nullptr, type, AttrAsStr(obj, attr).data()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an InputDescriptor instance from a Napi::Object describing an input image
|
||||||
|
InputDescriptor* CreateInputDescriptor(Napi::Object input);
|
||||||
|
|
||||||
enum class ImageType {
|
enum class ImageType {
|
||||||
UNKNOWN,
|
|
||||||
JPEG,
|
JPEG,
|
||||||
PNG,
|
PNG,
|
||||||
WEBP,
|
WEBP,
|
||||||
|
JP2,
|
||||||
TIFF,
|
TIFF,
|
||||||
MAGICK
|
GIF,
|
||||||
|
SVG,
|
||||||
|
HEIF,
|
||||||
|
PDF,
|
||||||
|
MAGICK,
|
||||||
|
OPENSLIDE,
|
||||||
|
PPM,
|
||||||
|
FITS,
|
||||||
|
EXR,
|
||||||
|
JXL,
|
||||||
|
VIPS,
|
||||||
|
RAW,
|
||||||
|
UNKNOWN,
|
||||||
|
MISSING
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Canvas {
|
||||||
|
CROP,
|
||||||
|
EMBED,
|
||||||
|
MAX,
|
||||||
|
MIN,
|
||||||
|
IGNORE_ASPECT
|
||||||
};
|
};
|
||||||
|
|
||||||
// How many tasks are in the queue?
|
// How many tasks are in the queue?
|
||||||
extern volatile int counterQueue;
|
extern std::atomic<int> counterQueue;
|
||||||
|
|
||||||
// How many tasks are being processed?
|
// How many tasks are being processed?
|
||||||
extern volatile int counterProcess;
|
extern std::atomic<int> counterProcess;
|
||||||
|
|
||||||
// Filename extension checkers
|
// Filename extension checkers
|
||||||
bool IsJpeg(std::string const &str);
|
bool IsJpeg(std::string const &str);
|
||||||
bool IsPng(std::string const &str);
|
bool IsPng(std::string const &str);
|
||||||
bool IsWebp(std::string const &str);
|
bool IsWebp(std::string const &str);
|
||||||
|
bool IsJp2(std::string const &str);
|
||||||
|
bool IsGif(std::string const &str);
|
||||||
bool IsTiff(std::string const &str);
|
bool IsTiff(std::string const &str);
|
||||||
|
bool IsHeic(std::string const &str);
|
||||||
|
bool IsHeif(std::string const &str);
|
||||||
|
bool IsAvif(std::string const &str);
|
||||||
|
bool IsJxl(std::string const &str);
|
||||||
|
bool IsDz(std::string const &str);
|
||||||
|
bool IsDzZip(std::string const &str);
|
||||||
|
bool IsV(std::string const &str);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Trim space from end of string.
|
||||||
|
*/
|
||||||
|
std::string TrimEnd(std::string const &str);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Provide a string identifier for the given image type.
|
||||||
|
*/
|
||||||
|
std::string ImageTypeId(ImageType const imageType);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Determine image format of a buffer.
|
Determine image format of a buffer.
|
||||||
@@ -35,37 +203,176 @@ namespace sharp {
|
|||||||
ImageType DetermineImageType(char const *file);
|
ImageType DetermineImageType(char const *file);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Initialise and return a VipsImage from a buffer. Supports JPEG, PNG, WebP and TIFF.
|
Does this image type support multiple pages?
|
||||||
*/
|
*/
|
||||||
VipsImage* InitImage(ImageType imageType, void *buffer, size_t const length, VipsAccess const access);
|
bool ImageTypeSupportsPage(ImageType imageType);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Initialise and return a VipsImage from a file.
|
Does this image type support removal of safety limits?
|
||||||
*/
|
*/
|
||||||
VipsImage* InitImage(ImageType imageType, char const *file, VipsAccess const access);
|
bool ImageTypeSupportsUnlimited(ImageType imageType);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data)
|
||||||
|
*/
|
||||||
|
std::tuple<VImage, ImageType> OpenInput(InputDescriptor *descriptor);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Does this image have an embedded profile?
|
Does this image have an embedded profile?
|
||||||
*/
|
*/
|
||||||
bool HasProfile(VipsImage *image);
|
bool HasProfile(VImage image);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Does this image have an alpha channel?
|
Does this image have an alpha channel?
|
||||||
Uses colour space interpretation with number of channels to guess this.
|
Uses colour space interpretation with number of channels to guess this.
|
||||||
*/
|
*/
|
||||||
bool HasAlpha(VipsImage *image);
|
bool HasAlpha(VImage image);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Get EXIF Orientation of image, if any.
|
Get EXIF Orientation of image, if any.
|
||||||
*/
|
*/
|
||||||
int ExifOrientation(VipsImage const *image);
|
int ExifOrientation(VImage image);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Returns the window size for the named interpolator. For example,
|
Set EXIF Orientation of image.
|
||||||
a window size of 3 means a 3x3 pixel grid is used for the calculation.
|
|
||||||
*/
|
*/
|
||||||
int InterpolatorWindowSize(char const *name);
|
VImage SetExifOrientation(VImage image, int const orientation);
|
||||||
|
|
||||||
} // namespace
|
/*
|
||||||
|
Remove EXIF Orientation from image.
|
||||||
|
*/
|
||||||
|
VImage RemoveExifOrientation(VImage image);
|
||||||
|
|
||||||
#endif
|
/*
|
||||||
|
Set animation properties if necessary.
|
||||||
|
*/
|
||||||
|
VImage SetAnimationProperties(VImage image, int nPages, int pageHeight, std::vector<int> delay, int loop);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove animation properties from image.
|
||||||
|
*/
|
||||||
|
VImage RemoveAnimationProperties(VImage image);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove GIF palette from image.
|
||||||
|
*/
|
||||||
|
VImage RemoveGifPalette(VImage image);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Does this image have a non-default density?
|
||||||
|
*/
|
||||||
|
bool HasDensity(VImage image);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get pixels/mm resolution as pixels/inch density.
|
||||||
|
*/
|
||||||
|
int GetDensity(VImage image);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set pixels/mm resolution based on a pixels/inch density.
|
||||||
|
*/
|
||||||
|
VImage SetDensity(VImage image, const double density);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Multi-page images can have a page height. Fetch it, and sanity check it.
|
||||||
|
If page-height is not set, it defaults to the image height
|
||||||
|
*/
|
||||||
|
int GetPageHeight(VImage image);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Check the proposed format supports the current dimensions.
|
||||||
|
*/
|
||||||
|
void AssertImageTypeDimensions(VImage image, ImageType const imageType);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows
|
||||||
|
*/
|
||||||
|
extern std::function<void(void*, char*)> FreeCallback;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Called with warnings from the glib-registered "VIPS" domain
|
||||||
|
*/
|
||||||
|
void VipsWarningCallback(char const* log_domain, GLogLevelFlags log_level, char const* message, void* ignore);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Pop the oldest warning message from the queue
|
||||||
|
*/
|
||||||
|
std::string VipsWarningPop();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Attach an event listener for progress updates, used to detect timeout
|
||||||
|
*/
|
||||||
|
void SetTimeout(VImage image, int const timeoutSeconds);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Event listener for progress updates, used to detect timeout
|
||||||
|
*/
|
||||||
|
void VipsProgressCallBack(VipsImage *image, VipsProgress *progress, int *timeoutSeconds);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Calculate the (left, top) coordinates of the output image
|
||||||
|
within the input image, applying the given gravity during an embed.
|
||||||
|
*/
|
||||||
|
std::tuple<int, int> CalculateEmbedPosition(int const inWidth, int const inHeight,
|
||||||
|
int const outWidth, int const outHeight, int const gravity);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Calculate the (left, top) coordinates of the output image
|
||||||
|
within the input image, applying the given gravity.
|
||||||
|
*/
|
||||||
|
std::tuple<int, int> CalculateCrop(int const inWidth, int const inHeight,
|
||||||
|
int const outWidth, int const outHeight, int const gravity);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Calculate the (left, top) coordinates of the output image
|
||||||
|
within the input image, applying the given x and y offsets of the output image.
|
||||||
|
*/
|
||||||
|
std::tuple<int, int> CalculateCrop(int const inWidth, int const inHeight,
|
||||||
|
int const outWidth, int const outHeight, int const x, int const y);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Are pixel values in this image 16-bit integer?
|
||||||
|
*/
|
||||||
|
bool Is16Bit(VipsInterpretation const interpretation);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Return the image alpha maximum. Useful for combining alpha bands. scRGB
|
||||||
|
images are 0 - 1 for image data, but the alpha is 0 - 255.
|
||||||
|
*/
|
||||||
|
double MaximumImageAlpha(VipsInterpretation const interpretation);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Convert RGBA value to another colourspace
|
||||||
|
*/
|
||||||
|
std::vector<double> GetRgbaAsColourspace(std::vector<double> const rgba,
|
||||||
|
VipsInterpretation const interpretation, bool premultiply);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Apply the alpha channel to a given colour
|
||||||
|
*/
|
||||||
|
std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour, bool premultiply);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Removes alpha channel, if any.
|
||||||
|
*/
|
||||||
|
VImage RemoveAlpha(VImage image);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Ensures alpha channel, if missing.
|
||||||
|
*/
|
||||||
|
VImage EnsureAlpha(VImage image, double const value);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Calculate the horizontal and vertical shrink factors, taking the canvas mode into account.
|
||||||
|
*/
|
||||||
|
std::pair<double, double> ResolveShrink(int width, int height, int targetWidth, int targetHeight,
|
||||||
|
Canvas canvas, bool withoutEnlargement, bool withoutReduction);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Ensure decoding remains sequential.
|
||||||
|
*/
|
||||||
|
VImage StaySequential(VImage image, VipsAccess access, bool condition = TRUE);
|
||||||
|
|
||||||
|
} // namespace sharp
|
||||||
|
|
||||||
|
#endif // SRC_COMMON_H_
|
||||||
|
|||||||
351
src/metadata.cc
Executable file → Normal file
351
src/metadata.cc
Executable file → Normal file
@@ -1,148 +1,287 @@
|
|||||||
#include <node.h>
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
#include <vips/vips.h>
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
#include "nan.h"
|
#include <numeric>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <napi.h>
|
||||||
|
#include <vips/vips8>
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "metadata.h"
|
#include "metadata.h"
|
||||||
|
|
||||||
using namespace v8;
|
class MetadataWorker : public Napi::AsyncWorker {
|
||||||
using namespace sharp;
|
|
||||||
|
|
||||||
struct MetadataBaton {
|
|
||||||
// Input
|
|
||||||
std::string fileIn;
|
|
||||||
void* bufferIn;
|
|
||||||
size_t bufferInLength;
|
|
||||||
// Output
|
|
||||||
std::string format;
|
|
||||||
int width;
|
|
||||||
int height;
|
|
||||||
std::string space;
|
|
||||||
int channels;
|
|
||||||
bool hasProfile;
|
|
||||||
bool hasAlpha;
|
|
||||||
int orientation;
|
|
||||||
std::string err;
|
|
||||||
|
|
||||||
MetadataBaton():
|
|
||||||
bufferInLength(0),
|
|
||||||
orientation(0) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class MetadataWorker : public NanAsyncWorker {
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MetadataWorker(NanCallback *callback, MetadataBaton *baton) : NanAsyncWorker(callback), baton(baton) {}
|
MetadataWorker(Napi::Function callback, MetadataBaton *baton, Napi::Function debuglog) :
|
||||||
|
Napi::AsyncWorker(callback), baton(baton), debuglog(Napi::Persistent(debuglog)) {}
|
||||||
~MetadataWorker() {}
|
~MetadataWorker() {}
|
||||||
|
|
||||||
void Execute() {
|
void Execute() {
|
||||||
// Decrement queued task counter
|
// Decrement queued task counter
|
||||||
g_atomic_int_dec_and_test(&counterQueue);
|
sharp::counterQueue--;
|
||||||
|
|
||||||
ImageType imageType = ImageType::UNKNOWN;
|
vips::VImage image;
|
||||||
VipsImage *image = NULL;
|
sharp::ImageType imageType = sharp::ImageType::UNKNOWN;
|
||||||
if (baton->bufferInLength > 1) {
|
try {
|
||||||
// From buffer
|
std::tie(image, imageType) = OpenInput(baton->input);
|
||||||
imageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
|
} catch (vips::VError const &err) {
|
||||||
if (imageType != ImageType::UNKNOWN) {
|
(baton->err).append(err.what());
|
||||||
image = InitImage(imageType, baton->bufferIn, baton->bufferInLength, VIPS_ACCESS_RANDOM);
|
|
||||||
} else {
|
|
||||||
(baton->err).append("Input buffer contains unsupported image format");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// From file
|
|
||||||
imageType = DetermineImageType(baton->fileIn.c_str());
|
|
||||||
if (imageType != ImageType::UNKNOWN) {
|
|
||||||
image = InitImage(imageType, baton->fileIn.c_str(), VIPS_ACCESS_RANDOM);
|
|
||||||
} else {
|
|
||||||
(baton->err).append("File is of an unsupported image format");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (image != NULL && imageType != ImageType::UNKNOWN) {
|
if (imageType != sharp::ImageType::UNKNOWN) {
|
||||||
// Image type
|
// Image type
|
||||||
switch (imageType) {
|
baton->format = sharp::ImageTypeId(imageType);
|
||||||
case ImageType::JPEG: baton->format = "jpeg"; break;
|
|
||||||
case ImageType::PNG: baton->format = "png"; break;
|
|
||||||
case ImageType::WEBP: baton->format = "webp"; break;
|
|
||||||
case ImageType::TIFF: baton->format = "tiff"; break;
|
|
||||||
case ImageType::MAGICK: baton->format = "magick"; break;
|
|
||||||
case ImageType::UNKNOWN: break;
|
|
||||||
}
|
|
||||||
// VipsImage attributes
|
// VipsImage attributes
|
||||||
baton->width = image->Xsize;
|
baton->width = image.width();
|
||||||
baton->height = image->Ysize;
|
baton->height = image.height();
|
||||||
baton->space = vips_enum_nick(VIPS_TYPE_INTERPRETATION, image->Type);
|
baton->space = vips_enum_nick(VIPS_TYPE_INTERPRETATION, image.interpretation());
|
||||||
baton->channels = image->Bands;
|
baton->channels = image.bands();
|
||||||
baton->hasProfile = HasProfile(image);
|
baton->depth = vips_enum_nick(VIPS_TYPE_BAND_FORMAT, image.format());
|
||||||
|
if (sharp::HasDensity(image)) {
|
||||||
|
baton->density = sharp::GetDensity(image);
|
||||||
|
}
|
||||||
|
if (image.get_typeof("jpeg-chroma-subsample") == VIPS_TYPE_REF_STRING) {
|
||||||
|
baton->chromaSubsampling = image.get_string("jpeg-chroma-subsample");
|
||||||
|
}
|
||||||
|
if (image.get_typeof("interlaced") == G_TYPE_INT) {
|
||||||
|
baton->isProgressive = image.get_int("interlaced") == 1;
|
||||||
|
}
|
||||||
|
if (image.get_typeof("palette-bit-depth") == G_TYPE_INT) {
|
||||||
|
baton->paletteBitDepth = image.get_int("palette-bit-depth");
|
||||||
|
}
|
||||||
|
if (image.get_typeof(VIPS_META_N_PAGES) == G_TYPE_INT) {
|
||||||
|
baton->pages = image.get_int(VIPS_META_N_PAGES);
|
||||||
|
}
|
||||||
|
if (image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT) {
|
||||||
|
baton->pageHeight = image.get_int(VIPS_META_PAGE_HEIGHT);
|
||||||
|
}
|
||||||
|
if (image.get_typeof("loop") == G_TYPE_INT) {
|
||||||
|
baton->loop = image.get_int("loop");
|
||||||
|
}
|
||||||
|
if (image.get_typeof("delay") == VIPS_TYPE_ARRAY_INT) {
|
||||||
|
baton->delay = image.get_array_int("delay");
|
||||||
|
}
|
||||||
|
if (image.get_typeof("heif-primary") == G_TYPE_INT) {
|
||||||
|
baton->pagePrimary = image.get_int("heif-primary");
|
||||||
|
}
|
||||||
|
if (image.get_typeof("heif-compression") == VIPS_TYPE_REF_STRING) {
|
||||||
|
baton->compression = image.get_string("heif-compression");
|
||||||
|
}
|
||||||
|
if (image.get_typeof(VIPS_META_RESOLUTION_UNIT) == VIPS_TYPE_REF_STRING) {
|
||||||
|
baton->resolutionUnit = image.get_string(VIPS_META_RESOLUTION_UNIT);
|
||||||
|
}
|
||||||
|
if (image.get_typeof("magick-format") == VIPS_TYPE_REF_STRING) {
|
||||||
|
baton->formatMagick = image.get_string("magick-format");
|
||||||
|
}
|
||||||
|
if (image.get_typeof("openslide.level-count") == VIPS_TYPE_REF_STRING) {
|
||||||
|
int const levels = std::stoi(image.get_string("openslide.level-count"));
|
||||||
|
for (int l = 0; l < levels; l++) {
|
||||||
|
std::string prefix = "openslide.level[" + std::to_string(l) + "].";
|
||||||
|
int const width = std::stoi(image.get_string((prefix + "width").data()));
|
||||||
|
int const height = std::stoi(image.get_string((prefix + "height").data()));
|
||||||
|
baton->levels.push_back(std::pair<int, int>(width, height));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (image.get_typeof(VIPS_META_N_SUBIFDS) == G_TYPE_INT) {
|
||||||
|
baton->subifds = image.get_int(VIPS_META_N_SUBIFDS);
|
||||||
|
}
|
||||||
|
baton->hasProfile = sharp::HasProfile(image);
|
||||||
|
if (image.get_typeof("background") == VIPS_TYPE_ARRAY_DOUBLE) {
|
||||||
|
baton->background = image.get_array_double("background");
|
||||||
|
}
|
||||||
// Derived attributes
|
// Derived attributes
|
||||||
baton->hasAlpha = HasAlpha(image);
|
baton->hasAlpha = sharp::HasAlpha(image);
|
||||||
baton->orientation = ExifOrientation(image);
|
baton->orientation = sharp::ExifOrientation(image);
|
||||||
// Drop image reference
|
// EXIF
|
||||||
g_object_unref(image);
|
if (image.get_typeof(VIPS_META_EXIF_NAME) == VIPS_TYPE_BLOB) {
|
||||||
|
size_t exifLength;
|
||||||
|
void const *exif = image.get_blob(VIPS_META_EXIF_NAME, &exifLength);
|
||||||
|
baton->exif = static_cast<char*>(g_malloc(exifLength));
|
||||||
|
memcpy(baton->exif, exif, exifLength);
|
||||||
|
baton->exifLength = exifLength;
|
||||||
|
}
|
||||||
|
// ICC profile
|
||||||
|
if (image.get_typeof(VIPS_META_ICC_NAME) == VIPS_TYPE_BLOB) {
|
||||||
|
size_t iccLength;
|
||||||
|
void const *icc = image.get_blob(VIPS_META_ICC_NAME, &iccLength);
|
||||||
|
baton->icc = static_cast<char*>(g_malloc(iccLength));
|
||||||
|
memcpy(baton->icc, icc, iccLength);
|
||||||
|
baton->iccLength = iccLength;
|
||||||
|
}
|
||||||
|
// IPTC
|
||||||
|
if (image.get_typeof(VIPS_META_IPTC_NAME) == VIPS_TYPE_BLOB) {
|
||||||
|
size_t iptcLength;
|
||||||
|
void const *iptc = image.get_blob(VIPS_META_IPTC_NAME, &iptcLength);
|
||||||
|
baton->iptc = static_cast<char *>(g_malloc(iptcLength));
|
||||||
|
memcpy(baton->iptc, iptc, iptcLength);
|
||||||
|
baton->iptcLength = iptcLength;
|
||||||
|
}
|
||||||
|
// XMP
|
||||||
|
if (image.get_typeof(VIPS_META_XMP_NAME) == VIPS_TYPE_BLOB) {
|
||||||
|
size_t xmpLength;
|
||||||
|
void const *xmp = image.get_blob(VIPS_META_XMP_NAME, &xmpLength);
|
||||||
|
baton->xmp = static_cast<char *>(g_malloc(xmpLength));
|
||||||
|
memcpy(baton->xmp, xmp, xmpLength);
|
||||||
|
baton->xmpLength = xmpLength;
|
||||||
|
}
|
||||||
|
// TIFFTAG_PHOTOSHOP
|
||||||
|
if (image.get_typeof(VIPS_META_PHOTOSHOP_NAME) == VIPS_TYPE_BLOB) {
|
||||||
|
size_t tifftagPhotoshopLength;
|
||||||
|
void const *tifftagPhotoshop = image.get_blob(VIPS_META_PHOTOSHOP_NAME, &tifftagPhotoshopLength);
|
||||||
|
baton->tifftagPhotoshop = static_cast<char *>(g_malloc(tifftagPhotoshopLength));
|
||||||
|
memcpy(baton->tifftagPhotoshop, tifftagPhotoshop, tifftagPhotoshopLength);
|
||||||
|
baton->tifftagPhotoshopLength = tifftagPhotoshopLength;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up
|
// Clean up
|
||||||
vips_error_clear();
|
vips_error_clear();
|
||||||
vips_thread_shutdown();
|
vips_thread_shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
void HandleOKCallback () {
|
void OnOK() {
|
||||||
NanScope();
|
Napi::Env env = Env();
|
||||||
|
Napi::HandleScope scope(env);
|
||||||
|
|
||||||
Handle<Value> argv[2] = { NanNull(), NanNull() };
|
// Handle warnings
|
||||||
if (!baton->err.empty()) {
|
std::string warning = sharp::VipsWarningPop();
|
||||||
// Error
|
while (!warning.empty()) {
|
||||||
argv[0] = Exception::Error(NanNew<String>(baton->err.data(), baton->err.size()));
|
debuglog.MakeCallback(Receiver().Value(), { Napi::String::New(env, warning) });
|
||||||
} else {
|
warning = sharp::VipsWarningPop();
|
||||||
// Metadata Object
|
|
||||||
Local<Object> info = NanNew<Object>();
|
|
||||||
info->Set(NanNew<String>("format"), NanNew<String>(baton->format));
|
|
||||||
info->Set(NanNew<String>("width"), NanNew<Number>(baton->width));
|
|
||||||
info->Set(NanNew<String>("height"), NanNew<Number>(baton->height));
|
|
||||||
info->Set(NanNew<String>("space"), NanNew<String>(baton->space));
|
|
||||||
info->Set(NanNew<String>("channels"), NanNew<Number>(baton->channels));
|
|
||||||
info->Set(NanNew<String>("hasProfile"), NanNew<Boolean>(baton->hasProfile));
|
|
||||||
info->Set(NanNew<String>("hasAlpha"), NanNew<Boolean>(baton->hasAlpha));
|
|
||||||
if (baton->orientation > 0) {
|
|
||||||
info->Set(NanNew<String>("orientation"), NanNew<Number>(baton->orientation));
|
|
||||||
}
|
|
||||||
argv[1] = info;
|
|
||||||
}
|
}
|
||||||
delete baton;
|
|
||||||
|
|
||||||
// Return to JavaScript
|
if (baton->err.empty()) {
|
||||||
callback->Call(2, argv);
|
Napi::Object info = Napi::Object::New(env);
|
||||||
|
info.Set("format", baton->format);
|
||||||
|
if (baton->input->bufferLength > 0) {
|
||||||
|
info.Set("size", baton->input->bufferLength);
|
||||||
|
}
|
||||||
|
info.Set("width", baton->width);
|
||||||
|
info.Set("height", baton->height);
|
||||||
|
info.Set("space", baton->space);
|
||||||
|
info.Set("channels", baton->channels);
|
||||||
|
info.Set("depth", baton->depth);
|
||||||
|
if (baton->density > 0) {
|
||||||
|
info.Set("density", baton->density);
|
||||||
|
}
|
||||||
|
if (!baton->chromaSubsampling.empty()) {
|
||||||
|
info.Set("chromaSubsampling", baton->chromaSubsampling);
|
||||||
|
}
|
||||||
|
info.Set("isProgressive", baton->isProgressive);
|
||||||
|
if (baton->paletteBitDepth > 0) {
|
||||||
|
info.Set("paletteBitDepth", baton->paletteBitDepth);
|
||||||
|
}
|
||||||
|
if (baton->pages > 0) {
|
||||||
|
info.Set("pages", baton->pages);
|
||||||
|
}
|
||||||
|
if (baton->pageHeight > 0) {
|
||||||
|
info.Set("pageHeight", baton->pageHeight);
|
||||||
|
}
|
||||||
|
if (baton->loop >= 0) {
|
||||||
|
info.Set("loop", baton->loop);
|
||||||
|
}
|
||||||
|
if (!baton->delay.empty()) {
|
||||||
|
int i = 0;
|
||||||
|
Napi::Array delay = Napi::Array::New(env, static_cast<size_t>(baton->delay.size()));
|
||||||
|
for (int const d : baton->delay) {
|
||||||
|
delay.Set(i++, d);
|
||||||
|
}
|
||||||
|
info.Set("delay", delay);
|
||||||
|
}
|
||||||
|
if (baton->pagePrimary > -1) {
|
||||||
|
info.Set("pagePrimary", baton->pagePrimary);
|
||||||
|
}
|
||||||
|
if (!baton->compression.empty()) {
|
||||||
|
info.Set("compression", baton->compression);
|
||||||
|
}
|
||||||
|
if (!baton->resolutionUnit.empty()) {
|
||||||
|
info.Set("resolutionUnit", baton->resolutionUnit == "in" ? "inch" : baton->resolutionUnit);
|
||||||
|
}
|
||||||
|
if (!baton->formatMagick.empty()) {
|
||||||
|
info.Set("formatMagick", baton->formatMagick);
|
||||||
|
}
|
||||||
|
if (!baton->levels.empty()) {
|
||||||
|
int i = 0;
|
||||||
|
Napi::Array levels = Napi::Array::New(env, static_cast<size_t>(baton->levels.size()));
|
||||||
|
for (std::pair<int, int> const &l : baton->levels) {
|
||||||
|
Napi::Object level = Napi::Object::New(env);
|
||||||
|
level.Set("width", l.first);
|
||||||
|
level.Set("height", l.second);
|
||||||
|
levels.Set(i++, level);
|
||||||
|
}
|
||||||
|
info.Set("levels", levels);
|
||||||
|
}
|
||||||
|
if (baton->subifds > 0) {
|
||||||
|
info.Set("subifds", baton->subifds);
|
||||||
|
}
|
||||||
|
if (!baton->background.empty()) {
|
||||||
|
if (baton->background.size() == 3) {
|
||||||
|
Napi::Object background = Napi::Object::New(env);
|
||||||
|
background.Set("r", baton->background[0]);
|
||||||
|
background.Set("g", baton->background[1]);
|
||||||
|
background.Set("b", baton->background[2]);
|
||||||
|
info.Set("background", background);
|
||||||
|
} else {
|
||||||
|
info.Set("background", baton->background[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info.Set("hasProfile", baton->hasProfile);
|
||||||
|
info.Set("hasAlpha", baton->hasAlpha);
|
||||||
|
if (baton->orientation > 0) {
|
||||||
|
info.Set("orientation", baton->orientation);
|
||||||
|
}
|
||||||
|
if (baton->exifLength > 0) {
|
||||||
|
info.Set("exif", Napi::Buffer<char>::NewOrCopy(env, baton->exif, baton->exifLength, sharp::FreeCallback));
|
||||||
|
}
|
||||||
|
if (baton->iccLength > 0) {
|
||||||
|
info.Set("icc", Napi::Buffer<char>::NewOrCopy(env, baton->icc, baton->iccLength, sharp::FreeCallback));
|
||||||
|
}
|
||||||
|
if (baton->iptcLength > 0) {
|
||||||
|
info.Set("iptc", Napi::Buffer<char>::NewOrCopy(env, baton->iptc, baton->iptcLength, sharp::FreeCallback));
|
||||||
|
}
|
||||||
|
if (baton->xmpLength > 0) {
|
||||||
|
info.Set("xmp", Napi::Buffer<char>::NewOrCopy(env, baton->xmp, baton->xmpLength, sharp::FreeCallback));
|
||||||
|
}
|
||||||
|
if (baton->tifftagPhotoshopLength > 0) {
|
||||||
|
info.Set("tifftagPhotoshop",
|
||||||
|
Napi::Buffer<char>::NewOrCopy(env, baton->tifftagPhotoshop,
|
||||||
|
baton->tifftagPhotoshopLength, sharp::FreeCallback));
|
||||||
|
}
|
||||||
|
Callback().MakeCallback(Receiver().Value(), { env.Null(), info });
|
||||||
|
} else {
|
||||||
|
Callback().MakeCallback(Receiver().Value(), { Napi::Error::New(env, sharp::TrimEnd(baton->err)).Value() });
|
||||||
|
}
|
||||||
|
|
||||||
|
delete baton->input;
|
||||||
|
delete baton;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
MetadataBaton* baton;
|
MetadataBaton* baton;
|
||||||
|
Napi::FunctionReference debuglog;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
metadata(options, callback)
|
metadata(options, callback)
|
||||||
*/
|
*/
|
||||||
NAN_METHOD(metadata) {
|
Napi::Value metadata(const Napi::CallbackInfo& info) {
|
||||||
NanScope();
|
|
||||||
|
|
||||||
// V8 objects are converted to non-V8 types held in the baton struct
|
// V8 objects are converted to non-V8 types held in the baton struct
|
||||||
MetadataBaton *baton = new MetadataBaton;
|
MetadataBaton *baton = new MetadataBaton;
|
||||||
Local<Object> options = args[0]->ToObject();
|
Napi::Object options = info[size_t(0)].As<Napi::Object>();
|
||||||
|
|
||||||
// Input filename
|
// Input
|
||||||
baton->fileIn = *String::Utf8Value(options->Get(NanNew<String>("fileIn"))->ToString());
|
baton->input = sharp::CreateInputDescriptor(options.Get("input").As<Napi::Object>());
|
||||||
// Input Buffer object
|
|
||||||
if (options->Get(NanNew<String>("bufferIn"))->IsObject()) {
|
// Function to notify of libvips warnings
|
||||||
Local<Object> buffer = options->Get(NanNew<String>("bufferIn"))->ToObject();
|
Napi::Function debuglog = options.Get("debuglog").As<Napi::Function>();
|
||||||
baton->bufferInLength = node::Buffer::Length(buffer);
|
|
||||||
baton->bufferIn = node::Buffer::Data(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Join queue for worker thread
|
// Join queue for worker thread
|
||||||
NanCallback *callback = new NanCallback(args[1].As<v8::Function>());
|
Napi::Function callback = info[size_t(1)].As<Napi::Function>();
|
||||||
NanAsyncQueueWorker(new MetadataWorker(callback, baton));
|
MetadataWorker *worker = new MetadataWorker(callback, baton, debuglog);
|
||||||
|
worker->Receiver().Set("options", options);
|
||||||
|
worker->Queue();
|
||||||
|
|
||||||
// Increment queued task counter
|
// Increment queued task counter
|
||||||
g_atomic_int_inc(&counterQueue);
|
sharp::counterQueue++;
|
||||||
|
|
||||||
NanReturnUndefined();
|
return info.Env().Undefined();
|
||||||
}
|
}
|
||||||
|
|||||||
84
src/metadata.h
Executable file → Normal file
84
src/metadata.h
Executable file → Normal file
@@ -1,8 +1,82 @@
|
|||||||
#ifndef SHARP_METADATA_H
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
#define SHARP_METADATA_H
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
#include "nan.h"
|
#ifndef SRC_METADATA_H_
|
||||||
|
#define SRC_METADATA_H_
|
||||||
|
|
||||||
NAN_METHOD(metadata);
|
#include <string>
|
||||||
|
#include <napi.h>
|
||||||
|
|
||||||
#endif
|
#include "./common.h"
|
||||||
|
|
||||||
|
struct MetadataBaton {
|
||||||
|
// Input
|
||||||
|
sharp::InputDescriptor *input;
|
||||||
|
// Output
|
||||||
|
std::string format;
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
std::string space;
|
||||||
|
int channels;
|
||||||
|
std::string depth;
|
||||||
|
int density;
|
||||||
|
std::string chromaSubsampling;
|
||||||
|
bool isProgressive;
|
||||||
|
int paletteBitDepth;
|
||||||
|
int pages;
|
||||||
|
int pageHeight;
|
||||||
|
int loop;
|
||||||
|
std::vector<int> delay;
|
||||||
|
int pagePrimary;
|
||||||
|
std::string compression;
|
||||||
|
std::string resolutionUnit;
|
||||||
|
std::string formatMagick;
|
||||||
|
std::vector<std::pair<int, int>> levels;
|
||||||
|
int subifds;
|
||||||
|
std::vector<double> background;
|
||||||
|
bool hasProfile;
|
||||||
|
bool hasAlpha;
|
||||||
|
int orientation;
|
||||||
|
char *exif;
|
||||||
|
size_t exifLength;
|
||||||
|
char *icc;
|
||||||
|
size_t iccLength;
|
||||||
|
char *iptc;
|
||||||
|
size_t iptcLength;
|
||||||
|
char *xmp;
|
||||||
|
size_t xmpLength;
|
||||||
|
char *tifftagPhotoshop;
|
||||||
|
size_t tifftagPhotoshopLength;
|
||||||
|
std::string err;
|
||||||
|
|
||||||
|
MetadataBaton():
|
||||||
|
input(nullptr),
|
||||||
|
width(0),
|
||||||
|
height(0),
|
||||||
|
channels(0),
|
||||||
|
density(0),
|
||||||
|
isProgressive(false),
|
||||||
|
paletteBitDepth(0),
|
||||||
|
pages(0),
|
||||||
|
pageHeight(0),
|
||||||
|
loop(-1),
|
||||||
|
pagePrimary(-1),
|
||||||
|
subifds(0),
|
||||||
|
hasProfile(false),
|
||||||
|
hasAlpha(false),
|
||||||
|
orientation(0),
|
||||||
|
exif(nullptr),
|
||||||
|
exifLength(0),
|
||||||
|
icc(nullptr),
|
||||||
|
iccLength(0),
|
||||||
|
iptc(nullptr),
|
||||||
|
iptcLength(0),
|
||||||
|
xmp(nullptr),
|
||||||
|
xmpLength(0),
|
||||||
|
tifftagPhotoshop(nullptr),
|
||||||
|
tifftagPhotoshopLength(0) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
Napi::Value metadata(const Napi::CallbackInfo& info);
|
||||||
|
|
||||||
|
#endif // SRC_METADATA_H_
|
||||||
|
|||||||
454
src/operations.cc
Normal file
454
src/operations.cc
Normal file
@@ -0,0 +1,454 @@
|
|||||||
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <tuple>
|
||||||
|
#include <vector>
|
||||||
|
#include <vips/vips8>
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "operations.h"
|
||||||
|
|
||||||
|
using vips::VImage;
|
||||||
|
using vips::VError;
|
||||||
|
|
||||||
|
namespace sharp {
|
||||||
|
/*
|
||||||
|
* Tint an image using the specified chroma, preserving the original image luminance
|
||||||
|
*/
|
||||||
|
VImage Tint(VImage image, double const a, double const b) {
|
||||||
|
// Get original colourspace
|
||||||
|
VipsInterpretation typeBeforeTint = image.interpretation();
|
||||||
|
if (typeBeforeTint == VIPS_INTERPRETATION_RGB) {
|
||||||
|
typeBeforeTint = VIPS_INTERPRETATION_sRGB;
|
||||||
|
}
|
||||||
|
// Extract luminance
|
||||||
|
VImage luminance = image.colourspace(VIPS_INTERPRETATION_LAB)[0];
|
||||||
|
// Create the tinted version by combining the L from the original and the chroma from the tint
|
||||||
|
std::vector<double> chroma {a, b};
|
||||||
|
VImage tinted = luminance
|
||||||
|
.bandjoin(chroma)
|
||||||
|
.copy(VImage::option()->set("interpretation", VIPS_INTERPRETATION_LAB))
|
||||||
|
.colourspace(typeBeforeTint);
|
||||||
|
// Attach original alpha channel, if any
|
||||||
|
if (HasAlpha(image)) {
|
||||||
|
// Extract original alpha channel
|
||||||
|
VImage alpha = image[image.bands() - 1];
|
||||||
|
// Join alpha channel to normalised image
|
||||||
|
tinted = tinted.bandjoin(alpha);
|
||||||
|
}
|
||||||
|
return tinted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Stretch luminance to cover full dynamic range.
|
||||||
|
*/
|
||||||
|
VImage Normalise(VImage image, int const lower, int const upper) {
|
||||||
|
// Get original colourspace
|
||||||
|
VipsInterpretation typeBeforeNormalize = image.interpretation();
|
||||||
|
if (typeBeforeNormalize == VIPS_INTERPRETATION_RGB) {
|
||||||
|
typeBeforeNormalize = VIPS_INTERPRETATION_sRGB;
|
||||||
|
}
|
||||||
|
// Convert to LAB colourspace
|
||||||
|
VImage lab = image.colourspace(VIPS_INTERPRETATION_LAB);
|
||||||
|
// Extract luminance
|
||||||
|
VImage luminance = lab[0];
|
||||||
|
|
||||||
|
// Find luminance range
|
||||||
|
int const min = lower == 0 ? luminance.min() : luminance.percent(lower);
|
||||||
|
int const max = upper == 100 ? luminance.max() : luminance.percent(upper);
|
||||||
|
|
||||||
|
if (std::abs(max - min) > 1) {
|
||||||
|
// Extract chroma
|
||||||
|
VImage chroma = lab.extract_band(1, VImage::option()->set("n", 2));
|
||||||
|
// Calculate multiplication factor and addition
|
||||||
|
double f = 100.0 / (max - min);
|
||||||
|
double a = -(min * f);
|
||||||
|
// Scale luminance, join to chroma, convert back to original colourspace
|
||||||
|
VImage normalized = luminance.linear(f, a).bandjoin(chroma).colourspace(typeBeforeNormalize);
|
||||||
|
// Attach original alpha channel, if any
|
||||||
|
if (HasAlpha(image)) {
|
||||||
|
// Extract original alpha channel
|
||||||
|
VImage alpha = image[image.bands() - 1];
|
||||||
|
// Join alpha channel to normalised image
|
||||||
|
return normalized.bandjoin(alpha);
|
||||||
|
} else {
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Contrast limiting adapative histogram equalization (CLAHE)
|
||||||
|
*/
|
||||||
|
VImage Clahe(VImage image, int const width, int const height, int const maxSlope) {
|
||||||
|
return image.hist_local(width, height, VImage::option()->set("max_slope", maxSlope));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Gamma encoding/decoding
|
||||||
|
*/
|
||||||
|
VImage Gamma(VImage image, double const exponent) {
|
||||||
|
if (HasAlpha(image)) {
|
||||||
|
// Separate alpha channel
|
||||||
|
VImage alpha = image[image.bands() - 1];
|
||||||
|
return RemoveAlpha(image).gamma(VImage::option()->set("exponent", exponent)).bandjoin(alpha);
|
||||||
|
} else {
|
||||||
|
return image.gamma(VImage::option()->set("exponent", exponent));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Flatten image to remove alpha channel
|
||||||
|
*/
|
||||||
|
VImage Flatten(VImage image, std::vector<double> flattenBackground) {
|
||||||
|
double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
|
||||||
|
std::vector<double> background {
|
||||||
|
flattenBackground[0] * multiplier,
|
||||||
|
flattenBackground[1] * multiplier,
|
||||||
|
flattenBackground[2] * multiplier
|
||||||
|
};
|
||||||
|
return image.flatten(VImage::option()->set("background", background));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produce the "negative" of the image.
|
||||||
|
*/
|
||||||
|
VImage Negate(VImage image, bool const negateAlpha) {
|
||||||
|
if (HasAlpha(image) && !negateAlpha) {
|
||||||
|
// Separate alpha channel
|
||||||
|
VImage alpha = image[image.bands() - 1];
|
||||||
|
return RemoveAlpha(image).invert().bandjoin(alpha);
|
||||||
|
} else {
|
||||||
|
return image.invert();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Gaussian blur. Use sigma of -1.0 for fast blur.
|
||||||
|
*/
|
||||||
|
VImage Blur(VImage image, double const sigma) {
|
||||||
|
if (sigma == -1.0) {
|
||||||
|
// Fast, mild blur - averages neighbouring pixels
|
||||||
|
VImage blur = VImage::new_matrixv(3, 3,
|
||||||
|
1.0, 1.0, 1.0,
|
||||||
|
1.0, 1.0, 1.0,
|
||||||
|
1.0, 1.0, 1.0);
|
||||||
|
blur.set("scale", 9.0);
|
||||||
|
return image.conv(blur);
|
||||||
|
} else {
|
||||||
|
// Slower, accurate Gaussian blur
|
||||||
|
return image.gaussblur(sigma);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convolution with a kernel.
|
||||||
|
*/
|
||||||
|
VImage Convolve(VImage image, int const width, int const height,
|
||||||
|
double const scale, double const offset,
|
||||||
|
std::unique_ptr<double[]> const &kernel_v
|
||||||
|
) {
|
||||||
|
VImage kernel = VImage::new_from_memory(
|
||||||
|
kernel_v.get(),
|
||||||
|
width * height * sizeof(double),
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
1,
|
||||||
|
VIPS_FORMAT_DOUBLE);
|
||||||
|
kernel.set("scale", scale);
|
||||||
|
kernel.set("offset", offset);
|
||||||
|
|
||||||
|
return image.conv(kernel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Recomb with a Matrix of the given bands/channel size.
|
||||||
|
* Eg. RGB will be a 3x3 matrix.
|
||||||
|
*/
|
||||||
|
VImage Recomb(VImage image, std::unique_ptr<double[]> const &matrix) {
|
||||||
|
double *m = matrix.get();
|
||||||
|
image = image.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
|
return image
|
||||||
|
.recomb(image.bands() == 3
|
||||||
|
? VImage::new_from_memory(
|
||||||
|
m, 9 * sizeof(double), 3, 3, 1, VIPS_FORMAT_DOUBLE
|
||||||
|
)
|
||||||
|
: VImage::new_matrixv(4, 4,
|
||||||
|
m[0], m[1], m[2], 0.0,
|
||||||
|
m[3], m[4], m[5], 0.0,
|
||||||
|
m[6], m[7], m[8], 0.0,
|
||||||
|
0.0, 0.0, 0.0, 1.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
VImage Modulate(VImage image, double const brightness, double const saturation,
|
||||||
|
int const hue, double const lightness) {
|
||||||
|
VipsInterpretation colourspaceBeforeModulate = image.interpretation();
|
||||||
|
if (HasAlpha(image)) {
|
||||||
|
// Separate alpha channel
|
||||||
|
VImage alpha = image[image.bands() - 1];
|
||||||
|
return RemoveAlpha(image)
|
||||||
|
.colourspace(VIPS_INTERPRETATION_LCH)
|
||||||
|
.linear(
|
||||||
|
{ brightness, saturation, 1},
|
||||||
|
{ lightness, 0.0, static_cast<double>(hue) }
|
||||||
|
)
|
||||||
|
.colourspace(colourspaceBeforeModulate)
|
||||||
|
.bandjoin(alpha);
|
||||||
|
} else {
|
||||||
|
return image
|
||||||
|
.colourspace(VIPS_INTERPRETATION_LCH)
|
||||||
|
.linear(
|
||||||
|
{ brightness, saturation, 1 },
|
||||||
|
{ lightness, 0.0, static_cast<double>(hue) }
|
||||||
|
)
|
||||||
|
.colourspace(colourspaceBeforeModulate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
|
||||||
|
*/
|
||||||
|
VImage Sharpen(VImage image, double const sigma, double const m1, double const m2,
|
||||||
|
double const x1, double const y2, double const y3) {
|
||||||
|
if (sigma == -1.0) {
|
||||||
|
// Fast, mild sharpen
|
||||||
|
VImage sharpen = VImage::new_matrixv(3, 3,
|
||||||
|
-1.0, -1.0, -1.0,
|
||||||
|
-1.0, 32.0, -1.0,
|
||||||
|
-1.0, -1.0, -1.0);
|
||||||
|
sharpen.set("scale", 24.0);
|
||||||
|
return image.conv(sharpen);
|
||||||
|
} else {
|
||||||
|
// Slow, accurate sharpen in LAB colour space, with control over flat vs jagged areas
|
||||||
|
VipsInterpretation colourspaceBeforeSharpen = image.interpretation();
|
||||||
|
if (colourspaceBeforeSharpen == VIPS_INTERPRETATION_RGB) {
|
||||||
|
colourspaceBeforeSharpen = VIPS_INTERPRETATION_sRGB;
|
||||||
|
}
|
||||||
|
return image
|
||||||
|
.sharpen(VImage::option()
|
||||||
|
->set("sigma", sigma)
|
||||||
|
->set("m1", m1)
|
||||||
|
->set("m2", m2)
|
||||||
|
->set("x1", x1)
|
||||||
|
->set("y2", y2)
|
||||||
|
->set("y3", y3))
|
||||||
|
.colourspace(colourspaceBeforeSharpen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VImage Threshold(VImage image, double const threshold, bool const thresholdGrayscale) {
|
||||||
|
if (!thresholdGrayscale) {
|
||||||
|
return image >= threshold;
|
||||||
|
}
|
||||||
|
return image.colourspace(VIPS_INTERPRETATION_B_W) >= threshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Perform boolean/bitwise operation on image color channels - results in one channel image
|
||||||
|
*/
|
||||||
|
VImage Bandbool(VImage image, VipsOperationBoolean const boolean) {
|
||||||
|
image = image.bandbool(boolean);
|
||||||
|
return image.copy(VImage::option()->set("interpretation", VIPS_INTERPRETATION_B_W));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Perform bitwise boolean operation between images
|
||||||
|
*/
|
||||||
|
VImage Boolean(VImage image, VImage imageR, VipsOperationBoolean const boolean) {
|
||||||
|
return image.boolean(imageR, boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Trim an image
|
||||||
|
*/
|
||||||
|
VImage Trim(VImage image, std::vector<double> background, double threshold) {
|
||||||
|
if (image.width() < 3 && image.height() < 3) {
|
||||||
|
throw VError("Image to trim must be at least 3x3 pixels");
|
||||||
|
}
|
||||||
|
if (background.size() == 0) {
|
||||||
|
// Top-left pixel provides the default background colour if none is given
|
||||||
|
background = image.extract_area(0, 0, 1, 1)(0, 0);
|
||||||
|
} else if (sharp::Is16Bit(image.interpretation())) {
|
||||||
|
for (size_t i = 0; i < background.size(); i++) {
|
||||||
|
background[i] *= 256.0;
|
||||||
|
}
|
||||||
|
threshold *= 256.0;
|
||||||
|
}
|
||||||
|
std::vector<double> backgroundAlpha({ background.back() });
|
||||||
|
if (HasAlpha(image)) {
|
||||||
|
background.pop_back();
|
||||||
|
} else {
|
||||||
|
background.resize(image.bands());
|
||||||
|
}
|
||||||
|
int left, top, width, height;
|
||||||
|
left = image.find_trim(&top, &width, &height, VImage::option()
|
||||||
|
->set("background", background)
|
||||||
|
->set("threshold", threshold));
|
||||||
|
if (HasAlpha(image)) {
|
||||||
|
// Search alpha channel (A)
|
||||||
|
int leftA, topA, widthA, heightA;
|
||||||
|
VImage alpha = image[image.bands() - 1];
|
||||||
|
leftA = alpha.find_trim(&topA, &widthA, &heightA, VImage::option()
|
||||||
|
->set("background", backgroundAlpha)
|
||||||
|
->set("threshold", threshold));
|
||||||
|
if (widthA > 0 && heightA > 0) {
|
||||||
|
if (width > 0 && height > 0) {
|
||||||
|
// Combined bounding box (B)
|
||||||
|
int const leftB = std::min(left, leftA);
|
||||||
|
int const topB = std::min(top, topA);
|
||||||
|
int const widthB = std::max(left + width, leftA + widthA) - leftB;
|
||||||
|
int const heightB = std::max(top + height, topA + heightA) - topB;
|
||||||
|
return image.extract_area(leftB, topB, widthB, heightB);
|
||||||
|
} else {
|
||||||
|
// Use alpha only
|
||||||
|
return image.extract_area(leftA, topA, widthA, heightA);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (width > 0 && height > 0) {
|
||||||
|
return image.extract_area(left, top, width, height);
|
||||||
|
}
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calculate (a * in + b)
|
||||||
|
*/
|
||||||
|
VImage Linear(VImage image, std::vector<double> const a, std::vector<double> const b) {
|
||||||
|
size_t const bands = static_cast<size_t>(image.bands());
|
||||||
|
if (a.size() > bands) {
|
||||||
|
throw VError("Band expansion using linear is unsupported");
|
||||||
|
}
|
||||||
|
bool const uchar = !Is16Bit(image.interpretation());
|
||||||
|
if (HasAlpha(image) && a.size() != bands && (a.size() == 1 || a.size() == bands - 1 || bands - 1 == 1)) {
|
||||||
|
// Separate alpha channel
|
||||||
|
VImage alpha = image[bands - 1];
|
||||||
|
return RemoveAlpha(image).linear(a, b, VImage::option()->set("uchar", uchar)).bandjoin(alpha);
|
||||||
|
} else {
|
||||||
|
return image.linear(a, b, VImage::option()->set("uchar", uchar));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Unflatten
|
||||||
|
*/
|
||||||
|
VImage Unflatten(VImage image) {
|
||||||
|
if (HasAlpha(image)) {
|
||||||
|
VImage alpha = image[image.bands() - 1];
|
||||||
|
VImage noAlpha = RemoveAlpha(image);
|
||||||
|
return noAlpha.bandjoin(alpha & (noAlpha.colourspace(VIPS_INTERPRETATION_B_W) < 255));
|
||||||
|
} else {
|
||||||
|
return image.bandjoin(image.colourspace(VIPS_INTERPRETATION_B_W) < 255);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensure the image is in a given colourspace
|
||||||
|
*/
|
||||||
|
VImage EnsureColourspace(VImage image, VipsInterpretation colourspace) {
|
||||||
|
if (colourspace != VIPS_INTERPRETATION_LAST && image.interpretation() != colourspace) {
|
||||||
|
image = image.colourspace(colourspace,
|
||||||
|
VImage::option()->set("source_space", image.interpretation()));
|
||||||
|
}
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Split and crop each frame, reassemble, and update pageHeight.
|
||||||
|
*/
|
||||||
|
VImage CropMultiPage(VImage image, int left, int top, int width, int height,
|
||||||
|
int nPages, int *pageHeight) {
|
||||||
|
if (top == 0 && height == *pageHeight) {
|
||||||
|
// Fast path; no need to adjust the height of the multi-page image
|
||||||
|
return image.extract_area(left, 0, width, image.height());
|
||||||
|
} else {
|
||||||
|
std::vector<VImage> pages;
|
||||||
|
pages.reserve(nPages);
|
||||||
|
|
||||||
|
// Split the image into cropped frames
|
||||||
|
for (int i = 0; i < nPages; i++) {
|
||||||
|
pages.push_back(
|
||||||
|
image.extract_area(left, *pageHeight * i + top, width, height));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reassemble the frames into a tall, thin image
|
||||||
|
VImage assembled = VImage::arrayjoin(pages,
|
||||||
|
VImage::option()->set("across", 1));
|
||||||
|
|
||||||
|
// Update the page height
|
||||||
|
*pageHeight = height;
|
||||||
|
|
||||||
|
return assembled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Split into frames, embed each frame, reassemble, and update pageHeight.
|
||||||
|
*/
|
||||||
|
VImage EmbedMultiPage(VImage image, int left, int top, int width, int height,
|
||||||
|
VipsExtend extendWith, std::vector<double> background, int nPages, int *pageHeight) {
|
||||||
|
if (top == 0 && height == *pageHeight) {
|
||||||
|
// Fast path; no need to adjust the height of the multi-page image
|
||||||
|
return image.embed(left, 0, width, image.height(), VImage::option()
|
||||||
|
->set("extend", extendWith)
|
||||||
|
->set("background", background));
|
||||||
|
} else if (left == 0 && width == image.width()) {
|
||||||
|
// Fast path; no need to adjust the width of the multi-page image
|
||||||
|
std::vector<VImage> pages;
|
||||||
|
pages.reserve(nPages);
|
||||||
|
|
||||||
|
// Rearrange the tall image into a vertical grid
|
||||||
|
image = image.grid(*pageHeight, nPages, 1);
|
||||||
|
|
||||||
|
// Do the embed on the wide image
|
||||||
|
image = image.embed(0, top, image.width(), height, VImage::option()
|
||||||
|
->set("extend", extendWith)
|
||||||
|
->set("background", background));
|
||||||
|
|
||||||
|
// Split the wide image into frames
|
||||||
|
for (int i = 0; i < nPages; i++) {
|
||||||
|
pages.push_back(
|
||||||
|
image.extract_area(width * i, 0, width, height));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reassemble the frames into a tall, thin image
|
||||||
|
VImage assembled = VImage::arrayjoin(pages,
|
||||||
|
VImage::option()->set("across", 1));
|
||||||
|
|
||||||
|
// Update the page height
|
||||||
|
*pageHeight = height;
|
||||||
|
|
||||||
|
return assembled;
|
||||||
|
} else {
|
||||||
|
std::vector<VImage> pages;
|
||||||
|
pages.reserve(nPages);
|
||||||
|
|
||||||
|
// Split the image into frames
|
||||||
|
for (int i = 0; i < nPages; i++) {
|
||||||
|
pages.push_back(
|
||||||
|
image.extract_area(0, *pageHeight * i, image.width(), *pageHeight));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Embed each frame in the target size
|
||||||
|
for (int i = 0; i < nPages; i++) {
|
||||||
|
pages[i] = pages[i].embed(left, top, width, height, VImage::option()
|
||||||
|
->set("extend", extendWith)
|
||||||
|
->set("background", background));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reassemble the frames into a tall, thin image
|
||||||
|
VImage assembled = VImage::arrayjoin(pages,
|
||||||
|
VImage::option()->set("across", 1));
|
||||||
|
|
||||||
|
// Update the page height
|
||||||
|
*pageHeight = height;
|
||||||
|
|
||||||
|
return assembled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sharp
|
||||||
125
src/operations.h
Normal file
125
src/operations.h
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
#ifndef SRC_OPERATIONS_H_
|
||||||
|
#define SRC_OPERATIONS_H_
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <tuple>
|
||||||
|
#include <vips/vips8>
|
||||||
|
|
||||||
|
using vips::VImage;
|
||||||
|
|
||||||
|
namespace sharp {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tint an image using the specified chroma, preserving the original image luminance
|
||||||
|
*/
|
||||||
|
VImage Tint(VImage image, double const a, double const b);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Stretch luminance to cover full dynamic range.
|
||||||
|
*/
|
||||||
|
VImage Normalise(VImage image, int const lower, int const upper);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Contrast limiting adapative histogram equalization (CLAHE)
|
||||||
|
*/
|
||||||
|
VImage Clahe(VImage image, int const width, int const height, int const maxSlope);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Gamma encoding/decoding
|
||||||
|
*/
|
||||||
|
VImage Gamma(VImage image, double const exponent);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Flatten image to remove alpha channel
|
||||||
|
*/
|
||||||
|
VImage Flatten(VImage image, std::vector<double> flattenBackground);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Produce the "negative" of the image.
|
||||||
|
*/
|
||||||
|
VImage Negate(VImage image, bool const negateAlpha);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Gaussian blur. Use sigma of -1.0 for fast blur.
|
||||||
|
*/
|
||||||
|
VImage Blur(VImage image, double const sigma);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convolution with a kernel.
|
||||||
|
*/
|
||||||
|
VImage Convolve(VImage image, int const width, int const height,
|
||||||
|
double const scale, double const offset, std::unique_ptr<double[]> const &kernel_v);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
|
||||||
|
*/
|
||||||
|
VImage Sharpen(VImage image, double const sigma, double const m1, double const m2,
|
||||||
|
double const x1, double const y2, double const y3);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Threshold an image
|
||||||
|
*/
|
||||||
|
VImage Threshold(VImage image, double const threshold, bool const thresholdColor);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Perform boolean/bitwise operation on image color channels - results in one channel image
|
||||||
|
*/
|
||||||
|
VImage Bandbool(VImage image, VipsOperationBoolean const boolean);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Perform bitwise boolean operation between images
|
||||||
|
*/
|
||||||
|
VImage Boolean(VImage image, VImage imageR, VipsOperationBoolean const boolean);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Trim an image
|
||||||
|
*/
|
||||||
|
VImage Trim(VImage image, std::vector<double> background, double const threshold);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Linear adjustment (a * in + b)
|
||||||
|
*/
|
||||||
|
VImage Linear(VImage image, std::vector<double> const a, std::vector<double> const b);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Unflatten
|
||||||
|
*/
|
||||||
|
VImage Unflatten(VImage image);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Recomb with a Matrix of the given bands/channel size.
|
||||||
|
* Eg. RGB will be a 3x3 matrix.
|
||||||
|
*/
|
||||||
|
VImage Recomb(VImage image, std::unique_ptr<double[]> const &matrix);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Modulate brightness, saturation, hue and lightness
|
||||||
|
*/
|
||||||
|
VImage Modulate(VImage image, double const brightness, double const saturation,
|
||||||
|
int const hue, double const lightness);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensure the image is in a given colourspace
|
||||||
|
*/
|
||||||
|
VImage EnsureColourspace(VImage image, VipsInterpretation colourspace);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Split and crop each frame, reassemble, and update pageHeight.
|
||||||
|
*/
|
||||||
|
VImage CropMultiPage(VImage image, int left, int top, int width, int height,
|
||||||
|
int nPages, int *pageHeight);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Split into frames, embed each frame, reassemble, and update pageHeight.
|
||||||
|
*/
|
||||||
|
VImage EmbedMultiPage(VImage image, int left, int top, int width, int height,
|
||||||
|
VipsExtend extendWith, std::vector<double> background, int nPages, int *pageHeight);
|
||||||
|
|
||||||
|
} // namespace sharp
|
||||||
|
|
||||||
|
#endif // SRC_OPERATIONS_H_
|
||||||
1714
src/pipeline.cc
Normal file
1714
src/pipeline.cc
Normal file
File diff suppressed because it is too large
Load Diff
381
src/pipeline.h
Normal file
381
src/pipeline.h
Normal file
@@ -0,0 +1,381 @@
|
|||||||
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
#ifndef SRC_PIPELINE_H_
|
||||||
|
#define SRC_PIPELINE_H_
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include <napi.h>
|
||||||
|
#include <vips/vips8>
|
||||||
|
|
||||||
|
#include "./common.h"
|
||||||
|
|
||||||
|
Napi::Value pipeline(const Napi::CallbackInfo& info);
|
||||||
|
|
||||||
|
struct Composite {
|
||||||
|
sharp::InputDescriptor *input;
|
||||||
|
VipsBlendMode mode;
|
||||||
|
int gravity;
|
||||||
|
int left;
|
||||||
|
int top;
|
||||||
|
bool hasOffset;
|
||||||
|
bool tile;
|
||||||
|
bool premultiplied;
|
||||||
|
|
||||||
|
Composite():
|
||||||
|
input(nullptr),
|
||||||
|
mode(VIPS_BLEND_MODE_OVER),
|
||||||
|
gravity(0),
|
||||||
|
left(0),
|
||||||
|
top(0),
|
||||||
|
hasOffset(false),
|
||||||
|
tile(false),
|
||||||
|
premultiplied(false) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PipelineBaton {
|
||||||
|
sharp::InputDescriptor *input;
|
||||||
|
std::string formatOut;
|
||||||
|
std::string fileOut;
|
||||||
|
void *bufferOut;
|
||||||
|
size_t bufferOutLength;
|
||||||
|
std::vector<Composite *> composite;
|
||||||
|
std::vector<sharp::InputDescriptor *> joinChannelIn;
|
||||||
|
int topOffsetPre;
|
||||||
|
int leftOffsetPre;
|
||||||
|
int widthPre;
|
||||||
|
int heightPre;
|
||||||
|
int topOffsetPost;
|
||||||
|
int leftOffsetPost;
|
||||||
|
int widthPost;
|
||||||
|
int heightPost;
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
int channels;
|
||||||
|
VipsKernel kernel;
|
||||||
|
sharp::Canvas canvas;
|
||||||
|
int position;
|
||||||
|
std::vector<double> resizeBackground;
|
||||||
|
bool hasCropOffset;
|
||||||
|
int cropOffsetLeft;
|
||||||
|
int cropOffsetTop;
|
||||||
|
bool hasAttentionCenter;
|
||||||
|
int attentionX;
|
||||||
|
int attentionY;
|
||||||
|
bool premultiplied;
|
||||||
|
bool tileCentre;
|
||||||
|
bool fastShrinkOnLoad;
|
||||||
|
double tintA;
|
||||||
|
double tintB;
|
||||||
|
bool flatten;
|
||||||
|
std::vector<double> flattenBackground;
|
||||||
|
bool unflatten;
|
||||||
|
bool negate;
|
||||||
|
bool negateAlpha;
|
||||||
|
double blurSigma;
|
||||||
|
double brightness;
|
||||||
|
double saturation;
|
||||||
|
int hue;
|
||||||
|
double lightness;
|
||||||
|
int medianSize;
|
||||||
|
double sharpenSigma;
|
||||||
|
double sharpenM1;
|
||||||
|
double sharpenM2;
|
||||||
|
double sharpenX1;
|
||||||
|
double sharpenY2;
|
||||||
|
double sharpenY3;
|
||||||
|
int threshold;
|
||||||
|
bool thresholdGrayscale;
|
||||||
|
std::vector<double> trimBackground;
|
||||||
|
double trimThreshold;
|
||||||
|
int trimOffsetLeft;
|
||||||
|
int trimOffsetTop;
|
||||||
|
std::vector<double> linearA;
|
||||||
|
std::vector<double> linearB;
|
||||||
|
double gamma;
|
||||||
|
double gammaOut;
|
||||||
|
bool greyscale;
|
||||||
|
bool normalise;
|
||||||
|
int normaliseLower;
|
||||||
|
int normaliseUpper;
|
||||||
|
int claheWidth;
|
||||||
|
int claheHeight;
|
||||||
|
int claheMaxSlope;
|
||||||
|
bool useExifOrientation;
|
||||||
|
int angle;
|
||||||
|
double rotationAngle;
|
||||||
|
std::vector<double> rotationBackground;
|
||||||
|
bool rotateBeforePreExtract;
|
||||||
|
bool flip;
|
||||||
|
bool flop;
|
||||||
|
int extendTop;
|
||||||
|
int extendBottom;
|
||||||
|
int extendLeft;
|
||||||
|
int extendRight;
|
||||||
|
std::vector<double> extendBackground;
|
||||||
|
VipsExtend extendWith;
|
||||||
|
bool withoutEnlargement;
|
||||||
|
bool withoutReduction;
|
||||||
|
std::vector<double> affineMatrix;
|
||||||
|
std::vector<double> affineBackground;
|
||||||
|
double affineIdx;
|
||||||
|
double affineIdy;
|
||||||
|
double affineOdx;
|
||||||
|
double affineOdy;
|
||||||
|
std::string affineInterpolator;
|
||||||
|
int jpegQuality;
|
||||||
|
bool jpegProgressive;
|
||||||
|
std::string jpegChromaSubsampling;
|
||||||
|
bool jpegTrellisQuantisation;
|
||||||
|
int jpegQuantisationTable;
|
||||||
|
bool jpegOvershootDeringing;
|
||||||
|
bool jpegOptimiseScans;
|
||||||
|
bool jpegOptimiseCoding;
|
||||||
|
bool pngProgressive;
|
||||||
|
int pngCompressionLevel;
|
||||||
|
bool pngAdaptiveFiltering;
|
||||||
|
bool pngPalette;
|
||||||
|
int pngQuality;
|
||||||
|
int pngEffort;
|
||||||
|
int pngBitdepth;
|
||||||
|
double pngDither;
|
||||||
|
int jp2Quality;
|
||||||
|
bool jp2Lossless;
|
||||||
|
int jp2TileHeight;
|
||||||
|
int jp2TileWidth;
|
||||||
|
std::string jp2ChromaSubsampling;
|
||||||
|
int webpQuality;
|
||||||
|
int webpAlphaQuality;
|
||||||
|
bool webpNearLossless;
|
||||||
|
bool webpLossless;
|
||||||
|
bool webpSmartSubsample;
|
||||||
|
VipsForeignWebpPreset webpPreset;
|
||||||
|
int webpEffort;
|
||||||
|
bool webpMinSize;
|
||||||
|
bool webpMixed;
|
||||||
|
int gifBitdepth;
|
||||||
|
int gifEffort;
|
||||||
|
double gifDither;
|
||||||
|
double gifInterFrameMaxError;
|
||||||
|
double gifInterPaletteMaxError;
|
||||||
|
bool gifReuse;
|
||||||
|
bool gifProgressive;
|
||||||
|
int tiffQuality;
|
||||||
|
VipsForeignTiffCompression tiffCompression;
|
||||||
|
VipsForeignTiffPredictor tiffPredictor;
|
||||||
|
bool tiffPyramid;
|
||||||
|
int tiffBitdepth;
|
||||||
|
bool tiffTile;
|
||||||
|
int tiffTileHeight;
|
||||||
|
int tiffTileWidth;
|
||||||
|
double tiffXres;
|
||||||
|
double tiffYres;
|
||||||
|
VipsForeignTiffResunit tiffResolutionUnit;
|
||||||
|
int heifQuality;
|
||||||
|
VipsForeignHeifCompression heifCompression;
|
||||||
|
int heifEffort;
|
||||||
|
std::string heifChromaSubsampling;
|
||||||
|
bool heifLossless;
|
||||||
|
double jxlDistance;
|
||||||
|
int jxlDecodingTier;
|
||||||
|
int jxlEffort;
|
||||||
|
bool jxlLossless;
|
||||||
|
VipsBandFormat rawDepth;
|
||||||
|
std::string err;
|
||||||
|
bool withMetadata;
|
||||||
|
int withMetadataOrientation;
|
||||||
|
double withMetadataDensity;
|
||||||
|
std::string withMetadataIcc;
|
||||||
|
std::unordered_map<std::string, std::string> withMetadataStrs;
|
||||||
|
int timeoutSeconds;
|
||||||
|
std::unique_ptr<double[]> convKernel;
|
||||||
|
int convKernelWidth;
|
||||||
|
int convKernelHeight;
|
||||||
|
double convKernelScale;
|
||||||
|
double convKernelOffset;
|
||||||
|
sharp::InputDescriptor *boolean;
|
||||||
|
VipsOperationBoolean booleanOp;
|
||||||
|
VipsOperationBoolean bandBoolOp;
|
||||||
|
int extractChannel;
|
||||||
|
bool removeAlpha;
|
||||||
|
double ensureAlpha;
|
||||||
|
VipsInterpretation colourspaceInput;
|
||||||
|
VipsInterpretation colourspace;
|
||||||
|
std::vector<int> delay;
|
||||||
|
int loop;
|
||||||
|
int tileSize;
|
||||||
|
int tileOverlap;
|
||||||
|
VipsForeignDzContainer tileContainer;
|
||||||
|
VipsForeignDzLayout tileLayout;
|
||||||
|
std::string tileFormat;
|
||||||
|
int tileAngle;
|
||||||
|
std::vector<double> tileBackground;
|
||||||
|
int tileSkipBlanks;
|
||||||
|
VipsForeignDzDepth tileDepth;
|
||||||
|
std::string tileId;
|
||||||
|
std::string tileBasename;
|
||||||
|
std::unique_ptr<double[]> recombMatrix;
|
||||||
|
|
||||||
|
PipelineBaton():
|
||||||
|
input(nullptr),
|
||||||
|
bufferOutLength(0),
|
||||||
|
topOffsetPre(-1),
|
||||||
|
topOffsetPost(-1),
|
||||||
|
channels(0),
|
||||||
|
kernel(VIPS_KERNEL_LANCZOS3),
|
||||||
|
canvas(sharp::Canvas::CROP),
|
||||||
|
position(0),
|
||||||
|
resizeBackground{ 0.0, 0.0, 0.0, 255.0 },
|
||||||
|
hasCropOffset(false),
|
||||||
|
cropOffsetLeft(0),
|
||||||
|
cropOffsetTop(0),
|
||||||
|
hasAttentionCenter(false),
|
||||||
|
attentionX(0),
|
||||||
|
attentionY(0),
|
||||||
|
premultiplied(false),
|
||||||
|
tintA(128.0),
|
||||||
|
tintB(128.0),
|
||||||
|
flatten(false),
|
||||||
|
flattenBackground{ 0.0, 0.0, 0.0 },
|
||||||
|
unflatten(false),
|
||||||
|
negate(false),
|
||||||
|
negateAlpha(true),
|
||||||
|
blurSigma(0.0),
|
||||||
|
brightness(1.0),
|
||||||
|
saturation(1.0),
|
||||||
|
hue(0),
|
||||||
|
lightness(0),
|
||||||
|
medianSize(0),
|
||||||
|
sharpenSigma(0.0),
|
||||||
|
sharpenM1(1.0),
|
||||||
|
sharpenM2(2.0),
|
||||||
|
sharpenX1(2.0),
|
||||||
|
sharpenY2(10.0),
|
||||||
|
sharpenY3(20.0),
|
||||||
|
threshold(0),
|
||||||
|
thresholdGrayscale(true),
|
||||||
|
trimBackground{},
|
||||||
|
trimThreshold(0.0),
|
||||||
|
trimOffsetLeft(0),
|
||||||
|
trimOffsetTop(0),
|
||||||
|
linearA{},
|
||||||
|
linearB{},
|
||||||
|
gamma(0.0),
|
||||||
|
greyscale(false),
|
||||||
|
normalise(false),
|
||||||
|
normaliseLower(1),
|
||||||
|
normaliseUpper(99),
|
||||||
|
claheWidth(0),
|
||||||
|
claheHeight(0),
|
||||||
|
claheMaxSlope(3),
|
||||||
|
useExifOrientation(false),
|
||||||
|
angle(0),
|
||||||
|
rotationAngle(0.0),
|
||||||
|
rotationBackground{ 0.0, 0.0, 0.0, 255.0 },
|
||||||
|
flip(false),
|
||||||
|
flop(false),
|
||||||
|
extendTop(0),
|
||||||
|
extendBottom(0),
|
||||||
|
extendLeft(0),
|
||||||
|
extendRight(0),
|
||||||
|
extendBackground{ 0.0, 0.0, 0.0, 255.0 },
|
||||||
|
extendWith(VIPS_EXTEND_BACKGROUND),
|
||||||
|
withoutEnlargement(false),
|
||||||
|
withoutReduction(false),
|
||||||
|
affineMatrix{ 1.0, 0.0, 0.0, 1.0 },
|
||||||
|
affineBackground{ 0.0, 0.0, 0.0, 255.0 },
|
||||||
|
affineIdx(0),
|
||||||
|
affineIdy(0),
|
||||||
|
affineOdx(0),
|
||||||
|
affineOdy(0),
|
||||||
|
affineInterpolator("bicubic"),
|
||||||
|
jpegQuality(80),
|
||||||
|
jpegProgressive(false),
|
||||||
|
jpegChromaSubsampling("4:2:0"),
|
||||||
|
jpegTrellisQuantisation(false),
|
||||||
|
jpegQuantisationTable(0),
|
||||||
|
jpegOvershootDeringing(false),
|
||||||
|
jpegOptimiseScans(false),
|
||||||
|
jpegOptimiseCoding(true),
|
||||||
|
pngProgressive(false),
|
||||||
|
pngCompressionLevel(6),
|
||||||
|
pngAdaptiveFiltering(false),
|
||||||
|
pngPalette(false),
|
||||||
|
pngQuality(100),
|
||||||
|
pngEffort(7),
|
||||||
|
pngBitdepth(8),
|
||||||
|
pngDither(1.0),
|
||||||
|
jp2Quality(80),
|
||||||
|
jp2Lossless(false),
|
||||||
|
jp2TileHeight(512),
|
||||||
|
jp2TileWidth(512),
|
||||||
|
jp2ChromaSubsampling("4:4:4"),
|
||||||
|
webpQuality(80),
|
||||||
|
webpAlphaQuality(100),
|
||||||
|
webpNearLossless(false),
|
||||||
|
webpLossless(false),
|
||||||
|
webpSmartSubsample(false),
|
||||||
|
webpPreset(VIPS_FOREIGN_WEBP_PRESET_DEFAULT),
|
||||||
|
webpEffort(4),
|
||||||
|
webpMinSize(false),
|
||||||
|
webpMixed(false),
|
||||||
|
gifBitdepth(8),
|
||||||
|
gifEffort(7),
|
||||||
|
gifDither(1.0),
|
||||||
|
gifInterFrameMaxError(0.0),
|
||||||
|
gifInterPaletteMaxError(3.0),
|
||||||
|
gifReuse(true),
|
||||||
|
gifProgressive(false),
|
||||||
|
tiffQuality(80),
|
||||||
|
tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG),
|
||||||
|
tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL),
|
||||||
|
tiffPyramid(false),
|
||||||
|
tiffBitdepth(8),
|
||||||
|
tiffTile(false),
|
||||||
|
tiffTileHeight(256),
|
||||||
|
tiffTileWidth(256),
|
||||||
|
tiffXres(1.0),
|
||||||
|
tiffYres(1.0),
|
||||||
|
tiffResolutionUnit(VIPS_FOREIGN_TIFF_RESUNIT_INCH),
|
||||||
|
heifQuality(50),
|
||||||
|
heifCompression(VIPS_FOREIGN_HEIF_COMPRESSION_AV1),
|
||||||
|
heifEffort(4),
|
||||||
|
heifChromaSubsampling("4:4:4"),
|
||||||
|
heifLossless(false),
|
||||||
|
jxlDistance(1.0),
|
||||||
|
jxlDecodingTier(0),
|
||||||
|
jxlEffort(7),
|
||||||
|
jxlLossless(false),
|
||||||
|
rawDepth(VIPS_FORMAT_UCHAR),
|
||||||
|
withMetadata(false),
|
||||||
|
withMetadataOrientation(-1),
|
||||||
|
withMetadataDensity(0.0),
|
||||||
|
timeoutSeconds(0),
|
||||||
|
convKernelWidth(0),
|
||||||
|
convKernelHeight(0),
|
||||||
|
convKernelScale(0.0),
|
||||||
|
convKernelOffset(0.0),
|
||||||
|
boolean(nullptr),
|
||||||
|
booleanOp(VIPS_OPERATION_BOOLEAN_LAST),
|
||||||
|
bandBoolOp(VIPS_OPERATION_BOOLEAN_LAST),
|
||||||
|
extractChannel(-1),
|
||||||
|
removeAlpha(false),
|
||||||
|
ensureAlpha(-1.0),
|
||||||
|
colourspaceInput(VIPS_INTERPRETATION_LAST),
|
||||||
|
colourspace(VIPS_INTERPRETATION_LAST),
|
||||||
|
loop(-1),
|
||||||
|
tileSize(256),
|
||||||
|
tileOverlap(0),
|
||||||
|
tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS),
|
||||||
|
tileLayout(VIPS_FOREIGN_DZ_LAYOUT_DZ),
|
||||||
|
tileAngle(0),
|
||||||
|
tileBackground{ 255.0, 255.0, 255.0, 255.0 },
|
||||||
|
tileSkipBlanks(-1),
|
||||||
|
tileDepth(VIPS_FOREIGN_DZ_DEPTH_LAST) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SRC_PIPELINE_H_
|
||||||
911
src/resize.cc
911
src/resize.cc
@@ -1,911 +0,0 @@
|
|||||||
#include <tuple>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cmath>
|
|
||||||
#include <node.h>
|
|
||||||
#include <node_buffer.h>
|
|
||||||
#include <vips/vips.h>
|
|
||||||
|
|
||||||
#include "nan.h"
|
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
#include "resize.h"
|
|
||||||
|
|
||||||
using namespace v8;
|
|
||||||
using namespace sharp;
|
|
||||||
|
|
||||||
enum class Canvas {
|
|
||||||
CROP,
|
|
||||||
MAX,
|
|
||||||
EMBED
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class Angle {
|
|
||||||
D0,
|
|
||||||
D90,
|
|
||||||
D180,
|
|
||||||
D270,
|
|
||||||
DLAST
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ResizeBaton {
|
|
||||||
std::string fileIn;
|
|
||||||
void* bufferIn;
|
|
||||||
size_t bufferInLength;
|
|
||||||
std::string iccProfilePath;
|
|
||||||
std::string output;
|
|
||||||
std::string outputFormat;
|
|
||||||
void* bufferOut;
|
|
||||||
size_t bufferOutLength;
|
|
||||||
int topOffsetPre;
|
|
||||||
int leftOffsetPre;
|
|
||||||
int widthPre;
|
|
||||||
int heightPre;
|
|
||||||
int topOffsetPost;
|
|
||||||
int leftOffsetPost;
|
|
||||||
int widthPost;
|
|
||||||
int heightPost;
|
|
||||||
int width;
|
|
||||||
int height;
|
|
||||||
Canvas canvas;
|
|
||||||
int gravity;
|
|
||||||
std::string interpolator;
|
|
||||||
double background[4];
|
|
||||||
bool flatten;
|
|
||||||
double blurSigma;
|
|
||||||
int sharpenRadius;
|
|
||||||
double sharpenFlat;
|
|
||||||
double sharpenJagged;
|
|
||||||
double gamma;
|
|
||||||
bool greyscale;
|
|
||||||
int angle;
|
|
||||||
bool flip;
|
|
||||||
bool flop;
|
|
||||||
bool progressive;
|
|
||||||
bool withoutEnlargement;
|
|
||||||
VipsAccess accessMethod;
|
|
||||||
int quality;
|
|
||||||
int compressionLevel;
|
|
||||||
bool withoutAdaptiveFiltering;
|
|
||||||
std::string err;
|
|
||||||
bool withMetadata;
|
|
||||||
|
|
||||||
ResizeBaton():
|
|
||||||
bufferInLength(0),
|
|
||||||
outputFormat(""),
|
|
||||||
bufferOutLength(0),
|
|
||||||
topOffsetPre(-1),
|
|
||||||
topOffsetPost(-1),
|
|
||||||
canvas(Canvas::CROP),
|
|
||||||
gravity(0),
|
|
||||||
flatten(false),
|
|
||||||
blurSigma(0.0),
|
|
||||||
sharpenRadius(0),
|
|
||||||
sharpenFlat(1.0),
|
|
||||||
sharpenJagged(2.0),
|
|
||||||
gamma(0.0),
|
|
||||||
greyscale(false),
|
|
||||||
angle(0),
|
|
||||||
flip(false),
|
|
||||||
flop(false),
|
|
||||||
progressive(false),
|
|
||||||
withoutEnlargement(false),
|
|
||||||
quality(80),
|
|
||||||
compressionLevel(6),
|
|
||||||
withoutAdaptiveFiltering(false),
|
|
||||||
withMetadata(false) {
|
|
||||||
background[0] = 0.0;
|
|
||||||
background[1] = 0.0;
|
|
||||||
background[2] = 0.0;
|
|
||||||
background[3] = 255.0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class ResizeWorker : public NanAsyncWorker {
|
|
||||||
|
|
||||||
public:
|
|
||||||
ResizeWorker(NanCallback *callback, ResizeBaton *baton) : NanAsyncWorker(callback), baton(baton) {}
|
|
||||||
~ResizeWorker() {}
|
|
||||||
|
|
||||||
/*
|
|
||||||
libuv worker
|
|
||||||
*/
|
|
||||||
void Execute() {
|
|
||||||
// Decrement queued task counter
|
|
||||||
g_atomic_int_dec_and_test(&counterQueue);
|
|
||||||
// Increment processing task counter
|
|
||||||
g_atomic_int_inc(&counterProcess);
|
|
||||||
|
|
||||||
// Latest v2 sRGB ICC profile
|
|
||||||
std::string srgbProfile = baton->iccProfilePath + "sRGB_IEC61966-2-1_black_scaled.icc";
|
|
||||||
|
|
||||||
// Hang image references from this hook object
|
|
||||||
VipsObject *hook = reinterpret_cast<VipsObject*>(vips_image_new());
|
|
||||||
|
|
||||||
// Input
|
|
||||||
ImageType inputImageType = ImageType::UNKNOWN;
|
|
||||||
VipsImage *image;
|
|
||||||
if (baton->bufferInLength > 1) {
|
|
||||||
// From buffer
|
|
||||||
inputImageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
|
|
||||||
if (inputImageType != ImageType::UNKNOWN) {
|
|
||||||
image = InitImage(inputImageType, baton->bufferIn, baton->bufferInLength, baton->accessMethod);
|
|
||||||
} else {
|
|
||||||
(baton->err).append("Input buffer contains unsupported image format");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// From file
|
|
||||||
inputImageType = DetermineImageType(baton->fileIn.c_str());
|
|
||||||
if (inputImageType != ImageType::UNKNOWN) {
|
|
||||||
image = InitImage(inputImageType, baton->fileIn.c_str(), baton->accessMethod);
|
|
||||||
} else {
|
|
||||||
(baton->err).append("File is of an unsupported image format");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (inputImageType == ImageType::UNKNOWN) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
vips_object_local(hook, image);
|
|
||||||
|
|
||||||
// Pre extraction
|
|
||||||
if (baton->topOffsetPre != -1) {
|
|
||||||
VipsImage *extractedPre;
|
|
||||||
if (vips_extract_area(image, &extractedPre, baton->leftOffsetPre, baton->topOffsetPre, baton->widthPre, baton->heightPre, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
vips_object_local(hook, extractedPre);
|
|
||||||
image = extractedPre;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get input image width and height
|
|
||||||
int inputWidth = image->Xsize;
|
|
||||||
int inputHeight = image->Ysize;
|
|
||||||
|
|
||||||
// Calculate angle of rotation, to be carried out later
|
|
||||||
Angle rotation;
|
|
||||||
bool flip;
|
|
||||||
std::tie(rotation, flip) = CalculateRotationAndFlip(baton->angle, image);
|
|
||||||
if (rotation == Angle::D90 || rotation == Angle::D270) {
|
|
||||||
// Swap input output width and height when rotating by 90 or 270 degrees
|
|
||||||
int swap = inputWidth;
|
|
||||||
inputWidth = inputHeight;
|
|
||||||
inputHeight = swap;
|
|
||||||
}
|
|
||||||
if (flip && !baton->flip) {
|
|
||||||
// Add flip operation due to EXIF mirroring
|
|
||||||
baton->flip = TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get window size of interpolator, used for determining shrink vs affine
|
|
||||||
int interpolatorWindowSize = InterpolatorWindowSize(baton->interpolator.c_str());
|
|
||||||
|
|
||||||
// Scaling calculations
|
|
||||||
double factor;
|
|
||||||
if (baton->width > 0 && baton->height > 0) {
|
|
||||||
// Fixed width and height
|
|
||||||
double xfactor = static_cast<double>(inputWidth) / static_cast<double>(baton->width);
|
|
||||||
double yfactor = static_cast<double>(inputHeight) / static_cast<double>(baton->height);
|
|
||||||
factor = (baton->canvas == Canvas::CROP) ? std::min(xfactor, yfactor) : std::max(xfactor, yfactor);
|
|
||||||
// if max is set, we need to compute the real size of the thumb image
|
|
||||||
if (baton->canvas == Canvas::MAX) {
|
|
||||||
if (xfactor > yfactor) {
|
|
||||||
baton->height = round(static_cast<double>(inputHeight) / xfactor);
|
|
||||||
} else {
|
|
||||||
baton->width = round(static_cast<double>(inputWidth) / yfactor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (baton->width > 0) {
|
|
||||||
// Fixed width, auto height
|
|
||||||
factor = static_cast<double>(inputWidth) / static_cast<double>(baton->width);
|
|
||||||
baton->height = floor(static_cast<double>(inputHeight) / factor);
|
|
||||||
} else if (baton->height > 0) {
|
|
||||||
// Fixed height, auto width
|
|
||||||
factor = static_cast<double>(inputHeight) / static_cast<double>(baton->height);
|
|
||||||
baton->width = floor(static_cast<double>(inputWidth) / factor);
|
|
||||||
} else {
|
|
||||||
// Identity transform
|
|
||||||
factor = 1;
|
|
||||||
baton->width = inputWidth;
|
|
||||||
baton->height = inputHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate integral box shrink
|
|
||||||
int shrink = 1;
|
|
||||||
if (factor >= 2 && interpolatorWindowSize > 3) {
|
|
||||||
// Shrink less, affine more with interpolators that use at least 4x4 pixel window, e.g. bicubic
|
|
||||||
shrink = floor(factor * 3.0 / interpolatorWindowSize);
|
|
||||||
} else {
|
|
||||||
shrink = floor(factor);
|
|
||||||
}
|
|
||||||
if (shrink < 1) {
|
|
||||||
shrink = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate residual float affine transformation
|
|
||||||
double residual = static_cast<double>(shrink) / factor;
|
|
||||||
|
|
||||||
// Do not enlarge the output if the input width *or* height are already less than the required dimensions
|
|
||||||
if (baton->withoutEnlargement) {
|
|
||||||
if (inputWidth < baton->width || inputHeight < baton->height) {
|
|
||||||
factor = 1;
|
|
||||||
shrink = 1;
|
|
||||||
residual = 0;
|
|
||||||
baton->width = inputWidth;
|
|
||||||
baton->height = inputHeight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to use libjpeg shrink-on-load, but not when applying gamma correction or pre-resize extract
|
|
||||||
int shrink_on_load = 1;
|
|
||||||
if (inputImageType == ImageType::JPEG && shrink >= 2 && baton->gamma == 0 && baton->topOffsetPre == -1) {
|
|
||||||
if (shrink >= 8) {
|
|
||||||
factor = factor / 8;
|
|
||||||
shrink_on_load = 8;
|
|
||||||
} else if (shrink >= 4) {
|
|
||||||
factor = factor / 4;
|
|
||||||
shrink_on_load = 4;
|
|
||||||
} else if (shrink >= 2) {
|
|
||||||
factor = factor / 2;
|
|
||||||
shrink_on_load = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (shrink_on_load > 1) {
|
|
||||||
// Recalculate integral shrink and double residual
|
|
||||||
factor = std::max(factor, 1.0);
|
|
||||||
if (factor >= 2 && interpolatorWindowSize > 3) {
|
|
||||||
shrink = floor(factor * 3.0 / interpolatorWindowSize);
|
|
||||||
} else {
|
|
||||||
shrink = floor(factor);
|
|
||||||
}
|
|
||||||
residual = static_cast<double>(shrink) / factor;
|
|
||||||
// Reload input using shrink-on-load
|
|
||||||
VipsImage *shrunkOnLoad;
|
|
||||||
if (baton->bufferInLength > 1) {
|
|
||||||
if (vips_jpegload_buffer(baton->bufferIn, baton->bufferInLength, &shrunkOnLoad, "shrink", shrink_on_load, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (vips_jpegload((baton->fileIn).c_str(), &shrunkOnLoad, "shrink", shrink_on_load, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vips_object_local(hook, shrunkOnLoad);
|
|
||||||
image = shrunkOnLoad;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure we're using a device-independent colour space
|
|
||||||
if (HasProfile(image)) {
|
|
||||||
// Convert to sRGB using embedded profile
|
|
||||||
VipsImage *transformed;
|
|
||||||
if (!vips_icc_transform(image, &transformed, srgbProfile.c_str(), "embedded", TRUE, NULL)) {
|
|
||||||
// Embedded profile can fail, so only update references on success
|
|
||||||
vips_object_local(hook, transformed);
|
|
||||||
image = transformed;
|
|
||||||
}
|
|
||||||
} else if (image->Type == VIPS_INTERPRETATION_CMYK) {
|
|
||||||
// Convert to sRGB using default "USWebCoatedSWOP" CMYK profile
|
|
||||||
std::string cmykProfile = baton->iccProfilePath + "USWebCoatedSWOP.icc";
|
|
||||||
VipsImage *transformed;
|
|
||||||
if (vips_icc_transform(image, &transformed, srgbProfile.c_str(), "input_profile", cmykProfile.c_str(), NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
vips_object_local(hook, transformed);
|
|
||||||
image = transformed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flatten image to remove alpha channel
|
|
||||||
if (baton->flatten && HasAlpha(image)) {
|
|
||||||
// Background colour
|
|
||||||
VipsArrayDouble *background = vips_array_double_newv(
|
|
||||||
3, // Ignore alpha channel as we're about to remove it
|
|
||||||
baton->background[0],
|
|
||||||
baton->background[1],
|
|
||||||
baton->background[2]
|
|
||||||
);
|
|
||||||
VipsImage *flattened;
|
|
||||||
if (vips_flatten(image, &flattened, "background", background, NULL)) {
|
|
||||||
vips_area_unref(reinterpret_cast<VipsArea*>(background));
|
|
||||||
return Error(baton, hook);
|
|
||||||
};
|
|
||||||
vips_area_unref(reinterpret_cast<VipsArea*>(background));
|
|
||||||
vips_object_local(hook, flattened);
|
|
||||||
image = flattened;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gamma encoding (darken)
|
|
||||||
if (baton->gamma >= 1 && baton->gamma <= 3) {
|
|
||||||
VipsImage *gammaEncoded;
|
|
||||||
if (vips_gamma(image, &gammaEncoded, "exponent", 1.0 / baton->gamma, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
vips_object_local(hook, gammaEncoded);
|
|
||||||
image = gammaEncoded;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to greyscale (linear, therefore after gamma encoding, if any)
|
|
||||||
if (baton->greyscale) {
|
|
||||||
VipsImage *greyscale;
|
|
||||||
if (vips_colourspace(image, &greyscale, VIPS_INTERPRETATION_B_W, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
vips_object_local(hook, greyscale);
|
|
||||||
image = greyscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shrink > 1) {
|
|
||||||
VipsImage *shrunk;
|
|
||||||
// Use vips_shrink with the integral reduction
|
|
||||||
if (vips_shrink(image, &shrunk, shrink, shrink, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
vips_object_local(hook, shrunk);
|
|
||||||
image = shrunk;
|
|
||||||
// Recalculate residual float based on dimensions of required vs shrunk images
|
|
||||||
double shrunkWidth = shrunk->Xsize;
|
|
||||||
double shrunkHeight = shrunk->Ysize;
|
|
||||||
if (rotation == Angle::D90 || rotation == Angle::D270) {
|
|
||||||
// Swap input output width and height when rotating by 90 or 270 degrees
|
|
||||||
int swap = shrunkWidth;
|
|
||||||
shrunkWidth = shrunkHeight;
|
|
||||||
shrunkHeight = swap;
|
|
||||||
}
|
|
||||||
double residualx = static_cast<double>(baton->width) / static_cast<double>(shrunkWidth);
|
|
||||||
double residualy = static_cast<double>(baton->height) / static_cast<double>(shrunkHeight);
|
|
||||||
if (baton->canvas == Canvas::EMBED) {
|
|
||||||
residual = std::min(residualx, residualy);
|
|
||||||
} else {
|
|
||||||
residual = std::max(residualx, residualy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use vips_affine with the remaining float part
|
|
||||||
if (residual != 0.0) {
|
|
||||||
// Apply Gaussian blur before large affine reductions
|
|
||||||
if (residual < 1.0) {
|
|
||||||
// Calculate standard deviation
|
|
||||||
double sigma = ((1.0 / residual) - 0.4) / 3.0;
|
|
||||||
if (sigma >= 0.3) {
|
|
||||||
// Create Gaussian function for standard deviation
|
|
||||||
VipsImage *gaussian;
|
|
||||||
if (vips_gaussmat(&gaussian, sigma, 0.2, "separable", TRUE, "integer", TRUE, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
vips_object_local(hook, gaussian);
|
|
||||||
// Apply Gaussian function
|
|
||||||
VipsImage *blurred;
|
|
||||||
if (vips_convsep(image, &blurred, gaussian, "precision", VIPS_PRECISION_INTEGER, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
vips_object_local(hook, blurred);
|
|
||||||
image = blurred;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Create interpolator - "bilinear" (default), "bicubic" or "nohalo"
|
|
||||||
VipsInterpolate *interpolator = vips_interpolate_new(baton->interpolator.c_str());
|
|
||||||
vips_object_local(hook, interpolator);
|
|
||||||
// Perform affine transformation
|
|
||||||
VipsImage *affined;
|
|
||||||
if (vips_affine(image, &affined, residual, 0.0, 0.0, residual, "interpolate", interpolator, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
vips_object_local(hook, affined);
|
|
||||||
image = affined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rotate
|
|
||||||
if (rotation != Angle::D0) {
|
|
||||||
VipsImage *rotated;
|
|
||||||
if (vips_rot(image, &rotated, static_cast<VipsAngle>(rotation), NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
vips_object_local(hook, rotated);
|
|
||||||
image = rotated;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flip (mirror about Y axis)
|
|
||||||
if (baton->flip) {
|
|
||||||
VipsImage *flipped;
|
|
||||||
if (vips_flip(image, &flipped, VIPS_DIRECTION_VERTICAL, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
vips_object_local(hook, flipped);
|
|
||||||
image = flipped;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flop (mirror about X axis)
|
|
||||||
if (baton->flop) {
|
|
||||||
VipsImage *flopped;
|
|
||||||
if (vips_flip(image, &flopped, VIPS_DIRECTION_HORIZONTAL, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
vips_object_local(hook, flopped);
|
|
||||||
image = flopped;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Crop/embed
|
|
||||||
if (image->Xsize != baton->width || image->Ysize != baton->height) {
|
|
||||||
if (baton->canvas == Canvas::EMBED) {
|
|
||||||
// Match background colour space, namely sRGB
|
|
||||||
if (image->Type != VIPS_INTERPRETATION_sRGB) {
|
|
||||||
// Convert to sRGB colour space
|
|
||||||
VipsImage *colourspaced;
|
|
||||||
if (vips_colourspace(image, &colourspaced, VIPS_INTERPRETATION_sRGB, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
vips_object_local(hook, colourspaced);
|
|
||||||
image = colourspaced;
|
|
||||||
}
|
|
||||||
// Add non-transparent alpha channel, if required
|
|
||||||
if (baton->background[3] < 255.0 && !HasAlpha(image)) {
|
|
||||||
// Create single-channel transparency
|
|
||||||
VipsImage *black;
|
|
||||||
if (vips_black(&black, image->Xsize, image->Ysize, "bands", 1, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
vips_object_local(hook, black);
|
|
||||||
// Invert to become non-transparent
|
|
||||||
VipsImage *alpha;
|
|
||||||
if (vips_invert(black, &alpha, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
vips_object_local(hook, alpha);
|
|
||||||
// Append alpha channel to existing image
|
|
||||||
VipsImage *joined;
|
|
||||||
if (vips_bandjoin2(image, alpha, &joined, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
vips_object_local(hook, joined);
|
|
||||||
image = joined;
|
|
||||||
}
|
|
||||||
// Create background
|
|
||||||
VipsArrayDouble *background;
|
|
||||||
if (baton->background[3] < 255.0) {
|
|
||||||
background = vips_array_double_newv(
|
|
||||||
4, baton->background[0], baton->background[1], baton->background[2], baton->background[3]
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
background = vips_array_double_newv(
|
|
||||||
3, baton->background[0], baton->background[1], baton->background[2]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// Embed
|
|
||||||
int left = (baton->width - image->Xsize) / 2;
|
|
||||||
int top = (baton->height - image->Ysize) / 2;
|
|
||||||
VipsImage *embedded;
|
|
||||||
if (vips_embed(image, &embedded, left, top, baton->width, baton->height,
|
|
||||||
"extend", VIPS_EXTEND_BACKGROUND, "background", background, NULL
|
|
||||||
)) {
|
|
||||||
vips_area_unref(reinterpret_cast<VipsArea*>(background));
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
vips_area_unref(reinterpret_cast<VipsArea*>(background));
|
|
||||||
vips_object_local(hook, embedded);
|
|
||||||
image = embedded;
|
|
||||||
} else {
|
|
||||||
// Crop/max
|
|
||||||
int left;
|
|
||||||
int top;
|
|
||||||
std::tie(left, top) = CalculateCrop(image->Xsize, image->Ysize, baton->width, baton->height, baton->gravity);
|
|
||||||
int width = std::min(image->Xsize, baton->width);
|
|
||||||
int height = std::min(image->Ysize, baton->height);
|
|
||||||
VipsImage *extracted;
|
|
||||||
if (vips_extract_area(image, &extracted, left, top, width, height, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
vips_object_local(hook, extracted);
|
|
||||||
image = extracted;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Post extraction
|
|
||||||
if (baton->topOffsetPost != -1) {
|
|
||||||
VipsImage *extractedPost;
|
|
||||||
if (vips_extract_area(image, &extractedPost, baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
vips_object_local(hook, extractedPost);
|
|
||||||
image = extractedPost;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Blur
|
|
||||||
if (baton->blurSigma != 0.0) {
|
|
||||||
VipsImage *blurred;
|
|
||||||
if (baton->blurSigma < 0.0) {
|
|
||||||
// Fast, mild blur - averages neighbouring pixels
|
|
||||||
VipsImage *blur = vips_image_new_matrixv(3, 3,
|
|
||||||
1.0, 1.0, 1.0,
|
|
||||||
1.0, 1.0, 1.0,
|
|
||||||
1.0, 1.0, 1.0);
|
|
||||||
vips_image_set_double(blur, "scale", 9);
|
|
||||||
vips_object_local(hook, blur);
|
|
||||||
if (vips_conv(image, &blurred, blur, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Slower, accurate Gaussian blur
|
|
||||||
// Create Gaussian function for standard deviation
|
|
||||||
VipsImage *gaussian;
|
|
||||||
if (vips_gaussmat(&gaussian, baton->blurSigma, 0.2, "separable", TRUE, "integer", TRUE, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
vips_object_local(hook, gaussian);
|
|
||||||
// Apply Gaussian function
|
|
||||||
if (vips_convsep(image, &blurred, gaussian, "precision", VIPS_PRECISION_INTEGER, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vips_object_local(hook, blurred);
|
|
||||||
image = blurred;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sharpen
|
|
||||||
if (baton->sharpenRadius != 0) {
|
|
||||||
VipsImage *sharpened;
|
|
||||||
if (baton->sharpenRadius == -1) {
|
|
||||||
// Fast, mild sharpen
|
|
||||||
VipsImage *sharpen = vips_image_new_matrixv(3, 3,
|
|
||||||
-1.0, -1.0, -1.0,
|
|
||||||
-1.0, 32.0, -1.0,
|
|
||||||
-1.0, -1.0, -1.0);
|
|
||||||
vips_image_set_double(sharpen, "scale", 24);
|
|
||||||
vips_object_local(hook, sharpen);
|
|
||||||
if (vips_conv(image, &sharpened, sharpen, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Slow, accurate sharpen in LAB colour space, with control over flat vs jagged areas
|
|
||||||
if (vips_sharpen(image, &sharpened, "radius", baton->sharpenRadius, "m1", baton->sharpenFlat, "m2", baton->sharpenJagged, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vips_object_local(hook, sharpened);
|
|
||||||
image = sharpened;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gamma decoding (brighten)
|
|
||||||
if (baton->gamma >= 1 && baton->gamma <= 3) {
|
|
||||||
VipsImage *gammaDecoded;
|
|
||||||
if (vips_gamma(image, &gammaDecoded, "exponent", baton->gamma, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
vips_object_local(hook, gammaDecoded);
|
|
||||||
image = gammaDecoded;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert image to sRGB, if not already
|
|
||||||
if (image->Type != VIPS_INTERPRETATION_sRGB) {
|
|
||||||
// Switch intrepretation to sRGB
|
|
||||||
VipsImage *rgb;
|
|
||||||
if (vips_colourspace(image, &rgb, VIPS_INTERPRETATION_sRGB, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
vips_object_local(hook, rgb);
|
|
||||||
image = rgb;
|
|
||||||
// Tranform colours from embedded profile to sRGB profile
|
|
||||||
if (baton->withMetadata && HasProfile(image)) {
|
|
||||||
VipsImage *profiled;
|
|
||||||
if (vips_icc_transform(image, &profiled, srgbProfile.c_str(), "embedded", TRUE, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
vips_object_local(hook, profiled);
|
|
||||||
image = profiled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if !(VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 40 && VIPS_MINOR_VERSION >= 5)
|
|
||||||
// Generate image tile cache when interlace output is required - no longer required as of libvips 7.40.5+
|
|
||||||
if (baton->progressive) {
|
|
||||||
VipsImage *cached;
|
|
||||||
if (vips_tilecache(image, &cached, "threaded", TRUE, "persistent", TRUE, "max_tiles", -1, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
vips_object_local(hook, cached);
|
|
||||||
image = cached;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Output
|
|
||||||
if (baton->output == "__jpeg" || (baton->output == "__input" && inputImageType == ImageType::JPEG)) {
|
|
||||||
// Write JPEG to buffer
|
|
||||||
if (vips_jpegsave_buffer(image, &baton->bufferOut, &baton->bufferOutLength, "strip", !baton->withMetadata,
|
|
||||||
"Q", baton->quality, "optimize_coding", TRUE, "interlace", baton->progressive, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
baton->outputFormat = "jpeg";
|
|
||||||
} else if (baton->output == "__png" || (baton->output == "__input" && inputImageType == ImageType::PNG)) {
|
|
||||||
#if (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 42)
|
|
||||||
// Select PNG row filter
|
|
||||||
int filter = baton->withoutAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_NONE : VIPS_FOREIGN_PNG_FILTER_ALL;
|
|
||||||
// Write PNG to buffer
|
|
||||||
if (vips_pngsave_buffer(image, &baton->bufferOut, &baton->bufferOutLength, "strip", !baton->withMetadata,
|
|
||||||
"compression", baton->compressionLevel, "interlace", baton->progressive, "filter", filter, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
// Write PNG to buffer
|
|
||||||
if (vips_pngsave_buffer(image, &baton->bufferOut, &baton->bufferOutLength, "strip", !baton->withMetadata,
|
|
||||||
"compression", baton->compressionLevel, "interlace", baton->progressive, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
baton->outputFormat = "png";
|
|
||||||
} else if (baton->output == "__webp" || (baton->output == "__input" && inputImageType == ImageType::WEBP)) {
|
|
||||||
// Write WEBP to buffer
|
|
||||||
if (vips_webpsave_buffer(image, &baton->bufferOut, &baton->bufferOutLength, "strip", !baton->withMetadata,
|
|
||||||
"Q", baton->quality, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
baton->outputFormat = "webp";
|
|
||||||
} else {
|
|
||||||
bool outputJpeg = IsJpeg(baton->output);
|
|
||||||
bool outputPng = IsPng(baton->output);
|
|
||||||
bool outputWebp = IsWebp(baton->output);
|
|
||||||
bool outputTiff = IsTiff(baton->output);
|
|
||||||
bool matchInput = !(outputJpeg || outputPng || outputWebp || outputTiff);
|
|
||||||
if (outputJpeg || (matchInput && inputImageType == ImageType::JPEG)) {
|
|
||||||
// Write JPEG to file
|
|
||||||
if (vips_jpegsave(image, baton->output.c_str(), "strip", !baton->withMetadata,
|
|
||||||
"Q", baton->quality, "optimize_coding", TRUE, "interlace", baton->progressive, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
baton->outputFormat = "jpeg";
|
|
||||||
} else if (outputPng || (matchInput && inputImageType == ImageType::PNG)) {
|
|
||||||
#if (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 41)
|
|
||||||
// Select PNG row filter
|
|
||||||
int filter = baton->withoutAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_NONE : VIPS_FOREIGN_PNG_FILTER_ALL;
|
|
||||||
// Write PNG to file
|
|
||||||
if (vips_pngsave(image, baton->output.c_str(), "strip", !baton->withMetadata,
|
|
||||||
"compression", baton->compressionLevel, "interlace", baton->progressive, "filter", filter, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
// Write PNG to file
|
|
||||||
if (vips_pngsave(image, baton->output.c_str(), "strip", !baton->withMetadata,
|
|
||||||
"compression", baton->compressionLevel, "interlace", baton->progressive, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
baton->outputFormat = "png";
|
|
||||||
} else if (outputWebp || (matchInput && inputImageType == ImageType::WEBP)) {
|
|
||||||
// Write WEBP to file
|
|
||||||
if (vips_webpsave(image, baton->output.c_str(), "strip", !baton->withMetadata,
|
|
||||||
"Q", baton->quality, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
baton->outputFormat = "webp";
|
|
||||||
} else if (outputTiff || (matchInput && inputImageType == ImageType::TIFF)) {
|
|
||||||
// Write TIFF to file
|
|
||||||
if (vips_tiffsave(image, baton->output.c_str(), "strip", !baton->withMetadata,
|
|
||||||
"compression", VIPS_FOREIGN_TIFF_COMPRESSION_JPEG, "Q", baton->quality, NULL)) {
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
baton->outputFormat = "tiff";
|
|
||||||
} else {
|
|
||||||
(baton->err).append("Unsupported output " + baton->output);
|
|
||||||
return Error(baton, hook);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Clean up any dangling image references
|
|
||||||
g_object_unref(hook);
|
|
||||||
// Clean up libvips' per-request data and threads
|
|
||||||
vips_error_clear();
|
|
||||||
vips_thread_shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
void HandleOKCallback () {
|
|
||||||
NanScope();
|
|
||||||
|
|
||||||
// Free input Buffer
|
|
||||||
if (baton->bufferInLength > 0) {
|
|
||||||
g_free(baton->bufferIn);
|
|
||||||
}
|
|
||||||
|
|
||||||
Handle<Value> argv[3] = { NanNull(), NanNull(), NanNull() };
|
|
||||||
if (!baton->err.empty()) {
|
|
||||||
// Error
|
|
||||||
argv[0] = Exception::Error(NanNew<String>(baton->err.data(), baton->err.size()));
|
|
||||||
} else {
|
|
||||||
int width = baton->width;
|
|
||||||
int height = baton->height;
|
|
||||||
if (baton->topOffsetPre != -1 && (baton->width == -1 || baton->height == -1)) {
|
|
||||||
width = baton->widthPre;
|
|
||||||
height = baton->heightPre;
|
|
||||||
}
|
|
||||||
if (baton->topOffsetPost != -1) {
|
|
||||||
width = baton->widthPost;
|
|
||||||
height = baton->heightPost;
|
|
||||||
}
|
|
||||||
// Info Object
|
|
||||||
Local<Object> info = NanNew<Object>();
|
|
||||||
info->Set(NanNew<String>("format"), NanNew<String>(baton->outputFormat));
|
|
||||||
info->Set(NanNew<String>("width"), NanNew<Integer>(width));
|
|
||||||
info->Set(NanNew<String>("height"), NanNew<Integer>(height));
|
|
||||||
|
|
||||||
if (baton->bufferOutLength > 0) {
|
|
||||||
// Copy data to new Buffer
|
|
||||||
argv[1] = NanNewBufferHandle(static_cast<char*>(baton->bufferOut), baton->bufferOutLength);
|
|
||||||
g_free(baton->bufferOut);
|
|
||||||
// Add buffer size to info
|
|
||||||
info->Set(NanNew<String>("size"), NanNew<Uint32>(static_cast<uint32_t>(baton->bufferOutLength)));
|
|
||||||
argv[2] = info;
|
|
||||||
} else {
|
|
||||||
// Add file size to info
|
|
||||||
struct stat st;
|
|
||||||
g_stat(baton->output.c_str(), &st);
|
|
||||||
info->Set(NanNew<String>("size"), NanNew<Uint32>(static_cast<uint32_t>(st.st_size)));
|
|
||||||
argv[1] = info;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete baton;
|
|
||||||
|
|
||||||
// Decrement processing task counter
|
|
||||||
g_atomic_int_dec_and_test(&counterProcess);
|
|
||||||
|
|
||||||
// Return to JavaScript
|
|
||||||
callback->Call(3, argv);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
ResizeBaton* baton;
|
|
||||||
|
|
||||||
/*
|
|
||||||
Calculate the angle of rotation and need-to-flip for the output image.
|
|
||||||
In order of priority:
|
|
||||||
1. Use explicitly requested angle (supports 90, 180, 270)
|
|
||||||
2. Use input image EXIF Orientation header - supports mirroring
|
|
||||||
3. Otherwise default to zero, i.e. no rotation
|
|
||||||
*/
|
|
||||||
std::tuple<Angle, bool>
|
|
||||||
CalculateRotationAndFlip(int const angle, VipsImage const *input) {
|
|
||||||
Angle rotate = Angle::D0;
|
|
||||||
bool flip = FALSE;
|
|
||||||
if (angle == -1) {
|
|
||||||
switch(ExifOrientation(input)) {
|
|
||||||
case 6: rotate = Angle::D90; break;
|
|
||||||
case 3: rotate = Angle::D180; break;
|
|
||||||
case 8: rotate = Angle::D270; break;
|
|
||||||
case 2: flip = TRUE; break; // flip 1
|
|
||||||
case 7: flip = TRUE; rotate = Angle::D90; break; // flip 6
|
|
||||||
case 4: flip = TRUE; rotate = Angle::D180; break; // flip 3
|
|
||||||
case 5: flip = TRUE; rotate = Angle::D270; break; // flip 8
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (angle == 90) {
|
|
||||||
rotate = Angle::D90;
|
|
||||||
} else if (angle == 180) {
|
|
||||||
rotate = Angle::D180;
|
|
||||||
} else if (angle == 270) {
|
|
||||||
rotate = Angle::D270;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return std::make_tuple(rotate, flip);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Calculate the (left, top) coordinates of the output image
|
|
||||||
within the input image, applying the given gravity.
|
|
||||||
*/
|
|
||||||
std::tuple<int, int>
|
|
||||||
CalculateCrop(int const inWidth, int const inHeight, int const outWidth, int const outHeight, int const gravity) {
|
|
||||||
int left = 0;
|
|
||||||
int top = 0;
|
|
||||||
switch (gravity) {
|
|
||||||
case 1: // North
|
|
||||||
left = (inWidth - outWidth + 1) / 2;
|
|
||||||
break;
|
|
||||||
case 2: // East
|
|
||||||
left = inWidth - outWidth;
|
|
||||||
top = (inHeight - outHeight + 1) / 2;
|
|
||||||
break;
|
|
||||||
case 3: // South
|
|
||||||
left = (inWidth - outWidth + 1) / 2;
|
|
||||||
top = inHeight - outHeight;
|
|
||||||
break;
|
|
||||||
case 4: // West
|
|
||||||
top = (inHeight - outHeight + 1) / 2;
|
|
||||||
break;
|
|
||||||
default: // Centre
|
|
||||||
left = (inWidth - outWidth + 1) / 2;
|
|
||||||
top = (inHeight - outHeight + 1) / 2;
|
|
||||||
}
|
|
||||||
return std::make_tuple(left, top);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Copy then clear the error message.
|
|
||||||
Unref all transitional images on the hook.
|
|
||||||
Clear all thread-local data.
|
|
||||||
*/
|
|
||||||
void Error(ResizeBaton *baton, VipsObject *hook) {
|
|
||||||
// Get libvips' error message
|
|
||||||
(baton->err).append(vips_error_buffer());
|
|
||||||
// Clean up any dangling image references
|
|
||||||
g_object_unref(hook);
|
|
||||||
// Clean up libvips' per-request data and threads
|
|
||||||
vips_error_clear();
|
|
||||||
vips_thread_shutdown();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
resize(options, output, callback)
|
|
||||||
*/
|
|
||||||
NAN_METHOD(resize) {
|
|
||||||
NanScope();
|
|
||||||
|
|
||||||
// V8 objects are converted to non-V8 types held in the baton struct
|
|
||||||
ResizeBaton *baton = new ResizeBaton;
|
|
||||||
Local<Object> options = args[0]->ToObject();
|
|
||||||
|
|
||||||
// Input filename
|
|
||||||
baton->fileIn = *String::Utf8Value(options->Get(NanNew<String>("fileIn"))->ToString());
|
|
||||||
baton->accessMethod = options->Get(NanNew<String>("sequentialRead"))->BooleanValue() ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
|
|
||||||
// Input Buffer object
|
|
||||||
if (options->Get(NanNew<String>("bufferIn"))->IsObject()) {
|
|
||||||
Local<Object> buffer = options->Get(NanNew<String>("bufferIn"))->ToObject();
|
|
||||||
// Take a copy of the input Buffer to avoid problems with V8 heap compaction
|
|
||||||
baton->bufferInLength = node::Buffer::Length(buffer);
|
|
||||||
baton->bufferIn = g_malloc(baton->bufferInLength);
|
|
||||||
memcpy(baton->bufferIn, node::Buffer::Data(buffer), baton->bufferInLength);
|
|
||||||
options->Set(NanNew<String>("bufferIn"), NanNull());
|
|
||||||
}
|
|
||||||
// ICC profile to use when input CMYK image has no embedded profile
|
|
||||||
baton->iccProfilePath = *String::Utf8Value(options->Get(NanNew<String>("iccProfilePath"))->ToString());
|
|
||||||
// Extract image options
|
|
||||||
baton->topOffsetPre = options->Get(NanNew<String>("topOffsetPre"))->Int32Value();
|
|
||||||
baton->leftOffsetPre = options->Get(NanNew<String>("leftOffsetPre"))->Int32Value();
|
|
||||||
baton->widthPre = options->Get(NanNew<String>("widthPre"))->Int32Value();
|
|
||||||
baton->heightPre = options->Get(NanNew<String>("heightPre"))->Int32Value();
|
|
||||||
baton->topOffsetPost = options->Get(NanNew<String>("topOffsetPost"))->Int32Value();
|
|
||||||
baton->leftOffsetPost = options->Get(NanNew<String>("leftOffsetPost"))->Int32Value();
|
|
||||||
baton->widthPost = options->Get(NanNew<String>("widthPost"))->Int32Value();
|
|
||||||
baton->heightPost = options->Get(NanNew<String>("heightPost"))->Int32Value();
|
|
||||||
// Output image dimensions
|
|
||||||
baton->width = options->Get(NanNew<String>("width"))->Int32Value();
|
|
||||||
baton->height = options->Get(NanNew<String>("height"))->Int32Value();
|
|
||||||
// Canvas option
|
|
||||||
Local<String> canvas = options->Get(NanNew<String>("canvas"))->ToString();
|
|
||||||
if (canvas->Equals(NanNew<String>("c"))) {
|
|
||||||
baton->canvas = Canvas::CROP;
|
|
||||||
} else if (canvas->Equals(NanNew<String>("m"))) {
|
|
||||||
baton->canvas = Canvas::MAX;
|
|
||||||
} else if (canvas->Equals(NanNew<String>("e"))) {
|
|
||||||
baton->canvas = Canvas::EMBED;
|
|
||||||
}
|
|
||||||
// Background colour
|
|
||||||
Local<Array> background = Local<Array>::Cast(options->Get(NanNew<String>("background")));
|
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
baton->background[i] = background->Get(i)->NumberValue();
|
|
||||||
}
|
|
||||||
// Resize options
|
|
||||||
baton->withoutEnlargement = options->Get(NanNew<String>("withoutEnlargement"))->BooleanValue();
|
|
||||||
baton->gravity = options->Get(NanNew<String>("gravity"))->Int32Value();
|
|
||||||
baton->interpolator = *String::Utf8Value(options->Get(NanNew<String>("interpolator"))->ToString());
|
|
||||||
// Operators
|
|
||||||
baton->flatten = options->Get(NanNew<String>("flatten"))->BooleanValue();
|
|
||||||
baton->blurSigma = options->Get(NanNew<String>("blurSigma"))->NumberValue();
|
|
||||||
baton->sharpenRadius = options->Get(NanNew<String>("sharpenRadius"))->Int32Value();
|
|
||||||
baton->sharpenFlat = options->Get(NanNew<String>("sharpenFlat"))->NumberValue();
|
|
||||||
baton->sharpenJagged = options->Get(NanNew<String>("sharpenJagged"))->NumberValue();
|
|
||||||
baton->gamma = options->Get(NanNew<String>("gamma"))->NumberValue();
|
|
||||||
baton->greyscale = options->Get(NanNew<String>("greyscale"))->BooleanValue();
|
|
||||||
baton->angle = options->Get(NanNew<String>("angle"))->Int32Value();
|
|
||||||
baton->flip = options->Get(NanNew<String>("flip"))->BooleanValue();
|
|
||||||
baton->flop = options->Get(NanNew<String>("flop"))->BooleanValue();
|
|
||||||
// Output options
|
|
||||||
baton->progressive = options->Get(NanNew<String>("progressive"))->BooleanValue();
|
|
||||||
baton->quality = options->Get(NanNew<String>("quality"))->Int32Value();
|
|
||||||
baton->compressionLevel = options->Get(NanNew<String>("compressionLevel"))->Int32Value();
|
|
||||||
baton->withoutAdaptiveFiltering = options->Get(NanNew<String>("withoutAdaptiveFiltering"))->BooleanValue();
|
|
||||||
baton->withMetadata = options->Get(NanNew<String>("withMetadata"))->BooleanValue();
|
|
||||||
// Output filename or __format for Buffer
|
|
||||||
baton->output = *String::Utf8Value(options->Get(NanNew<String>("output"))->ToString());
|
|
||||||
|
|
||||||
// Join queue for worker thread
|
|
||||||
NanCallback *callback = new NanCallback(args[1].As<v8::Function>());
|
|
||||||
NanAsyncQueueWorker(new ResizeWorker(callback, baton));
|
|
||||||
|
|
||||||
// Increment queued task counter
|
|
||||||
g_atomic_int_inc(&counterQueue);
|
|
||||||
|
|
||||||
NanReturnUndefined();
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
#ifndef SHARP_RESIZE_H
|
|
||||||
#define SHARP_RESIZE_H
|
|
||||||
|
|
||||||
#include "nan.h"
|
|
||||||
|
|
||||||
NAN_METHOD(resize);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
57
src/sharp.cc
Executable file → Normal file
57
src/sharp.cc
Executable file → Normal file
@@ -1,39 +1,40 @@
|
|||||||
#include <node.h>
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
#include <vips/vips.h>
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
#include "nan.h"
|
#include <mutex> // NOLINT(build/c++11)
|
||||||
|
|
||||||
|
#include <napi.h>
|
||||||
|
#include <vips/vips8>
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "metadata.h"
|
#include "metadata.h"
|
||||||
#include "resize.h"
|
#include "pipeline.h"
|
||||||
#include "utilities.h"
|
#include "utilities.h"
|
||||||
|
#include "stats.h"
|
||||||
|
|
||||||
using namespace v8;
|
Napi::Object init(Napi::Env env, Napi::Object exports) {
|
||||||
|
static std::once_flag sharp_vips_init_once;
|
||||||
|
std::call_once(sharp_vips_init_once, []() {
|
||||||
|
vips_init("sharp");
|
||||||
|
});
|
||||||
|
|
||||||
static void at_exit(void* arg) {
|
g_log_set_handler("VIPS", static_cast<GLogLevelFlags>(G_LOG_LEVEL_WARNING),
|
||||||
NanScope();
|
static_cast<GLogFunc>(sharp::VipsWarningCallback), nullptr);
|
||||||
vips_shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" void init(Handle<Object> target) {
|
|
||||||
NanScope();
|
|
||||||
vips_init("sharp");
|
|
||||||
node::AtExit(at_exit);
|
|
||||||
|
|
||||||
// Set libvips operation cache limits
|
|
||||||
vips_cache_set_max_mem(100 * 1048576); // 100 MB
|
|
||||||
vips_cache_set_max(500); // 500 operations
|
|
||||||
|
|
||||||
// Notify the V8 garbage collector of max cache size
|
|
||||||
NanAdjustExternalMemory(vips_cache_get_max_mem());
|
|
||||||
|
|
||||||
// Methods available to JavaScript
|
// Methods available to JavaScript
|
||||||
NODE_SET_METHOD(target, "metadata", metadata);
|
exports.Set("metadata", Napi::Function::New(env, metadata));
|
||||||
NODE_SET_METHOD(target, "resize", resize);
|
exports.Set("pipeline", Napi::Function::New(env, pipeline));
|
||||||
NODE_SET_METHOD(target, "cache", cache);
|
exports.Set("cache", Napi::Function::New(env, cache));
|
||||||
NODE_SET_METHOD(target, "concurrency", concurrency);
|
exports.Set("concurrency", Napi::Function::New(env, concurrency));
|
||||||
NODE_SET_METHOD(target, "counters", counters);
|
exports.Set("counters", Napi::Function::New(env, counters));
|
||||||
NODE_SET_METHOD(target, "libvipsVersion", libvipsVersion);
|
exports.Set("simd", Napi::Function::New(env, simd));
|
||||||
|
exports.Set("libvipsVersion", Napi::Function::New(env, libvipsVersion));
|
||||||
|
exports.Set("format", Napi::Function::New(env, format));
|
||||||
|
exports.Set("block", Napi::Function::New(env, block));
|
||||||
|
exports.Set("_maxColourDistance", Napi::Function::New(env, _maxColourDistance));
|
||||||
|
exports.Set("_isUsingJemalloc", Napi::Function::New(env, _isUsingJemalloc));
|
||||||
|
exports.Set("stats", Napi::Function::New(env, stats));
|
||||||
|
return exports;
|
||||||
}
|
}
|
||||||
|
|
||||||
NODE_MODULE(sharp, init)
|
NODE_API_MODULE(sharp, init)
|
||||||
|
|||||||
183
src/stats.cc
Normal file
183
src/stats.cc
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
#include <numeric>
|
||||||
|
#include <vector>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <napi.h>
|
||||||
|
#include <vips/vips8>
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "stats.h"
|
||||||
|
|
||||||
|
class StatsWorker : public Napi::AsyncWorker {
|
||||||
|
public:
|
||||||
|
StatsWorker(Napi::Function callback, StatsBaton *baton, Napi::Function debuglog) :
|
||||||
|
Napi::AsyncWorker(callback), baton(baton), debuglog(Napi::Persistent(debuglog)) {}
|
||||||
|
~StatsWorker() {}
|
||||||
|
|
||||||
|
const int STAT_MIN_INDEX = 0;
|
||||||
|
const int STAT_MAX_INDEX = 1;
|
||||||
|
const int STAT_SUM_INDEX = 2;
|
||||||
|
const int STAT_SQ_SUM_INDEX = 3;
|
||||||
|
const int STAT_MEAN_INDEX = 4;
|
||||||
|
const int STAT_STDEV_INDEX = 5;
|
||||||
|
const int STAT_MINX_INDEX = 6;
|
||||||
|
const int STAT_MINY_INDEX = 7;
|
||||||
|
const int STAT_MAXX_INDEX = 8;
|
||||||
|
const int STAT_MAXY_INDEX = 9;
|
||||||
|
|
||||||
|
void Execute() {
|
||||||
|
// Decrement queued task counter
|
||||||
|
sharp::counterQueue--;
|
||||||
|
|
||||||
|
vips::VImage image;
|
||||||
|
sharp::ImageType imageType = sharp::ImageType::UNKNOWN;
|
||||||
|
try {
|
||||||
|
std::tie(image, imageType) = OpenInput(baton->input);
|
||||||
|
} catch (vips::VError const &err) {
|
||||||
|
(baton->err).append(err.what());
|
||||||
|
}
|
||||||
|
if (imageType != sharp::ImageType::UNKNOWN) {
|
||||||
|
try {
|
||||||
|
vips::VImage stats = image.stats();
|
||||||
|
int const bands = image.bands();
|
||||||
|
for (int b = 1; b <= bands; b++) {
|
||||||
|
ChannelStats cStats(
|
||||||
|
static_cast<int>(stats.getpoint(STAT_MIN_INDEX, b).front()),
|
||||||
|
static_cast<int>(stats.getpoint(STAT_MAX_INDEX, b).front()),
|
||||||
|
stats.getpoint(STAT_SUM_INDEX, b).front(),
|
||||||
|
stats.getpoint(STAT_SQ_SUM_INDEX, b).front(),
|
||||||
|
stats.getpoint(STAT_MEAN_INDEX, b).front(),
|
||||||
|
stats.getpoint(STAT_STDEV_INDEX, b).front(),
|
||||||
|
static_cast<int>(stats.getpoint(STAT_MINX_INDEX, b).front()),
|
||||||
|
static_cast<int>(stats.getpoint(STAT_MINY_INDEX, b).front()),
|
||||||
|
static_cast<int>(stats.getpoint(STAT_MAXX_INDEX, b).front()),
|
||||||
|
static_cast<int>(stats.getpoint(STAT_MAXY_INDEX, b).front()));
|
||||||
|
baton->channelStats.push_back(cStats);
|
||||||
|
}
|
||||||
|
// Image is not opaque when alpha layer is present and contains a non-mamixa value
|
||||||
|
if (sharp::HasAlpha(image)) {
|
||||||
|
double const minAlpha = static_cast<double>(stats.getpoint(STAT_MIN_INDEX, bands).front());
|
||||||
|
if (minAlpha != sharp::MaximumImageAlpha(image.interpretation())) {
|
||||||
|
baton->isOpaque = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Convert to greyscale
|
||||||
|
vips::VImage greyscale = image.colourspace(VIPS_INTERPRETATION_B_W)[0];
|
||||||
|
// Estimate entropy via histogram of greyscale value frequency
|
||||||
|
baton->entropy = std::abs(greyscale.hist_find().hist_entropy());
|
||||||
|
// Estimate sharpness via standard deviation of greyscale laplacian
|
||||||
|
if (image.width() > 1 || image.height() > 1) {
|
||||||
|
VImage laplacian = VImage::new_matrixv(3, 3,
|
||||||
|
0.0, 1.0, 0.0,
|
||||||
|
1.0, -4.0, 1.0,
|
||||||
|
0.0, 1.0, 0.0);
|
||||||
|
laplacian.set("scale", 9.0);
|
||||||
|
baton->sharpness = greyscale.conv(laplacian).deviate();
|
||||||
|
}
|
||||||
|
// Most dominant sRGB colour via 4096-bin 3D histogram
|
||||||
|
vips::VImage hist = sharp::RemoveAlpha(image)
|
||||||
|
.colourspace(VIPS_INTERPRETATION_sRGB)
|
||||||
|
.hist_find_ndim(VImage::option()->set("bins", 16));
|
||||||
|
std::complex<double> maxpos = hist.maxpos();
|
||||||
|
int const dx = static_cast<int>(std::real(maxpos));
|
||||||
|
int const dy = static_cast<int>(std::imag(maxpos));
|
||||||
|
std::vector<double> pel = hist(dx, dy);
|
||||||
|
int const dz = std::distance(pel.begin(), std::find(pel.begin(), pel.end(), hist.max()));
|
||||||
|
baton->dominantRed = dx * 16 + 8;
|
||||||
|
baton->dominantGreen = dy * 16 + 8;
|
||||||
|
baton->dominantBlue = dz * 16 + 8;
|
||||||
|
} catch (vips::VError const &err) {
|
||||||
|
(baton->err).append(err.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
vips_error_clear();
|
||||||
|
vips_thread_shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnOK() {
|
||||||
|
Napi::Env env = Env();
|
||||||
|
Napi::HandleScope scope(env);
|
||||||
|
|
||||||
|
// Handle warnings
|
||||||
|
std::string warning = sharp::VipsWarningPop();
|
||||||
|
while (!warning.empty()) {
|
||||||
|
debuglog.MakeCallback(Receiver().Value(), { Napi::String::New(env, warning) });
|
||||||
|
warning = sharp::VipsWarningPop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (baton->err.empty()) {
|
||||||
|
// Stats Object
|
||||||
|
Napi::Object info = Napi::Object::New(env);
|
||||||
|
Napi::Array channels = Napi::Array::New(env);
|
||||||
|
|
||||||
|
std::vector<ChannelStats>::iterator it;
|
||||||
|
int i = 0;
|
||||||
|
for (it = baton->channelStats.begin(); it < baton->channelStats.end(); it++, i++) {
|
||||||
|
Napi::Object channelStat = Napi::Object::New(env);
|
||||||
|
channelStat.Set("min", it->min);
|
||||||
|
channelStat.Set("max", it->max);
|
||||||
|
channelStat.Set("sum", it->sum);
|
||||||
|
channelStat.Set("squaresSum", it->squaresSum);
|
||||||
|
channelStat.Set("mean", it->mean);
|
||||||
|
channelStat.Set("stdev", it->stdev);
|
||||||
|
channelStat.Set("minX", it->minX);
|
||||||
|
channelStat.Set("minY", it->minY);
|
||||||
|
channelStat.Set("maxX", it->maxX);
|
||||||
|
channelStat.Set("maxY", it->maxY);
|
||||||
|
channels.Set(i, channelStat);
|
||||||
|
}
|
||||||
|
|
||||||
|
info.Set("channels", channels);
|
||||||
|
info.Set("isOpaque", baton->isOpaque);
|
||||||
|
info.Set("entropy", baton->entropy);
|
||||||
|
info.Set("sharpness", baton->sharpness);
|
||||||
|
Napi::Object dominant = Napi::Object::New(env);
|
||||||
|
dominant.Set("r", baton->dominantRed);
|
||||||
|
dominant.Set("g", baton->dominantGreen);
|
||||||
|
dominant.Set("b", baton->dominantBlue);
|
||||||
|
info.Set("dominant", dominant);
|
||||||
|
Callback().MakeCallback(Receiver().Value(), { env.Null(), info });
|
||||||
|
} else {
|
||||||
|
Callback().MakeCallback(Receiver().Value(), { Napi::Error::New(env, sharp::TrimEnd(baton->err)).Value() });
|
||||||
|
}
|
||||||
|
|
||||||
|
delete baton->input;
|
||||||
|
delete baton;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
StatsBaton* baton;
|
||||||
|
Napi::FunctionReference debuglog;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
stats(options, callback)
|
||||||
|
*/
|
||||||
|
Napi::Value stats(const Napi::CallbackInfo& info) {
|
||||||
|
// V8 objects are converted to non-V8 types held in the baton struct
|
||||||
|
StatsBaton *baton = new StatsBaton;
|
||||||
|
Napi::Object options = info[size_t(0)].As<Napi::Object>();
|
||||||
|
|
||||||
|
// Input
|
||||||
|
baton->input = sharp::CreateInputDescriptor(options.Get("input").As<Napi::Object>());
|
||||||
|
baton->input->access = VIPS_ACCESS_RANDOM;
|
||||||
|
|
||||||
|
// Function to notify of libvips warnings
|
||||||
|
Napi::Function debuglog = options.Get("debuglog").As<Napi::Function>();
|
||||||
|
|
||||||
|
// Join queue for worker thread
|
||||||
|
Napi::Function callback = info[size_t(1)].As<Napi::Function>();
|
||||||
|
StatsWorker *worker = new StatsWorker(callback, baton, debuglog);
|
||||||
|
worker->Receiver().Set("options", options);
|
||||||
|
worker->Queue();
|
||||||
|
|
||||||
|
// Increment queued task counter
|
||||||
|
sharp::counterQueue++;
|
||||||
|
|
||||||
|
return info.Env().Undefined();
|
||||||
|
}
|
||||||
59
src/stats.h
Normal file
59
src/stats.h
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
#ifndef SRC_STATS_H_
|
||||||
|
#define SRC_STATS_H_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <napi.h>
|
||||||
|
|
||||||
|
#include "./common.h"
|
||||||
|
|
||||||
|
struct ChannelStats {
|
||||||
|
// stats per channel
|
||||||
|
int min;
|
||||||
|
int max;
|
||||||
|
double sum;
|
||||||
|
double squaresSum;
|
||||||
|
double mean;
|
||||||
|
double stdev;
|
||||||
|
int minX;
|
||||||
|
int minY;
|
||||||
|
int maxX;
|
||||||
|
int maxY;
|
||||||
|
|
||||||
|
ChannelStats(int minVal, int maxVal, double sumVal, double squaresSumVal,
|
||||||
|
double meanVal, double stdevVal, int minXVal, int minYVal, int maxXVal, int maxYVal):
|
||||||
|
min(minVal), max(maxVal), sum(sumVal), squaresSum(squaresSumVal),
|
||||||
|
mean(meanVal), stdev(stdevVal), minX(minXVal), minY(minYVal), maxX(maxXVal), maxY(maxYVal) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StatsBaton {
|
||||||
|
// Input
|
||||||
|
sharp::InputDescriptor *input;
|
||||||
|
|
||||||
|
// Output
|
||||||
|
std::vector<ChannelStats> channelStats;
|
||||||
|
bool isOpaque;
|
||||||
|
double entropy;
|
||||||
|
double sharpness;
|
||||||
|
int dominantRed;
|
||||||
|
int dominantGreen;
|
||||||
|
int dominantBlue;
|
||||||
|
|
||||||
|
std::string err;
|
||||||
|
|
||||||
|
StatsBaton():
|
||||||
|
input(nullptr),
|
||||||
|
isOpaque(true),
|
||||||
|
entropy(0.0),
|
||||||
|
sharpness(0.0),
|
||||||
|
dominantRed(0),
|
||||||
|
dominantGreen(0),
|
||||||
|
dominantBlue(0)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
Napi::Value stats(const Napi::CallbackInfo& info);
|
||||||
|
|
||||||
|
#endif // SRC_STATS_H_
|
||||||
267
src/utilities.cc
Executable file → Normal file
267
src/utilities.cc
Executable file → Normal file
@@ -1,75 +1,254 @@
|
|||||||
#include <node.h>
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
#include <vips/vips.h>
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
#include "nan.h"
|
#include <cmath>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <napi.h>
|
||||||
|
#include <vips/vips8>
|
||||||
|
#include <vips/vector.h>
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
#include "operations.h"
|
||||||
#include "utilities.h"
|
#include "utilities.h"
|
||||||
|
|
||||||
using namespace v8;
|
|
||||||
using namespace sharp;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Get and set cache memory and item limits
|
Get and set cache limits
|
||||||
*/
|
*/
|
||||||
NAN_METHOD(cache) {
|
Napi::Value cache(const Napi::CallbackInfo& info) {
|
||||||
NanScope();
|
Napi::Env env = info.Env();
|
||||||
|
|
||||||
// Set cache memory limit
|
// Set memory limit
|
||||||
if (args[0]->IsInt32()) {
|
if (info[size_t(0)].IsNumber()) {
|
||||||
int newMax = args[0]->Int32Value() * 1048576;
|
vips_cache_set_max_mem(info[size_t(0)].As<Napi::Number>().Int32Value() * 1048576);
|
||||||
int oldMax = vips_cache_get_max_mem();
|
}
|
||||||
vips_cache_set_max_mem(newMax);
|
// Set file limit
|
||||||
|
if (info[size_t(1)].IsNumber()) {
|
||||||
// Notify the V8 garbage collector of delta in max cache size
|
vips_cache_set_max_files(info[size_t(1)].As<Napi::Number>().Int32Value());
|
||||||
NanAdjustExternalMemory(newMax - oldMax);
|
}
|
||||||
|
// Set items limit
|
||||||
|
if (info[size_t(2)].IsNumber()) {
|
||||||
|
vips_cache_set_max(info[size_t(2)].As<Napi::Number>().Int32Value());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set cache items limit
|
// Get memory stats
|
||||||
if (args[1]->IsInt32()) {
|
Napi::Object memory = Napi::Object::New(env);
|
||||||
vips_cache_set_max(args[1]->Int32Value());
|
memory.Set("current", round(vips_tracked_get_mem() / 1048576));
|
||||||
}
|
memory.Set("high", round(vips_tracked_get_mem_highwater() / 1048576));
|
||||||
|
memory.Set("max", round(vips_cache_get_max_mem() / 1048576));
|
||||||
|
// Get file stats
|
||||||
|
Napi::Object files = Napi::Object::New(env);
|
||||||
|
files.Set("current", vips_tracked_get_files());
|
||||||
|
files.Set("max", vips_cache_get_max_files());
|
||||||
|
|
||||||
// Get cache statistics
|
// Get item stats
|
||||||
Local<Object> cache = NanNew<Object>();
|
Napi::Object items = Napi::Object::New(env);
|
||||||
cache->Set(NanNew<String>("current"), NanNew<Number>(vips_tracked_get_mem() / 1048576));
|
items.Set("current", vips_cache_get_size());
|
||||||
cache->Set(NanNew<String>("high"), NanNew<Number>(vips_tracked_get_mem_highwater() / 1048576));
|
items.Set("max", vips_cache_get_max());
|
||||||
cache->Set(NanNew<String>("memory"), NanNew<Number>(vips_cache_get_max_mem() / 1048576));
|
|
||||||
cache->Set(NanNew<String>("items"), NanNew<Number>(vips_cache_get_max()));
|
Napi::Object cache = Napi::Object::New(env);
|
||||||
NanReturnValue(cache);
|
cache.Set("memory", memory);
|
||||||
|
cache.Set("files", files);
|
||||||
|
cache.Set("items", items);
|
||||||
|
return cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Get and set size of thread pool
|
Get and set size of thread pool
|
||||||
*/
|
*/
|
||||||
NAN_METHOD(concurrency) {
|
Napi::Value concurrency(const Napi::CallbackInfo& info) {
|
||||||
NanScope();
|
|
||||||
|
|
||||||
// Set concurrency
|
// Set concurrency
|
||||||
if (args[0]->IsInt32()) {
|
if (info[size_t(0)].IsNumber()) {
|
||||||
vips_concurrency_set(args[0]->Int32Value());
|
vips_concurrency_set(info[size_t(0)].As<Napi::Number>().Int32Value());
|
||||||
}
|
}
|
||||||
// Get concurrency
|
// Get concurrency
|
||||||
NanReturnValue(NanNew<Number>(vips_concurrency_get()));
|
return Napi::Number::New(info.Env(), vips_concurrency_get());
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Get internal counters (queued tasks, processing tasks)
|
Get internal counters (queued tasks, processing tasks)
|
||||||
*/
|
*/
|
||||||
NAN_METHOD(counters) {
|
Napi::Value counters(const Napi::CallbackInfo& info) {
|
||||||
NanScope();
|
Napi::Object counters = Napi::Object::New(info.Env());
|
||||||
Local<Object> counters = NanNew<Object>();
|
counters.Set("queue", static_cast<int>(sharp::counterQueue));
|
||||||
counters->Set(NanNew<String>("queue"), NanNew<Number>(counterQueue));
|
counters.Set("process", static_cast<int>(sharp::counterProcess));
|
||||||
counters->Set(NanNew<String>("process"), NanNew<Number>(counterProcess));
|
return counters;
|
||||||
NanReturnValue(counters);
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get and set use of SIMD vector unit instructions
|
||||||
|
*/
|
||||||
|
Napi::Value simd(const Napi::CallbackInfo& info) {
|
||||||
|
// Set state
|
||||||
|
if (info[size_t(0)].IsBoolean()) {
|
||||||
|
vips_vector_set_enabled(info[size_t(0)].As<Napi::Boolean>().Value());
|
||||||
|
}
|
||||||
|
// Get state
|
||||||
|
return Napi::Boolean::New(info.Env(), vips_vector_isenabled());
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Get libvips version
|
Get libvips version
|
||||||
*/
|
*/
|
||||||
NAN_METHOD(libvipsVersion) {
|
Napi::Value libvipsVersion(const Napi::CallbackInfo& info) {
|
||||||
NanScope();
|
|
||||||
char version[9];
|
char version[9];
|
||||||
snprintf(version, 9, "%d.%d.%d", vips_version(0), vips_version(1), vips_version(2));
|
g_snprintf(version, sizeof(version), "%d.%d.%d", vips_version(0), vips_version(1), vips_version(2));
|
||||||
NanReturnValue(NanNew<String>(version));
|
return Napi::String::New(info.Env(), version);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get available input/output file/buffer/stream formats
|
||||||
|
*/
|
||||||
|
Napi::Value format(const Napi::CallbackInfo& info) {
|
||||||
|
Napi::Env env = info.Env();
|
||||||
|
Napi::Object format = Napi::Object::New(env);
|
||||||
|
for (std::string const f : {
|
||||||
|
"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz",
|
||||||
|
"ppm", "fits", "gif", "svg", "heif", "pdf", "vips", "jp2k", "jxl"
|
||||||
|
}) {
|
||||||
|
// Input
|
||||||
|
const VipsObjectClass *oc = vips_class_find("VipsOperation", (f + "load").c_str());
|
||||||
|
Napi::Boolean hasInputFile = Napi::Boolean::New(env, oc);
|
||||||
|
Napi::Boolean hasInputBuffer =
|
||||||
|
Napi::Boolean::New(env, vips_type_find("VipsOperation", (f + "load_buffer").c_str()));
|
||||||
|
Napi::Object input = Napi::Object::New(env);
|
||||||
|
input.Set("file", hasInputFile);
|
||||||
|
input.Set("buffer", hasInputBuffer);
|
||||||
|
input.Set("stream", hasInputBuffer);
|
||||||
|
if (hasInputFile) {
|
||||||
|
const VipsForeignClass *fc = VIPS_FOREIGN_CLASS(oc);
|
||||||
|
if (fc->suffs) {
|
||||||
|
Napi::Array fileSuffix = Napi::Array::New(env);
|
||||||
|
const char **suffix = fc->suffs;
|
||||||
|
for (int i = 0; *suffix; i++, suffix++) {
|
||||||
|
fileSuffix.Set(i, Napi::String::New(env, *suffix));
|
||||||
|
}
|
||||||
|
input.Set("fileSuffix", fileSuffix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Output
|
||||||
|
Napi::Boolean hasOutputFile =
|
||||||
|
Napi::Boolean::New(env, vips_type_find("VipsOperation", (f + "save").c_str()));
|
||||||
|
Napi::Boolean hasOutputBuffer =
|
||||||
|
Napi::Boolean::New(env, vips_type_find("VipsOperation", (f + "save_buffer").c_str()));
|
||||||
|
Napi::Object output = Napi::Object::New(env);
|
||||||
|
output.Set("file", hasOutputFile);
|
||||||
|
output.Set("buffer", hasOutputBuffer);
|
||||||
|
output.Set("stream", hasOutputBuffer);
|
||||||
|
// Other attributes
|
||||||
|
Napi::Object container = Napi::Object::New(env);
|
||||||
|
container.Set("id", f);
|
||||||
|
container.Set("input", input);
|
||||||
|
container.Set("output", output);
|
||||||
|
// Add to set of formats
|
||||||
|
format.Set(f, container);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Raw, uncompressed data
|
||||||
|
Napi::Boolean supported = Napi::Boolean::New(env, true);
|
||||||
|
Napi::Boolean unsupported = Napi::Boolean::New(env, false);
|
||||||
|
Napi::Object rawInput = Napi::Object::New(env);
|
||||||
|
rawInput.Set("file", unsupported);
|
||||||
|
rawInput.Set("buffer", supported);
|
||||||
|
rawInput.Set("stream", supported);
|
||||||
|
Napi::Object rawOutput = Napi::Object::New(env);
|
||||||
|
rawOutput.Set("file", unsupported);
|
||||||
|
rawOutput.Set("buffer", supported);
|
||||||
|
rawOutput.Set("stream", supported);
|
||||||
|
Napi::Object raw = Napi::Object::New(env);
|
||||||
|
raw.Set("id", "raw");
|
||||||
|
raw.Set("input", rawInput);
|
||||||
|
raw.Set("output", rawOutput);
|
||||||
|
format.Set("raw", raw);
|
||||||
|
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
(Un)block libvips operations at runtime.
|
||||||
|
*/
|
||||||
|
void block(const Napi::CallbackInfo& info) {
|
||||||
|
Napi::Array ops = info[size_t(0)].As<Napi::Array>();
|
||||||
|
bool const state = info[size_t(1)].As<Napi::Boolean>().Value();
|
||||||
|
for (unsigned int i = 0; i < ops.Length(); i++) {
|
||||||
|
vips_operation_block_set(ops.Get(i).As<Napi::String>().Utf8Value().c_str(), state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Synchronous, internal-only method used by some of the functional tests.
|
||||||
|
Calculates the maximum colour distance using the DE2000 algorithm
|
||||||
|
between two images of the same dimensions and number of channels.
|
||||||
|
*/
|
||||||
|
Napi::Value _maxColourDistance(const Napi::CallbackInfo& info) {
|
||||||
|
Napi::Env env = info.Env();
|
||||||
|
|
||||||
|
// Open input files
|
||||||
|
VImage image1;
|
||||||
|
sharp::ImageType imageType1 = sharp::DetermineImageType(info[size_t(0)].As<Napi::String>().Utf8Value().data());
|
||||||
|
if (imageType1 != sharp::ImageType::UNKNOWN) {
|
||||||
|
try {
|
||||||
|
image1 = VImage::new_from_file(info[size_t(0)].As<Napi::String>().Utf8Value().c_str());
|
||||||
|
} catch (...) {
|
||||||
|
throw Napi::Error::New(env, "Input file 1 has corrupt header");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw Napi::Error::New(env, "Input file 1 is of an unsupported image format");
|
||||||
|
}
|
||||||
|
VImage image2;
|
||||||
|
sharp::ImageType imageType2 = sharp::DetermineImageType(info[size_t(1)].As<Napi::String>().Utf8Value().data());
|
||||||
|
if (imageType2 != sharp::ImageType::UNKNOWN) {
|
||||||
|
try {
|
||||||
|
image2 = VImage::new_from_file(info[size_t(1)].As<Napi::String>().Utf8Value().c_str());
|
||||||
|
} catch (...) {
|
||||||
|
throw Napi::Error::New(env, "Input file 2 has corrupt header");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw Napi::Error::New(env, "Input file 2 is of an unsupported image format");
|
||||||
|
}
|
||||||
|
// Ensure same number of channels
|
||||||
|
if (image1.bands() != image2.bands()) {
|
||||||
|
throw Napi::Error::New(env, "mismatchedBands");
|
||||||
|
}
|
||||||
|
// Ensure same dimensions
|
||||||
|
if (image1.width() != image2.width() || image1.height() != image2.height()) {
|
||||||
|
throw Napi::Error::New(env, "mismatchedDimensions");
|
||||||
|
}
|
||||||
|
|
||||||
|
double maxColourDistance;
|
||||||
|
try {
|
||||||
|
// Premultiply and remove alpha
|
||||||
|
if (sharp::HasAlpha(image1)) {
|
||||||
|
image1 = image1.premultiply().extract_band(1, VImage::option()->set("n", image1.bands() - 1));
|
||||||
|
}
|
||||||
|
if (sharp::HasAlpha(image2)) {
|
||||||
|
image2 = image2.premultiply().extract_band(1, VImage::option()->set("n", image2.bands() - 1));
|
||||||
|
}
|
||||||
|
// Calculate colour distance
|
||||||
|
maxColourDistance = image1.dE00(image2).max();
|
||||||
|
} catch (vips::VError const &err) {
|
||||||
|
throw Napi::Error::New(env, err.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up libvips' per-request data and threads
|
||||||
|
vips_error_clear();
|
||||||
|
vips_thread_shutdown();
|
||||||
|
|
||||||
|
return Napi::Number::New(env, maxColourDistance);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(__GNUC__)
|
||||||
|
// mallctl will be resolved by the runtime linker when jemalloc is being used
|
||||||
|
extern "C" {
|
||||||
|
int mallctl(const char *name, void *oldp, size_t *oldlenp, void *newp, size_t newlen) __attribute__((weak));
|
||||||
|
}
|
||||||
|
Napi::Value _isUsingJemalloc(const Napi::CallbackInfo& info) {
|
||||||
|
Napi::Env env = info.Env();
|
||||||
|
return Napi::Boolean::New(env, mallctl != nullptr);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
Napi::Value _isUsingJemalloc(const Napi::CallbackInfo& info) {
|
||||||
|
Napi::Env env = info.Env();
|
||||||
|
return Napi::Boolean::New(env, false);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|||||||
24
src/utilities.h
Executable file → Normal file
24
src/utilities.h
Executable file → Normal file
@@ -1,11 +1,19 @@
|
|||||||
#ifndef SHARP_UTILITIES_H
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
#define SHARP_UTILITIES_H
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
#include "nan.h"
|
#ifndef SRC_UTILITIES_H_
|
||||||
|
#define SRC_UTILITIES_H_
|
||||||
|
|
||||||
NAN_METHOD(cache);
|
#include <napi.h>
|
||||||
NAN_METHOD(concurrency);
|
|
||||||
NAN_METHOD(counters);
|
|
||||||
NAN_METHOD(libvipsVersion);
|
|
||||||
|
|
||||||
#endif
|
Napi::Value cache(const Napi::CallbackInfo& info);
|
||||||
|
Napi::Value concurrency(const Napi::CallbackInfo& info);
|
||||||
|
Napi::Value counters(const Napi::CallbackInfo& info);
|
||||||
|
Napi::Value simd(const Napi::CallbackInfo& info);
|
||||||
|
Napi::Value libvipsVersion(const Napi::CallbackInfo& info);
|
||||||
|
Napi::Value format(const Napi::CallbackInfo& info);
|
||||||
|
void block(const Napi::CallbackInfo& info);
|
||||||
|
Napi::Value _maxColourDistance(const Napi::CallbackInfo& info);
|
||||||
|
Napi::Value _isUsingJemalloc(const Napi::CallbackInfo& info);
|
||||||
|
|
||||||
|
#endif // SRC_UTILITIES_H_
|
||||||
|
|||||||
24
test/beforeEach.js
Normal file
24
test/beforeEach.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const sharp = require('../');
|
||||||
|
|
||||||
|
const usingCache = !process.env.G_DEBUG;
|
||||||
|
const usingSimd = !process.env.VIPS_NOVECTOR;
|
||||||
|
const concurrency = Number(process.env.VIPS_CONCURRENCY) || 0;
|
||||||
|
|
||||||
|
exports.mochaHooks = {
|
||||||
|
beforeEach () {
|
||||||
|
sharp.cache(usingCache);
|
||||||
|
sharp.simd(usingSimd);
|
||||||
|
sharp.concurrency(concurrency);
|
||||||
|
},
|
||||||
|
|
||||||
|
afterEach () {
|
||||||
|
if (global.gc) {
|
||||||
|
global.gc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
31
test/bench/Dockerfile
Normal file
31
test/bench/Dockerfile
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
FROM ubuntu:22.04
|
||||||
|
ARG BRANCH=main
|
||||||
|
|
||||||
|
# Install basic dependencies
|
||||||
|
RUN apt-get -y update && apt-get install -y build-essential curl git
|
||||||
|
|
||||||
|
# Install latest Node.js LTS
|
||||||
|
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash -
|
||||||
|
RUN apt-get install -y nodejs
|
||||||
|
|
||||||
|
# Install benchmark dependencies
|
||||||
|
RUN apt-get install -y imagemagick libmagick++-dev graphicsmagick
|
||||||
|
|
||||||
|
# Install sharp
|
||||||
|
RUN mkdir /tmp/sharp
|
||||||
|
RUN cd /tmp && git clone --single-branch --branch $BRANCH https://github.com/lovell/sharp.git
|
||||||
|
RUN cd /tmp/sharp && npm install --build-from-source
|
||||||
|
|
||||||
|
# Install benchmark test
|
||||||
|
RUN cd /tmp/sharp/test/bench && npm install --omit optional
|
||||||
|
|
||||||
|
RUN cat /etc/os-release | grep VERSION=
|
||||||
|
RUN node -v
|
||||||
|
|
||||||
|
WORKDIR /tmp/sharp/test/bench
|
||||||
|
|
||||||
|
# Workaround for: https://github.com/emscripten-core/emscripten/pull/16917
|
||||||
|
# This could be removed once Squoosh is an optional dependency.
|
||||||
|
ENV NODE_OPTIONS="--no-experimental-fetch"
|
||||||
|
|
||||||
|
CMD [ "node", "perf" ]
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user