mirror of
https://github.com/lovell/sharp.git
synced 2026-02-04 13:46:19 +01:00
Compare commits
749 Commits
v0.15.0
...
v0.27.0-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
15
.cirrus.yml
Normal file
15
.cirrus.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
freebsd_instance:
|
||||
image_family: freebsd-13-0-snap
|
||||
|
||||
task:
|
||||
name: FreeBSD 13.0
|
||||
env:
|
||||
IGNORE_OSVERSION: yes
|
||||
prerequisites_script:
|
||||
- pkg update -f
|
||||
- pkg upgrade -y
|
||||
- pkg install -y pkgconf vips node npm
|
||||
install_script:
|
||||
- npm install --build-from-source --unsafe-perm
|
||||
test_script:
|
||||
- npm test
|
||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
src/libvips/* linguist-vendored
|
||||
49
CONTRIBUTING.md → .github/CONTRIBUTING.md
vendored
49
CONTRIBUTING.md → .github/CONTRIBUTING.md
vendored
@@ -12,9 +12,12 @@ 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.
|
||||
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](http://www.vips.ecs.soton.ac.uk/supported/current/doc/html/libvips/ch03.html) the feature you need.
|
||||
Implementation is usually straightforward if libvips
|
||||
[already supports](https://libvips.github.io/libvips/API/current/func-list.html)
|
||||
the feature you need.
|
||||
|
||||
## Submit a Pull Request to fix a bug
|
||||
|
||||
@@ -39,20 +42,15 @@ You deserve to add your details to the [list of contributors](https://github.com
|
||||
|
||||
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.
|
||||
|
||||
| Release | WIP branch |
|
||||
| ------: | :--------- |
|
||||
| v0.16.0 | pencil |
|
||||
| v0.17.0 | quill |
|
||||
|
||||
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_.
|
||||
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. Methods with mandatory parameters often have names like `doSomethingWith(X)`.
|
||||
|
||||
Please ensure backwards compatibility where possible. Methods to modify previously default behaviour often have names like `withoutOptionY()` or `withExtraZ()`.
|
||||
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.
|
||||
|
||||
@@ -60,7 +58,18 @@ Feel free to create a [new issue](https://github.com/lovell/sharp/issues/new) to
|
||||
|
||||
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 [bilinearInterpolation method](https://github.com/lovell/sharp/blob/v0.6.0/index.js#L155) present in v0.5.0 was deprecated in v0.6.0 and removed in v0.7.0.
|
||||
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
|
||||
|
||||
@@ -78,15 +87,11 @@ Requires [Valgrind](http://valgrind.org/).
|
||||
npm run test-leak
|
||||
```
|
||||
|
||||
### Packaging tests
|
||||
|
||||
Tests the installation on a number of Linux-based operating systems.
|
||||
Requires docker.
|
||||
|
||||
```sh
|
||||
npm run test-packaging
|
||||
```
|
||||
|
||||
## Finally
|
||||
|
||||
Please feel free to ask any questions via a [new issue](https://github.com/lovell/sharp/issues/new) or contact me by [e-mail](https://github.com/lovell/sharp/blob/master/package.json#L4).
|
||||
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/master/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
|
||||
16
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
16
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea
|
||||
labels: enhancement
|
||||
|
||||
---
|
||||
|
||||
What are you trying to achieve?
|
||||
|
||||
Have you searched for similar feature requests?
|
||||
|
||||
What would you expect the API to look like?
|
||||
|
||||
What alternatives have you considered?
|
||||
|
||||
Is there a sample image that helps explain?
|
||||
20
.github/ISSUE_TEMPLATE/installation.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/installation.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Installation
|
||||
about: Something went wrong during either 'npm install sharp' or 'require("sharp")'
|
||||
labels: installation
|
||||
|
||||
---
|
||||
|
||||
Did you see the [documentation relating to installation](https://sharp.pixelplumbing.com/install)?
|
||||
|
||||
Have you ensured the architecture and platform of Node.js used for `npm install` is the same as the architecture and platform of Node.js used at runtime?
|
||||
|
||||
Are you using the latest version? Is the version currently in use as reported by `npm ls sharp` the same as the latest version as reported by `npm view sharp dist-tags.latest`?
|
||||
|
||||
If you are installing as a `root` or `sudo` user, have you tried with the `npm install --unsafe-perm` flag?
|
||||
|
||||
If you are using the `ignore-scripts` feature of `npm`, have you tried with the `npm install --ignore-scripts=false` flag?
|
||||
|
||||
What is the complete output of running `npm install --verbose sharp`? Have you checked this output for useful error messages?
|
||||
|
||||
What is the output of running `npx envinfo --binaries --system`?
|
||||
20
.github/ISSUE_TEMPLATE/possible-bug.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/possible-bug.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
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. -->
|
||||
|
||||
Are you using the latest version? Is the version currently in use as reported by `npm ls sharp` the same as the latest version as reported by `npm view sharp dist-tags.latest`?
|
||||
|
||||
What are the steps to reproduce?
|
||||
|
||||
What is the expected behaviour?
|
||||
|
||||
Are you able to provide a minimal, standalone code sample, without other dependencies, that demonstrates this problem?
|
||||
|
||||
Are you able to provide a sample image that helps explain the problem?
|
||||
|
||||
What is the output of running `npx envinfo --binaries --system`?
|
||||
16
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
16
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
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. -->
|
||||
|
||||
What are you trying to achieve?
|
||||
|
||||
Have you searched for similar questions?
|
||||
|
||||
Are you able to provide a minimal, standalone code sample that demonstrates this question?
|
||||
|
||||
Are you able to provide a sample image that helps explain the question?
|
||||
85
.github/workflows/ci.yml
vendored
Normal file
85
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
jobs:
|
||||
CI:
|
||||
runs-on: ${{ matrix.os }}
|
||||
container: ${{ matrix.container }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-20.04
|
||||
container: centos:7
|
||||
nodejs_version: 10
|
||||
coverage: true
|
||||
prebuild: true
|
||||
- os: ubuntu-20.04
|
||||
container: centos:7
|
||||
nodejs_version: 12
|
||||
- os: ubuntu-20.04
|
||||
container: centos:7
|
||||
nodejs_version: 14
|
||||
- os: ubuntu-20.04
|
||||
container: centos:7
|
||||
nodejs_version: 15
|
||||
- os: ubuntu-20.04
|
||||
container: node:10-alpine3.11
|
||||
prebuild: true
|
||||
- os: ubuntu-20.04
|
||||
container: node:12-alpine3.11
|
||||
- os: ubuntu-20.04
|
||||
container: node:14-alpine3.11
|
||||
- os: ubuntu-20.04
|
||||
container: node:15-alpine3.11
|
||||
- os: macos-10.15
|
||||
nodejs_version: 10
|
||||
prebuild: true
|
||||
- os: macos-10.15
|
||||
nodejs_version: 12
|
||||
- os: macos-10.15
|
||||
nodejs_version: 14
|
||||
- os: macos-10.15
|
||||
nodejs_version: 15
|
||||
- os: windows-2019
|
||||
nodejs_version: 10
|
||||
prebuild: true
|
||||
- os: windows-2019
|
||||
nodejs_version: 12
|
||||
- os: windows-2019
|
||||
nodejs_version: 14
|
||||
- os: windows-2019
|
||||
nodejs_version: 15
|
||||
steps:
|
||||
- name: Dependencies (Linux glibc)
|
||||
if: contains(matrix.container, 'centos')
|
||||
run: |
|
||||
curl -sL https://rpm.nodesource.com/setup_${{ matrix.nodejs_version }}.x | bash -
|
||||
yum install -y gcc-c++ make git python3 nodejs
|
||||
- name: Dependencies (Linux musl)
|
||||
if: contains(matrix.container, 'alpine')
|
||||
run: apk add build-base git python3 --update-cache
|
||||
- name: Dependencies (macOS, Windows)
|
||||
if: contains(matrix.os, 'macos') || contains(matrix.os, 'windows')
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.nodejs_version }}
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Fix working directory ownership
|
||||
if: matrix.container
|
||||
run: chown root.root .
|
||||
- name: Install
|
||||
run: npm install --build-from-source --unsafe-perm
|
||||
- name: Test
|
||||
run: npm test
|
||||
- name: Coverage
|
||||
if: matrix.coverage
|
||||
uses: coverallsapp/github-action@v1.1.2
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Prebuild
|
||||
if: matrix.prebuild && startsWith(github.ref, 'refs/tags/')
|
||||
env:
|
||||
prebuild_upload: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: npx prebuild --runtime napi --target 3
|
||||
17
.gitignore
vendored
17
.gitignore
vendored
@@ -1,11 +1,18 @@
|
||||
build
|
||||
node_modules
|
||||
coverage
|
||||
/coverage
|
||||
test/bench/node_modules
|
||||
test/fixtures/output*
|
||||
test/leak/libvips.supp
|
||||
lib
|
||||
include
|
||||
packaging/libvips*
|
||||
packaging/*.log
|
||||
test/saliency/report.json
|
||||
test/saliency/Image*
|
||||
test/saliency/[Uu]serData*
|
||||
!test/saliency/userData.js
|
||||
vendor
|
||||
.gitattributes
|
||||
.DS_Store
|
||||
.nyc_output
|
||||
.vscode/
|
||||
package-lock.json
|
||||
.idea
|
||||
.firebase
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
node_modules
|
||||
test/bench/node_modules
|
||||
coverage
|
||||
12
.jshintrc
12
.jshintrc
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"strict": true,
|
||||
"node": true,
|
||||
"maxparams": 4,
|
||||
"maxcomplexity": 13,
|
||||
"globals": {
|
||||
"beforeEach": true,
|
||||
"afterEach": true,
|
||||
"describe": true,
|
||||
"it": true
|
||||
}
|
||||
}
|
||||
16
.npmignore
16
.npmignore
@@ -1,16 +0,0 @@
|
||||
build
|
||||
node_modules
|
||||
coverage
|
||||
.editorconfig
|
||||
.jshintignore
|
||||
.jshintrc
|
||||
.gitignore
|
||||
test
|
||||
.travis.yml
|
||||
appveyor.yml
|
||||
circle.yml
|
||||
mkdocs.yml
|
||||
lib
|
||||
include
|
||||
packaging
|
||||
preinstall.sh
|
||||
4
.prebuildrc
Normal file
4
.prebuildrc
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"include-regex": "(sharp\\.node|libvips-cpp\\.dll)",
|
||||
"strip": true
|
||||
}
|
||||
85
.travis.yml
85
.travis.yml
@@ -1,23 +1,62 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
- "0.12"
|
||||
- "4"
|
||||
- "5"
|
||||
- "6"
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
sudo: false
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-4.8
|
||||
osx_image: xcode7.3
|
||||
before_install:
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export CXX=g++-4.8; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install homebrew/science/vips; fi
|
||||
after_success:
|
||||
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
|
||||
jobs:
|
||||
include:
|
||||
- name: "Linux ARM64v8 (Debian 11, glibc 2.29) - Node.js 10"
|
||||
arch: arm64
|
||||
os: linux
|
||||
dist: bionic
|
||||
language: shell
|
||||
before_install:
|
||||
- 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"
|
||||
- sudo docker exec sharp sh -c "curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -"
|
||||
- sudo docker exec sharp sh -c "echo 'deb https://deb.nodesource.com/node_10.x sid main' >/etc/apt/sources.list.d/nodesource.list"
|
||||
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs=10.*"
|
||||
install: sudo docker exec sharp sh -c "npm install --build-from-source --unsafe-perm"
|
||||
script: sudo docker exec sharp sh -c "npm test"
|
||||
after_success: "[[ -n $TRAVIS_TAG ]] && sudo docker exec --env prebuild_upload sharp sh -c \"npx prebuild --runtime napi --target 3\""
|
||||
|
||||
- name: "Linux ARM64v8 (Debian 11, glibc 2.29) - Node.js 12"
|
||||
arch: arm64
|
||||
os: linux
|
||||
dist: bionic
|
||||
language: shell
|
||||
before_install:
|
||||
- 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"
|
||||
- sudo docker exec sharp sh -c "curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -"
|
||||
- sudo docker exec sharp sh -c "echo 'deb https://deb.nodesource.com/node_12.x sid main' >/etc/apt/sources.list.d/nodesource.list"
|
||||
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs"
|
||||
install: sudo docker exec sharp sh -c "npm install --build-from-source --unsafe-perm"
|
||||
script: sudo docker exec sharp sh -c "npm test"
|
||||
|
||||
- name: "Linux ARM64v8 (Debian 11, glibc 2.29) - Node.js 14"
|
||||
arch: arm64
|
||||
os: linux
|
||||
dist: bionic
|
||||
language: shell
|
||||
before_install:
|
||||
- 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"
|
||||
- sudo docker exec sharp sh -c "curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -"
|
||||
- sudo docker exec sharp sh -c "echo 'deb https://deb.nodesource.com/node_14.x sid main' >/etc/apt/sources.list.d/nodesource.list"
|
||||
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs"
|
||||
install: sudo docker exec sharp sh -c "npm install --build-from-source --unsafe-perm"
|
||||
script: sudo docker exec sharp sh -c "npm test"
|
||||
|
||||
- name: "Linux ARM64v8 (Debian 11, glibc 2.29) - Node.js 15"
|
||||
arch: arm64
|
||||
os: linux
|
||||
dist: bionic
|
||||
language: shell
|
||||
before_install:
|
||||
- sudo chown 0.0 ${PWD}
|
||||
- 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"
|
||||
- sudo docker exec sharp sh -c "curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -"
|
||||
- sudo docker exec sharp sh -c "echo 'deb https://deb.nodesource.com/node_15.x sid main' >/etc/apt/sources.list.d/nodesource.list"
|
||||
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs"
|
||||
install: sudo docker exec sharp sh -c "npm install --build-from-source --unsafe-perm"
|
||||
script: sudo docker exec sharp sh -c "npm test"
|
||||
|
||||
cache:
|
||||
npm: false
|
||||
|
||||
96
README.md
96
README.md
@@ -1,11 +1,14 @@
|
||||
# sharp
|
||||
|
||||
<img src="https://cdn.jsdelivr.net/gh/lovell/sharp@master/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 and WebP images of varying dimensions.
|
||||
|
||||
Resizing an image is typically 4x-5x faster than using the
|
||||
quickest ImageMagick and GraphicsMagick settings.
|
||||
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.
|
||||
@@ -13,35 +16,98 @@ 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 Windows (x64), Linux and ARMv6+ systems do not require
|
||||
the installation of any external runtime dependencies.
|
||||
Most modern macOS, Windows and Linux systems running Node.js v10+
|
||||
do not require any additional install or runtime dependencies.
|
||||
|
||||
Use with OS X is as simple as running `brew install homebrew/science/vips`
|
||||
to install the libvips dependency.
|
||||
## Examples
|
||||
|
||||
[](https://coveralls.io/r/lovell/sharp?branch=master)
|
||||
```sh
|
||||
npm install sharp
|
||||
```
|
||||
|
||||
```javascript
|
||||
const sharp = require('sharp');
|
||||
```
|
||||
|
||||
### Callback
|
||||
|
||||
```javascript
|
||||
sharp(inputBuffer)
|
||||
.resize(320, 240)
|
||||
.toFile('output.webp', (err, info) => { ... });
|
||||
```
|
||||
|
||||
### Promise
|
||||
|
||||
```javascript
|
||||
sharp('input.jpg')
|
||||
.rotate()
|
||||
.resize(200)
|
||||
.toBuffer()
|
||||
.then( data => { ... })
|
||||
.catch( err => { ... });
|
||||
```
|
||||
|
||||
### Async/await
|
||||
|
||||
```javascript
|
||||
const semiTransparentRedPng = await sharp({
|
||||
create: {
|
||||
width: 48,
|
||||
height: 48,
|
||||
channels: 4,
|
||||
background: { r: 255, g: 0, b: 0, alpha: 0.5 }
|
||||
}
|
||||
})
|
||||
.png()
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
### Stream
|
||||
|
||||
```javascript
|
||||
const roundedCorners = Buffer.from(
|
||||
'<svg><rect x="0" y="0" width="200" height="200" rx="50" ry="50"/></svg>'
|
||||
);
|
||||
|
||||
const roundedCornerResizer =
|
||||
sharp()
|
||||
.resize(200, 200)
|
||||
.composite([{
|
||||
input: roundedCorners,
|
||||
blend: 'dest-in'
|
||||
}])
|
||||
.png();
|
||||
|
||||
readableStream
|
||||
.pipe(roundedCornerResizer)
|
||||
.pipe(writableStream);
|
||||
```
|
||||
|
||||
[](https://coveralls.io/r/lovell/sharp?branch=master)
|
||||
[](https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api_version_matrix)
|
||||
|
||||
### Documentation
|
||||
|
||||
Visit [sharp.dimens.io](http://sharp.dimens.io/) for complete
|
||||
[installation instructions](http://sharp.dimens.io/page/install),
|
||||
[API documentation](http://sharp.dimens.io/page/api),
|
||||
[benchmark tests](http://sharp.dimens.io/page/performance) and
|
||||
[changelog](http://sharp.dimens.io/page/changelog).
|
||||
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).
|
||||
|
||||
### Contributing
|
||||
|
||||
A [guide for contributors](https://github.com/lovell/sharp/blob/master/CONTRIBUTING.md)
|
||||
A [guide for contributors](https://github.com/lovell/sharp/blob/master/.github/CONTRIBUTING.md)
|
||||
covers reporting bugs, requesting features and submitting code changes.
|
||||
|
||||
### Licence
|
||||
### Licensing
|
||||
|
||||
Copyright 2013, 2014, 2015, 2016 Lovell Fuller and contributors.
|
||||
Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
|
||||
|
||||
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
|
||||
[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0.html)
|
||||
[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,
|
||||
|
||||
22
appveyor.yml
22
appveyor.yml
@@ -1,16 +1,18 @@
|
||||
os: Visual Studio 2015
|
||||
os: Visual Studio 2019
|
||||
version: "{build}"
|
||||
build: off
|
||||
platform: x64
|
||||
platform: x86
|
||||
environment:
|
||||
VIPS_WARNING: 0
|
||||
matrix:
|
||||
- nodejs_version: "0.12"
|
||||
- nodejs_version: "4"
|
||||
- nodejs_version: "5"
|
||||
- nodejs_version: "6"
|
||||
- nodejs_version: "10"
|
||||
prebuild: true
|
||||
- nodejs_version: "12"
|
||||
- nodejs_version: "14"
|
||||
- nodejs_version: "15"
|
||||
install:
|
||||
- ps: Install-Product node $env:nodejs_version x64
|
||||
- npm install
|
||||
- ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version)
|
||||
- npm install --build-from-source
|
||||
test_script:
|
||||
- npm run-script test-win
|
||||
- npm test
|
||||
on_success:
|
||||
- if [%prebuild%] == [true] if [%APPVEYOR_REPO_TAG%] == [true] npx prebuild --runtime napi --target 3
|
||||
|
||||
290
binding.gyp
290
binding.gyp
@@ -1,37 +1,57 @@
|
||||
{
|
||||
'variables': {
|
||||
'vips_version': '<!(node -p "require(\'./lib/libvips\').minimumLibvipsVersion")',
|
||||
'sharp_vendor_dir': '<(module_root_dir)/vendor/<(vips_version)'
|
||||
},
|
||||
'targets': [{
|
||||
'target_name': 'libvips-cpp',
|
||||
'conditions': [
|
||||
['OS == "win"', {
|
||||
# Build libvips C++ binding for Windows due to MSVC std library ABI changes
|
||||
'type': 'shared_library',
|
||||
'variables': {
|
||||
'download_vips': '<!(node -e "require(\'./binding\').download_vips()")'
|
||||
},
|
||||
'defines': [
|
||||
'VIPS_CPLUSPLUS_EXPORTS',
|
||||
'_ALLOW_KEYWORD_MACROS'
|
||||
],
|
||||
'sources': [
|
||||
'src/libvips/cplusplus/VError.cpp',
|
||||
'src/libvips/cplusplus/VConnection.cpp',
|
||||
'src/libvips/cplusplus/VInterpolate.cpp',
|
||||
'src/libvips/cplusplus/VImage.cpp'
|
||||
],
|
||||
'include_dirs': [
|
||||
'<(module_root_dir)/include',
|
||||
'<(module_root_dir)/include/glib-2.0',
|
||||
'<(module_root_dir)/lib/glib-2.0/include'
|
||||
],
|
||||
'libraries': [
|
||||
'<(module_root_dir)/lib/libvips.lib',
|
||||
'<(module_root_dir)/lib/libglib-2.0.lib',
|
||||
'<(module_root_dir)/lib/libgobject-2.0.lib'
|
||||
'<(sharp_vendor_dir)/include',
|
||||
'<(sharp_vendor_dir)/include/glib-2.0',
|
||||
'<(sharp_vendor_dir)/lib/glib-2.0/include'
|
||||
],
|
||||
'link_settings': {
|
||||
'library_dirs': ['<(sharp_vendor_dir)/lib'],
|
||||
'libraries': [
|
||||
'libvips.lib',
|
||||
'libglib-2.0.lib',
|
||||
'libgobject-2.0.lib'
|
||||
],
|
||||
},
|
||||
'configurations': {
|
||||
'Release': {
|
||||
'msvs_settings': {
|
||||
'VCCLCompilerTool': {
|
||||
'ExceptionHandling': 1
|
||||
'ExceptionHandling': 1,
|
||||
'WholeProgramOptimization': 'true'
|
||||
},
|
||||
'VCLibrarianTool': {
|
||||
'AdditionalOptions': [
|
||||
'/LTCG:INCREMENTAL'
|
||||
]
|
||||
},
|
||||
'VCLinkerTool': {
|
||||
'ImageHasSafeExceptionHandlers': 'false',
|
||||
'OptimizeReferences': 2,
|
||||
'EnableCOMDATFolding': 2,
|
||||
'LinkIncremental': 1,
|
||||
'AdditionalOptions': [
|
||||
'/LTCG:INCREMENTAL'
|
||||
]
|
||||
}
|
||||
},
|
||||
'msvs_disabled_warnings': [
|
||||
@@ -46,60 +66,39 @@
|
||||
]
|
||||
}, {
|
||||
'target_name': 'sharp',
|
||||
'defines': [
|
||||
'NAPI_VERSION=3'
|
||||
],
|
||||
'dependencies': [
|
||||
'<!(node -p "require(\'node-addon-api\').gyp")',
|
||||
'libvips-cpp'
|
||||
],
|
||||
# Nested variables "pattern" borrowed from http://src.chromium.org/viewvc/chrome/trunk/src/build/common.gypi
|
||||
'variables': {
|
||||
'variables': {
|
||||
'variables': {
|
||||
'conditions': [
|
||||
['OS != "win"', {
|
||||
# Build the PKG_CONFIG_PATH environment variable with all possible combinations
|
||||
'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'
|
||||
}, {
|
||||
'pkg_config_path': ''
|
||||
}]
|
||||
],
|
||||
},
|
||||
'conditions': [
|
||||
['OS != "win"', {
|
||||
# Which version, if any, of libvips is available globally via pkg-config?
|
||||
'global_vips_version': '<!(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --modversion vips-cpp 2>/dev/null || true)'
|
||||
}, {
|
||||
'global_vips_version': ''
|
||||
}]
|
||||
],
|
||||
'pkg_config_path%': '<(pkg_config_path)'
|
||||
},
|
||||
'pkg_config_path%': '<(pkg_config_path)',
|
||||
'runtime_link%': 'shared',
|
||||
'conditions': [
|
||||
['OS != "win"', {
|
||||
# Does the globally available version of libvips, if any, meet the minimum version requirement?
|
||||
'use_global_vips': '<!(GLOBAL_VIPS_VERSION="<(global_vips_version)" node -e "require(\'./binding\').use_global_vips()")'
|
||||
'pkg_config_path': '<!(node -p "require(\'./lib/libvips\').pkgConfigPath()")',
|
||||
'use_global_libvips': '<!(node -p "Boolean(require(\'./lib/libvips\').useGlobalLibvips()).toString()")'
|
||||
}, {
|
||||
'use_global_vips': ''
|
||||
'pkg_config_path': '',
|
||||
'use_global_libvips': ''
|
||||
}]
|
||||
]
|
||||
},
|
||||
'sources': [
|
||||
'src/common.cc',
|
||||
'src/metadata.cc',
|
||||
'src/stats.cc',
|
||||
'src/operations.cc',
|
||||
'src/pipeline.cc',
|
||||
'src/sharp.cc',
|
||||
'src/utilities.cc'
|
||||
],
|
||||
'defines': [
|
||||
'_GLIBCXX_USE_CXX11_ABI=0',
|
||||
'_ALLOW_KEYWORD_MACROS'
|
||||
'src/utilities.cc',
|
||||
'src/sharp.cc'
|
||||
],
|
||||
'include_dirs': [
|
||||
'<!(node -e "require(\'nan\')")'
|
||||
'<!(node -p "require(\'node-addon-api\').include_dir")',
|
||||
],
|
||||
'conditions': [
|
||||
['use_global_vips == "true"', {
|
||||
['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)'],
|
||||
'conditions': [
|
||||
@@ -107,63 +106,66 @@
|
||||
'libraries': ['<!@(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --libs --static vips-cpp)']
|
||||
}, {
|
||||
'libraries': ['<!@(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --libs vips-cpp)']
|
||||
}],
|
||||
['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)'
|
||||
]
|
||||
}]
|
||||
]
|
||||
}, {
|
||||
# Attempt to download pre-built libvips and install locally within node_modules
|
||||
# Use pre-built libvips stored locally within node_modules
|
||||
'include_dirs': [
|
||||
'<(module_root_dir)/include',
|
||||
'<(module_root_dir)/include/glib-2.0',
|
||||
'<(module_root_dir)/lib/glib-2.0/include'
|
||||
'<(sharp_vendor_dir)/include',
|
||||
'<(sharp_vendor_dir)/include/glib-2.0',
|
||||
'<(sharp_vendor_dir)/lib/glib-2.0/include'
|
||||
],
|
||||
'conditions': [
|
||||
['OS == "win"', {
|
||||
'libraries': [
|
||||
'<(module_root_dir)/lib/libvips.lib',
|
||||
'<(module_root_dir)/lib/libglib-2.0.lib',
|
||||
'<(module_root_dir)/lib/libgobject-2.0.lib'
|
||||
]
|
||||
'defines': [
|
||||
'_ALLOW_KEYWORD_MACROS',
|
||||
'_FILE_OFFSET_BITS=64'
|
||||
],
|
||||
'link_settings': {
|
||||
'library_dirs': ['<(sharp_vendor_dir)/lib'],
|
||||
'libraries': [
|
||||
'libvips.lib',
|
||||
'libglib-2.0.lib',
|
||||
'libgobject-2.0.lib'
|
||||
]
|
||||
}
|
||||
}],
|
||||
['OS == "mac"', {
|
||||
'link_settings': {
|
||||
'library_dirs': ['<(sharp_vendor_dir)/lib'],
|
||||
'libraries': [
|
||||
'libvips-cpp.42.dylib',
|
||||
'libvips.42.dylib'
|
||||
]
|
||||
},
|
||||
'xcode_settings': {
|
||||
'OTHER_LDFLAGS': [
|
||||
# Ensure runtime linking is relative to sharp.node
|
||||
'-Wl,-rpath,\'@loader_path/../../vendor/<(vips_version)/lib\''
|
||||
]
|
||||
}
|
||||
}],
|
||||
['OS == "linux"', {
|
||||
'variables': {
|
||||
'download_vips': '<!(LDD_VERSION="<!(ldd --version 2>&1 || true)" node -e "require(\'./binding\').download_vips()")'
|
||||
},
|
||||
'libraries': [
|
||||
'<(module_root_dir)/lib/libvips-cpp.so',
|
||||
'<(module_root_dir)/lib/libvips.so',
|
||||
'<(module_root_dir)/lib/libglib-2.0.so',
|
||||
'<(module_root_dir)/lib/libgobject-2.0.so',
|
||||
# Dependencies of dependencies, included for openSUSE support
|
||||
'<(module_root_dir)/lib/libcairo.so',
|
||||
'<(module_root_dir)/lib/libcroco-0.6.so',
|
||||
'<(module_root_dir)/lib/libexif.so',
|
||||
'<(module_root_dir)/lib/libffi.so',
|
||||
'<(module_root_dir)/lib/libfontconfig.so',
|
||||
'<(module_root_dir)/lib/libfreetype.so',
|
||||
'<(module_root_dir)/lib/libgdk_pixbuf-2.0.so',
|
||||
'<(module_root_dir)/lib/libgif.so',
|
||||
'<(module_root_dir)/lib/libgio-2.0.so',
|
||||
'<(module_root_dir)/lib/libgmodule-2.0.so',
|
||||
'<(module_root_dir)/lib/libgsf-1.so',
|
||||
'<(module_root_dir)/lib/libgthread-2.0.so',
|
||||
'<(module_root_dir)/lib/libharfbuzz.so',
|
||||
'<(module_root_dir)/lib/libjpeg.so',
|
||||
'<(module_root_dir)/lib/liblcms2.so',
|
||||
'<(module_root_dir)/lib/liborc-0.4.so',
|
||||
'<(module_root_dir)/lib/libpango-1.0.so',
|
||||
'<(module_root_dir)/lib/libpangocairo-1.0.so',
|
||||
'<(module_root_dir)/lib/libpangoft2-1.0.so',
|
||||
'<(module_root_dir)/lib/libpixman-1.so',
|
||||
'<(module_root_dir)/lib/libpng.so',
|
||||
'<(module_root_dir)/lib/libpng16.so',
|
||||
'<(module_root_dir)/lib/librsvg-2.so',
|
||||
'<(module_root_dir)/lib/libtiff.so',
|
||||
'<(module_root_dir)/lib/libwebp.so',
|
||||
'<(module_root_dir)/lib/libxml2.so',
|
||||
'<(module_root_dir)/lib/libz.so',
|
||||
# Ensure runtime linking is relative to sharp.node
|
||||
'-Wl,-rpath=\'$${ORIGIN}/../../lib\''
|
||||
]
|
||||
'defines': [
|
||||
'_GLIBCXX_USE_CXX11_ABI=0'
|
||||
],
|
||||
'link_settings': {
|
||||
'library_dirs': ['<(sharp_vendor_dir)/lib'],
|
||||
'libraries': [
|
||||
'-l:libvips-cpp.so.42',
|
||||
'-l:libvips.so.42'
|
||||
],
|
||||
'ldflags': [
|
||||
# Ensure runtime linking is relative to sharp.node
|
||||
'-Wl,-s -Wl,--disable-new-dtags -Wl,-rpath=\'$$ORIGIN/../../vendor/<(vips_version)/lib\''
|
||||
]
|
||||
}
|
||||
}]
|
||||
]
|
||||
}]
|
||||
@@ -176,8 +178,7 @@
|
||||
],
|
||||
'xcode_settings': {
|
||||
'CLANG_CXX_LANGUAGE_STANDARD': 'c++11',
|
||||
'CLANG_CXX_LIBRARY': 'libc++',
|
||||
'MACOSX_DEPLOYMENT_TARGET': '10.7',
|
||||
'MACOSX_DEPLOYMENT_TARGET': '10.9',
|
||||
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
|
||||
'GCC_ENABLE_CPP_RTTI': 'YES',
|
||||
'OTHER_CPLUSPLUSFLAGS': [
|
||||
@@ -188,69 +189,44 @@
|
||||
},
|
||||
'configurations': {
|
||||
'Release': {
|
||||
'msvs_settings': {
|
||||
'VCCLCompilerTool': {
|
||||
'ExceptionHandling': 1
|
||||
}
|
||||
},
|
||||
'msvs_disabled_warnings': [
|
||||
4275
|
||||
'conditions': [
|
||||
['OS == "linux"', {
|
||||
'cflags_cc': [
|
||||
'-Wno-cast-function-type'
|
||||
]
|
||||
}],
|
||||
['target_arch == "arm"', {
|
||||
'cflags_cc': [
|
||||
'-Wno-psabi'
|
||||
]
|
||||
}],
|
||||
['OS == "win"', {
|
||||
'msvs_settings': {
|
||||
'VCCLCompilerTool': {
|
||||
'ExceptionHandling': 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': 'win_copy_dlls',
|
||||
'type': 'none',
|
||||
'dependencies': [
|
||||
'sharp'
|
||||
],
|
||||
'conditions': [
|
||||
['OS == "win"', {
|
||||
# Windows lacks support for rpath
|
||||
'copies': [{
|
||||
'destination': '<(module_root_dir)/build/Release',
|
||||
'files': [
|
||||
'<(module_root_dir)/lib/GNU.Gettext.dll',
|
||||
'<(module_root_dir)/lib/libasprintf-0.dll',
|
||||
'<(module_root_dir)/lib/libcairo-2.dll',
|
||||
'<(module_root_dir)/lib/libcairo-gobject-2.dll',
|
||||
'<(module_root_dir)/lib/libcairo-script-interpreter-2.dll',
|
||||
'<(module_root_dir)/lib/libcroco-0.6-3.dll',
|
||||
'<(module_root_dir)/lib/libexif-12.dll',
|
||||
'<(module_root_dir)/lib/libexpat-1.dll',
|
||||
'<(module_root_dir)/lib/libffi-6.dll',
|
||||
'<(module_root_dir)/lib/libfftw3-3.dll',
|
||||
'<(module_root_dir)/lib/libfontconfig-1.dll',
|
||||
'<(module_root_dir)/lib/libfreetype-6.dll',
|
||||
'<(module_root_dir)/lib/libgcc_s_seh-1.dll',
|
||||
'<(module_root_dir)/lib/libgdk_pixbuf-2.0-0.dll',
|
||||
'<(module_root_dir)/lib/libgif-4.dll',
|
||||
'<(module_root_dir)/lib/libgio-2.0-0.dll',
|
||||
'<(module_root_dir)/lib/libglib-2.0-0.dll',
|
||||
'<(module_root_dir)/lib/libgmodule-2.0-0.dll',
|
||||
'<(module_root_dir)/lib/libgobject-2.0-0.dll',
|
||||
'<(module_root_dir)/lib/libgsf-1-114.dll',
|
||||
'<(module_root_dir)/lib/libgthread-2.0-0.dll',
|
||||
'<(module_root_dir)/lib/libintl-8.dll',
|
||||
'<(module_root_dir)/lib/libjpeg-62.dll',
|
||||
'<(module_root_dir)/lib/liblcms2-2.dll',
|
||||
'<(module_root_dir)/lib/libpango-1.0-0.dll',
|
||||
'<(module_root_dir)/lib/libpangocairo-1.0-0.dll',
|
||||
'<(module_root_dir)/lib/libpangowin32-1.0-0.dll',
|
||||
'<(module_root_dir)/lib/libpixman-1-0.dll',
|
||||
'<(module_root_dir)/lib/libpng16-16.dll',
|
||||
'<(module_root_dir)/lib/libquadmath-0.dll',
|
||||
'<(module_root_dir)/lib/librsvg-2-2.dll',
|
||||
'<(module_root_dir)/lib/libssp-0.dll',
|
||||
'<(module_root_dir)/lib/libstdc++-6.dll',
|
||||
'<(module_root_dir)/lib/libtiff-5.dll',
|
||||
'<(module_root_dir)/lib/libvips-42.dll',
|
||||
'<(module_root_dir)/lib/libwebp-6.dll',
|
||||
'<(module_root_dir)/lib/libxml2-2.dll',
|
||||
'<(module_root_dir)/lib/zlib1.dll'
|
||||
]
|
||||
}]
|
||||
}]
|
||||
]
|
||||
}]
|
||||
}
|
||||
|
||||
134
binding.js
134
binding.js
@@ -1,134 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var zlib = require('zlib');
|
||||
|
||||
var semver = require('semver');
|
||||
var request = require('request');
|
||||
var tar = require('tar');
|
||||
|
||||
var tmp = require('os').tmpdir();
|
||||
|
||||
var distBaseUrl = 'https://dl.bintray.com/lovell/sharp/';
|
||||
|
||||
// Use NPM-provided environment variable where available, falling back to require-based method for Electron
|
||||
var minimumLibvipsVersion = process.env.npm_package_config_libvips || require('./package.json').config.libvips;
|
||||
|
||||
var vipsHeaderPath = path.join(__dirname, 'include', 'vips', 'vips.h');
|
||||
|
||||
// -- Helpers
|
||||
|
||||
// Does this file exist?
|
||||
var isFile = function(file) {
|
||||
var exists = false;
|
||||
try {
|
||||
exists = fs.statSync(file).isFile();
|
||||
} catch (err) {}
|
||||
return exists;
|
||||
};
|
||||
|
||||
var unpack = function(tarPath, done) {
|
||||
var extractor = tar.Extract({
|
||||
path: __dirname
|
||||
});
|
||||
extractor.on('error', error);
|
||||
extractor.on('end', function() {
|
||||
if (!isFile(vipsHeaderPath)) {
|
||||
error('Could not unpack ' + tarPath);
|
||||
}
|
||||
if (typeof done === 'function') {
|
||||
done();
|
||||
}
|
||||
});
|
||||
fs.createReadStream(tarPath).on('error', error)
|
||||
.pipe(zlib.Unzip())
|
||||
.pipe(extractor);
|
||||
};
|
||||
|
||||
// Error
|
||||
var error = function(msg) {
|
||||
if (msg instanceof Error) {
|
||||
msg = msg.message;
|
||||
}
|
||||
process.stderr.write('ERROR: ' + msg + '\n');
|
||||
process.exit(1);
|
||||
};
|
||||
|
||||
// -- Binary downloaders
|
||||
|
||||
module.exports.download_vips = function() {
|
||||
// Has vips been installed locally?
|
||||
if (!isFile(vipsHeaderPath)) {
|
||||
// Ensure Intel 64-bit or ARM
|
||||
if (process.arch === 'ia32') {
|
||||
error('Intel Architecture 32-bit systems require manual installation - please see http://sharp.dimens.io/en/stable/install/');
|
||||
}
|
||||
// Ensure glibc >= 2.15
|
||||
var lddVersion = process.env.LDD_VERSION;
|
||||
if (lddVersion) {
|
||||
if (/(glibc|gnu libc)/i.test(lddVersion)) {
|
||||
var glibcVersion = lddVersion ? lddVersion.split(/\n/)[0].split(' ').slice(-1)[0].trim() : '';
|
||||
if (glibcVersion && semver.lt(glibcVersion + '.0', '2.13.0')) {
|
||||
error('glibc version ' + glibcVersion + ' requires manual installation - please see http://sharp.dimens.io/en/stable/install/');
|
||||
}
|
||||
} else {
|
||||
error(lddVersion.split(/\n/)[0] + ' requires manual installation - please see http://sharp.dimens.io/en/stable/install/');
|
||||
}
|
||||
}
|
||||
// Arch/platform-specific .tar.gz
|
||||
var platform = (process.arch === 'arm') ? 'arm' : process.platform.substr(0, 3);
|
||||
var tarFilename = ['libvips', minimumLibvipsVersion, platform].join('-') + '.tar.gz';
|
||||
var tarPath = path.join(__dirname, 'packaging', tarFilename);
|
||||
if (isFile(tarPath)) {
|
||||
unpack(tarPath);
|
||||
} else {
|
||||
// Download to per-process temporary file
|
||||
tarPath = path.join(tmp, process.pid + '-' + tarFilename);
|
||||
var tmpFile = fs.createWriteStream(tarPath).on('finish', function() {
|
||||
unpack(tarPath, function() {
|
||||
// Attempt to remove temporary file
|
||||
try {
|
||||
fs.unlinkSync(tarPath);
|
||||
} catch (err) {}
|
||||
});
|
||||
});
|
||||
var options = {
|
||||
url: distBaseUrl + tarFilename
|
||||
};
|
||||
if (process.env.npm_config_https_proxy) {
|
||||
// Use the NPM-configured HTTPS proxy
|
||||
options.proxy = process.env.npm_config_https_proxy;
|
||||
}
|
||||
request(options).on('response', function(response) {
|
||||
if (response.statusCode !== 200) {
|
||||
error(distBaseUrl + tarFilename + ' status code ' + response.statusCode);
|
||||
}
|
||||
}).on('error', function(err) {
|
||||
error('Download from ' + distBaseUrl + tarFilename + ' failed: ' + err.message);
|
||||
}).pipe(tmpFile);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.use_global_vips = function() {
|
||||
var useGlobalVips = false;
|
||||
var globalVipsVersion = process.env.GLOBAL_VIPS_VERSION;
|
||||
if (globalVipsVersion) {
|
||||
useGlobalVips = semver.gte(
|
||||
globalVipsVersion,
|
||||
minimumLibvipsVersion
|
||||
);
|
||||
}
|
||||
if (process.platform === 'darwin' && !useGlobalVips) {
|
||||
if (globalVipsVersion) {
|
||||
error(
|
||||
'Found libvips ' + globalVipsVersion + ' but require ' + minimumLibvipsVersion +
|
||||
'\nPlease upgrade libvips by running: brew update && brew upgrade'
|
||||
);
|
||||
} else {
|
||||
error('Please install libvips by running: brew install homebrew/science/vips --with-webp --with-graphicsmagick');
|
||||
}
|
||||
}
|
||||
process.stdout.write(useGlobalVips ? 'true' : 'false');
|
||||
};
|
||||
@@ -1,6 +0,0 @@
|
||||
machine:
|
||||
services:
|
||||
- docker
|
||||
test:
|
||||
override:
|
||||
- ./packaging/test.sh
|
||||
80
docs/README.md
Normal file
80
docs/README.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# sharp
|
||||
|
||||
<img src="https://cdn.jsdelivr.net/gh/lovell/sharp@master/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, AVIF and WebP 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 v10+
|
||||
do not require any additional install or runtime dependencies.
|
||||
|
||||
### Formats
|
||||
|
||||
This module supports reading JPEG, PNG, WebP, AVIF, TIFF, GIF and SVG images.
|
||||
|
||||
Output images can be in JPEG, PNG, WebP, 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
|
||||
|
||||
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/).
|
||||
|
||||
### Contributing
|
||||
|
||||
A [guide for contributors](https://github.com/lovell/sharp/blob/master/.github/CONTRIBUTING.md)
|
||||
covers reporting bugs, requesting features and submitting code changes.
|
||||
|
||||
### Licensing
|
||||
|
||||
Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
|
||||
|
||||
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.
|
||||
120
docs/api-channel.md
Normal file
120
docs/api-channel.md
Normal file
@@ -0,0 +1,120 @@
|
||||
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
|
||||
|
||||
## removeAlpha
|
||||
|
||||
Remove alpha channel, if any. This is a no-op if the image does not have an alpha channel.
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
sharp('rgba.png')
|
||||
.removeAlpha()
|
||||
.toFile('rgb.png', function(err, info) {
|
||||
// rgb.png is a 3 channel image without an alpha channel
|
||||
});
|
||||
```
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## ensureAlpha
|
||||
|
||||
Ensure alpha channel, if missing. The added alpha channel will be fully opaque. This is a no-op if the image already has an alpha channel.
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
sharp('rgb.jpg')
|
||||
.ensureAlpha()
|
||||
.toFile('rgba.png', function(err, info) {
|
||||
// rgba.png is a 4 channel image with a fully opaque alpha channel
|
||||
});
|
||||
```
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
**Meta**
|
||||
|
||||
- **since**: 0.21.2
|
||||
|
||||
## extractChannel
|
||||
|
||||
Extract a single channel from a multi-channel image.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `channel` **([number][1] \| [string][2])** zero-indexed channel/band number to extract, or `red`, `green`, `blue` or `alpha`.
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
sharp(input)
|
||||
.extractChannel('green')
|
||||
.toColourspace('b-w')
|
||||
.toFile('green.jpg', function(err, info) {
|
||||
// info.channels === 1
|
||||
// green.jpg is a greyscale image containing the green channel of the input
|
||||
});
|
||||
```
|
||||
|
||||
- Throws **[Error][3]** Invalid channel
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## joinChannel
|
||||
|
||||
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: JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data.
|
||||
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.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `images` **([Array][4]<([string][2] \| [Buffer][5])> | [string][2] \| [Buffer][5])** one or more images (file paths, Buffers).
|
||||
- `options` **[Object][6]** image options, see `sharp()` constructor.
|
||||
|
||||
|
||||
- Throws **[Error][3]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## bandbool
|
||||
|
||||
Perform a bitwise boolean operation on all input image channels (bands) to produce a single channel output image.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `boolOp` **[string][2]** one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
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`.
|
||||
});
|
||||
```
|
||||
|
||||
- Throws **[Error][3]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
|
||||
|
||||
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
|
||||
|
||||
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
|
||||
|
||||
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
|
||||
|
||||
[5]: https://nodejs.org/api/buffer.html
|
||||
|
||||
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
|
||||
79
docs/api-colour.md
Normal file
79
docs/api-colour.md
Normal file
@@ -0,0 +1,79 @@
|
||||
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
|
||||
|
||||
## tint
|
||||
|
||||
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.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `rgb` **([string][1] \| [Object][2])** parsed by the [color][3] module to extract chroma values.
|
||||
|
||||
|
||||
- Throws **[Error][4]** Invalid parameter
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## 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.
|
||||
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.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `greyscale` **[Boolean][5]** (optional, default `true`)
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## grayscale
|
||||
|
||||
Alternative spelling of `greyscale`.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `grayscale` **[Boolean][5]** (optional, default `true`)
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## toColourspace
|
||||
|
||||
Set the output colourspace.
|
||||
By default output image will be web-friendly sRGB, with additional channels interpreted as alpha channels.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `colourspace` **[string][1]?** output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...][6]
|
||||
|
||||
|
||||
- Throws **[Error][4]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## toColorspace
|
||||
|
||||
Alternative spelling of `toColourspace`.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `colorspace` **[string][1]?** output colorspace.
|
||||
|
||||
|
||||
- Throws **[Error][4]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
|
||||
|
||||
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
|
||||
|
||||
[3]: https://www.npmjs.org/package/color
|
||||
|
||||
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
|
||||
|
||||
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
||||
|
||||
[6]: https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568
|
||||
86
docs/api-composite.md
Normal file
86
docs/api-composite.md
Normal file
@@ -0,0 +1,86 @@
|
||||
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
|
||||
|
||||
## composite
|
||||
|
||||
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`.
|
||||
|
||||
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://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode][1]
|
||||
and [https://www.cairographics.org/operators/][2]
|
||||
|
||||
### Parameters
|
||||
|
||||
- `images` **[Array][3]<[Object][4]>** Ordered list of images to composite
|
||||
- `images[].input` **([Buffer][5] \| [String][6])?** Buffer containing image data, String containing the path to an image file, or Create object (see below)
|
||||
- `images[].input.create` **[Object][4]?** describes a blank overlay to be created.
|
||||
- `images[].input.create.width` **[Number][7]?**
|
||||
- `images[].input.create.height` **[Number][7]?**
|
||||
- `images[].input.create.channels` **[Number][7]?** 3-4
|
||||
- `images[].input.create.background` **([String][6] \| [Object][4])?** parsed by the [color][8] module to extract values for red, green, blue and alpha.
|
||||
- `images[].blend` **[String][6]** how to blend this image with the image below. (optional, default `'over'`)
|
||||
- `images[].gravity` **[String][6]** gravity at which to place the overlay. (optional, default `'centre'`)
|
||||
- `images[].top` **[Number][7]?** the pixel offset from the top edge.
|
||||
- `images[].left` **[Number][7]?** the pixel offset from the left edge.
|
||||
- `images[].tile` **[Boolean][9]** set to true to repeat the overlay image across the entire image with the given `gravity`. (optional, default `false`)
|
||||
- `images[].premultiplied` **[Boolean][9]** set to true to avoid premultipling the image below. Equivalent to the `--premultiplied` vips option. (optional, default `false`)
|
||||
- `images[].density` **[Number][7]** number representing the DPI for vector overlay image. (optional, default `72`)
|
||||
- `images[].raw` **[Object][4]?** describes overlay when using raw pixel data.
|
||||
- `images[].raw.width` **[Number][7]?**
|
||||
- `images[].raw.height` **[Number][7]?**
|
||||
- `images[].raw.channels` **[Number][7]?**
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
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!
|
||||
});
|
||||
```
|
||||
|
||||
- Throws **[Error][10]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
**Meta**
|
||||
|
||||
- **since**: 0.22.0
|
||||
|
||||
[1]: https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode
|
||||
|
||||
[2]: https://www.cairographics.org/operators/
|
||||
|
||||
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
|
||||
|
||||
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
|
||||
|
||||
[5]: https://nodejs.org/api/buffer.html
|
||||
|
||||
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
|
||||
|
||||
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
|
||||
|
||||
[8]: https://www.npmjs.org/package/color
|
||||
|
||||
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
||||
|
||||
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
|
||||
175
docs/api-constructor.md
Normal file
175
docs/api-constructor.md
Normal file
@@ -0,0 +1,175 @@
|
||||
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
|
||||
|
||||
## Sharp
|
||||
|
||||
Constructor factory to create an instance of `sharp`, to which further methods are chained.
|
||||
|
||||
JPEG, PNG, WebP 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][1] class.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `input` **([Buffer][2] \| [string][3])?** if present, can be
|
||||
a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
|
||||
a String containing the filesystem path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
|
||||
JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
|
||||
- `options` **[Object][4]?** if present, is an Object with optional attributes.
|
||||
- `options.failOnError` **[boolean][5]** by default halt processing and raise an error when loading invalid images.
|
||||
Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid. (optional, default `true`)
|
||||
- `options.limitInputPixels` **([number][6] \| [boolean][5])** 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). (optional, default `268402689`)
|
||||
- `options.sequentialRead` **[boolean][5]** Set this to `true` to use sequential rather than random access where possible.
|
||||
This can reduce memory usage and might improve performance on some systems. (optional, default `false`)
|
||||
- `options.density` **[number][6]** number representing the DPI for vector images in the range 1 to 100000. (optional, default `72`)
|
||||
- `options.pages` **[number][6]** number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages. (optional, default `1`)
|
||||
- `options.page` **[number][6]** page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based. (optional, default `0`)
|
||||
- `options.level` **[number][6]** level to extract from a multi-level input (OpenSlide), zero based. (optional, default `0`)
|
||||
- `options.animated` **[boolean][5]** Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`). (optional, default `false`)
|
||||
- `options.raw` **[Object][4]?** describes raw pixel input image data. See `raw()` for pixel ordering.
|
||||
- `options.raw.width` **[number][6]?**
|
||||
- `options.raw.height` **[number][6]?**
|
||||
- `options.raw.channels` **[number][6]?** 1-4
|
||||
- `options.create` **[Object][4]?** describes a new image to be created.
|
||||
- `options.create.width` **[number][6]?**
|
||||
- `options.create.height` **[number][6]?**
|
||||
- `options.create.channels` **[number][6]?** 3-4
|
||||
- `options.create.background` **([string][3] \| [Object][4])?** parsed by the [color][7] module to extract values for red, green, blue and alpha.
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
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
|
||||
});
|
||||
```
|
||||
|
||||
```javascript
|
||||
// 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);
|
||||
```
|
||||
|
||||
```javascript
|
||||
// Create a blank 300x200 PNG image of semi-transluent red pixels
|
||||
sharp({
|
||||
create: {
|
||||
width: 300,
|
||||
height: 200,
|
||||
channels: 4,
|
||||
background: { r: 255, g: 0, b: 0, alpha: 0.5 }
|
||||
}
|
||||
})
|
||||
.png()
|
||||
.toBuffer()
|
||||
.then( ... );
|
||||
```
|
||||
|
||||
```javascript
|
||||
// Convert an animated GIF to an animated WebP
|
||||
await sharp('in.gif', { animated: true }).toFile('out.webp');
|
||||
```
|
||||
|
||||
- Throws **[Error][8]** Invalid parameters
|
||||
|
||||
Returns **[Sharp][9]**
|
||||
|
||||
## clone
|
||||
|
||||
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.
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
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
|
||||
```
|
||||
|
||||
```javascript
|
||||
// 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({
|
||||
failOnError: false
|
||||
});
|
||||
|
||||
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#gotstreamurl-options
|
||||
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][9]**
|
||||
|
||||
[1]: http://nodejs.org/api/stream.html#stream_class_stream_duplex
|
||||
|
||||
[2]: https://nodejs.org/api/buffer.html
|
||||
|
||||
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
|
||||
|
||||
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
|
||||
|
||||
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
||||
|
||||
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
|
||||
|
||||
[7]: https://www.npmjs.org/package/color
|
||||
|
||||
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
|
||||
|
||||
[9]: #sharp
|
||||
109
docs/api-input.md
Normal file
109
docs/api-input.md
Normal file
@@ -0,0 +1,109 @@
|
||||
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
|
||||
|
||||
## metadata
|
||||
|
||||
Fast access to (uncached) image metadata without decoding any compressed image data.
|
||||
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)
|
||||
- `height`: Number of pixels high (EXIF orientation is not taken into consideration)
|
||||
- `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...][1]
|
||||
- `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
|
||||
- `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...][2]
|
||||
- `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.
|
||||
- `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
|
||||
- `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][3] 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
|
||||
|
||||
### Parameters
|
||||
|
||||
- `callback` **[Function][4]?** called with the arguments `(err, metadata)`
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
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
|
||||
});
|
||||
```
|
||||
|
||||
Returns **([Promise][5]<[Object][6]> | Sharp)**
|
||||
|
||||
## stats
|
||||
|
||||
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 (experimental)
|
||||
- `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any (experimental)
|
||||
- `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram (experimental)
|
||||
|
||||
### Parameters
|
||||
|
||||
- `callback` **[Function][4]?** called with the arguments `(err, stats)`
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
const image = sharp(inputJpg);
|
||||
image
|
||||
.stats()
|
||||
.then(function(stats) {
|
||||
// stats contains the channel-wise statistics array and the isOpaque value
|
||||
});
|
||||
```
|
||||
|
||||
```javascript
|
||||
const { entropy, sharpness, dominant } = await sharp(input).stats();
|
||||
const { r, g, b } = dominant;
|
||||
```
|
||||
|
||||
Returns **[Promise][5]<[Object][6]>**
|
||||
|
||||
[1]: https://libvips.github.io/libvips/API/current/VipsImage.html#VipsInterpretation
|
||||
|
||||
[2]: https://libvips.github.io/libvips/API/current/VipsImage.html#VipsBandFormat
|
||||
|
||||
[3]: https://www.npmjs.com/package/icc
|
||||
|
||||
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
|
||||
|
||||
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise
|
||||
|
||||
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
|
||||
401
docs/api-operation.md
Normal file
401
docs/api-operation.md
Normal file
@@ -0,0 +1,401 @@
|
||||
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
|
||||
|
||||
## rotate
|
||||
|
||||
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 270deg 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` implies the removal of the EXIF `Orientation` tag, if any.
|
||||
|
||||
Method order is important when both rotating and extracting regions,
|
||||
for example `rotate(x).extract(y)` will produce a different result to `extract(y).rotate(x)`.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `angle` **[number][1]** angle of rotation. (optional, default `auto`)
|
||||
- `options` **[Object][2]?** if present, is an Object with optional attributes.
|
||||
- `options.background` **([string][3] \| [Object][2])** parsed by the [color][4] module to extract values for red, green, blue and alpha. (optional, default `"#000000"`)
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
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);
|
||||
```
|
||||
|
||||
- Throws **[Error][5]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## flip
|
||||
|
||||
Flip the image about the vertical Y axis. This always occurs after rotation, if any.
|
||||
The use of `flip` implies the removal of the EXIF `Orientation` tag, if any.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `flip` **[Boolean][6]** (optional, default `true`)
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## flop
|
||||
|
||||
Flop the image about the horizontal X axis. This always occurs after rotation, if any.
|
||||
The use of `flop` implies the removal of the EXIF `Orientation` tag, if any.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `flop` **[Boolean][6]** (optional, default `true`)
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## affine
|
||||
|
||||
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.interpolator` Object e.g. `sharp.interpolator.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.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `matrix` **([Array][7]<[Array][7]<[number][1]>> | [Array][7]<[number][1]>)** affine transformation matrix
|
||||
- `options` **[Object][2]?** if present, is an Object with optional attributes.
|
||||
- `options.background` **([String][3] \| [Object][2])** parsed by the [color][4] module to extract values for red, green, blue and alpha. (optional, default `"#000000"`)
|
||||
- `options.idx` **[Number][1]** input horizontal offset (optional, default `0`)
|
||||
- `options.idy` **[Number][1]** input vertical offset (optional, default `0`)
|
||||
- `options.odx` **[Number][1]** output horizontal offset (optional, default `0`)
|
||||
- `options.ody` **[Number][1]** output vertical offset (optional, default `0`)
|
||||
- `options.interpolator` **[String][3]** interpolator (optional, default `sharp.interpolators.bicubic`)
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
const pipeline = sharp()
|
||||
.affine([[1, 0.3], [0.1, 0.7]], {
|
||||
background: 'white',
|
||||
interpolate: sharp.interpolators.nohalo
|
||||
})
|
||||
.toBuffer((err, outputBuffer, info) => {
|
||||
// outputBuffer contains the transformed image
|
||||
// info.width and info.height contain the new dimensions
|
||||
});
|
||||
|
||||
inputStream
|
||||
.pipe(pipeline);
|
||||
```
|
||||
|
||||
- Throws **[Error][5]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
**Meta**
|
||||
|
||||
- **since**: 0.27.0
|
||||
|
||||
## sharpen
|
||||
|
||||
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.
|
||||
Separate control over the level of sharpening in "flat" and "jagged" areas is available.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `sigma` **[number][1]?** the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
|
||||
- `flat` **[number][1]** the level of sharpening to apply to "flat" areas. (optional, default `1.0`)
|
||||
- `jagged` **[number][1]** the level of sharpening to apply to "jagged" areas. (optional, default `2.0`)
|
||||
|
||||
|
||||
- Throws **[Error][5]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## median
|
||||
|
||||
Apply median filter.
|
||||
When used without parameters the default window is 3x3.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `size` **[number][1]** square mask size: size x size (optional, default `3`)
|
||||
|
||||
|
||||
- Throws **[Error][5]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## blur
|
||||
|
||||
Blur the image.
|
||||
When used without parameters, performs a fast, mild blur of the output image.
|
||||
When a `sigma` is provided, performs a slower, more accurate Gaussian blur.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `sigma` **[number][1]?** a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
|
||||
|
||||
|
||||
- Throws **[Error][5]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## flatten
|
||||
|
||||
Merge alpha transparency channel, if any, with a background.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `options` **[Object][2]?**
|
||||
- `options.background` **([string][3] \| [Object][2])** background colour, parsed by the [color][4] module, defaults to black. (optional, default `{r:0,g:0,b:0}`)
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## 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`.
|
||||
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.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `gamma` **[number][1]** value between 1.0 and 3.0. (optional, default `2.2`)
|
||||
- `gammaOut` **[number][1]?** value between 1.0 and 3.0. (optional, defaults to same as `gamma`)
|
||||
|
||||
|
||||
- Throws **[Error][5]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## negate
|
||||
|
||||
Produce the "negative" of the image.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `negate` **[Boolean][6]** (optional, default `true`)
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## normalise
|
||||
|
||||
Enhance output image contrast by stretching its luminance to cover the full dynamic range.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `normalise` **[Boolean][6]** (optional, default `true`)
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## normalize
|
||||
|
||||
Alternative spelling of normalise.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `normalize` **[Boolean][6]** (optional, default `true`)
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## convolve
|
||||
|
||||
Convolve the image with the specified kernel.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `kernel` **[Object][2]**
|
||||
- `kernel.width` **[number][1]** width of the kernel in pixels.
|
||||
- `kernel.height` **[number][1]** width of the kernel in pixels.
|
||||
- `kernel.kernel` **[Array][7]<[number][1]>** Array of length `width*height` containing the kernel values.
|
||||
- `kernel.scale` **[number][1]** the scale of the kernel in pixels. (optional, default `sum`)
|
||||
- `kernel.offset` **[number][1]** the offset of the kernel in pixels. (optional, default `0`)
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
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
|
||||
});
|
||||
```
|
||||
|
||||
- Throws **[Error][5]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## threshold
|
||||
|
||||
Any pixel value greather than or equal to the threshold value will be set to 255, otherwise it will be set to 0.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `threshold` **[number][1]** a value in the range 0-255 representing the level at which the threshold will be applied. (optional, default `128`)
|
||||
- `options` **[Object][2]?**
|
||||
- `options.greyscale` **[Boolean][6]** convert to single channel greyscale. (optional, default `true`)
|
||||
- `options.grayscale` **[Boolean][6]** alternative spelling for greyscale. (optional, default `true`)
|
||||
|
||||
|
||||
- Throws **[Error][5]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## boolean
|
||||
|
||||
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.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `operand` **([Buffer][8] \| [string][3])** Buffer containing image data or string containing the path to an image file.
|
||||
- `operator` **[string][3]** one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
|
||||
- `options` **[Object][2]?**
|
||||
- `options.raw` **[Object][2]?** describes operand when using raw pixel data.
|
||||
- `options.raw.width` **[number][1]?**
|
||||
- `options.raw.height` **[number][1]?**
|
||||
- `options.raw.channels` **[number][1]?**
|
||||
|
||||
|
||||
- Throws **[Error][5]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## linear
|
||||
|
||||
Apply the linear formula a \* input + b to the image (levels adjustment)
|
||||
|
||||
### Parameters
|
||||
|
||||
- `a` **[number][1]** multiplier (optional, default `1.0`)
|
||||
- `b` **[number][1]** offset (optional, default `0.0`)
|
||||
|
||||
|
||||
- Throws **[Error][5]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## recomb
|
||||
|
||||
Recomb the image with the specified matrix.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `inputMatrix` **[Array][7]<[Array][7]<[number][1]>>** 3x3 Recombination matrix
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
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 recomb
|
||||
// With this example input, a sepia filter has been applied
|
||||
});
|
||||
```
|
||||
|
||||
- Throws **[Error][5]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
**Meta**
|
||||
|
||||
- **since**: 0.21.1
|
||||
|
||||
## modulate
|
||||
|
||||
Transforms the image using brightness, saturation and hue rotation.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `options` **[Object][2]?**
|
||||
- `options.brightness` **[number][1]?** Brightness multiplier
|
||||
- `options.saturation` **[number][1]?** Saturation multiplier
|
||||
- `options.hue` **[number][1]?** Degrees for hue rotation
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
sharp(input)
|
||||
.modulate({
|
||||
brightness: 2 // increase lightness by a factor of 2
|
||||
});
|
||||
|
||||
sharp(input)
|
||||
.modulate({
|
||||
hue: 180 // hue-rotate by 180 degrees
|
||||
});
|
||||
|
||||
// decreate brightness and saturation while also hue-rotating by 90 degrees
|
||||
sharp(input)
|
||||
.modulate({
|
||||
brightness: 0.5,
|
||||
saturation: 0.5,
|
||||
hue: 90
|
||||
});
|
||||
```
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
**Meta**
|
||||
|
||||
- **since**: 0.22.1
|
||||
|
||||
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
|
||||
|
||||
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
|
||||
|
||||
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
|
||||
|
||||
[4]: https://www.npmjs.org/package/color
|
||||
|
||||
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
|
||||
|
||||
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
||||
|
||||
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
|
||||
|
||||
[8]: https://nodejs.org/api/buffer.html
|
||||
438
docs/api-output.md
Normal file
438
docs/api-output.md
Normal file
@@ -0,0 +1,438 @@
|
||||
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
|
||||
|
||||
## toFile
|
||||
|
||||
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, TIFF, 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][1] for control over this.
|
||||
|
||||
A `Promise` is returned when `callback` is not provided.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `fileOut` **[string][2]** the path to write the image data to.
|
||||
- `callback` **[Function][3]?** 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`.
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
sharp(input)
|
||||
.toFile('output.png', (err, info) => { ... });
|
||||
```
|
||||
|
||||
```javascript
|
||||
sharp(input)
|
||||
.toFile('output.png')
|
||||
.then(info => { ... })
|
||||
.catch(err => { ... });
|
||||
```
|
||||
|
||||
- Throws **[Error][4]** Invalid parameters
|
||||
|
||||
Returns **[Promise][5]<[Object][6]>** when no callback is provided
|
||||
|
||||
## toBuffer
|
||||
|
||||
Write output to a Buffer.
|
||||
JPEG, PNG, WebP, TIFF and RAW output are supported.
|
||||
|
||||
If no explicit format is set, the output format will match the input image, except GIF and SVG input which become PNG output.
|
||||
|
||||
By default all metadata will be removed, which includes EXIF-based orientation.
|
||||
See [withMetadata][1] 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`.
|
||||
|
||||
A `Promise` is returned when `callback` is not provided.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `options` **[Object][6]?**
|
||||
- `options.resolveWithObject` **[boolean][7]?** Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
|
||||
- `callback` **[Function][3]?**
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
sharp(input)
|
||||
.toBuffer((err, data, info) => { ... });
|
||||
```
|
||||
|
||||
```javascript
|
||||
sharp(input)
|
||||
.toBuffer()
|
||||
.then(data => { ... })
|
||||
.catch(err => { ... });
|
||||
```
|
||||
|
||||
```javascript
|
||||
sharp(input)
|
||||
.toBuffer({ resolveWithObject: true })
|
||||
.then(({ data, info }) => { ... })
|
||||
.catch(err => { ... });
|
||||
```
|
||||
|
||||
Returns **[Promise][5]<[Buffer][8]>** when no callback is provided
|
||||
|
||||
## withMetadata
|
||||
|
||||
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 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.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `options` **[Object][6]?**
|
||||
- `options.orientation` **[number][9]?** value between 1 and 8, used to update the EXIF `Orientation` tag.
|
||||
- `options.icc` **[string][2]?** filesystem path to output ICC profile, defaults to sRGB.
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
sharp('input.jpg')
|
||||
.withMetadata()
|
||||
.toFile('output-with-metadata.jpg')
|
||||
.then(info => { ... });
|
||||
```
|
||||
|
||||
- Throws **[Error][4]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## toFormat
|
||||
|
||||
Force output to a given format.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `format` **([string][2] \| [Object][6])** as a string or an Object with an 'id' attribute
|
||||
- `options` **[Object][6]** output options
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
// Convert any input to PNG output
|
||||
const data = await sharp(input)
|
||||
.toFormat('png')
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
- Throws **[Error][4]** unsupported format or options
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## jpeg
|
||||
|
||||
Use these JPEG options for output image.
|
||||
|
||||
Some of these options require the use of a globally-installed libvips compiled with support for mozjpeg.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `options` **[Object][6]?** output options
|
||||
- `options.quality` **[number][9]** quality, integer 1-100 (optional, default `80`)
|
||||
- `options.progressive` **[boolean][7]** use progressive (interlace) scan (optional, default `false`)
|
||||
- `options.chromaSubsampling` **[string][2]** set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling (optional, default `'4:2:0'`)
|
||||
- `options.optimiseCoding` **[boolean][7]** optimise Huffman coding tables (optional, default `true`)
|
||||
- `options.optimizeCoding` **[boolean][7]** alternative spelling of optimiseCoding (optional, default `true`)
|
||||
- `options.trellisQuantisation` **[boolean][7]** apply trellis quantisation, requires libvips compiled with support for mozjpeg (optional, default `false`)
|
||||
- `options.overshootDeringing` **[boolean][7]** apply overshoot deringing, requires libvips compiled with support for mozjpeg (optional, default `false`)
|
||||
- `options.optimiseScans` **[boolean][7]** optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg (optional, default `false`)
|
||||
- `options.optimizeScans` **[boolean][7]** alternative spelling of optimiseScans, requires libvips compiled with support for mozjpeg (optional, default `false`)
|
||||
- `options.quantisationTable` **[number][9]** quantization table to use, integer 0-8, requires libvips compiled with support for mozjpeg (optional, default `0`)
|
||||
- `options.quantizationTable` **[number][9]** alternative spelling of quantisationTable, requires libvips compiled with support for mozjpeg (optional, default `0`)
|
||||
- `options.force` **[boolean][7]** force JPEG output, otherwise attempt to use input format (optional, default `true`)
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
// Convert any input to very high quality JPEG output
|
||||
const data = await sharp(input)
|
||||
.jpeg({
|
||||
quality: 100,
|
||||
chromaSubsampling: '4:4:4'
|
||||
})
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
- Throws **[Error][4]** Invalid options
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## png
|
||||
|
||||
Use these PNG options for output image.
|
||||
|
||||
PNG output is always 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.
|
||||
|
||||
Some of these options require the use of a globally-installed libvips compiled with support for libimagequant (GPL).
|
||||
|
||||
### Parameters
|
||||
|
||||
- `options` **[Object][6]?**
|
||||
- `options.progressive` **[boolean][7]** use progressive (interlace) scan (optional, default `false`)
|
||||
- `options.compressionLevel` **[number][9]** zlib compression level, 0-9 (optional, default `9`)
|
||||
- `options.adaptiveFiltering` **[boolean][7]** use adaptive row filtering (optional, default `false`)
|
||||
- `options.palette` **[boolean][7]** quantise to a palette-based image with alpha transparency support, requires libvips compiled with support for libimagequant (optional, default `false`)
|
||||
- `options.quality` **[number][9]** use the lowest number of colours needed to achieve given quality, sets `palette` to `true`, requires libvips compiled with support for libimagequant (optional, default `100`)
|
||||
- `options.colours` **[number][9]** maximum number of palette entries, sets `palette` to `true`, requires libvips compiled with support for libimagequant (optional, default `256`)
|
||||
- `options.colors` **[number][9]** alternative spelling of `options.colours`, sets `palette` to `true`, requires libvips compiled with support for libimagequant (optional, default `256`)
|
||||
- `options.dither` **[number][9]** level of Floyd-Steinberg error diffusion, sets `palette` to `true`, requires libvips compiled with support for libimagequant (optional, default `1.0`)
|
||||
- `options.force` **[boolean][7]** force PNG output, otherwise attempt to use input format (optional, default `true`)
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
// Convert any input to PNG output
|
||||
const data = await sharp(input)
|
||||
.png()
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
- Throws **[Error][4]** Invalid options
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## webp
|
||||
|
||||
Use these WebP options for output image.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `options` **[Object][6]?** output options
|
||||
- `options.quality` **[number][9]** quality, integer 1-100 (optional, default `80`)
|
||||
- `options.alphaQuality` **[number][9]** quality of alpha layer, integer 0-100 (optional, default `100`)
|
||||
- `options.lossless` **[boolean][7]** use lossless compression mode (optional, default `false`)
|
||||
- `options.nearLossless` **[boolean][7]** use near_lossless compression mode (optional, default `false`)
|
||||
- `options.smartSubsample` **[boolean][7]** use high quality chroma subsampling (optional, default `false`)
|
||||
- `options.reductionEffort` **[number][9]** level of CPU effort to reduce file size, integer 0-6 (optional, default `4`)
|
||||
- `options.pageHeight` **[number][9]?** page height for animated output
|
||||
- `options.loop` **[number][9]** number of animation iterations, use 0 for infinite animation (optional, default `0`)
|
||||
- `options.delay` **[Array][10]<[number][9]>?** list of delays between animation frames (in milliseconds)
|
||||
- `options.force` **[boolean][7]** force WebP output, otherwise attempt to use input format (optional, default `true`)
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
// Convert any input to lossless WebP output
|
||||
const data = await sharp(input)
|
||||
.webp({ lossless: true })
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
- Throws **[Error][4]** Invalid options
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## gif
|
||||
|
||||
Use these GIF options for output image.
|
||||
|
||||
Requires libvips compiled with support for ImageMagick or GraphicsMagick.
|
||||
The prebuilt binaries do not include this - see
|
||||
[installing a custom libvips][11].
|
||||
|
||||
### Parameters
|
||||
|
||||
- `options` **[Object][6]?** output options
|
||||
- `options.pageHeight` **[number][9]?** page height for animated output
|
||||
- `options.loop` **[number][9]** number of animation iterations, use 0 for infinite animation (optional, default `0`)
|
||||
- `options.delay` **[Array][10]<[number][9]>?** list of delays between animation frames (in milliseconds)
|
||||
- `options.force` **[boolean][7]** force GIF output, otherwise attempt to use input format (optional, default `true`)
|
||||
|
||||
|
||||
- Throws **[Error][4]** Invalid options
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## tiff
|
||||
|
||||
Use these TIFF options for output image.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `options` **[Object][6]?** output options
|
||||
- `options.quality` **[number][9]** quality, integer 1-100 (optional, default `80`)
|
||||
- `options.force` **[boolean][7]** force TIFF output, otherwise attempt to use input format (optional, default `true`)
|
||||
- `options.compression` **[boolean][7]** compression options: lzw, deflate, jpeg, ccittfax4 (optional, default `'jpeg'`)
|
||||
- `options.predictor` **[boolean][7]** compression predictor options: none, horizontal, float (optional, default `'horizontal'`)
|
||||
- `options.pyramid` **[boolean][7]** write an image pyramid (optional, default `false`)
|
||||
- `options.tile` **[boolean][7]** write a tiled tiff (optional, default `false`)
|
||||
- `options.tileWidth` **[boolean][7]** horizontal tile size (optional, default `256`)
|
||||
- `options.tileHeight` **[boolean][7]** vertical tile size (optional, default `256`)
|
||||
- `options.xres` **[number][9]** horizontal resolution in pixels/mm (optional, default `1.0`)
|
||||
- `options.yres` **[number][9]** vertical resolution in pixels/mm (optional, default `1.0`)
|
||||
- `options.bitdepth` **[boolean][7]** reduce bitdepth to 1, 2 or 4 bit (optional, default `8`)
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
// 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 => { ... });
|
||||
```
|
||||
|
||||
- Throws **[Error][4]** Invalid options
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## avif
|
||||
|
||||
Use these AVIF options for output image.
|
||||
|
||||
Whilst it is possible to create AVIF images smaller than 16x16 pixels,
|
||||
most web browsers do not display these properly.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `options` **[Object][6]?** output options
|
||||
- `options.quality` **[number][9]** quality, integer 1-100 (optional, default `50`)
|
||||
- `options.lossless` **[boolean][7]** use lossless compression (optional, default `false`)
|
||||
- `options.speed` **[boolean][7]** CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest) (optional, default `5`)
|
||||
|
||||
|
||||
- Throws **[Error][4]** Invalid options
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
**Meta**
|
||||
|
||||
- **since**: 0.27.0
|
||||
|
||||
## heif
|
||||
|
||||
Use these HEIF options for output image.
|
||||
|
||||
Support for patent-encumbered HEIC images requires the use of a
|
||||
globally-installed libvips compiled with support for libheif, libde265 and x265.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `options` **[Object][6]?** output options
|
||||
- `options.quality` **[number][9]** quality, integer 1-100 (optional, default `50`)
|
||||
- `options.compression` **[boolean][7]** compression format: av1, hevc (optional, default `'av1'`)
|
||||
- `options.lossless` **[boolean][7]** use lossless compression (optional, default `false`)
|
||||
- `options.speed` **[boolean][7]** CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest) (optional, default `5`)
|
||||
|
||||
|
||||
- Throws **[Error][4]** Invalid options
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
**Meta**
|
||||
|
||||
- **since**: 0.23.0
|
||||
|
||||
## raw
|
||||
|
||||
Force output to be raw, uncompressed, 8-bit unsigned integer (unit8) pixel data.
|
||||
Pixel ordering is left-to-right, top-to-bottom, without padding.
|
||||
Channel ordering will be RGB or RGBA for non-greyscale colourspaces.
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
// Extract raw RGB pixel data from JPEG input
|
||||
const { data, info } = await sharp('input.jpg')
|
||||
.raw()
|
||||
.toBuffer({ resolveWithObject: true });
|
||||
```
|
||||
|
||||
```javascript
|
||||
// Extract alpha channel as raw pixel data from PNG input
|
||||
const data = await sharp('input.png')
|
||||
.ensureAlpha()
|
||||
.extractChannel(3)
|
||||
.toColourspace('b-w')
|
||||
.raw()
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## tile
|
||||
|
||||
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.
|
||||
|
||||
Warning: multiple sharp instances concurrently producing tile output can expose a possible race condition in some versions of libgsf.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `options` **[Object][6]?**
|
||||
- `options.size` **[number][9]** tile size in pixels, a value between 1 and 8192. (optional, default `256`)
|
||||
- `options.overlap` **[number][9]** tile overlap in pixels, a value between 0 and 8192. (optional, default `0`)
|
||||
- `options.angle` **[number][9]** tile angle of rotation, must be a multiple of 90. (optional, default `0`)
|
||||
- `options.background` **([string][2] \| [Object][6])** background colour, parsed by the [color][12] module, defaults to white without transparency. (optional, default `{r:255,g:255,b:255,alpha:1}`)
|
||||
- `options.depth` **[string][2]?** how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
|
||||
- `options.skipBlanks` **[number][9]** threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images (optional, default `-1`)
|
||||
- `options.container` **[string][2]** tile container, with value `fs` (filesystem) or `zip` (compressed file). (optional, default `'fs'`)
|
||||
- `options.layout` **[string][2]** filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`. (optional, default `'dz'`)
|
||||
- `options.centre` **[boolean][7]** centre image in tile. (optional, default `false`)
|
||||
- `options.center` **[boolean][7]** alternative spelling of centre. (optional, default `false`)
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
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
|
||||
});
|
||||
```
|
||||
|
||||
- Throws **[Error][4]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
[1]: #withmetadata
|
||||
|
||||
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
|
||||
|
||||
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
|
||||
|
||||
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
|
||||
|
||||
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise
|
||||
|
||||
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
|
||||
|
||||
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
||||
|
||||
[8]: https://nodejs.org/api/buffer.html
|
||||
|
||||
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
|
||||
|
||||
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
|
||||
|
||||
[11]: https://sharp.pixelplumbing.com/install#custom-libvips
|
||||
|
||||
[12]: https://www.npmjs.org/package/color
|
||||
248
docs/api-resize.md
Normal file
248
docs/api-resize.md
Normal file
@@ -0,0 +1,248 @@
|
||||
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
|
||||
|
||||
## resize
|
||||
|
||||
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, 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][1] CSS property.
|
||||
|
||||
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][2] 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][3].
|
||||
- `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][4].
|
||||
- `cubic`: Use a [Catmull-Rom spline][5].
|
||||
- `mitchell`: Use a [Mitchell-Netravali spline][6].
|
||||
- `lanczos2`: Use a [Lanczos kernel][7] with `a=2`.
|
||||
- `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
|
||||
|
||||
### Parameters
|
||||
|
||||
- `width` **[number][8]?** pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height.
|
||||
- `height` **[number][8]?** pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
|
||||
- `options` **[Object][9]?**
|
||||
- `options.width` **[String][10]?** alternative means of specifying `width`. If both are present this take priority.
|
||||
- `options.height` **[String][10]?** alternative means of specifying `height`. If both are present this take priority.
|
||||
- `options.fit` **[String][10]** how the image should be resized to fit both provided dimensions, one of `cover`, `contain`, `fill`, `inside` or `outside`. (optional, default `'cover'`)
|
||||
- `options.position` **[String][10]** position, gravity or strategy to use when `fit` is `cover` or `contain`. (optional, default `'centre'`)
|
||||
- `options.background` **([String][10] \| [Object][9])** background colour when using a `fit` of `contain`, parsed by the [color][11] module, defaults to black without transparency. (optional, default `{r:0,g:0,b:0,alpha:1}`)
|
||||
- `options.kernel` **[String][10]** the kernel to use for image reduction. (optional, default `'lanczos3'`)
|
||||
- `options.withoutEnlargement` **[Boolean][12]** do not enlarge if the width _or_ height are already less than the specified dimensions, equivalent to GraphicsMagick's `>` geometry option. (optional, default `false`)
|
||||
- `options.fastShrinkOnLoad` **[Boolean][12]** take greater advantage of the JPEG and WebP shrink-on-load feature, which can lead to a slight moiré pattern on some images. (optional, default `true`)
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
sharp(input)
|
||||
.resize({ width: 100 })
|
||||
.toBuffer()
|
||||
.then(data => {
|
||||
// 100 pixels wide, auto-scaled height
|
||||
});
|
||||
```
|
||||
|
||||
```javascript
|
||||
sharp(input)
|
||||
.resize({ height: 100 })
|
||||
.toBuffer()
|
||||
.then(data => {
|
||||
// 100 pixels high, auto-scaled width
|
||||
});
|
||||
```
|
||||
|
||||
```javascript
|
||||
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
|
||||
});
|
||||
```
|
||||
|
||||
```javascript
|
||||
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);
|
||||
```
|
||||
|
||||
```javascript
|
||||
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
|
||||
});
|
||||
```
|
||||
|
||||
```javascript
|
||||
const scaleByHalf = await sharp(input)
|
||||
.metadata()
|
||||
.then(({ width }) => sharp(input)
|
||||
.resize(Math.round(width * 0.5))
|
||||
.toBuffer()
|
||||
);
|
||||
```
|
||||
|
||||
- Throws **[Error][13]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## extend
|
||||
|
||||
Extends/pads the edges of the image with the provided background colour.
|
||||
This operation will always occur after resizing and extraction, if any.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `extend` **([number][8] \| [Object][9])** single pixel count to add to all edges or an Object with per-edge counts
|
||||
- `extend.top` **[number][8]?**
|
||||
- `extend.left` **[number][8]?**
|
||||
- `extend.bottom` **[number][8]?**
|
||||
- `extend.right` **[number][8]?**
|
||||
- `extend.background` **([String][10] \| [Object][9])** background colour, parsed by the [color][11] module, defaults to black without transparency. (optional, default `{r:0,g:0,b:0,alpha:1}`)
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
// 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 }
|
||||
})
|
||||
...
|
||||
```
|
||||
|
||||
- Throws **[Error][13]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## extract
|
||||
|
||||
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` before and after for both.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `options` **[Object][9]** describes the region to extract using integral pixel values
|
||||
- `options.left` **[number][8]** zero-indexed offset from left edge
|
||||
- `options.top` **[number][8]** zero-indexed offset from top edge
|
||||
- `options.width` **[number][8]** width of region to extract
|
||||
- `options.height` **[number][8]** height of region to extract
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
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.
|
||||
});
|
||||
```
|
||||
|
||||
```javascript
|
||||
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
|
||||
});
|
||||
```
|
||||
|
||||
- Throws **[Error][13]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## trim
|
||||
|
||||
Trim "boring" pixels from all edges that contain values similar to the top-left pixel.
|
||||
Images consisting entirely of a single colour will calculate "boring" using the alpha channel, if any.
|
||||
|
||||
The `info` response Object, obtained from callback of `.toFile()` or `.toBuffer()`,
|
||||
will contain `trimOffsetLeft` and `trimOffsetTop` properties.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `threshold` **[number][8]** the allowed difference from the top-left pixel, a number greater than zero. (optional, default `10`)
|
||||
|
||||
|
||||
- Throws **[Error][13]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
[1]: https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit
|
||||
|
||||
[2]: https://developer.mozilla.org/en-US/docs/Web/CSS/object-position
|
||||
|
||||
[3]: https://en.wikipedia.org/wiki/Entropy_%28information_theory%29
|
||||
|
||||
[4]: http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation
|
||||
|
||||
[5]: https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline
|
||||
|
||||
[6]: https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf
|
||||
|
||||
[7]: https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel
|
||||
|
||||
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
|
||||
|
||||
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
|
||||
|
||||
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
|
||||
|
||||
[11]: https://www.npmjs.org/package/color
|
||||
|
||||
[12]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
||||
|
||||
[13]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
|
||||
185
docs/api-utility.md
Normal file
185
docs/api-utility.md
Normal file
@@ -0,0 +1,185 @@
|
||||
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
|
||||
|
||||
## format
|
||||
|
||||
An Object containing nested boolean values representing the available input and output formats/methods.
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
console.log(sharp.format);
|
||||
```
|
||||
|
||||
Returns **[Object][1]**
|
||||
|
||||
## interpolators
|
||||
|
||||
An Object containing the available interpolators and their proper values
|
||||
|
||||
Type: [string][2]
|
||||
|
||||
### nearest
|
||||
|
||||
[Nearest neighbour interpolation][3]. Suitable for image enlargement only.
|
||||
|
||||
### bilinear
|
||||
|
||||
[Bilinear interpolation][4]. Faster than bicubic but with less smooth results.
|
||||
|
||||
### bicubic
|
||||
|
||||
[Bicubic interpolation][5] (the default).
|
||||
|
||||
### locallyBoundedBicubic
|
||||
|
||||
[LBB interpolation][6]. Prevents some "[acutance][7]" but typically reduces performance by a factor of 2.
|
||||
|
||||
### nohalo
|
||||
|
||||
[Nohalo interpolation][8]. Prevents acutance but typically reduces performance by a factor of 3.
|
||||
|
||||
### vertexSplitQuadraticBasisSpline
|
||||
|
||||
[VSQBS interpolation][9]. Prevents "staircasing" when enlarging.
|
||||
|
||||
## versions
|
||||
|
||||
An Object containing the version numbers of libvips and its dependencies.
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
console.log(sharp.versions);
|
||||
```
|
||||
|
||||
## cache
|
||||
|
||||
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.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `options` **([Object][1] \| [boolean][10])** Object with the following attributes, or boolean where true uses default cache settings and false removes all caching (optional, default `true`)
|
||||
- `options.memory` **[number][11]** is the maximum memory in MB to use for this cache (optional, default `50`)
|
||||
- `options.files` **[number][11]** is the maximum number of files to hold open (optional, default `20`)
|
||||
- `options.items` **[number][11]** is the maximum number of operations to cache (optional, default `100`)
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
const stats = sharp.cache();
|
||||
```
|
||||
|
||||
```javascript
|
||||
sharp.cache( { items: 200 } );
|
||||
sharp.cache( { files: 0 } );
|
||||
sharp.cache(false);
|
||||
```
|
||||
|
||||
Returns **[Object][1]**
|
||||
|
||||
## concurrency
|
||||
|
||||
Gets or, when a concurrency is provided, sets
|
||||
the number of threads _libvips'_ should create to process each image.
|
||||
The default value is the number of CPU cores.
|
||||
A value of `0` will reset to this default.
|
||||
|
||||
The maximum number of images that can be processed in parallel
|
||||
is limited by libuv's `UV_THREADPOOL_SIZE` environment variable.
|
||||
|
||||
This method always returns the current concurrency.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `concurrency` **[number][11]?**
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
const threads = sharp.concurrency(); // 4
|
||||
sharp.concurrency(2); // 2
|
||||
sharp.concurrency(0); // 4
|
||||
```
|
||||
|
||||
Returns **[number][11]** concurrency
|
||||
|
||||
## queue
|
||||
|
||||
An EventEmitter that emits a `change` event when a task is either:
|
||||
|
||||
- queued, waiting for _libuv_ to provide a worker thread
|
||||
- complete
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
sharp.queue.on('change', function(queueLength) {
|
||||
console.log('Queue contains ' + queueLength + ' task(s)');
|
||||
});
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
const counters = sharp.counters(); // { queue: 2, process: 4 }
|
||||
```
|
||||
|
||||
Returns **[Object][1]**
|
||||
|
||||
## simd
|
||||
|
||||
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.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `simd` **[boolean][10]** (optional, default `true`)
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
const simd = sharp.simd();
|
||||
// simd is `true` if the runtime use of liborc is currently enabled
|
||||
```
|
||||
|
||||
```javascript
|
||||
const simd = sharp.simd(false);
|
||||
// prevent libvips from using liborc at runtime
|
||||
```
|
||||
|
||||
Returns **[boolean][10]**
|
||||
|
||||
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
|
||||
|
||||
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
|
||||
|
||||
[3]: http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation
|
||||
|
||||
[4]: http://en.wikipedia.org/wiki/Bilinear_interpolation
|
||||
|
||||
[5]: http://en.wikipedia.org/wiki/Bicubic_interpolation
|
||||
|
||||
[6]: https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100
|
||||
|
||||
[7]: http://en.wikipedia.org/wiki/Acutance
|
||||
|
||||
[8]: http://eprints.soton.ac.uk/268086/
|
||||
|
||||
[9]: https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48
|
||||
|
||||
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
||||
|
||||
[11]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
|
||||
724
docs/api.md
724
docs/api.md
@@ -1,724 +0,0 @@
|
||||
# API
|
||||
|
||||
```javascript
|
||||
var sharp = require('sharp');
|
||||
```
|
||||
|
||||
### Input
|
||||
|
||||
#### sharp([input], [options])
|
||||
|
||||
Constructor to which further methods are chained.
|
||||
|
||||
`input`, if present, can be one of:
|
||||
|
||||
* Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
|
||||
* String containing the path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
|
||||
|
||||
JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data
|
||||
can be streamed into the object when `input` is `null` or `undefined`.
|
||||
|
||||
`options`, if present, is an Object with the following optional attributes:
|
||||
|
||||
* `density` an integral number representing the DPI for vector images, defaulting to 72.
|
||||
* `raw` an Object containing `width`, `height` and `channels` when providing uncompressed data. See `raw()` for pixel ordering.
|
||||
|
||||
The object returned by the constructor implements the
|
||||
[stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.
|
||||
|
||||
JPEG, PNG or WebP format image data can be streamed out from this object.
|
||||
When using Stream based output, derived attributes are available from the `info` event.
|
||||
|
||||
```javascript
|
||||
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
|
||||
});
|
||||
```
|
||||
|
||||
```javascript
|
||||
// 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);
|
||||
```
|
||||
|
||||
#### 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 used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
|
||||
* `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
|
||||
* `density`: Number of pixels per inch (DPI), 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
|
||||
|
||||
A Promises/A+ promise is returned when `callback` is not provided.
|
||||
|
||||
```javascript
|
||||
var 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
|
||||
});
|
||||
```
|
||||
|
||||
#### clone()
|
||||
|
||||
Takes a "snapshot" of the 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.
|
||||
|
||||
```javascript
|
||||
var 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
|
||||
```
|
||||
|
||||
#### 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.
|
||||
|
||||
#### limitInputPixels(pixels)
|
||||
|
||||
Do not process input images where the number of pixels (width * height) exceeds this limit.
|
||||
|
||||
`pixels` is either an integral Number of pixels, with a value between 1 and the default 268402689 (0x3FFF * 0x3FFF) or
|
||||
a boolean. `false` will disable checking while `true` will revert to the default limit.
|
||||
|
||||
### Resizing
|
||||
|
||||
#### resize([width], [height], [options])
|
||||
|
||||
Scale output to `width` x `height`. By default, the resized image is cropped to the exact size specified.
|
||||
|
||||
`width` is the integral Number of pixels wide the resultant image should be, between 1 and 16383 (0x3FFF). Use `null` or `undefined` to auto-scale the width to match the height.
|
||||
|
||||
`height` is the integral Number of pixels high the resultant image should be, between 1 and 16383. Use `null` or `undefined` to auto-scale the height to match the width.
|
||||
|
||||
`options` is an optional Object. If present, it can contain one or more of:
|
||||
|
||||
* `options.kernel`, the kernel to use for image reduction, defaulting to `lanczos3`.
|
||||
* `options.interpolator`, the interpolator to use for image enlargement, defaulting to `bicubic`.
|
||||
|
||||
Possible kernels are:
|
||||
|
||||
* `cubic`: Use a [Catmull-Rom spline](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline).
|
||||
* `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).
|
||||
|
||||
Possible interpolators are:
|
||||
|
||||
* `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation).
|
||||
* `bilinear`: Use [bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation), faster than bicubic but with less smooth results.
|
||||
* `vertexSplitQuadraticBasisSpline`: Use the smoother [VSQBS interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48) to prevent "staircasing" when enlarging.
|
||||
* `bicubic`: Use [bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) (the default).
|
||||
* `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)" but typically reduces performance by a factor of 2.
|
||||
* `nohalo`: Use [Nohalo interpolation](http://eprints.soton.ac.uk/268086/), which prevents acutance but typically reduces performance by a factor of 3.
|
||||
|
||||
```javascript
|
||||
sharp(inputBuffer)
|
||||
.resize(200, 300, {
|
||||
kernel: sharp.kernel.lanczos2,
|
||||
interpolator: 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 lanczos2/nohalo scaled version, embedded on a white canvas,
|
||||
// of the image data in inputBuffer
|
||||
});
|
||||
```
|
||||
|
||||
#### crop([option])
|
||||
|
||||
Crop the resized image to the exact size specified, the default behaviour.
|
||||
|
||||
`option`, if present, is an attribute of:
|
||||
|
||||
* `sharp.gravity` e.g. `sharp.gravity.north`, to crop to an edge or corner, or
|
||||
* `sharp.strategy` e.g. `sharp.strategy.entropy`, to crop dynamically.
|
||||
|
||||
Possible attributes of `sharp.gravity` are
|
||||
`north`, `northeast`, `east`, `southeast`, `south`,
|
||||
`southwest`, `west`, `northwest`, `center` and `centre`.
|
||||
|
||||
Possible attributes of the experimental `sharp.strategy` are:
|
||||
|
||||
* `entropy`: resize so one dimension is at its target size
|
||||
then repeatedly remove pixels from the edge with the lowest
|
||||
[Shannon entropy](https://en.wikipedia.org/wiki/Entropy_%28information_theory%29)
|
||||
until it too reaches the target size.
|
||||
|
||||
The default crop option is a `center`/`centre` gravity.
|
||||
|
||||
```javascript
|
||||
var transformer = sharp()
|
||||
.resize(200, 200)
|
||||
.crop(sharp.strategy.entropy)
|
||||
.on('error', function(err) {
|
||||
console.log(err);
|
||||
});
|
||||
// Read image data from readableStream
|
||||
// Write 200px square auto-cropped image data to writableStream
|
||||
readableStream.pipe(transformer).pipe(writableStream);
|
||||
```
|
||||
|
||||
#### 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.
|
||||
|
||||
```javascript
|
||||
sharp('input.gif')
|
||||
.resize(200, 300)
|
||||
.background({r: 0, g: 0, b: 0, a: 0})
|
||||
.embed()
|
||||
.toFormat(sharp.format.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
|
||||
});
|
||||
```
|
||||
|
||||
#### max()
|
||||
|
||||
Preserving aspect ratio,
|
||||
resize the image to be as large as possible
|
||||
while ensuring its dimensions are less than or equal to
|
||||
the `width` and `height` specified.
|
||||
|
||||
Both `width` and `height` must be provided via
|
||||
`resize` otherwise the behaviour will default to `crop`.
|
||||
|
||||
```javascript
|
||||
sharp(inputBuffer)
|
||||
.resize(200, 200)
|
||||
.max()
|
||||
.toFormat('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
|
||||
});
|
||||
```
|
||||
|
||||
#### min()
|
||||
|
||||
Preserving aspect ratio,
|
||||
resize the image to be as small as possible
|
||||
while ensuring its dimensions are greater than or equal to
|
||||
the `width` and `height` specified.
|
||||
|
||||
Both `width` and `height` must be provided via `resize` otherwise the behaviour will default to `crop`.
|
||||
|
||||
#### 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*".
|
||||
|
||||
#### ignoreAspectRatio()
|
||||
|
||||
Ignoring the aspect ratio of the input, stretch the image to the exact `width` and/or `height` provided via `resize`.
|
||||
|
||||
### Operations
|
||||
|
||||
#### extract({ left: left, top: top, width: width, height: height })
|
||||
|
||||
Extract a region of the image. Can be used with or without a `resize` operation.
|
||||
|
||||
`left` and `top` 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.
|
||||
|
||||
```javascript
|
||||
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.
|
||||
});
|
||||
```
|
||||
|
||||
```javascript
|
||||
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
|
||||
});
|
||||
```
|
||||
|
||||
#### background(rgba)
|
||||
|
||||
Set the background for the `embed`, `flatten` and `extend` 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.
|
||||
|
||||
#### flatten()
|
||||
|
||||
Merge alpha transparency channel, if any, with `background`.
|
||||
|
||||
#### extend(extension)
|
||||
|
||||
Extends/pads the edges of the image with `background`, where `extension` is one of:
|
||||
|
||||
* a Number representing the pixel count to add to each edge, or
|
||||
* an Object containing `top`, `left`, `bottom` and `right` attributes, each a Number of pixels to add to that edge.
|
||||
|
||||
This operation will always occur after resizing and extraction, if any.
|
||||
|
||||
```javascript
|
||||
// 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)
|
||||
.background({r: 0, g: 0, b: 0, a: 0})
|
||||
.extend({top: 10, bottom: 20, left: 10, right: 10})
|
||||
...
|
||||
```
|
||||
|
||||
#### negate()
|
||||
|
||||
Produces the "negative" of the image. White => Black, Black => White, Blue => Yellow, etc.
|
||||
|
||||
#### 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.
|
||||
|
||||
Method order is important when both rotating and extracting regions, for example `rotate(x).extract(y)` will produce a different result to `extract(y).rotate(x)`.
|
||||
|
||||
The use of `rotate` implies the removal of the EXIF `Orientation` tag, if any.
|
||||
|
||||
```javascript
|
||||
var pipeline = sharp()
|
||||
.rotate()
|
||||
.resize(null, 200)
|
||||
.progressive()
|
||||
.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);
|
||||
```
|
||||
|
||||
#### flip()
|
||||
|
||||
Flip the image about the vertical Y axis. This always occurs after rotation, if any.
|
||||
The use of `flip` implies the removal of the EXIF `Orientation` tag, if any.
|
||||
|
||||
#### flop()
|
||||
|
||||
Flop the image about the horizontal X axis. This always occurs after rotation, if any.
|
||||
The use of `flop` implies the removal of the EXIF `Orientation` tag, if any.
|
||||
|
||||
#### 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 sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
|
||||
|
||||
#### sharpen([sigma], [flat], [jagged])
|
||||
|
||||
When used without parameters, performs a fast, mild sharpen of the output image. This typically reduces performance by 10%.
|
||||
|
||||
When a `sigma` 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%.
|
||||
|
||||
* `sigma`, if present, is a Number representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
|
||||
* `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.
|
||||
|
||||
#### threshold([threshold])
|
||||
|
||||
Converts all pixels in the image to greyscale white or black. Any pixel greather-than-or-equal-to the threshold (0..255) will be white. All others will be black.
|
||||
|
||||
* `threshold`, if present, is a Number, representing the level above which pixels will be forced to white.
|
||||
|
||||
#### 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 between 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.
|
||||
|
||||
#### normalize() / normalise()
|
||||
|
||||
Enhance output image contrast by stretching its luminance to cover the full dynamic range. This typically reduces performance by 30%.
|
||||
|
||||
#### overlayWith(image, [options])
|
||||
|
||||
Overlay (composite) a image containing an alpha channel over the processed (resized, extracted etc.) image.
|
||||
|
||||
`image` is one of the following, and must be the same size or smaller than the processed image:
|
||||
|
||||
* Buffer containing PNG, WebP, GIF or SVG image data, or
|
||||
* String containing the path to an image file, with most major transparency formats supported.
|
||||
|
||||
`options`, if present, is an Object with the following optional attributes:
|
||||
|
||||
* `gravity` is a String or an attribute of the `sharp.gravity` Object e.g. `sharp.gravity.north` at which to place the overlay, defaulting to `center`/`centre`.
|
||||
|
||||
```javascript
|
||||
sharp('input.png')
|
||||
.rotate(180)
|
||||
.resize(300)
|
||||
.flatten()
|
||||
.background('#ff6600')
|
||||
.overlayWith('overlay.png', { gravity: sharp.gravity.southeast } )
|
||||
.sharpen()
|
||||
.withMetadata()
|
||||
.quality(90)
|
||||
.webp()
|
||||
.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!
|
||||
});
|
||||
```
|
||||
|
||||
### Output
|
||||
|
||||
#### toFile(path, [callback])
|
||||
|
||||
`path` is a String containing the path to write the image data to.
|
||||
|
||||
If an explicit output format is not selected, it will be inferred from the extension, with JPEG, PNG, WebP, TIFF and DZI 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`, `height` and `channels`.
|
||||
|
||||
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`, `height` and `channels`.
|
||||
|
||||
A Promises/A+ promise is returned when `callback` is not provided.
|
||||
|
||||
#### jpeg()
|
||||
|
||||
Use JPEG format for the output image.
|
||||
|
||||
#### png()
|
||||
|
||||
Use PNG format for the output image.
|
||||
|
||||
#### webp()
|
||||
|
||||
Use WebP format for the output image.
|
||||
|
||||
#### raw()
|
||||
|
||||
Provide raw, uncompressed uint8 (unsigned char) image data for Buffer and Stream based output.
|
||||
|
||||
The number of channels depends on the input image and selected options.
|
||||
|
||||
* 1 channel for images converted to `greyscale()`, with each byte representing one pixel.
|
||||
* 3 channels for colour images without alpha transparency, with bytes ordered \[red, green, blue, red, green, blue, etc.\]).
|
||||
* 4 channels for colour images with alpha transparency, with bytes ordered \[red, green, blue, alpha, red, green, blue, alpha, etc.\].
|
||||
|
||||
#### toFormat(format)
|
||||
|
||||
Convenience method for the above output format methods, where `format` is either:
|
||||
|
||||
* an attribute of the `sharp.format` Object e.g. `sharp.format.jpeg`, or
|
||||
* a String containing `jpeg`, `png`, `webp` or `raw`.
|
||||
|
||||
#### 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([metadata])
|
||||
|
||||
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 optional `metadata` parameter, if present, is an Object with the attributes to update.
|
||||
New attributes cannot be inserted, only existing attributes updated.
|
||||
|
||||
* `orientation` is an integral Number between 1 and 8, used to update the value of the EXIF `Orientation` tag.
|
||||
This has no effect if the input image does not have an EXIF `Orientation` tag.
|
||||
|
||||
The default behaviour, when `withMetadata` is not used, is to strip all metadata and convert to the device-independent sRGB colour space.
|
||||
|
||||
#### tile(options)
|
||||
|
||||
The size, overlap, container and directory layout to use when generating square Deep Zoom image pyramid tiles.
|
||||
|
||||
`options` is an Object with one or more of the following attributes:
|
||||
|
||||
* `size` is an integral Number between 1 and 8192. The default value is 256 pixels.
|
||||
* `overlap` is an integral Number between 0 and 8192. The default value is 0 pixels.
|
||||
* `container` is a String, with value `fs` or `zip`. The default value is `fs`.
|
||||
* `layout` is a String, with value `dz`, `zoomify` or `google`. The default value is `dz`.
|
||||
|
||||
You can also use the file extension .zip or .szi to write to a ZIP container instead of the filesystem.
|
||||
|
||||
```javascript
|
||||
sharp('input.tiff')
|
||||
.tile({
|
||||
size: 512
|
||||
})
|
||||
.toFile('output.dzi', function(err, info) {
|
||||
// output.dzi is the Deep Zoom XML definition
|
||||
// output_files contains 512x512 tiles grouped by zoom level
|
||||
});
|
||||
```
|
||||
|
||||
#### withoutChromaSubsampling()
|
||||
|
||||
Disable the use of [chroma subsampling](http://en.wikipedia.org/wiki/Chroma_subsampling) with JPEG output (4:4:4).
|
||||
|
||||
This can improve colour representation at higher quality settings (90+),
|
||||
but usually increases output file size and typically reduces performance by 25%.
|
||||
|
||||
The default behaviour is to use chroma subsampling (4:2:0).
|
||||
|
||||
#### 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()
|
||||
|
||||
An advanced setting to disable adaptive row filtering for the lossless PNG output format.
|
||||
|
||||
#### trellisQuantisation() / trellisQuantization()
|
||||
|
||||
_Requires libvips to have been compiled with mozjpeg support_
|
||||
|
||||
An advanced setting to apply the use of
|
||||
[trellis quantisation](http://en.wikipedia.org/wiki/Trellis_quantization) with JPEG output.
|
||||
Reduces file size and slightly increases relative quality at the cost of increased compression time.
|
||||
|
||||
#### overshootDeringing()
|
||||
|
||||
_Requires libvips to have been compiled with mozjpeg support_
|
||||
|
||||
An advanced setting to reduce the effects of
|
||||
[ringing](http://en.wikipedia.org/wiki/Ringing_%28signal%29) in JPEG output,
|
||||
in particular where black text appears on a white background (or vice versa).
|
||||
|
||||
#### optimiseScans() / optimizeScans()
|
||||
|
||||
_Requires libvips to have been compiled with mozjpeg support_
|
||||
|
||||
An advanced setting for progressive (interlace) JPEG output.
|
||||
Calculates which spectrum of DCT coefficients uses the fewest bits.
|
||||
Usually reduces file size at the cost of increased compression time.
|
||||
|
||||
### Attributes
|
||||
|
||||
#### format
|
||||
|
||||
An Object containing nested boolean values
|
||||
representing the available input and output formats/methods,
|
||||
for example:
|
||||
|
||||
```javascript
|
||||
> console.dir(sharp.format);
|
||||
|
||||
{ jpeg: { id: 'jpeg',
|
||||
input: { file: true, buffer: true, stream: true },
|
||||
output: { file: true, buffer: true, stream: true } },
|
||||
png: { id: 'png',
|
||||
input: { file: true, buffer: true, stream: true },
|
||||
output: { file: true, buffer: true, stream: true } },
|
||||
webp: { id: 'webp',
|
||||
input: { file: true, buffer: true, stream: true },
|
||||
output: { file: true, buffer: true, stream: true } },
|
||||
tiff: { id: 'tiff',
|
||||
input: { file: true, buffer: true, stream: true },
|
||||
output: { file: true, buffer: false, stream: false } },
|
||||
raw: { id: 'raw',
|
||||
input: { file: false, buffer: false, stream: false },
|
||||
output: { file: false, buffer: true, stream: true } } }
|
||||
```
|
||||
|
||||
#### queue
|
||||
|
||||
An EventEmitter that emits a `change` event when a task is either:
|
||||
|
||||
* queued, waiting for _libuv_ to provide a worker thread
|
||||
* complete
|
||||
|
||||
```javascript
|
||||
sharp.queue.on('change', function(queueLength) {
|
||||
console.log('Queue contains ' + queueLength + ' task(s)');
|
||||
});
|
||||
```
|
||||
|
||||
#### versions
|
||||
|
||||
An Object containing the version numbers of libvips and, on Linux, its dependencies.
|
||||
|
||||
```javascript
|
||||
console.log(sharp.versions);
|
||||
```
|
||||
|
||||
### Utilities
|
||||
|
||||
#### sharp.cache([options])
|
||||
|
||||
If `options` is provided, sets the limits of _libvips'_ operation cache.
|
||||
|
||||
* `options.memory` is the maximum memory in MB to use for this cache, with a default value of 50
|
||||
* `options.files` is the maximum number of files to hold open, with a default value of 20
|
||||
* `options.items` is the maximum number of operations to cache, with a default value of 100
|
||||
|
||||
`options` can also be a boolean, where `true` enables the default cache settings and `false` disables all caching.
|
||||
|
||||
This method always returns cache statistics, useful for determining how much working memory is required for a particular task.
|
||||
|
||||
```javascript
|
||||
var stats = sharp.cache();
|
||||
```
|
||||
|
||||
```javascript
|
||||
sharp.cache( { items: 200 } );
|
||||
sharp.cache( { files: 0 } );
|
||||
sharp.cache(false);
|
||||
```
|
||||
|
||||
#### 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 }
|
||||
```
|
||||
|
||||
#### sharp.simd([enable])
|
||||
|
||||
_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.
|
||||
|
||||
* `enable`, if present, is a boolean where `true` enables and `false` disables the use of SIMD.
|
||||
|
||||
This method always returns the current state.
|
||||
|
||||
This feature is currently disabled by default
|
||||
but future versions may enable it by default.
|
||||
|
||||
When enabled, versions of liborc prior to 0.4.24
|
||||
and versions of libvips prior to 8.2.0
|
||||
have been known to crash under heavy load.
|
||||
|
||||
```javascript
|
||||
var simd = sharp.simd();
|
||||
// simd is `true` if SIMD is currently enabled
|
||||
```
|
||||
|
||||
```javascript
|
||||
var simd = sharp.simd(true);
|
||||
// attempts to enable the use of SIMD, returning true if available
|
||||
```
|
||||
File diff suppressed because it is too large
Load Diff
117
docs/firebase.json
Normal file
117
docs/firebase.json
Normal file
@@ -0,0 +1,117 @@
|
||||
{
|
||||
"hosting": {
|
||||
"site": "pixelplumbing-sharp",
|
||||
"public": ".",
|
||||
"ignore": [
|
||||
".*",
|
||||
"firebase.json",
|
||||
"*.md",
|
||||
"image/**",
|
||||
"search-index/**"
|
||||
],
|
||||
"headers": [
|
||||
{
|
||||
"source": "**",
|
||||
"headers": [
|
||||
{
|
||||
"key": "Cache-Control",
|
||||
"value": "max-age=86400"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
208
docs/humans.txt
Normal file
208
docs/humans.txt
Normal file
@@ -0,0 +1,208 @@
|
||||
/* 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
|
||||
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">
|
||||
<!-- Copyright 2019 Lovell Fuller. This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) License. -->
|
||||
<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: 592 B |
216
docs/index.html
Normal file
216
docs/index.html
Normal file
File diff suppressed because one or more lines are too long
111
docs/index.md
111
docs/index.md
@@ -1,111 +0,0 @@
|
||||
# sharp
|
||||
|
||||
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 and WebP images of varying dimensions.
|
||||
|
||||
Resizing an image is typically 4x-5x faster than using the
|
||||
quickest ImageMagick and GraphicsMagick settings.
|
||||
|
||||
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 Windows (x64), Linux and ARMv6+ systems do not require
|
||||
the installation of any external runtime dependencies.
|
||||
|
||||
Use with OS X is as simple as running `brew install homebrew/science/vips`
|
||||
to install the libvips dependency.
|
||||
|
||||
[](https://coveralls.io/r/lovell/sharp?branch=master)
|
||||
|
||||
### Formats
|
||||
|
||||
This module supports reading JPEG, PNG, WebP, TIFF, GIF and SVG images.
|
||||
|
||||
Output images can be in JPEG, PNG and WebP 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)
|
||||
and [Leaflet](https://github.com/turban/Leaflet.Zoomify).
|
||||
|
||||
### Fast
|
||||
|
||||
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).
|
||||
|
||||
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/A+ are supported.
|
||||
|
||||
### Optimal
|
||||
|
||||
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/).
|
||||
|
||||
### Contributing
|
||||
|
||||
A [guide for contributors](https://github.com/lovell/sharp/blob/master/CONTRIBUTING.md)
|
||||
covers reporting bugs, requesting features and submitting code changes.
|
||||
|
||||
### Credits
|
||||
|
||||
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)
|
||||
* [Maurus Cuelenaere](https://github.com/mcuelenaere)
|
||||
* [Linus Unnebäck](https://github.com/LinusU)
|
||||
* [Victor Mateevitsi](https://github.com/mvictoras)
|
||||
* [Alaric Holloway](https://github.com/skedastik)
|
||||
* [Bernhard K. Weisshuhn](https://github.com/bkw)
|
||||
* [David A. Carley](https://github.com/dacarley)
|
||||
* [John Tobin](https://github.com/jtobinisaniceguy)
|
||||
* [Kenton Gray](https://github.com/kentongray)
|
||||
* [Felix Bünemann](https://github.com/felixbuenemann)
|
||||
* [Samy Al Zahrani](https://github.com/salzhrani)
|
||||
|
||||
Thank you!
|
||||
|
||||
### Licence
|
||||
|
||||
Copyright 2013, 2014, 2015, 2016 Lovell Fuller and contributors.
|
||||
|
||||
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
|
||||
[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0.html)
|
||||
|
||||
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.
|
||||
307
docs/install.md
307
docs/install.md
@@ -4,192 +4,183 @@
|
||||
npm install sharp
|
||||
```
|
||||
|
||||
### Prerequisites
|
||||
|
||||
* C++11 compatible compiler such as gcc 4.8+, clang 3.0+ or MSVC 2013+
|
||||
* [node-gyp](https://github.com/TooTallNate/node-gyp#installation)
|
||||
|
||||
### Linux
|
||||
|
||||
[](https://travis-ci.org/lovell/sharp)
|
||||
[](https://circleci.com/gh/lovell/sharp)
|
||||
|
||||
libvips and its dependencies are fetched and stored within `node_modules/sharp/lib` during `npm install`.
|
||||
This involves an automated HTTPS download of approximately 6.7MB.
|
||||
|
||||
Most recent Linux-based operating systems with glibc running on x64 and ARMv6+ CPUs should "just work", e.g.:
|
||||
|
||||
* Debian 7, 8
|
||||
* Ubuntu 12.04, 14.04, 15.10, 16.04
|
||||
* Centos 7
|
||||
* Fedora 22, 23
|
||||
* openSUSE 13.2
|
||||
* Archlinux 2015.06.01
|
||||
* Raspbian Jessie
|
||||
* Amazon Linux 2015.03, 2015.09
|
||||
|
||||
To use your own version of libvips instead of the provided binaries, make sure it is
|
||||
at least the version listed under `config.libvips` in the `package.json` file,
|
||||
that it can be located using `pkg-config --modversion vips-cpp`
|
||||
and that it has been compiled with `_GLIBCXX_USE_CXX11_ABI=0`.
|
||||
|
||||
If you are using non-stadard paths (anything other than `/usr` or `/usr/local`),
|
||||
you might need to set `PKG_CONFIG_PATH` during `npm install`
|
||||
and `LD_LIBRARY_PATH` at runtime.
|
||||
|
||||
This allows the use of newer versions of libvips with older versions of sharp.
|
||||
|
||||
For 32-bit Intel CPUs and older Linux-based operating systems such as Centos 6,
|
||||
a system-wide installation of the most suitable version of
|
||||
libvips and its dependencies can be achieved by running
|
||||
the following command as a user with `sudo` access
|
||||
(requires `curl` and `pkg-config`):
|
||||
|
||||
```sh
|
||||
# WARNING: This script is deprecated. You probably don't need to run it. Please read above.
|
||||
curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash -
|
||||
yarn add sharp
|
||||
```
|
||||
|
||||
For Linux-based operating systems such as Alpine that use musl libc,
|
||||
the smaller stack size means libvips' cache should be disabled
|
||||
via `sharp.cache(false)` to avoid a stack overflow.
|
||||
## Prerequisites
|
||||
|
||||
Beware of Linux OS upgrades that introduce v5.1+ of the `g++` compiler due to
|
||||
[changes](https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_dual_abi.html)
|
||||
in the C++11 ABI.
|
||||
This module assumes the previous behaviour, which can be enforced by setting the
|
||||
`_GLIBCXX_USE_CXX11_ABI=0` environment variable at libvips' compile time.
|
||||
* Node.js v10+
|
||||
|
||||
### Mac OS
|
||||
## Prebuilt binaries
|
||||
|
||||
[](https://travis-ci.org/lovell/sharp)
|
||||
Ready-compiled sharp and libvips binaries are provided for use with
|
||||
Node.js v10+ on the most common platforms:
|
||||
|
||||
libvips must be installed before `npm install` is run.
|
||||
This can be achieved via homebrew:
|
||||
* macOS x64 (>= 10.13)
|
||||
* Linux x64 (glibc >= 2.17, musl >= 1.1.24)
|
||||
* Linux ARM64 (glibc >= 2.29)
|
||||
* Windows x64
|
||||
* Windows x86
|
||||
|
||||
A ~9MB tarball containing libvips and its most commonly used dependencies
|
||||
is downloaded via HTTPS and stored within `node_modules/sharp/vendor` during `npm install`.
|
||||
|
||||
This provides support for the
|
||||
JPEG, PNG, WebP, AVIF, TIFF, GIF (input) and SVG (input) image formats.
|
||||
|
||||
The following platforms have prebuilt libvips but not sharp:
|
||||
|
||||
* Linux ARMv6
|
||||
* Linux ARMv7 (glibc >= 2.28)
|
||||
* Windows ARM64
|
||||
|
||||
The following platforms require compilation of both libvips and sharp from source:
|
||||
|
||||
* macOS ARM64
|
||||
* Linux x86
|
||||
* Linux x64 (glibc <= 2.16, includes RHEL/CentOS 6)
|
||||
* Linux ARM64 (glibc <= 2.28, musl)
|
||||
* Linux PowerPC
|
||||
* FreeBSD
|
||||
* OpenBSD
|
||||
|
||||
## Common problems
|
||||
|
||||
The architecture and platform of Node.js used for `npm install`
|
||||
must be the same as the architecture and platform of Node.js used at runtime.
|
||||
|
||||
The `npm install --unsafe-perm` flag must be used when installing as `root` or a `sudo` user.
|
||||
|
||||
The `npm install --ignore-scripts=false` flag must be used when `npm` has been configured to ignore installation scripts.
|
||||
|
||||
Check the output of running `npm install --verbose sharp` for useful error messages.
|
||||
|
||||
## 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 `config.libvips` in the `package.json` file
|
||||
and that it can be located using `pkg-config --modversion vips-cpp`.
|
||||
|
||||
For help compiling libvips from source, please see
|
||||
[https://libvips.github.io/libvips/install.html#building-libvips-from-a-source-tarball](https://libvips.github.io/libvips/install.html#building-libvips-from-a-source-tarball).
|
||||
|
||||
The use of a globally-installed libvips is unsupported on Windows.
|
||||
|
||||
## 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),
|
||||
* prebuilt sharp binaries do not exist for the current platform, or
|
||||
* when the `npm install --build-from-source` flag is used.
|
||||
|
||||
Building from source requires:
|
||||
|
||||
* C++11 compiler
|
||||
* [node-gyp](https://github.com/nodejs/node-gyp#installation) and its dependencies
|
||||
|
||||
## Custom prebuilt binaries
|
||||
|
||||
This is an advanced approach that most people will not require.
|
||||
|
||||
To install the prebuilt sharp binaries from a custom URL,
|
||||
set the `sharp_binary_host` npm config option
|
||||
or the `npm_config_sharp_binary_host` environment variable.
|
||||
|
||||
To install the prebuilt libvips binaries from a custom URL,
|
||||
set the `sharp_libvips_binary_host` npm config option
|
||||
or the `npm_config_sharp_libvips_binary_host` environment variable.
|
||||
|
||||
The version subpath and file name are appended to these. There should be tarballs available
|
||||
that are compressed with both gzip and Brotli, as the format downloaded will vary depending
|
||||
on whether the user's version of Node supports Brotli decompression (Node.js v10.16.0+)
|
||||
|
||||
For example, if `sharp_libvips_binary_host` is set to `https://hostname/path`
|
||||
and the libvips version is `1.2.3` then the resultant URL will be
|
||||
`https://hostname/path/v1.2.3/libvips-1.2.3-platform-arch.tar.br` or
|
||||
`https://hostname/path/v1.2.3/libvips-1.2.3-platform-arch.tar.gz`.
|
||||
|
||||
See the Chinese mirror below for a further example.
|
||||
|
||||
## Chinese mirror
|
||||
|
||||
Alibaba provide a mirror site based in China containing binaries for both sharp and libvips.
|
||||
|
||||
To use this either set the following configuration:
|
||||
|
||||
```sh
|
||||
brew install homebrew/science/vips
|
||||
npm config set sharp_binary_host "https://npm.taobao.org/mirrors/sharp"
|
||||
npm config set sharp_libvips_binary_host "https://npm.taobao.org/mirrors/sharp-libvips"
|
||||
npm install sharp
|
||||
```
|
||||
|
||||
For WebP suppport use:
|
||||
or set the following environment variables:
|
||||
|
||||
```sh
|
||||
brew install homebrew/science/vips --with-webp
|
||||
npm_config_sharp_binary_host="https://npm.taobao.org/mirrors/sharp" \
|
||||
npm_config_sharp_libvips_binary_host="https://npm.taobao.org/mirrors/sharp-libvips" \
|
||||
npm install sharp
|
||||
```
|
||||
|
||||
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`.
|
||||
## FreeBSD
|
||||
|
||||
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`.
|
||||
The `vips` package must be installed before `npm install` is run.
|
||||
|
||||
### Windows
|
||||
|
||||
[](https://ci.appveyor.com/project/lovell/sharp)
|
||||
|
||||
libvips and its dependencies are fetched and stored within `node_modules\sharp` during `npm install`.
|
||||
This involves an automated HTTPS download of approximately 9MB.
|
||||
|
||||
Only 64-bit (x64) `node.exe` is supported.
|
||||
|
||||
### FreeBSD
|
||||
|
||||
libvips must be installed before `npm install` is run.
|
||||
This can be achieved via [FreshPorts](https://www.freshports.org/graphics/vips/):
|
||||
```sh
|
||||
pkg install -y pkgconf vips
|
||||
```
|
||||
|
||||
```sh
|
||||
cd /usr/ports/graphics/vips/ && make install clean
|
||||
```
|
||||
|
||||
### Heroku
|
||||
## Heroku
|
||||
|
||||
[Alessandro Tagliapietra](https://github.com/alex88) maintains an
|
||||
[Heroku buildpack for libvips](https://github.com/alex88/heroku-buildpack-vips)
|
||||
and its dependencies.
|
||||
Add the
|
||||
[jemalloc buildpack](https://github.com/gaffneyc/heroku-buildpack-jemalloc)
|
||||
to reduce the effects of memory fragmentation.
|
||||
|
||||
### Docker
|
||||
Set
|
||||
[NODE_MODULES_CACHE](https://devcenter.heroku.com/articles/nodejs-support#cache-behavior)
|
||||
to `false` when using the `yarn` package manager.
|
||||
|
||||
[Marc Bachmann](https://github.com/marcbachmann) maintains an
|
||||
[Ubuntu-based Dockerfile for libvips](https://github.com/marcbachmann/dockerfile-libvips).
|
||||
## AWS Lambda
|
||||
|
||||
Set the Lambda runtime to `nodejs12.x`.
|
||||
|
||||
The binaries in the `node_modules` directory of the
|
||||
[deployment package](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-package.html)
|
||||
must be for the Linux x64 platform.
|
||||
|
||||
When building your deployment package on machines other than Linux x64 (glibc),
|
||||
run the following commands:
|
||||
|
||||
macOS:
|
||||
```sh
|
||||
rm -rf node_modules/sharp
|
||||
SHARP_IGNORE_GLOBAL_LIBVIPS=1 npm install --arch=x64 --platform=linux sharp
|
||||
```
|
||||
|
||||
Windows:
|
||||
```sh
|
||||
rmdir /s /q node_modules/sharp
|
||||
npm install --arch=x64 --platform=linux sharp
|
||||
```
|
||||
|
||||
Alternatively a Docker container closely matching the Lambda runtime can be used:
|
||||
|
||||
```sh
|
||||
docker pull marcbachmann/libvips
|
||||
rm -rf node_modules/sharp
|
||||
docker run -v "$PWD":/var/task lambci/lambda:build-nodejs12.x npm install sharp
|
||||
```
|
||||
|
||||
[Will Jordan](https://github.com/wjordan) maintains an
|
||||
[Alpine-based Dockerfile for libvips](https://github.com/wjordan/dockerfile-libvips).
|
||||
To get the best performance select the largest memory available.
|
||||
A 1536 MB function provides ~12x more CPU time than a 128 MB function.
|
||||
|
||||
```sh
|
||||
docker pull wjordan/libvips
|
||||
```
|
||||
## Worker threads
|
||||
|
||||
### AWS Lambda
|
||||
|
||||
In order to use sharp on AWS Lambda, you need to [create a deployment package](http://docs.aws.amazon.com/lambda/latest/dg/nodejs-create-deployment-pkg.html). Because sharp
|
||||
downloads and links libraries for the current platform during `npm install` you have to
|
||||
do this on a system similar to the [Lambda Execution Environment](http://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html). The easiest ways to do this, is to setup a
|
||||
small t2.micro instance using the AMI ID listed in the previous link, ssh into it as ec2-user
|
||||
and follow the instructions below.
|
||||
|
||||
Install dependencies:
|
||||
|
||||
```sh
|
||||
curl -s https://rpm.nodesource.com/setup_4.x | sudo bash -
|
||||
sudo yum install -y gcc-c++ nodejs
|
||||
```
|
||||
|
||||
Copy your code and package.json to the instance using `scp` and create a deployment package:
|
||||
|
||||
```sh
|
||||
cd sharp-lambda-example
|
||||
npm install
|
||||
zip -ur9 ../sharp-lambda-example.zip index.js node_modules
|
||||
```
|
||||
|
||||
You can now download your deployment ZIP using `scp` and upload it to Lambda. Be sure to set your Lambda runtime to Node.js 4.3.
|
||||
|
||||
**Performance Tip:** To get the best performance on Lambda choose the largest memory available because this also gives you the most cpu time (a 1536 MB function is 12x faster than a 128 MB function).
|
||||
|
||||
### Build tools
|
||||
|
||||
* [gulp-responsive](https://www.npmjs.com/package/gulp-responsive)
|
||||
* [grunt-sharp](https://www.npmjs.com/package/grunt-sharp)
|
||||
|
||||
### Security
|
||||
|
||||
Many users of this module process untrusted, user-supplied images,
|
||||
but there are aspects of security to consider when doing so.
|
||||
|
||||
It is possible to compile libvips with support for various third-party image loaders.
|
||||
Each of these libraries has undergone differing levels of security testing.
|
||||
|
||||
Whilst tools such as [American Fuzzy Lop](http://lcamtuf.coredump.cx/afl/)
|
||||
and [Valgrind](http://valgrind.org/) have been used to test
|
||||
the most popular web-based formats, as well as libvips itself,
|
||||
you are advised to perform your own testing and sandboxing.
|
||||
|
||||
ImageMagick in particular has a relatively large attack surface,
|
||||
which can be partially mitigated with a
|
||||
[policy.xml](http://www.imagemagick.org/script/resources.php)
|
||||
configuration file to prevent the use of coders known to be vulnerable.
|
||||
|
||||
```xml
|
||||
<policymap>
|
||||
<policy domain="coder" rights="none" pattern="EPHEMERAL" />
|
||||
<policy domain="coder" rights="none" pattern="URL" />
|
||||
<policy domain="coder" rights="none" pattern="HTTPS" />
|
||||
<policy domain="coder" rights="none" pattern="MVG" />
|
||||
<policy domain="coder" rights="none" pattern="MSL" />
|
||||
<policy domain="coder" rights="none" pattern="TEXT" />
|
||||
<policy domain="coder" rights="none" pattern="SHOW" />
|
||||
<policy domain="coder" rights="none" pattern="WIN" />
|
||||
<policy domain="coder" rights="none" pattern="PLT" />
|
||||
</policymap>
|
||||
```
|
||||
|
||||
Set the `MAGICK_CONFIGURE_PATH` environment variable
|
||||
to the directory containing the `policy.xml` file.
|
||||
The main thread must call `require('sharp')`
|
||||
before worker threads are created
|
||||
to ensure shared libraries remain loaded in memory
|
||||
until after all threads are complete.
|
||||
|
||||
@@ -1,68 +1,63 @@
|
||||
# Performance
|
||||
|
||||
### Test environment
|
||||
A test to benchmark the performance of this module relative to alternatives.
|
||||
|
||||
* AWS EC2 [c4.xlarge](http://aws.amazon.com/ec2/instance-types/#c4) (4x E5-2666 v3 @ 2.90GHz)
|
||||
* Amazon Linux AMI 2016.03.1 (HVM), SSD Volume Type
|
||||
* Node.js v6.2.0
|
||||
## The contenders
|
||||
|
||||
### The contenders
|
||||
|
||||
* [jimp](https://www.npmjs.com/package/jimp) v0.2.24 - Image processing in pure JavaScript. Bilinear interpolation only.
|
||||
* [lwip](https://www.npmjs.com/package/lwip) v0.0.9 - Wrapper around CImg, compiles dependencies from source.
|
||||
* [imagemagick-native](https://www.npmjs.com/package/imagemagick-native) v1.9.2 - Wrapper around libmagick++, supports Buffers only.
|
||||
* [jimp](https://www.npmjs.com/package/jimp) v0.16.1 - Image processing in pure JavaScript. Provides bicubic interpolation.
|
||||
* [mapnik](https://www.npmjs.org/package/mapnik) v4.5.5 - Whilst primarily a map renderer, Mapnik contains bitmap image utilities.
|
||||
* [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.22.0 - Fully featured wrapper around GraphicsMagick's `gm` command line utility.
|
||||
* sharp v0.15.0 / libvips v8.3.1 - Caching within libvips disabled to ensure a fair comparison.
|
||||
* [gm](https://www.npmjs.com/package/gm) v1.23.1 - Fully featured wrapper around GraphicsMagick's `gm` command line utility.
|
||||
* sharp v0.27.0 / libvips v8.10.5 - Caching within libvips disabled to ensure a fair comparison.
|
||||
|
||||
### The task
|
||||
## The task
|
||||
|
||||
Decompress a 2725x2225 JPEG image,
|
||||
resize to 720x480 using Lanczos 3 resampling (where available),
|
||||
then compress to JPEG.
|
||||
resize to 720x588 using Lanczos 3 resampling (where available),
|
||||
then compress to JPEG at a "quality" setting of 80.
|
||||
|
||||
### Results
|
||||
## Test environment
|
||||
|
||||
* AWS EC2 eu-west-1 [c5d.large](https://aws.amazon.com/ec2/instance-types/c5/) (2x Xeon Platinum 8275CL CPU @ 3.00GHz)
|
||||
* Ubuntu 20.10 (ami-046cdbcee95cdd75c)
|
||||
* Node.js v14.15.3
|
||||
|
||||
## Results
|
||||
|
||||
| Module | Input | Output | Ops/sec | Speed-up |
|
||||
| :----------------- | :----- | :----- | ------: | -------: |
|
||||
| jimp (bilinear) | file | file | 0.94 | 1.0 |
|
||||
| jimp (bilinear) | buffer | buffer | 0.98 | 1.0 |
|
||||
| lwip | file | file | 1.14 | 1.2 |
|
||||
| lwip | buffer | buffer | 1.14 | 1.2 |
|
||||
| imagemagick-native | buffer | buffer | 1.66 | 1.8 |
|
||||
| imagemagick | file | file | 5.08 | 5.4 |
|
||||
| gm | buffer | buffer | 5.43 | 5.7 |
|
||||
| gm | file | file | 5.46 | 5.8 |
|
||||
| sharp | stream | stream | 26.52 | 28.2 |
|
||||
| sharp | file | file | 28.16 | 30.0 |
|
||||
| sharp | file | buffer | 28.27 | 30.1 |
|
||||
| sharp | buffer | file | 28.42 | 30.2 |
|
||||
| sharp | buffer | buffer | 28.42 | 30.2 |
|
||||
| jimp | buffer | buffer | 0.77 | 1.0 |
|
||||
| mapnik | buffer | buffer | 3.39 | 4.4 |
|
||||
| gm | buffer | buffer | 4.30 | 5.6 |
|
||||
| gm | file | file | 4.33 | 5.6 |
|
||||
| imagemagick | file | file | 4.39 | 5.7 |
|
||||
| sharp | stream | stream | 23.81 | 30.9 |
|
||||
| sharp | file | file | 25.09 | 32.6 |
|
||||
| sharp | buffer | buffer | 25.60 | 33.2 |
|
||||
|
||||
Greater libvips performance can be expected with caching enabled (default)
|
||||
and using 8+ core machines, especially those with larger L1/L2 CPU caches.
|
||||
and using 4+ 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.
|
||||
|
||||
### Benchmark test prerequisites
|
||||
## Running the benchmark test
|
||||
|
||||
Requires both _ImageMagick_ and _GraphicsMagick_:
|
||||
Requires _ImageMagick_, _GraphicsMagick_ and _Mapnik_:
|
||||
|
||||
```sh
|
||||
brew install imagemagick
|
||||
brew install graphicsmagick
|
||||
brew install mapnik
|
||||
```
|
||||
|
||||
```sh
|
||||
sudo apt-get install imagemagick libmagick++-dev graphicsmagick
|
||||
sudo apt-get install imagemagick libmagick++-dev graphicsmagick libmapnik-dev
|
||||
```
|
||||
|
||||
```sh
|
||||
sudo yum install ImageMagick-devel ImageMagick-c++-devel GraphicsMagick
|
||||
sudo yum install ImageMagick-devel ImageMagick-c++-devel GraphicsMagick mapnik-devel
|
||||
```
|
||||
|
||||
### Running the benchmark test
|
||||
|
||||
```sh
|
||||
git clone https://github.com/lovell/sharp.git
|
||||
cd sharp
|
||||
|
||||
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
60
docs/search-index/build.js
Normal file
60
docs/search-index/build.js
Normal file
@@ -0,0 +1,60 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { extractDescription, extractKeywords } = require('./extract');
|
||||
|
||||
const searchIndex = [];
|
||||
|
||||
// Install
|
||||
const contents = fs.readFileSync(path.join(__dirname, '..', 'install.md'), 'utf8');
|
||||
const matches = contents.matchAll(
|
||||
/## (?<title>[A-Za-z ]+)\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(
|
||||
/\n## (?<title>[A-Za-z]+)\n\n(?<firstparagraph>.+?)\n\n/gs
|
||||
);
|
||||
for (const match of matches) {
|
||||
const { title, firstparagraph } = match.groups;
|
||||
const description = firstparagraph.startsWith('###')
|
||||
? 'Constructor'
|
||||
: extractDescription(firstparagraph);
|
||||
|
||||
searchIndex.push({
|
||||
t: title,
|
||||
d: description,
|
||||
k: extractKeywords(`${title} ${description}`),
|
||||
l: `/api-${section}#${title.toLowerCase()}`
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(__dirname, '..', 'search-index.json'),
|
||||
JSON.stringify(searchIndex)
|
||||
);
|
||||
24
docs/search-index/extract.js
Normal file
24
docs/search-index/extract.js
Normal file
@@ -0,0 +1,24 @@
|
||||
'use strict';
|
||||
|
||||
const stopWords = require('./stop-words');
|
||||
|
||||
const extractDescription = (str) =>
|
||||
str
|
||||
.replace(/\(http[^)]+/g, '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.replace(/[^A-Za-z0-9_/\-,. ]/g, '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.substr(0, 140)
|
||||
.trim();
|
||||
|
||||
const extractKeywords = (str) =>
|
||||
[
|
||||
...new Set(
|
||||
str
|
||||
.split(/[ -/]/)
|
||||
.map((word) => word.toLowerCase().replace(/[^a-z]/g, ''))
|
||||
.filter((word) => word.length > 2 && !stopWords.includes(word))
|
||||
)
|
||||
].join(' ');
|
||||
|
||||
module.exports = { extractDescription, extractKeywords };
|
||||
71
docs/search-index/stop-words.js
Normal file
71
docs/search-index/stop-words.js
Normal file
@@ -0,0 +1,71 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = [
|
||||
'about',
|
||||
'after',
|
||||
'all',
|
||||
'already',
|
||||
'alternative',
|
||||
'always',
|
||||
'and',
|
||||
'any',
|
||||
'are',
|
||||
'been',
|
||||
'before',
|
||||
'can',
|
||||
'containing',
|
||||
'default',
|
||||
'does',
|
||||
'each',
|
||||
'either',
|
||||
'etc',
|
||||
'for',
|
||||
'from',
|
||||
'get',
|
||||
'gets',
|
||||
'given',
|
||||
'has',
|
||||
'have',
|
||||
'how',
|
||||
'image',
|
||||
'its',
|
||||
'may',
|
||||
'more',
|
||||
'most',
|
||||
'much',
|
||||
'must',
|
||||
'non',
|
||||
'not',
|
||||
'occur',
|
||||
'occurs',
|
||||
'over',
|
||||
'perform',
|
||||
'performs',
|
||||
'provide',
|
||||
'provided',
|
||||
'set',
|
||||
'sets',
|
||||
'should',
|
||||
'spelling',
|
||||
'support',
|
||||
'supported',
|
||||
'take',
|
||||
'that',
|
||||
'the',
|
||||
'their',
|
||||
'there',
|
||||
'therefore',
|
||||
'these',
|
||||
'this',
|
||||
'use',
|
||||
'used',
|
||||
'using',
|
||||
'value',
|
||||
'values',
|
||||
'when',
|
||||
'which',
|
||||
'while',
|
||||
'will',
|
||||
'with',
|
||||
'without'
|
||||
];
|
||||
Binary file not shown.
Binary file not shown.
37
install/dll-copy.js
Normal file
37
install/dll-copy.js
Normal file
@@ -0,0 +1,37 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const libvips = require('../lib/libvips');
|
||||
const npmLog = require('npmlog');
|
||||
|
||||
const minimumLibvipsVersion = libvips.minimumLibvipsVersion;
|
||||
|
||||
const platform = process.env.npm_config_platform || process.platform;
|
||||
if (platform === 'win32') {
|
||||
const buildDir = path.join(__dirname, '..', 'build');
|
||||
const buildReleaseDir = path.join(buildDir, 'Release');
|
||||
npmLog.info('sharp', `Creating ${buildReleaseDir}`);
|
||||
try {
|
||||
libvips.mkdirSync(buildDir);
|
||||
libvips.mkdirSync(buildReleaseDir);
|
||||
} catch (err) {}
|
||||
const vendorLibDir = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, 'lib');
|
||||
npmLog.info('sharp', `Copying DLLs from ${vendorLibDir} to ${buildReleaseDir}`);
|
||||
try {
|
||||
fs
|
||||
.readdirSync(vendorLibDir)
|
||||
.filter(function (filename) {
|
||||
return /\.dll$/.test(filename);
|
||||
})
|
||||
.forEach(function (filename) {
|
||||
fs.copyFileSync(
|
||||
path.join(vendorLibDir, filename),
|
||||
path.join(buildReleaseDir, filename)
|
||||
);
|
||||
});
|
||||
} catch (err) {
|
||||
npmLog.error('sharp', err.message);
|
||||
}
|
||||
}
|
||||
134
install/libvips.js
Normal file
134
install/libvips.js
Normal file
@@ -0,0 +1,134 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const stream = require('stream');
|
||||
const zlib = require('zlib');
|
||||
|
||||
const detectLibc = require('detect-libc');
|
||||
const npmLog = require('npmlog');
|
||||
const semver = require('semver');
|
||||
const simpleGet = require('simple-get');
|
||||
const tarFs = require('tar-fs');
|
||||
|
||||
const agent = require('../lib/agent');
|
||||
const libvips = require('../lib/libvips');
|
||||
const platform = require('../lib/platform');
|
||||
|
||||
const minimumGlibcVersionByArch = {
|
||||
arm: '2.28',
|
||||
arm64: '2.29',
|
||||
x64: '2.17'
|
||||
};
|
||||
|
||||
const { minimumLibvipsVersion, minimumLibvipsVersionLabelled } = libvips;
|
||||
const distHost = process.env.npm_config_sharp_libvips_binary_host || 'https://github.com/lovell/sharp-libvips/releases/download';
|
||||
const distBaseUrl = process.env.npm_config_sharp_dist_base_url || process.env.SHARP_DIST_BASE_URL || `${distHost}/v${minimumLibvipsVersionLabelled}/`;
|
||||
const supportsBrotli = ('BrotliDecompress' in zlib);
|
||||
|
||||
const fail = function (err) {
|
||||
npmLog.error('sharp', err.message);
|
||||
if (err.code === 'EACCES') {
|
||||
npmLog.info('sharp', 'Are you trying to install as a root or sudo user? Try again with the --unsafe-perm flag');
|
||||
}
|
||||
npmLog.info('sharp', 'Attempting to build from source via node-gyp but this may fail due to the above error');
|
||||
npmLog.info('sharp', 'Please see https://sharp.pixelplumbing.com/install for required dependencies');
|
||||
process.exit(1);
|
||||
};
|
||||
|
||||
const extractTarball = function (tarPath) {
|
||||
const vendorPath = path.join(__dirname, '..', 'vendor');
|
||||
libvips.mkdirSync(vendorPath);
|
||||
const versionedVendorPath = path.join(vendorPath, minimumLibvipsVersion);
|
||||
libvips.mkdirSync(versionedVendorPath);
|
||||
stream.pipeline(
|
||||
fs.createReadStream(tarPath),
|
||||
supportsBrotli ? new zlib.BrotliDecompress() : new zlib.Gunzip(),
|
||||
tarFs.extract(versionedVendorPath),
|
||||
function (err) {
|
||||
if (err) {
|
||||
if (/unexpected end of file/.test(err.message)) {
|
||||
npmLog.error('sharp', `Please delete ${tarPath} as it is not a valid tarball`);
|
||||
}
|
||||
fail(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
try {
|
||||
const useGlobalLibvips = libvips.useGlobalLibvips();
|
||||
|
||||
if (useGlobalLibvips) {
|
||||
const globalLibvipsVersion = libvips.globalLibvipsVersion();
|
||||
npmLog.info('sharp', `Detected globally-installed libvips v${globalLibvipsVersion}`);
|
||||
npmLog.info('sharp', 'Building from source via node-gyp');
|
||||
process.exit(1);
|
||||
} else if (libvips.hasVendoredLibvips()) {
|
||||
npmLog.info('sharp', `Using existing vendored libvips v${minimumLibvipsVersion}`);
|
||||
} else {
|
||||
// Is this arch/platform supported?
|
||||
const arch = process.env.npm_config_arch || process.arch;
|
||||
const platformAndArch = platform();
|
||||
if (arch === 'ia32' && !platformAndArch.startsWith('win32')) {
|
||||
throw new Error(`Intel Architecture 32-bit systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
|
||||
}
|
||||
if (platformAndArch === 'freebsd-x64' || platformAndArch === 'openbsd-x64' || platformAndArch === 'sunos-x64') {
|
||||
throw new Error(`BSD/SunOS systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
|
||||
}
|
||||
if (detectLibc.family === detectLibc.GLIBC && detectLibc.version) {
|
||||
if (semver.lt(`${detectLibc.version}.0`, `${minimumGlibcVersionByArch[arch]}.0`)) {
|
||||
throw new Error(`Use with glibc ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`);
|
||||
}
|
||||
}
|
||||
|
||||
const supportedNodeVersion = process.env.npm_package_engines_node || require('../package.json').engines.node;
|
||||
if (!semver.satisfies(process.versions.node, supportedNodeVersion)) {
|
||||
throw new Error(`Expected Node.js version ${supportedNodeVersion} but found ${process.versions.node}`);
|
||||
}
|
||||
|
||||
const extension = supportsBrotli ? 'br' : 'gz';
|
||||
|
||||
// Download to per-process temporary file
|
||||
const tarFilename = ['libvips', minimumLibvipsVersion, platformAndArch].join('-') + '.tar.' + extension;
|
||||
const tarPathCache = path.join(libvips.cachePath(), tarFilename);
|
||||
if (fs.existsSync(tarPathCache)) {
|
||||
npmLog.info('sharp', `Using cached ${tarPathCache}`);
|
||||
extractTarball(tarPathCache);
|
||||
} else {
|
||||
const tarPathTemp = path.join(os.tmpdir(), `${process.pid}-${tarFilename}`);
|
||||
const tmpFile = fs.createWriteStream(tarPathTemp);
|
||||
const url = distBaseUrl + tarFilename;
|
||||
npmLog.info('sharp', `Downloading ${url}`);
|
||||
simpleGet({ url: url, agent: agent() }, function (err, response) {
|
||||
if (err) {
|
||||
fail(err);
|
||||
} else if (response.statusCode === 404) {
|
||||
fail(new Error(`Prebuilt libvips ${minimumLibvipsVersion} binaries are not yet available for ${platformAndArch}`));
|
||||
} else if (response.statusCode !== 200) {
|
||||
fail(new Error(`Status ${response.statusCode} ${response.statusMessage}`));
|
||||
} else {
|
||||
response
|
||||
.on('error', fail)
|
||||
.pipe(tmpFile);
|
||||
}
|
||||
});
|
||||
tmpFile
|
||||
.on('error', fail)
|
||||
.on('close', function () {
|
||||
try {
|
||||
// Attempt to rename
|
||||
fs.renameSync(tarPathTemp, tarPathCache);
|
||||
} catch (err) {
|
||||
// Fall back to copy and unlink
|
||||
fs.copyFileSync(tarPathTemp, tarPathCache);
|
||||
fs.unlinkSync(tarPathTemp);
|
||||
}
|
||||
extractTarball(tarPathCache);
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
fail(err);
|
||||
}
|
||||
40
lib/agent.js
Normal file
40
lib/agent.js
Normal file
@@ -0,0 +1,40 @@
|
||||
'use strict';
|
||||
|
||||
const url = require('url');
|
||||
const tunnelAgent = require('tunnel-agent');
|
||||
|
||||
const is = require('./is');
|
||||
|
||||
const proxies = [
|
||||
'HTTPS_PROXY',
|
||||
'https_proxy',
|
||||
'HTTP_PROXY',
|
||||
'http_proxy',
|
||||
'npm_config_https_proxy',
|
||||
'npm_config_proxy'
|
||||
];
|
||||
|
||||
function env (key) {
|
||||
return process.env[key];
|
||||
}
|
||||
|
||||
module.exports = function () {
|
||||
try {
|
||||
const proxy = new url.URL(proxies.map(env).find(is.string));
|
||||
const tunnel = proxy.protocol === 'https:'
|
||||
? tunnelAgent.httpsOverHttps
|
||||
: tunnelAgent.httpsOverHttp;
|
||||
const proxyAuth = proxy.username && proxy.password
|
||||
? `${proxy.username}:${proxy.password}`
|
||||
: null;
|
||||
return tunnel({
|
||||
proxy: {
|
||||
port: Number(proxy.port),
|
||||
host: proxy.hostname,
|
||||
proxyAuth
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
147
lib/channel.js
Normal file
147
lib/channel.js
Normal file
@@ -0,0 +1,147 @@
|
||||
'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.
|
||||
*
|
||||
* @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 alpha channel, if missing. The added alpha channel will be fully opaque. This is a no-op if the image already has an alpha channel.
|
||||
*
|
||||
* @since 0.21.2
|
||||
*
|
||||
* @example
|
||||
* sharp('rgb.jpg')
|
||||
* .ensureAlpha()
|
||||
* .toFile('rgba.png', function(err, info) {
|
||||
* // rgba.png is a 4 channel image with a fully opaque alpha channel
|
||||
* });
|
||||
*
|
||||
* @returns {Sharp}
|
||||
*/
|
||||
function ensureAlpha () {
|
||||
this.options.ensureAlpha = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a single channel from a multi-channel image.
|
||||
*
|
||||
* @example
|
||||
* sharp(input)
|
||||
* .extractChannel('green')
|
||||
* .toColourspace('b-w')
|
||||
* .toFile('green.jpg', function(err, info) {
|
||||
* // info.channels === 1
|
||||
* // green.jpg is a greyscale image containing the green channel of the input
|
||||
* });
|
||||
*
|
||||
* @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: JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data.
|
||||
* 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;
|
||||
};
|
||||
123
lib/colour.js
Normal file
123
lib/colour.js
Normal file
@@ -0,0 +1,123 @@
|
||||
'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.
|
||||
*
|
||||
* @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.
|
||||
* @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 output colourspace.
|
||||
* By default output image will be web-friendly sRGB, with additional channels interpreted as alpha channels.
|
||||
* @param {string} [colourspace] - output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568)
|
||||
* @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,
|
||||
toColourspace,
|
||||
toColorspace,
|
||||
// Private
|
||||
_setBackgroundColourOption
|
||||
});
|
||||
// Class attributes
|
||||
Sharp.colourspace = colourspace;
|
||||
Sharp.colorspace = colourspace;
|
||||
};
|
||||
176
lib/composite.js
Normal file
176
lib/composite.js
Normal file
@@ -0,0 +1,176 @@
|
||||
'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`.
|
||||
*
|
||||
* 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://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode
|
||||
* and https://www.cairographics.org/operators/
|
||||
*
|
||||
* @since 0.22.0
|
||||
*
|
||||
* @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 {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 premultipling 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]
|
||||
* @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;
|
||||
};
|
||||
351
lib/constructor.js
Normal file
351
lib/constructor.js
Normal file
@@ -0,0 +1,351 @@
|
||||
'use strict';
|
||||
|
||||
const util = require('util');
|
||||
const stream = require('stream');
|
||||
const is = require('./is');
|
||||
|
||||
require('./libvips').hasVendoredLibvips();
|
||||
|
||||
/* istanbul ignore next */
|
||||
try {
|
||||
require('../build/Release/sharp.node');
|
||||
} catch (err) {
|
||||
// Bail early if bindings aren't available
|
||||
const help = ['', 'Something went wrong installing the "sharp" module', '', err.message, ''];
|
||||
if (/NODE_MODULE_VERSION/.test(err.message)) {
|
||||
help.push('- Ensure the version of Node.js used at install time matches that used at runtime');
|
||||
} else if (/invalid ELF header/.test(err.message)) {
|
||||
help.push(`- Ensure "${process.platform}" is used at install time as well as runtime`);
|
||||
} else if (/dylib/.test(err.message) && /Incompatible library version/.test(err.message)) {
|
||||
help.push('- Run "brew update && brew upgrade vips"');
|
||||
} else if (/Cannot find module/.test(err.message)) {
|
||||
help.push('- Run "npm rebuild --verbose sharp" and look for errors');
|
||||
} else {
|
||||
help.push(
|
||||
'- Remove the "node_modules/sharp" directory then run',
|
||||
' "npm install --ignore-scripts=false --verbose" and look for errors'
|
||||
);
|
||||
}
|
||||
help.push(
|
||||
'- Consult the installation documentation at https://sharp.pixelplumbing.com/install',
|
||||
'- Search for this error at https://github.com/lovell/sharp/issues', ''
|
||||
);
|
||||
const error = help.join('\n');
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
// 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 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-transluent 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');
|
||||
*
|
||||
* @param {(Buffer|string)} [input] - if present, can be
|
||||
* a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
|
||||
* a String containing the filesystem path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
|
||||
* JPEG, PNG, WebP, 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 {boolean} [options.failOnError=true] - by default halt processing and raise an error when loading invalid images.
|
||||
* Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid.
|
||||
* @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.sequentialRead=false] - Set this to `true` to use sequential rather than random access where possible.
|
||||
* This can reduce memory usage and might improve performance on some systems.
|
||||
* @param {number} [options.density=72] - number representing the DPI for vector images in the range 1 to 100000.
|
||||
* @param {number} [options.pages=1] - number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages.
|
||||
* @param {number} [options.page=0] - page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based.
|
||||
* @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 (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]
|
||||
* @param {number} [options.raw.height]
|
||||
* @param {number} [options.raw.channels] - 1-4
|
||||
* @param {Object} [options.create] - describes a new image to be created.
|
||||
* @param {number} [options.create.width]
|
||||
* @param {number} [options.create.height]
|
||||
* @param {number} [options.create.channels] - 3-4
|
||||
* @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.
|
||||
* @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],
|
||||
withoutEnlargement: 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],
|
||||
negate: false,
|
||||
medianSize: 0,
|
||||
blurSigma: 0,
|
||||
sharpenSigma: 0,
|
||||
sharpenFlat: 1,
|
||||
sharpenJagged: 2,
|
||||
threshold: 0,
|
||||
thresholdGrayscale: true,
|
||||
trimThreshold: 0,
|
||||
gamma: 0,
|
||||
gammaOut: 0,
|
||||
greyscale: false,
|
||||
normalise: false,
|
||||
brightness: 1,
|
||||
saturation: 1,
|
||||
hue: 0,
|
||||
booleanBufferIn: null,
|
||||
booleanFileIn: '',
|
||||
joinChannelIn: [],
|
||||
extractChannel: -1,
|
||||
removeAlpha: false,
|
||||
ensureAlpha: false,
|
||||
colourspace: 'srgb',
|
||||
composite: [],
|
||||
// output
|
||||
fileOut: '',
|
||||
formatOut: 'input',
|
||||
streamOut: false,
|
||||
withMetadata: false,
|
||||
withMetadataOrientation: -1,
|
||||
withMetadataIcc: '',
|
||||
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: 9,
|
||||
pngAdaptiveFiltering: false,
|
||||
pngPalette: false,
|
||||
pngQuality: 100,
|
||||
pngColours: 256,
|
||||
pngDither: 1,
|
||||
webpQuality: 80,
|
||||
webpAlphaQuality: 100,
|
||||
webpLossless: false,
|
||||
webpNearLossless: false,
|
||||
webpSmartSubsample: false,
|
||||
webpReductionEffort: 4,
|
||||
tiffQuality: 80,
|
||||
tiffCompression: 'jpeg',
|
||||
tiffPredictor: 'horizontal',
|
||||
tiffPyramid: false,
|
||||
tiffBitdepth: 8,
|
||||
tiffTile: false,
|
||||
tiffTileHeight: 256,
|
||||
tiffTileWidth: 256,
|
||||
tiffXres: 1.0,
|
||||
tiffYres: 1.0,
|
||||
heifQuality: 50,
|
||||
heifLossless: false,
|
||||
heifCompression: 'av1',
|
||||
heifSpeed: 5,
|
||||
tileSize: 256,
|
||||
tileOverlap: 0,
|
||||
tileContainer: 'fs',
|
||||
tileLayout: 'dz',
|
||||
tileFormat: 'last',
|
||||
tileDepth: 'last',
|
||||
tileAngle: 0,
|
||||
tileSkipBlanks: -1,
|
||||
tileBackground: [255, 255, 255, 255],
|
||||
tileCentre: false,
|
||||
linearA: 1,
|
||||
linearB: 0,
|
||||
// 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;
|
||||
};
|
||||
util.inherits(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({
|
||||
* failOnError: false
|
||||
* });
|
||||
*
|
||||
* 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#gotstreamurl-options
|
||||
* 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;
|
||||
13
lib/index.js
Normal file
13
lib/index.js
Normal file
@@ -0,0 +1,13 @@
|
||||
'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;
|
||||
382
lib/input.js
Normal file
382
lib/input.js
Normal file
@@ -0,0 +1,382 @@
|
||||
'use strict';
|
||||
|
||||
const color = require('color');
|
||||
const is = require('./is');
|
||||
const sharp = require('../build/Release/sharp.node');
|
||||
|
||||
/**
|
||||
* Extract input options, if any, from an object.
|
||||
* @private
|
||||
*/
|
||||
function _inputOptionsFromObject (obj) {
|
||||
const { raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages } = obj;
|
||||
return [raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages].some(is.defined)
|
||||
? { raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages }
|
||||
: undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Object containing input and input-related options.
|
||||
* @private
|
||||
*/
|
||||
function _createInputDescriptor (input, inputOptions, containerOptions) {
|
||||
const inputDescriptor = {
|
||||
failOnError: true,
|
||||
limitInputPixels: Math.pow(0x3FFF, 2),
|
||||
sequentialRead: false
|
||||
};
|
||||
if (is.string(input)) {
|
||||
// filesystem
|
||||
inputDescriptor.file = input;
|
||||
} else if (is.buffer(input)) {
|
||||
// Buffer
|
||||
inputDescriptor.buffer = input;
|
||||
} 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)) {
|
||||
// Fail on error
|
||||
if (is.defined(inputOptions.failOnError)) {
|
||||
if (is.bool(inputOptions.failOnError)) {
|
||||
inputDescriptor.failOnError = inputOptions.failOnError;
|
||||
} else {
|
||||
throw is.invalidParameterError('failOnError', 'boolean', inputOptions.failOnError);
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
// 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) && inputOptions.limitInputPixels >= 0) {
|
||||
inputDescriptor.limitInputPixels = inputOptions.limitInputPixels;
|
||||
} else {
|
||||
throw is.invalidParameterError('limitInputPixels', 'integer >= 0', inputOptions.limitInputPixels);
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
// 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) && is.inRange(inputOptions.create.channels, 3, 4) &&
|
||||
is.defined(inputOptions.create.background)
|
||||
) {
|
||||
inputDescriptor.createWidth = inputOptions.create.width;
|
||||
inputDescriptor.createHeight = inputOptions.create.height;
|
||||
inputDescriptor.createChannels = inputOptions.create.channels;
|
||||
const background = color(inputOptions.create.background);
|
||||
inputDescriptor.createBackground = [
|
||||
background.red(),
|
||||
background.green(),
|
||||
background.blue(),
|
||||
Math.round(background.alpha() * 255)
|
||||
];
|
||||
delete inputDescriptor.buffer;
|
||||
} else {
|
||||
throw new Error('Expected width, height, channels and background to create a new input image');
|
||||
}
|
||||
}
|
||||
} 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 image data.
|
||||
* 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)
|
||||
* - `height`: Number of pixels high (EXIF orientation is not taken into consideration)
|
||||
* - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://libvips.github.io/libvips/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://libvips.github.io/libvips/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.
|
||||
* - `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
|
||||
* - `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
|
||||
*
|
||||
* @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
|
||||
* });
|
||||
*
|
||||
* @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) => {
|
||||
this.on('finish', () => {
|
||||
this._flattenBufferIn();
|
||||
sharp.metadata(this.options, (err, metadata) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(metadata);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
} 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 (experimental)
|
||||
* - `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any (experimental)
|
||||
* - `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram (experimental)
|
||||
*
|
||||
* @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;
|
||||
*
|
||||
* @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
|
||||
});
|
||||
};
|
||||
119
lib/is.js
Normal file
119
lib/is.js
Normal file
@@ -0,0 +1,119 @@
|
||||
'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 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: defined,
|
||||
object: object,
|
||||
plainObject: plainObject,
|
||||
fn: fn,
|
||||
bool: bool,
|
||||
buffer: buffer,
|
||||
string: string,
|
||||
number: number,
|
||||
integer: integer,
|
||||
inRange: inRange,
|
||||
inArray: inArray,
|
||||
invalidParameterError: invalidParameterError
|
||||
};
|
||||
99
lib/libvips.js
Normal file
99
lib/libvips.js
Normal file
@@ -0,0 +1,99 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const spawnSync = require('child_process').spawnSync;
|
||||
const semver = require('semver');
|
||||
const platform = require('./platform');
|
||||
|
||||
const env = process.env;
|
||||
const minimumLibvipsVersionLabelled = env.npm_package_config_libvips || /* istanbul ignore next */
|
||||
require('../package.json').config.libvips;
|
||||
const minimumLibvipsVersion = semver.coerce(minimumLibvipsVersionLabelled).version;
|
||||
|
||||
const spawnSyncOptions = {
|
||||
encoding: 'utf8',
|
||||
shell: true
|
||||
};
|
||||
|
||||
const mkdirSync = function (dirPath) {
|
||||
try {
|
||||
fs.mkdirSync(dirPath);
|
||||
} catch (err) {
|
||||
/* istanbul ignore if */
|
||||
if (err.code !== 'EEXIST') {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const cachePath = function () {
|
||||
const npmCachePath = env.npm_config_cache || /* istanbul ignore next */
|
||||
(env.APPDATA ? path.join(env.APPDATA, 'npm-cache') : path.join(os.homedir(), '.npm'));
|
||||
mkdirSync(npmCachePath);
|
||||
const libvipsCachePath = path.join(npmCachePath, '_libvips');
|
||||
mkdirSync(libvipsCachePath);
|
||||
return libvipsCachePath;
|
||||
};
|
||||
|
||||
const globalLibvipsVersion = function () {
|
||||
if (process.platform !== 'win32') {
|
||||
const globalLibvipsVersion = spawnSync(`PKG_CONFIG_PATH="${pkgConfigPath()}" pkg-config --modversion vips-cpp`, spawnSyncOptions).stdout || '';
|
||||
return globalLibvipsVersion.trim();
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
const hasVendoredLibvips = function () {
|
||||
const currentPlatformId = platform();
|
||||
const vendorPath = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion);
|
||||
let vendorPlatformId;
|
||||
try {
|
||||
vendorPlatformId = require(path.join(vendorPath, 'platform.json'));
|
||||
} catch (err) {}
|
||||
/* istanbul ignore else */
|
||||
if (vendorPlatformId) {
|
||||
/* istanbul ignore else */
|
||||
if (currentPlatformId === vendorPlatformId) {
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(`'${vendorPlatformId}' binaries cannot be used on the '${currentPlatformId}' platform. Please remove the 'node_modules/sharp' directory and run 'npm install' on the '${currentPlatformId}' platform.`);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const pkgConfigPath = function () {
|
||||
if (process.platform !== 'win32') {
|
||||
const brewPkgConfigPath = spawnSync('which brew >/dev/null 2>&1 && eval $(brew --env) && echo $PKG_CONFIG_LIBDIR', spawnSyncOptions).stdout || '';
|
||||
return [brewPkgConfigPath.trim(), env.PKG_CONFIG_PATH, '/usr/local/lib/pkgconfig', '/usr/lib/pkgconfig']
|
||||
.filter(function (p) { return !!p; })
|
||||
.join(':');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
const useGlobalLibvips = function () {
|
||||
if (Boolean(env.SHARP_IGNORE_GLOBAL_LIBVIPS) === true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const globalVipsVersion = globalLibvipsVersion();
|
||||
return !!globalVipsVersion && /* istanbul ignore next */
|
||||
semver.gte(globalVipsVersion, minimumLibvipsVersion);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
minimumLibvipsVersion,
|
||||
minimumLibvipsVersionLabelled,
|
||||
cachePath,
|
||||
globalLibvipsVersion,
|
||||
hasVendoredLibvips,
|
||||
pkgConfigPath,
|
||||
useGlobalLibvips,
|
||||
mkdirSync
|
||||
};
|
||||
599
lib/operation.js
Normal file
599
lib/operation.js
Normal file
@@ -0,0 +1,599 @@
|
||||
'use strict';
|
||||
|
||||
const { flatten: flattenArray } = require('array-flatten');
|
||||
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 270deg 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` implies the removal of the EXIF `Orientation` tag, if any.
|
||||
*
|
||||
* Method order is important when both rotating and 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);
|
||||
*
|
||||
* @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 (!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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flip the image about the vertical Y axis. This always occurs after rotation, if any.
|
||||
* The use of `flip` implies the removal of the EXIF `Orientation` tag, if any.
|
||||
* @param {Boolean} [flip=true]
|
||||
* @returns {Sharp}
|
||||
*/
|
||||
function flip (flip) {
|
||||
this.options.flip = is.bool(flip) ? flip : true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flop the image about the horizontal X axis. This always occurs after rotation, if any.
|
||||
* The use of `flop` implies the removal of the EXIF `Orientation` tag, if any.
|
||||
* @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.interpolator` Object e.g. `sharp.interpolator.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',
|
||||
* interpolate: 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 = flattenArray(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.
|
||||
* Separate control over the level of sharpening in "flat" and "jagged" areas is available.
|
||||
*
|
||||
* @param {number} [sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
|
||||
* @param {number} [flat=1.0] - the level of sharpening to apply to "flat" areas.
|
||||
* @param {number} [jagged=2.0] - the level of sharpening to apply to "jagged" areas.
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
function sharpen (sigma, flat, jagged) {
|
||||
if (!is.defined(sigma)) {
|
||||
// No arguments: default to mild sharpen
|
||||
this.options.sharpenSigma = -1;
|
||||
} else if (is.bool(sigma)) {
|
||||
// Boolean argument: apply mild sharpen?
|
||||
this.options.sharpenSigma = sigma ? -1 : 0;
|
||||
} else if (is.number(sigma) && is.inRange(sigma, 0.01, 10000)) {
|
||||
// Numeric argument: specific sigma
|
||||
this.options.sharpenSigma = sigma;
|
||||
// Control over flat areas
|
||||
if (is.defined(flat)) {
|
||||
if (is.number(flat) && is.inRange(flat, 0, 10000)) {
|
||||
this.options.sharpenFlat = flat;
|
||||
} else {
|
||||
throw is.invalidParameterError('flat', 'number between 0 and 10000', flat);
|
||||
}
|
||||
}
|
||||
// Control over jagged areas
|
||||
if (is.defined(jagged)) {
|
||||
if (is.number(jagged) && is.inRange(jagged, 0, 10000)) {
|
||||
this.options.sharpenJagged = jagged;
|
||||
} else {
|
||||
throw is.invalidParameterError('jagged', 'number between 0 and 10000', jagged);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw is.invalidParameterError('sigma', 'number between 0.01 and 10000', sigma);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply median filter.
|
||||
* When used without parameters the default window is 3x3.
|
||||
* @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, mild blur of the output image.
|
||||
* When a `sigma` is provided, performs a slower, more accurate Gaussian blur.
|
||||
* @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.
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param {Boolean} [negate=true]
|
||||
* @returns {Sharp}
|
||||
*/
|
||||
function negate (negate) {
|
||||
this.options.negate = is.bool(negate) ? negate : true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhance output image contrast by stretching its luminance to cover the full dynamic range.
|
||||
* @param {Boolean} [normalise=true]
|
||||
* @returns {Sharp}
|
||||
*/
|
||||
function normalise (normalise) {
|
||||
this.options.normalise = is.bool(normalise) ? normalise : true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternative spelling of normalise.
|
||||
* @param {Boolean} [normalize=true]
|
||||
* @returns {Sharp}
|
||||
*/
|
||||
function normalize (normalize) {
|
||||
return this.normalise(normalize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 - width 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 greather 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 (levels adjustment)
|
||||
* @param {number} [a=1.0] multiplier
|
||||
* @param {number} [b=0.0] offset
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
function linear (a, b) {
|
||||
if (!is.defined(a)) {
|
||||
this.options.linearA = 1.0;
|
||||
} else if (is.number(a)) {
|
||||
this.options.linearA = a;
|
||||
} else {
|
||||
throw is.invalidParameterError('a', 'numeric', a);
|
||||
}
|
||||
if (!is.defined(b)) {
|
||||
this.options.linearB = 0.0;
|
||||
} else if (is.number(b)) {
|
||||
this.options.linearB = b;
|
||||
} else {
|
||||
throw is.invalidParameterError('b', 'numeric', b);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recomb 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 recomb
|
||||
* // 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 and hue rotation.
|
||||
*
|
||||
* @since 0.22.1
|
||||
*
|
||||
* @example
|
||||
* sharp(input)
|
||||
* .modulate({
|
||||
* brightness: 2 // increase lightness by a factor of 2
|
||||
* });
|
||||
*
|
||||
* sharp(input)
|
||||
* .modulate({
|
||||
* hue: 180 // hue-rotate by 180 degrees
|
||||
* });
|
||||
*
|
||||
* // decreate brightness and saturation while also hue-rotating by 90 degrees
|
||||
* sharp(input)
|
||||
* .modulate({
|
||||
* brightness: 0.5,
|
||||
* saturation: 0.5,
|
||||
* hue: 90
|
||||
* });
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* @param {number} [options.brightness] Brightness multiplier
|
||||
* @param {number} [options.saturation] Saturation multiplier
|
||||
* @param {number} [options.hue] Degrees for hue rotation
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
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,
|
||||
gamma,
|
||||
negate,
|
||||
normalise,
|
||||
normalize,
|
||||
convolve,
|
||||
threshold,
|
||||
boolean,
|
||||
linear,
|
||||
recomb,
|
||||
modulate
|
||||
});
|
||||
};
|
||||
926
lib/output.js
Normal file
926
lib/output.js
Normal file
@@ -0,0 +1,926 @@
|
||||
'use strict';
|
||||
|
||||
const is = require('./is');
|
||||
const sharp = require('../build/Release/sharp.node');
|
||||
|
||||
const formats = new Map([
|
||||
['heic', 'heif'],
|
||||
['heif', 'heif'],
|
||||
['avif', 'avif'],
|
||||
['jpeg', 'jpeg'],
|
||||
['jpg', 'jpeg'],
|
||||
['png', 'png'],
|
||||
['raw', 'raw'],
|
||||
['tiff', 'tiff'],
|
||||
['webp', 'webp'],
|
||||
['gif', 'gif']
|
||||
]);
|
||||
|
||||
/**
|
||||
* 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, TIFF, 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 {@link withMetadata} for control over this.
|
||||
*
|
||||
* A `Promise` is returned when `callback` is not provided.
|
||||
*
|
||||
* @example
|
||||
* sharp(input)
|
||||
* .toFile('output.png', (err, info) => { ... });
|
||||
*
|
||||
* @example
|
||||
* sharp(input)
|
||||
* .toFile('output.png')
|
||||
* .then(info => { ... })
|
||||
* .catch(err => { ... });
|
||||
*
|
||||
* @param {string} fileOut - the path to write the image data to.
|
||||
* @param {Function} [callback] - 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`.
|
||||
* @returns {Promise<Object>} - when no callback is provided
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
function toFile (fileOut, callback) {
|
||||
if (!fileOut || fileOut.length === 0) {
|
||||
const errOutputInvalid = new Error('Missing output file path');
|
||||
if (is.fn(callback)) {
|
||||
callback(errOutputInvalid);
|
||||
} else {
|
||||
return Promise.reject(errOutputInvalid);
|
||||
}
|
||||
} else {
|
||||
if (this.options.input.file === fileOut) {
|
||||
const errOutputIsInput = new Error('Cannot use same file for input and output');
|
||||
if (is.fn(callback)) {
|
||||
callback(errOutputIsInput);
|
||||
} else {
|
||||
return Promise.reject(errOutputIsInput);
|
||||
}
|
||||
} else {
|
||||
this.options.fileOut = fileOut;
|
||||
return this._pipeline(callback);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write output to a Buffer.
|
||||
* JPEG, PNG, WebP, TIFF and RAW output are supported.
|
||||
*
|
||||
* If no explicit format is set, the output format will match the input image, except GIF and SVG input which become PNG output.
|
||||
*
|
||||
* By default all metadata will be removed, which includes EXIF-based orientation.
|
||||
* See {@link 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`.
|
||||
*
|
||||
* A `Promise` is returned when `callback` is not provided.
|
||||
*
|
||||
* @example
|
||||
* sharp(input)
|
||||
* .toBuffer((err, data, info) => { ... });
|
||||
*
|
||||
* @example
|
||||
* sharp(input)
|
||||
* .toBuffer()
|
||||
* .then(data => { ... })
|
||||
* .catch(err => { ... });
|
||||
*
|
||||
* @example
|
||||
* sharp(input)
|
||||
* .toBuffer({ resolveWithObject: true })
|
||||
* .then(({ data, info }) => { ... })
|
||||
* .catch(err => { ... });
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* @param {boolean} [options.resolveWithObject] Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
|
||||
* @param {Function} [callback]
|
||||
* @returns {Promise<Buffer>} - when no callback is provided
|
||||
*/
|
||||
function toBuffer (options, callback) {
|
||||
if (is.object(options)) {
|
||||
this._setBooleanOption('resolveWithObject', options.resolveWithObject);
|
||||
} else if (this.options.resolveWithObject) {
|
||||
this.options.resolveWithObject = false;
|
||||
}
|
||||
return this._pipeline(is.fn(options) ? options : callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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.
|
||||
*
|
||||
* @example
|
||||
* sharp('input.jpg')
|
||||
* .withMetadata()
|
||||
* .toFile('output-with-metadata.jpg')
|
||||
* .then(info => { ... });
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* @param {number} [options.orientation] value between 1 and 8, used to update the EXIF `Orientation` tag.
|
||||
* @param {string} [options.icc] filesystem path to output ICC profile, defaults to sRGB.
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
function withMetadata (options) {
|
||||
this.options.withMetadata = is.bool(options) ? options : true;
|
||||
if (is.object(options)) {
|
||||
if (is.defined(options.orientation)) {
|
||||
if (is.integer(options.orientation) && is.inRange(options.orientation, 1, 8)) {
|
||||
this.options.withMetadataOrientation = options.orientation;
|
||||
} else {
|
||||
throw is.invalidParameterError('orientation', 'integer between 1 and 8', options.orientation);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.icc)) {
|
||||
if (is.string(options.icc)) {
|
||||
this.options.withMetadataIcc = options.icc;
|
||||
} else {
|
||||
throw is.invalidParameterError('icc', 'string filesystem path to ICC profile', options.icc);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force output to a given format.
|
||||
*
|
||||
* @example
|
||||
* // Convert any input to PNG output
|
||||
* const data = await sharp(input)
|
||||
* .toFormat('png')
|
||||
* .toBuffer();
|
||||
*
|
||||
* @param {(string|Object)} format - as a string or an Object with an 'id' attribute
|
||||
* @param {Object} options - output options
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} unsupported format or options
|
||||
*/
|
||||
function toFormat (format, options) {
|
||||
const actualFormat = formats.get(is.object(format) && is.string(format.id) ? format.id : format);
|
||||
if (!actualFormat) {
|
||||
throw is.invalidParameterError('format', `one of: ${[...formats.keys()].join(', ')}`, format);
|
||||
}
|
||||
return this[actualFormat](options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use these JPEG options for output image.
|
||||
*
|
||||
* Some of these options require the use of a globally-installed libvips compiled with support for mozjpeg.
|
||||
*
|
||||
* @example
|
||||
* // Convert any input to very high quality JPEG output
|
||||
* const data = await sharp(input)
|
||||
* .jpeg({
|
||||
* quality: 100,
|
||||
* chromaSubsampling: '4:4:4'
|
||||
* })
|
||||
* .toBuffer();
|
||||
*
|
||||
* @param {Object} [options] - output options
|
||||
* @param {number} [options.quality=80] - quality, integer 1-100
|
||||
* @param {boolean} [options.progressive=false] - use progressive (interlace) scan
|
||||
* @param {string} [options.chromaSubsampling='4:2:0'] - set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling
|
||||
* @param {boolean} [options.optimiseCoding=true] - optimise Huffman coding tables
|
||||
* @param {boolean} [options.optimizeCoding=true] - alternative spelling of optimiseCoding
|
||||
* @param {boolean} [options.trellisQuantisation=false] - apply trellis quantisation, requires libvips compiled with support for mozjpeg
|
||||
* @param {boolean} [options.overshootDeringing=false] - apply overshoot deringing, requires libvips compiled with support for mozjpeg
|
||||
* @param {boolean} [options.optimiseScans=false] - optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg
|
||||
* @param {boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans, requires libvips compiled with support for mozjpeg
|
||||
* @param {number} [options.quantisationTable=0] - quantization table to use, integer 0-8, requires libvips compiled with support for mozjpeg
|
||||
* @param {number} [options.quantizationTable=0] - alternative spelling of quantisationTable, requires libvips compiled with support for mozjpeg
|
||||
* @param {boolean} [options.force=true] - force JPEG output, otherwise attempt to use input format
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid options
|
||||
*/
|
||||
function jpeg (options) {
|
||||
if (is.object(options)) {
|
||||
if (is.defined(options.quality)) {
|
||||
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
|
||||
this.options.jpegQuality = options.quality;
|
||||
} else {
|
||||
throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.progressive)) {
|
||||
this._setBooleanOption('jpegProgressive', options.progressive);
|
||||
}
|
||||
if (is.defined(options.chromaSubsampling)) {
|
||||
if (is.string(options.chromaSubsampling) && is.inArray(options.chromaSubsampling, ['4:2:0', '4:4:4'])) {
|
||||
this.options.jpegChromaSubsampling = options.chromaSubsampling;
|
||||
} else {
|
||||
throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling);
|
||||
}
|
||||
}
|
||||
const trellisQuantisation = is.bool(options.trellisQuantization) ? options.trellisQuantization : options.trellisQuantisation;
|
||||
if (is.defined(trellisQuantisation)) {
|
||||
this._setBooleanOption('jpegTrellisQuantisation', trellisQuantisation);
|
||||
}
|
||||
if (is.defined(options.overshootDeringing)) {
|
||||
this._setBooleanOption('jpegOvershootDeringing', options.overshootDeringing);
|
||||
}
|
||||
const optimiseScans = is.bool(options.optimizeScans) ? options.optimizeScans : options.optimiseScans;
|
||||
if (is.defined(optimiseScans)) {
|
||||
this._setBooleanOption('jpegOptimiseScans', optimiseScans);
|
||||
if (optimiseScans) {
|
||||
this.options.jpegProgressive = true;
|
||||
}
|
||||
}
|
||||
const optimiseCoding = is.bool(options.optimizeCoding) ? options.optimizeCoding : options.optimiseCoding;
|
||||
if (is.defined(optimiseCoding)) {
|
||||
this._setBooleanOption('jpegOptimiseCoding', optimiseCoding);
|
||||
}
|
||||
const quantisationTable = is.number(options.quantizationTable) ? options.quantizationTable : options.quantisationTable;
|
||||
if (is.defined(quantisationTable)) {
|
||||
if (is.integer(quantisationTable) && is.inRange(quantisationTable, 0, 8)) {
|
||||
this.options.jpegQuantisationTable = quantisationTable;
|
||||
} else {
|
||||
throw is.invalidParameterError('quantisationTable', 'integer between 0 and 8', quantisationTable);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this._updateFormatOut('jpeg', options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use these PNG options for output image.
|
||||
*
|
||||
* PNG output is always 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.
|
||||
*
|
||||
* Some of these options require the use of a globally-installed libvips compiled with support for libimagequant (GPL).
|
||||
*
|
||||
* @example
|
||||
* // Convert any input to PNG output
|
||||
* const data = await sharp(input)
|
||||
* .png()
|
||||
* .toBuffer();
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* @param {boolean} [options.progressive=false] - use progressive (interlace) scan
|
||||
* @param {number} [options.compressionLevel=9] - zlib compression level, 0-9
|
||||
* @param {boolean} [options.adaptiveFiltering=false] - use adaptive row filtering
|
||||
* @param {boolean} [options.palette=false] - quantise to a palette-based image with alpha transparency support, requires libvips compiled with support for libimagequant
|
||||
* @param {number} [options.quality=100] - use the lowest number of colours needed to achieve given quality, sets `palette` to `true`, requires libvips compiled with support for libimagequant
|
||||
* @param {number} [options.colours=256] - maximum number of palette entries, sets `palette` to `true`, requires libvips compiled with support for libimagequant
|
||||
* @param {number} [options.colors=256] - alternative spelling of `options.colours`, sets `palette` to `true`, requires libvips compiled with support for libimagequant
|
||||
* @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, sets `palette` to `true`, requires libvips compiled with support for libimagequant
|
||||
* @param {boolean} [options.force=true] - force PNG output, otherwise attempt to use input format
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid options
|
||||
*/
|
||||
function png (options) {
|
||||
if (is.object(options)) {
|
||||
if (is.defined(options.progressive)) {
|
||||
this._setBooleanOption('pngProgressive', options.progressive);
|
||||
}
|
||||
if (is.defined(options.compressionLevel)) {
|
||||
if (is.integer(options.compressionLevel) && is.inRange(options.compressionLevel, 0, 9)) {
|
||||
this.options.pngCompressionLevel = options.compressionLevel;
|
||||
} else {
|
||||
throw is.invalidParameterError('compressionLevel', 'integer between 0 and 9', options.compressionLevel);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.adaptiveFiltering)) {
|
||||
this._setBooleanOption('pngAdaptiveFiltering', options.adaptiveFiltering);
|
||||
}
|
||||
if (is.defined(options.palette)) {
|
||||
this._setBooleanOption('pngPalette', options.palette);
|
||||
} else if (is.defined(options.quality) || is.defined(options.colours || options.colors) || is.defined(options.dither)) {
|
||||
this._setBooleanOption('pngPalette', true);
|
||||
}
|
||||
if (this.options.pngPalette) {
|
||||
if (is.defined(options.quality)) {
|
||||
if (is.integer(options.quality) && is.inRange(options.quality, 0, 100)) {
|
||||
this.options.pngQuality = options.quality;
|
||||
} else {
|
||||
throw is.invalidParameterError('quality', 'integer between 0 and 100', options.quality);
|
||||
}
|
||||
}
|
||||
const colours = options.colours || options.colors;
|
||||
if (is.defined(colours)) {
|
||||
if (is.integer(colours) && is.inRange(colours, 2, 256)) {
|
||||
this.options.pngColours = colours;
|
||||
} else {
|
||||
throw is.invalidParameterError('colours', 'integer between 2 and 256', colours);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.dither)) {
|
||||
if (is.number(options.dither) && is.inRange(options.dither, 0, 1)) {
|
||||
this.options.pngDither = options.dither;
|
||||
} else {
|
||||
throw is.invalidParameterError('dither', 'number between 0.0 and 1.0', options.dither);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return this._updateFormatOut('png', options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use these WebP options for output image.
|
||||
*
|
||||
* @example
|
||||
* // Convert any input to lossless WebP output
|
||||
* const data = await sharp(input)
|
||||
* .webp({ lossless: true })
|
||||
* .toBuffer();
|
||||
*
|
||||
* @param {Object} [options] - output options
|
||||
* @param {number} [options.quality=80] - quality, integer 1-100
|
||||
* @param {number} [options.alphaQuality=100] - quality of alpha layer, integer 0-100
|
||||
* @param {boolean} [options.lossless=false] - use lossless compression mode
|
||||
* @param {boolean} [options.nearLossless=false] - use near_lossless compression mode
|
||||
* @param {boolean} [options.smartSubsample=false] - use high quality chroma subsampling
|
||||
* @param {number} [options.reductionEffort=4] - level of CPU effort to reduce file size, integer 0-6
|
||||
* @param {number} [options.pageHeight] - page height for animated output
|
||||
* @param {number} [options.loop=0] - number of animation iterations, use 0 for infinite animation
|
||||
* @param {number[]} [options.delay] - list of delays between animation frames (in milliseconds)
|
||||
* @param {boolean} [options.force=true] - force WebP output, otherwise attempt to use input format
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid options
|
||||
*/
|
||||
function webp (options) {
|
||||
if (is.object(options) && is.defined(options.quality)) {
|
||||
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
|
||||
this.options.webpQuality = options.quality;
|
||||
} else {
|
||||
throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
|
||||
}
|
||||
}
|
||||
if (is.object(options) && is.defined(options.alphaQuality)) {
|
||||
if (is.integer(options.alphaQuality) && is.inRange(options.alphaQuality, 0, 100)) {
|
||||
this.options.webpAlphaQuality = options.alphaQuality;
|
||||
} else {
|
||||
throw is.invalidParameterError('alphaQuality', 'integer between 0 and 100', options.alphaQuality);
|
||||
}
|
||||
}
|
||||
if (is.object(options) && is.defined(options.lossless)) {
|
||||
this._setBooleanOption('webpLossless', options.lossless);
|
||||
}
|
||||
if (is.object(options) && is.defined(options.nearLossless)) {
|
||||
this._setBooleanOption('webpNearLossless', options.nearLossless);
|
||||
}
|
||||
if (is.object(options) && is.defined(options.smartSubsample)) {
|
||||
this._setBooleanOption('webpSmartSubsample', options.smartSubsample);
|
||||
}
|
||||
if (is.object(options) && is.defined(options.reductionEffort)) {
|
||||
if (is.integer(options.reductionEffort) && is.inRange(options.reductionEffort, 0, 6)) {
|
||||
this.options.webpReductionEffort = options.reductionEffort;
|
||||
} else {
|
||||
throw is.invalidParameterError('reductionEffort', 'integer between 0 and 6', options.reductionEffort);
|
||||
}
|
||||
}
|
||||
|
||||
trySetAnimationOptions(options, this.options);
|
||||
return this._updateFormatOut('webp', options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use these GIF options for output image.
|
||||
*
|
||||
* Requires libvips compiled with support for ImageMagick or GraphicsMagick.
|
||||
* The prebuilt binaries do not include this - see
|
||||
* {@link https://sharp.pixelplumbing.com/install#custom-libvips installing a custom libvips}.
|
||||
*
|
||||
* @param {Object} [options] - output options
|
||||
* @param {number} [options.pageHeight] - page height for animated output
|
||||
* @param {number} [options.loop=0] - number of animation iterations, use 0 for infinite animation
|
||||
* @param {number[]} [options.delay] - list of delays between animation frames (in milliseconds)
|
||||
* @param {boolean} [options.force=true] - force GIF output, otherwise attempt to use input format
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid options
|
||||
*/
|
||||
/* istanbul ignore next */
|
||||
function gif (options) {
|
||||
if (!this.constructor.format.magick.output.buffer) {
|
||||
throw new Error('The gif operation requires libvips to have been installed with support for ImageMagick');
|
||||
}
|
||||
trySetAnimationOptions(options, this.options);
|
||||
return this._updateFormatOut('gif', options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set animation options if available.
|
||||
* @private
|
||||
*
|
||||
* @param {Object} [source] - output options
|
||||
* @param {number} [source.pageHeight] - page height for animated output
|
||||
* @param {number} [source.loop=0] - number of animation iterations, use 0 for infinite animation
|
||||
* @param {number[]} [source.delay] - list of delays between animation frames (in milliseconds)
|
||||
* @param {Object} [target] - target object for valid options
|
||||
* @throws {Error} Invalid options
|
||||
*/
|
||||
function trySetAnimationOptions (source, target) {
|
||||
if (is.object(source) && is.defined(source.pageHeight)) {
|
||||
if (is.integer(source.pageHeight) && source.pageHeight > 0) {
|
||||
target.pageHeight = source.pageHeight;
|
||||
} else {
|
||||
throw is.invalidParameterError('pageHeight', 'integer larger than 0', source.pageHeight);
|
||||
}
|
||||
}
|
||||
if (is.object(source) && is.defined(source.loop)) {
|
||||
if (is.integer(source.loop) && is.inRange(source.loop, 0, 65535)) {
|
||||
target.loop = source.loop;
|
||||
} else {
|
||||
throw is.invalidParameterError('loop', 'integer between 0 and 65535', source.loop);
|
||||
}
|
||||
}
|
||||
if (is.object(source) && is.defined(source.delay)) {
|
||||
if (
|
||||
Array.isArray(source.delay) &&
|
||||
source.delay.every(is.integer) &&
|
||||
source.delay.every(v => is.inRange(v, 0, 65535))) {
|
||||
target.delay = source.delay;
|
||||
} else {
|
||||
throw is.invalidParameterError('delay', 'array of integers between 0 and 65535', source.delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use these TIFF options for output image.
|
||||
*
|
||||
* @example
|
||||
* // 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 => { ... });
|
||||
*
|
||||
* @param {Object} [options] - output options
|
||||
* @param {number} [options.quality=80] - quality, integer 1-100
|
||||
* @param {boolean} [options.force=true] - force TIFF output, otherwise attempt to use input format
|
||||
* @param {boolean} [options.compression='jpeg'] - compression options: lzw, deflate, jpeg, ccittfax4
|
||||
* @param {boolean} [options.predictor='horizontal'] - compression predictor options: none, horizontal, float
|
||||
* @param {boolean} [options.pyramid=false] - write an image pyramid
|
||||
* @param {boolean} [options.tile=false] - write a tiled tiff
|
||||
* @param {boolean} [options.tileWidth=256] - horizontal tile size
|
||||
* @param {boolean} [options.tileHeight=256] - vertical tile size
|
||||
* @param {number} [options.xres=1.0] - horizontal resolution in pixels/mm
|
||||
* @param {number} [options.yres=1.0] - vertical resolution in pixels/mm
|
||||
* @param {boolean} [options.bitdepth=8] - reduce bitdepth to 1, 2 or 4 bit
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid options
|
||||
*/
|
||||
function tiff (options) {
|
||||
if (is.object(options)) {
|
||||
if (is.defined(options.quality)) {
|
||||
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
|
||||
this.options.tiffQuality = options.quality;
|
||||
} else {
|
||||
throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.bitdepth)) {
|
||||
if (is.integer(options.bitdepth) && is.inArray(options.bitdepth, [1, 2, 4, 8])) {
|
||||
this.options.tiffBitdepth = options.bitdepth;
|
||||
} else {
|
||||
throw is.invalidParameterError('bitdepth', '1, 2, 4 or 8', options.bitdepth);
|
||||
}
|
||||
}
|
||||
// tiling
|
||||
if (is.defined(options.tile)) {
|
||||
this._setBooleanOption('tiffTile', options.tile);
|
||||
}
|
||||
if (is.defined(options.tileWidth)) {
|
||||
if (is.integer(options.tileWidth) && options.tileWidth > 0) {
|
||||
this.options.tiffTileWidth = options.tileWidth;
|
||||
} else {
|
||||
throw is.invalidParameterError('tileWidth', 'integer greater than zero', options.tileWidth);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.tileHeight)) {
|
||||
if (is.integer(options.tileHeight) && options.tileHeight > 0) {
|
||||
this.options.tiffTileHeight = options.tileHeight;
|
||||
} else {
|
||||
throw is.invalidParameterError('tileHeight', 'integer greater than zero', options.tileHeight);
|
||||
}
|
||||
}
|
||||
// pyramid
|
||||
if (is.defined(options.pyramid)) {
|
||||
this._setBooleanOption('tiffPyramid', options.pyramid);
|
||||
}
|
||||
// resolution
|
||||
if (is.defined(options.xres)) {
|
||||
if (is.number(options.xres) && options.xres > 0) {
|
||||
this.options.tiffXres = options.xres;
|
||||
} else {
|
||||
throw is.invalidParameterError('xres', 'number greater than zero', options.xres);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.yres)) {
|
||||
if (is.number(options.yres) && options.yres > 0) {
|
||||
this.options.tiffYres = options.yres;
|
||||
} else {
|
||||
throw is.invalidParameterError('yres', 'number greater than zero', options.yres);
|
||||
}
|
||||
}
|
||||
// compression
|
||||
if (is.defined(options.compression)) {
|
||||
if (is.string(options.compression) && is.inArray(options.compression, ['lzw', 'deflate', 'jpeg', 'ccittfax4', 'none'])) {
|
||||
this.options.tiffCompression = options.compression;
|
||||
} else {
|
||||
throw is.invalidParameterError('compression', 'one of: lzw, deflate, jpeg, ccittfax4, none', options.compression);
|
||||
}
|
||||
}
|
||||
// predictor
|
||||
if (is.defined(options.predictor)) {
|
||||
if (is.string(options.predictor) && is.inArray(options.predictor, ['none', 'horizontal', 'float'])) {
|
||||
this.options.tiffPredictor = options.predictor;
|
||||
} else {
|
||||
throw is.invalidParameterError('predictor', 'one of: none, horizontal, float', options.predictor);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this._updateFormatOut('tiff', options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use these AVIF options for output image.
|
||||
*
|
||||
* Whilst it is possible to create AVIF images smaller than 16x16 pixels,
|
||||
* most web browsers do not display these properly.
|
||||
*
|
||||
* @since 0.27.0
|
||||
*
|
||||
* @param {Object} [options] - output options
|
||||
* @param {number} [options.quality=50] - quality, integer 1-100
|
||||
* @param {boolean} [options.lossless=false] - use lossless compression
|
||||
* @param {boolean} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid options
|
||||
*/
|
||||
function avif (options) {
|
||||
return this.heif({ ...options, compression: 'av1' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Use these HEIF options for output image.
|
||||
*
|
||||
* Support for patent-encumbered HEIC images requires the use of a
|
||||
* globally-installed libvips compiled with support for libheif, libde265 and x265.
|
||||
*
|
||||
* @since 0.23.0
|
||||
*
|
||||
* @param {Object} [options] - output options
|
||||
* @param {number} [options.quality=50] - quality, integer 1-100
|
||||
* @param {boolean} [options.compression='av1'] - compression format: av1, hevc
|
||||
* @param {boolean} [options.lossless=false] - use lossless compression
|
||||
* @param {boolean} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid options
|
||||
*/
|
||||
function heif (options) {
|
||||
if (is.object(options)) {
|
||||
if (is.defined(options.quality)) {
|
||||
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
|
||||
this.options.heifQuality = options.quality;
|
||||
} else {
|
||||
throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.lossless)) {
|
||||
if (is.bool(options.lossless)) {
|
||||
this.options.heifLossless = options.lossless;
|
||||
} else {
|
||||
throw is.invalidParameterError('lossless', 'boolean', options.lossless);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.compression)) {
|
||||
if (is.string(options.compression) && is.inArray(options.compression, ['av1', 'hevc'])) {
|
||||
this.options.heifCompression = options.compression;
|
||||
} else {
|
||||
throw is.invalidParameterError('compression', 'one of: av1, hevc', options.compression);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.speed)) {
|
||||
if (is.integer(options.speed) && is.inRange(options.speed, 0, 8)) {
|
||||
this.options.heifSpeed = options.speed;
|
||||
} else {
|
||||
throw is.invalidParameterError('speed', 'integer between 0 and 8', options.speed);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this._updateFormatOut('heif', options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force output to be raw, uncompressed, 8-bit unsigned integer (unit8) pixel data.
|
||||
* Pixel ordering is left-to-right, top-to-bottom, without padding.
|
||||
* Channel ordering will be RGB or RGBA for non-greyscale colourspaces.
|
||||
*
|
||||
* @example
|
||||
* // Extract raw RGB pixel data from JPEG input
|
||||
* const { data, info } = await sharp('input.jpg')
|
||||
* .raw()
|
||||
* .toBuffer({ resolveWithObject: true });
|
||||
*
|
||||
* @example
|
||||
* // Extract alpha channel as raw pixel data from PNG input
|
||||
* const data = await sharp('input.png')
|
||||
* .ensureAlpha()
|
||||
* .extractChannel(3)
|
||||
* .toColourspace('b-w')
|
||||
* .raw()
|
||||
* .toBuffer();
|
||||
*
|
||||
* @returns {Sharp}
|
||||
*/
|
||||
function raw () {
|
||||
return this._updateFormatOut('raw');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* Warning: multiple sharp instances concurrently producing tile output can expose a possible race condition in some versions of libgsf.
|
||||
*
|
||||
* @example
|
||||
* 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
|
||||
* });
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* @param {number} [options.size=256] tile size in pixels, a value between 1 and 8192.
|
||||
* @param {number} [options.overlap=0] tile overlap in pixels, a value between 0 and 8192.
|
||||
* @param {number} [options.angle=0] tile angle of rotation, must be a multiple of 90.
|
||||
* @param {string|Object} [options.background={r: 255, g: 255, b: 255, alpha: 1}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to white without transparency.
|
||||
* @param {string} [options.depth] how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
|
||||
* @param {number} [options.skipBlanks=-1] threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images
|
||||
* @param {string} [options.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file).
|
||||
* @param {string} [options.layout='dz'] filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`.
|
||||
* @param {boolean} [options.centre=false] centre image in tile.
|
||||
* @param {boolean} [options.center=false] alternative spelling of centre.
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
function tile (options) {
|
||||
if (is.object(options)) {
|
||||
// Size of square tiles, in pixels
|
||||
if (is.defined(options.size)) {
|
||||
if (is.integer(options.size) && is.inRange(options.size, 1, 8192)) {
|
||||
this.options.tileSize = options.size;
|
||||
} else {
|
||||
throw is.invalidParameterError('size', 'integer between 1 and 8192', options.size);
|
||||
}
|
||||
}
|
||||
// Overlap of tiles, in pixels
|
||||
if (is.defined(options.overlap)) {
|
||||
if (is.integer(options.overlap) && is.inRange(options.overlap, 0, 8192)) {
|
||||
if (options.overlap > this.options.tileSize) {
|
||||
throw is.invalidParameterError('overlap', `<= size (${this.options.tileSize})`, options.overlap);
|
||||
}
|
||||
this.options.tileOverlap = options.overlap;
|
||||
} else {
|
||||
throw is.invalidParameterError('overlap', 'integer between 0 and 8192', options.overlap);
|
||||
}
|
||||
}
|
||||
// Container
|
||||
if (is.defined(options.container)) {
|
||||
if (is.string(options.container) && is.inArray(options.container, ['fs', 'zip'])) {
|
||||
this.options.tileContainer = options.container;
|
||||
} else {
|
||||
throw is.invalidParameterError('container', 'one of: fs, zip', options.container);
|
||||
}
|
||||
}
|
||||
// Layout
|
||||
if (is.defined(options.layout)) {
|
||||
if (is.string(options.layout) && is.inArray(options.layout, ['dz', 'google', 'iiif', 'zoomify'])) {
|
||||
this.options.tileLayout = options.layout;
|
||||
} else {
|
||||
throw is.invalidParameterError('layout', 'one of: dz, google, iiif, zoomify', options.layout);
|
||||
}
|
||||
}
|
||||
// Angle of rotation,
|
||||
if (is.defined(options.angle)) {
|
||||
if (is.integer(options.angle) && !(options.angle % 90)) {
|
||||
this.options.tileAngle = options.angle;
|
||||
} else {
|
||||
throw is.invalidParameterError('angle', 'positive/negative multiple of 90', options.angle);
|
||||
}
|
||||
}
|
||||
// Background colour
|
||||
this._setBackgroundColourOption('tileBackground', options.background);
|
||||
// Depth of tiles
|
||||
if (is.defined(options.depth)) {
|
||||
if (is.string(options.depth) && is.inArray(options.depth, ['onepixel', 'onetile', 'one'])) {
|
||||
this.options.tileDepth = options.depth;
|
||||
} else {
|
||||
throw is.invalidParameterError('depth', 'one of: onepixel, onetile, one', options.depth);
|
||||
}
|
||||
}
|
||||
// Threshold to skip blank tiles
|
||||
if (is.defined(options.skipBlanks)) {
|
||||
if (is.integer(options.skipBlanks) && is.inRange(options.skipBlanks, -1, 65535)) {
|
||||
this.options.tileSkipBlanks = options.skipBlanks;
|
||||
} else {
|
||||
throw is.invalidParameterError('skipBlanks', 'integer between -1 and 255/65535', options.skipBlanks);
|
||||
}
|
||||
} else if (is.defined(options.layout) && options.layout === 'google') {
|
||||
this.options.tileSkipBlanks = 5;
|
||||
}
|
||||
// Center image in tile
|
||||
const centre = is.bool(options.center) ? options.center : options.centre;
|
||||
if (is.defined(centre)) {
|
||||
this._setBooleanOption('tileCentre', centre);
|
||||
}
|
||||
}
|
||||
// Format
|
||||
if (is.inArray(this.options.formatOut, ['jpeg', 'png', 'webp'])) {
|
||||
this.options.tileFormat = this.options.formatOut;
|
||||
} else if (this.options.formatOut !== 'input') {
|
||||
throw is.invalidParameterError('format', 'one of: jpeg, png, webp', this.options.formatOut);
|
||||
}
|
||||
return this._updateFormatOut('dz');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the output format unless options.force is false,
|
||||
* in which case revert to input format.
|
||||
* @private
|
||||
* @param {string} formatOut
|
||||
* @param {Object} [options]
|
||||
* @param {boolean} [options.force=true] - force output format, otherwise attempt to use input format
|
||||
* @returns {Sharp}
|
||||
*/
|
||||
function _updateFormatOut (formatOut, options) {
|
||||
if (!(is.object(options) && options.force === false)) {
|
||||
this.options.formatOut = formatOut;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a boolean attribute of the this.options Object.
|
||||
* @private
|
||||
* @param {string} key
|
||||
* @param {boolean} val
|
||||
* @throws {Error} Invalid key
|
||||
*/
|
||||
function _setBooleanOption (key, val) {
|
||||
if (is.bool(val)) {
|
||||
this.options[key] = val;
|
||||
} else {
|
||||
throw is.invalidParameterError(key, 'boolean', val);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by a WriteableStream to notify us it is ready for data.
|
||||
* @private
|
||||
*/
|
||||
function _read () {
|
||||
/* istanbul ignore else */
|
||||
if (!this.options.streamOut) {
|
||||
this.options.streamOut = true;
|
||||
this._pipeline();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the C++ image processing pipeline
|
||||
* Supports callback, stream and promise variants
|
||||
* @private
|
||||
*/
|
||||
function _pipeline (callback) {
|
||||
if (typeof callback === 'function') {
|
||||
// output=file/buffer
|
||||
if (this._isStreamInput()) {
|
||||
// output=file/buffer, input=stream
|
||||
this.on('finish', () => {
|
||||
this._flattenBufferIn();
|
||||
sharp.pipeline(this.options, callback);
|
||||
});
|
||||
} else {
|
||||
// output=file/buffer, input=file/buffer
|
||||
sharp.pipeline(this.options, callback);
|
||||
}
|
||||
return this;
|
||||
} else if (this.options.streamOut) {
|
||||
// output=stream
|
||||
if (this._isStreamInput()) {
|
||||
// output=stream, input=stream
|
||||
this.once('finish', () => {
|
||||
this._flattenBufferIn();
|
||||
sharp.pipeline(this.options, (err, data, info) => {
|
||||
if (err) {
|
||||
this.emit('error', err);
|
||||
} else {
|
||||
this.emit('info', info);
|
||||
this.push(data);
|
||||
}
|
||||
this.push(null);
|
||||
});
|
||||
});
|
||||
if (this.streamInFinished) {
|
||||
this.emit('finish');
|
||||
}
|
||||
} else {
|
||||
// output=stream, input=file/buffer
|
||||
sharp.pipeline(this.options, (err, data, info) => {
|
||||
if (err) {
|
||||
this.emit('error', err);
|
||||
} else {
|
||||
this.emit('info', info);
|
||||
this.push(data);
|
||||
}
|
||||
this.push(null);
|
||||
});
|
||||
}
|
||||
return this;
|
||||
} else {
|
||||
// output=promise
|
||||
if (this._isStreamInput()) {
|
||||
// output=promise, input=stream
|
||||
return new Promise((resolve, reject) => {
|
||||
this.once('finish', () => {
|
||||
this._flattenBufferIn();
|
||||
sharp.pipeline(this.options, (err, data, info) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
if (this.options.resolveWithObject) {
|
||||
resolve({ data, info });
|
||||
} else {
|
||||
resolve(data);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// output=promise, input=file/buffer
|
||||
return new Promise((resolve, reject) => {
|
||||
sharp.pipeline(this.options, (err, data, info) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
if (this.options.resolveWithObject) {
|
||||
resolve({ data: data, info: info });
|
||||
} else {
|
||||
resolve(data);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorate the Sharp prototype with output-related functions.
|
||||
* @private
|
||||
*/
|
||||
module.exports = function (Sharp) {
|
||||
Object.assign(Sharp.prototype, {
|
||||
// Public
|
||||
toFile,
|
||||
toBuffer,
|
||||
withMetadata,
|
||||
toFormat,
|
||||
jpeg,
|
||||
png,
|
||||
webp,
|
||||
tiff,
|
||||
avif,
|
||||
heif,
|
||||
gif,
|
||||
raw,
|
||||
tile,
|
||||
// Private
|
||||
_updateFormatOut,
|
||||
_setBooleanOption,
|
||||
_read,
|
||||
_pipeline
|
||||
});
|
||||
};
|
||||
25
lib/platform.js
Normal file
25
lib/platform.js
Normal file
@@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
const detectLibc = require('detect-libc');
|
||||
|
||||
const env = process.env;
|
||||
|
||||
module.exports = function () {
|
||||
const arch = env.npm_config_arch || process.arch;
|
||||
const platform = env.npm_config_platform || process.platform;
|
||||
/* istanbul ignore next */
|
||||
const libc = (platform === 'linux' && detectLibc.isNonGlibcLinux) ? detectLibc.family : '';
|
||||
|
||||
const platformId = [`${platform}${libc}`];
|
||||
|
||||
if (arch === 'arm') {
|
||||
const fallback = process.versions.electron ? '7' : '6';
|
||||
platformId.push(`armv${env.npm_config_arm_version || process.config.variables.arm_version || fallback}`);
|
||||
} else if (arch === 'arm64') {
|
||||
platformId.push(`arm64v${env.npm_config_arm_version || '8'}`);
|
||||
} else {
|
||||
platformId.push(arch);
|
||||
}
|
||||
|
||||
return platformId.join('-');
|
||||
};
|
||||
427
lib/resize.js
Normal file
427
lib/resize.js
Normal file
@@ -0,0 +1,427 @@
|
||||
'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
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, 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.
|
||||
*
|
||||
* 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).
|
||||
*
|
||||
* @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
|
||||
* const scaleByHalf = await sharp(input)
|
||||
* .metadata()
|
||||
* .then(({ width }) => sharp(input)
|
||||
* .resize(Math.round(width * 0.5))
|
||||
* .toBuffer()
|
||||
* );
|
||||
*
|
||||
* @param {number} [width] - pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height.
|
||||
* @param {number} [height] - pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
|
||||
* @param {Object} [options]
|
||||
* @param {String} [options.width] - alternative means of specifying `width`. If both are present this take priority.
|
||||
* @param {String} [options.height] - alternative means of specifying `height`. If both are present this take priority.
|
||||
* @param {String} [options.fit='cover'] - how the image should be resized to fit both provided dimensions, one of `cover`, `contain`, `fill`, `inside` or `outside`.
|
||||
* @param {String} [options.position='centre'] - 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 using a `fit` of `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.
|
||||
* @param {Boolean} [options.withoutEnlargement=false] - do not enlarge if the width *or* height are already less than the specified dimensions, equivalent to GraphicsMagick's `>` geometry option.
|
||||
* @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 on some images.
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
function resize (width, height, options) {
|
||||
if (is.defined(width)) {
|
||||
if (is.object(width) && !is.defined(options)) {
|
||||
options = width;
|
||||
} else if (is.integer(width) && width > 0) {
|
||||
this.options.width = width;
|
||||
} else {
|
||||
throw is.invalidParameterError('width', 'positive integer', width);
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
// Shrink on load
|
||||
if (is.defined(options.fastShrinkOnLoad)) {
|
||||
this._setBooleanOption('fastShrinkOnLoad', options.fastShrinkOnLoad);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends/pads the edges of the image with the provided background colour.
|
||||
* 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 }
|
||||
* })
|
||||
* ...
|
||||
*
|
||||
* @param {(number|Object)} extend - single pixel count to add to all edges or an Object with per-edge counts
|
||||
* @param {number} [extend.top]
|
||||
* @param {number} [extend.left]
|
||||
* @param {number} [extend.bottom]
|
||||
* @param {number} [extend.right]
|
||||
* @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) &&
|
||||
is.integer(extend.top) && extend.top >= 0 &&
|
||||
is.integer(extend.bottom) && extend.bottom >= 0 &&
|
||||
is.integer(extend.left) && extend.left >= 0 &&
|
||||
is.integer(extend.right) && extend.right >= 0
|
||||
) {
|
||||
this.options.extendTop = extend.top;
|
||||
this.options.extendBottom = extend.bottom;
|
||||
this.options.extendLeft = extend.left;
|
||||
this.options.extendRight = extend.right;
|
||||
this._setBackgroundColourOption('extendBackground', extend.background);
|
||||
} 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` before and after for both.
|
||||
*
|
||||
* @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 = this.options.width === -1 && this.options.height === -1 ? 'Pre' : 'Post';
|
||||
['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 (suffix === 'Pre' && isRotationExpected(this.options)) {
|
||||
this.options.rotateBeforePreExtract = true;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim "boring" pixels from all edges that contain values similar to the top-left pixel.
|
||||
* Images consisting entirely of a single colour will calculate "boring" using the alpha channel, if any.
|
||||
*
|
||||
* The `info` response Object, obtained from callback of `.toFile()` or `.toBuffer()`,
|
||||
* will contain `trimOffsetLeft` and `trimOffsetTop` properties.
|
||||
*
|
||||
* @param {number} [threshold=10] the allowed difference from the top-left pixel, a number greater than zero.
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
function trim (threshold) {
|
||||
if (!is.defined(threshold)) {
|
||||
this.options.trimThreshold = 10;
|
||||
} else if (is.number(threshold) && threshold > 0) {
|
||||
this.options.trimThreshold = threshold;
|
||||
} else {
|
||||
throw is.invalidParameterError('threshold', 'number greater than zero', threshold);
|
||||
}
|
||||
if (this.options.trimThreshold && 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;
|
||||
};
|
||||
172
lib/utility.js
Normal file
172
lib/utility.js
Normal file
@@ -0,0 +1,172 @@
|
||||
'use strict';
|
||||
|
||||
const events = require('events');
|
||||
const is = require('./is');
|
||||
const sharp = require('../build/Release/sharp.node');
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* 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/jcupitt/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/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48). Prevents "staircasing" when enlarging. */
|
||||
vertexSplitQuadraticBasisSpline: 'vsqbs'
|
||||
};
|
||||
|
||||
/**
|
||||
* An Object containing the version numbers of libvips and its dependencies.
|
||||
* @member
|
||||
* @example
|
||||
* console.log(sharp.versions);
|
||||
*/
|
||||
let versions = {
|
||||
vips: sharp.libvipsVersion()
|
||||
};
|
||||
try {
|
||||
versions = require(`../vendor/${versions.vips}/versions.json`);
|
||||
} catch (err) {}
|
||||
|
||||
/**
|
||||
* 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 number of threads _libvips'_ should create to process each image.
|
||||
* The default value is the number of CPU cores.
|
||||
* A value of `0` will reset to this default.
|
||||
*
|
||||
* The maximum number of images that can be processed in parallel
|
||||
* is limited by libuv's `UV_THREADPOOL_SIZE` environment variable.
|
||||
*
|
||||
* This method always returns the current concurrency.
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* Decorate the Sharp class with utility-related functions.
|
||||
* @private
|
||||
*/
|
||||
module.exports = function (Sharp) {
|
||||
[
|
||||
cache,
|
||||
concurrency,
|
||||
counters,
|
||||
simd
|
||||
].forEach(function (f) {
|
||||
Sharp[f.name] = f;
|
||||
});
|
||||
Sharp.format = format;
|
||||
Sharp.interpolators = interpolators;
|
||||
Sharp.versions = versions;
|
||||
Sharp.queue = queue;
|
||||
};
|
||||
17
mkdocs.yml
17
mkdocs.yml
@@ -1,17 +0,0 @@
|
||||
site_name: sharp
|
||||
site_url: http://sharp.dimens.io/
|
||||
repo_url: https://github.com/lovell/sharp
|
||||
site_description: The fastest Node.js module for resizing JPEG, PNG, WebP and TIFF images. Uses the libvips library.
|
||||
copyright: <a href="https://dimens.io/">dimens.io</a>
|
||||
google_analytics: ['UA-13034748-12', 'sharp.dimens.io']
|
||||
theme: readthedocs
|
||||
markdown_extensions:
|
||||
- toc:
|
||||
permalink: True
|
||||
dev_addr: 0.0.0.0:10101
|
||||
pages:
|
||||
- Home: index.md
|
||||
- Installation: install.md
|
||||
- API: api.md
|
||||
- Performance: performance.md
|
||||
- Changelog: changelog.md
|
||||
144
package.json
144
package.json
@@ -1,7 +1,9 @@
|
||||
{
|
||||
"name": "sharp",
|
||||
"version": "0.15.0",
|
||||
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images",
|
||||
"version": "0.27.0-beta1",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://github.com/lovell/sharp",
|
||||
"contributors": [
|
||||
"Pierre Inglebert <pierre.inglebert@gmail.com>",
|
||||
"Jonathan Ong <jonathanrichardong@gmail.com>",
|
||||
@@ -22,18 +24,75 @@
|
||||
"John Tobin <john@limelightmobileinc.com>",
|
||||
"Kenton Gray <kentongray@gmail.com>",
|
||||
"Felix Bünemann <Felix.Buenemann@gmail.com>",
|
||||
"Samy Al Zahrani <samyalzahrany@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>"
|
||||
],
|
||||
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images",
|
||||
"scripts": {
|
||||
"clean": "rm -rf node_modules/ build/ include/ lib/ coverage/ test/fixtures/output.*",
|
||||
"test": "VIPS_WARNING=0 node ./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha -- --slow=5000 --timeout=30000 ./test/unit/*.js",
|
||||
"test-win": "node ./node_modules/mocha/bin/mocha --slow=5000 --timeout=60000 ./test/unit/*.js",
|
||||
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
|
||||
"clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*",
|
||||
"test": "semistandard && cpplint && npm run test-unit && npm run test-licensing",
|
||||
"test-unit": "nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js",
|
||||
"test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
|
||||
"test-coverage": "./test/coverage/report.sh",
|
||||
"test-leak": "./test/leak/leak.sh",
|
||||
"test-packaging": "./packaging/test.sh",
|
||||
"test-clean": "rm -rf coverage/ test/fixtures/output.* && npm install && npm test"
|
||||
"docs-build": "documentation lint lib && for m in constructor input resize composite operation colour channel output utility; do documentation build --shallow --format=md --markdown-toc=false lib/$m.js >docs/api-$m.md; done && 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",
|
||||
"main": "lib/index.js",
|
||||
"files": [
|
||||
"binding.gyp",
|
||||
"install/**",
|
||||
"lib/**",
|
||||
"src/**"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/lovell/sharp"
|
||||
@@ -42,6 +101,7 @@
|
||||
"jpeg",
|
||||
"png",
|
||||
"webp",
|
||||
"avif",
|
||||
"tiff",
|
||||
"gif",
|
||||
"svg",
|
||||
@@ -50,35 +110,63 @@
|
||||
"resize",
|
||||
"thumbnail",
|
||||
"crop",
|
||||
"embed",
|
||||
"libvips",
|
||||
"vips"
|
||||
],
|
||||
"dependencies": {
|
||||
"bluebird": "^3.3.5",
|
||||
"color": "^0.11.1",
|
||||
"nan": "^2.2.1",
|
||||
"semver": "^5.1.0",
|
||||
"request": "^2.71.0",
|
||||
"tar": "^2.2.1"
|
||||
"array-flatten": "^3.0.0",
|
||||
"color": "^3.1.3",
|
||||
"detect-libc": "^1.0.3",
|
||||
"node-addon-api": "^3.1.0",
|
||||
"npmlog": "^4.1.2",
|
||||
"prebuild-install": "^6.0.0",
|
||||
"semver": "^7.3.4",
|
||||
"simple-get": "^4.0.0",
|
||||
"tar-fs": "^2.1.1",
|
||||
"tunnel-agent": "^0.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"async": "^1.5.2",
|
||||
"bufferutil": "^1.2.1",
|
||||
"coveralls": "^2.11.9",
|
||||
"exif-reader": "^1.0.0",
|
||||
"icc": "^0.0.2",
|
||||
"istanbul": "^0.4.3",
|
||||
"mocha": "^2.4.5",
|
||||
"mocha-jshint": "^2.3.1",
|
||||
"node-cpplint": "^0.4.0",
|
||||
"rimraf": "^2.5.2",
|
||||
"unzip": "^0.1.11"
|
||||
"async": "^3.2.0",
|
||||
"cc": "^3.0.1",
|
||||
"decompress-zip": "^0.3.2",
|
||||
"documentation": "^13.1.0",
|
||||
"exif-reader": "^1.0.3",
|
||||
"icc": "^2.0.0",
|
||||
"license-checker": "^25.0.1",
|
||||
"mocha": "^8.2.1",
|
||||
"mock-fs": "^4.13.0",
|
||||
"nyc": "^15.1.0",
|
||||
"prebuild": "^10.0.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"semistandard": "^16.0.0"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"config": {
|
||||
"libvips": "8.3.1"
|
||||
"libvips": "8.10.5",
|
||||
"runtime": "napi",
|
||||
"target": 3
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"binary": {
|
||||
"napi_versions": [
|
||||
3
|
||||
]
|
||||
},
|
||||
"semistandard": {
|
||||
"env": [
|
||||
"mocha"
|
||||
]
|
||||
},
|
||||
"cc": {
|
||||
"linelength": "120",
|
||||
"filter": [
|
||||
"build/include"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "Usage: $0 IP"
|
||||
echo "Build libvips for ARM using Docker, where IP is"
|
||||
echo "the address of a Raspberry Pi running HypriotOS"
|
||||
exit 1
|
||||
fi
|
||||
IP="$1"
|
||||
|
||||
echo "Verifying connectivity to $IP"
|
||||
if ! ping -c 1 $IP; then
|
||||
echo "Could not connect to $IP"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! type sshpass >/dev/null; then
|
||||
echo "Please install sshpass"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export SSHPASS=hypriot
|
||||
|
||||
echo "Copying arm/Dockerfile and arm/build.sh to device"
|
||||
sshpass -e scp -o PreferredAuthentications=password -r arm root@${IP}:/root
|
||||
|
||||
echo "Building Raspbian-based container"
|
||||
sshpass -e ssh -o PreferredAuthentications=password -t root@${IP} "docker build -t vips-dev-arm arm"
|
||||
|
||||
echo "Running arm/build.sh within container"
|
||||
sshpass -e ssh -o PreferredAuthentications=password -t root@${IP} "docker run -i -t --rm -v \${PWD}/arm:/arm vips-dev-arm sh -c 'cd /arm && ./build.sh' | tee arm/build.log"
|
||||
|
||||
echo "Copying resultant tar.gz file from device"
|
||||
sshpass -e scp -o PreferredAuthentications=password root@${IP}:/root/arm/*.tar.gz .
|
||||
@@ -1,28 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "Usage: $0 IP"
|
||||
echo "Test sharp on ARM using Docker, where IP is"
|
||||
echo "the address of a Raspberry Pi running HypriotOS"
|
||||
exit 1
|
||||
fi
|
||||
IP="$1"
|
||||
|
||||
echo "Verifying connectivity to $IP"
|
||||
if ! ping -c 1 $IP; then
|
||||
echo "Could not connect to $IP"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! type sshpass >/dev/null; then
|
||||
echo "Please install sshpass"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export SSHPASS=hypriot
|
||||
|
||||
echo "Copying sharp source to device"
|
||||
sshpass -e scp -o PreferredAuthentications=password -r ../../sharp root@${IP}:/root/sharp
|
||||
|
||||
echo "Compile and test within container"
|
||||
sshpass -e ssh -o PreferredAuthentications=password -t root@${IP} "docker run -i -t --rm -v \${PWD}/sharp:/s hypriot/rpi-node:5 sh -c 'cd /s && npm install --unsafe-perm && npm test'"
|
||||
@@ -1,5 +0,0 @@
|
||||
FROM resin/rpi-raspbian:jessie
|
||||
MAINTAINER Lovell Fuller <npm@lovell.info>
|
||||
|
||||
# Build dependencies
|
||||
RUN apt-get update && apt-get install -y build-essential autoconf libtool nasm gtk-doc-tools texinfo curl
|
||||
@@ -1,211 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# To be run inside a Raspbian container
|
||||
|
||||
# Working directories
|
||||
DEPS=/deps
|
||||
TARGET=/target
|
||||
mkdir ${DEPS}
|
||||
mkdir ${TARGET}
|
||||
|
||||
# Common build paths and flags
|
||||
export PKG_CONFIG_PATH="${PKG_CONFIG_PATH}:${TARGET}/lib/pkgconfig"
|
||||
export PATH="${PATH}:${TARGET}/bin"
|
||||
export CPPFLAGS="-I${TARGET}/include"
|
||||
export LDFLAGS="-L${TARGET}/lib"
|
||||
export CFLAGS="-O3"
|
||||
export CXXFLAGS="-O3"
|
||||
|
||||
# Dependency version numbers
|
||||
VERSION_ZLIB=1.2.8
|
||||
VERSION_FFI=3.2.1
|
||||
VERSION_GLIB=2.48.0
|
||||
VERSION_XML2=2.9.3
|
||||
VERSION_GSF=1.14.36
|
||||
VERSION_EXIF=0.6.21
|
||||
VERSION_LCMS2=2.7
|
||||
VERSION_JPEG=1.4.90
|
||||
VERSION_PNG16=1.6.21
|
||||
VERSION_WEBP=0.5.0
|
||||
VERSION_TIFF=4.0.6
|
||||
VERSION_ORC=0.4.25
|
||||
VERSION_GDKPIXBUF=2.34.0
|
||||
VERSION_FREETYPE=2.6.3
|
||||
VERSION_FONTCONFIG=2.11.95
|
||||
VERSION_HARFBUZZ=1.2.6
|
||||
VERSION_PIXMAN=0.34.0
|
||||
VERSION_CAIRO=1.14.6
|
||||
VERSION_PANGO=1.40.1
|
||||
VERSION_CROCO=0.6.11
|
||||
VERSION_SVG=2.40.15
|
||||
VERSION_GIF=5.1.4
|
||||
VERSION_VIPS=8.3.1
|
||||
|
||||
# Least out-of-sync Sourceforge mirror
|
||||
SOURCEFORGE_MIRROR=netix
|
||||
|
||||
mkdir ${DEPS}/zlib
|
||||
curl -Ls http://zlib.net/zlib-${VERSION_ZLIB}.tar.xz | tar xJC ${DEPS}/zlib --strip-components=1
|
||||
cd ${DEPS}/zlib
|
||||
./configure --prefix=${TARGET} && make install
|
||||
rm ${TARGET}/lib/libz.a
|
||||
|
||||
mkdir ${DEPS}/ffi
|
||||
curl -Ls ftp://sourceware.org/pub/libffi/libffi-${VERSION_FFI}.tar.gz | tar xzC ${DEPS}/ffi --strip-components=1
|
||||
cd ${DEPS}/ffi
|
||||
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-builddir && make install-strip
|
||||
|
||||
mkdir ${DEPS}/glib
|
||||
curl -Ls https://download.gnome.org/sources/glib/2.48/glib-${VERSION_GLIB}.tar.xz | tar xJC ${DEPS}/glib --strip-components=1
|
||||
cd ${DEPS}/glib
|
||||
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --with-pcre=internal && make install-strip
|
||||
|
||||
mkdir ${DEPS}/xml2
|
||||
curl -Ls http://xmlsoft.org/sources/libxml2-${VERSION_XML2}.tar.gz | tar xzC ${DEPS}/xml2 --strip-components=1
|
||||
cd ${DEPS}/xml2
|
||||
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --without-python --with-zlib=${TARGET} && make install-strip
|
||||
|
||||
mkdir ${DEPS}/gsf
|
||||
curl -Ls https://download.gnome.org/sources/libgsf/1.14/libgsf-${VERSION_GSF}.tar.xz | tar xJC ${DEPS}/gsf --strip-components=1
|
||||
cd ${DEPS}/gsf
|
||||
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
|
||||
|
||||
mkdir ${DEPS}/exif
|
||||
curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/libexif/libexif/${VERSION_EXIF}/libexif-${VERSION_EXIF}.tar.bz2 | tar xjC ${DEPS}/exif --strip-components=1
|
||||
cd ${DEPS}/exif
|
||||
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
|
||||
|
||||
mkdir ${DEPS}/lcms2
|
||||
curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/lcms/lcms/${VERSION_LCMS2}/lcms2-${VERSION_LCMS2}.tar.gz | tar xzC ${DEPS}/lcms2 --strip-components=1
|
||||
cd ${DEPS}/lcms2
|
||||
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
|
||||
|
||||
mkdir ${DEPS}/jpeg
|
||||
curl -Ls https://github.com/libjpeg-turbo/libjpeg-turbo/archive/${VERSION_JPEG}.tar.gz | tar xzC ${DEPS}/jpeg --strip-components=1
|
||||
cd ${DEPS}/jpeg
|
||||
autoreconf -fiv && ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --with-jpeg8 --without-turbojpeg && make install-strip
|
||||
|
||||
mkdir ${DEPS}/png16
|
||||
curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/libpng/libpng16/${VERSION_PNG16}/libpng-${VERSION_PNG16}.tar.xz | tar xJC ${DEPS}/png16 --strip-components=1
|
||||
cd ${DEPS}/png16
|
||||
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
|
||||
|
||||
mkdir ${DEPS}/webp
|
||||
curl -Ls http://downloads.webmproject.org/releases/webp/libwebp-${VERSION_WEBP}.tar.gz | tar xzC ${DEPS}/webp --strip-components=1
|
||||
cd ${DEPS}/webp
|
||||
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
|
||||
|
||||
mkdir ${DEPS}/tiff
|
||||
curl -Ls http://download.osgeo.org/libtiff/tiff-${VERSION_TIFF}.tar.gz | tar xzC ${DEPS}/tiff --strip-components=1
|
||||
cd ${DEPS}/tiff
|
||||
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
|
||||
rm ${TARGET}/lib/libtiffxx*
|
||||
|
||||
mkdir ${DEPS}/orc
|
||||
curl -Ls http://gstreamer.freedesktop.org/data/src/orc/orc-${VERSION_ORC}.tar.xz | tar xJC ${DEPS}/orc --strip-components=1
|
||||
cd ${DEPS}/orc
|
||||
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
|
||||
|
||||
mkdir ${DEPS}/gdkpixbuf
|
||||
curl -Ls https://download.gnome.org/sources/gdk-pixbuf/2.34/gdk-pixbuf-${VERSION_GDKPIXBUF}.tar.xz | tar xJC ${DEPS}/gdkpixbuf --strip-components=1
|
||||
cd ${DEPS}/gdkpixbuf
|
||||
LD_LIBRARY_PATH=${TARGET}/lib ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
|
||||
--disable-introspection --disable-modules --without-libpng --without-libjpeg --without-libtiff --without-gdiplus --with-included-loaders= \
|
||||
&& make install-strip
|
||||
|
||||
mkdir ${DEPS}/freetype
|
||||
curl -Ls http://download.savannah.gnu.org/releases/freetype/freetype-${VERSION_FREETYPE}.tar.gz | tar xzC ${DEPS}/freetype --strip-components=1
|
||||
cd ${DEPS}/freetype
|
||||
./configure --prefix=${TARGET} --enable-shared --disable-static && make install
|
||||
|
||||
mkdir ${DEPS}/fontconfig
|
||||
curl -Ls https://www.freedesktop.org/software/fontconfig/release/fontconfig-${VERSION_FONTCONFIG}.tar.bz2 | tar xjC ${DEPS}/fontconfig --strip-components=1
|
||||
cd ${DEPS}/fontconfig
|
||||
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --enable-libxml2 && make install-strip
|
||||
|
||||
mkdir ${DEPS}/harfbuzz
|
||||
curl -Ls https://www.freedesktop.org/software/harfbuzz/release/harfbuzz-${VERSION_HARFBUZZ}.tar.bz2 | tar xjC ${DEPS}/harfbuzz --strip-components=1
|
||||
cd ${DEPS}/harfbuzz
|
||||
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
|
||||
|
||||
mkdir ${DEPS}/pixman
|
||||
curl -Ls http://cairographics.org/releases/pixman-${VERSION_PIXMAN}.tar.gz | tar xzC ${DEPS}/pixman --strip-components=1
|
||||
cd ${DEPS}/pixman
|
||||
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-libpng --disable-arm-iwmmxt && make install-strip
|
||||
|
||||
mkdir ${DEPS}/cairo
|
||||
curl -Ls http://cairographics.org/releases/cairo-${VERSION_CAIRO}.tar.xz | tar xJC ${DEPS}/cairo --strip-components=1
|
||||
cd ${DEPS}/cairo
|
||||
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
|
||||
--disable-xlib --disable-xcb --disable-quartz --disable-win32 --disable-egl --disable-glx --disable-wgl \
|
||||
--disable-script --disable-ps --disable-gobject --disable-trace --disable-interpreter \
|
||||
&& make install-strip
|
||||
|
||||
mkdir ${DEPS}/pango
|
||||
curl -Ls https://download.gnome.org/sources/pango/1.40/pango-${VERSION_PANGO}.tar.xz | tar xJC ${DEPS}/pango --strip-components=1
|
||||
cd ${DEPS}/pango
|
||||
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
|
||||
|
||||
mkdir ${DEPS}/croco
|
||||
curl -Ls https://download.gnome.org/sources/libcroco/0.6/libcroco-${VERSION_CROCO}.tar.xz | tar xJC ${DEPS}/croco --strip-components=1
|
||||
cd ${DEPS}/croco
|
||||
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
|
||||
|
||||
mkdir ${DEPS}/svg
|
||||
curl -Ls https://download.gnome.org/sources/librsvg/2.40/librsvg-${VERSION_SVG}.tar.xz | tar xJC ${DEPS}/svg --strip-components=1
|
||||
cd ${DEPS}/svg
|
||||
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
|
||||
--disable-introspection --disable-tools \
|
||||
&& make install-strip
|
||||
|
||||
mkdir ${DEPS}/gif
|
||||
curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/giflib/giflib-${VERSION_GIF}.tar.gz | tar xzC ${DEPS}/gif --strip-components=1
|
||||
cd ${DEPS}/gif
|
||||
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
|
||||
|
||||
mkdir ${DEPS}/vips
|
||||
curl -Ls http://www.vips.ecs.soton.ac.uk/supported/8.3/vips-${VERSION_VIPS}.tar.gz | tar xzC ${DEPS}/vips --strip-components=1
|
||||
cd ${DEPS}/vips
|
||||
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
|
||||
--disable-debug --disable-introspection --without-python --without-fftw \
|
||||
--without-magick --without-pangoft2 --without-ppm --without-analyze --without-radiance \
|
||||
--with-zip-includes=${TARGET}/include --with-zip-libraries=${TARGET}/lib \
|
||||
--with-jpeg-includes=${TARGET}/include --with-jpeg-libraries=${TARGET}/lib \
|
||||
&& make install-strip
|
||||
|
||||
# Remove the old C++ bindings
|
||||
cd ${TARGET}/include
|
||||
rm -rf vips/vipsc++.h vips/vipscpp.h
|
||||
cd ${TARGET}/lib
|
||||
rm -rf pkgconfig .libs *.la libvipsCC*
|
||||
|
||||
# Create JSON file of version numbers
|
||||
cd ${TARGET}
|
||||
echo "{\n\
|
||||
\"cairo\": \"${VERSION_CAIRO}\",\n\
|
||||
\"croco\": \"${VERSION_CROCO}\",\n\
|
||||
\"exif\": \"${VERSION_EXIF}\",\n\
|
||||
\"ffi\": \"${VERSION_FFI}\",\n\
|
||||
\"fontconfig\": \"${VERSION_FONTCONFIG}\",\n\
|
||||
\"freetype\": \"${VERSION_FREETYPE}\",\n\
|
||||
\"gdkpixbuf\": \"${VERSION_GDKPIXBUF}\",\n\
|
||||
\"gif\": \"${VERSION_GIF}\",\n\
|
||||
\"glib\": \"${VERSION_GLIB}\",\n\
|
||||
\"gsf\": \"${VERSION_GSF}\",\n\
|
||||
\"harfbuzz\": \"${VERSION_HARFBUZZ}\",\n\
|
||||
\"jpeg\": \"${VERSION_JPEG}\",\n\
|
||||
\"lcms\": \"${VERSION_LCMS2}\",\n\
|
||||
\"orc\": \"${VERSION_ORC}\",\n\
|
||||
\"pango\": \"${VERSION_PANGO}\",\n\
|
||||
\"pixman\": \"${VERSION_PIXMAN}\",\n\
|
||||
\"png\": \"${VERSION_PNG16}\",\n\
|
||||
\"svg\": \"${VERSION_SVG}\",\n\
|
||||
\"tiff\": \"${VERSION_TIFF}\",\n\
|
||||
\"vips\": \"${VERSION_VIPS}\",\n\
|
||||
\"webp\": \"${VERSION_WEBP}\",\n\
|
||||
\"xml\": \"${VERSION_XML2}\",\n\
|
||||
\"zlib\": \"${VERSION_ZLIB}\"\n\
|
||||
}" >lib/versions.json
|
||||
|
||||
# Create .tar.gz
|
||||
GZIP=-9 tar czf /arm/libvips-${VERSION_VIPS}-arm.tar.gz include lib
|
||||
@@ -1,30 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
VERSION_VIPS=8.3.1
|
||||
|
||||
# Is docker available?
|
||||
|
||||
if ! type docker >/dev/null; then
|
||||
echo "Please install docker"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# TODO: docker v1.9.0 allows build-time args - https://github.com/docker/docker/pull/15182
|
||||
|
||||
# Windows
|
||||
|
||||
docker build -t vips-dev-win win
|
||||
WIN_CONTAINER_ID=$(docker run -d vips-dev-win)
|
||||
docker cp "${WIN_CONTAINER_ID}:/libvips-${VERSION_VIPS}-win.tar.gz" .
|
||||
docker rm "${WIN_CONTAINER_ID}"
|
||||
|
||||
# Linux
|
||||
|
||||
docker build -t vips-dev-lin lin
|
||||
LIN_CONTAINER_ID=$(docker run -d vips-dev-lin)
|
||||
docker cp "${LIN_CONTAINER_ID}:/libvips-${VERSION_VIPS}-lin.tar.gz" .
|
||||
docker rm "${LIN_CONTAINER_ID}"
|
||||
|
||||
# Checksums
|
||||
|
||||
sha256sum *.tar.gz
|
||||
@@ -1,221 +0,0 @@
|
||||
FROM debian:wheezy
|
||||
MAINTAINER Lovell Fuller <npm@lovell.info>
|
||||
|
||||
# Build dependencies
|
||||
RUN apt-get update && apt-get install -y build-essential autoconf libtool nasm gtk-doc-tools texinfo
|
||||
|
||||
# Create working directories
|
||||
ENV DEPS=/deps \
|
||||
TARGET=/target
|
||||
RUN mkdir ${DEPS} && mkdir ${TARGET}
|
||||
|
||||
# Common build paths and flags
|
||||
ENV PKG_CONFIG_PATH="${PKG_CONFIG_PATH}:${TARGET}/lib/pkgconfig" \
|
||||
PATH="${PATH}:${TARGET}/bin" \
|
||||
CPPFLAGS="-I${TARGET}/include" \
|
||||
LDFLAGS="-L${TARGET}/lib" \
|
||||
CFLAGS="-O3" \
|
||||
CXXFLAGS="-O3"
|
||||
|
||||
# Dependency version numbers
|
||||
ENV VERSION_ZLIB=1.2.8 \
|
||||
VERSION_FFI=3.2.1 \
|
||||
VERSION_GLIB=2.48.0 \
|
||||
VERSION_XML2=2.9.3 \
|
||||
VERSION_GSF=1.14.36 \
|
||||
VERSION_EXIF=0.6.21 \
|
||||
VERSION_LCMS2=2.7 \
|
||||
VERSION_JPEG=1.4.90 \
|
||||
VERSION_PNG16=1.6.21 \
|
||||
VERSION_WEBP=0.5.0 \
|
||||
VERSION_TIFF=4.0.6 \
|
||||
VERSION_ORC=0.4.25 \
|
||||
VERSION_GDKPIXBUF=2.34.0 \
|
||||
VERSION_FREETYPE=2.6.3 \
|
||||
VERSION_FONTCONFIG=2.11.95 \
|
||||
VERSION_HARFBUZZ=1.2.6 \
|
||||
VERSION_PIXMAN=0.34.0 \
|
||||
VERSION_CAIRO=1.14.6 \
|
||||
VERSION_PANGO=1.40.1 \
|
||||
VERSION_CROCO=0.6.11 \
|
||||
VERSION_SVG=2.40.15 \
|
||||
VERSION_GIF=5.1.4 \
|
||||
VERSION_VIPS=8.3.1
|
||||
|
||||
# Least out-of-sync Sourceforge mirror
|
||||
ENV SOURCEFORGE_MIRROR=netix
|
||||
|
||||
RUN mkdir ${DEPS}/zlib
|
||||
RUN curl -Ls http://zlib.net/zlib-${VERSION_ZLIB}.tar.xz | tar xJC ${DEPS}/zlib --strip-components=1
|
||||
WORKDIR ${DEPS}/zlib
|
||||
RUN ./configure --prefix=${TARGET} && make install
|
||||
RUN rm ${TARGET}/lib/libz.a
|
||||
|
||||
RUN mkdir ${DEPS}/ffi
|
||||
RUN curl -Ls ftp://sourceware.org/pub/libffi/libffi-${VERSION_FFI}.tar.gz | tar xzC ${DEPS}/ffi --strip-components=1
|
||||
WORKDIR ${DEPS}/ffi
|
||||
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-builddir && make install-strip
|
||||
|
||||
RUN mkdir ${DEPS}/glib
|
||||
RUN curl -Ls https://download.gnome.org/sources/glib/2.48/glib-${VERSION_GLIB}.tar.xz | tar xJC ${DEPS}/glib --strip-components=1
|
||||
WORKDIR ${DEPS}/glib
|
||||
RUN CFLAGS="${CFLAGS} -Wl,--default-symver" CXXFLAGS="${CXXFLAGS} -Wl,--default-symver" \
|
||||
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --with-pcre=internal && make install-strip
|
||||
|
||||
RUN mkdir ${DEPS}/xml2
|
||||
RUN curl -Ls http://xmlsoft.org/sources/libxml2-${VERSION_XML2}.tar.gz | tar xzC ${DEPS}/xml2 --strip-components=1
|
||||
WORKDIR ${DEPS}/xml2
|
||||
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
|
||||
--without-python --without-debug --without-docbook --without-ftp --without-html --without-legacy \
|
||||
--without-pattern --without-push --without-regexps --without-schemas --without-schematron --with-zlib=${TARGET} \
|
||||
&& make install-strip
|
||||
|
||||
RUN mkdir ${DEPS}/gsf
|
||||
RUN curl -Ls https://download.gnome.org/sources/libgsf/1.14/libgsf-${VERSION_GSF}.tar.xz | tar xJC ${DEPS}/gsf --strip-components=1
|
||||
WORKDIR ${DEPS}/gsf
|
||||
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
|
||||
|
||||
RUN mkdir ${DEPS}/exif
|
||||
RUN curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/libexif/libexif/${VERSION_EXIF}/libexif-${VERSION_EXIF}.tar.bz2 | tar xjC ${DEPS}/exif --strip-components=1
|
||||
WORKDIR ${DEPS}/exif
|
||||
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
|
||||
|
||||
RUN mkdir ${DEPS}/lcms2
|
||||
RUN curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/lcms/lcms/${VERSION_LCMS2}/lcms2-${VERSION_LCMS2}.tar.gz | tar xzC ${DEPS}/lcms2 --strip-components=1
|
||||
WORKDIR ${DEPS}/lcms2
|
||||
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
|
||||
|
||||
RUN mkdir ${DEPS}/jpeg
|
||||
RUN curl -Ls https://github.com/libjpeg-turbo/libjpeg-turbo/archive/${VERSION_JPEG}.tar.gz | tar xzC ${DEPS}/jpeg --strip-components=1
|
||||
WORKDIR ${DEPS}/jpeg
|
||||
RUN autoreconf -fiv && ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --with-jpeg8 --without-turbojpeg && make install-strip
|
||||
|
||||
RUN mkdir ${DEPS}/png16
|
||||
RUN curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/libpng/libpng16/${VERSION_PNG16}/libpng-${VERSION_PNG16}.tar.xz | tar xJC ${DEPS}/png16 --strip-components=1
|
||||
WORKDIR ${DEPS}/png16
|
||||
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
|
||||
|
||||
RUN mkdir ${DEPS}/webp
|
||||
RUN curl -Ls http://downloads.webmproject.org/releases/webp/libwebp-${VERSION_WEBP}.tar.gz | tar xzC ${DEPS}/webp --strip-components=1
|
||||
WORKDIR ${DEPS}/webp
|
||||
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
|
||||
|
||||
RUN mkdir ${DEPS}/tiff
|
||||
RUN curl -Ls http://download.osgeo.org/libtiff/tiff-${VERSION_TIFF}.tar.gz | tar xzC ${DEPS}/tiff --strip-components=1
|
||||
WORKDIR ${DEPS}/tiff
|
||||
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
|
||||
RUN rm ${TARGET}/lib/libtiffxx*
|
||||
|
||||
RUN mkdir ${DEPS}/orc
|
||||
RUN curl -Ls http://gstreamer.freedesktop.org/data/src/orc/orc-${VERSION_ORC}.tar.xz | tar xJC ${DEPS}/orc --strip-components=1
|
||||
WORKDIR ${DEPS}/orc
|
||||
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
|
||||
RUN rm ${TARGET}/lib/liborc-test-*
|
||||
|
||||
RUN mkdir ${DEPS}/gdkpixbuf
|
||||
RUN curl -Ls https://download.gnome.org/sources/gdk-pixbuf/2.34/gdk-pixbuf-${VERSION_GDKPIXBUF}.tar.xz | tar xJC ${DEPS}/gdkpixbuf --strip-components=1
|
||||
WORKDIR ${DEPS}/gdkpixbuf
|
||||
RUN LD_LIBRARY_PATH=${TARGET}/lib ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
|
||||
--disable-introspection --disable-modules --without-libpng --without-libjpeg --without-libtiff --without-gdiplus --with-included-loaders= \
|
||||
&& make install-strip
|
||||
|
||||
RUN mkdir ${DEPS}/freetype
|
||||
RUN curl -Ls http://download.savannah.gnu.org/releases/freetype/freetype-${VERSION_FREETYPE}.tar.gz | tar xzC ${DEPS}/freetype --strip-components=1
|
||||
WORKDIR ${DEPS}/freetype
|
||||
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static && make install
|
||||
|
||||
RUN mkdir ${DEPS}/fontconfig
|
||||
RUN curl -Ls https://www.freedesktop.org/software/fontconfig/release/fontconfig-${VERSION_FONTCONFIG}.tar.bz2 | tar xjC ${DEPS}/fontconfig --strip-components=1
|
||||
WORKDIR ${DEPS}/fontconfig
|
||||
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --enable-libxml2 && make install-strip
|
||||
|
||||
RUN mkdir ${DEPS}/harfbuzz
|
||||
RUN curl -Ls https://www.freedesktop.org/software/harfbuzz/release/harfbuzz-${VERSION_HARFBUZZ}.tar.bz2 | tar xjC ${DEPS}/harfbuzz --strip-components=1
|
||||
WORKDIR ${DEPS}/harfbuzz
|
||||
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
|
||||
|
||||
RUN mkdir ${DEPS}/pixman
|
||||
RUN curl -Ls http://cairographics.org/releases/pixman-${VERSION_PIXMAN}.tar.gz | tar xzC ${DEPS}/pixman --strip-components=1
|
||||
WORKDIR ${DEPS}/pixman
|
||||
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-libpng && make install-strip
|
||||
|
||||
RUN mkdir ${DEPS}/cairo
|
||||
RUN curl -Ls http://cairographics.org/releases/cairo-${VERSION_CAIRO}.tar.xz | tar xJC ${DEPS}/cairo --strip-components=1
|
||||
WORKDIR ${DEPS}/cairo
|
||||
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
|
||||
--disable-xlib --disable-xcb --disable-quartz --disable-win32 --disable-egl --disable-glx --disable-wgl \
|
||||
--disable-script --disable-ps --disable-gobject --disable-trace --disable-interpreter \
|
||||
&& make install-strip
|
||||
|
||||
RUN mkdir ${DEPS}/pango
|
||||
RUN curl -Ls https://download.gnome.org/sources/pango/1.40/pango-${VERSION_PANGO}.tar.xz | tar xJC ${DEPS}/pango --strip-components=1
|
||||
WORKDIR ${DEPS}/pango
|
||||
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
|
||||
|
||||
RUN mkdir ${DEPS}/croco
|
||||
RUN curl -Ls https://download.gnome.org/sources/libcroco/0.6/libcroco-${VERSION_CROCO}.tar.xz | tar xJC ${DEPS}/croco --strip-components=1
|
||||
WORKDIR ${DEPS}/croco
|
||||
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
|
||||
|
||||
RUN mkdir ${DEPS}/svg
|
||||
RUN curl -Ls https://download.gnome.org/sources/librsvg/2.40/librsvg-${VERSION_SVG}.tar.xz | tar xJC ${DEPS}/svg --strip-components=1
|
||||
WORKDIR ${DEPS}/svg
|
||||
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
|
||||
--disable-introspection --disable-tools \
|
||||
&& make install-strip
|
||||
|
||||
RUN mkdir ${DEPS}/gif
|
||||
RUN curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/giflib/giflib-${VERSION_GIF}.tar.gz | tar xzC ${DEPS}/gif --strip-components=1
|
||||
WORKDIR ${DEPS}/gif
|
||||
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
|
||||
|
||||
RUN mkdir ${DEPS}/vips
|
||||
RUN curl -Ls http://www.vips.ecs.soton.ac.uk/supported/8.3/vips-${VERSION_VIPS}.tar.gz | tar xzC ${DEPS}/vips --strip-components=1
|
||||
#RUN apt-get install -y swig gobject-introspection gettext glib2.0-dev
|
||||
#RUN curl -Ls https://github.com/jcupitt/libvips/archive/master.tar.gz | tar xzC ${DEPS}/vips --strip-components=1
|
||||
WORKDIR ${DEPS}/vips
|
||||
#RUN ./bootstrap.sh
|
||||
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
|
||||
--disable-debug --disable-introspection --without-python --without-fftw \
|
||||
--without-magick --without-pangoft2 --without-ppm --without-analyze --without-radiance \
|
||||
--with-zip-includes=${TARGET}/include --with-zip-libraries=${TARGET}/lib \
|
||||
--with-jpeg-includes=${TARGET}/include --with-jpeg-libraries=${TARGET}/lib \
|
||||
&& make install-strip
|
||||
|
||||
# Remove the old C++ bindings
|
||||
WORKDIR ${TARGET}/include
|
||||
RUN rm -rf vips/vipsc++.h vips/vipscpp.h
|
||||
WORKDIR ${TARGET}/lib
|
||||
RUN rm -rf pkgconfig .libs *.la libvipsCC*
|
||||
|
||||
# Create JSON file of version numbers
|
||||
WORKDIR ${TARGET}
|
||||
RUN echo "{\n\
|
||||
\"cairo\": \"${VERSION_CAIRO}\",\n\
|
||||
\"croco\": \"${VERSION_CROCO}\",\n\
|
||||
\"exif\": \"${VERSION_EXIF}\",\n\
|
||||
\"ffi\": \"${VERSION_FFI}\",\n\
|
||||
\"fontconfig\": \"${VERSION_FONTCONFIG}\",\n\
|
||||
\"freetype\": \"${VERSION_FREETYPE}\",\n\
|
||||
\"gdkpixbuf\": \"${VERSION_GDKPIXBUF}\",\n\
|
||||
\"gif\": \"${VERSION_GIF}\",\n\
|
||||
\"glib\": \"${VERSION_GLIB}\",\n\
|
||||
\"gsf\": \"${VERSION_GSF}\",\n\
|
||||
\"harfbuzz\": \"${VERSION_HARFBUZZ}\",\n\
|
||||
\"jpeg\": \"${VERSION_JPEG}\",\n\
|
||||
\"lcms\": \"${VERSION_LCMS2}\",\n\
|
||||
\"orc\": \"${VERSION_ORC}\",\n\
|
||||
\"pango\": \"${VERSION_PANGO}\",\n\
|
||||
\"pixman\": \"${VERSION_PIXMAN}\",\n\
|
||||
\"png\": \"${VERSION_PNG16}\",\n\
|
||||
\"svg\": \"${VERSION_SVG}\",\n\
|
||||
\"tiff\": \"${VERSION_TIFF}\",\n\
|
||||
\"vips\": \"${VERSION_VIPS}\",\n\
|
||||
\"webp\": \"${VERSION_WEBP}\",\n\
|
||||
\"xml\": \"${VERSION_XML2}\",\n\
|
||||
\"zlib\": \"${VERSION_ZLIB}\"\n\
|
||||
}" >lib/versions.json
|
||||
|
||||
# Create .tar.gz
|
||||
WORKDIR ${TARGET}
|
||||
RUN GZIP=-9 tar czf /libvips-${VERSION_VIPS}-lin.tar.gz include lib
|
||||
@@ -1,56 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Verify docker is available
|
||||
if ! type docker >/dev/null; then
|
||||
echo "Please install docker"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
version_node=4.4.2
|
||||
|
||||
test="npm run clean; npm install --unsafe-perm; npm test"
|
||||
|
||||
# Debian 7, 8
|
||||
# Ubuntu 14.04
|
||||
for dist in wheezy jessie trusty; do
|
||||
echo "Testing $dist..."
|
||||
if docker run -i -t --rm -v $PWD:/v -e "NODE_ENV=development" nodesource/$dist:$version_node >packaging/$dist.log 2>&1 sh -c "cd /v; ./packaging/test/debian.sh; $test";
|
||||
then echo "$dist OK"
|
||||
else echo "$dist fail" && cat packaging/$dist.log
|
||||
fi
|
||||
done
|
||||
|
||||
# Centos 7
|
||||
echo "Testing centos7..."
|
||||
if docker run -i -t --rm -v $PWD:/v -e "NODE_ENV=development" nodesource/centos7:$version_node >packaging/$dist.log 2>&1 sh -c "cd /v; $test";
|
||||
then echo "$dist OK"
|
||||
else echo "$dist fail" && cat packaging/$dist.log
|
||||
fi
|
||||
|
||||
# Fedora 22
|
||||
echo "Testing fedora22..."
|
||||
if docker run -i -t --rm -v $PWD:/v -e "NODE_ENV=development" nodesource/fedora22:$version_node >packaging/$dist.log 2>&1 sh -c "cd /v; $test";
|
||||
then echo "$dist OK"
|
||||
else echo "$dist fail" && cat packaging/$dist.log
|
||||
fi
|
||||
|
||||
# openSUSE 13.2
|
||||
echo "Testing opensuse..."
|
||||
if docker run -i -t --rm -v $PWD:/v opensuse:13.2 >packaging/opensuse.log 2>&1 /bin/sh -c "cd /v; ./packaging/test/opensuse.sh; $test";
|
||||
then echo "opensuse OK"
|
||||
else echo "opensuse fail" && cat packaging/opensuse.log
|
||||
fi
|
||||
|
||||
# Archlinux 2015.06.01
|
||||
echo "Testing archlinux..."
|
||||
if docker run -i -t --rm -v $PWD:/v base/archlinux:2015.06.01 >packaging/archlinux.log 2>&1 sh -c "cd /v; ./packaging/test/archlinux.sh; $test";
|
||||
then echo "archlinux OK"
|
||||
else echo "archlinux fail" && cat packaging/archlinux.log
|
||||
fi
|
||||
|
||||
# Alpine
|
||||
echo "Testing alpine..."
|
||||
if docker run -i -t --rm -v $PWD:/v -e "SHARP_TEST_WITHOUT_CACHE=0" alpine:edge >packaging/alpine.log 2>&1 sh -c "cd /v; ./packaging/test/alpine.sh; $test";
|
||||
then echo "alpine OK"
|
||||
else echo "alpine fail" && cat packaging/alpine.log
|
||||
fi
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Install build dependencies
|
||||
apk add --update make gcc g++ python nodejs
|
||||
|
||||
# Install libvips with build headers and dependencies
|
||||
apk add libvips-dev --update --repository https://s3.amazonaws.com/wjordan-apk --allow-untrusted
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Install Node.js on Archlinux
|
||||
pacman -Sy --noconfirm gcc make python2 nodejs npm | cat
|
||||
ln -s /usr/bin/python2 /usr/bin/python
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Install pkg-config on Debian/Ubuntu
|
||||
apt-get update
|
||||
apt-get install -y pkg-config
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Install Node.js on openSUSE 13.2
|
||||
zypper addrepo http://download.opensuse.org/repositories/devel:languages:nodejs/openSUSE_13.2/devel:languages:nodejs.repo
|
||||
zypper --gpg-auto-import-keys refresh
|
||||
zypper --non-interactive install gcc-c++ make nodejs-devel npm
|
||||
npm install -g npm
|
||||
@@ -1,20 +0,0 @@
|
||||
FROM debian:wheezy
|
||||
MAINTAINER Lovell Fuller <npm@lovell.info>
|
||||
|
||||
RUN apt-get update && apt-get install -y curl zip
|
||||
|
||||
ENV VERSION_VIPS=8.3.1
|
||||
|
||||
# Fetch and unzip
|
||||
RUN mkdir /vips
|
||||
WORKDIR /vips
|
||||
RUN curl -L -O https://github.com/lovell/build-win64/releases/download/v${VERSION_VIPS}/vips-dev-w64-web-${VERSION_VIPS}.zip
|
||||
RUN unzip vips-dev-w64-web-${VERSION_VIPS}.zip
|
||||
|
||||
# Clean and zip
|
||||
WORKDIR /vips/vips-dev-8.3
|
||||
RUN rm bin/libvipsCC-42.dll bin/libvips-cpp-42.dll bin/libgsf-win32-1-114.dll
|
||||
RUN cp bin/*.dll lib/
|
||||
RUN cp -r lib64/* lib/
|
||||
|
||||
RUN GZIP=-9 tar czf /libvips-${VERSION_VIPS}-win.tar.gz include lib/glib-2.0 lib/libvips.lib lib/libglib-2.0.lib lib/libgobject-2.0.lib lib/*.dll
|
||||
313
preinstall.sh
313
preinstall.sh
@@ -1,313 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Use of this script is deprecated
|
||||
|
||||
echo
|
||||
echo "WARNING: This script is no longer required on most 64-bit Linux systems when using sharp v0.12.0+"
|
||||
echo
|
||||
echo "See http://sharp.dimens.io/page/install#linux"
|
||||
echo
|
||||
echo "If you really, really need this script, it will attempt"
|
||||
echo "to globally install libvips if not already available."
|
||||
echo
|
||||
|
||||
vips_version_minimum=8.3.1
|
||||
vips_version_latest_major_minor=8.3
|
||||
vips_version_latest_patch=1
|
||||
|
||||
openslide_version_minimum=3.4.0
|
||||
openslide_version_latest_major_minor=3.4
|
||||
openslide_version_latest_patch=1
|
||||
|
||||
install_libvips_from_source() {
|
||||
echo "Compiling libvips $vips_version_latest_major_minor.$vips_version_latest_patch from source"
|
||||
curl -O http://www.vips.ecs.soton.ac.uk/supported/$vips_version_latest_major_minor/vips-$vips_version_latest_major_minor.$vips_version_latest_patch.tar.gz
|
||||
tar zvxf vips-$vips_version_latest_major_minor.$vips_version_latest_patch.tar.gz
|
||||
cd vips-$vips_version_latest_major_minor.$vips_version_latest_patch
|
||||
CXXFLAGS="-D_GLIBCXX_USE_CXX11_ABI=0" ./configure --disable-debug --disable-docs --disable-static --disable-introspection --disable-dependency-tracking --enable-cxx=yes --without-python --without-orc --without-fftw $1
|
||||
make
|
||||
make install
|
||||
cd ..
|
||||
rm -rf vips-$vips_version_latest_major_minor.$vips_version_latest_patch
|
||||
rm vips-$vips_version_latest_major_minor.$vips_version_latest_patch.tar.gz
|
||||
ldconfig
|
||||
echo "Installed libvips $(PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig pkg-config --modversion vips)"
|
||||
}
|
||||
|
||||
install_libopenslide_from_source() {
|
||||
echo "Compiling openslide $openslide_version_latest_major_minor.$openslide_version_latest_patch from source"
|
||||
curl -O -L https://github.com/openslide/openslide/releases/download/v$openslide_version_latest_major_minor.$openslide_version_latest_patch/openslide-$openslide_version_latest_major_minor.$openslide_version_latest_patch.tar.gz
|
||||
tar xzvf openslide-$openslide_version_latest_major_minor.$openslide_version_latest_patch.tar.gz
|
||||
cd openslide-$openslide_version_latest_major_minor.$openslide_version_latest_patch
|
||||
PKG_CONFIG_PATH=$pkg_config_path ./configure $1
|
||||
make
|
||||
make install
|
||||
cd ..
|
||||
rm -rf openslide-$openslide_version_latest_major_minor.$openslide_version_latest_patch
|
||||
rm openslide-$openslide_version_latest_major_minor.$openslide_version_latest_patch.tar.gz
|
||||
ldconfig
|
||||
echo "Installed libopenslide $openslide_version_latest_major_minor.$openslide_version_latest_patch"
|
||||
}
|
||||
|
||||
sorry() {
|
||||
echo "Sorry, I don't yet know how to install lib$1 on $2"
|
||||
exit 1
|
||||
}
|
||||
|
||||
pkg_config_path="$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig"
|
||||
|
||||
check_if_library_exists() {
|
||||
PKG_CONFIG_PATH=$pkg_config_path pkg-config --exists $1
|
||||
if [ $? -eq 0 ]; then
|
||||
version_found=$(PKG_CONFIG_PATH=$pkg_config_path pkg-config --modversion $1)
|
||||
PKG_CONFIG_PATH=$pkg_config_path pkg-config --atleast-version=$2 $1
|
||||
if [ $? -eq 0 ]; then
|
||||
# Found suitable version of libvips
|
||||
echo "Found lib$1 $version_found"
|
||||
return 1
|
||||
fi
|
||||
echo "Found lib$1 $version_found but require $2"
|
||||
else
|
||||
echo "Could not find lib$1 using a PKG_CONFIG_PATH of '$pkg_config_path'"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
enable_openslide=0
|
||||
# Is libvips already installed, and is it at least the minimum required version?
|
||||
if [ $# -eq 1 ]; then
|
||||
if [ "$1" = "--with-openslide" ]; then
|
||||
echo "Installing vips with openslide support"
|
||||
enable_openslide=1
|
||||
else
|
||||
echo "Sorry, $1 is not supported. Did you mean --with-openslide?"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! type pkg-config >/dev/null; then
|
||||
sorry "vips" "a system without pkg-config"
|
||||
fi
|
||||
|
||||
openslide_exists=0
|
||||
if [ $enable_openslide -eq 1 ]; then
|
||||
check_if_library_exists "openslide" "$openslide_version_minimum"
|
||||
openslide_exists=$?
|
||||
fi
|
||||
|
||||
check_if_library_exists "vips" "$vips_version_minimum"
|
||||
vips_exists=$?
|
||||
if [ $vips_exists -eq 1 ] && [ $enable_openslide -eq 1 ]; then
|
||||
if [ $openslide_exists -eq 1 ]; then
|
||||
# Check if vips compiled with openslide support
|
||||
vips_with_openslide=`vips list classes | grep -i opensli`
|
||||
if [ -z $vips_with_openslide ]; then
|
||||
echo "Vips compiled without openslide support."
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
elif [ $vips_exists -eq 1 ] && [ $enable_openslide -eq 0 ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Verify root/sudo access
|
||||
if [ "$(id -u)" -ne "0" ]; then
|
||||
echo "Sorry, I need root/sudo access to continue"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Deprecation warning
|
||||
if [ "$(arch)" == "x86_64" ]; then
|
||||
echo "This script is no longer required on most 64-bit Linux systems when using sharp v0.12.0+"
|
||||
fi
|
||||
|
||||
# OS-specific installations of libopenslide follows
|
||||
# Either openslide does not exist, or vips is installed without openslide support
|
||||
if [ $enable_openslide -eq 1 ] && [ -z $vips_with_openslide ] && [ $openslide_exists -eq 0 ]; then
|
||||
if [ -f /etc/debian_version ]; then
|
||||
# Debian Linux
|
||||
DISTRO=$(lsb_release -c -s)
|
||||
echo "Detected Debian Linux '$DISTRO'"
|
||||
case "$DISTRO" in
|
||||
jessie|vivid|wily|xenial)
|
||||
# Debian 8, Ubuntu 15
|
||||
echo "Installing libopenslide via apt-get"
|
||||
apt-get install -y libopenslide-dev
|
||||
;;
|
||||
trusty|utopic|qiana|rebecca|rafaela|freya)
|
||||
# Ubuntu 14, Mint 17
|
||||
echo "Installing libopenslide dependencies via apt-get"
|
||||
apt-get install -y automake build-essential curl zlib1g-dev libopenjpeg-dev libpng12-dev libjpeg-dev libtiff5-dev libgdk-pixbuf2.0-dev libxml2-dev libsqlite3-dev libcairo2-dev libglib2.0-dev sqlite3 libsqlite3-dev
|
||||
install_libopenslide_from_source
|
||||
;;
|
||||
precise|wheezy|maya)
|
||||
# Debian 7, Ubuntu 12.04, Mint 13
|
||||
echo "Installing libopenslide dependencies via apt-get"
|
||||
apt-get install -y automake build-essential curl zlib1g-dev libopenjpeg-dev libpng12-dev libjpeg-dev libtiff5-dev libgdk-pixbuf2.0-dev libxml2-dev libsqlite3-dev libcairo2-dev libglib2.0-dev sqlite3 libsqlite3-dev
|
||||
install_libopenslide_from_source
|
||||
;;
|
||||
*)
|
||||
# Unsupported Debian-based OS
|
||||
sorry "openslide" "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 libopenslide dependencies via yum"
|
||||
yum groupinstall -y "Development Tools"
|
||||
yum install -y tar curl libpng-devel libjpeg-devel libxml2-devel zlib-devel openjpeg-devel libtiff-devel gdk-pixbuf2-devel sqlite-devel cairo-devel glib2-devel
|
||||
install_libopenslide_from_source "--prefix=/usr"
|
||||
;;
|
||||
"Red Hat Enterprise Linux release 6."*|"CentOS release 6."*|"Scientific Linux release 6."*)
|
||||
# RHEL/CentOS 6
|
||||
echo "Installing libopenslide dependencies via yum"
|
||||
yum groupinstall -y "Development Tools"
|
||||
yum install -y tar curl libpng-devel libjpeg-devel libxml2-devel zlib-devel openjpeg-devel libtiff-devel gdk-pixbuf2-devel sqlite-devel cairo-devel glib2-devel
|
||||
install_libopenslide_from_source "--prefix=/usr"
|
||||
;;
|
||||
"Fedora release 21 "*|"Fedora release 22 "*)
|
||||
# Fedora 21, 22
|
||||
echo "Installing libopenslide via yum"
|
||||
yum install -y openslide-devel
|
||||
;;
|
||||
*)
|
||||
# Unsupported RHEL-based OS
|
||||
sorry "openslide" "$RELEASE"
|
||||
;;
|
||||
esac
|
||||
elif [ -f /etc/os-release ]; then
|
||||
RELEASE=$(cat /etc/os-release | grep VERSION)
|
||||
echo "Detected OpenSuse Linux '$RELEASE'"
|
||||
case $RELEASE in
|
||||
*"13.2"*)
|
||||
echo "Installing libopenslide via zypper"
|
||||
zypper --gpg-auto-import-keys install -y libopenslide-devel
|
||||
;;
|
||||
esac
|
||||
elif [ -f /etc/SuSE-brand ]; then
|
||||
RELEASE=$(cat /etc/SuSE-brand | grep VERSION)
|
||||
echo "Detected OpenSuse Linux '$RELEASE'"
|
||||
case $RELEASE in
|
||||
*"13.1")
|
||||
echo "Installing libopenslide dependencies via zypper"
|
||||
zypper --gpg-auto-import-keys install -y --type pattern devel_basis
|
||||
zypper --gpg-auto-import-keys install -y tar curl libpng16-devel libjpeg-turbo libjpeg8-devel libxml2-devel zlib-devel openjpeg-devel libtiff-devel libgdk_pixbuf-2_0-0 sqlite3-devel cairo-devel glib2-devel
|
||||
install_libopenslide_from_source
|
||||
;;
|
||||
esac
|
||||
else
|
||||
# Unsupported OS
|
||||
sorry "openslide" "$(uname -a)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# OS-specific installations of libvips follows
|
||||
|
||||
if [ -f /etc/debian_version ]; then
|
||||
# Debian Linux
|
||||
DISTRO=$(lsb_release -c -s)
|
||||
echo "Detected Debian Linux '$DISTRO'"
|
||||
case "$DISTRO" in
|
||||
jessie|trusty|utopic|vivid|wily|xenial|qiana|rebecca|rafaela|freya)
|
||||
# Debian 8, Ubuntu 14.04+, 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-dev libpng12-dev libwebp-dev libtiff5-dev libexif-dev libgsf-1-dev liblcms2-dev libxml2-dev swig libmagickcore-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-dev libpng12-dev libwebp-dev libtiff4-dev libexif-dev libgsf-1-dev liblcms2-dev libxml2-dev swig libmagickcore-dev curl
|
||||
install_libvips_from_source
|
||||
;;
|
||||
*)
|
||||
# Unsupported Debian-based OS
|
||||
sorry "vips" "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 tar curl gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel libgsf-devel lcms2-devel ImageMagick-devel gobject-introspection-devel libwebp-devel
|
||||
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 tar curl gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel libgsf-devel lcms-devel ImageMagick-devel
|
||||
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"*)
|
||||
# Fedora 21, 22, 23
|
||||
echo "Installing libvips dependencies via yum"
|
||||
yum groupinstall -y "Development Tools"
|
||||
yum install -y gcc-c++ 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"
|
||||
;;
|
||||
*)
|
||||
# Unsupported RHEL-based OS
|
||||
sorry "vips" "$RELEASE"
|
||||
;;
|
||||
esac
|
||||
elif [ -f /etc/system-release ]; then
|
||||
# Probably Amazon Linux
|
||||
RELEASE=$(cat /etc/system-release)
|
||||
case $RELEASE in
|
||||
"Amazon Linux AMI release 2015.03"|"Amazon Linux AMI release 2015.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 libgsf-devel lcms2-devel ImageMagick-devel gobject-introspection-devel libwebp-devel curl
|
||||
install_libvips_from_source "--prefix=/usr"
|
||||
;;
|
||||
*)
|
||||
# Unsupported Amazon Linux version
|
||||
sorry "vips" "$RELEASE"
|
||||
;;
|
||||
esac
|
||||
elif [ -f /etc/os-release ]; then
|
||||
RELEASE=$(cat /etc/os-release | grep VERSION)
|
||||
echo "Detected OpenSuse Linux '$RELEASE'"
|
||||
case $RELEASE in
|
||||
*"13.2"*)
|
||||
echo "Installing libvips dependencies via zypper"
|
||||
zypper --gpg-auto-import-keys install -y --type pattern devel_basis
|
||||
zypper --gpg-auto-import-keys install -y tar curl gtk-doc libxml2-devel libjpeg-turbo libjpeg8-devel libpng16-devel libtiff-devel libexif-devel liblcms2-devel ImageMagick-devel gobject-introspection-devel libwebp-devel
|
||||
install_libvips_from_source
|
||||
;;
|
||||
esac
|
||||
elif [ -f /etc/SuSE-brand ]; then
|
||||
RELEASE=$(cat /etc/SuSE-brand | grep VERSION)
|
||||
echo "Detected OpenSuse Linux '$RELEASE'"
|
||||
case $RELEASE in
|
||||
*"13.1")
|
||||
echo "Installing libvips dependencies via zypper"
|
||||
zypper --gpg-auto-import-keys install -y --type pattern devel_basis
|
||||
zypper --gpg-auto-import-keys install -y tar curl gtk-doc libxml2-devel libjpeg-turbo libjpeg8-devel libpng16-devel libtiff-devel libexif-devel liblcms2-devel ImageMagick-devel gobject-introspection-devel libwebp-devel
|
||||
install_libvips_from_source
|
||||
;;
|
||||
esac
|
||||
else
|
||||
# Unsupported OS
|
||||
sorry "vips" "$(uname -a)"
|
||||
fi
|
||||
662
src/common.cc
662
src/common.cc
@@ -1,32 +1,123 @@
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// http://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.
|
||||
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
#include <map>
|
||||
#include <mutex> // NOLINT(build/c++11)
|
||||
|
||||
#include <napi.h>
|
||||
#include <vips/vips8>
|
||||
|
||||
#include "common.h"
|
||||
|
||||
// Verify platform and compiler compatibility
|
||||
|
||||
#if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 2))
|
||||
#error libvips version 8.2.0+ required - see sharp.dimens.io/page/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 - see sharp.dimens.io/page/install#prerequisites
|
||||
#endif
|
||||
|
||||
#if (defined(__clang__) && defined(__has_feature))
|
||||
#if (!__has_feature(cxx_range_for))
|
||||
#error clang version 3.0+ is required for C++11 features - see sharp.dimens.io/page/install#prerequisites
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define EXIF_IFD0_ORIENTATION "exif-ifd0-Orientation"
|
||||
|
||||
using vips::VImage;
|
||||
|
||||
namespace sharp {
|
||||
|
||||
// Convenience methods to access the attributes of a Napi::Object
|
||||
bool HasAttr(Napi::Object obj, std::string attr) {
|
||||
return obj.Has(attr);
|
||||
}
|
||||
std::string AttrAsStr(Napi::Object obj, std::string attr) {
|
||||
return obj.Get(attr).As<Napi::String>();
|
||||
}
|
||||
uint32_t AttrAsUint32(Napi::Object obj, std::string attr) {
|
||||
return obj.Get(attr).As<Napi::Number>().Uint32Value();
|
||||
}
|
||||
int32_t AttrAsInt32(Napi::Object obj, std::string attr) {
|
||||
return obj.Get(attr).As<Napi::Number>().Int32Value();
|
||||
}
|
||||
int32_t AttrAsInt32(Napi::Object obj, unsigned int const attr) {
|
||||
return obj.Get(attr).As<Napi::Number>().Int32Value();
|
||||
}
|
||||
double AttrAsDouble(Napi::Object obj, std::string attr) {
|
||||
return obj.Get(attr).As<Napi::Number>().DoubleValue();
|
||||
}
|
||||
double AttrAsDouble(Napi::Object obj, unsigned int const attr) {
|
||||
return obj.Get(attr).As<Napi::Number>().DoubleValue();
|
||||
}
|
||||
bool AttrAsBool(Napi::Object obj, std::string attr) {
|
||||
return obj.Get(attr).As<Napi::Boolean>().Value();
|
||||
}
|
||||
std::vector<double> AttrAsVectorOfDouble(Napi::Object obj, std::string attr) {
|
||||
Napi::Array napiArray = obj.Get(attr).As<Napi::Array>();
|
||||
std::vector<double> vectorOfDouble(napiArray.Length());
|
||||
for (unsigned int i = 0; i < napiArray.Length(); i++) {
|
||||
vectorOfDouble[i] = AttrAsDouble(napiArray, i);
|
||||
}
|
||||
return vectorOfDouble;
|
||||
}
|
||||
std::vector<int32_t> AttrAsInt32Vector(Napi::Object obj, std::string attr) {
|
||||
Napi::Array array = obj.Get(attr).As<Napi::Array>();
|
||||
std::vector<int32_t> vector(array.Length());
|
||||
for (unsigned int i = 0; i < array.Length(); i++) {
|
||||
vector[i] = AttrAsInt32(array, i);
|
||||
}
|
||||
return vector;
|
||||
}
|
||||
|
||||
// Create an InputDescriptor instance from a Napi::Object describing an input image
|
||||
InputDescriptor* CreateInputDescriptor(Napi::Object input) {
|
||||
InputDescriptor *descriptor = new InputDescriptor;
|
||||
if (HasAttr(input, "file")) {
|
||||
descriptor->file = AttrAsStr(input, "file");
|
||||
} else if (HasAttr(input, "buffer")) {
|
||||
Napi::Buffer<char> buffer = input.Get("buffer").As<Napi::Buffer<char>>();
|
||||
descriptor->bufferLength = buffer.Length();
|
||||
descriptor->buffer = buffer.Data();
|
||||
descriptor->isBuffer = TRUE;
|
||||
}
|
||||
descriptor->failOnError = AttrAsBool(input, "failOnError");
|
||||
// Density for vector-based input
|
||||
if (HasAttr(input, "density")) {
|
||||
descriptor->density = AttrAsDouble(input, "density");
|
||||
}
|
||||
// Raw pixel input
|
||||
if (HasAttr(input, "rawChannels")) {
|
||||
descriptor->rawChannels = AttrAsUint32(input, "rawChannels");
|
||||
descriptor->rawWidth = AttrAsUint32(input, "rawWidth");
|
||||
descriptor->rawHeight = AttrAsUint32(input, "rawHeight");
|
||||
}
|
||||
// Multi-page input (GIF, TIFF, PDF)
|
||||
if (HasAttr(input, "pages")) {
|
||||
descriptor->pages = AttrAsInt32(input, "pages");
|
||||
}
|
||||
if (HasAttr(input, "page")) {
|
||||
descriptor->page = AttrAsUint32(input, "page");
|
||||
}
|
||||
// Multi-level input (OpenSlide)
|
||||
if (HasAttr(input, "level")) {
|
||||
descriptor->level = AttrAsUint32(input, "level");
|
||||
}
|
||||
// Create new image
|
||||
if (HasAttr(input, "createChannels")) {
|
||||
descriptor->createChannels = AttrAsUint32(input, "createChannels");
|
||||
descriptor->createWidth = AttrAsUint32(input, "createWidth");
|
||||
descriptor->createHeight = AttrAsUint32(input, "createHeight");
|
||||
descriptor->createBackground = AttrAsVectorOfDouble(input, "createBackground");
|
||||
}
|
||||
// Limit input images to a given number of pixels, where pixels = width * height
|
||||
descriptor->limitInputPixels = AttrAsUint32(input, "limitInputPixels");
|
||||
// Allow switch from random to sequential access
|
||||
descriptor->access = AttrAsBool(input, "sequentialRead") ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
// How many tasks are in the queue?
|
||||
volatile int counterQueue = 0;
|
||||
|
||||
@@ -46,15 +137,30 @@ namespace sharp {
|
||||
bool IsWebp(std::string const &str) {
|
||||
return EndsWith(str, ".webp") || EndsWith(str, ".WEBP");
|
||||
}
|
||||
bool IsGif(std::string const &str) {
|
||||
return EndsWith(str, ".gif") || EndsWith(str, ".GIF");
|
||||
}
|
||||
bool IsTiff(std::string const &str) {
|
||||
return EndsWith(str, ".tif") || EndsWith(str, ".tiff") || EndsWith(str, ".TIF") || EndsWith(str, ".TIFF");
|
||||
}
|
||||
bool IsHeic(std::string const &str) {
|
||||
return EndsWith(str, ".heic") || EndsWith(str, ".HEIC");
|
||||
}
|
||||
bool IsHeif(std::string const &str) {
|
||||
return EndsWith(str, ".heif") || EndsWith(str, ".HEIF") || IsHeic(str) || IsAvif(str);
|
||||
}
|
||||
bool IsAvif(std::string const &str) {
|
||||
return EndsWith(str, ".avif") || EndsWith(str, ".AVIF");
|
||||
}
|
||||
bool IsDz(std::string const &str) {
|
||||
return EndsWith(str, ".dzi") || EndsWith(str, ".DZI");
|
||||
}
|
||||
bool IsDzZip(std::string const &str) {
|
||||
return EndsWith(str, ".zip") || EndsWith(str, ".ZIP") || EndsWith(str, ".szi") || EndsWith(str, ".SZI");
|
||||
}
|
||||
bool IsV(std::string const &str) {
|
||||
return EndsWith(str, ".v") || EndsWith(str, ".V") || EndsWith(str, ".vips") || EndsWith(str, ".VIPS");
|
||||
}
|
||||
|
||||
/*
|
||||
Provide a string identifier for the given image type.
|
||||
@@ -68,41 +174,58 @@ namespace sharp {
|
||||
case ImageType::TIFF: id = "tiff"; break;
|
||||
case ImageType::GIF: id = "gif"; break;
|
||||
case ImageType::SVG: id = "svg"; break;
|
||||
case ImageType::HEIF: id = "heif"; break;
|
||||
case ImageType::PDF: id = "pdf"; break;
|
||||
case ImageType::MAGICK: id = "magick"; break;
|
||||
case ImageType::OPENSLIDE: id = "openslide"; break;
|
||||
case ImageType::PPM: id = "ppm"; break;
|
||||
case ImageType::FITS: id = "fits"; break;
|
||||
case ImageType::EXR: id = "exr"; break;
|
||||
case ImageType::VIPS: id = "vips"; break;
|
||||
case ImageType::RAW: id = "raw"; break;
|
||||
case ImageType::UNKNOWN: id = "unknown"; break;
|
||||
case ImageType::MISSING: id = "missing"; break;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
std::map<std::string, ImageType> loaderToType = {
|
||||
{ "jpegload", ImageType::JPEG },
|
||||
{ "jpegload_buffer", ImageType::JPEG },
|
||||
{ "pngload", ImageType::PNG },
|
||||
{ "pngload_buffer", ImageType::PNG },
|
||||
{ "webpload", ImageType::WEBP },
|
||||
{ "webpload_buffer", ImageType::WEBP },
|
||||
{ "tiffload", ImageType::TIFF },
|
||||
{ "tiffload_buffer", ImageType::TIFF },
|
||||
{ "gifload", ImageType::GIF },
|
||||
{ "gifload_buffer", ImageType::GIF },
|
||||
{ "svgload", ImageType::SVG },
|
||||
{ "svgload_buffer", ImageType::SVG },
|
||||
{ "heifload", ImageType::HEIF },
|
||||
{ "heifload_buffer", ImageType::HEIF },
|
||||
{ "pdfload", ImageType::PDF },
|
||||
{ "pdfload_buffer", ImageType::PDF },
|
||||
{ "magickload", ImageType::MAGICK },
|
||||
{ "magickload_buffer", ImageType::MAGICK },
|
||||
{ "openslideload", ImageType::OPENSLIDE },
|
||||
{ "ppmload", ImageType::PPM },
|
||||
{ "fitsload", ImageType::FITS },
|
||||
{ "openexrload", ImageType::EXR },
|
||||
{ "vipsload", ImageType::VIPS },
|
||||
{ "rawload", ImageType::RAW }
|
||||
};
|
||||
|
||||
/*
|
||||
Determine image format of a buffer.
|
||||
*/
|
||||
ImageType DetermineImageType(void *buffer, size_t const length) {
|
||||
ImageType imageType = ImageType::UNKNOWN;
|
||||
char const *load = vips_foreign_find_load_buffer(buffer, length);
|
||||
if (load != NULL) {
|
||||
std::string const loader = load;
|
||||
if (EndsWith(loader, "JpegBuffer")) {
|
||||
imageType = ImageType::JPEG;
|
||||
} else if (EndsWith(loader, "PngBuffer")) {
|
||||
imageType = ImageType::PNG;
|
||||
} else if (EndsWith(loader, "WebpBuffer")) {
|
||||
imageType = ImageType::WEBP;
|
||||
} else if (EndsWith(loader, "TiffBuffer")) {
|
||||
imageType = ImageType::TIFF;
|
||||
} else if (EndsWith(loader, "GifBuffer")) {
|
||||
imageType = ImageType::GIF;
|
||||
} else if (EndsWith(loader, "SvgBuffer")) {
|
||||
imageType = ImageType::SVG;
|
||||
} else if (EndsWith(loader, "PdfBuffer")) {
|
||||
imageType = ImageType::PDF;
|
||||
} else if (EndsWith(loader, "MagickBuffer")) {
|
||||
imageType = ImageType::MAGICK;
|
||||
if (load != nullptr) {
|
||||
auto it = loaderToType.find(vips_nickname_find(g_type_from_name(load)));
|
||||
if (it != loaderToType.end()) {
|
||||
imageType = it->second;
|
||||
}
|
||||
}
|
||||
return imageType;
|
||||
@@ -115,34 +238,144 @@ namespace sharp {
|
||||
ImageType imageType = ImageType::UNKNOWN;
|
||||
char const *load = vips_foreign_find_load(file);
|
||||
if (load != nullptr) {
|
||||
std::string const loader = load;
|
||||
if (EndsWith(loader, "JpegFile")) {
|
||||
imageType = ImageType::JPEG;
|
||||
} else if (EndsWith(loader, "Png")) {
|
||||
imageType = ImageType::PNG;
|
||||
} else if (EndsWith(loader, "WebpFile")) {
|
||||
imageType = ImageType::WEBP;
|
||||
} else if (EndsWith(loader, "Openslide")) {
|
||||
imageType = ImageType::OPENSLIDE;
|
||||
} else if (EndsWith(loader, "TiffFile")) {
|
||||
imageType = ImageType::TIFF;
|
||||
} else if (EndsWith(loader, "GifFile")) {
|
||||
imageType = ImageType::GIF;
|
||||
} else if (EndsWith(loader, "SvgFile")) {
|
||||
imageType = ImageType::SVG;
|
||||
} else if (EndsWith(loader, "PdfFile")) {
|
||||
imageType = ImageType::PDF;
|
||||
} else if (EndsWith(loader, "Ppm")) {
|
||||
imageType = ImageType::PPM;
|
||||
} else if (EndsWith(loader, "Fits")) {
|
||||
imageType = ImageType::FITS;
|
||||
} else if (EndsWith(loader, "Magick") || EndsWith(loader, "MagickFile")) {
|
||||
imageType = ImageType::MAGICK;
|
||||
auto it = loaderToType.find(vips_nickname_find(g_type_from_name(load)));
|
||||
if (it != loaderToType.end()) {
|
||||
imageType = it->second;
|
||||
}
|
||||
} else {
|
||||
if (EndsWith(vips::VError().what(), " does not exist\n")) {
|
||||
imageType = ImageType::MISSING;
|
||||
}
|
||||
}
|
||||
return imageType;
|
||||
}
|
||||
|
||||
/*
|
||||
Does this image type support multiple pages?
|
||||
*/
|
||||
bool ImageTypeSupportsPage(ImageType imageType) {
|
||||
return
|
||||
imageType == ImageType::WEBP ||
|
||||
imageType == ImageType::MAGICK ||
|
||||
imageType == ImageType::GIF ||
|
||||
imageType == ImageType::TIFF ||
|
||||
imageType == ImageType::HEIF ||
|
||||
imageType == ImageType::PDF;
|
||||
}
|
||||
|
||||
/*
|
||||
Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data)
|
||||
*/
|
||||
std::tuple<VImage, ImageType> OpenInput(InputDescriptor *descriptor) {
|
||||
VImage image;
|
||||
ImageType imageType;
|
||||
if (descriptor->isBuffer) {
|
||||
if (descriptor->rawChannels > 0) {
|
||||
// Raw, uncompressed pixel data
|
||||
image = VImage::new_from_memory(descriptor->buffer, descriptor->bufferLength,
|
||||
descriptor->rawWidth, descriptor->rawHeight, descriptor->rawChannels, VIPS_FORMAT_UCHAR);
|
||||
if (descriptor->rawChannels < 3) {
|
||||
image.get_image()->Type = VIPS_INTERPRETATION_B_W;
|
||||
} else {
|
||||
image.get_image()->Type = VIPS_INTERPRETATION_sRGB;
|
||||
}
|
||||
imageType = ImageType::RAW;
|
||||
} else {
|
||||
// Compressed data
|
||||
imageType = DetermineImageType(descriptor->buffer, descriptor->bufferLength);
|
||||
if (imageType != ImageType::UNKNOWN) {
|
||||
try {
|
||||
vips::VOption *option = VImage::option()
|
||||
->set("access", descriptor->access)
|
||||
->set("fail", descriptor->failOnError);
|
||||
if (imageType == ImageType::SVG) {
|
||||
option->set("unlimited", TRUE);
|
||||
}
|
||||
if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
|
||||
option->set("dpi", descriptor->density);
|
||||
}
|
||||
if (imageType == ImageType::MAGICK) {
|
||||
option->set("density", std::to_string(descriptor->density).data());
|
||||
}
|
||||
if (ImageTypeSupportsPage(imageType)) {
|
||||
option->set("n", descriptor->pages);
|
||||
option->set("page", descriptor->page);
|
||||
}
|
||||
if (imageType == ImageType::OPENSLIDE) {
|
||||
option->set("level", descriptor->level);
|
||||
}
|
||||
image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option);
|
||||
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
|
||||
image = SetDensity(image, descriptor->density);
|
||||
}
|
||||
} catch (vips::VError const &err) {
|
||||
throw vips::VError(std::string("Input buffer has corrupt header: ") + err.what());
|
||||
}
|
||||
} else {
|
||||
throw vips::VError("Input buffer contains unsupported image format");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (descriptor->createChannels > 0) {
|
||||
// Create new image
|
||||
std::vector<double> background = {
|
||||
descriptor->createBackground[0],
|
||||
descriptor->createBackground[1],
|
||||
descriptor->createBackground[2]
|
||||
};
|
||||
if (descriptor->createChannels == 4) {
|
||||
background.push_back(descriptor->createBackground[3]);
|
||||
}
|
||||
image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight).new_from_image(background);
|
||||
image.get_image()->Type = VIPS_INTERPRETATION_sRGB;
|
||||
imageType = ImageType::RAW;
|
||||
} else {
|
||||
// From filesystem
|
||||
imageType = DetermineImageType(descriptor->file.data());
|
||||
if (imageType == ImageType::MISSING) {
|
||||
throw vips::VError("Input file is missing");
|
||||
}
|
||||
if (imageType != ImageType::UNKNOWN) {
|
||||
try {
|
||||
vips::VOption *option = VImage::option()
|
||||
->set("access", descriptor->access)
|
||||
->set("fail", descriptor->failOnError);
|
||||
if (imageType == ImageType::SVG) {
|
||||
option->set("unlimited", TRUE);
|
||||
}
|
||||
if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
|
||||
option->set("dpi", descriptor->density);
|
||||
}
|
||||
if (imageType == ImageType::MAGICK) {
|
||||
option->set("density", std::to_string(descriptor->density).data());
|
||||
}
|
||||
if (ImageTypeSupportsPage(imageType)) {
|
||||
option->set("n", descriptor->pages);
|
||||
option->set("page", descriptor->page);
|
||||
}
|
||||
if (imageType == ImageType::OPENSLIDE) {
|
||||
option->set("level", descriptor->level);
|
||||
}
|
||||
image = VImage::new_from_file(descriptor->file.data(), option);
|
||||
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
|
||||
image = SetDensity(image, descriptor->density);
|
||||
}
|
||||
} catch (vips::VError const &err) {
|
||||
throw vips::VError(std::string("Input file has corrupt header: ") + err.what());
|
||||
}
|
||||
} else {
|
||||
throw vips::VError("Input file contains unsupported image format");
|
||||
}
|
||||
}
|
||||
}
|
||||
// Limit input images to a given number of pixels, where pixels = width * height
|
||||
if (descriptor->limitInputPixels > 0 &&
|
||||
static_cast<uint64_t>(image.width() * image.height()) > static_cast<uint64_t>(descriptor->limitInputPixels)) {
|
||||
throw vips::VError("Input image exceeds pixel limit");
|
||||
}
|
||||
return std::make_tuple(image, imageType);
|
||||
}
|
||||
|
||||
/*
|
||||
Does this image have an embedded profile?
|
||||
*/
|
||||
@@ -160,8 +393,7 @@ namespace sharp {
|
||||
return (
|
||||
(bands == 2 && interpretation == VIPS_INTERPRETATION_B_W) ||
|
||||
(bands == 4 && interpretation != VIPS_INTERPRETATION_CMYK) ||
|
||||
(bands == 5 && interpretation == VIPS_INTERPRETATION_CMYK)
|
||||
);
|
||||
(bands == 5 && interpretation == VIPS_INTERPRETATION_CMYK));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -169,11 +401,8 @@ namespace sharp {
|
||||
*/
|
||||
int ExifOrientation(VImage image) {
|
||||
int orientation = 0;
|
||||
if (image.get_typeof(EXIF_IFD0_ORIENTATION) != 0) {
|
||||
char const *exif = image.get_string(EXIF_IFD0_ORIENTATION);
|
||||
if (exif != nullptr) {
|
||||
orientation = atoi(&exif[0]);
|
||||
}
|
||||
if (image.get_typeof(VIPS_META_ORIENTATION) != 0) {
|
||||
orientation = image.get_int(VIPS_META_ORIENTATION);
|
||||
}
|
||||
return orientation;
|
||||
}
|
||||
@@ -181,17 +410,51 @@ namespace sharp {
|
||||
/*
|
||||
Set EXIF Orientation of image.
|
||||
*/
|
||||
void SetExifOrientation(VImage image, int const orientation) {
|
||||
char exif[3];
|
||||
g_snprintf(exif, sizeof(exif), "%d", orientation);
|
||||
image.set(EXIF_IFD0_ORIENTATION, exif);
|
||||
VImage SetExifOrientation(VImage image, int const orientation) {
|
||||
VImage copy = image.copy();
|
||||
copy.set(VIPS_META_ORIENTATION, orientation);
|
||||
return copy;
|
||||
}
|
||||
|
||||
/*
|
||||
Remove EXIF Orientation from image.
|
||||
*/
|
||||
void RemoveExifOrientation(VImage image) {
|
||||
SetExifOrientation(image, 0);
|
||||
VImage RemoveExifOrientation(VImage image) {
|
||||
VImage copy = image.copy();
|
||||
copy.remove(VIPS_META_ORIENTATION);
|
||||
return copy;
|
||||
}
|
||||
|
||||
/*
|
||||
Set animation properties if necessary.
|
||||
Non-provided properties will be loaded from image.
|
||||
*/
|
||||
VImage SetAnimationProperties(VImage image, int pageHeight, std::vector<int> delay, int loop) {
|
||||
bool hasDelay = delay.size() != 1 || delay.front() != -1;
|
||||
|
||||
if (pageHeight == 0 && image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT) {
|
||||
pageHeight = image.get_int(VIPS_META_PAGE_HEIGHT);
|
||||
}
|
||||
|
||||
if (!hasDelay && image.get_typeof("delay") == VIPS_TYPE_ARRAY_INT) {
|
||||
delay = image.get_array_int("delay");
|
||||
hasDelay = true;
|
||||
}
|
||||
|
||||
if (loop == -1 && image.get_typeof("loop") == G_TYPE_INT) {
|
||||
loop = image.get_int("loop");
|
||||
}
|
||||
|
||||
if (pageHeight == 0) return image;
|
||||
|
||||
// It is necessary to create the copy as otherwise, pageHeight will be ignored!
|
||||
VImage copy = image.copy();
|
||||
|
||||
copy.set(VIPS_META_PAGE_HEIGHT, pageHeight);
|
||||
if (hasDelay) copy.set("delay", delay);
|
||||
if (loop != -1) copy.set("loop", loop);
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -211,25 +474,129 @@ namespace sharp {
|
||||
/*
|
||||
Set pixels/mm resolution based on a pixels/inch density.
|
||||
*/
|
||||
void SetDensity(VImage image, const int density) {
|
||||
const double pixelsPerMm = static_cast<double>(density) / 25.4;
|
||||
image.set("Xres", pixelsPerMm);
|
||||
image.set("Yres", pixelsPerMm);
|
||||
image.set(VIPS_META_RESOLUTION_UNIT, "in");
|
||||
VImage SetDensity(VImage image, const double density) {
|
||||
const double pixelsPerMm = density / 25.4;
|
||||
VImage copy = image.copy();
|
||||
copy.set("Xres", pixelsPerMm);
|
||||
copy.set("Yres", pixelsPerMm);
|
||||
copy.set(VIPS_META_RESOLUTION_UNIT, "in");
|
||||
return copy;
|
||||
}
|
||||
|
||||
/*
|
||||
Check the proposed format supports the current dimensions.
|
||||
*/
|
||||
void AssertImageTypeDimensions(VImage image, ImageType const imageType) {
|
||||
const int height = image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT
|
||||
? image.get_int(VIPS_META_PAGE_HEIGHT)
|
||||
: image.height();
|
||||
if (imageType == ImageType::JPEG) {
|
||||
if (image.width() > 65535 || height > 65535) {
|
||||
throw vips::VError("Processed image is too large for the JPEG format");
|
||||
}
|
||||
} else if (imageType == ImageType::WEBP) {
|
||||
if (image.width() > 16383 || height > 16383) {
|
||||
throw vips::VError("Processed image is too large for the WebP format");
|
||||
}
|
||||
} else if (imageType == ImageType::GIF) {
|
||||
if (image.width() > 65535 || height > 65535) {
|
||||
throw vips::VError("Processed image is too large for the GIF format");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows
|
||||
*/
|
||||
void FreeCallback(char* data, void* hint) {
|
||||
if (data != nullptr) {
|
||||
g_free(data);
|
||||
std::function<void(void*, char*)> FreeCallback = [](void*, char* data) {
|
||||
g_free(data);
|
||||
};
|
||||
|
||||
/*
|
||||
Temporary buffer of warnings
|
||||
*/
|
||||
std::queue<std::string> vipsWarnings;
|
||||
std::mutex vipsWarningsMutex;
|
||||
|
||||
/*
|
||||
Called with warnings from the glib-registered "VIPS" domain
|
||||
*/
|
||||
void VipsWarningCallback(char const* log_domain, GLogLevelFlags log_level, char const* message, void* ignore) {
|
||||
std::lock_guard<std::mutex> lock(vipsWarningsMutex);
|
||||
vipsWarnings.emplace(message);
|
||||
}
|
||||
|
||||
/*
|
||||
Pop the oldest warning message from the queue
|
||||
*/
|
||||
std::string VipsWarningPop() {
|
||||
std::string warning;
|
||||
std::lock_guard<std::mutex> lock(vipsWarningsMutex);
|
||||
if (!vipsWarnings.empty()) {
|
||||
warning = vipsWarnings.front();
|
||||
vipsWarnings.pop();
|
||||
}
|
||||
return warning;
|
||||
}
|
||||
|
||||
/*
|
||||
Calculate the (left, top) coordinates of the output image
|
||||
within the input image, applying the given gravity.
|
||||
within the input image, applying the given gravity during an embed.
|
||||
|
||||
@Azurebyte: We are basically swapping the inWidth and outWidth, inHeight and outHeight from the CalculateCrop function.
|
||||
*/
|
||||
std::tuple<int, int> CalculateEmbedPosition(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 = (outWidth - inWidth) / 2;
|
||||
break;
|
||||
case 2:
|
||||
// East
|
||||
left = outWidth - inWidth;
|
||||
top = (outHeight - inHeight) / 2;
|
||||
break;
|
||||
case 3:
|
||||
// South
|
||||
left = (outWidth - inWidth) / 2;
|
||||
top = outHeight - inHeight;
|
||||
break;
|
||||
case 4:
|
||||
// West
|
||||
top = (outHeight - inHeight) / 2;
|
||||
break;
|
||||
case 5:
|
||||
// Northeast
|
||||
left = outWidth - inWidth;
|
||||
break;
|
||||
case 6:
|
||||
// Southeast
|
||||
left = outWidth - inWidth;
|
||||
top = outHeight - inHeight;
|
||||
break;
|
||||
case 7:
|
||||
// Southwest
|
||||
top = outHeight - inHeight;
|
||||
break;
|
||||
case 8:
|
||||
// Northwest
|
||||
// Which is the default is 0,0 so we do not assign anything here.
|
||||
break;
|
||||
default:
|
||||
// Centre
|
||||
left = (outWidth - inWidth) / 2;
|
||||
top = (outHeight - inHeight) / 2;
|
||||
}
|
||||
return std::make_tuple(left, top);
|
||||
}
|
||||
|
||||
/*
|
||||
Calculate the (left, top) coordinates of the output image
|
||||
within the input image, applying the given gravity during a crop.
|
||||
*/
|
||||
std::tuple<int, int> CalculateCrop(int const inWidth, int const inHeight,
|
||||
int const outWidth, int const outHeight, int const gravity) {
|
||||
@@ -263,9 +630,11 @@ namespace sharp {
|
||||
// Southeast
|
||||
left = inWidth - outWidth;
|
||||
top = inHeight - outHeight;
|
||||
break;
|
||||
case 7:
|
||||
// Southwest
|
||||
top = inHeight - outHeight;
|
||||
break;
|
||||
case 8:
|
||||
// Northwest
|
||||
break;
|
||||
@@ -277,4 +646,135 @@ namespace sharp {
|
||||
return std::make_tuple(left, top);
|
||||
}
|
||||
|
||||
/*
|
||||
Calculate the (left, top) coordinates of the output image
|
||||
within the input image, applying the given x and y offsets.
|
||||
*/
|
||||
std::tuple<int, int> CalculateCrop(int const inWidth, int const inHeight,
|
||||
int const outWidth, int const outHeight, int const x, int const y) {
|
||||
|
||||
// default values
|
||||
int left = 0;
|
||||
int top = 0;
|
||||
|
||||
// assign only if valid
|
||||
if (x < (inWidth - outWidth)) {
|
||||
left = x;
|
||||
} else if (x >= (inWidth - outWidth)) {
|
||||
left = inWidth - outWidth;
|
||||
}
|
||||
|
||||
if (y < (inHeight - outHeight)) {
|
||||
top = y;
|
||||
} else if (y >= (inHeight - outHeight)) {
|
||||
top = inHeight - outHeight;
|
||||
}
|
||||
|
||||
return std::make_tuple(left, top);
|
||||
}
|
||||
|
||||
/*
|
||||
Are pixel values in this image 16-bit integer?
|
||||
*/
|
||||
bool Is16Bit(VipsInterpretation const interpretation) {
|
||||
return interpretation == VIPS_INTERPRETATION_RGB16 || interpretation == VIPS_INTERPRETATION_GREY16;
|
||||
}
|
||||
|
||||
/*
|
||||
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) {
|
||||
return Is16Bit(interpretation) ? 65535.0 : 255.0;
|
||||
}
|
||||
|
||||
/*
|
||||
Get boolean operation type from string
|
||||
*/
|
||||
VipsOperationBoolean GetBooleanOperation(std::string const opStr) {
|
||||
return static_cast<VipsOperationBoolean>(
|
||||
vips_enum_from_nick(nullptr, VIPS_TYPE_OPERATION_BOOLEAN, opStr.data()));
|
||||
}
|
||||
|
||||
/*
|
||||
Get interpretation type from string
|
||||
*/
|
||||
VipsInterpretation GetInterpretation(std::string const typeStr) {
|
||||
return static_cast<VipsInterpretation>(
|
||||
vips_enum_from_nick(nullptr, VIPS_TYPE_INTERPRETATION, typeStr.data()));
|
||||
}
|
||||
|
||||
/*
|
||||
Convert RGBA value to another colourspace
|
||||
*/
|
||||
std::vector<double> GetRgbaAsColourspace(std::vector<double> const rgba, VipsInterpretation const interpretation) {
|
||||
int const bands = static_cast<int>(rgba.size());
|
||||
if (bands < 3 || interpretation == VIPS_INTERPRETATION_sRGB || interpretation == VIPS_INTERPRETATION_RGB) {
|
||||
return rgba;
|
||||
} else {
|
||||
VImage pixel = VImage::new_matrix(1, 1);
|
||||
pixel.set("bands", bands);
|
||||
pixel = pixel.new_from_image(rgba);
|
||||
pixel = pixel.colourspace(interpretation, VImage::option()->set("source_space", VIPS_INTERPRETATION_sRGB));
|
||||
return pixel(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Apply the alpha channel to a given colour
|
||||
*/
|
||||
std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour) {
|
||||
// Scale up 8-bit values to match 16-bit input image
|
||||
double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
|
||||
// Create alphaColour colour
|
||||
std::vector<double> alphaColour;
|
||||
if (image.bands() > 2) {
|
||||
alphaColour = {
|
||||
multiplier * colour[0],
|
||||
multiplier * colour[1],
|
||||
multiplier * colour[2]
|
||||
};
|
||||
} else {
|
||||
// Convert sRGB to greyscale
|
||||
alphaColour = { multiplier * (
|
||||
0.2126 * colour[0] +
|
||||
0.7152 * colour[1] +
|
||||
0.0722 * colour[2])
|
||||
};
|
||||
}
|
||||
// Add alpha channel to alphaColour colour
|
||||
if (colour[3] < 255.0 || HasAlpha(image)) {
|
||||
alphaColour.push_back(colour[3] * multiplier);
|
||||
}
|
||||
// Ensure alphaColour colour uses correct colourspace
|
||||
alphaColour = sharp::GetRgbaAsColourspace(alphaColour, image.interpretation());
|
||||
// Add non-transparent alpha channel, if required
|
||||
if (colour[3] < 255.0 && !HasAlpha(image)) {
|
||||
image = image.bandjoin(
|
||||
VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier));
|
||||
}
|
||||
return std::make_tuple(image, alphaColour);
|
||||
}
|
||||
|
||||
/*
|
||||
Removes alpha channel, if any.
|
||||
*/
|
||||
VImage RemoveAlpha(VImage image) {
|
||||
if (HasAlpha(image)) {
|
||||
image = image.extract_band(0, VImage::option()->set("n", image.bands() - 1));
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
/*
|
||||
Ensures alpha channel, if missing.
|
||||
*/
|
||||
VImage EnsureAlpha(VImage image) {
|
||||
if (!HasAlpha(image)) {
|
||||
std::vector<double> alpha;
|
||||
alpha.push_back(sharp::MaximumImageAlpha(image.interpretation()));
|
||||
image = image.bandjoin_const(alpha);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
} // namespace sharp
|
||||
|
||||
195
src/common.h
195
src/common.h
@@ -1,15 +1,105 @@
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// http://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.
|
||||
|
||||
#ifndef SRC_COMMON_H_
|
||||
#define SRC_COMMON_H_
|
||||
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include <napi.h>
|
||||
#include <vips/vips8>
|
||||
|
||||
// Verify platform and compiler compatibility
|
||||
|
||||
#if (VIPS_MAJOR_VERSION < 8) || \
|
||||
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 10) || \
|
||||
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 10 && VIPS_MICRO_VERSION < 5)
|
||||
#error "libvips version 8.10.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 {
|
||||
|
||||
struct InputDescriptor { // NOLINT(runtime/indentation_namespace)
|
||||
std::string name;
|
||||
std::string file;
|
||||
char *buffer;
|
||||
bool failOnError;
|
||||
int limitInputPixels;
|
||||
VipsAccess access;
|
||||
size_t bufferLength;
|
||||
bool isBuffer;
|
||||
double density;
|
||||
int rawChannels;
|
||||
int rawWidth;
|
||||
int rawHeight;
|
||||
int pages;
|
||||
int page;
|
||||
int level;
|
||||
int createChannels;
|
||||
int createWidth;
|
||||
int createHeight;
|
||||
std::vector<double> createBackground;
|
||||
|
||||
InputDescriptor():
|
||||
buffer(nullptr),
|
||||
failOnError(TRUE),
|
||||
limitInputPixels(0x3FFF * 0x3FFF),
|
||||
access(VIPS_ACCESS_RANDOM),
|
||||
bufferLength(0),
|
||||
isBuffer(FALSE),
|
||||
density(72.0),
|
||||
rawChannels(0),
|
||||
rawWidth(0),
|
||||
rawHeight(0),
|
||||
pages(1),
|
||||
page(0),
|
||||
level(0),
|
||||
createChannels(0),
|
||||
createWidth(0),
|
||||
createHeight(0),
|
||||
createBackground{ 0.0, 0.0, 0.0, 255.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);
|
||||
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);
|
||||
|
||||
// Create an InputDescriptor instance from a Napi::Object describing an input image
|
||||
InputDescriptor* CreateInputDescriptor(Napi::Object input);
|
||||
|
||||
enum class ImageType {
|
||||
JPEG,
|
||||
PNG,
|
||||
@@ -17,13 +107,17 @@ namespace sharp {
|
||||
TIFF,
|
||||
GIF,
|
||||
SVG,
|
||||
HEIF,
|
||||
PDF,
|
||||
MAGICK,
|
||||
OPENSLIDE,
|
||||
PPM,
|
||||
FITS,
|
||||
EXR,
|
||||
VIPS,
|
||||
RAW,
|
||||
UNKNOWN
|
||||
UNKNOWN,
|
||||
MISSING
|
||||
};
|
||||
|
||||
// How many tasks are in the queue?
|
||||
@@ -36,9 +130,14 @@ namespace sharp {
|
||||
bool IsJpeg(std::string const &str);
|
||||
bool IsPng(std::string const &str);
|
||||
bool IsWebp(std::string const &str);
|
||||
bool IsGif(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 IsDz(std::string const &str);
|
||||
bool IsDzZip(std::string const &str);
|
||||
bool IsV(std::string const &str);
|
||||
|
||||
/*
|
||||
Provide a string identifier for the given image type.
|
||||
@@ -55,6 +154,16 @@ namespace sharp {
|
||||
*/
|
||||
ImageType DetermineImageType(char const *file);
|
||||
|
||||
/*
|
||||
Does this image type support multiple pages?
|
||||
*/
|
||||
bool ImageTypeSupportsPage(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?
|
||||
*/
|
||||
@@ -74,12 +183,18 @@ namespace sharp {
|
||||
/*
|
||||
Set EXIF Orientation of image.
|
||||
*/
|
||||
void SetExifOrientation(VImage image, int const orientation);
|
||||
VImage SetExifOrientation(VImage image, int const orientation);
|
||||
|
||||
/*
|
||||
Remove EXIF Orientation from image.
|
||||
*/
|
||||
void RemoveExifOrientation(VImage image);
|
||||
VImage RemoveExifOrientation(VImage image);
|
||||
|
||||
/*
|
||||
Set animation properties if necessary.
|
||||
Non-provided properties will be loaded from image.
|
||||
*/
|
||||
VImage SetAnimationProperties(VImage image, int pageHeight, std::vector<int> delay, int loop);
|
||||
|
||||
/*
|
||||
Does this image have a non-default density?
|
||||
@@ -94,12 +209,34 @@ namespace sharp {
|
||||
/*
|
||||
Set pixels/mm resolution based on a pixels/inch density.
|
||||
*/
|
||||
void SetDensity(VImage image, const int density);
|
||||
VImage SetDensity(VImage image, const double density);
|
||||
|
||||
/*
|
||||
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
|
||||
*/
|
||||
void FreeCallback(char* data, void* hint);
|
||||
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();
|
||||
|
||||
/*
|
||||
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
|
||||
@@ -108,6 +245,54 @@ namespace sharp {
|
||||
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);
|
||||
|
||||
/*
|
||||
Get boolean operation type from string
|
||||
*/
|
||||
VipsOperationBoolean GetBooleanOperation(std::string const opStr);
|
||||
|
||||
/*
|
||||
Get interpretation type from string
|
||||
*/
|
||||
VipsInterpretation GetInterpretation(std::string const typeStr);
|
||||
|
||||
/*
|
||||
Convert RGBA value to another colourspace
|
||||
*/
|
||||
std::vector<double> GetRgbaAsColourspace(std::vector<double> const rgba, VipsInterpretation const interpretation);
|
||||
|
||||
/*
|
||||
Apply the alpha channel to a given colour
|
||||
*/
|
||||
std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour);
|
||||
|
||||
/*
|
||||
Removes alpha channel, if any.
|
||||
*/
|
||||
VImage RemoveAlpha(VImage image);
|
||||
|
||||
/*
|
||||
Ensures alpha channel, if missing.
|
||||
*/
|
||||
VImage EnsureAlpha(VImage image);
|
||||
|
||||
} // namespace sharp
|
||||
|
||||
#endif // SRC_COMMON_H_
|
||||
|
||||
178
src/libvips/cplusplus/VConnection.cpp
Normal file
178
src/libvips/cplusplus/VConnection.cpp
Normal file
@@ -0,0 +1,178 @@
|
||||
/* Object part of the VSource and VTarget class
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
Copyright (C) 1991-2001 The National Gallery
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
02110-1301 USA
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
|
||||
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
#include <vips/intl.h>
|
||||
|
||||
#include <vips/vips8>
|
||||
|
||||
#include <vips/debug.h>
|
||||
|
||||
/*
|
||||
#define VIPS_DEBUG
|
||||
#define VIPS_DEBUG_VERBOSE
|
||||
*/
|
||||
|
||||
VIPS_NAMESPACE_START
|
||||
|
||||
VSource
|
||||
VSource::new_from_descriptor( int descriptor )
|
||||
{
|
||||
VipsSource *input;
|
||||
|
||||
if( !(input = vips_source_new_from_descriptor( descriptor )) )
|
||||
throw VError();
|
||||
|
||||
VSource out( input );
|
||||
|
||||
return( out );
|
||||
}
|
||||
|
||||
VSource
|
||||
VSource::new_from_file( const char *filename )
|
||||
{
|
||||
VipsSource *input;
|
||||
|
||||
if( !(input = vips_source_new_from_file( filename )) )
|
||||
throw VError();
|
||||
|
||||
VSource out( input );
|
||||
|
||||
return( out );
|
||||
}
|
||||
|
||||
VSource
|
||||
VSource::new_from_blob( VipsBlob *blob )
|
||||
{
|
||||
VipsSource *input;
|
||||
|
||||
if( !(input = vips_source_new_from_blob( blob )) )
|
||||
throw VError();
|
||||
|
||||
VSource out( input );
|
||||
|
||||
return( out );
|
||||
}
|
||||
|
||||
VSource
|
||||
VSource::new_from_memory( const void *data,
|
||||
size_t size )
|
||||
{
|
||||
VipsSource *input;
|
||||
|
||||
if( !(input = vips_source_new_from_memory( data, size )) )
|
||||
throw VError();
|
||||
|
||||
VSource out( input );
|
||||
|
||||
return( out );
|
||||
}
|
||||
|
||||
VSource
|
||||
VSource::new_from_options( const char *options )
|
||||
{
|
||||
VipsSource *input;
|
||||
|
||||
if( !(input = vips_source_new_from_options( options )) )
|
||||
throw VError();
|
||||
|
||||
VSource out( input );
|
||||
|
||||
return( out );
|
||||
}
|
||||
|
||||
VOption *
|
||||
VOption::set( const char *name, const VSource value )
|
||||
{
|
||||
Pair *pair = new Pair( name );
|
||||
|
||||
pair->input = true;
|
||||
g_value_init( &pair->value, VIPS_TYPE_SOURCE );
|
||||
g_value_set_object( &pair->value, value.get_source() );
|
||||
options.push_back( pair );
|
||||
|
||||
return( this );
|
||||
}
|
||||
|
||||
VTarget
|
||||
VTarget::new_to_descriptor( int descriptor )
|
||||
{
|
||||
VipsTarget *output;
|
||||
|
||||
if( !(output = vips_target_new_to_descriptor( descriptor )) )
|
||||
throw VError();
|
||||
|
||||
VTarget out( output );
|
||||
|
||||
return( out );
|
||||
}
|
||||
|
||||
VTarget
|
||||
VTarget::new_to_file( const char *filename )
|
||||
{
|
||||
VipsTarget *output;
|
||||
|
||||
if( !(output = vips_target_new_to_file( filename )) )
|
||||
throw VError();
|
||||
|
||||
VTarget out( output );
|
||||
|
||||
return( out );
|
||||
}
|
||||
|
||||
VTarget
|
||||
VTarget::new_to_memory()
|
||||
{
|
||||
VipsTarget *output;
|
||||
|
||||
if( !(output = vips_target_new_to_memory()) )
|
||||
throw VError();
|
||||
|
||||
VTarget out( output );
|
||||
|
||||
return( out );
|
||||
}
|
||||
|
||||
VOption *
|
||||
VOption::set( const char *name, const VTarget value )
|
||||
{
|
||||
Pair *pair = new Pair( name );
|
||||
|
||||
pair->input = true;
|
||||
g_value_init( &pair->value, VIPS_TYPE_TARGET );
|
||||
g_value_set_object( &pair->value, value.get_target() );
|
||||
options.push_back( pair );
|
||||
|
||||
return( this );
|
||||
}
|
||||
|
||||
VIPS_NAMESPACE_END
|
||||
@@ -32,8 +32,6 @@
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
#include <vips/intl.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <vips/vips8>
|
||||
|
||||
VIPS_NAMESPACE_START
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -61,7 +61,7 @@ VInterpolate::new_from_name( const char *name, VOption *options )
|
||||
}
|
||||
|
||||
VOption *
|
||||
VOption::set( const char *name, VInterpolate value )
|
||||
VOption::set( const char *name, const VInterpolate value )
|
||||
{
|
||||
Pair *pair = new Pair( name );
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
352
src/metadata.cc
352
src/metadata.cc
@@ -1,135 +1,92 @@
|
||||
#include <node.h>
|
||||
#include <vips/vips8>
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// http://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.
|
||||
|
||||
#include "nan.h"
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
|
||||
#include <napi.h>
|
||||
#include <vips/vips8>
|
||||
|
||||
#include "common.h"
|
||||
#include "metadata.h"
|
||||
|
||||
using v8::Handle;
|
||||
using v8::Local;
|
||||
using v8::Value;
|
||||
using v8::Object;
|
||||
using v8::Number;
|
||||
using v8::String;
|
||||
using v8::Boolean;
|
||||
using v8::Function;
|
||||
using v8::Exception;
|
||||
|
||||
using Nan::AsyncQueueWorker;
|
||||
using Nan::AsyncWorker;
|
||||
using Nan::Callback;
|
||||
using Nan::HandleScope;
|
||||
using Nan::Utf8String;
|
||||
using Nan::Has;
|
||||
using Nan::Get;
|
||||
using Nan::Set;
|
||||
using Nan::New;
|
||||
using Nan::NewBuffer;
|
||||
using Nan::Null;
|
||||
using Nan::Error;
|
||||
|
||||
using vips::VImage;
|
||||
using vips::VError;
|
||||
|
||||
using sharp::ImageType;
|
||||
using sharp::ImageTypeId;
|
||||
using sharp::DetermineImageType;
|
||||
using sharp::HasProfile;
|
||||
using sharp::HasAlpha;
|
||||
using sharp::ExifOrientation;
|
||||
using sharp::HasDensity;
|
||||
using sharp::GetDensity;
|
||||
using sharp::FreeCallback;
|
||||
using sharp::counterQueue;
|
||||
|
||||
struct MetadataBaton {
|
||||
// Input
|
||||
std::string fileIn;
|
||||
char *bufferIn;
|
||||
size_t bufferInLength;
|
||||
// Output
|
||||
std::string format;
|
||||
int width;
|
||||
int height;
|
||||
std::string space;
|
||||
int channels;
|
||||
int density;
|
||||
bool hasProfile;
|
||||
bool hasAlpha;
|
||||
int orientation;
|
||||
char *exif;
|
||||
size_t exifLength;
|
||||
char *icc;
|
||||
size_t iccLength;
|
||||
std::string err;
|
||||
|
||||
MetadataBaton():
|
||||
bufferInLength(0),
|
||||
density(0),
|
||||
orientation(0),
|
||||
exifLength(0),
|
||||
iccLength(0) {}
|
||||
};
|
||||
|
||||
class MetadataWorker : public AsyncWorker {
|
||||
class MetadataWorker : public Napi::AsyncWorker {
|
||||
public:
|
||||
MetadataWorker(Callback *callback, MetadataBaton *baton, const Local<Object> &bufferIn) :
|
||||
AsyncWorker(callback), baton(baton) {
|
||||
if (baton->bufferInLength > 0) {
|
||||
SaveToPersistent("bufferIn", bufferIn);
|
||||
}
|
||||
}
|
||||
MetadataWorker(Napi::Function callback, MetadataBaton *baton, Napi::Function debuglog) :
|
||||
Napi::AsyncWorker(callback), baton(baton), debuglog(Napi::Persistent(debuglog)) {}
|
||||
~MetadataWorker() {}
|
||||
|
||||
void Execute() {
|
||||
// Decrement queued task counter
|
||||
g_atomic_int_dec_and_test(&counterQueue);
|
||||
g_atomic_int_dec_and_test(&sharp::counterQueue);
|
||||
|
||||
ImageType imageType = ImageType::UNKNOWN;
|
||||
VImage image;
|
||||
if (baton->bufferInLength > 0) {
|
||||
// From buffer
|
||||
imageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
|
||||
if (imageType != ImageType::UNKNOWN) {
|
||||
try {
|
||||
image = VImage::new_from_buffer(baton->bufferIn, baton->bufferInLength, nullptr);
|
||||
} catch (...) {
|
||||
(baton->err).append("Input buffer has corrupt header");
|
||||
imageType = ImageType::UNKNOWN;
|
||||
}
|
||||
} else {
|
||||
(baton->err).append("Input buffer contains unsupported image format");
|
||||
}
|
||||
} else {
|
||||
// From file
|
||||
imageType = DetermineImageType(baton->fileIn.data());
|
||||
if (imageType != ImageType::UNKNOWN) {
|
||||
try {
|
||||
image = VImage::new_from_file(baton->fileIn.data());
|
||||
} catch (...) {
|
||||
(baton->err).append("Input file has corrupt header");
|
||||
imageType = ImageType::UNKNOWN;
|
||||
}
|
||||
} else {
|
||||
(baton->err).append("Input file is missing or of an unsupported image format");
|
||||
}
|
||||
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 != ImageType::UNKNOWN) {
|
||||
if (imageType != sharp::ImageType::UNKNOWN) {
|
||||
// Image type
|
||||
baton->format = ImageTypeId(imageType);
|
||||
baton->format = sharp::ImageTypeId(imageType);
|
||||
// VipsImage attributes
|
||||
baton->width = image.width();
|
||||
baton->height = image.height();
|
||||
baton->space = vips_enum_nick(VIPS_TYPE_INTERPRETATION, image.interpretation());
|
||||
baton->channels = image.bands();
|
||||
if (HasDensity(image)) {
|
||||
baton->density = GetDensity(image);
|
||||
baton->depth = vips_enum_nick(VIPS_TYPE_BAND_FORMAT, image.format());
|
||||
if (sharp::HasDensity(image)) {
|
||||
baton->density = sharp::GetDensity(image);
|
||||
}
|
||||
baton->hasProfile = HasProfile(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("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));
|
||||
}
|
||||
}
|
||||
baton->hasProfile = sharp::HasProfile(image);
|
||||
// Derived attributes
|
||||
baton->hasAlpha = HasAlpha(image);
|
||||
baton->orientation = ExifOrientation(image);
|
||||
baton->hasAlpha = sharp::HasAlpha(image);
|
||||
baton->orientation = sharp::ExifOrientation(image);
|
||||
// EXIF
|
||||
if (image.get_typeof(VIPS_META_EXIF_NAME) == VIPS_TYPE_BLOB) {
|
||||
size_t exifLength;
|
||||
@@ -146,88 +103,157 @@ class MetadataWorker : public AsyncWorker {
|
||||
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
|
||||
vips_error_clear();
|
||||
vips_thread_shutdown();
|
||||
}
|
||||
|
||||
void HandleOKCallback () {
|
||||
HandleScope();
|
||||
void OnOK() {
|
||||
Napi::Env env = Env();
|
||||
Napi::HandleScope scope(env);
|
||||
|
||||
Local<Value> argv[2] = { Null(), Null() };
|
||||
if (!baton->err.empty()) {
|
||||
// Error
|
||||
argv[0] = Error(baton->err.data());
|
||||
} else {
|
||||
// Metadata Object
|
||||
Local<Object> info = New<Object>();
|
||||
Set(info, New("format").ToLocalChecked(), New<String>(baton->format).ToLocalChecked());
|
||||
Set(info, New("width").ToLocalChecked(), New<Number>(baton->width));
|
||||
Set(info, New("height").ToLocalChecked(), New<Number>(baton->height));
|
||||
Set(info, New("space").ToLocalChecked(), New<String>(baton->space).ToLocalChecked());
|
||||
Set(info, New("channels").ToLocalChecked(), New<Number>(baton->channels));
|
||||
if (baton->density > 0) {
|
||||
Set(info, New("density").ToLocalChecked(), New<Number>(baton->density));
|
||||
// Handle warnings
|
||||
std::string warning = sharp::VipsWarningPop();
|
||||
while (!warning.empty()) {
|
||||
debuglog.Call({ Napi::String::New(env, warning) });
|
||||
warning = sharp::VipsWarningPop();
|
||||
}
|
||||
|
||||
if (baton->err.empty()) {
|
||||
Napi::Object info = Napi::Object::New(env);
|
||||
info.Set("format", baton->format);
|
||||
if (baton->input->bufferLength > 0) {
|
||||
info.Set("size", baton->input->bufferLength);
|
||||
}
|
||||
Set(info, New("hasProfile").ToLocalChecked(), New<Boolean>(baton->hasProfile));
|
||||
Set(info, New("hasAlpha").ToLocalChecked(), New<Boolean>(baton->hasAlpha));
|
||||
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->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);
|
||||
}
|
||||
info.Set("hasProfile", baton->hasProfile);
|
||||
info.Set("hasAlpha", baton->hasAlpha);
|
||||
if (baton->orientation > 0) {
|
||||
Set(info, New("orientation").ToLocalChecked(), New<Number>(baton->orientation));
|
||||
info.Set("orientation", baton->orientation);
|
||||
}
|
||||
if (baton->exifLength > 0) {
|
||||
Set(info,
|
||||
New("exif").ToLocalChecked(),
|
||||
NewBuffer(baton->exif, baton->exifLength, FreeCallback, nullptr).ToLocalChecked()
|
||||
);
|
||||
info.Set("exif", Napi::Buffer<char>::New(env, baton->exif, baton->exifLength, sharp::FreeCallback));
|
||||
}
|
||||
if (baton->iccLength > 0) {
|
||||
Set(info,
|
||||
New("icc").ToLocalChecked(),
|
||||
NewBuffer(baton->icc, baton->iccLength, FreeCallback, nullptr).ToLocalChecked()
|
||||
);
|
||||
info.Set("icc", Napi::Buffer<char>::New(env, baton->icc, baton->iccLength, sharp::FreeCallback));
|
||||
}
|
||||
argv[1] = info;
|
||||
if (baton->iptcLength > 0) {
|
||||
info.Set("iptc", Napi::Buffer<char>::New(env, baton->iptc, baton->iptcLength, sharp::FreeCallback));
|
||||
}
|
||||
if (baton->xmpLength > 0) {
|
||||
info.Set("xmp", Napi::Buffer<char>::New(env, baton->xmp, baton->xmpLength, sharp::FreeCallback));
|
||||
}
|
||||
if (baton->tifftagPhotoshopLength > 0) {
|
||||
info.Set("tifftagPhotoshop",
|
||||
Napi::Buffer<char>::New(env, baton->tifftagPhotoshop, baton->tifftagPhotoshopLength, sharp::FreeCallback));
|
||||
}
|
||||
Callback().MakeCallback(Receiver().Value(), { env.Null(), info });
|
||||
} else {
|
||||
Callback().MakeCallback(Receiver().Value(), { Napi::Error::New(env, baton->err).Value() });
|
||||
}
|
||||
|
||||
// Dispose of Persistent wrapper around input Buffer so it can be garbage collected
|
||||
if (baton->bufferInLength > 0) {
|
||||
GetFromPersistent("bufferIn");
|
||||
}
|
||||
delete baton->input;
|
||||
delete baton;
|
||||
|
||||
// Return to JavaScript
|
||||
callback->Call(2, argv);
|
||||
}
|
||||
|
||||
private:
|
||||
MetadataBaton* baton;
|
||||
Napi::FunctionReference debuglog;
|
||||
};
|
||||
|
||||
/*
|
||||
metadata(options, callback)
|
||||
*/
|
||||
NAN_METHOD(metadata) {
|
||||
HandleScope();
|
||||
|
||||
Napi::Value metadata(const Napi::CallbackInfo& info) {
|
||||
// V8 objects are converted to non-V8 types held in the baton struct
|
||||
MetadataBaton *baton = new MetadataBaton;
|
||||
Local<Object> options = info[0].As<Object>();
|
||||
Napi::Object options = info[0].As<Napi::Object>();
|
||||
|
||||
// Input filename
|
||||
baton->fileIn = *Utf8String(Get(options, New("fileIn").ToLocalChecked()).ToLocalChecked());
|
||||
// Input Buffer object
|
||||
Local<Object> bufferIn;
|
||||
if (node::Buffer::HasInstance(Get(options, New("bufferIn").ToLocalChecked()).ToLocalChecked())) {
|
||||
bufferIn = Get(options, New("bufferIn").ToLocalChecked()).ToLocalChecked().As<Object>();
|
||||
baton->bufferInLength = node::Buffer::Length(bufferIn);
|
||||
baton->bufferIn = node::Buffer::Data(bufferIn);
|
||||
}
|
||||
// Input
|
||||
baton->input = sharp::CreateInputDescriptor(options.Get("input").As<Napi::Object>());
|
||||
|
||||
// Function to notify of libvips warnings
|
||||
Napi::Function debuglog = options.Get("debuglog").As<Napi::Function>();
|
||||
|
||||
// Join queue for worker thread
|
||||
Callback *callback = new Callback(info[1].As<Function>());
|
||||
AsyncQueueWorker(new MetadataWorker(callback, baton, bufferIn));
|
||||
Napi::Function callback = info[1].As<Napi::Function>();
|
||||
MetadataWorker *worker = new MetadataWorker(callback, baton, debuglog);
|
||||
worker->Receiver().Set("options", options);
|
||||
worker->Queue();
|
||||
|
||||
// Increment queued task counter
|
||||
g_atomic_int_inc(&counterQueue);
|
||||
g_atomic_int_inc(&sharp::counterQueue);
|
||||
|
||||
return info.Env().Undefined();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,87 @@
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// http://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.
|
||||
|
||||
#ifndef SRC_METADATA_H_
|
||||
#define SRC_METADATA_H_
|
||||
|
||||
#include "nan.h"
|
||||
#include <string>
|
||||
#include <napi.h>
|
||||
|
||||
NAN_METHOD(metadata);
|
||||
#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::vector<std::pair<int, int>> levels;
|
||||
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),
|
||||
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_
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// http://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.
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include <vips/vips8>
|
||||
|
||||
#include "common.h"
|
||||
@@ -9,82 +27,37 @@ using vips::VImage;
|
||||
using vips::VError;
|
||||
|
||||
namespace sharp {
|
||||
|
||||
/*
|
||||
Alpha composite src over dst with given gravity.
|
||||
Assumes alpha channels are already premultiplied and will be unpremultiplied after.
|
||||
* Tint an image using the specified chroma, preserving the original image luminance
|
||||
*/
|
||||
VImage Composite(VImage src, VImage dst, const int gravity) {
|
||||
using sharp::CalculateCrop;
|
||||
using sharp::HasAlpha;
|
||||
|
||||
if (!HasAlpha(src)) {
|
||||
throw VError("Overlay image must have an alpha channel");
|
||||
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;
|
||||
}
|
||||
if (!HasAlpha(dst)) {
|
||||
throw VError("Image to be overlaid must have an alpha channel");
|
||||
// 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);
|
||||
}
|
||||
if (src.width() > dst.width() || src.height() > dst.height()) {
|
||||
throw VError("Overlay image must have same dimensions or smaller");
|
||||
}
|
||||
|
||||
// Enlarge overlay src, if required
|
||||
if (src.width() < dst.width() || src.height() < dst.height()) {
|
||||
// Calculate the (left, top) coordinates of the output image within the input image, applying the given gravity.
|
||||
int left;
|
||||
int top;
|
||||
std::tie(left, top) = CalculateCrop(dst.width(), dst.height(), src.width(), src.height(), gravity);
|
||||
// Embed onto transparent background
|
||||
std::vector<double> background { 0.0, 0.0, 0.0, 0.0 };
|
||||
src = src.embed(left, top, dst.width(), dst.height(), VImage::option()
|
||||
->set("extend", VIPS_EXTEND_BACKGROUND)
|
||||
->set("background", background)
|
||||
);
|
||||
}
|
||||
|
||||
// Split src into non-alpha and alpha channels
|
||||
VImage srcWithoutAlpha = src.extract_band(0, VImage::option()->set("n", src.bands() - 1));
|
||||
VImage srcAlpha = src[src.bands() - 1] * (1.0 / 255.0);
|
||||
|
||||
// Split dst into non-alpha and alpha channels
|
||||
VImage dstWithoutAlpha = dst.extract_band(0, VImage::option()->set("n", dst.bands() - 1));
|
||||
VImage dstAlpha = dst[dst.bands() - 1] * (1.0 / 255.0);
|
||||
|
||||
//
|
||||
// Compute normalized output alpha channel:
|
||||
//
|
||||
// References:
|
||||
// - http://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
|
||||
// - https://github.com/jcupitt/ruby-vips/issues/28#issuecomment-9014826
|
||||
//
|
||||
// out_a = src_a + dst_a * (1 - src_a)
|
||||
// ^^^^^^^^^^^
|
||||
// t0
|
||||
VImage t0 = srcAlpha.linear(-1.0, 1.0);
|
||||
VImage outAlphaNormalized = srcAlpha + dstAlpha * t0;
|
||||
|
||||
//
|
||||
// Compute output RGB channels:
|
||||
//
|
||||
// Wikipedia:
|
||||
// out_rgb = (src_rgb * src_a + dst_rgb * dst_a * (1 - src_a)) / out_a
|
||||
// ^^^^^^^^^^^
|
||||
// t0
|
||||
//
|
||||
// Omit division by `out_a` since `Compose` is supposed to output a
|
||||
// premultiplied RGBA image as reversal of premultiplication is handled
|
||||
// externally.
|
||||
//
|
||||
VImage outRGBPremultiplied = srcWithoutAlpha + dstWithoutAlpha * t0;
|
||||
|
||||
// Combine RGB and alpha channel into output image:
|
||||
return outRGBPremultiplied.bandjoin(outAlphaNormalized * 255.0);
|
||||
return tinted;
|
||||
}
|
||||
|
||||
/*
|
||||
* Stretch luminance to cover full dynamic range.
|
||||
*/
|
||||
VImage Normalize(VImage image) {
|
||||
VImage Normalise(VImage image) {
|
||||
// Get original colourspace
|
||||
VipsInterpretation typeBeforeNormalize = image.interpretation();
|
||||
if (typeBeforeNormalize == VIPS_INTERPRETATION_RGB) {
|
||||
@@ -125,10 +98,8 @@ namespace sharp {
|
||||
VImage Gamma(VImage image, double const exponent) {
|
||||
if (HasAlpha(image)) {
|
||||
// Separate alpha channel
|
||||
VImage imageWithoutAlpha = image.extract_band(0,
|
||||
VImage::option()->set("n", image.bands() - 1));
|
||||
VImage alpha = image[image.bands() - 1];
|
||||
return imageWithoutAlpha.gamma(VImage::option()->set("exponent", exponent)).bandjoin(alpha);
|
||||
return RemoveAlpha(image).gamma(VImage::option()->set("exponent", exponent)).bandjoin(alpha);
|
||||
} else {
|
||||
return image.gamma(VImage::option()->set("exponent", exponent));
|
||||
}
|
||||
@@ -152,6 +123,68 @@ namespace sharp {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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();
|
||||
return image
|
||||
.colourspace(VIPS_INTERPRETATION_sRGB)
|
||||
.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) {
|
||||
if (HasAlpha(image)) {
|
||||
// Separate alpha channel
|
||||
VImage alpha = image[image.bands() - 1];
|
||||
return RemoveAlpha(image)
|
||||
.colourspace(VIPS_INTERPRETATION_LCH)
|
||||
.linear(
|
||||
{ brightness, saturation, 1},
|
||||
{ 0.0, 0.0, static_cast<double>(hue) }
|
||||
)
|
||||
.colourspace(VIPS_INTERPRETATION_sRGB)
|
||||
.bandjoin(alpha);
|
||||
} else {
|
||||
return image
|
||||
.colourspace(VIPS_INTERPRETATION_LCH)
|
||||
.linear(
|
||||
{ brightness, saturation, 1 },
|
||||
{ 0.0, 0.0, static_cast<double>(hue) }
|
||||
)
|
||||
.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
|
||||
*/
|
||||
@@ -166,106 +199,80 @@ namespace sharp {
|
||||
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", flat)->set("m2", jagged)
|
||||
);
|
||||
VImage::option()->set("sigma", sigma)->set("m1", flat)->set("m2", jagged))
|
||||
.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;
|
||||
}
|
||||
|
||||
/*
|
||||
Calculate crop area based on image entropy
|
||||
Perform boolean/bitwise operation on image color channels - results in one channel image
|
||||
*/
|
||||
std::tuple<int, int> EntropyCrop(VImage image, int const outWidth, int const outHeight) {
|
||||
int left = 0;
|
||||
int top = 0;
|
||||
int const inWidth = image.width();
|
||||
int const inHeight = image.height();
|
||||
if (inWidth > outWidth) {
|
||||
// Reduce width by repeated removing slices from edge with lowest entropy
|
||||
int width = inWidth;
|
||||
double leftEntropy = 0.0;
|
||||
double rightEntropy = 0.0;
|
||||
// Max width of each slice
|
||||
int const maxSliceWidth = static_cast<int>(ceil((inWidth - outWidth) / 8.0));
|
||||
while (width > outWidth) {
|
||||
// Width of current slice
|
||||
int const slice = std::min(width - outWidth, maxSliceWidth);
|
||||
if (leftEntropy == 0.0) {
|
||||
// Update entropy of left slice
|
||||
leftEntropy = Entropy(image.extract_area(left, 0, slice, inHeight));
|
||||
}
|
||||
if (rightEntropy == 0.0) {
|
||||
// Update entropy of right slice
|
||||
rightEntropy = Entropy(image.extract_area(width - slice - 1, 0, slice, inHeight));
|
||||
}
|
||||
// Keep slice with highest entropy
|
||||
if (leftEntropy >= rightEntropy) {
|
||||
// Discard right slice
|
||||
rightEntropy = 0.0;
|
||||
} else {
|
||||
// Discard left slice
|
||||
leftEntropy = 0.0;
|
||||
left = left + slice;
|
||||
}
|
||||
width = width - slice;
|
||||
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, double const threshold) {
|
||||
if (image.width() < 3 && image.height() < 3) {
|
||||
throw VError("Image to trim must be at least 3x3 pixels");
|
||||
}
|
||||
// Top-left pixel provides the background colour
|
||||
VImage background = image.extract_area(0, 0, 1, 1);
|
||||
if (HasAlpha(background)) {
|
||||
background = background.flatten();
|
||||
}
|
||||
int left, top, width, height;
|
||||
left = image.find_trim(&top, &width, &height, VImage::option()
|
||||
->set("background", background(0, 0))
|
||||
->set("threshold", threshold));
|
||||
if (width == 0 || height == 0) {
|
||||
if (HasAlpha(image)) {
|
||||
// Search alpha channel
|
||||
VImage alpha = image[image.bands() - 1];
|
||||
VImage backgroundAlpha = alpha.extract_area(0, 0, 1, 1);
|
||||
left = alpha.find_trim(&top, &width, &height, VImage::option()
|
||||
->set("background", backgroundAlpha(0, 0))
|
||||
->set("threshold", threshold));
|
||||
}
|
||||
if (width == 0 || height == 0) {
|
||||
throw VError("Unexpected error while trimming. Try to lower the tolerance");
|
||||
}
|
||||
}
|
||||
if (inHeight > outHeight) {
|
||||
// Reduce height by repeated removing slices from edge with lowest entropy
|
||||
int height = inHeight;
|
||||
double topEntropy = 0.0;
|
||||
double bottomEntropy = 0.0;
|
||||
// Max height of each slice
|
||||
int const maxSliceHeight = static_cast<int>(ceil((inHeight - outHeight) / 8.0));
|
||||
while (height > outHeight) {
|
||||
// Height of current slice
|
||||
int const slice = std::min(height - outHeight, maxSliceHeight);
|
||||
if (topEntropy == 0.0) {
|
||||
// Update entropy of top slice
|
||||
topEntropy = Entropy(image.extract_area(0, top, inWidth, slice));
|
||||
}
|
||||
if (bottomEntropy == 0.0) {
|
||||
// Update entropy of bottom slice
|
||||
bottomEntropy = Entropy(image.extract_area(0, height - slice - 1, inWidth, slice));
|
||||
}
|
||||
// Keep slice with highest entropy
|
||||
if (topEntropy >= bottomEntropy) {
|
||||
// Discard bottom slice
|
||||
bottomEntropy = 0.0;
|
||||
} else {
|
||||
// Discard top slice
|
||||
topEntropy = 0.0;
|
||||
top = top + slice;
|
||||
}
|
||||
height = height - slice;
|
||||
}
|
||||
return image.extract_area(left, top, width, height);
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate (a * in + b)
|
||||
*/
|
||||
VImage Linear(VImage image, double const a, double const b) {
|
||||
if (HasAlpha(image)) {
|
||||
// Separate alpha channel
|
||||
VImage alpha = image[image.bands() - 1];
|
||||
return RemoveAlpha(image).linear(a, b).bandjoin(alpha);
|
||||
} else {
|
||||
return image.linear(a, b);
|
||||
}
|
||||
return std::make_tuple(left, top);
|
||||
}
|
||||
|
||||
/*
|
||||
Calculate the Shannon entropy for an image
|
||||
*/
|
||||
double Entropy(VImage image) {
|
||||
return image.hist_find().hist_entropy();
|
||||
}
|
||||
|
||||
/*
|
||||
Insert a tile cache to prevent over-computation of any previous operations in the pipeline
|
||||
*/
|
||||
VImage TileCache(VImage image, double const factor) {
|
||||
int tile_width;
|
||||
int tile_height;
|
||||
int scanline_count;
|
||||
vips_get_tile_size(image.get_image(), &tile_width, &tile_height, &scanline_count);
|
||||
double const need_lines = 1.2 * scanline_count / factor;
|
||||
return image.tilecache(VImage::option()
|
||||
->set("tile_width", image.width())
|
||||
->set("tile_height", 10)
|
||||
->set("max_tiles", static_cast<int>(round(1.0 + need_lines / 10.0)))
|
||||
->set("access", VIPS_ACCESS_SEQUENTIAL)
|
||||
->set("threaded", TRUE)
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace sharp
|
||||
|
||||
@@ -1,6 +1,23 @@
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// http://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.
|
||||
|
||||
#ifndef SRC_OPERATIONS_H_
|
||||
#define SRC_OPERATIONS_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <vips/vips8>
|
||||
|
||||
@@ -9,15 +26,14 @@ using vips::VImage;
|
||||
namespace sharp {
|
||||
|
||||
/*
|
||||
Alpha composite src over dst with given gravity.
|
||||
Assumes alpha channels are already premultiplied and will be unpremultiplied after.
|
||||
* Tint an image using the specified chroma, preserving the original image luminance
|
||||
*/
|
||||
VImage Composite(VImage src, VImage dst, const int gravity);
|
||||
VImage Tint(VImage image, double const a, double const b);
|
||||
|
||||
/*
|
||||
* Stretch luminance to cover full dynamic range.
|
||||
*/
|
||||
VImage Normalize(VImage image);
|
||||
VImage Normalise(VImage image);
|
||||
|
||||
/*
|
||||
* Gamma encoding/decoding
|
||||
@@ -29,25 +45,52 @@ namespace sharp {
|
||||
*/
|
||||
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 flat, double const jagged);
|
||||
|
||||
/*
|
||||
Calculate crop area based on image entropy
|
||||
Threshold an image
|
||||
*/
|
||||
std::tuple<int, int> EntropyCrop(VImage image, int const outWidth, int const outHeight);
|
||||
VImage Threshold(VImage image, double const threshold, bool const thresholdColor);
|
||||
|
||||
/*
|
||||
Calculate the Shannon entropy for an image
|
||||
Perform boolean/bitwise operation on image color channels - results in one channel image
|
||||
*/
|
||||
double Entropy(VImage image);
|
||||
VImage Bandbool(VImage image, VipsOperationBoolean const boolean);
|
||||
|
||||
/*
|
||||
Insert a tile cache to prevent over-computation of any previous operations in the pipeline
|
||||
Perform bitwise boolean operation between images
|
||||
*/
|
||||
VImage TileCache(VImage image, double const factor);
|
||||
VImage Boolean(VImage image, VImage imageR, VipsOperationBoolean const boolean);
|
||||
|
||||
/*
|
||||
Trim an image
|
||||
*/
|
||||
VImage Trim(VImage image, double const threshold);
|
||||
|
||||
/*
|
||||
* Linear adjustment (a * in + b)
|
||||
*/
|
||||
VImage Linear(VImage image, double const a, double const b);
|
||||
|
||||
/*
|
||||
* 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 and hue
|
||||
*/
|
||||
VImage Modulate(VImage image, double const brightness, double const saturation, int const hue);
|
||||
|
||||
} // namespace sharp
|
||||
|
||||
|
||||
1702
src/pipeline.cc
1702
src/pipeline.cc
File diff suppressed because it is too large
Load Diff
276
src/pipeline.h
276
src/pipeline.h
@@ -1,11 +1,30 @@
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// http://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.
|
||||
|
||||
#ifndef SRC_PIPELINE_H_
|
||||
#define SRC_PIPELINE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <napi.h>
|
||||
#include <vips/vips8>
|
||||
|
||||
#include "nan.h"
|
||||
#include "./common.h"
|
||||
|
||||
NAN_METHOD(pipeline);
|
||||
Napi::Value pipeline(const Napi::CallbackInfo& info);
|
||||
|
||||
enum class Canvas {
|
||||
CROP,
|
||||
@@ -15,24 +34,35 @@ enum class Canvas {
|
||||
IGNORE_ASPECT
|
||||
};
|
||||
|
||||
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 {
|
||||
std::string fileIn;
|
||||
char *bufferIn;
|
||||
size_t bufferInLength;
|
||||
std::string iccProfilePath;
|
||||
int limitInputPixels;
|
||||
int density;
|
||||
int rawWidth;
|
||||
int rawHeight;
|
||||
int rawChannels;
|
||||
sharp::InputDescriptor *input;
|
||||
std::string formatOut;
|
||||
std::string fileOut;
|
||||
void *bufferOut;
|
||||
size_t bufferOutLength;
|
||||
std::string overlayFileIn;
|
||||
char *overlayBufferIn;
|
||||
size_t overlayBufferInLength;
|
||||
int overlayGravity;
|
||||
std::vector<Composite *> composite;
|
||||
std::vector<sharp::InputDescriptor *> joinChannelIn;
|
||||
int topOffsetPre;
|
||||
int leftOffsetPre;
|
||||
int widthPre;
|
||||
@@ -45,21 +75,43 @@ struct PipelineBaton {
|
||||
int height;
|
||||
int channels;
|
||||
Canvas canvas;
|
||||
int crop;
|
||||
int position;
|
||||
std::vector<double> resizeBackground;
|
||||
bool hasCropOffset;
|
||||
int cropOffsetLeft;
|
||||
int cropOffsetTop;
|
||||
bool premultiplied;
|
||||
bool tileCentre;
|
||||
std::string kernel;
|
||||
std::string interpolator;
|
||||
double background[4];
|
||||
bool fastShrinkOnLoad;
|
||||
double tintA;
|
||||
double tintB;
|
||||
bool flatten;
|
||||
std::vector<double> flattenBackground;
|
||||
bool negate;
|
||||
double blurSigma;
|
||||
double brightness;
|
||||
double saturation;
|
||||
int hue;
|
||||
int medianSize;
|
||||
double sharpenSigma;
|
||||
double sharpenFlat;
|
||||
double sharpenJagged;
|
||||
int threshold;
|
||||
bool thresholdGrayscale;
|
||||
double trimThreshold;
|
||||
int trimOffsetLeft;
|
||||
int trimOffsetTop;
|
||||
double linearA;
|
||||
double linearB;
|
||||
double gamma;
|
||||
double gammaOut;
|
||||
bool greyscale;
|
||||
bool normalize;
|
||||
bool normalise;
|
||||
bool useExifOrientation;
|
||||
int angle;
|
||||
double rotationAngle;
|
||||
std::vector<double> rotationBackground;
|
||||
bool rotateBeforePreExtract;
|
||||
bool flip;
|
||||
bool flop;
|
||||
@@ -67,78 +119,194 @@ struct PipelineBaton {
|
||||
int extendBottom;
|
||||
int extendLeft;
|
||||
int extendRight;
|
||||
bool progressive;
|
||||
std::vector<double> extendBackground;
|
||||
bool withoutEnlargement;
|
||||
VipsAccess accessMethod;
|
||||
int quality;
|
||||
int compressionLevel;
|
||||
bool withoutAdaptiveFiltering;
|
||||
bool withoutChromaSubsampling;
|
||||
bool trellisQuantisation;
|
||||
bool overshootDeringing;
|
||||
bool optimiseScans;
|
||||
std::vector<double> affineMatrix;
|
||||
std::vector<double> affineBackground;
|
||||
double affineIdx;
|
||||
double affineIdy;
|
||||
double affineOdx;
|
||||
double affineOdy;
|
||||
vips::VInterpolate 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 pngColours;
|
||||
double pngDither;
|
||||
int webpQuality;
|
||||
int webpAlphaQuality;
|
||||
bool webpNearLossless;
|
||||
bool webpLossless;
|
||||
bool webpSmartSubsample;
|
||||
int webpReductionEffort;
|
||||
int tiffQuality;
|
||||
VipsForeignTiffCompression tiffCompression;
|
||||
VipsForeignTiffPredictor tiffPredictor;
|
||||
bool tiffPyramid;
|
||||
int tiffBitdepth;
|
||||
bool tiffTile;
|
||||
int tiffTileHeight;
|
||||
int tiffTileWidth;
|
||||
double tiffXres;
|
||||
double tiffYres;
|
||||
int heifQuality;
|
||||
VipsForeignHeifCompression heifCompression;
|
||||
int heifSpeed;
|
||||
bool heifLossless;
|
||||
std::string err;
|
||||
bool withMetadata;
|
||||
int withMetadataOrientation;
|
||||
std::string withMetadataIcc;
|
||||
std::unique_ptr<double[]> convKernel;
|
||||
int convKernelWidth;
|
||||
int convKernelHeight;
|
||||
double convKernelScale;
|
||||
double convKernelOffset;
|
||||
sharp::InputDescriptor *boolean;
|
||||
VipsOperationBoolean booleanOp;
|
||||
VipsOperationBoolean bandBoolOp;
|
||||
int extractChannel;
|
||||
bool removeAlpha;
|
||||
bool ensureAlpha;
|
||||
VipsInterpretation colourspace;
|
||||
int pageHeight;
|
||||
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::unique_ptr<double[]> recombMatrix;
|
||||
|
||||
PipelineBaton():
|
||||
bufferInLength(0),
|
||||
limitInputPixels(0),
|
||||
density(72),
|
||||
rawWidth(0),
|
||||
rawHeight(0),
|
||||
rawChannels(0),
|
||||
formatOut(""),
|
||||
fileOut(""),
|
||||
input(nullptr),
|
||||
bufferOutLength(0),
|
||||
overlayBufferInLength(0),
|
||||
overlayGravity(0),
|
||||
topOffsetPre(-1),
|
||||
topOffsetPost(-1),
|
||||
channels(0),
|
||||
canvas(Canvas::CROP),
|
||||
crop(0),
|
||||
position(0),
|
||||
resizeBackground{ 0.0, 0.0, 0.0, 255.0 },
|
||||
hasCropOffset(false),
|
||||
cropOffsetLeft(0),
|
||||
cropOffsetTop(0),
|
||||
premultiplied(false),
|
||||
tintA(128.0),
|
||||
tintB(128.0),
|
||||
flatten(false),
|
||||
flattenBackground{ 0.0, 0.0, 0.0 },
|
||||
negate(false),
|
||||
blurSigma(0.0),
|
||||
brightness(1.0),
|
||||
saturation(1.0),
|
||||
hue(0),
|
||||
medianSize(0),
|
||||
sharpenSigma(0.0),
|
||||
sharpenFlat(1.0),
|
||||
sharpenJagged(2.0),
|
||||
threshold(0),
|
||||
thresholdGrayscale(true),
|
||||
trimThreshold(0.0),
|
||||
trimOffsetLeft(0),
|
||||
trimOffsetTop(0),
|
||||
linearA(1.0),
|
||||
linearB(0.0),
|
||||
gamma(0.0),
|
||||
greyscale(false),
|
||||
normalize(false),
|
||||
normalise(false),
|
||||
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),
|
||||
progressive(false),
|
||||
extendBackground{ 0.0, 0.0, 0.0, 255.0 },
|
||||
withoutEnlargement(false),
|
||||
quality(80),
|
||||
compressionLevel(6),
|
||||
withoutAdaptiveFiltering(false),
|
||||
withoutChromaSubsampling(false),
|
||||
trellisQuantisation(false),
|
||||
overshootDeringing(false),
|
||||
optimiseScans(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(vips::VInterpolate::new_from_name("bicubic")),
|
||||
jpegQuality(80),
|
||||
jpegProgressive(false),
|
||||
jpegChromaSubsampling("4:2:0"),
|
||||
jpegTrellisQuantisation(false),
|
||||
jpegQuantisationTable(0),
|
||||
jpegOvershootDeringing(false),
|
||||
jpegOptimiseScans(false),
|
||||
jpegOptimiseCoding(true),
|
||||
pngProgressive(false),
|
||||
pngCompressionLevel(9),
|
||||
pngAdaptiveFiltering(false),
|
||||
pngPalette(false),
|
||||
pngQuality(100),
|
||||
pngColours(256),
|
||||
pngDither(1.0),
|
||||
webpQuality(80),
|
||||
webpAlphaQuality(100),
|
||||
webpNearLossless(false),
|
||||
webpLossless(false),
|
||||
webpSmartSubsample(false),
|
||||
webpReductionEffort(4),
|
||||
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),
|
||||
heifQuality(50),
|
||||
heifCompression(VIPS_FOREIGN_HEIF_COMPRESSION_AV1),
|
||||
heifSpeed(5),
|
||||
heifLossless(false),
|
||||
withMetadata(false),
|
||||
withMetadataOrientation(-1),
|
||||
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(false),
|
||||
colourspace(VIPS_INTERPRETATION_LAST),
|
||||
pageHeight(0),
|
||||
delay{-1},
|
||||
loop(-1),
|
||||
tileSize(256),
|
||||
tileOverlap(0),
|
||||
tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS),
|
||||
tileLayout(VIPS_FOREIGN_DZ_LAYOUT_DZ) {
|
||||
background[0] = 0.0;
|
||||
background[1] = 0.0;
|
||||
background[2] = 0.0;
|
||||
background[3] = 255.0;
|
||||
}
|
||||
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_
|
||||
|
||||
67
src/sharp.cc
67
src/sharp.cc
@@ -1,35 +1,52 @@
|
||||
#include <node.h>
|
||||
#include <vips/vips8>
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// http://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.
|
||||
|
||||
#include "nan.h"
|
||||
#include <napi.h>
|
||||
#include <vips/vips8>
|
||||
|
||||
#include "common.h"
|
||||
#include "metadata.h"
|
||||
#include "pipeline.h"
|
||||
#include "utilities.h"
|
||||
#include "stats.h"
|
||||
|
||||
NAN_MODULE_INIT(init) {
|
||||
static void* sharp_vips_init(void*) {
|
||||
g_setenv("VIPS_MIN_STACK_SIZE", "2m", FALSE);
|
||||
g_setenv("PANGOCAIRO_BACKEND", "fontconfig", FALSE);
|
||||
vips_init("sharp");
|
||||
|
||||
// Methods available to JavaScript
|
||||
Nan::Set(target, Nan::New("metadata").ToLocalChecked(),
|
||||
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(metadata)).ToLocalChecked());
|
||||
Nan::Set(target, Nan::New("pipeline").ToLocalChecked(),
|
||||
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(pipeline)).ToLocalChecked());
|
||||
Nan::Set(target, Nan::New("cache").ToLocalChecked(),
|
||||
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(cache)).ToLocalChecked());
|
||||
Nan::Set(target, Nan::New("concurrency").ToLocalChecked(),
|
||||
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(concurrency)).ToLocalChecked());
|
||||
Nan::Set(target, Nan::New("counters").ToLocalChecked(),
|
||||
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(counters)).ToLocalChecked());
|
||||
Nan::Set(target, Nan::New("simd").ToLocalChecked(),
|
||||
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(simd)).ToLocalChecked());
|
||||
Nan::Set(target, Nan::New("libvipsVersion").ToLocalChecked(),
|
||||
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(libvipsVersion)).ToLocalChecked());
|
||||
Nan::Set(target, Nan::New("format").ToLocalChecked(),
|
||||
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(format)).ToLocalChecked());
|
||||
Nan::Set(target, Nan::New("_maxColourDistance").ToLocalChecked(),
|
||||
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(_maxColourDistance)).ToLocalChecked());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
NODE_MODULE(sharp, init)
|
||||
Napi::Object init(Napi::Env env, Napi::Object exports) {
|
||||
static GOnce sharp_vips_init_once = G_ONCE_INIT;
|
||||
g_once(&sharp_vips_init_once, static_cast<GThreadFunc>(sharp_vips_init), nullptr);
|
||||
|
||||
g_log_set_handler("VIPS", static_cast<GLogLevelFlags>(G_LOG_LEVEL_WARNING),
|
||||
static_cast<GLogFunc>(sharp::VipsWarningCallback), nullptr);
|
||||
|
||||
// Methods available to JavaScript
|
||||
exports.Set("metadata", Napi::Function::New(env, metadata));
|
||||
exports.Set("pipeline", Napi::Function::New(env, pipeline));
|
||||
exports.Set("cache", Napi::Function::New(env, cache));
|
||||
exports.Set("concurrency", Napi::Function::New(env, concurrency));
|
||||
exports.Set("counters", Napi::Function::New(env, counters));
|
||||
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("_maxColourDistance", Napi::Function::New(env, _maxColourDistance));
|
||||
exports.Set("stats", Napi::Function::New(env, stats));
|
||||
return exports;
|
||||
}
|
||||
|
||||
NODE_API_MODULE(sharp, init)
|
||||
|
||||
193
src/stats.cc
Normal file
193
src/stats.cc
Normal file
@@ -0,0 +1,193 @@
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// http://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.
|
||||
|
||||
#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
|
||||
g_atomic_int_dec_and_test(&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.Call({ 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, 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[0].As<Napi::Object>();
|
||||
|
||||
// Input
|
||||
baton->input = sharp::CreateInputDescriptor(options.Get("input").As<Napi::Object>());
|
||||
|
||||
// Function to notify of libvips warnings
|
||||
Napi::Function debuglog = options.Get("debuglog").As<Napi::Function>();
|
||||
|
||||
// Join queue for worker thread
|
||||
Napi::Function callback = info[1].As<Napi::Function>();
|
||||
StatsWorker *worker = new StatsWorker(callback, baton, debuglog);
|
||||
worker->Receiver().Set("options", options);
|
||||
worker->Queue();
|
||||
|
||||
// Increment queued task counter
|
||||
g_atomic_int_inc(&sharp::counterQueue);
|
||||
|
||||
return info.Env().Undefined();
|
||||
}
|
||||
70
src/stats.h
Normal file
70
src/stats.h
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// http://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.
|
||||
|
||||
#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_
|
||||
266
src/utilities.cc
266
src/utilities.cc
@@ -1,193 +1,167 @@
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// http://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.
|
||||
|
||||
#include <cmath>
|
||||
#include <node.h>
|
||||
#include <string>
|
||||
|
||||
#include <napi.h>
|
||||
#include <vips/vips8>
|
||||
#include <vips/vector.h>
|
||||
|
||||
#include "nan.h"
|
||||
|
||||
#include "common.h"
|
||||
#include "operations.h"
|
||||
#include "utilities.h"
|
||||
|
||||
using v8::Boolean;
|
||||
using v8::Integer;
|
||||
using v8::Local;
|
||||
using v8::Number;
|
||||
using v8::Object;
|
||||
using v8::String;
|
||||
|
||||
using Nan::HandleScope;
|
||||
using Nan::New;
|
||||
using Nan::Set;
|
||||
using Nan::ThrowError;
|
||||
using Nan::To;
|
||||
using Nan::Utf8String;
|
||||
|
||||
/*
|
||||
Get and set cache limits
|
||||
*/
|
||||
NAN_METHOD(cache) {
|
||||
HandleScope();
|
||||
Napi::Value cache(const Napi::CallbackInfo& info) {
|
||||
Napi::Env env = info.Env();
|
||||
|
||||
// Set memory limit
|
||||
if (info[0]->IsInt32()) {
|
||||
vips_cache_set_max_mem(To<int32_t>(info[0]).FromJust() * 1048576);
|
||||
if (info[0].IsNumber()) {
|
||||
vips_cache_set_max_mem(info[0].As<Napi::Number>().Int32Value() * 1048576);
|
||||
}
|
||||
// Set file limit
|
||||
if (info[1]->IsInt32()) {
|
||||
vips_cache_set_max_files(To<int32_t>(info[1]).FromJust());
|
||||
if (info[1].IsNumber()) {
|
||||
vips_cache_set_max_files(info[1].As<Napi::Number>().Int32Value());
|
||||
}
|
||||
// Set items limit
|
||||
if (info[2]->IsInt32()) {
|
||||
vips_cache_set_max(To<int32_t>(info[2]).FromJust());
|
||||
if (info[2].IsNumber()) {
|
||||
vips_cache_set_max(info[2].As<Napi::Number>().Int32Value());
|
||||
}
|
||||
|
||||
// Get memory stats
|
||||
Local<Object> memory = New<Object>();
|
||||
Set(memory, New("current").ToLocalChecked(),
|
||||
New<Integer>(static_cast<int>(round(vips_tracked_get_mem() / 1048576)))
|
||||
);
|
||||
Set(memory, New("high").ToLocalChecked(),
|
||||
New<Integer>(static_cast<int>(round(vips_tracked_get_mem_highwater() / 1048576)))
|
||||
);
|
||||
Set(memory, New("max").ToLocalChecked(),
|
||||
New<Integer>(static_cast<int>(round(vips_cache_get_max_mem() / 1048576)))
|
||||
);
|
||||
Napi::Object memory = Napi::Object::New(env);
|
||||
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
|
||||
Local<Object> files = New<Object>();
|
||||
Set(files, New("current").ToLocalChecked(), New<Integer>(vips_tracked_get_files()));
|
||||
Set(files, New("max").ToLocalChecked(), New<Integer>(vips_cache_get_max_files()));
|
||||
Napi::Object files = Napi::Object::New(env);
|
||||
files.Set("current", vips_tracked_get_files());
|
||||
files.Set("max", vips_cache_get_max_files());
|
||||
|
||||
// Get item stats
|
||||
Local<Object> items = New<Object>();
|
||||
Set(items, New("current").ToLocalChecked(), New<Integer>(vips_cache_get_size()));
|
||||
Set(items, New("max").ToLocalChecked(), New<Integer>(vips_cache_get_max()));
|
||||
Napi::Object items = Napi::Object::New(env);
|
||||
items.Set("current", vips_cache_get_size());
|
||||
items.Set("max", vips_cache_get_max());
|
||||
|
||||
Local<Object> cache = New<Object>();
|
||||
Set(cache, New("memory").ToLocalChecked(), memory);
|
||||
Set(cache, New("files").ToLocalChecked(), files);
|
||||
Set(cache, New("items").ToLocalChecked(), items);
|
||||
info.GetReturnValue().Set(cache);
|
||||
Napi::Object cache = Napi::Object::New(env);
|
||||
cache.Set("memory", memory);
|
||||
cache.Set("files", files);
|
||||
cache.Set("items", items);
|
||||
return cache;
|
||||
}
|
||||
|
||||
/*
|
||||
Get and set size of thread pool
|
||||
*/
|
||||
NAN_METHOD(concurrency) {
|
||||
HandleScope();
|
||||
|
||||
Napi::Value concurrency(const Napi::CallbackInfo& info) {
|
||||
// Set concurrency
|
||||
if (info[0]->IsInt32()) {
|
||||
vips_concurrency_set(To<int32_t>(info[0]).FromJust());
|
||||
if (info[0].IsNumber()) {
|
||||
vips_concurrency_set(info[0].As<Napi::Number>().Int32Value());
|
||||
}
|
||||
// Get concurrency
|
||||
info.GetReturnValue().Set(New<Integer>(vips_concurrency_get()));
|
||||
return Napi::Number::New(info.Env(), vips_concurrency_get());
|
||||
}
|
||||
|
||||
/*
|
||||
Get internal counters (queued tasks, processing tasks)
|
||||
*/
|
||||
NAN_METHOD(counters) {
|
||||
using sharp::counterProcess;
|
||||
using sharp::counterQueue;
|
||||
|
||||
HandleScope();
|
||||
Local<Object> counters = New<Object>();
|
||||
Set(counters, New("queue").ToLocalChecked(), New<Integer>(counterQueue));
|
||||
Set(counters, New("process").ToLocalChecked(), New<Integer>(counterProcess));
|
||||
info.GetReturnValue().Set(counters);
|
||||
Napi::Value counters(const Napi::CallbackInfo& info) {
|
||||
Napi::Object counters = Napi::Object::New(info.Env());
|
||||
counters.Set("queue", sharp::counterQueue);
|
||||
counters.Set("process", sharp::counterProcess);
|
||||
return counters;
|
||||
}
|
||||
|
||||
/*
|
||||
Get and set use of SIMD vector unit instructions
|
||||
*/
|
||||
NAN_METHOD(simd) {
|
||||
HandleScope();
|
||||
|
||||
Napi::Value simd(const Napi::CallbackInfo& info) {
|
||||
// Set state
|
||||
if (info[0]->IsBoolean()) {
|
||||
vips_vector_set_enabled(To<bool>(info[0]).FromJust());
|
||||
if (info[0].IsBoolean()) {
|
||||
vips_vector_set_enabled(info[0].As<Napi::Boolean>().Value());
|
||||
}
|
||||
// Get state
|
||||
info.GetReturnValue().Set(New<Boolean>(vips_vector_isenabled()));
|
||||
return Napi::Boolean::New(info.Env(), vips_vector_isenabled());
|
||||
}
|
||||
|
||||
/*
|
||||
Get libvips version
|
||||
*/
|
||||
NAN_METHOD(libvipsVersion) {
|
||||
HandleScope();
|
||||
Napi::Value libvipsVersion(const Napi::CallbackInfo& info) {
|
||||
char version[9];
|
||||
g_snprintf(version, sizeof(version), "%d.%d.%d", vips_version(0), vips_version(1), vips_version(2));
|
||||
info.GetReturnValue().Set(New(version).ToLocalChecked());
|
||||
return Napi::String::New(info.Env(), version);
|
||||
}
|
||||
|
||||
/*
|
||||
Get available input/output file/buffer/stream formats
|
||||
*/
|
||||
NAN_METHOD(format) {
|
||||
HandleScope();
|
||||
|
||||
// Attribute names
|
||||
Local<String> attrId = New("id").ToLocalChecked();
|
||||
Local<String> attrInput = New("input").ToLocalChecked();
|
||||
Local<String> attrOutput = New("output").ToLocalChecked();
|
||||
Local<String> attrFile = New("file").ToLocalChecked();
|
||||
Local<String> attrBuffer = New("buffer").ToLocalChecked();
|
||||
Local<String> attrStream = New("stream").ToLocalChecked();
|
||||
|
||||
// Which load/save operations are available for each compressed format?
|
||||
Local<Object> format = New<Object>();
|
||||
for (std::string f : {
|
||||
"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz", "ppm", "fits", "gif", "svg", "pdf"
|
||||
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"
|
||||
}) {
|
||||
// Input
|
||||
Local<Boolean> hasInputFile =
|
||||
New<Boolean>(vips_type_find("VipsOperation", (f + "load").c_str()));
|
||||
Local<Boolean> hasInputBuffer =
|
||||
New<Boolean>(vips_type_find("VipsOperation", (f + "load_buffer").c_str()));
|
||||
Local<Object> input = New<Object>();
|
||||
Set(input, attrFile, hasInputFile);
|
||||
Set(input, attrBuffer, hasInputBuffer);
|
||||
Set(input, attrStream, hasInputBuffer);
|
||||
Napi::Boolean hasInputFile =
|
||||
Napi::Boolean::New(env, vips_type_find("VipsOperation", (f + "load").c_str()));
|
||||
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);
|
||||
// Output
|
||||
Local<Boolean> hasOutputFile =
|
||||
New<Boolean>(vips_type_find("VipsOperation", (f + "save").c_str()));
|
||||
Local<Boolean> hasOutputBuffer =
|
||||
New<Boolean>(vips_type_find("VipsOperation", (f + "save_buffer").c_str()));
|
||||
Local<Object> output = New<Object>();
|
||||
Set(output, attrFile, hasOutputFile);
|
||||
Set(output, attrBuffer, hasOutputBuffer);
|
||||
Set(output, attrStream, hasOutputBuffer);
|
||||
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
|
||||
Local<Object> container = New<Object>();
|
||||
Local<String> formatId = New(f).ToLocalChecked();
|
||||
Set(container, attrId, formatId);
|
||||
Set(container, attrInput, input);
|
||||
Set(container, attrOutput, output);
|
||||
Napi::Object container = Napi::Object::New(env);
|
||||
container.Set("id", f);
|
||||
container.Set("input", input);
|
||||
container.Set("output", output);
|
||||
// Add to set of formats
|
||||
Set(format, formatId, container);
|
||||
format.Set(f, container);
|
||||
}
|
||||
|
||||
// Raw, uncompressed data
|
||||
Local<Object> raw = New<Object>();
|
||||
Local<String> rawId = New("raw").ToLocalChecked();
|
||||
Set(raw, attrId, rawId);
|
||||
Set(format, rawId, raw);
|
||||
Local<Boolean> supported = New<Boolean>(true);
|
||||
Local<Boolean> unsupported = New<Boolean>(false);
|
||||
Local<Object> rawInput = New<Object>();
|
||||
Set(rawInput, attrFile, unsupported);
|
||||
Set(rawInput, attrBuffer, supported);
|
||||
Set(rawInput, attrStream, supported);
|
||||
Set(raw, attrInput, rawInput);
|
||||
Local<Object> rawOutput = New<Object>();
|
||||
Set(rawOutput, attrFile, unsupported);
|
||||
Set(rawOutput, attrBuffer, supported);
|
||||
Set(rawOutput, attrStream, supported);
|
||||
Set(raw, attrOutput, rawOutput);
|
||||
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);
|
||||
|
||||
info.GetReturnValue().Set(format);
|
||||
return format;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -195,65 +169,59 @@ NAN_METHOD(format) {
|
||||
Calculates the maximum colour distance using the DE2000 algorithm
|
||||
between two images of the same dimensions and number of channels.
|
||||
*/
|
||||
NAN_METHOD(_maxColourDistance) {
|
||||
using vips::VImage;
|
||||
using vips::VError;
|
||||
using sharp::DetermineImageType;
|
||||
using sharp::ImageType;
|
||||
using sharp::HasAlpha;
|
||||
|
||||
HandleScope();
|
||||
Napi::Value _maxColourDistance(const Napi::CallbackInfo& info) {
|
||||
Napi::Env env = info.Env();
|
||||
|
||||
// Open input files
|
||||
VImage image1;
|
||||
ImageType imageType1 = DetermineImageType(*Utf8String(info[0]));
|
||||
if (imageType1 != ImageType::UNKNOWN) {
|
||||
sharp::ImageType imageType1 = sharp::DetermineImageType(info[0].As<Napi::String>().Utf8Value().data());
|
||||
if (imageType1 != sharp::ImageType::UNKNOWN) {
|
||||
try {
|
||||
image1 = VImage::new_from_file(*Utf8String(info[0]));
|
||||
image1 = VImage::new_from_file(info[0].As<Napi::String>().Utf8Value().c_str());
|
||||
} catch (...) {
|
||||
return ThrowError("Input file 1 has corrupt header");
|
||||
throw Napi::Error::New(env, "Input file 1 has corrupt header");
|
||||
}
|
||||
} else {
|
||||
return ThrowError("Input file 1 is of an unsupported image format");
|
||||
throw Napi::Error::New(env, "Input file 1 is of an unsupported image format");
|
||||
}
|
||||
VImage image2;
|
||||
ImageType imageType2 = DetermineImageType(*Utf8String(info[1]));
|
||||
if (imageType2 != ImageType::UNKNOWN) {
|
||||
sharp::ImageType imageType2 = sharp::DetermineImageType(info[1].As<Napi::String>().Utf8Value().data());
|
||||
if (imageType2 != sharp::ImageType::UNKNOWN) {
|
||||
try {
|
||||
image2 = VImage::new_from_file(*Utf8String(info[1]));
|
||||
image2 = VImage::new_from_file(info[1].As<Napi::String>().Utf8Value().c_str());
|
||||
} catch (...) {
|
||||
return ThrowError("Input file 2 has corrupt header");
|
||||
throw Napi::Error::New(env, "Input file 2 has corrupt header");
|
||||
}
|
||||
} else {
|
||||
return ThrowError("Input file 2 is of an unsupported image format");
|
||||
throw Napi::Error::New(env, "Input file 2 is of an unsupported image format");
|
||||
}
|
||||
// Ensure same number of channels
|
||||
if (image1.bands() != image2.bands()) {
|
||||
return ThrowError("mismatchedBands");
|
||||
throw Napi::Error::New(env, "mismatchedBands");
|
||||
}
|
||||
// Ensure same dimensions
|
||||
if (image1.width() != image2.width() || image1.height() != image2.height()) {
|
||||
return ThrowError("mismatchedDimensions");
|
||||
throw Napi::Error::New(env, "mismatchedDimensions");
|
||||
}
|
||||
|
||||
double maxColourDistance;
|
||||
try {
|
||||
// Premultiply and remove alpha
|
||||
if (HasAlpha(image1)) {
|
||||
if (sharp::HasAlpha(image1)) {
|
||||
image1 = image1.premultiply().extract_band(1, VImage::option()->set("n", image1.bands() - 1));
|
||||
}
|
||||
if (HasAlpha(image2)) {
|
||||
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 (VError err) {
|
||||
return ThrowError(err.what());
|
||||
} 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();
|
||||
|
||||
info.GetReturnValue().Set(New<Number>(maxColourDistance));
|
||||
return Napi::Number::New(env, maxColourDistance);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,28 @@
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// http://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.
|
||||
|
||||
#ifndef SRC_UTILITIES_H_
|
||||
#define SRC_UTILITIES_H_
|
||||
|
||||
#include "nan.h"
|
||||
#include <napi.h>
|
||||
|
||||
NAN_METHOD(cache);
|
||||
NAN_METHOD(concurrency);
|
||||
NAN_METHOD(counters);
|
||||
NAN_METHOD(simd);
|
||||
NAN_METHOD(libvipsVersion);
|
||||
NAN_METHOD(format);
|
||||
NAN_METHOD(_maxColourDistance);
|
||||
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);
|
||||
Napi::Value _maxColourDistance(const Napi::CallbackInfo& info);
|
||||
|
||||
#endif // SRC_UTILITIES_H_
|
||||
|
||||
@@ -5,20 +5,19 @@
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"description": "Benchmark and performance tests for sharp",
|
||||
"scripts": {
|
||||
"test": "VIPS_WARNING=0 node perf && node random && node parallel"
|
||||
"test": "node perf && node random && node parallel"
|
||||
},
|
||||
"devDependencies": {
|
||||
"async": "^1.5.2",
|
||||
"benchmark": "^2.1.0",
|
||||
"gm": "^1.22.0",
|
||||
"imagemagick": "^0.1.3",
|
||||
"imagemagick-native": "^1.9.2",
|
||||
"jimp": "^0.2.24",
|
||||
"lwip": "^0.0.9",
|
||||
"semver": "^5.1.0"
|
||||
"async": "3.2.0",
|
||||
"benchmark": "2.1.4",
|
||||
"gm": "1.23.1",
|
||||
"imagemagick": "0.1.3",
|
||||
"jimp": "0.16.1",
|
||||
"mapnik": "4.5.5",
|
||||
"semver": "7.3.4"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
"node": "14"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,43 +2,44 @@
|
||||
|
||||
process.env.UV_THREADPOOL_SIZE = 64;
|
||||
|
||||
var assert = require('assert');
|
||||
var async = require('async');
|
||||
const assert = require('assert');
|
||||
const async = require('async');
|
||||
|
||||
var sharp = require('../../index');
|
||||
var fixtures = require('../fixtures');
|
||||
const sharp = require('../../');
|
||||
const fixtures = require('../fixtures');
|
||||
|
||||
var width = 720;
|
||||
var height = 480;
|
||||
const width = 720;
|
||||
const height = 480;
|
||||
|
||||
sharp.concurrency(1);
|
||||
sharp.simd(true);
|
||||
|
||||
var timer = setInterval(function() {
|
||||
const timer = setInterval(function () {
|
||||
console.dir(sharp.counters());
|
||||
}, 100);
|
||||
|
||||
async.mapSeries([1, 1, 2, 4, 8, 16, 32, 64], function(parallelism, next) {
|
||||
var start = new Date().getTime();
|
||||
async.mapSeries([1, 1, 2, 4, 8, 16, 32, 64], function (parallelism, next) {
|
||||
const start = new Date().getTime();
|
||||
async.times(parallelism,
|
||||
function(id, callback) {
|
||||
/*jslint unused: false */
|
||||
sharp(fixtures.inputJpg).resize(width, height).toBuffer(function(err, buffer) {
|
||||
function (id, callback) {
|
||||
/* jslint unused: false */
|
||||
sharp(fixtures.inputJpg).resize(width, height).toBuffer(function (err, buffer) {
|
||||
buffer = null;
|
||||
callback(err, new Date().getTime() - start);
|
||||
});
|
||||
},
|
||||
function(err, ids) {
|
||||
function (err, ids) {
|
||||
assert(!err);
|
||||
assert(ids.length === parallelism);
|
||||
var mean = ids.reduce(function(a, b) {
|
||||
ids.sort();
|
||||
const mean = ids.reduce(function (a, b) {
|
||||
return a + b;
|
||||
}) / ids.length;
|
||||
console.log(parallelism + ' parallel calls: fastest=' + ids[0] + 'ms slowest=' + ids[ids.length - 1] + 'ms mean=' + mean + 'ms');
|
||||
next();
|
||||
}
|
||||
);
|
||||
}, function() {
|
||||
}, function () {
|
||||
clearInterval(timer);
|
||||
console.dir(sharp.counters());
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user