Compare commits

...

214 Commits

Author SHA1 Message Date
Lovell Fuller
1a3c38d35f Pre-release v0.28.0-alpha1 2021-03-14 11:50:33 +00:00
Lovell Fuller
00aece0538 Ensure id attr can be set for IIIF tile output #2612 2021-03-14 11:19:53 +00:00
Lovell Fuller
5a9cc835b3 Reduce concurrency when using glibc-based Linux
to help prevent memory fragmentation
2021-03-14 11:19:53 +00:00
Lovell Fuller
58526cc849 Upgrade to libvips v8.10.6-alpha3 2021-03-14 11:19:53 +00:00
Lovell Fuller
955b5f43a5 Tests: small speed up to a couple of tile-related tests 2021-03-14 11:19:53 +00:00
Lovell Fuller
447aec3fde Tests: update leak suppressions 2021-03-14 11:19:53 +00:00
Lovell Fuller
473260a836 Docs: update with install-time improvements 2021-03-14 11:19:53 +00:00
Lovell Fuller
4d2784c10c Prebuilt libvips v8.10.6 binaries work with musl 1.1.x and 1.2.x 2021-03-14 11:19:53 +00:00
Lovell Fuller
d9af897595 Tests: ensure AVIF order is read, write, read+write 2021-03-14 11:19:53 +00:00
Lovell Fuller
23a48be315 Upgrade to libvips v8.10.6-alpha2
- Prebuilt Linux libvips binaries now use 'new' C++11 ABI
2021-03-14 11:19:53 +00:00
Lovell Fuller
ce8f48e5d1 CI: Add linuxmusl-arm64v8 environment 2021-03-14 11:19:53 +00:00
Kleis Auke Wolthuizen
6aaf839662 Use a single shared library 2021-03-14 11:19:53 +00:00
Lovell Fuller
984a9e653e Upgrade to libvips 8.10.6-alpha1
- Prebuilt binaries now include mozjpeg and libimagequant (BSD 2-Clause)
- Prebuilt binaries limit AVIF support to the most common 8-bit depth
- Add `mozjpeg` option to `jpeg` method, sets mozjpeg defaults
- Reduce the default PNG `compressionLevel` to the more commonly used 6
2021-03-14 11:19:53 +00:00
Lovell Fuller
8dffa28b4d Remove npmlog as a direct dependency
It remains a transitive dependency via prebuild-install
2021-03-14 11:19:53 +00:00
Lovell Fuller
b05a4bdadd Use same version of simple-get as prebuild-install
to prevent two different versions being installed
2021-03-14 11:19:53 +00:00
Lovell Fuller
36087fe518 Remove array-flatten dependency 2021-03-14 11:19:53 +00:00
Lovell Fuller
5eed87ec4d Install: skip header files when using prebuilds 2021-03-14 11:19:53 +00:00
Tobias Nießen
af66a73225 Tests: fix unit test description (#2619) 2021-03-13 16:18:40 +00:00
Alex Bradley
dcf913c17e Install: fail on incomplete download and clean up tempfile (#2608)
- Fail when the connection closes before the response is complete
- Create tempfile only when needed, and clean it up on failure
2021-03-05 15:21:34 +00:00
Lovell Fuller
68ccba8f74 Docs: refresh search index 2021-02-22 21:17:31 +00:00
Lovell Fuller
956f7e29db Release v0.27.2 2021-02-22 20:30:42 +00:00
Lovell Fuller
4264c0577e Improve experience for those using Apple M1 devices #2460
- For Rosetta x64, prevent use of global ARM64 libvips
- For ARM64, improve error message when global libvips not found
2021-02-22 13:49:31 +00:00
Lovell Fuller
cc37b59309 Switch to libvips' recently-exposed has_alpha #2569 2021-02-22 12:32:20 +00:00
Lovell Fuller
9f2f92095d Skip prebuilt binaries for musl >=1.2.0 #2570 2021-02-20 19:40:40 +00:00
Lovell Fuller
0c1075c089 Docs: local compilation requires --build-from-source flag 2021-02-20 15:43:48 +00:00
allx
9c64710c8b Allow code bundling of utility functions (#2586) 2021-02-20 15:39:25 +00:00
Lovell Fuller
f6f16b91db Allow use of recomb op with 1/2 channel input #2584 2021-02-19 16:37:29 +00:00
Lovell Fuller
1986b5cfe6 Bump deps 2021-02-19 15:49:21 +00:00
Lovell Fuller
6445b72d41 Docs: Changelog entry and credit for #2581 2021-02-19 15:48:59 +00:00
Florian Busch
df7b8ba738 Add support for non lower case extensions with toFormat 2021-02-17 20:46:13 +00:00
Pedro Poveda
202083999e Docs: add closing parenthesis so code example runs 2021-02-14 15:57:06 +00:00
aprat84
315f519e1d Docs: correct type for AVIF speed output option (#2568) 2021-02-08 20:37:56 +00:00
Lovell Fuller
d7d580ae6f Tests: using parallel fails on latest Node.js 15.8.0 2021-02-08 13:12:35 +00:00
Lovell Fuller
7017af303d Improve error message when attempting toFile/GIF without magick 2021-02-08 11:46:13 +00:00
Lovell Fuller
0dc325daa4 Docs: add section about Webpack configuration 2021-01-29 11:29:24 +00:00
Lovell Fuller
6dffb47973 Docs: small search index improvements 2021-01-29 11:28:48 +00:00
Lovell Fuller
b19dad69d6 Release v0.27.1 2021-01-27 19:44:39 +00:00
Lovell Fuller
94c5ac64e9 Bump devDeps 2021-01-27 19:44:01 +00:00
Lovell Fuller
c4bcd088fb Tests: run in parallel, ~20% faster on multicore machines 2021-01-26 20:27:52 +00:00
Lovell Fuller
aeecbe9396 Tests: ensure faster metadata tests pass on ARM64 2021-01-26 20:27:20 +00:00
Lovell Fuller
171aade9cd Tests: reduce time taken by metadata tests 2021-01-26 19:52:33 +00:00
Lovell Fuller
67213ae86c Tests: refactor output paths, might enable parallel runs 2021-01-26 18:43:48 +00:00
Lovell Fuller
24d9e53c3f Docs: add example of 16-bit RGB output #2528 2021-01-26 15:03:43 +00:00
Kleis Auke Wolthuizen
573ed5f4b5 Avoid calling g_type_from_name #2535 2021-01-26 14:42:08 +00:00
Bert Verhelst
ceff628add Docs: ensure correct types for output options 2021-01-26 14:23:56 +00:00
Randy Ridge
0bb8cb9203 Ensure TIFF is cast when using float predictor (#2502) 2021-01-26 14:00:25 +00:00
Lovell Fuller
98349bde28 Docs: add section on known conflicts 2021-01-24 17:15:28 +00:00
Lovell Fuller
f09be932eb Docs: add info about npm v7 directory ownership change 2021-01-24 16:52:10 +00:00
Lovell Fuller
4c57ac2bbe Docs: sharp logos are now in the public domain 2021-01-18 16:52:23 +00:00
Lovell Fuller
1dd93c1b6b Docs: changelog entry and example for #2527 2021-01-16 14:26:38 +00:00
alza54
c9f85fe27f Expose libvips gaussnoise operation (#2527) 2021-01-16 14:03:25 +00:00
Lovell Fuller
419cbe50f6 Preserve transparancy in monochrome logo 2021-01-16 10:53:15 +00:00
Lovell Fuller
5031c8323f Add monochrome version of sharp logo 2021-01-15 22:03:00 +00:00
Lovell Fuller
762d5913ce Avoid nested macro, replace VIPS_AREA w/ reinterpret_cast 2021-01-13 18:32:37 +00:00
Lovell Fuller
290df1b1c7 Windows: fix preprocessor syntax 2021-01-13 18:09:42 +00:00
Lovell Fuller
79170afc51 Docs: add 2021 as a copyright year 2021-01-13 18:06:28 +00:00
Lovell Fuller
bba00c2bfe Revert: ensure all platforms use fontconfig #2399 #2515 2021-01-13 17:50:58 +00:00
Lovell Fuller
f7e2b3688f Bump devDependencies 2021-01-13 17:23:29 +00:00
Lovell Fuller
8d49b7dde1 Ensure tests pass with latest libvips master branch
Expose forthcoming HEIF features where available
2021-01-13 16:47:49 +00:00
Lovell Fuller
138e60adb3 Docs: add section for Apple M1 users #2460 2021-01-06 13:39:47 +00:00
Lovell Fuller
d6376c31e0 Test: ensure toBuffer tests return any errors 2021-01-06 13:12:24 +00:00
Lovell Fuller
a7003e93c8 Docs: changelog entry for #2511 2021-01-06 11:10:11 +00:00
Leon Radley
4821a11223 Add support for Uint8(Clamped)Array input (#2511) 2021-01-06 09:49:24 +00:00
Lovell Fuller
bf1b326988 Docs: allow docs to be built on Windows 2021-01-01 15:19:35 +00:00
Lovell Fuller
39ddb6a175 Docs: improve descripion of create/raw props 2021-01-01 14:47:26 +00:00
Lovell Fuller
b2a0b8c0f0 Release v0.27.0 2020-12-22 11:50:23 +00:00
Lovell Fuller
4debc46d0e Docs: add AVIF to supported formats 2020-12-22 11:47:54 +00:00
Lovell Fuller
f4e259d10f Pre-release v0.27.0-beta1 (prebuild test) 2020-12-21 21:14:15 +00:00
Lovell Fuller
774d78228e Docs: update performance test results 2020-12-21 21:00:44 +00:00
Lovell Fuller
0e62bde5c3 Update (and pin) benchmark module versions 2020-12-21 20:26:57 +00:00
Lovell Fuller
2bbd9b23e6 Add new leak test suppressions (fontconfig, gsf, rsvg) 2020-12-21 11:24:34 +00:00
Manan Jadhav
02676140e8 Allow for negative top/left offsets in composite overlays
A top or left offset value of -1 will no longer mean that the
value is not set, but will now be an actual offset of -1.

INT_MIN for left & top will mean that the values are not set.

Co-authored-by: Christian Flintrup <chr@gigahost.dk>
2020-12-20 17:36:39 +00:00
Lovell Fuller
182beaa4a1 Docs: add note about AVIF images smaller than 16x16 2020-12-20 17:17:49 +00:00
Lovell Fuller
7c08a09529 Add new leak test suppressions (rsvg, vips, heif, aom) 2020-12-20 17:05:37 +00:00
Lovell Fuller
ef964b5472 Ensure all platforms use fontconfig to render #2399 2020-12-20 10:23:26 +00:00
Lovell Fuller
ee54ce9913 Upgrade to stable libvips v8.10.5 prebuild 2020-12-20 09:51:33 +00:00
Lovell Fuller
e59e146887 CI: migrate x64 Linux, macOS and Windows to GitHub Actions 2020-12-18 17:39:11 +00:00
Lovell Fuller
103ec0d58f Upgrade to libvips 8.10.5, AVIF support in prebuilt binaries
Remove experimental status from HEIF, changing defaults
to prefer royalty-free AV1 over patent-encumbered HEVC
2020-12-18 17:32:16 +00:00
Lovell Fuller
a0d89ed514 Add link to documentation on new issue page 2020-12-09 15:01:14 +00:00
Lovell Fuller
c10888e6fe Release v0.26.3 2020-11-16 16:44:18 +00:00
Lovell Fuller
93455f8eb5 Docs: changelog entry for #2412 2020-11-16 15:26:27 +00:00
Matt Kane
65acd96c8d Install: conditionally use Brotli or gzip prebuilt libvips (#2412) 2020-11-16 15:22:13 +00:00
Lovell Fuller
fabe720b9b Docs: keyword search improvements 2020-11-16 15:10:28 +00:00
Lovell Fuller
53dd313e97 CI: force build from source, req of prebuild-install v6+ 2020-11-16 13:55:55 +00:00
Lovell Fuller
2678d7a660 Bump dev dependencies 2020-11-16 12:52:03 +00:00
Lovell Fuller
46718102c6 Docs: changelog for #2336 2020-11-16 12:35:19 +00:00
Guillermo Varela
0f473fe3b1 Expose libvips affine operation (#2336) 2020-11-16 12:27:38 +00:00
Nick Jones
2872602c9e Deps: upgrade to prebuild-install 6.0.0 (#2419) 2020-11-16 12:22:35 +00:00
Lovell Fuller
ab653cae33 Docs: use of N-API removes Electron-specific binaries
Clarify Lambda deployment for Windows/macOS users
2020-11-16 12:20:29 +00:00
Nicolas Stepien
e6a035e575 CI: add Node.js 15 (#2415) 2020-10-23 10:01:29 +01:00
Lovell Fuller
fbe48d75dd Release v0.26.2 2020-10-14 17:59:55 +01:00
Lovell Fuller
20ba0f49dd Changelog entry and doc refresh for #2397 2020-10-08 10:05:39 +01:00
beig
c213e9878d Add centre/center option to tile-based output (#2397) 2020-10-08 09:51:54 +01:00
Lovell Fuller
9704ca4c18 Add support for libvips compiled with OpenEXR #698 2020-10-07 15:12:06 +01:00
Lovell Fuller
49dce6219e Docs: changelog entry for #2379 2020-10-07 14:43:13 +01:00
Adam Coster
260ff6c94f Docs: clarify response object description for trim (#2400) 2020-10-06 17:08:32 +01:00
Adam Lovatt
3ec281d104 Ensure support for yarn v2 (#2379) 2020-09-23 21:41:14 +01:00
Lovell Fuller
c4c43d525b Release v0.26.1 2020-09-20 09:29:50 +01:00
Lovell Fuller
6c5cde363a Ensure animated GIF output is optimised #2376 2020-09-19 20:53:15 +01:00
Lovell Fuller
d46b4d950f Allow spaces in installation directory #2279
Uses new include_dir property of node-addon-api
2020-09-19 14:26:30 +01:00
Lovell Fuller
b369c4bb8a Ensure stats can be calculated for 1x1 input #2372 2020-09-17 11:08:52 +01:00
Lovell Fuller
c3898487c4 CI: name the FreeBSD task 2020-09-17 11:02:16 +01:00
Lovell Fuller
ca3c5b400f CI: install ARM64 nodejs package with dependency on python3-minimal
Node.js 14 will work but 10 and 12 will fail until
https://github.com/nodesource/distributions/issues/1100 is fixed
2020-09-15 19:21:32 +01:00
Lovell Fuller
97772b176c Docs: changelog entry for #2369 2020-09-15 18:05:22 +01:00
AcrylicShrimp
08a2965f1c Ensure animation-related properties work with stream-based input 2020-09-15 15:12:26 +01:00
Lovell Fuller
76dcddfa3d Changelog, credit, doc update for #2348 2020-09-02 09:06:15 +01:00
stefanprobst
79f476ae4d Allow input density range up to 100000 DPI (#2348) 2020-09-02 08:56:12 +01:00
Lovell Fuller
d406cb619c Improve error messaging for unsupported Node.js versions 2020-08-28 21:39:11 +01:00
Lovell Fuller
4f3890f1e4 Issue template: ask about installation architecture and platform 2020-08-28 13:59:44 +01:00
Lovell Fuller
7a9d58cc51 Docs: changelog entry for #2343 2020-08-28 13:56:35 +01:00
Lovell Fuller
eef87da0e1 Docs: CSP allow inline JS 2020-08-28 13:53:19 +01:00
Denis Soldatov
00e65f6f14 Ensure correct pageHeight when verifying image dimensions (#2343) 2020-08-28 13:39:19 +01:00
Lovell Fuller
866e9824d1 Release v0.26.0 2020-08-25 18:42:06 +01:00
Lovell Fuller
482e6078e2 Tests: Update leak suppressions for librsvg static data 2020-08-25 18:40:12 +01:00
Lovell Fuller
bc7ab296ef Tests: Update leak suppressions for static libs 2020-08-24 21:10:20 +01:00
Lovell Fuller
a5f4f53b56 Tests: remove no-longer-used conditional assertions 2020-08-23 20:11:23 +01:00
Lovell Fuller
b1227f526d Verify minimum libvips version at compile time 2020-08-23 20:10:27 +01:00
Lovell Fuller
78b42c8306 Docs: update performance test results 2020-08-23 16:48:46 +01:00
Lovell Fuller
4b6d45ab8e Prerelease v0.26.0-beta1 2020-08-23 15:56:25 +01:00
Lovell Fuller
7980341923 Upgrade libvips to v8.10.0 2020-08-23 15:36:03 +01:00
Lovell Fuller
3917efdebd Benchmarks: ensure PNG tests use consistent settings 2020-08-21 21:10:27 +01:00
Robert O'Rourke
eaecb7347b Add support to withMetadata for custom ICC profile #2271 2020-08-19 21:32:15 +01:00
Lovell Fuller
05ca7d3129 CI: further attempts to get Windows to play nicely 2020-08-18 20:53:18 +01:00
Lovell Fuller
4beae0de71 Add 'animated' constructor property as shortcut for 'pages'
Provides easier-to-understand API when handling animated images
2020-08-18 20:28:35 +01:00
Lovell Fuller
b711661784 CI: improve cross-platform (i.e. Windows) process spawning 2020-08-18 17:11:42 +01:00
Lovell Fuller
0c4a45b1f4 CI: workaround Appveyor ignoring v-prefixed tag names 2020-08-18 14:31:33 +01:00
Lovell Fuller
ec2beb0039 Prerelease v0.26.0-alpha2 2020-08-18 14:01:19 +01:00
Lovell Fuller
cafb3e8bac CI: ensure git tag is available inside prebuild containers 2020-08-18 14:00:14 +01:00
Lovell Fuller
e896d5e920 Prerelease v0.26.0-alpha1 2020-08-18 09:39:09 +01:00
Lovell Fuller
42d4228595 Replace prebuild-ci with simple wrapper around prebuild
Drop Node.js 13, generate prebuilds for NAPI v3 only
2020-08-18 09:37:57 +01:00
Lovell Fuller
341ea3e4ea Doc refresh and changelog entry for #2012 2020-08-17 16:20:10 +01:00
Tomáš Szabo
cb1baede87 Add support for animated WebP and GIF (via magick) (#2012) 2020-08-17 14:48:38 +01:00
diegodev3
45653ca2e7 Ensure prebuilt binaries for ARM default to v7 when using Electron 2020-08-14 17:27:14 +01:00
Lovell Fuller
77c861b74b Docs: ensure unreleased changes included in next release 2020-08-14 15:03:47 +01:00
Lovell Fuller
b586c2146f Ensure node-addon-api include path is array context
Need to revert to scalar context after node-addon-api v3.0.2
2020-08-14 12:39:58 +01:00
Lovell Fuller
4d42bed4f8 Upgrade to libvips v8.10.0-rc4 2020-08-14 10:33:37 +01:00
Lovell Fuller
ea54c119ab Bump deps 2020-08-14 08:20:35 +01:00
Lovell Fuller
d95b124771 CI: ensure ARM64 Node.js 10 is installed 2020-08-11 10:09:21 +01:00
Lovell Fuller
61ccf09e6f CI: ensure ARM64 Node.js 10 is installed from specific repo 2020-08-11 09:37:19 +01:00
Lovell Fuller
8da30f0b41 Map libvips file loader nicknames to sharp image types
Use stable API rather than internal class names, reduces complexity
2020-08-09 09:39:27 +01:00
Lovell Fuller
90a57e7664 Upgrade to libvips 8.10.0-rc1 2020-07-17 14:46:53 +01:00
Lovell Fuller
c42de19d2a Add most dominant colour to image stats #640 2020-07-15 21:57:37 +01:00
Lovell Fuller
dcc42f8514 Ensure electron-builder and electron-rebuild use N-API v3 prebuilds 2020-07-15 09:05:53 +01:00
Lovell Fuller
3150fad909 Upgrade to libvips 8.10.0-beta2 2020-07-14 19:45:25 +01:00
Lovell Fuller
ba17db3ab3 Switch to Brotli-compressed binaries, requires Node.js 10.16.0+ 2020-06-29 20:53:58 +01:00
Kleis Auke Wolthuizen
7c1c48327e Add support for statically-linked dependencies (lovell/sharp-libvips#39) (#2223) 2020-06-28 21:17:15 +01:00
Lovell Fuller
85459e0ec6 macOS: remove linker flag ignored by clang 2020-06-27 11:55:10 +01:00
Lovell Fuller
13bdb68366 CI: pin Windows x86 Node.js 14 to 14.2.0
This is the least unstable version on the 14 release line.
Windows x86 users should probably delay upgrading to Node.js 14.
2020-06-27 11:42:00 +01:00
Lovell Fuller
c91373fba3 Docs: add section about worker threads 2020-06-18 13:42:04 +01:00
Lovell Fuller
e8149f5295 Docs: fix markdown in changelog 2020-06-16 19:01:36 +01:00
Lovell Fuller
ab015ef90f Avoid copy in metadata levels for loop 2020-06-16 18:59:20 +01:00
Lovell Fuller
fdfc89e577 Docs: add content security policy 2020-06-16 18:41:18 +01:00
Lovell Fuller
e647e1f7bd Add changelog entry and credit for #2258 #2259 2020-06-16 18:39:58 +01:00
Jerome Vouillon
a05d735e31 Allow multi-page input via ImageMagic (#2259) 2020-06-15 13:01:53 +01:00
Jerome Vouillon
4470048ba4 Add support for libvips ImageMagick v7 loaders (#2258) 2020-06-15 12:59:20 +01:00
Lovell Fuller
0f58f956a7 Docs: fix external CSS 2020-06-12 17:07:05 +01:00
Lovell Fuller
20284757a6 Docs: fix binary mirror example 2020-06-12 17:06:46 +01:00
Lovell Fuller
df6efa0285 Release v0.25.4 2020-06-12 14:17:23 +01:00
Lovell Fuller
3c10e118e3 Docs: ensure presence of constructor description 2020-06-12 13:52:19 +01:00
Lovell Fuller
19980190f7 Emit 'warning' event for non-critical problems #2032 2020-06-12 13:46:12 +01:00
Lovell Fuller
8f5495a446 Add experimental sharpness calc to stats #2251 2020-06-12 11:25:57 +01:00
Mikhail Bodrov
9431029917 Simplify type checking of plainObject, buffer (#2252) 2020-06-10 20:54:01 +01:00
Lovell Fuller
17ea70a102 Add named 'alpha' channel to extractChannel op #2138 2020-06-07 10:43:43 +01:00
Lovell Fuller
7f142bddb3 Add level constructor opt for multi-level input #2222 2020-06-06 16:10:56 +01:00
Lovell Fuller
98e0516ac1 Tests: latest libvips sets JPEG density 2020-06-06 16:09:52 +01:00
Mikhail Bodrov
7717516d1d Simplify is helpers (#2244) 2020-06-05 09:48:26 +01:00
Lovell Fuller
760550ca0d Expose levels metadata for multi-level images #2222 2020-06-04 20:29:02 +01:00
Lovell Fuller
f8144dd89c Bump dependencies 2020-06-04 20:17:15 +01:00
Lovell Fuller
ac4070cb49 Tests: skip empty Buffer test when *magick supported 2020-06-04 20:07:30 +01:00
Lovell Fuller
25b964e3df Docs: changelog and credit update for #2226 2020-05-28 22:42:37 +01:00
Lovell Fuller
17ec6c72df Docs: inline small external docute CSS/JS 2020-05-28 22:41:24 +01:00
Roman Malieiev
a7b1185602 Enable PNG palette when at least one of quality, colours, colors or dither is set (#2226) 2020-05-28 22:21:33 +01:00
Lovell Fuller
c76ae06fd1 Attempt to reduce file size of binaries at link time 2020-05-22 19:47:19 +01:00
Lovell Fuller
b3dd54d550 Changelog and doc update for #2217 2020-05-22 16:48:27 +01:00
malice00
d248eadb06 Allow libvips binary location override with version appended (#2217) 2020-05-22 16:07:32 +01:00
Lovell Fuller
507eef3053 Docs: reorder/highlight optional output properties 2020-05-19 18:55:33 +01:00
Lovell Fuller
fc2f672337 Improve error messaging when platform mismatch detected 2020-05-19 18:53:39 +01:00
Lovell Fuller
6f49be8f26 Docs: small perf boost by prefetching image DNS 2020-05-19 18:50:18 +01:00
Lovell Fuller
dad7f1e1f6 Release v0.25.3 2020-05-17 14:21:39 +01:00
Lovell Fuller
c3a9384dcf Upgrade deps that have dropped Node.js 8 support 2020-05-17 13:29:55 +01:00
Lovell Fuller
0ee08bfe46 Docs: lint JSDoc annotations when building docs 2020-05-15 14:07:27 +01:00
Lovell Fuller
0872f495f9 Tests: temporarily skip known failure on FreeBSD 2020-05-15 12:17:45 +01:00
Lovell Fuller
33ac8b93a9 CI: allow FreeBSD to update to latest packages 2020-05-13 16:12:17 +01:00
Lovell Fuller
bbff1c222d Docs: clarify description of withMetadata
Default and non-default behaviour was mixed in the same paragraph,
leading to confusion about when an ICC profile might be expected.
2020-05-13 16:01:03 +01:00
Lovell Fuller
b534f99870 Docs: correct typo in composite 2020-05-13 15:56:44 +01:00
Lovell Fuller
86acf2460e Allow parsing of SVG attribute values >10MB #2195 2020-05-06 20:56:08 +01:00
NickNaso
40c00035ee Docs: add N-API badge 2020-05-01 11:53:10 +01:00
Lovell Fuller
4673abae7d Docs: serve logo via CDN 2020-05-01 11:41:38 +01:00
Lovell Fuller
ef8a705957 Docs: move npm install command to examples 2020-05-01 11:38:09 +01:00
Lovell Fuller
48255fa009 Docs: serve logo via CDN 2020-05-01 11:27:00 +01:00
Lovell Fuller
ed6269bc9c Bump dependencies 2020-05-01 11:26:05 +01:00
Lovell Fuller
16b8d9afe5 Docs: changelog entry and credit for #2188 2020-05-01 11:25:43 +01:00
Dumitru
f29fcb1f73 Ensure npm platform flag is respected when copying DLLs 2020-04-29 18:24:27 +01:00
Lovell Fuller
b36ad25030 CI: Upgrade FreeBSD to version 13
It currently provides only vips v8.8.3 so will (still) fail.
2020-04-23 21:39:45 +01:00
Lovell Fuller
edf3fe24c9 CI: add Node.js 14 2020-04-23 21:30:04 +01:00
Lovell Fuller
5a1bdf0eb1 Bump deps: semver, documentation, nyc 2020-04-23 20:51:12 +01:00
Lovell Fuller
d8e24d7e76 Docs: slightly reduce whitespace before headings, lists 2020-04-23 20:47:10 +01:00
Floris de Bijl
786c5330e9 docs: change 'colourspace' to 'toColourspace' (#2174) 2020-04-21 09:44:22 +01:00
Lovell Fuller
70e730bb67 Docs: add basic search feature for install and API 2020-04-14 21:26:41 +01:00
Lovell Fuller
1d6ef630a5 Docs: extract might also be referred to as 'crop' 2020-04-14 21:24:26 +01:00
Vincent Voyer
1eedb22ef5 docs(clone): add promise example 2020-04-08 09:45:04 +01:00
Lovell Fuller
6ed1f49ad3 Ensure libvips is initialised only once #2143 2020-03-29 19:44:21 +01:00
Lovell Fuller
ecd01afad3 Docs: clarify JPEG quality/chromaSubsampling interdependence 2020-03-26 20:22:47 +00:00
Lovell Fuller
8bd8709f2b Release v0.25.2 2020-03-20 19:20:34 +00:00
Lovell Fuller
e82a585cec Ensure AsyncWorker options are persisted #2130 2020-03-19 21:21:21 +00:00
Lovell Fuller
c1d4a68558 Add changelog entry, docs and credit for #2098 2020-03-18 13:54:41 +00:00
Edward Silverton
eff36dc09f Add IIIF layout support to tile-based output (#2098) 2020-03-18 13:27:54 +00:00
Lovell Fuller
031a58f817 Docs: AWS Lambda cross-platform no longer requires target flag 2020-03-17 18:33:49 +00:00
Lovell Fuller
cf39fc4fb1 Simplify tiled layout JPEG file extension logic 2020-03-14 22:08:46 +00:00
Lovell Fuller
c9bff94e17 Docs: expand descriptions of resize cover/contain 2020-03-14 21:34:48 +00:00
Lovell Fuller
e78e919925 Clarify diff between install vs bug issue templates 2020-03-10 21:36:48 +00:00
Lovell Fuller
76bb25262e Docs: ARM64/RHEL7 prebuilt binaries changelog entry 2020-03-10 21:22:30 +00:00
Lovell Fuller
d8426b1ed5 CI: Switch Linux x64 to CentOS 7 for glibc 2.17, gcc 4.8.5
Expand Linux ARM64v8 to all supported versions of Node.js
2020-03-10 19:39:26 +00:00
Lovell Fuller
4894c10dd9 Ensure consistent, correct detection of input opts #2118 2020-03-08 14:32:17 +00:00
119 changed files with 5036 additions and 1608 deletions

View File

@@ -1,13 +1,15 @@
freebsd_instance: freebsd_instance:
image_family: freebsd-12-0 image_family: freebsd-13-0-snap
task: task:
name: FreeBSD 13.0
env:
IGNORE_OSVERSION: yes
prerequisites_script: prerequisites_script:
- sed -i '' 's/quarterly/latest/g' /etc/pkg/FreeBSD.conf
- pkg update -f - pkg update -f
- pkg upgrade -y - pkg upgrade -y
- pkg install -y pkgconf vips libnghttp2 node npm - pkg install -y pkgconf vips node npm
install_script: install_script:
- npm install --unsafe-perm - npm install --build-from-source --unsafe-perm
test_script: test_script:
- npm test - npm test

View File

@@ -27,7 +27,7 @@ Please select the `master` branch as the destination for your Pull Request so yo
Please squash your changes into a single commit using a command like `git rebase -i upstream/master`. Please squash your changes into a single commit using a command like `git rebase -i upstream/master`.
To test C++ changes, you can compile the module using `npm install` and then run the tests using `npm test`. To test C++ changes, you can compile the module using `npm install --build-from-source` and then run the tests using `npm test`.
## Submit a Pull Request with a new feature ## Submit a Pull Request with a new feature
@@ -42,10 +42,6 @@ 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. 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.26.0 | zoom |
Please squash your changes into a single commit using a command like `git rebase -i upstream/<wip-branch>`. Please squash your changes into a single commit using a command like `git rebase -i upstream/<wip-branch>`.
### Add a new public method ### Add a new public method

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View 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

View File

@@ -1,17 +1,19 @@
--- ---
name: Installation name: Installation
about: Something went wrong **installing** sharp about: Something went wrong during either 'npm install sharp' or 'require("sharp")'
labels: installation labels: installation
--- ---
Did you see the [documentation relating to installation](https://sharp.pixelplumbing.com/install)? Did you see the [documentation relating to installation](https://sharp.pixelplumbing.com/install)?
Have you ensured the platform and version of Node.js used for `npm install` is the same as the platform and version of Node.js used at runtime? 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`? 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 npm v6 or earlier and installing as a `root` or `sudo` user, have you tried with the `npm install --unsafe-perm` flag?
If you are using npm v7, does the user running `npm install` own the directory it is run in?
If you are using the `ignore-scripts` feature of `npm`, have you tried with the `npm install --ignore-scripts=false` flag? If you are using the `ignore-scripts` feature of `npm`, have you tried with the `npm install --ignore-scripts=false` flag?

View File

@@ -1,6 +1,6 @@
--- ---
name: Possible bug name: Possible bug
about: Something unexpected occurred **using** sharp about: Installation of sharp was successful but then something unexpected occurred using one of its features
labels: triage labels: triage
--- ---

87
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,87 @@
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:14-alpine3.13
- 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

View File

@@ -1,75 +1,108 @@
matrix: jobs:
include: include:
- name: "Linux (glibc 2.17+) - Node.js 10" - name: "Linux ARM64v8 (Debian 11, glibc 2.29) - Node.js 10"
os: linux
dist: xenial
language: node_js
node_js: "10"
- name: "Linux (glibc 2.17+) - Node.js 12"
os: linux
dist: xenial
language: node_js
node_js: "12"
- name: "Linux (glibc 2.17+) - Node.js 13"
os: linux
dist: xenial
language: node_js
node_js: "13"
after_success:
- npm install coveralls
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
- name: "Linux (musl 1.1.20+) - Node.js 10"
os: linux
dist: bionic
language: minimal
before_install:
- sudo docker run -dit --name sharp --env CI --env PREBUILD_TOKEN --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:10.17.0-alpine3.9 # https://github.com/nodejs/docker-node/issues/1158
- sudo docker exec sharp apk add build-base git python2 --update-cache
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
- name: "Linux (musl 1.1.20+) - Node.js 12"
os: linux
dist: bionic
language: minimal
before_install:
- sudo docker run -dit --name sharp --env CI --env PREBUILD_TOKEN --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:12.0-alpine
- sudo docker exec sharp apk add build-base git python2 --update-cache
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
- name: "Linux (musl 1.1.20+) - Node.js 13"
os: linux
dist: bionic
language: minimal
before_install:
- sudo docker run -dit --name sharp --env CI --env PREBUILD_TOKEN --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:13.0-alpine
- sudo docker exec sharp apk add build-base git python2 --update-cache
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
- name: "Linux ARM64v8 (glibc 2.29+) - Node.js 10"
arch: arm64 arch: arm64
os: linux os: linux
dist: bionic dist: bionic
language: minimal language: shell
before_install: before_install:
- sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp arm64v8/debian:bullseye - sudo docker run -dit --name sharp --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp arm64v8/debian:bullseye
- sudo docker exec sharp apt-get update - sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python3 curl"
- sudo docker exec sharp apt-get install -y build-essential git python2 nodejs npm - sudo docker exec sharp sh -c "curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -"
install: sudo docker exec sharp sh -c "npm install --unsafe-perm" - 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" script: sudo docker exec sharp sh -c "npm test"
- name: "macOS (10.13+) - Node.js 10" after_success: "[[ -n $TRAVIS_TAG ]] && sudo docker exec --env prebuild_upload sharp sh -c \"npx prebuild --runtime napi --target 3\""
os: osx
osx_image: xcode10.1 - name: "Linux ARM64v8 (Debian 11, glibc 2.29) - Node.js 12"
language: node_js arch: arm64
node_js: "10" os: linux
- name: "macOS (10.13+) - Node.js 12" dist: bionic
os: osx language: shell
osx_image: xcode10.1 before_install:
language: node_js - sudo docker run -dit --name sharp --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp arm64v8/debian:bullseye
node_js: "12" - sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python3 curl"
- name: "macOS (10.13+) - Node.js 13" - sudo docker exec sharp sh -c "curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -"
os: osx - sudo docker exec sharp sh -c "echo 'deb https://deb.nodesource.com/node_12.x sid main' >/etc/apt/sources.list.d/nodesource.list"
osx_image: xcode10.1 - sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs"
language: node_js install: sudo docker exec sharp sh -c "npm install --build-from-source --unsafe-perm"
node_js: "13" 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"
- name: "Linux ARM64v8 (Alpine 3.11, musl 1.1.24) - Node.js 10"
arch: arm64
os: linux
dist: focal
language: shell
before_install:
- sudo docker run -dit --name sharp --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:10-alpine3.11
- sudo docker exec sharp sh -c "apk add build-base git python3 --update-cache"
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 (Alpine 3.11, musl 1.1.24) - Node.js 12"
arch: arm64
os: linux
dist: focal
language: shell
before_install:
- sudo docker run -dit --name sharp --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:12-alpine3.11
- sudo docker exec sharp sh -c "apk add build-base git python3 --update-cache"
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 (Alpine 3.11, musl 1.1.24) - Node.js 14"
arch: arm64
os: linux
dist: focal
language: shell
before_install:
- sudo docker run -dit --name sharp --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:14-alpine3.11
- sudo docker exec sharp sh -c "apk add build-base git python3 --update-cache"
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 (Alpine 3.11, musl 1.1.24) - Node.js 15"
arch: arm64
os: linux
dist: focal
language: shell
before_install:
- sudo chown 0.0 ${PWD}
- sudo docker run -dit --name sharp --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:15-alpine3.11
- sudo docker exec sharp sh -c "apk add build-base git python3 --update-cache"
install: sudo docker exec sharp sh -c "npm install --build-from-source --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
cache: cache:
npm: false npm: false

View File

@@ -1,14 +1,10 @@
# sharp # sharp
<img src="https://raw.githubusercontent.com/lovell/sharp/master/docs/image/sharp-logo.svg?sanitize=true" width="160" height="160" alt="sharp logo" align="right"> <img src="https://cdn.jsdelivr.net/gh/lovell/sharp@master/docs/image/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
```sh
npm install sharp
```
The typical use case for this high speed Node.js module The typical use case for this high speed Node.js module
is to convert large images in common formats to is to convert large images in common formats to
smaller, web-friendly JPEG, PNG and WebP images of varying dimensions. smaller, web-friendly JPEG, PNG, WebP and AVIF images of varying dimensions.
Resizing an image is typically 4x-5x faster than using the Resizing an image is typically 4x-5x faster than using the
quickest ImageMagick and GraphicsMagick settings quickest ImageMagick and GraphicsMagick settings
@@ -25,6 +21,10 @@ do not require any additional install or runtime dependencies.
## Examples ## Examples
```sh
npm install sharp
```
```javascript ```javascript
const sharp = require('sharp'); const sharp = require('sharp');
``` ```
@@ -85,6 +85,7 @@ readableStream
``` ```
[![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.svg?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master) [![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.svg?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master)
[![N-API v3](https://img.shields.io/badge/N--API-v3-green.svg)](https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api_version_matrix)
### Documentation ### Documentation
@@ -101,7 +102,7 @@ covers reporting bugs, requesting features and submitting code changes.
### Licensing ### Licensing
Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors. Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Lovell Fuller and contributors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@@ -1,22 +1,18 @@
os: Visual Studio 2017 os: Visual Studio 2019
version: "{build}" version: "{build}"
build: off build: off
platform: x86
environment: environment:
matrix: matrix:
- nodejs_version: "10" - nodejs_version: "10"
platform: x86 prebuild: true
- nodejs_version: "10"
platform: x64
- nodejs_version: "12" - nodejs_version: "12"
platform: x86 - nodejs_version: "14"
- nodejs_version: "12" - nodejs_version: "15"
platform: x64
- nodejs_version: "13"
platform: x86
- nodejs_version: "13"
platform: x64
install: install:
- ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) $env:platform - ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version)
- npm install - npm install --build-from-source
test_script: test_script:
- npm test - npm test
on_success:
- if [%prebuild%] == [true] if [%APPVEYOR_REPO_TAG%] == [true] npx prebuild --runtime napi --target 3

View File

@@ -1,4 +1,8 @@
{ {
'variables': {
'vips_version': '<!(node -p "require(\'./lib/libvips\').minimumLibvipsVersion")',
'sharp_vendor_dir': '<(module_root_dir)/vendor/<(vips_version)'
},
'targets': [{ 'targets': [{
'target_name': 'libvips-cpp', 'target_name': 'libvips-cpp',
'conditions': [ 'conditions': [
@@ -16,23 +20,38 @@
'src/libvips/cplusplus/VImage.cpp' 'src/libvips/cplusplus/VImage.cpp'
], ],
'include_dirs': [ 'include_dirs': [
'vendor/include', '<(sharp_vendor_dir)/include',
'vendor/include/glib-2.0', '<(sharp_vendor_dir)/include/glib-2.0',
'vendor/lib/glib-2.0/include' '<(sharp_vendor_dir)/lib/glib-2.0/include'
],
'libraries': [
'../vendor/lib/libvips.lib',
'../vendor/lib/libglib-2.0.lib',
'../vendor/lib/libgobject-2.0.lib'
], ],
'link_settings': {
'library_dirs': ['<(sharp_vendor_dir)/lib'],
'libraries': [
'libvips.lib',
'libglib-2.0.lib',
'libgobject-2.0.lib'
],
},
'configurations': { 'configurations': {
'Release': { 'Release': {
'msvs_settings': { 'msvs_settings': {
'VCCLCompilerTool': { 'VCCLCompilerTool': {
'ExceptionHandling': 1 'ExceptionHandling': 1,
'WholeProgramOptimization': 'true'
},
'VCLibrarianTool': {
'AdditionalOptions': [
'/LTCG:INCREMENTAL'
]
}, },
'VCLinkerTool': { 'VCLinkerTool': {
'ImageHasSafeExceptionHandlers': 'false' 'ImageHasSafeExceptionHandlers': 'false',
'OptimizeReferences': 2,
'EnableCOMDATFolding': 2,
'LinkIncremental': 1,
'AdditionalOptions': [
'/LTCG:INCREMENTAL'
]
} }
}, },
'msvs_disabled_warnings': [ 'msvs_disabled_warnings': [
@@ -58,8 +77,8 @@
'runtime_link%': 'shared', 'runtime_link%': 'shared',
'conditions': [ 'conditions': [
['OS != "win"', { ['OS != "win"', {
'pkg_config_path': '<!(node -e "console.log(require(\'./lib/libvips\').pkgConfigPath())")', 'pkg_config_path': '<!(node -p "require(\'./lib/libvips\').pkgConfigPath()")',
'use_global_libvips': '<!(node -e "console.log(Boolean(require(\'./lib/libvips\').useGlobalLibvips()).toString())")' 'use_global_libvips': '<!(node -p "Boolean(require(\'./lib/libvips\').useGlobalLibvips()).toString()")'
}, { }, {
'pkg_config_path': '', 'pkg_config_path': '',
'use_global_libvips': '' 'use_global_libvips': ''
@@ -76,7 +95,7 @@
'src/sharp.cc' 'src/sharp.cc'
], ],
'include_dirs': [ 'include_dirs': [
'<!@(node -p "require(\'node-addon-api\').include")', '<!(node -p "require(\'node-addon-api\').include_dir")',
], ],
'conditions': [ 'conditions': [
['use_global_libvips == "true"', { ['use_global_libvips == "true"', {
@@ -98,9 +117,9 @@
}, { }, {
# Use pre-built libvips stored locally within node_modules # Use pre-built libvips stored locally within node_modules
'include_dirs': [ 'include_dirs': [
'vendor/include', '<(sharp_vendor_dir)/include',
'vendor/include/glib-2.0', '<(sharp_vendor_dir)/include/glib-2.0',
'vendor/lib/glib-2.0/include' '<(sharp_vendor_dir)/lib/glib-2.0/include'
], ],
'conditions': [ 'conditions': [
['OS == "win"', { ['OS == "win"', {
@@ -108,64 +127,43 @@
'_ALLOW_KEYWORD_MACROS', '_ALLOW_KEYWORD_MACROS',
'_FILE_OFFSET_BITS=64' '_FILE_OFFSET_BITS=64'
], ],
'libraries': [ 'link_settings': {
'../vendor/lib/libvips.lib', 'library_dirs': ['<(sharp_vendor_dir)/lib'],
'../vendor/lib/libglib-2.0.lib', 'libraries': [
'../vendor/lib/libgobject-2.0.lib' 'libvips.lib',
] 'libglib-2.0.lib',
'libgobject-2.0.lib'
]
}
}], }],
['OS == "mac"', { ['OS == "mac"', {
'libraries': [ 'link_settings': {
'../vendor/lib/libvips-cpp.42.dylib', 'library_dirs': ['<(sharp_vendor_dir)/lib'],
'../vendor/lib/libvips.42.dylib', 'libraries': [
'../vendor/lib/libglib-2.0.0.dylib', 'libvips-cpp.42.dylib'
'../vendor/lib/libgobject-2.0.0.dylib', ]
# Ensure runtime linking is relative to sharp.node },
'-rpath \'@loader_path/../../vendor/lib\'' 'xcode_settings': {
] 'OTHER_LDFLAGS': [
# Ensure runtime linking is relative to sharp.node
'-Wl,-rpath,\'@loader_path/../../vendor/<(vips_version)/lib\''
]
}
}], }],
['OS == "linux"', { ['OS == "linux"', {
'defines': [ 'defines': [
'_GLIBCXX_USE_CXX11_ABI=0' '_GLIBCXX_USE_CXX11_ABI=1'
], ],
'libraries': [ 'link_settings': {
'../vendor/lib/libvips-cpp.so', 'library_dirs': ['<(sharp_vendor_dir)/lib'],
'../vendor/lib/libvips.so', 'libraries': [
'../vendor/lib/libglib-2.0.so', '-l:libvips-cpp.so.42'
'../vendor/lib/libgobject-2.0.so', ],
# Dependencies of dependencies, included for openSUSE support 'ldflags': [
'../vendor/lib/libcairo.so', # Ensure runtime linking is relative to sharp.node
'../vendor/lib/libexif.so', '-Wl,-s -Wl,--disable-new-dtags -Wl,-rpath=\'$$ORIGIN/../../vendor/<(vips_version)/lib\''
'../vendor/lib/libexpat.so', ]
'../vendor/lib/libffi.so', }
'../vendor/lib/libfontconfig.so',
'../vendor/lib/libfreetype.so',
'../vendor/lib/libfribidi.so',
'../vendor/lib/libgdk_pixbuf-2.0.so',
'../vendor/lib/libgif.so',
'../vendor/lib/libgio-2.0.so',
'../vendor/lib/libgmodule-2.0.so',
'../vendor/lib/libgsf-1.so',
'../vendor/lib/libgthread-2.0.so',
'../vendor/lib/libharfbuzz.so',
'../vendor/lib/libjpeg.so',
'../vendor/lib/liblcms2.so',
'../vendor/lib/liborc-0.4.so',
'../vendor/lib/libpango-1.0.so',
'../vendor/lib/libpangocairo-1.0.so',
'../vendor/lib/libpangoft2-1.0.so',
'../vendor/lib/libpixman-1.so',
'../vendor/lib/libpng.so',
'../vendor/lib/librsvg-2.so',
'../vendor/lib/libtiff.so',
'../vendor/lib/libwebp.so',
'../vendor/lib/libwebpdemux.so',
'../vendor/lib/libwebpmux.so',
'../vendor/lib/libxml2.so',
'../vendor/lib/libz.so',
# Ensure runtime linking is relative to sharp.node
'-Wl,--disable-new-dtags -Wl,-rpath=\'$${ORIGIN}/../../vendor/lib\''
]
}] }]
] ]
}] }]
@@ -178,8 +176,7 @@
], ],
'xcode_settings': { 'xcode_settings': {
'CLANG_CXX_LANGUAGE_STANDARD': 'c++11', 'CLANG_CXX_LANGUAGE_STANDARD': 'c++11',
'CLANG_CXX_LIBRARY': 'libc++', 'MACOSX_DEPLOYMENT_TARGET': '10.9',
'MACOSX_DEPLOYMENT_TARGET': '10.7',
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
'GCC_ENABLE_CPP_RTTI': 'YES', 'GCC_ENABLE_CPP_RTTI': 'YES',
'OTHER_CPLUSPLUSFLAGS': [ 'OTHER_CPLUSPLUSFLAGS': [
@@ -204,10 +201,22 @@
['OS == "win"', { ['OS == "win"', {
'msvs_settings': { 'msvs_settings': {
'VCCLCompilerTool': { 'VCCLCompilerTool': {
'ExceptionHandling': 1 'ExceptionHandling': 1,
'WholeProgramOptimization': 'true'
},
'VCLibrarianTool': {
'AdditionalOptions': [
'/LTCG:INCREMENTAL'
]
}, },
'VCLinkerTool': { 'VCLinkerTool': {
'ImageHasSafeExceptionHandlers': 'false' 'ImageHasSafeExceptionHandlers': 'false',
'OptimizeReferences': 2,
'EnableCOMDATFolding': 2,
'LinkIncremental': 1,
'AdditionalOptions': [
'/LTCG:INCREMENTAL'
]
} }
}, },
'msvs_disabled_warnings': [ 'msvs_disabled_warnings': [

View File

@@ -1,10 +1,10 @@
# sharp # sharp
<img src="https://raw.githubusercontent.com/lovell/sharp/master/docs/image/sharp-logo.svg?sanitize=true" width="160" height="160" alt="sharp logo" align="right"> <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 The typical use case for this high speed Node.js module
is to convert large images in common formats to is to convert large images in common formats to
smaller, web-friendly JPEG, PNG and WebP images of varying dimensions. smaller, web-friendly JPEG, PNG, AVIF and WebP images of varying dimensions.
Resizing an image is typically 4x-5x faster than using the Resizing an image is typically 4x-5x faster than using the
quickest ImageMagick and GraphicsMagick settings quickest ImageMagick and GraphicsMagick settings
@@ -21,9 +21,9 @@ do not require any additional install or runtime dependencies.
### Formats ### Formats
This module supports reading JPEG, PNG, WebP, TIFF, GIF and SVG images. This module supports reading JPEG, PNG, WebP, AVIF, TIFF, GIF and SVG images.
Output images can be in JPEG, PNG, WebP and TIFF formats as well as uncompressed raw pixel data. 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. Streams, Buffer objects and the filesystem can be used for input and output.
@@ -66,7 +66,7 @@ covers reporting bugs, requesting features and submitting code changes.
### Licensing ### Licensing
Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors. Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Lovell Fuller and contributors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@@ -42,7 +42,7 @@ Extract a single channel from a multi-channel image.
### Parameters ### Parameters
- `channel` **([Number][1] \| [String][2])** zero-indexed band number to extract, or `red`, `green` or `blue` as alternative to `0`, `1` or `2` respectively. - `channel` **([number][1] \| [string][2])** zero-indexed channel/band number to extract, or `red`, `green`, `blue` or `alpha`.
### Examples ### Examples
@@ -70,12 +70,12 @@ Channel ordering follows vips convention:
- sRGB: 0: Red, 1: Green, 2: Blue, 3: Alpha. - sRGB: 0: Red, 1: Green, 2: Blue, 3: Alpha.
- CMYK: 0: Magenta, 1: Cyan, 2: Yellow, 3: Black, 4: 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. Buffers may be any of the image formats supported by sharp.
For raw pixel input, the `options` object should contain a `raw` attribute, which follows the format of the attribute of the same name in the `sharp()` constructor. 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 ### Parameters
- `images` **([Array][4]&lt;([String][2] \| [Buffer][5])> | [String][2] \| [Buffer][5])** one or more images (file paths, Buffers). - `images` **([Array][4]&lt;([string][2] \| [Buffer][5])> | [string][2] \| [Buffer][5])** one or more images (file paths, Buffers).
- `options` **[Object][6]** image options, see `sharp()` constructor. - `options` **[Object][6]** image options, see `sharp()` constructor.
@@ -89,7 +89,7 @@ Perform a bitwise boolean operation on all input image channels (bands) to produ
### Parameters ### Parameters
- `boolOp` **[String][2]** one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively. - `boolOp` **[string][2]** one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
### Examples ### Examples

View File

@@ -7,7 +7,7 @@ An alpha channel may be present and will be unchanged by the operation.
### Parameters ### Parameters
- `rgb` **([String][1] \| [Object][2])** parsed by the [color][3] module to extract chroma values. - `rgb` **([string][1] \| [Object][2])** parsed by the [color][3] module to extract chroma values.
- Throws **[Error][4]** Invalid parameter - Throws **[Error][4]** Invalid parameter
@@ -46,8 +46,16 @@ By default output image will be web-friendly sRGB, with additional channels inte
### Parameters ### Parameters
- `colourspace` **[String][1]?** output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...][6] - `colourspace` **[string][1]?** output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...][6]
### Examples
```javascript
// Output 16 bits per pixel RGB
await sharp(input)
.toColourspace('rgb16')
.toFile('16-bpp.png')
```
- Throws **[Error][4]** Invalid parameters - Throws **[Error][4]** Invalid parameters
@@ -59,7 +67,7 @@ Alternative spelling of `toColourspace`.
### Parameters ### Parameters
- `colorspace` **[String][1]?** output colorspace. - `colorspace` **[string][1]?** output colorspace.
- Throws **[Error][4]** Invalid parameters - Throws **[Error][4]** Invalid parameters

View File

@@ -20,7 +20,7 @@ and [https://www.cairographics.org/operators/][2]
### Parameters ### Parameters
- `images` **[Array][3]&lt;[Object][4]>** Ordered list of images to composite - `images` **[Array][3]&lt;[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 bellow) - `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` **[Object][4]?** describes a blank overlay to be created.
- `images[].input.create.width` **[Number][7]?** - `images[].input.create.width` **[Number][7]?**
- `images[].input.create.height` **[Number][7]?** - `images[].input.create.height` **[Number][7]?**

View File

@@ -2,32 +2,47 @@
## Sharp ## Sharp
Constructor factory to create an instance of `sharp`, to which further methods are chained.
JPEG, PNG, WebP, AVIF or TIFF format image data can be streamed out from this object.
When using Stream based output, derived attributes are available from the `info` event.
Non-critical problems encountered during processing are emitted as `warning` events.
Implements the [stream.Duplex][1] class.
### Parameters ### Parameters
- `input` **([Buffer][1] \| [String][2])?** if present, can be - `input` **([Buffer][2] \| [Uint8Array][3] \| [Uint8ClampedArray][4] \| [string][5])?** if present, can be
a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or a Buffer / Uint8Array / Uint8ClampedArray containing JPEG, PNG, WebP, AVIF, 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. a String containing the filesystem path to an JPEG, PNG, WebP, AVIF, 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. JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
- `options` **[Object][3]?** if present, is an Object with optional attributes. - `options` **[Object][6]?** if present, is an Object with optional attributes.
- `options.failOnError` **[Boolean][4]** by default halt processing and raise an error when loading invalid images. - `options.failOnError` **[boolean][7]** 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`) 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][5] \| [Boolean][4])** Do not process input images where the number of pixels - `options.limitInputPixels` **([number][8] \| [boolean][7])** 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. (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`) 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][4]** Set this to `true` to use sequential rather than random access where possible. - `options.sequentialRead` **[boolean][7]** 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`) This can reduce memory usage and might improve performance on some systems. (optional, default `false`)
- `options.density` **[Number][5]** number representing the DPI for vector images. (optional, default `72`) - `options.density` **[number][8]** number representing the DPI for vector images in the range 1 to 100000. (optional, default `72`)
- `options.pages` **[Number][5]** number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages. (optional, default `1`) - `options.pages` **[number][8]** number of pages to extract for multi-page input (GIF, WebP, AVIF, TIFF, PDF), use -1 for all pages. (optional, default `1`)
- `options.page` **[Number][5]** page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based. (optional, default `0`) - `options.page` **[number][8]** page number to start extracting from for multi-page input (GIF, WebP, AVIF, TIFF, PDF), zero based. (optional, default `0`)
- `options.raw` **[Object][3]?** describes raw pixel input image data. See `raw()` for pixel ordering. - `options.level` **[number][8]** level to extract from a multi-level input (OpenSlide), zero based. (optional, default `0`)
- `options.raw.width` **[Number][5]?** - `options.animated` **[boolean][7]** Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`). (optional, default `false`)
- `options.raw.height` **[Number][5]?** - `options.raw` **[Object][6]?** describes raw pixel input image data. See `raw()` for pixel ordering.
- `options.raw.channels` **[Number][5]?** 1-4 - `options.raw.width` **[number][8]?** integral number of pixels wide.
- `options.create` **[Object][3]?** describes a new image to be created. - `options.raw.height` **[number][8]?** integral number of pixels high.
- `options.create.width` **[Number][5]?** - `options.raw.channels` **[number][8]?** integral number of channels, between 1 and 4.
- `options.create.height` **[Number][5]?** - `options.create` **[Object][6]?** describes a new image to be created.
- `options.create.channels` **[Number][5]?** 3-4 - `options.create.width` **[number][8]?** integral number of pixels wide.
- `options.create.background` **([String][2] \| [Object][3])?** parsed by the [color][6] module to extract values for red, green, blue and alpha. - `options.create.height` **[number][8]?** integral number of pixels high.
- `options.create.channels` **[number][8]?** integral number of channels, either 3 (RGB) or 4 (RGBA).
- `options.create.background` **([string][5] \| [Object][6])?** parsed by the [color][9] module to extract values for red, green, blue and alpha.
- `options.create.noise` **[Object][6]?** describes a noise to be created.
- `options.create.noise.type` **[string][5]?** type of generated noise, currently only `gaussian` is supported.
- `options.create.noise.mean` **[number][8]?** mean of pixels in generated noise.
- `options.create.noise.sigma` **[number][8]?** standard deviation of pixels in generated noise.
### Examples ### Examples
@@ -68,9 +83,45 @@ sharp({
.then( ... ); .then( ... );
``` ```
- Throws **[Error][7]** Invalid parameters ```javascript
// Convert an animated GIF to an animated WebP
await sharp('in.gif', { animated: true }).toFile('out.webp');
```
Returns **[Sharp][8]** ```javascript
// Read a raw array of pixels and save it to a png
const input = Uint8Array.from([255, 255, 255, 0, 0, 0]); // or Uint8ClampedArray
const image = sharp(input, {
// because the input does not contain its dimensions or how many channels it has
// we need to specify it in the constructor options
raw: {
width: 2,
height: 1,
channels: 3
}
});
await image.toFile('my-two-pixels.png');
```
```javascript
// Generate RGB Gaussian noise
await sharp({
create: {
width: 300,
height: 200,
channels: 3,
noise: {
type: 'gaussian',
mean: 128,
sigma: 30
}
}
}).toFile('noise.png');
```
- Throws **[Error][10]** Invalid parameters
Returns **[Sharp][11]**
## clone ## clone
@@ -89,20 +140,75 @@ readableStream.pipe(pipeline);
// secondWritableStream receives auto-rotated, extracted region of readableStream // secondWritableStream receives auto-rotated, extracted region of readableStream
``` ```
Returns **[Sharp][8]** ```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
});
[1]: https://nodejs.org/api/buffer.html const promises = [];
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String promises.push(
sharpStream
.clone()
.jpeg({ quality: 100 })
.toFile("originalFile.jpg")
);
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object promises.push(
sharpStream
.clone()
.resize({ width: 500 })
.jpeg({ quality: 80 })
.toFile("optimized-500.jpg")
);
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean promises.push(
sharpStream
.clone()
.resize({ width: 500 })
.webp({ quality: 80 })
.toFile("optimized-500.webp")
);
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number // https://github.com/sindresorhus/got#gotstreamurl-options
got.stream("https://www.example.com/some-file.jpg").pipe(sharpStream);
[6]: https://www.npmjs.org/package/color 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) {}
});
```
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error Returns **[Sharp][11]**
[8]: #sharp [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/Uint8Array
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint8ClampedArray
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[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://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[9]: https://www.npmjs.org/package/color
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
[11]: #sharp

View File

@@ -20,6 +20,7 @@ A `Promise` is returned when `callback` is not provided.
- `loop`: Number of times to loop an animated image, zero refers to a continuous loop. - `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. - `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 - `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 - `hasProfile`: Boolean indicating the presence of an embedded ICC profile
- `hasAlpha`: Boolean indicating the presence of an alpha transparency channel - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
- `orientation`: Number value of the EXIF Orientation header, if present - `orientation`: Number value of the EXIF Orientation header, if present
@@ -70,6 +71,8 @@ A `Promise` is returned when `callback` is not provided.
- `maxY` (y-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. - `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) - `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 ### Parameters
@@ -86,6 +89,11 @@ image
}); });
``` ```
```javascript
const { entropy, sharpness, dominant } = await sharp(input).stats();
const { r, g, b } = dominant;
```
Returns **[Promise][5]&lt;[Object][6]>** Returns **[Promise][5]&lt;[Object][6]>**
[1]: https://libvips.github.io/libvips/API/current/VipsImage.html#VipsInterpretation [1]: https://libvips.github.io/libvips/API/current/VipsImage.html#VipsInterpretation

View File

@@ -21,9 +21,9 @@ for example `rotate(x).extract(y)` will produce a different result to `extract(y
### Parameters ### Parameters
- `angle` **[Number][1]** angle of rotation. (optional, default `auto`) - `angle` **[number][1]** angle of rotation. (optional, default `auto`)
- `options` **[Object][2]?** if present, is an Object with optional attributes. - `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.background` **([string][3] \| [Object][2])** parsed by the [color][4] module to extract values for red, green, blue and alpha. (optional, default `"#000000"`)
### Examples ### Examples
@@ -65,6 +65,61 @@ The use of `flop` implies the removal of the EXIF `Orientation` tag, if any.
Returns **Sharp** 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]&lt;[Array][7]&lt;[number][1]>> | [Array][7]&lt;[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
Sharpen the image. Sharpen the image.
@@ -74,9 +129,9 @@ Separate control over the level of sharpening in "flat" and "jagged" areas is av
### Parameters ### Parameters
- `sigma` **[Number][1]?** the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`. - `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`) - `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`) - `jagged` **[number][1]** the level of sharpening to apply to "jagged" areas. (optional, default `2.0`)
- Throws **[Error][5]** Invalid parameters - Throws **[Error][5]** Invalid parameters
@@ -90,7 +145,7 @@ When used without parameters the default window is 3x3.
### Parameters ### Parameters
- `size` **[Number][1]** square mask size: size x size (optional, default `3`) - `size` **[number][1]** square mask size: size x size (optional, default `3`)
- Throws **[Error][5]** Invalid parameters - Throws **[Error][5]** Invalid parameters
@@ -105,7 +160,7 @@ When a `sigma` is provided, performs a slower, more accurate Gaussian blur.
### Parameters ### Parameters
- `sigma` **[Number][1]?** a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`. - `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 - Throws **[Error][5]** Invalid parameters
@@ -119,7 +174,7 @@ Merge alpha transparency channel, if any, with a background.
### Parameters ### Parameters
- `options` **[Object][2]?** - `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}`) - `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** Returns **Sharp**
@@ -135,8 +190,8 @@ Supply a second argument to use a different output gamma value, otherwise the fi
### Parameters ### Parameters
- `gamma` **[Number][1]** value between 1.0 and 3.0. (optional, default `2.2`) - `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`) - `gammaOut` **[number][1]?** value between 1.0 and 3.0. (optional, defaults to same as `gamma`)
- Throws **[Error][5]** Invalid parameters - Throws **[Error][5]** Invalid parameters
@@ -180,11 +235,11 @@ Convolve the image with the specified kernel.
### Parameters ### Parameters
- `kernel` **[Object][2]** - `kernel` **[Object][2]**
- `kernel.width` **[Number][1]** width of the kernel in pixels. - `kernel.width` **[number][1]** width of the kernel in pixels.
- `kernel.height` **[Number][1]** width of the kernel in pixels. - `kernel.height` **[number][1]** width of the kernel in pixels.
- `kernel.kernel` **[Array][7]&lt;[Number][1]>** Array of length `width*height` containing the kernel values. - `kernel.kernel` **[Array][7]&lt;[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.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`) - `kernel.offset` **[number][1]** the offset of the kernel in pixels. (optional, default `0`)
### Examples ### Examples
@@ -212,7 +267,7 @@ Any pixel value greather than or equal to the threshold value will be set to 255
### Parameters ### Parameters
- `threshold` **[Number][1]** a value in the range 0-255 representing the level at which the threshold will be applied. (optional, default `128`) - `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` **[Object][2]?**
- `options.greyscale` **[Boolean][6]** convert to single channel greyscale. (optional, default `true`) - `options.greyscale` **[Boolean][6]** convert to single channel greyscale. (optional, default `true`)
- `options.grayscale` **[Boolean][6]** alternative spelling for greyscale. (optional, default `true`) - `options.grayscale` **[Boolean][6]** alternative spelling for greyscale. (optional, default `true`)
@@ -231,13 +286,13 @@ the selected bitwise boolean `operation` between the corresponding pixels of the
### Parameters ### Parameters
- `operand` **([Buffer][8] \| [String][3])** Buffer containing image data or String containing the path to an image file. - `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. - `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` **[Object][2]?**
- `options.raw` **[Object][2]?** describes operand when using raw pixel data. - `options.raw` **[Object][2]?** describes operand when using raw pixel data.
- `options.raw.width` **[Number][1]?** - `options.raw.width` **[number][1]?**
- `options.raw.height` **[Number][1]?** - `options.raw.height` **[number][1]?**
- `options.raw.channels` **[Number][1]?** - `options.raw.channels` **[number][1]?**
- Throws **[Error][5]** Invalid parameters - Throws **[Error][5]** Invalid parameters
@@ -250,8 +305,8 @@ Apply the linear formula a \* input + b to the image (levels adjustment)
### Parameters ### Parameters
- `a` **[Number][1]** multiplier (optional, default `1.0`) - `a` **[number][1]** multiplier (optional, default `1.0`)
- `b` **[Number][1]** offset (optional, default `0.0`) - `b` **[number][1]** offset (optional, default `0.0`)
- Throws **[Error][5]** Invalid parameters - Throws **[Error][5]** Invalid parameters
@@ -264,8 +319,7 @@ Recomb the image with the specified matrix.
### Parameters ### Parameters
- `inputMatrix` - `inputMatrix` **[Array][7]&lt;[Array][7]&lt;[number][1]>>** 3x3 Recombination matrix
- `3x3` **[Array][7]&lt;[Array][7]&lt;[Number][1]>>** Recombination matrix
### Examples ### Examples
@@ -298,9 +352,9 @@ Transforms the image using brightness, saturation and hue rotation.
### Parameters ### Parameters
- `options` **[Object][2]?** - `options` **[Object][2]?**
- `options.brightness` **[Number][1]?** Brightness multiplier - `options.brightness` **[number][1]?** Brightness multiplier
- `options.saturation` **[Number][1]?** Saturation multiplier - `options.saturation` **[number][1]?** Saturation multiplier
- `options.hue` **[Number][1]?** Degrees for hue rotation - `options.hue` **[number][1]?** Degrees for hue rotation
### Examples ### Examples

View File

@@ -5,7 +5,7 @@
Write output image data to a file. Write output image data to a file.
If an explicit output format is not selected, it will be inferred from the extension, 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. with JPEG, PNG, WebP, AVIF, TIFF, DZI, and libvips' V format supported.
Note that raw pixel data is only supported for buffer output. Note that raw pixel data is only supported for buffer output.
By default all metadata will be removed, which includes EXIF-based orientation. By default all metadata will be removed, which includes EXIF-based orientation.
@@ -15,7 +15,7 @@ A `Promise` is returned when `callback` is not provided.
### Parameters ### Parameters
- `fileOut` **[String][2]** the path to write the image data to. - `fileOut` **[string][2]** the path to write the image data to.
- `callback` **[Function][3]?** called on completion with two arguments `(err, info)`. - `callback` **[Function][3]?** called on completion with two arguments `(err, info)`.
`info` contains the output image `format`, `size` (bytes), `width`, `height`, `info` contains the output image `format`, `size` (bytes), `width`, `height`,
`channels` and `premultiplied` (indicating if premultiplication was used). `channels` and `premultiplied` (indicating if premultiplication was used).
@@ -42,7 +42,7 @@ Returns **[Promise][5]&lt;[Object][6]>** when no callback is provided
## toBuffer ## toBuffer
Write output to a Buffer. Write output to a Buffer.
JPEG, PNG, WebP, TIFF and RAW output are supported. JPEG, PNG, WebP, AVIF, TIFF and raw pixel data 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. If no explicit format is set, the output format will match the input image, except GIF and SVG input which become PNG output.
@@ -62,7 +62,7 @@ A `Promise` is returned when `callback` is not provided.
### Parameters ### Parameters
- `options` **[Object][6]?** - `options` **[Object][6]?**
- `options.resolveWithObject` **[Boolean][7]?** Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`. - `options.resolveWithObject` **[boolean][7]?** Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
- `callback` **[Function][3]?** - `callback` **[Function][3]?**
### Examples ### Examples
@@ -86,18 +86,37 @@ sharp(input)
.catch(err => { ... }); .catch(err => { ... });
``` ```
```javascript
const data = await sharp('my-image.jpg')
// output the raw pixels
.raw()
.toBuffer();
// create a more type safe way to work with the raw pixel data
// this will not copy the data, instead it will change `data`s underlying ArrayBuffer
// so `data` and `pixelArray` point to the same memory location
const pixelArray = new Uint8ClampedArray(data.buffer);
// When you are done changing the pixelArray, sharp takes the `pixelArray` as an input
await sharp(pixelArray).toFile('my-changed-image.jpg');
```
Returns **[Promise][5]&lt;[Buffer][8]>** when no callback is provided Returns **[Promise][5]&lt;[Buffer][8]>** when no callback is provided
## withMetadata ## withMetadata
Include all metadata (EXIF, XMP, IPTC) from the input image in the output image. Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
The default behaviour, when `withMetadata` is not used, is to strip all metadata and convert to the device-independent sRGB colour space. This will also convert to and add a web-friendly sRGB ICC profile unless a custom
This will also convert to and add a web-friendly sRGB ICC profile. 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 ### Parameters
- `options` **[Object][6]?** - `options` **[Object][6]?**
- `options.orientation` **[Number][9]?** value between 1 and 8, used to update the EXIF `Orientation` tag. - `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 ### Examples
@@ -118,7 +137,7 @@ Force output to a given format.
### Parameters ### Parameters
- `format` **([String][2] \| [Object][6])** as a String or an Object with an 'id' attribute - `format` **([string][2] \| [Object][6])** as a string or an Object with an 'id' attribute
- `options` **[Object][6]** output options - `options` **[Object][6]** output options
### Examples ### Examples
@@ -141,18 +160,19 @@ Use these JPEG options for output image.
### Parameters ### Parameters
- `options` **[Object][6]?** output options - `options` **[Object][6]?** output options
- `options.quality` **[Number][9]** quality, integer 1-100 (optional, default `80`) - `options.quality` **[number][9]** quality, integer 1-100 (optional, default `80`)
- `options.progressive` **[Boolean][7]** use progressive (interlace) scan (optional, default `false`) - `options.progressive` **[boolean][7]** use progressive (interlace) scan (optional, default `false`)
- `options.chromaSubsampling` **[String][2]** set to '4:4:4' to prevent chroma subsampling when quality &lt;= 90 (optional, default `'4:2:0'`) - `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.trellisQuantisation` **[Boolean][7]** apply trellis quantisation, requires libvips compiled with support for mozjpeg (optional, default `false`) - `options.optimiseCoding` **[boolean][7]** optimise Huffman coding tables (optional, default `true`)
- `options.overshootDeringing` **[Boolean][7]** apply overshoot deringing, requires libvips compiled with support for mozjpeg (optional, default `false`) - `options.optimizeCoding` **[boolean][7]** alternative spelling of optimiseCoding (optional, default `true`)
- `options.optimiseScans` **[Boolean][7]** optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg (optional, default `false`) - `options.mozjpeg` **[boolean][7]** use mozjpeg defaults, equivalent to `{ trellisQuantisation: true, overshootDeringing: true, optimiseScans: true, quantisationTable: 3 }` (optional, default `false`)
- `options.optimizeScans` **[Boolean][7]** alternative spelling of optimiseScans (optional, default `false`) - `options.trellisQuantisation` **[boolean][7]** apply trellis quantisation (optional, default `false`)
- `options.optimiseCoding` **[Boolean][7]** optimise Huffman coding tables (optional, default `true`) - `options.overshootDeringing` **[boolean][7]** apply overshoot deringing (optional, default `false`)
- `options.optimizeCoding` **[Boolean][7]** alternative spelling of optimiseCoding (optional, default `true`) - `options.optimiseScans` **[boolean][7]** optimise progressive scans, forces progressive (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.optimizeScans` **[boolean][7]** alternative spelling of optimiseScans (optional, default `false`)
- `options.quantizationTable` **[Number][9]** alternative spelling of quantisationTable (optional, default `0`) - `options.quantisationTable` **[number][9]** quantization table to use, integer 0-8 (optional, default `0`)
- `options.force` **[Boolean][7]** force JPEG output, otherwise attempt to use input format (optional, default `true`) - `options.quantizationTable` **[number][9]** alternative spelling of quantisationTable (optional, default `0`)
- `options.force` **[boolean][7]** force JPEG output, otherwise attempt to use input format (optional, default `true`)
### Examples ### Examples
@@ -166,6 +186,13 @@ const data = await sharp(input)
.toBuffer(); .toBuffer();
``` ```
```javascript
// Use mozjpeg to reduce output JPEG file size (slower)
const data = await sharp(input)
.jpeg({ mozjpeg: true })
.toBuffer();
```
- Throws **[Error][4]** Invalid options - Throws **[Error][4]** Invalid options
Returns **Sharp** Returns **Sharp**
@@ -174,31 +201,39 @@ Returns **Sharp**
Use these PNG options for output image. Use these PNG options for output image.
PNG output is always full colour at 8 or 16 bits per pixel. By default, PNG output is full colour at 8 or 16 bits per pixel.
Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel. Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel.
Set `palette` to `true` for slower, indexed PNG output.
### Parameters ### Parameters
- `options` **[Object][6]?** - `options` **[Object][6]?**
- `options.progressive` **[Boolean][7]** use progressive (interlace) scan (optional, default `false`) - `options.progressive` **[boolean][7]** use progressive (interlace) scan (optional, default `false`)
- `options.compressionLevel` **[Number][9]** zlib compression level, 0-9 (optional, default `9`) - `options.compressionLevel` **[number][9]** zlib compression level, 0 (fastest, largest) to 9 (slowest, smallest) (optional, default `6`)
- `options.adaptiveFiltering` **[Boolean][7]** use adaptive row filtering (optional, default `false`) - `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.palette` **[boolean][7]** quantise to a palette-based image with alpha transparency support (optional, default `false`)
- `options.quality` **[Number][9]** use the lowest number of colours needed to achieve given quality, requires libvips compiled with support for libimagequant (optional, default `100`) - `options.quality` **[number][9]** use the lowest number of colours needed to achieve given quality, sets `palette` to `true` (optional, default `100`)
- `options.colours` **[Number][9]** maximum number of palette entries, requires libvips compiled with support for libimagequant (optional, default `256`) - `options.colours` **[number][9]** maximum number of palette entries, sets `palette` to `true` (optional, default `256`)
- `options.colors` **[Number][9]** alternative spelling of `options.colours`, requires libvips compiled with support for libimagequant (optional, default `256`) - `options.colors` **[number][9]** alternative spelling of `options.colours`, sets `palette` to `true` (optional, default `256`)
- `options.dither` **[Number][9]** level of Floyd-Steinberg error diffusion, requires libvips compiled with support for libimagequant (optional, default `1.0`) - `options.dither` **[number][9]** level of Floyd-Steinberg error diffusion, sets `palette` to `true` (optional, default `1.0`)
- `options.force` **[Boolean][7]** force PNG output, otherwise attempt to use input format (optional, default `true`) - `options.force` **[boolean][7]** force PNG output, otherwise attempt to use input format (optional, default `true`)
### Examples ### Examples
```javascript ```javascript
// Convert any input to PNG output // Convert any input to full colour PNG output
const data = await sharp(input) const data = await sharp(input)
.png() .png()
.toBuffer(); .toBuffer();
``` ```
```javascript
// Convert any input to indexed PNG output (slower)
const data = await sharp(input)
.png({ palette: true })
.toBuffer();
```
- Throws **[Error][4]** Invalid options - Throws **[Error][4]** Invalid options
Returns **Sharp** Returns **Sharp**
@@ -210,13 +245,16 @@ Use these WebP options for output image.
### Parameters ### Parameters
- `options` **[Object][6]?** output options - `options` **[Object][6]?** output options
- `options.quality` **[Number][9]** quality, integer 1-100 (optional, default `80`) - `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.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.lossless` **[boolean][7]** use lossless compression mode (optional, default `false`)
- `options.nearLossless` **[Boolean][7]** use near_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.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.reductionEffort` **[number][9]** level of CPU effort to reduce file size, integer 0-6 (optional, default `4`)
- `options.force` **[Boolean][7]** force WebP output, otherwise attempt to use input format (optional, default `true`) - `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]&lt;[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 ### Examples
@@ -227,6 +265,27 @@ const data = await sharp(input)
.toBuffer(); .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]&lt;[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 - Throws **[Error][4]** Invalid options
Returns **Sharp** Returns **Sharp**
@@ -238,17 +297,17 @@ Use these TIFF options for output image.
### Parameters ### Parameters
- `options` **[Object][6]?** output options - `options` **[Object][6]?** output options
- `options.quality` **[Number][9]** quality, integer 1-100 (optional, default `80`) - `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.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.compression` **[string][2]** compression options: lzw, deflate, jpeg, ccittfax4 (optional, default `'jpeg'`)
- `options.predictor` **[Boolean][7]** compression predictor options: none, horizontal, float (optional, default `'horizontal'`) - `options.predictor` **[string][2]** compression predictor options: none, horizontal, float (optional, default `'horizontal'`)
- `options.pyramid` **[Boolean][7]** write an image pyramid (optional, default `false`) - `options.pyramid` **[boolean][7]** write an image pyramid (optional, default `false`)
- `options.tile` **[Boolean][7]** write a tiled tiff (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.tileWidth` **[number][9]** horizontal tile size (optional, default `256`)
- `options.tileHeight` **[Boolean][7]** vertical tile size (optional, default `256`) - `options.tileHeight` **[number][9]** vertical tile size (optional, default `256`)
- `options.xres` **[Number][9]** horizontal resolution in pixels/mm (optional, default `1.0`) - `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.yres` **[number][9]** vertical resolution in pixels/mm (optional, default `1.0`)
- `options.squash` **[Boolean][7]** squash 8-bit images down to 1 bit (optional, default `false`) - `options.bitdepth` **[number][9]** reduce bitdepth to 1, 2 or 4 bit (optional, default `8`)
### Examples ### Examples
@@ -257,7 +316,7 @@ Use these TIFF options for output image.
sharp('input.svg') sharp('input.svg')
.tiff({ .tiff({
compression: 'lzw', compression: 'lzw',
squash: true bitdepth: 1
}) })
.toFile('1-bpp-output.tiff') .toFile('1-bpp-output.tiff')
.then(info => { ... }); .then(info => { ... });
@@ -267,23 +326,45 @@ sharp('input.svg')
Returns **Sharp** Returns **Sharp**
## heif ## avif
Use these HEIF options for output image. Use these AVIF options for output image.
Support for HEIF (HEIC/AVIF) is experimental. Whilst it is possible to create AVIF images smaller than 16x16 pixels,
Do not use this in production systems. most web browsers do not display these properly.
Requires a custom, globally-installed libvips compiled with support for libheif.
Most versions of libheif support only the patent-encumbered HEVC compression format.
### Parameters ### Parameters
- `options` **[Object][6]?** output options - `options` **[Object][6]?** output options
- `options.quality` **[Number][9]** quality, integer 1-100 (optional, default `80`) - `options.quality` **[number][9]** quality, integer 1-100 (optional, default `50`)
- `options.compression` **[Boolean][7]** compression format: hevc, avc, jpeg, av1 (optional, default `'hevc'`) - `options.lossless` **[boolean][7]** use lossless compression (optional, default `false`)
- `options.lossless` **[Boolean][7]** use lossless compression (optional, default `false`) - `options.speed` **[number][9]** CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest) (optional, default `5`)
- `options.chromaSubsampling` **[string][2]** set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling, requires libvips v8.11.0 (optional, default `'4:2:0'`)
- 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` **[string][2]** compression format: av1, hevc (optional, default `'av1'`)
- `options.lossless` **[boolean][7]** use lossless compression (optional, default `false`)
- `options.speed` **[number][9]** CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest) (optional, default `5`)
- `options.chromaSubsampling` **[string][2]** set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling, requires libvips v8.11.0 (optional, default `'4:2:0'`)
- Throws **[Error][4]** Invalid options - Throws **[Error][4]** Invalid options
@@ -314,7 +395,7 @@ const { data, info } = await sharp('input.jpg')
const data = await sharp('input.png') const data = await sharp('input.png')
.ensureAlpha() .ensureAlpha()
.extractChannel(3) .extractChannel(3)
.colourspace('b-w') .toColourspace('b-w')
.raw() .raw()
.toBuffer(); .toBuffer();
``` ```
@@ -332,14 +413,16 @@ Warning: multiple sharp instances concurrently producing tile output can expose
### Parameters ### Parameters
- `options` **[Object][6]?** - `options` **[Object][6]?**
- `options.size` **[Number][9]** tile size in pixels, a value between 1 and 8192. (optional, default `256`) - `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.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.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][10] module, defaults to white without transparency. (optional, default `{r:255,g:255,b:255,alpha:1}`) - `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.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.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.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`, `zoomify` or `google`. (optional, default `'dz'`) - `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 ### Examples
@@ -377,4 +460,8 @@ Returns **Sharp**
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number [9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[10]: https://www.npmjs.org/package/color [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

View File

@@ -6,8 +6,8 @@ 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: When both a `width` and `height` are provided, the possible methods by which the image should **fit** these are:
- `cover`: Crop to cover both provided dimensions (the default). - `cover`: (default) Preserving aspect ratio, ensure the image covers both provided dimensions by cropping/clipping to fit.
- `contain`: Embed within both provided dimensions. - `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. - `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. - `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. - `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.
@@ -38,8 +38,8 @@ Possible interpolation kernels are:
### Parameters ### Parameters
- `width` **[Number][8]?** pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height. - `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. - `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` **[Object][9]?**
- `options.width` **[String][10]?** alternative means of specifying `width`. If both are present this take priority. - `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.height` **[String][10]?** alternative means of specifying `height`. If both are present this take priority.
@@ -136,11 +136,11 @@ This operation will always occur after resizing and extraction, if any.
### Parameters ### Parameters
- `extend` **([Number][8] \| [Object][9])** single pixel count to add to all edges or an Object with per-edge counts - `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.top` **[number][8]?**
- `extend.left` **[Number][8]?** - `extend.left` **[number][8]?**
- `extend.bottom` **[Number][8]?** - `extend.bottom` **[number][8]?**
- `extend.right` **[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}`) - `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 ### Examples
@@ -166,7 +166,7 @@ Returns **Sharp**
## extract ## extract
Extract a region of the image. Extract/crop a region of the image.
- Use `extract` before `resize` for pre-resize extraction. - Use `extract` before `resize` for pre-resize extraction.
- Use `extract` after `resize` for post-resize extraction. - Use `extract` after `resize` for post-resize extraction.
@@ -175,10 +175,10 @@ Extract a region of the image.
### Parameters ### Parameters
- `options` **[Object][9]** describes the region to extract using integral pixel values - `options` **[Object][9]** describes the region to extract using integral pixel values
- `options.left` **[Number][8]** zero-indexed offset from left edge - `options.left` **[number][8]** zero-indexed offset from left edge
- `options.top` **[Number][8]** zero-indexed offset from top edge - `options.top` **[number][8]** zero-indexed offset from top edge
- `options.width` **[Number][8]** width of region to extract - `options.width` **[number][8]** width of region to extract
- `options.height` **[Number][8]** height of region to extract - `options.height` **[number][8]** height of region to extract
### Examples ### Examples
@@ -209,11 +209,12 @@ Returns **Sharp**
Trim "boring" pixels from all edges that contain values similar to the top-left pixel. 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. Images consisting entirely of a single colour will calculate "boring" using the alpha channel, if any.
The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` properties. The `info` response Object, obtained from callback of `.toFile()` or `.toBuffer()`,
will contain `trimOffsetLeft` and `trimOffsetTop` properties.
### Parameters ### Parameters
- `threshold` **[Number][8]** the allowed difference from the top-left pixel, a number greater than zero. (optional, default `10`) - `threshold` **[number][8]** the allowed difference from the top-left pixel, a number greater than zero. (optional, default `10`)
- Throws **[Error][13]** Invalid parameters - Throws **[Error][13]** Invalid parameters

View File

@@ -12,6 +12,36 @@ console.log(sharp.format);
Returns **[Object][1]** 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 ## versions
An Object containing the version numbers of libvips and its dependencies. An Object containing the version numbers of libvips and its dependencies.
@@ -31,10 +61,10 @@ useful for determining how much working memory is required for a particular task
### Parameters ### Parameters
- `options` **([Object][1] \| [Boolean][2])** Object with the following attributes, or Boolean where true uses default cache settings and false removes all caching (optional, default `true`) - `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][3]** is the maximum memory in MB to use for this cache (optional, default `50`) - `options.memory` **[number][11]** is the maximum memory in MB to use for this cache (optional, default `50`)
- `options.files` **[Number][3]** is the maximum number of files to hold open (optional, default `20`) - `options.files` **[number][11]** is the maximum number of files to hold open (optional, default `20`)
- `options.items` **[Number][3]** is the maximum number of operations to cache (optional, default `100`) - `options.items` **[number][11]** is the maximum number of operations to cache (optional, default `100`)
### Examples ### Examples
@@ -64,7 +94,7 @@ This method always returns the current concurrency.
### Parameters ### Parameters
- `concurrency` **[Number][3]?** - `concurrency` **[number][11]?**
### Examples ### Examples
@@ -74,7 +104,7 @@ sharp.concurrency(2); // 2
sharp.concurrency(0); // 4 sharp.concurrency(0); // 4
``` ```
Returns **[Number][3]** concurrency Returns **[number][11]** concurrency
## queue ## queue
@@ -116,7 +146,7 @@ by taking advantage of the SIMD vector unit of the CPU, e.g. Intel SSE and ARM N
### Parameters ### Parameters
- `simd` **[Boolean][2]** (optional, default `true`) - `simd` **[boolean][10]** (optional, default `true`)
### Examples ### Examples
@@ -130,10 +160,26 @@ const simd = sharp.simd(false);
// prevent libvips from using liborc at runtime // prevent libvips from using liborc at runtime
``` ```
Returns **[Boolean][2]** Returns **[boolean][10]**
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object [1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean [2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number [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

25
docs/build.js Normal file
View File

@@ -0,0 +1,25 @@
'use strict';
const fs = require('fs').promises;
const path = require('path');
const documentation = require('documentation');
[
'constructor',
'input',
'resize',
'composite',
'operation',
'colour',
'channel',
'output',
'utility'
].forEach(async (m) => {
const input = path.join('lib', `${m}.js`);
const output = path.join('docs', `api-${m}.md`);
const ast = await documentation.build(input, { shallow: true });
const markdown = await documentation.formats.md(ast, { markdownToc: false });
await fs.writeFile(output, markdown);
});

View File

@@ -1,9 +1,211 @@
# Changelog # Changelog
## v0.28 - *bijou*
Requires libvips v8.10.6
### v0.28.0 - TBD
* Prebuilt binaries now include mozjpeg and libimagequant (BSD 2-Clause).
* Prebuilt binaries limit AVIF support to the most common 8-bit depth.
* Add `mozjpeg` option to `jpeg` method, sets mozjpeg defaults.
* Reduce the default PNG `compressionLevel` to the more commonly used 6.
* Reduce concurrency on glibc-based Linux when using the default memory allocator to help prevent fragmentation.
* Ensure `@id` attribute can be set for IIIF tile-based output.
[#2612](https://github.com/lovell/sharp/issues/2612)
[@edsilv](https://github.com/edsilv)
## v0.27 - *avif*
Requires libvips v8.10.5
### v0.27.2 - 22nd February 2021
* macOS: Prevent use of globally-installed ARM64 libvips with Rosetta x64 emulation.
[#2460](https://github.com/lovell/sharp/issues/2460)
* Linux (musl): Prevent use of prebuilt linuxmusl-x64 binaries with musl >= 1.2.0.
[#2570](https://github.com/lovell/sharp/issues/2570)
* Improve 16-bit grey+alpha support by using libvips' `has_alpha` detection.
[#2569](https://github.com/lovell/sharp/issues/2569)
* Allow the use of non lower case extensions with `toFormat`.
[#2581](https://github.com/lovell/sharp/pull/2581)
[@florian-busch](https://github.com/florian-busch)
* Allow use of `recomb` operation with single channel input.
[#2584](https://github.com/lovell/sharp/issues/2584)
### v0.27.1 - 27th January 2021
* Ensure TIFF is cast when using float predictor.
[#2502](https://github.com/lovell/sharp/pull/2502)
[@randyridge](https://github.com/randyridge)
* Add support for Uint8Array and Uint8ClampedArray input.
[#2511](https://github.com/lovell/sharp/pull/2511)
[@leon](https://github.com/leon)
* Revert: ensure all platforms use fontconfig for font rendering.
[#2515](https://github.com/lovell/sharp/issues/2515)
* Expose libvips gaussnoise operation to allow creation of Gaussian noise.
[#2527](https://github.com/lovell/sharp/pull/2527)
[@alza54](https://github.com/alza54)
### v0.27.0 - 22nd December 2020
* Add support for AVIF to prebuilt binaries.
* Remove experimental status from `heif` output, defaults are now AVIF-centric.
* Allow negative top/left offsets for composite operation.
[#2391](https://github.com/lovell/sharp/pull/2391)
[@CurosMJ](https://github.com/CurosMJ)
* Ensure all platforms use fontconfig for font rendering.
[#2399](https://github.com/lovell/sharp/issues/2399)
## v0.26 - *zoom*
Requires libvips v8.10.0
### v0.26.3 - 16th November 2020
* Expose libvips' affine operation.
[#2336](https://github.com/lovell/sharp/pull/2336)
[@guillevc](https://github.com/guillevc)
* Fallback to tar.gz for prebuilt libvips when Brotli not available.
[#2412](https://github.com/lovell/sharp/pull/2412)
[@ascorbic](https://github.com/ascorbic)
### v0.26.2 - 14th October 2020
* Add support for EXR input. Requires libvips compiled with OpenEXR.
[#698](https://github.com/lovell/sharp/issues/698)
* Ensure support for yarn v2.
[#2379](https://github.com/lovell/sharp/pull/2379)
[@jalovatt](https://github.com/jalovatt)
* Add centre/center option to tile-based output.
[#2397](https://github.com/lovell/sharp/pull/2397)
[@beig](https://github.com/beig)
### v0.26.1 - 20th September 2020
* Ensure correct pageHeight when verifying multi-page image dimensions.
[#2343](https://github.com/lovell/sharp/pull/2343)
[@derom](https://github.com/derom)
* Allow input density range up to 100000 DPI.
[#2348](https://github.com/lovell/sharp/pull/2348)
[@stefanprobst](https://github.com/stefanprobst)
* Ensure animation-related properties can be set for Stream-based input.
[#2369](https://github.com/lovell/sharp/pull/2369)
[@AcrylicShrimp](https://github.com/AcrylicShrimp)
* Ensure `stats` can be calculated for 1x1 input.
[#2372](https://github.com/lovell/sharp/issues/2372)
* Ensure animated GIF output is optimised.
[#2376](https://github.com/lovell/sharp/issues/2376)
### v0.26.0 - 25th August 2020
* Prebuilt libvips binaries are now statically-linked and Brotli-compressed, requiring Node.js 10.16.0+.
* TIFF output `squash` is replaced by `bitdepth` to reduce to 1, 2 or 4 bit.
* JPEG output `quality` >= 90 no longer automatically sets `chromaSubsampling` to `4:4:4`.
* Add most `dominant` colour to image `stats`.
[#640](https://github.com/lovell/sharp/issues/640)
* Add support for animated GIF (requires \*magick) and WebP output.
[#2012](https://github.com/lovell/sharp/pull/2012)
[@deftomat](https://github.com/deftomat)
* Add support for libvips ImageMagick v7 loaders.
[#2258](https://github.com/lovell/sharp/pull/2258)
[@vouillon](https://github.com/vouillon)
* Allow multi-page input via \*magick.
[#2259](https://github.com/lovell/sharp/pull/2259)
[@vouillon](https://github.com/vouillon)
* Add support to `withMetadata` for custom ICC profile.
[#2271](https://github.com/lovell/sharp/pull/2271)
[@roborourke](https://github.com/roborourke)
* Ensure prebuilt binaries for ARM default to v7 when using Electron.
[#2292](https://github.com/lovell/sharp/pull/2292)
[@diegodev3](https://github.com/diegodev3)
## v0.25 - *yield* ## v0.25 - *yield*
Requires libvips v8.9.1 Requires libvips v8.9.1
### v0.25.4 - 12th June 2020
* Allow libvips binary location override where version is appended.
[#2217](https://github.com/lovell/sharp/pull/2217)
[@malice00](https://github.com/malice00)
* Enable PNG palette when setting quality, colours, colors or dither.
[#2226](https://github.com/lovell/sharp/pull/2226)
[@romaleev](https://github.com/romaleev)
* Add `level` constructor option to use a specific level of a multi-level image.
Expose `levels` metadata for multi-level images.
[#2222](https://github.com/lovell/sharp/issues/2222)
* Add support for named `alpha` channel to `extractChannel` operation.
[#2138](https://github.com/lovell/sharp/issues/2138)
* Add experimental `sharpness` calculation to `stats()` response.
[#2251](https://github.com/lovell/sharp/issues/2251)
* Emit `warning` event for non-critical processing problems.
[#2032](https://github.com/lovell/sharp/issues/2032)
### v0.25.3 - 17th May 2020
* Ensure libvips is initialised only once, improves worker thread safety.
[#2143](https://github.com/lovell/sharp/issues/2143)
* Ensure npm platform flag is respected when copying DLLs.
[#2188](https://github.com/lovell/sharp/pull/2188)
[@dimadeveatii](https://github.com/dimadeveatii)
* Allow SVG input with large inline images to be parsed.
[#2195](https://github.com/lovell/sharp/issues/2195)
### v0.25.2 - 20th March 2020
* Provide prebuilt binaries for Linux ARM64v8.
* Add IIIF layout support to tile-based output.
[#2098](https://github.com/lovell/sharp/pull/2098)
[@edsilv](https://github.com/edsilv)
* Ensure input options are consistently and correctly detected.
[#2118](https://github.com/lovell/sharp/issues/2118)
* Ensure N-API prebuilt binaries work on RHEL7 and its derivatives.
[#2119](https://github.com/lovell/sharp/issues/2119)
* Ensure AsyncWorker options are persisted.
[#2130](https://github.com/lovell/sharp/issues/2130)
### v0.25.1 - 7th March 2020 ### v0.25.1 - 7th March 2020
* Ensure prebuilt binaries are fetched based on N-API version. * Ensure prebuilt binaries are fetched based on N-API version.

View File

@@ -4,9 +4,11 @@
"public": ".", "public": ".",
"ignore": [ "ignore": [
".*", ".*",
"*.json", "build.js",
"firebase.json",
"*.md", "*.md",
"image/**" "image/**",
"search-index/**"
], ],
"headers": [ "headers": [
{ {

View File

@@ -176,3 +176,36 @@ GitHub: https://github.com/rustyguts
Name: Brychan Bennett-Odlum Name: Brychan Bennett-Odlum
GitHub: https://github.com/BrychanOdlum GitHub: https://github.com/BrychanOdlum
Name: Edward Silverton
GitHub: https://github.com/edsilv
Name: Dumitru Deveatii
GitHub: https://github.com/dimadeveatii
Name: Roland Asmann
GitHub: https://github.com/malice00
Name: Roman Malieiev
GitHub: https://github.com/romaleev
Name: Jerome Vouillon
GitHub: https://github.com/vouillon
Name: Tomáš Szabo
GitHub: https://github.com/deftomat
Name: Robert O'Rourke
GitHub: https://github.com/roborourke
Name: Denis Soldatov
GitHub: https://github.com/derom
Name: Stefan Probst
GitHub: https://github.com/stefanprobst
Name: Thomas Beiganz
GitHub: https://github.com/beig
Name: Florian Busch
GitHub: https://github.com/florian-busch

View File

@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="86 86 550 550">
<!-- Creative Commons CC0 1.0 Universal Public Domain Dedication -->
<defs>
<mask id="c">
<path fill="none" stroke="#fff" stroke-width="80" d="M258.411 285.777l200.176-26.8M244.113 466.413L451.44 438.66m.001 0V238.484m0-150.121v171.572l178.725-23.917m-359.843 19.584V477.22m2.387 156.95V462.591L93.984 486.515"/>
<path fill="none" stroke="#000" stroke-width="112" d="M451.441 610.246V438.66l178.725-23.91M269.688 112.59v171.58L90.964 308.093"/>
</mask>
</defs>
<path mask="url(#c)" fill="none" stroke="#000" stroke-width="80" d="M258.411 285.777l200.176-26.8M244.113 466.413L451.44 438.66m.001 0V238.484m0-150.121v171.572l178.725-23.917m-359.843 19.584V477.22m2.387 156.95V462.591L93.984 486.515"/>
<path fill="none" stroke="#000" stroke-width="80" d="M451.441 610.246V438.66l178.725-23.91M269.688 112.59v171.58L90.964 308.093"/>
</svg>

After

Width:  |  Height:  |  Size: 929 B

View File

@@ -1,5 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="86 86 550 550"> <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. --> <!-- Creative Commons CC0 1.0 Universal Public Domain Dedication -->
<path fill="none" stroke="#9c0" stroke-width="80" d="M258.411 285.777l200.176-26.8M244.113 466.413L451.44 438.66M451.441 438.66V238.484M451.441 88.363v171.572l178.725-23.917M270.323 255.602V477.22M272.71 634.17V462.591L93.984 486.515"/> <path fill="none" stroke="#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"/> <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> </svg>

Before

Width:  |  Height:  |  Size: 592 B

After

Width:  |  Height:  |  Size: 508 B

File diff suppressed because one or more lines are too long

View File

@@ -19,25 +19,28 @@ Node.js v10+ on the most common platforms:
* macOS x64 (>= 10.13) * macOS x64 (>= 10.13)
* Linux x64 (glibc >= 2.17, musl >= 1.1.24) * Linux x64 (glibc >= 2.17, musl >= 1.1.24)
* Windows * Linux ARM64 (glibc >= 2.29, musl >= 1.1.24)
* Windows x64
* Windows x86
A ~10MB tarball containing libvips and its most commonly used dependencies An ~8MB tarball containing libvips and its most commonly used dependencies
is downloaded via HTTPS and stored within `node_modules/sharp/vendor` during `npm install`. is downloaded via HTTPS and stored within `node_modules/sharp/vendor` during `npm install`.
This provides support for the This provides support for the
JPEG, PNG, WebP, TIFF, GIF (input) and SVG (input) image formats. JPEG, PNG, WebP, AVIF, TIFF, GIF (input) and SVG (input) image formats.
The following platforms have prebuilt libvips but not sharp: The following platforms have prebuilt libvips but not sharp:
* macOS ARM64
* Linux ARMv6 * Linux ARMv6
* Linux ARMv7 (glibc >= 2.28) * Linux ARMv7 (glibc >= 2.28)
* Linux ARM64 (glibc >= 2.29) * Windows ARM64
The following platforms require compilation of both libvips and sharp from source: The following platforms require compilation of both libvips and sharp from source:
* Linux x86 * Linux x86
* Linux x64 (glibc <= 2.16, includes RHEL/CentOS 6) * Linux x64 (glibc <= 2.16, includes RHEL/CentOS 6)
* Linux ARM64 (glibc <= 2.28, musl) * Linux ARM64 (glibc <= 2.28)
* Linux PowerPC * Linux PowerPC
* FreeBSD * FreeBSD
* OpenBSD * OpenBSD
@@ -47,12 +50,26 @@ The following platforms require compilation of both libvips and sharp from sourc
The architecture and platform of Node.js used for `npm install` 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. 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. When using npm v6 or earlier, the `npm install --unsafe-perm` flag must be used when installing as `root` or a `sudo` user.
When using npm v7, the user running `npm install` must own the directory it is run in.
The `npm install --ignore-scripts=false` flag must be used when `npm` has been configured to ignore installation scripts. 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. Check the output of running `npm install --verbose sharp` for useful error messages.
## Apple M1
Prebuilt libvips binaries are provided for macOS on ARM64 (since sharp v0.28.0).
During `npm install` sharp will be built locally,
which requires Xcode and Python - see
[building from source](#building-from-source).
When this new ARM64 CPU is made freely available
to open source projects via a CI service
then prebuilt sharp binaries can also be provided.
## Custom libvips ## Custom libvips
To use a custom, globally-installed version of libvips instead of the provided binaries, To use a custom, globally-installed version of libvips instead of the provided binaries,
@@ -69,7 +86,7 @@ The use of a globally-installed libvips is unsupported on Windows.
This module will be compiled from source at `npm install` time when: 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), * a globally-installed libvips is detected (set the `SHARP_IGNORE_GLOBAL_LIBVIPS` environment variable to skip this),
* prebuilt binaries do not exist for the current platform and Node.js version, or * prebuilt sharp binaries do not exist for the current platform, or
* when the `npm install --build-from-source` flag is used. * when the `npm install --build-from-source` flag is used.
Building from source requires: Building from source requires:
@@ -81,24 +98,44 @@ Building from source requires:
This is an advanced approach that most people will not require. This is an advanced approach that most people will not require.
To install the prebuilt libvips binaries from a custom URL, To install the prebuilt sharp binaries from a custom URL,
set the `sharp_dist_base_url` npm config option set the `sharp_binary_host` npm config option
or the `SHARP_DIST_BASE_URL` environment variable. or the `npm_config_sharp_binary_host` environment variable.
For example, both of the following will result in an attempt to download the file located at To install the prebuilt libvips binaries from a custom URL,
`https://hostname/path/libvips-x.y.z-platform.tar.gz`. 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 ```sh
npm config set sharp_dist_base_url "https://hostname/path/" 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 npm install sharp
``` ```
```sh or set the following environment variables:
SHARP_DIST_BASE_URL="https://hostname/path/" npm install sharp
```
To install the prebuilt sharp binaries from a custom URL, please see ```sh
[https://github.com/prebuild/prebuild-install#custom-binaries](https://github.com/prebuild/prebuild-install#custom-binaries) 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
```
## FreeBSD ## FreeBSD
@@ -130,11 +167,19 @@ The binaries in the `node_modules` directory of the
[deployment package](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-package.html) [deployment package](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-package.html)
must be for the Linux x64 platform. must be for the Linux x64 platform.
On machines other than Linux x64, such as macOS and Windows, run the following: When building your deployment package on machines other than Linux x64 (glibc),
run the following commands:
macOS:
```sh ```sh
rm -rf node_modules/sharp rm -rf node_modules/sharp
npm install --arch=x64 --platform=linux --target=12.0.0 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: Alternatively a Docker container closely matching the Lambda runtime can be used:
@@ -147,16 +192,34 @@ docker run -v "$PWD":/var/task lambci/lambda:build-nodejs12.x npm install sharp
To get the best performance select the largest memory available. To get the best performance select the largest memory available.
A 1536 MB function provides ~12x more CPU time than a 128 MB function. A 1536 MB function provides ~12x more CPU time than a 128 MB function.
## Electron ## Webpack
Electron provides versions of the V8 JavaScript engine Ensure sharp is added to the
that are incompatible with Node.js. [externals](https://webpack.js.org/configuration/externals/)
To ensure the correct binaries are used, run the following: configuration.
```sh ```js
npm install externals: {
npx electron-rebuild 'sharp': 'commonjs sharp'
}
``` ```
Further help can be found at ## Worker threads
[https://electronjs.org/docs/tutorial/using-native-node-modules](https://electronjs.org/docs/tutorial/using-native-node-modules)
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.
## Known conflicts
### Canvas and Windows
The prebuilt binaries provided by `canvas` for Windows depend on the unmaintained GTK 2, last updated in 2011.
These conflict with the modern, up-to-date binaries provided by sharp.
If both modules are used in the same Windows process, the following error will occur:
```
The specified procedure could not be found.
```

View File

@@ -4,11 +4,11 @@ A test to benchmark the performance of this module relative to alternatives.
## The contenders ## The contenders
* [jimp](https://www.npmjs.com/package/jimp) v0.9.3 - Image processing in pure JavaScript. Provides bicubic interpolation. * [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.3.1 - Whilst primarily a map renderer, Mapnik contains bitmap image utilities. * [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*". * [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.23.1 - Fully featured wrapper around GraphicsMagick's `gm` command line utility. * [gm](https://www.npmjs.com/package/gm) v1.23.1 - Fully featured wrapper around GraphicsMagick's `gm` command line utility.
* sharp v0.24.0 / libvips v8.9.0 - Caching within libvips disabled to ensure a fair comparison. * sharp v0.27.0 / libvips v8.10.5 - Caching within libvips disabled to ensure a fair comparison.
## The task ## The task
@@ -18,25 +18,25 @@ then compress to JPEG at a "quality" setting of 80.
## Test environment ## Test environment
* AWS EC2 eu-west-1 [c5.large](https://aws.amazon.com/ec2/instance-types/c5/) (2x Xeon Platinum 8124M CPU @ 3.00GHz) * AWS EC2 eu-west-1 [c5d.large](https://aws.amazon.com/ec2/instance-types/c5/) (2x Xeon Platinum 8275CL CPU @ 3.00GHz)
* Ubuntu 18.04 (hvm-ssd/ubuntu-bionic-18.04-amd64-server-20180912 ami-00035f41c82244dab) * Ubuntu 20.10 (ami-046cdbcee95cdd75c)
* Node.js v12.14.1 * Node.js v14.15.3
## Results ## Results
| Module | Input | Output | Ops/sec | Speed-up | | Module | Input | Output | Ops/sec | Speed-up |
| :----------------- | :----- | :----- | ------: | -------: | | :----------------- | :----- | :----- | ------: | -------: |
| jimp | buffer | buffer | 0.72 | 1.0 | | jimp | buffer | buffer | 0.77 | 1.0 |
| mapnik | buffer | buffer | 3.02 | 4.2 | | mapnik | buffer | buffer | 3.39 | 4.4 |
| gm | buffer | buffer | 3.90 | 5.4 | | gm | buffer | buffer | 4.30 | 5.6 |
| gm | file | file | 3.94 | 5.5 | | gm | file | file | 4.33 | 5.6 |
| imagemagick | file | file | 4.30 | 6.0 | | imagemagick | file | file | 4.39 | 5.7 |
| sharp | stream | stream | 23.65 | 32.8 | | sharp | stream | stream | 23.81 | 30.9 |
| sharp | file | file | 24.66 | 34.3 | | sharp | file | file | 25.09 | 32.6 |
| sharp | buffer | buffer | 25.14 | 34.9 | | sharp | buffer | buffer | 25.60 | 33.2 |
Greater libvips performance can be expected with caching enabled (default) 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. The I/O limits of the relevant (de)compression library will generally determine maximum throughput.

1
docs/search-index.json Normal file

File diff suppressed because one or more lines are too long

View 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-z0-9 ]+)\n\n(?<body>[^#]+)/gs
);
for (const match of matches) {
const { title, body } = match.groups;
const description = extractDescription(body);
searchIndex.push({
t: title,
d: description,
k: extractKeywords(`${title} ${description}`),
l: `/install#${title.toLowerCase().replace(/ /g, '-')}`
});
}
// API
[
'constructor',
'input',
'output',
'resize',
'composite',
'operation',
'channel',
'colour',
'utility'
].forEach((section) => {
const contents = fs.readFileSync(path.join(__dirname, '..', `api-${section}.md`), 'utf8');
const matches = contents.matchAll(
/\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)
);

View 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 && word.length < 15 && !stopWords.includes(word))
)
].join(' ');
module.exports = { extractDescription, extractKeywords };

View File

@@ -0,0 +1,73 @@
'use strict';
module.exports = [
'about',
'after',
'all',
'already',
'alternative',
'always',
'and',
'any',
'are',
'been',
'before',
'can',
'containing',
'default',
'does',
'each',
'either',
'ensure',
'etc',
'every',
'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'
];

View File

@@ -4,18 +4,20 @@ const fs = require('fs');
const path = require('path'); const path = require('path');
const libvips = require('../lib/libvips'); const libvips = require('../lib/libvips');
const npmLog = require('npmlog');
if (process.platform === 'win32') { const minimumLibvipsVersion = libvips.minimumLibvipsVersion;
const platform = process.env.npm_config_platform || process.platform;
if (platform === 'win32') {
const buildDir = path.join(__dirname, '..', 'build'); const buildDir = path.join(__dirname, '..', 'build');
const buildReleaseDir = path.join(buildDir, 'Release'); const buildReleaseDir = path.join(buildDir, 'Release');
npmLog.info('sharp', `Creating ${buildReleaseDir}`); libvips.log(`Creating ${buildReleaseDir}`);
try { try {
libvips.mkdirSync(buildDir); libvips.mkdirSync(buildDir);
libvips.mkdirSync(buildReleaseDir); libvips.mkdirSync(buildReleaseDir);
} catch (err) {} } catch (err) {}
const vendorLibDir = path.join(__dirname, '..', 'vendor', 'lib'); const vendorLibDir = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, 'lib');
npmLog.info('sharp', `Copying DLLs from ${vendorLibDir} to ${buildReleaseDir}`); libvips.log(`Copying DLLs from ${vendorLibDir} to ${buildReleaseDir}`);
try { try {
fs fs
.readdirSync(vendorLibDir) .readdirSync(vendorLibDir)
@@ -29,6 +31,7 @@ if (process.platform === 'win32') {
); );
}); });
} catch (err) { } catch (err) {
npmLog.error('sharp', err.message); libvips.log(err);
process.exit(1);
} }
} }

View File

@@ -3,12 +3,13 @@
const fs = require('fs'); const fs = require('fs');
const os = require('os'); const os = require('os');
const path = require('path'); const path = require('path');
const stream = require('stream');
const zlib = require('zlib');
const detectLibc = require('detect-libc'); const detectLibc = require('detect-libc');
const npmLog = require('npmlog');
const semver = require('semver'); const semver = require('semver');
const simpleGet = require('simple-get'); const simpleGet = require('simple-get');
const tar = require('tar'); const tarFs = require('tar-fs');
const agent = require('../lib/agent'); const agent = require('../lib/agent');
const libvips = require('../lib/libvips'); const libvips = require('../lib/libvips');
@@ -20,45 +21,67 @@ const minimumGlibcVersionByArch = {
x64: '2.17' x64: '2.17'
}; };
const hasSharpPrebuild = [
'darwin-x64',
'linux-arm64',
'linux-x64',
'linuxmusl-x64',
'linuxmusl-arm64',
'win32-ia32',
'win32-x64'
];
const { minimumLibvipsVersion, minimumLibvipsVersionLabelled } = libvips; const { minimumLibvipsVersion, minimumLibvipsVersionLabelled } = libvips;
const distBaseUrl = process.env.npm_config_sharp_dist_base_url || process.env.SHARP_DIST_BASE_URL || `https://github.com/lovell/sharp-libvips/releases/download/v${minimumLibvipsVersionLabelled}/`; 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) { const fail = function (err) {
npmLog.error('sharp', err.message); libvips.log(err);
if (err.code === 'EACCES') { 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'); libvips.log('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'); libvips.log('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'); libvips.log('Please see https://sharp.pixelplumbing.com/install for required dependencies');
process.exit(1); process.exit(1);
}; };
const extractTarball = function (tarPath) { const extractTarball = function (tarPath, platformAndArch) {
const vendorPath = path.join(__dirname, '..', 'vendor'); const vendorPath = path.join(__dirname, '..', 'vendor');
libvips.mkdirSync(vendorPath); libvips.mkdirSync(vendorPath);
tar const versionedVendorPath = path.join(vendorPath, minimumLibvipsVersion);
.extract({ libvips.mkdirSync(versionedVendorPath);
file: tarPath,
cwd: vendorPath, const ignoreVendorInclude = hasSharpPrebuild.includes(platformAndArch) && !process.env.npm_config_build_from_source;
strict: true const ignore = function (name) {
}) return ignoreVendorInclude && name.includes('include/');
.catch(function (err) { };
if (/unexpected end of file/.test(err.message)) {
npmLog.error('sharp', `Please delete ${tarPath} as it is not a valid tarball`); stream.pipeline(
fs.createReadStream(tarPath),
supportsBrotli ? new zlib.BrotliDecompress() : new zlib.Gunzip(),
tarFs.extract(versionedVendorPath, { ignore }),
function (err) {
if (err) {
if (/unexpected end of file/.test(err.message)) {
fail(new Error(`Please delete ${tarPath} as it is not a valid tarball`));
}
fail(err);
} }
fail(err); }
}); );
}; };
try { try {
const useGlobalLibvips = libvips.useGlobalLibvips(); const useGlobalLibvips = libvips.useGlobalLibvips();
if (useGlobalLibvips) { if (useGlobalLibvips) {
const globalLibvipsVersion = libvips.globalLibvipsVersion(); const globalLibvipsVersion = libvips.globalLibvipsVersion();
npmLog.info('sharp', `Detected globally-installed libvips v${globalLibvipsVersion}`); libvips.log(`Detected globally-installed libvips v${globalLibvipsVersion}`);
npmLog.info('sharp', 'Building from source via node-gyp'); libvips.log('Building from source via node-gyp');
process.exit(1); process.exit(1);
} else if (libvips.hasVendoredLibvips()) { } else if (libvips.hasVendoredLibvips()) {
npmLog.info('sharp', `Using existing vendored libvips v${minimumLibvipsVersion}`); libvips.log(`Using existing vendored libvips v${minimumLibvipsVersion}`);
} else { } else {
// Is this arch/platform supported? // Is this arch/platform supported?
const arch = process.env.npm_config_arch || process.arch; const arch = process.env.npm_config_arch || process.arch;
@@ -66,6 +89,9 @@ try {
if (arch === 'ia32' && !platformAndArch.startsWith('win32')) { if (arch === 'ia32' && !platformAndArch.startsWith('win32')) {
throw new Error(`Intel Architecture 32-bit systems require manual installation of libvips >= ${minimumLibvipsVersion}`); throw new Error(`Intel Architecture 32-bit systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
} }
if (platformAndArch === 'darwin-arm64') {
throw new Error("Please run 'brew install vips' to install libvips on Apple M1 (ARM64) systems");
}
if (platformAndArch === 'freebsd-x64' || platformAndArch === 'openbsd-x64' || platformAndArch === 'sunos-x64') { if (platformAndArch === 'freebsd-x64' || platformAndArch === 'openbsd-x64' || platformAndArch === 'sunos-x64') {
throw new Error(`BSD/SunOS systems require manual installation of libvips >= ${minimumLibvipsVersion}`); throw new Error(`BSD/SunOS systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
} }
@@ -74,17 +100,28 @@ try {
throw new Error(`Use with glibc ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`); throw new Error(`Use with glibc ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`);
} }
} }
if (detectLibc.family === detectLibc.MUSL && detectLibc.version) {
if (semver.lt(detectLibc.version, '1.1.24')) {
throw new Error(`Use with musl ${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 // Download to per-process temporary file
const tarFilename = ['libvips', minimumLibvipsVersion, platformAndArch].join('-') + '.tar.gz'; const tarFilename = ['libvips', minimumLibvipsVersion, platformAndArch].join('-') + '.tar.' + extension;
const tarPathCache = path.join(libvips.cachePath(), tarFilename); const tarPathCache = path.join(libvips.cachePath(), tarFilename);
if (fs.existsSync(tarPathCache)) { if (fs.existsSync(tarPathCache)) {
npmLog.info('sharp', `Using cached ${tarPathCache}`); libvips.log(`Using cached ${tarPathCache}`);
extractTarball(tarPathCache); extractTarball(tarPathCache, platformAndArch);
} else { } else {
const tarPathTemp = path.join(os.tmpdir(), `${process.pid}-${tarFilename}`);
const tmpFile = fs.createWriteStream(tarPathTemp);
const url = distBaseUrl + tarFilename; const url = distBaseUrl + tarFilename;
npmLog.info('sharp', `Downloading ${url}`); libvips.log(`Downloading ${url}`);
simpleGet({ url: url, agent: agent() }, function (err, response) { simpleGet({ url: url, agent: agent() }, function (err, response) {
if (err) { if (err) {
fail(err); fail(err);
@@ -93,24 +130,37 @@ try {
} else if (response.statusCode !== 200) { } else if (response.statusCode !== 200) {
fail(new Error(`Status ${response.statusCode} ${response.statusMessage}`)); fail(new Error(`Status ${response.statusCode} ${response.statusMessage}`));
} else { } else {
const tarPathTemp = path.join(os.tmpdir(), `${process.pid}-${tarFilename}`);
const tmpFileStream = fs.createWriteStream(tarPathTemp);
response response
.on('error', fail) .on('error', function (err) {
.pipe(tmpFile); tmpFileStream.destroy(err);
})
.on('close', function () {
if (!response.complete) {
tmpFileStream.destroy(new Error('Download incomplete (connection was terminated)'));
}
})
.pipe(tmpFileStream);
tmpFileStream
.on('error', function (err) {
// Clean up temporary file
fs.unlinkSync(tarPathTemp);
fail(err);
})
.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);
});
} }
}); });
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) { } catch (err) {

View File

@@ -60,22 +60,19 @@ function ensureAlpha () {
* // green.jpg is a greyscale image containing the green channel of the input * // green.jpg is a greyscale image containing the green channel of the input
* }); * });
* *
* @param {Number|String} channel - zero-indexed band number to extract, or `red`, `green` or `blue` as alternative to `0`, `1` or `2` respectively. * @param {number|string} channel - zero-indexed channel/band number to extract, or `red`, `green`, `blue` or `alpha`.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid channel * @throws {Error} Invalid channel
*/ */
function extractChannel (channel) { function extractChannel (channel) {
if (channel === 'red') { const channelMap = { red: 0, green: 1, blue: 2, alpha: 3 };
channel = 0; if (Object.keys(channelMap).includes(channel)) {
} else if (channel === 'green') { channel = channelMap[channel];
channel = 1;
} else if (channel === 'blue') {
channel = 2;
} }
if (is.integer(channel) && is.inRange(channel, 0, 4)) { if (is.integer(channel) && is.inRange(channel, 0, 4)) {
this.options.extractChannel = channel; this.options.extractChannel = channel;
} else { } else {
throw is.invalidParameterError('channel', 'integer or one of: red, green, blue', channel); throw is.invalidParameterError('channel', 'integer or one of: red, green, blue, alpha', channel);
} }
return this; return this;
} }
@@ -88,10 +85,10 @@ function extractChannel (channel) {
* - sRGB: 0: Red, 1: Green, 2: Blue, 3: Alpha. * - sRGB: 0: Red, 1: Green, 2: Blue, 3: Alpha.
* - CMYK: 0: Magenta, 1: Cyan, 2: Yellow, 3: Black, 4: 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. * Buffers may be any of the image formats supported by sharp.
* For raw pixel input, the `options` object should contain a `raw` attribute, which follows the format of the attribute of the same name in the `sharp()` constructor. * 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 {Array<string|Buffer>|string|Buffer} images - one or more images (file paths, Buffers).
* @param {Object} options - image options, see `sharp()` constructor. * @param {Object} options - image options, see `sharp()` constructor.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
@@ -119,7 +116,7 @@ function joinChannel (images, options) {
* // then `O(1,1) = 0b11110111 & 0b10101010 & 0b00001111 = 0b00000010 = 2`. * // 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. * @param {string} boolOp - one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */

View File

@@ -19,7 +19,7 @@ const colourspace = {
* Tint the image using the provided chroma while preserving the image luminance. * 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. * 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. * @param {string|Object} rgb - parsed by the [color](https://www.npmjs.org/package/color) module to extract chroma values.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameter * @throws {Error} Invalid parameter
*/ */
@@ -57,7 +57,14 @@ function grayscale (grayscale) {
/** /**
* Set the output colourspace. * Set the output colourspace.
* By default output image will be web-friendly sRGB, with additional channels interpreted as alpha channels. * 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) *
* @example
* // Output 16 bits per pixel RGB
* await sharp(input)
* .toColourspace('rgb16')
* .toFile('16-bpp.png')
*
* @param {string} [colourspace] - output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568)
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -71,7 +78,7 @@ function toColourspace (colourspace) {
/** /**
* Alternative spelling of `toColourspace`. * Alternative spelling of `toColourspace`.
* @param {String} [colorspace] - output colorspace. * @param {string} [colorspace] - output colorspace.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -82,8 +89,8 @@ function toColorspace (colorspace) {
/** /**
* Update a colour attribute of the this.options Object. * Update a colour attribute of the this.options Object.
* @private * @private
* @param {String} key * @param {string} key
* @param {String|Object} value * @param {string|Object} value
* @throws {Error} Invalid value * @throws {Error} Invalid value
*/ */
function _setBackgroundColourOption (key, value) { function _setBackgroundColourOption (key, value) {

View File

@@ -72,7 +72,7 @@ const blend = {
* }); * });
* *
* @param {Object[]} images - Ordered list of images to composite * @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 bellow) * @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 {Object} [images[].input.create] - describes a blank overlay to be created.
* @param {Number} [images[].input.create.width] * @param {Number} [images[].input.create.width]
* @param {Number} [images[].input.create.height] * @param {Number} [images[].input.create.height]
@@ -100,16 +100,14 @@ function composite (images) {
if (!is.object(image)) { if (!is.object(image)) {
throw is.invalidParameterError('image to composite', 'object', image); throw is.invalidParameterError('image to composite', 'object', image);
} }
const { raw, density, limitInputPixels, sequentialRead } = image; const inputOptions = this._inputOptionsFromObject(image);
const inputOptions = [raw, density, limitInputPixels, sequentialRead].some(is.defined)
? { raw, density, limitInputPixels, sequentialRead }
: undefined;
const composite = { const composite = {
input: this._createInputDescriptor(image.input, inputOptions, { allowStream: false }), input: this._createInputDescriptor(image.input, inputOptions, { allowStream: false }),
blend: 'over', blend: 'over',
tile: false, tile: false,
left: -1, left: 0,
top: -1, top: 0,
hasOffset: false,
gravity: 0, gravity: 0,
premultiplied: false premultiplied: false
}; };
@@ -128,21 +126,23 @@ function composite (images) {
} }
} }
if (is.defined(image.left)) { if (is.defined(image.left)) {
if (is.integer(image.left) && image.left >= 0) { if (is.integer(image.left)) {
composite.left = image.left; composite.left = image.left;
} else { } else {
throw is.invalidParameterError('left', 'positive integer', image.left); throw is.invalidParameterError('left', 'integer', image.left);
} }
} }
if (is.defined(image.top)) { if (is.defined(image.top)) {
if (is.integer(image.top) && image.top >= 0) { if (is.integer(image.top)) {
composite.top = image.top; composite.top = image.top;
} else { } else {
throw is.invalidParameterError('top', 'positive integer', image.top); throw is.invalidParameterError('top', 'integer', image.top);
} }
} }
if (composite.left !== composite.top && Math.min(composite.left, composite.top) === -1) { if (is.defined(image.top) !== is.defined(image.left)) {
throw new Error('Expected both left and top to be set'); 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.defined(image.gravity)) {
if (is.integer(image.gravity) && is.inRange(image.gravity, 0, 8)) { if (is.integer(image.gravity) && is.inRange(image.gravity, 0, 8)) {

View File

@@ -18,12 +18,10 @@ try {
help.push(`- Ensure "${process.platform}" is used at install time as well as runtime`); 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)) { } else if (/dylib/.test(err.message) && /Incompatible library version/.test(err.message)) {
help.push('- Run "brew update && brew upgrade vips"'); 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 { } else {
help.push( help.push(
'- Remove the "node_modules/sharp" directory then run', '- Remove the "node_modules/sharp" directory then run',
' "npm install --ignore-scripts=false --verbose" and look for errors' ' "npm install --ignore-scripts=false --verbose sharp" and look for errors'
); );
} }
help.push( help.push(
@@ -38,15 +36,20 @@ try {
const debuglog = util.debuglog('sharp'); const debuglog = util.debuglog('sharp');
/** /**
* @constructs sharp
*
* Constructor factory to create an instance of `sharp`, to which further methods are chained. * 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. * JPEG, PNG, WebP, AVIF or TIFF format image data can be streamed out from this object.
* When using Stream based output, derived attributes are available from the `info` event. * 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. * Implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.
* *
* @constructs Sharp
*
* @emits Sharp#info
* @emits Sharp#warning
*
* @example * @example
* sharp('input.jpg') * sharp('input.jpg')
* .resize(300, 200) * .resize(300, 200)
@@ -81,30 +84,69 @@ const debuglog = util.debuglog('sharp');
* .toBuffer() * .toBuffer()
* .then( ... ); * .then( ... );
* *
* @param {(Buffer|String)} [input] - if present, can be * @example
* a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or * // Convert an animated GIF to an animated WebP
* a String containing the filesystem path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file. * await sharp('in.gif', { animated: true }).toFile('out.webp');
* JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present. *
* @example
* // Read a raw array of pixels and save it to a png
* const input = Uint8Array.from([255, 255, 255, 0, 0, 0]); // or Uint8ClampedArray
* const image = sharp(input, {
* // because the input does not contain its dimensions or how many channels it has
* // we need to specify it in the constructor options
* raw: {
* width: 2,
* height: 1,
* channels: 3
* }
* });
* await image.toFile('my-two-pixels.png');
*
* @example
* // Generate RGB Gaussian noise
* await sharp({
* create: {
* width: 300,
* height: 200,
* channels: 3,
* noise: {
* type: 'gaussian',
* mean: 128,
* sigma: 30
* }
* }
* }).toFile('noise.png');
*
* @param {(Buffer|Uint8Array|Uint8ClampedArray|string)} [input] - if present, can be
* a Buffer / Uint8Array / Uint8ClampedArray containing JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data, or
* a String containing the filesystem path to an JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image file.
* JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
* @param {Object} [options] - if present, is an Object with optional attributes. * @param {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. * @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. * 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 * @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. * (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). * 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. * @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. * This can reduce memory usage and might improve performance on some systems.
* @param {Number} [options.density=72] - number representing the DPI for vector images. * @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.pages=1] - number of pages to extract for multi-page input (GIF, WebP, AVIF, 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.page=0] - page number to start extracting from for multi-page input (GIF, WebP, AVIF, 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 {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering.
* @param {Number} [options.raw.width] * @param {number} [options.raw.width] - integral number of pixels wide.
* @param {Number} [options.raw.height] * @param {number} [options.raw.height] - integral number of pixels high.
* @param {Number} [options.raw.channels] - 1-4 * @param {number} [options.raw.channels] - integral number of channels, between 1 and 4.
* @param {Object} [options.create] - describes a new image to be created. * @param {Object} [options.create] - describes a new image to be created.
* @param {Number} [options.create.width] * @param {number} [options.create.width] - integral number of pixels wide.
* @param {Number} [options.create.height] * @param {number} [options.create.height] - integral number of pixels high.
* @param {Number} [options.create.channels] - 3-4 * @param {number} [options.create.channels] - integral number of channels, either 3 (RGB) or 4 (RGBA).
* @param {String|Object} [options.create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. * @param {string|Object} [options.create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
* @param {Object} [options.create.noise] - describes a noise to be created.
* @param {string} [options.create.noise.type] - type of generated noise, currently only `gaussian` is supported.
* @param {number} [options.create.noise.mean] - mean of pixels in generated noise.
* @param {number} [options.create.noise.sigma] - standard deviation of pixels in generated noise.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -144,6 +186,13 @@ const Sharp = function (input, options) {
extendRight: 0, extendRight: 0,
extendBackground: [0, 0, 0, 255], extendBackground: [0, 0, 0, 255],
withoutEnlargement: false, withoutEnlargement: false,
affineMatrix: [],
affineBackground: [0, 0, 0, 255],
affineIdx: 0,
affineIdy: 0,
affineOdx: 0,
affineOdy: 0,
affineInterpolator: this.constructor.interpolators.bilinear,
kernel: 'lanczos3', kernel: 'lanczos3',
fastShrinkOnLoad: true, fastShrinkOnLoad: true,
// operations // operations
@@ -181,6 +230,7 @@ const Sharp = function (input, options) {
streamOut: false, streamOut: false,
withMetadata: false, withMetadata: false,
withMetadataOrientation: -1, withMetadataOrientation: -1,
withMetadataIcc: '',
resolveWithObject: false, resolveWithObject: false,
// output format // output format
jpegQuality: 80, jpegQuality: 80,
@@ -192,7 +242,7 @@ const Sharp = function (input, options) {
jpegOptimiseCoding: true, jpegOptimiseCoding: true,
jpegQuantisationTable: 0, jpegQuantisationTable: 0,
pngProgressive: false, pngProgressive: false,
pngCompressionLevel: 9, pngCompressionLevel: 6,
pngAdaptiveFiltering: false, pngAdaptiveFiltering: false,
pngPalette: false, pngPalette: false,
pngQuality: 100, pngQuality: 100,
@@ -208,15 +258,17 @@ const Sharp = function (input, options) {
tiffCompression: 'jpeg', tiffCompression: 'jpeg',
tiffPredictor: 'horizontal', tiffPredictor: 'horizontal',
tiffPyramid: false, tiffPyramid: false,
tiffSquash: false, tiffBitdepth: 8,
tiffTile: false, tiffTile: false,
tiffTileHeight: 256, tiffTileHeight: 256,
tiffTileWidth: 256, tiffTileWidth: 256,
tiffXres: 1.0, tiffXres: 1.0,
tiffYres: 1.0, tiffYres: 1.0,
heifQuality: 80, heifQuality: 50,
heifLossless: false, heifLossless: false,
heifCompression: 'hevc', heifCompression: 'av1',
heifSpeed: 5,
heifChromaSubsampling: '4:2:0',
tileSize: 256, tileSize: 256,
tileOverlap: 0, tileOverlap: 0,
tileContainer: 'fs', tileContainer: 'fs',
@@ -226,10 +278,15 @@ const Sharp = function (input, options) {
tileAngle: 0, tileAngle: 0,
tileSkipBlanks: -1, tileSkipBlanks: -1,
tileBackground: [255, 255, 255, 255], tileBackground: [255, 255, 255, 255],
tileCentre: false,
tileId: 'https://example.com/iiif',
linearA: 1, linearA: 1,
linearB: 0, linearB: 0,
// Function to notify of libvips warnings // Function to notify of libvips warnings
debuglog: debuglog, debuglog: warning => {
this.emit('warning', warning);
debuglog(warning);
},
// Function to notify of queue length changes // Function to notify of queue length changes
queueListener: function (queueLength) { queueListener: function (queueLength) {
Sharp.queue.emit('change', queueLength); Sharp.queue.emit('change', queueLength);
@@ -253,6 +310,54 @@ util.inherits(Sharp, stream.Duplex);
* // firstWritableStream receives auto-rotated, resized readableStream * // firstWritableStream receives auto-rotated, resized readableStream
* // secondWritableStream receives auto-rotated, extracted region of 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} * @returns {Sharp}
*/ */
function clone () { function clone () {

View File

@@ -4,6 +4,17 @@ const color = require('color');
const is = require('./is'); const is = require('./is');
const sharp = require('../build/Release/sharp.node'); 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. * Create Object containing input and input-related options.
* @private * @private
@@ -20,15 +31,18 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
} else if (is.buffer(input)) { } else if (is.buffer(input)) {
// Buffer // Buffer
inputDescriptor.buffer = input; inputDescriptor.buffer = input;
} else if (is.uint8Array(input)) {
// Uint8Array or Uint8ClampedArray
inputDescriptor.buffer = Buffer.from(input.buffer);
} else if (is.plainObject(input) && !is.defined(inputOptions)) { } else if (is.plainObject(input) && !is.defined(inputOptions)) {
// Plain Object descriptor, e.g. create // Plain Object descriptor, e.g. create
inputOptions = input; inputOptions = input;
if (is.plainObject(inputOptions.raw) || is.bool(inputOptions.failOnError)) { if (_inputOptionsFromObject(inputOptions)) {
// Raw Stream // Stream with options
inputDescriptor.buffer = []; inputDescriptor.buffer = [];
} }
} else if (!is.defined(input) && !is.defined(inputOptions) && is.object(containerOptions) && containerOptions.allowStream) { } else if (!is.defined(input) && !is.defined(inputOptions) && is.object(containerOptions) && containerOptions.allowStream) {
// Stream // Stream without options
inputDescriptor.buffer = []; inputDescriptor.buffer = [];
} else { } else {
throw new Error(`Unsupported input '${input}' of type ${typeof input}${ throw new Error(`Unsupported input '${input}' of type ${typeof input}${
@@ -46,10 +60,10 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
} }
// Density // Density
if (is.defined(inputOptions.density)) { if (is.defined(inputOptions.density)) {
if (is.inRange(inputOptions.density, 1, 2400)) { if (is.inRange(inputOptions.density, 1, 100000)) {
inputDescriptor.density = inputOptions.density; inputDescriptor.density = inputOptions.density;
} else { } else {
throw is.invalidParameterError('density', 'number between 1 and 2400', inputOptions.density); throw is.invalidParameterError('density', 'number between 1 and 100000', inputOptions.density);
} }
} }
// limitInputPixels // limitInputPixels
@@ -88,6 +102,13 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
} }
} }
// Multi-page input (GIF, TIFF, PDF) // 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.defined(inputOptions.pages)) {
if (is.integer(inputOptions.pages) && is.inRange(inputOptions.pages, -1, 100000)) { if (is.integer(inputOptions.pages) && is.inRange(inputOptions.pages, -1, 100000)) {
inputDescriptor.pages = inputOptions.pages; inputDescriptor.pages = inputOptions.pages;
@@ -102,28 +123,64 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
throw is.invalidParameterError('page', 'integer between 0 and 100000', inputOptions.page); 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 // Create new image
if (is.defined(inputOptions.create)) { if (is.defined(inputOptions.create)) {
if ( if (
is.object(inputOptions.create) && is.object(inputOptions.create) &&
is.integer(inputOptions.create.width) && inputOptions.create.width > 0 && is.integer(inputOptions.create.width) && inputOptions.create.width > 0 &&
is.integer(inputOptions.create.height) && inputOptions.create.height > 0 && is.integer(inputOptions.create.height) && inputOptions.create.height > 0 &&
is.integer(inputOptions.create.channels) && is.inRange(inputOptions.create.channels, 3, 4) && is.integer(inputOptions.create.channels)
is.defined(inputOptions.create.background)
) { ) {
inputDescriptor.createWidth = inputOptions.create.width; inputDescriptor.createWidth = inputOptions.create.width;
inputDescriptor.createHeight = inputOptions.create.height; inputDescriptor.createHeight = inputOptions.create.height;
inputDescriptor.createChannels = inputOptions.create.channels; inputDescriptor.createChannels = inputOptions.create.channels;
const background = color(inputOptions.create.background); // Noise
inputDescriptor.createBackground = [ if (is.defined(inputOptions.create.noise)) {
background.red(), if (!is.object(inputOptions.create.noise)) {
background.green(), throw new Error('Expected noise to be an object');
background.blue(), }
Math.round(background.alpha() * 255) if (!is.inArray(inputOptions.create.noise.type, ['gaussian'])) {
]; throw new Error('Only gaussian noise is supported at the moment');
}
if (!is.inRange(inputOptions.create.channels, 1, 4)) {
throw is.invalidParameterError('create.channels', 'number between 1 and 4', inputOptions.create.channels);
}
inputDescriptor.createNoiseType = inputOptions.create.noise.type;
if (is.number(inputOptions.create.noise.mean) && is.inRange(inputOptions.create.noise.mean, 0, 10000)) {
inputDescriptor.createNoiseMean = inputOptions.create.noise.mean;
} else {
throw is.invalidParameterError('create.noise.mean', 'number between 0 and 10000', inputOptions.create.noise.mean);
}
if (is.number(inputOptions.create.noise.sigma) && is.inRange(inputOptions.create.noise.sigma, 0, 10000)) {
inputDescriptor.createNoiseSigma = inputOptions.create.noise.sigma;
} else {
throw is.invalidParameterError('create.noise.sigma', 'number between 0 and 10000', inputOptions.create.noise.sigma);
}
} else if (is.defined(inputOptions.create.background)) {
if (!is.inRange(inputOptions.create.channels, 3, 4)) {
throw is.invalidParameterError('create.channels', 'number between 3 and 4', inputOptions.create.channels);
}
const background = color(inputOptions.create.background);
inputDescriptor.createBackground = [
background.red(),
background.green(),
background.blue(),
Math.round(background.alpha() * 255)
];
} else {
throw new Error('Expected valid noise or background to create a new input image');
}
delete inputDescriptor.buffer; delete inputDescriptor.buffer;
} else { } else {
throw new Error('Expected width, height, channels and background to create a new input image'); throw new Error('Expected valid width, height and channels to create a new input image');
} }
} }
} else if (is.defined(inputOptions)) { } else if (is.defined(inputOptions)) {
@@ -136,7 +193,7 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
* Handle incoming Buffer chunk on Writable Stream. * Handle incoming Buffer chunk on Writable Stream.
* @private * @private
* @param {Buffer} chunk * @param {Buffer} chunk
* @param {String} encoding - unused * @param {string} encoding - unused
* @param {Function} callback * @param {Function} callback
*/ */
function _write (chunk, encoding, callback) { function _write (chunk, encoding, callback) {
@@ -172,7 +229,7 @@ function _flattenBufferIn () {
/** /**
* Are we expecting Stream-based input? * Are we expecting Stream-based input?
* @private * @private
* @returns {Boolean} * @returns {boolean}
*/ */
function _isStreamInput () { function _isStreamInput () {
return Array.isArray(this.options.input.buffer); return Array.isArray(this.options.input.buffer);
@@ -197,6 +254,7 @@ function _isStreamInput () {
* - `loop`: Number of times to loop an animated image, zero refers to a continuous loop. * - `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. * - `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 * - `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 * - `hasProfile`: Boolean indicating the presence of an embedded ICC profile
* - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel * - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
* - `orientation`: Number value of the EXIF Orientation header, if present * - `orientation`: Number value of the EXIF Orientation header, if present
@@ -279,6 +337,8 @@ function metadata (callback) {
* - `maxY` (y-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. * - `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) * - `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 * @example
* const image = sharp(inputJpg); * const image = sharp(inputJpg);
@@ -288,6 +348,10 @@ function metadata (callback) {
* // stats contains the channel-wise statistics array and the isOpaque value * // 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)` * @param {Function} [callback] - called with the arguments `(err, stats)`
* @returns {Promise<Object>} * @returns {Promise<Object>}
*/ */
@@ -337,6 +401,7 @@ function stats (callback) {
module.exports = function (Sharp) { module.exports = function (Sharp) {
Object.assign(Sharp.prototype, { Object.assign(Sharp.prototype, {
// Private // Private
_inputOptionsFromObject,
_createInputDescriptor, _createInputDescriptor,
_write, _write,
_flattenBufferIn, _flattenBufferIn,

View File

@@ -21,7 +21,7 @@ const object = function (val) {
* @private * @private
*/ */
const plainObject = function (val) { const plainObject = function (val) {
return object(val) && Object.prototype.toString.call(val) === '[object Object]'; return Object.prototype.toString.call(val) === '[object Object]';
}; };
/** /**
@@ -45,7 +45,16 @@ const bool = function (val) {
* @private * @private
*/ */
const buffer = function (val) { const buffer = function (val) {
return object(val) && val instanceof Buffer; return val instanceof Buffer;
};
/**
* Is this value a Uint8Array or Uint8ClampedArray object?
* @private
*/
const uint8Array = function (val) {
// allow both since Uint8ClampedArray simply clamps the values between 0-255
return val instanceof Uint8Array || val instanceof Uint8ClampedArray;
}; };
/** /**
@@ -69,7 +78,7 @@ const number = function (val) {
* @private * @private
*/ */
const integer = function (val) { const integer = function (val) {
return number(val) && val % 1 === 0; return Number.isInteger(val);
}; };
/** /**
@@ -85,14 +94,14 @@ const inRange = function (val, min, max) {
* @private * @private
*/ */
const inArray = function (val, list) { const inArray = function (val, list) {
return list.indexOf(val) !== -1; return list.includes(val);
}; };
/** /**
* Create an Error with a message relating to an invalid parameter. * Create an Error with a message relating to an invalid parameter.
* *
* @param {String} name - parameter name. * @param {string} name - parameter name.
* @param {String} expected - description of the type/value/range expected. * @param {string} expected - description of the type/value/range expected.
* @param {*} actual - the value received. * @param {*} actual - the value received.
* @returns {Error} Containing the formatted message. * @returns {Error} Containing the formatted message.
* @private * @private
@@ -110,6 +119,7 @@ module.exports = {
fn: fn, fn: fn,
bool: bool, bool: bool,
buffer: buffer, buffer: buffer,
uint8Array: uint8Array,
string: string, string: string,
number: number, number: number,
integer: integer, integer: integer,

View File

@@ -37,10 +37,28 @@ const cachePath = function () {
return libvipsCachePath; return libvipsCachePath;
}; };
const log = function (item) {
if (item instanceof Error) {
console.error(`sharp: ${item.message}`);
} else {
console.log(`sharp: ${item}`);
}
};
const isRosetta = function () {
/* istanbul ignore next */
if (process.platform === 'darwin' && process.arch === 'x64') {
const translated = spawnSync('sysctl sysctl.proc_translated', spawnSyncOptions).stdout;
return (translated || '').trim() === 'sysctl.proc_translated: 1';
}
return false;
};
const globalLibvipsVersion = function () { const globalLibvipsVersion = function () {
if (process.platform !== 'win32') { if (process.platform !== 'win32') {
const globalLibvipsVersion = spawnSync(`PKG_CONFIG_PATH="${pkgConfigPath()}" pkg-config --modversion vips-cpp`, spawnSyncOptions).stdout || ''; const globalLibvipsVersion = spawnSync(`PKG_CONFIG_PATH="${pkgConfigPath()}" pkg-config --modversion vips-cpp`, spawnSyncOptions).stdout;
return globalLibvipsVersion.trim(); /* istanbul ignore next */
return (globalLibvipsVersion || '').trim();
} else { } else {
return ''; return '';
} }
@@ -48,24 +66,18 @@ const globalLibvipsVersion = function () {
const hasVendoredLibvips = function () { const hasVendoredLibvips = function () {
const currentPlatformId = platform(); const currentPlatformId = platform();
const vendorPath = path.join(__dirname, '..', 'vendor'); const vendorPath = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion);
let vendorVersionId;
let vendorPlatformId; let vendorPlatformId;
try { try {
vendorVersionId = require(path.join(vendorPath, 'versions.json')).vips;
vendorPlatformId = require(path.join(vendorPath, 'platform.json')); vendorPlatformId = require(path.join(vendorPath, 'platform.json'));
} catch (err) {} } catch (err) {}
/* istanbul ignore if */
if (vendorVersionId && vendorVersionId !== minimumLibvipsVersion) {
throw new Error(`Found vendored libvips v${vendorVersionId} but require v${minimumLibvipsVersion}. Please remove the 'node_modules/sharp/vendor' directory and run 'npm install'.`);
}
/* istanbul ignore else */ /* istanbul ignore else */
if (vendorPlatformId) { if (vendorPlatformId) {
/* istanbul ignore else */ /* istanbul ignore else */
if (currentPlatformId === vendorPlatformId) { if (currentPlatformId === vendorPlatformId) {
return true; return true;
} else { } else {
throw new Error(`'${vendorPlatformId}' binaries cannot be used on the '${currentPlatformId}' platform. Please remove the 'node_modules/sharp/vendor' directory and run 'npm install'.`); 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 { } else {
return false; return false;
@@ -87,7 +99,10 @@ const useGlobalLibvips = function () {
if (Boolean(env.SHARP_IGNORE_GLOBAL_LIBVIPS) === true) { if (Boolean(env.SHARP_IGNORE_GLOBAL_LIBVIPS) === true) {
return false; return false;
} }
/* istanbul ignore next */
if (isRosetta()) {
return false;
}
const globalVipsVersion = globalLibvipsVersion(); const globalVipsVersion = globalLibvipsVersion();
return !!globalVipsVersion && /* istanbul ignore next */ return !!globalVipsVersion && /* istanbul ignore next */
semver.gte(globalVipsVersion, minimumLibvipsVersion); semver.gte(globalVipsVersion, minimumLibvipsVersion);
@@ -97,6 +112,7 @@ module.exports = {
minimumLibvipsVersion, minimumLibvipsVersion,
minimumLibvipsVersionLabelled, minimumLibvipsVersionLabelled,
cachePath, cachePath,
log,
globalLibvipsVersion, globalLibvipsVersion,
hasVendoredLibvips, hasVendoredLibvips,
pkgConfigPath, pkgConfigPath,

View File

@@ -32,9 +32,9 @@ const is = require('./is');
* }); * });
* readableStream.pipe(pipeline); * readableStream.pipe(pipeline);
* *
* @param {Number} [angle=auto] angle of rotation. * @param {number} [angle=auto] angle of rotation.
* @param {Object} [options] - if present, is an Object with optional attributes. * @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 {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} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -82,15 +82,112 @@ function flop (flop) {
return this; 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 = [].concat(...matrix);
if (flatMatrix.length === 4 && flatMatrix.every(is.number)) {
this.options.affineMatrix = flatMatrix;
} else {
throw is.invalidParameterError('matrix', '1x4 or 2x2 array', matrix);
}
if (is.defined(options)) {
if (is.object(options)) {
this._setBackgroundColourOption('affineBackground', options.background);
if (is.defined(options.idx)) {
if (is.number(options.idx)) {
this.options.affineIdx = options.idx;
} else {
throw is.invalidParameterError('options.idx', 'number', options.idx);
}
}
if (is.defined(options.idy)) {
if (is.number(options.idy)) {
this.options.affineIdy = options.idy;
} else {
throw is.invalidParameterError('options.idy', 'number', options.idy);
}
}
if (is.defined(options.odx)) {
if (is.number(options.odx)) {
this.options.affineOdx = options.odx;
} else {
throw is.invalidParameterError('options.odx', 'number', options.odx);
}
}
if (is.defined(options.ody)) {
if (is.number(options.ody)) {
this.options.affineOdy = options.ody;
} else {
throw is.invalidParameterError('options.ody', 'number', options.ody);
}
}
if (is.defined(options.interpolator)) {
if (is.inArray(options.interpolator, Object.values(this.constructor.interpolators))) {
this.options.affineInterpolator = options.interpolator;
} else {
throw is.invalidParameterError('options.interpolator', 'valid interpolator name', options.interpolator);
}
}
} else {
throw is.invalidParameterError('options', 'object', options);
}
}
return this;
}
/** /**
* Sharpen the image. * Sharpen the image.
* When used without parameters, performs a fast, mild sharpen of the output 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. * 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. * 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} [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} [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. * @param {number} [jagged=2.0] - the level of sharpening to apply to "jagged" areas.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -129,7 +226,7 @@ function sharpen (sigma, flat, jagged) {
/** /**
* Apply median filter. * Apply median filter.
* When used without parameters the default window is 3x3. * When used without parameters the default window is 3x3.
* @param {Number} [size=3] square mask size: size x size * @param {number} [size=3] square mask size: size x size
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -150,7 +247,7 @@ function median (size) {
* Blur the image. * Blur the image.
* When used without parameters, performs a fast, mild blur of the output 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. * 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`. * @param {number} [sigma] a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -173,7 +270,7 @@ function blur (sigma) {
/** /**
* Merge alpha transparency channel, if any, with a background. * Merge alpha transparency channel, if any, with a background.
* @param {Object} [options] * @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. * @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} * @returns {Sharp}
*/ */
function flatten (options) { function flatten (options) {
@@ -193,8 +290,8 @@ function flatten (options) {
* *
* Supply a second argument to use a different output gamma value, otherwise the first value is used in both cases. * 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} [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`) * @param {number} [gammaOut] value between 1.0 and 3.0. (optional, defaults to same as `gamma`)
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -264,11 +361,11 @@ function normalize (normalize) {
* }); * });
* *
* @param {Object} kernel * @param {Object} kernel
* @param {Number} kernel.width - width of the kernel in pixels. * @param {number} kernel.width - width of the kernel in pixels.
* @param {Number} kernel.height - 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 {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.scale=sum] - the scale of the kernel in pixels.
* @param {Number} [kernel.offset=0] - the offset of the kernel in pixels. * @param {number} [kernel.offset=0] - the offset of the kernel in pixels.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -300,7 +397,7 @@ function convolve (kernel) {
/** /**
* Any pixel value greather than or equal to the threshold value will be set to 255, otherwise it will be set to 0. * 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 {number} [threshold=128] - a value in the range 0-255 representing the level at which the threshold will be applied.
* @param {Object} [options] * @param {Object} [options]
* @param {Boolean} [options.greyscale=true] - convert to single channel greyscale. * @param {Boolean} [options.greyscale=true] - convert to single channel greyscale.
* @param {Boolean} [options.grayscale=true] - alternative spelling for greyscale. * @param {Boolean} [options.grayscale=true] - alternative spelling for greyscale.
@@ -331,13 +428,13 @@ function threshold (threshold, options) {
* This operation creates an output image where each pixel is the result of * 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. * 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 {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 {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]
* @param {Object} [options.raw] - describes operand when using raw pixel data. * @param {Object} [options.raw] - describes operand when using raw pixel data.
* @param {Number} [options.raw.width] * @param {number} [options.raw.width]
* @param {Number} [options.raw.height] * @param {number} [options.raw.height]
* @param {Number} [options.raw.channels] * @param {number} [options.raw.channels]
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -353,8 +450,8 @@ function boolean (operand, operator, options) {
/** /**
* Apply the linear formula a * input + b to the image (levels adjustment) * Apply the linear formula a * input + b to the image (levels adjustment)
* @param {Number} [a=1.0] multiplier * @param {number} [a=1.0] multiplier
* @param {Number} [b=0.0] offset * @param {number} [b=0.0] offset
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -394,7 +491,7 @@ function linear (a, b) {
* // With this example input, a sepia filter has been applied * // With this example input, a sepia filter has been applied
* }); * });
* *
* @param {Array<Array<Number>>} 3x3 Recombination matrix * @param {Array<Array<number>>} inputMatrix - 3x3 Recombination matrix
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -440,9 +537,9 @@ function recomb (inputMatrix) {
* }); * });
* *
* @param {Object} [options] * @param {Object} [options]
* @param {Number} [options.brightness] Brightness multiplier * @param {number} [options.brightness] Brightness multiplier
* @param {Number} [options.saturation] Saturation multiplier * @param {number} [options.saturation] Saturation multiplier
* @param {Number} [options.hue] Degrees for hue rotation * @param {number} [options.hue] Degrees for hue rotation
* @returns {Sharp} * @returns {Sharp}
*/ */
function modulate (options) { function modulate (options) {
@@ -482,6 +579,7 @@ module.exports = function (Sharp) {
rotate, rotate,
flip, flip,
flop, flop,
affine,
sharpen, sharpen,
median, median,
blur, blur,

View File

@@ -6,19 +6,23 @@ const sharp = require('../build/Release/sharp.node');
const formats = new Map([ const formats = new Map([
['heic', 'heif'], ['heic', 'heif'],
['heif', 'heif'], ['heif', 'heif'],
['avif', 'avif'],
['jpeg', 'jpeg'], ['jpeg', 'jpeg'],
['jpg', 'jpeg'], ['jpg', 'jpeg'],
['png', 'png'], ['png', 'png'],
['raw', 'raw'], ['raw', 'raw'],
['tiff', 'tiff'], ['tiff', 'tiff'],
['webp', 'webp'] ['webp', 'webp'],
['gif', 'gif']
]); ]);
const errMagickSave = new Error('GIF output requires libvips with support for ImageMagick');
/** /**
* Write output image data to a file. * Write output image data to a file.
* *
* If an explicit output format is not selected, it will be inferred from the extension, * 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. * with JPEG, PNG, WebP, AVIF, TIFF, DZI, and libvips' V format supported.
* Note that raw pixel data is only supported for buffer output. * Note that raw pixel data is only supported for buffer output.
* *
* By default all metadata will be removed, which includes EXIF-based orientation. * By default all metadata will be removed, which includes EXIF-based orientation.
@@ -36,7 +40,7 @@ const formats = new Map([
* .then(info => { ... }) * .then(info => { ... })
* .catch(err => { ... }); * .catch(err => { ... });
* *
* @param {String} fileOut - the path to write the image data to. * @param {string} fileOut - the path to write the image data to.
* @param {Function} [callback] - called on completion with two arguments `(err, info)`. * @param {Function} [callback] - called on completion with two arguments `(err, info)`.
* `info` contains the output image `format`, `size` (bytes), `width`, `height`, * `info` contains the output image `format`, `size` (bytes), `width`, `height`,
* `channels` and `premultiplied` (indicating if premultiplication was used). * `channels` and `premultiplied` (indicating if premultiplication was used).
@@ -45,32 +49,30 @@ const formats = new Map([
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
function toFile (fileOut, callback) { function toFile (fileOut, callback) {
if (!fileOut || fileOut.length === 0) { let err;
const errOutputInvalid = new Error('Missing output file path'); if (!is.string(fileOut)) {
err = new Error('Missing output file path');
} else if (this.options.input.file === fileOut) {
err = new Error('Cannot use same file for input and output');
} else if (this.options.formatOut === 'input' && fileOut.toLowerCase().endsWith('.gif') && !this.constructor.format.magick.output.file) {
err = errMagickSave;
}
if (err) {
if (is.fn(callback)) { if (is.fn(callback)) {
callback(errOutputInvalid); callback(err);
} else { } else {
return Promise.reject(errOutputInvalid); return Promise.reject(err);
} }
} else { } else {
if (this.options.input.file === fileOut) { this.options.fileOut = fileOut;
const errOutputIsInput = new Error('Cannot use same file for input and output'); return this._pipeline(callback);
if (is.fn(callback)) {
callback(errOutputIsInput);
} else {
return Promise.reject(errOutputIsInput);
}
} else {
this.options.fileOut = fileOut;
return this._pipeline(callback);
}
} }
return this; return this;
} }
/** /**
* Write output to a Buffer. * Write output to a Buffer.
* JPEG, PNG, WebP, TIFF and RAW output are supported. * JPEG, PNG, WebP, AVIF, TIFF and raw pixel data 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. * If no explicit format is set, the output format will match the input image, except GIF and SVG input which become PNG output.
* *
@@ -102,8 +104,22 @@ function toFile (fileOut, callback) {
* .then(({ data, info }) => { ... }) * .then(({ data, info }) => { ... })
* .catch(err => { ... }); * .catch(err => { ... });
* *
* @example
* const data = await sharp('my-image.jpg')
* // output the raw pixels
* .raw()
* .toBuffer();
*
* // create a more type safe way to work with the raw pixel data
* // this will not copy the data, instead it will change `data`s underlying ArrayBuffer
* // so `data` and `pixelArray` point to the same memory location
* const pixelArray = new Uint8ClampedArray(data.buffer);
*
* // When you are done changing the pixelArray, sharp takes the `pixelArray` as an input
* await sharp(pixelArray).toFile('my-changed-image.jpg');
*
* @param {Object} [options] * @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 {boolean} [options.resolveWithObject] Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
* @param {Function} [callback] * @param {Function} [callback]
* @returns {Promise<Buffer>} - when no callback is provided * @returns {Promise<Buffer>} - when no callback is provided
*/ */
@@ -118,8 +134,11 @@ function toBuffer (options, callback) {
/** /**
* Include all metadata (EXIF, XMP, IPTC) from the input image in the output image. * Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
* The default behaviour, when `withMetadata` is not used, is to strip all metadata and convert to the device-independent sRGB colour space. * This will also convert to and add a web-friendly sRGB ICC profile unless a custom
* This will also convert to and add a web-friendly sRGB ICC profile. * 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 * @example
* sharp('input.jpg') * sharp('input.jpg')
@@ -128,7 +147,8 @@ function toBuffer (options, callback) {
* .then(info => { ... }); * .then(info => { ... });
* *
* @param {Object} [options] * @param {Object} [options]
* @param {Number} [options.orientation] value between 1 and 8, used to update the EXIF `Orientation` tag. * @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} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -142,6 +162,13 @@ function withMetadata (options) {
throw is.invalidParameterError('orientation', 'integer between 1 and 8', options.orientation); 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; return this;
} }
@@ -155,13 +182,13 @@ function withMetadata (options) {
* .toFormat('png') * .toFormat('png')
* .toBuffer(); * .toBuffer();
* *
* @param {(String|Object)} format - as a String or an Object with an 'id' attribute * @param {(string|Object)} format - as a string or an Object with an 'id' attribute
* @param {Object} options - output options * @param {Object} options - output options
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} unsupported format or options * @throws {Error} unsupported format or options
*/ */
function toFormat (format, options) { function toFormat (format, options) {
const actualFormat = formats.get(is.object(format) && is.string(format.id) ? format.id : format); const actualFormat = formats.get((is.object(format) && is.string(format.id) ? format.id : format).toLowerCase());
if (!actualFormat) { if (!actualFormat) {
throw is.invalidParameterError('format', `one of: ${[...formats.keys()].join(', ')}`, format); throw is.invalidParameterError('format', `one of: ${[...formats.keys()].join(', ')}`, format);
} }
@@ -180,19 +207,26 @@ function toFormat (format, options) {
* }) * })
* .toBuffer(); * .toBuffer();
* *
* @example
* // Use mozjpeg to reduce output JPEG file size (slower)
* const data = await sharp(input)
* .jpeg({ mozjpeg: true })
* .toBuffer();
*
* @param {Object} [options] - output options * @param {Object} [options] - output options
* @param {Number} [options.quality=80] - quality, integer 1-100 * @param {number} [options.quality=80] - quality, integer 1-100
* @param {Boolean} [options.progressive=false] - use progressive (interlace) scan * @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 when quality <= 90 * @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.trellisQuantisation=false] - apply trellis quantisation, requires libvips compiled with support for mozjpeg * @param {boolean} [options.optimiseCoding=true] - optimise Huffman coding tables
* @param {Boolean} [options.overshootDeringing=false] - apply overshoot deringing, requires libvips compiled with support for mozjpeg * @param {boolean} [options.optimizeCoding=true] - alternative spelling of optimiseCoding
* @param {Boolean} [options.optimiseScans=false] - optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg * @param {boolean} [options.mozjpeg=false] - use mozjpeg defaults, equivalent to `{ trellisQuantisation: true, overshootDeringing: true, optimiseScans: true, quantisationTable: 3 }`
* @param {Boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans * @param {boolean} [options.trellisQuantisation=false] - apply trellis quantisation
* @param {Boolean} [options.optimiseCoding=true] - optimise Huffman coding tables * @param {boolean} [options.overshootDeringing=false] - apply overshoot deringing
* @param {Boolean} [options.optimizeCoding=true] - alternative spelling of optimiseCoding * @param {boolean} [options.optimiseScans=false] - optimise progressive scans, forces progressive
* @param {Number} [options.quantisationTable=0] - quantization table to use, integer 0-8, requires libvips compiled with support for mozjpeg * @param {boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans
* @param {Number} [options.quantizationTable=0] - alternative spelling of quantisationTable * @param {number} [options.quantisationTable=0] - quantization table to use, integer 0-8
* @param {Boolean} [options.force=true] - force JPEG output, otherwise attempt to use input format * @param {number} [options.quantizationTable=0] - alternative spelling of quantisationTable
* @param {boolean} [options.force=true] - force JPEG output, otherwise attempt to use input format
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
@@ -215,6 +249,23 @@ function jpeg (options) {
throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling); throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling);
} }
} }
const optimiseCoding = is.bool(options.optimizeCoding) ? options.optimizeCoding : options.optimiseCoding;
if (is.defined(optimiseCoding)) {
this._setBooleanOption('jpegOptimiseCoding', optimiseCoding);
}
if (is.defined(options.mozjpeg)) {
if (is.bool(options.mozjpeg)) {
if (options.mozjpeg) {
this.options.jpegTrellisQuantisation = true;
this.options.jpegOvershootDeringing = true;
this.options.jpegOptimiseScans = true;
this.options.jpegProgressive = true;
this.options.jpegQuantisationTable = 3;
}
} else {
throw is.invalidParameterError('mozjpeg', 'boolean', options.mozjpeg);
}
}
const trellisQuantisation = is.bool(options.trellisQuantization) ? options.trellisQuantization : options.trellisQuantisation; const trellisQuantisation = is.bool(options.trellisQuantization) ? options.trellisQuantization : options.trellisQuantisation;
if (is.defined(trellisQuantisation)) { if (is.defined(trellisQuantisation)) {
this._setBooleanOption('jpegTrellisQuantisation', trellisQuantisation); this._setBooleanOption('jpegTrellisQuantisation', trellisQuantisation);
@@ -229,10 +280,6 @@ function jpeg (options) {
this.options.jpegProgressive = true; 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; const quantisationTable = is.number(options.quantizationTable) ? options.quantizationTable : options.quantisationTable;
if (is.defined(quantisationTable)) { if (is.defined(quantisationTable)) {
if (is.integer(quantisationTable) && is.inRange(quantisationTable, 0, 8)) { if (is.integer(quantisationTable) && is.inRange(quantisationTable, 0, 8)) {
@@ -248,25 +295,32 @@ function jpeg (options) {
/** /**
* Use these PNG options for output image. * Use these PNG options for output image.
* *
* PNG output is always full colour at 8 or 16 bits per pixel. * By default, PNG output is full colour at 8 or 16 bits per pixel.
* Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel. * Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel.
* Set `palette` to `true` for slower, indexed PNG output.
* *
* @example * @example
* // Convert any input to PNG output * // Convert any input to full colour PNG output
* const data = await sharp(input) * const data = await sharp(input)
* .png() * .png()
* .toBuffer(); * .toBuffer();
* *
* @example
* // Convert any input to indexed PNG output (slower)
* const data = await sharp(input)
* .png({ palette: true })
* .toBuffer();
*
* @param {Object} [options] * @param {Object} [options]
* @param {Boolean} [options.progressive=false] - use progressive (interlace) scan * @param {boolean} [options.progressive=false] - use progressive (interlace) scan
* @param {Number} [options.compressionLevel=9] - zlib compression level, 0-9 * @param {number} [options.compressionLevel=6] - zlib compression level, 0 (fastest, largest) to 9 (slowest, smallest)
* @param {Boolean} [options.adaptiveFiltering=false] - use adaptive row filtering * @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 {boolean} [options.palette=false] - quantise to a palette-based image with alpha transparency support
* @param {Number} [options.quality=100] - use the lowest number of colours needed to achieve given quality, 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`
* @param {Number} [options.colours=256] - maximum number of palette entries, requires libvips compiled with support for libimagequant * @param {number} [options.colours=256] - maximum number of palette entries, sets `palette` to `true`
* @param {Number} [options.colors=256] - alternative spelling of `options.colours`, requires libvips compiled with support for libimagequant * @param {number} [options.colors=256] - alternative spelling of `options.colours`, sets `palette` to `true`
* @param {Number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, requires libvips compiled with support for libimagequant * @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, sets `palette` to `true`
* @param {Boolean} [options.force=true] - force PNG output, otherwise attempt to use input format * @param {boolean} [options.force=true] - force PNG output, otherwise attempt to use input format
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
@@ -287,28 +341,30 @@ function png (options) {
} }
if (is.defined(options.palette)) { if (is.defined(options.palette)) {
this._setBooleanOption('pngPalette', options.palette); this._setBooleanOption('pngPalette', options.palette);
if (this.options.pngPalette) { } else if (is.defined(options.quality) || is.defined(options.colours || options.colors) || is.defined(options.dither)) {
if (is.defined(options.quality)) { this._setBooleanOption('pngPalette', true);
if (is.integer(options.quality) && is.inRange(options.quality, 0, 100)) { }
this.options.pngQuality = options.quality; if (this.options.pngPalette) {
} else { if (is.defined(options.quality)) {
throw is.invalidParameterError('quality', 'integer between 0 and 100', 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)) { const colours = options.colours || options.colors;
if (is.integer(colours) && is.inRange(colours, 2, 256)) { if (is.defined(colours)) {
this.options.pngColours = colours; if (is.integer(colours) && is.inRange(colours, 2, 256)) {
} else { this.options.pngColours = colours;
throw is.invalidParameterError('colours', 'integer between 2 and 256', 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)) { if (is.defined(options.dither)) {
this.options.pngDither = options.dither; if (is.number(options.dither) && is.inRange(options.dither, 0, 1)) {
} else { this.options.pngDither = options.dither;
throw is.invalidParameterError('dither', 'number between 0.0 and 1.0', options.dither); } else {
} throw is.invalidParameterError('dither', 'number between 0.0 and 1.0', options.dither);
} }
} }
} }
@@ -326,13 +382,16 @@ function png (options) {
* .toBuffer(); * .toBuffer();
* *
* @param {Object} [options] - output options * @param {Object} [options] - output options
* @param {Number} [options.quality=80] - quality, integer 1-100 * @param {number} [options.quality=80] - quality, integer 1-100
* @param {Number} [options.alphaQuality=100] - quality of alpha layer, integer 0-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.lossless=false] - use lossless compression mode
* @param {Boolean} [options.nearLossless=false] - use near_lossless compression mode * @param {boolean} [options.nearLossless=false] - use near_lossless compression mode
* @param {Boolean} [options.smartSubsample=false] - use high quality chroma subsampling * @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.reductionEffort=4] - level of CPU effort to reduce file size, integer 0-6
* @param {Boolean} [options.force=true] - force WebP output, otherwise attempt to use input format * @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} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
@@ -367,9 +426,73 @@ function webp (options) {
throw is.invalidParameterError('reductionEffort', 'integer between 0 and 6', options.reductionEffort); throw is.invalidParameterError('reductionEffort', 'integer between 0 and 6', options.reductionEffort);
} }
} }
trySetAnimationOptions(options, this.options);
return this._updateFormatOut('webp', 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 errMagickSave;
}
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. * Use these TIFF options for output image.
* *
@@ -378,23 +501,23 @@ function webp (options) {
* sharp('input.svg') * sharp('input.svg')
* .tiff({ * .tiff({
* compression: 'lzw', * compression: 'lzw',
* squash: true * bitdepth: 1
* }) * })
* .toFile('1-bpp-output.tiff') * .toFile('1-bpp-output.tiff')
* .then(info => { ... }); * .then(info => { ... });
* *
* @param {Object} [options] - output options * @param {Object} [options] - output options
* @param {Number} [options.quality=80] - quality, integer 1-100 * @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.force=true] - force TIFF output, otherwise attempt to use input format
* @param {Boolean} [options.compression='jpeg'] - compression options: lzw, deflate, jpeg, ccittfax4 * @param {string} [options.compression='jpeg'] - compression options: lzw, deflate, jpeg, ccittfax4
* @param {Boolean} [options.predictor='horizontal'] - compression predictor options: none, horizontal, float * @param {string} [options.predictor='horizontal'] - compression predictor options: none, horizontal, float
* @param {Boolean} [options.pyramid=false] - write an image pyramid * @param {boolean} [options.pyramid=false] - write an image pyramid
* @param {Boolean} [options.tile=false] - write a tiled tiff * @param {boolean} [options.tile=false] - write a tiled tiff
* @param {Boolean} [options.tileWidth=256] - horizontal tile size * @param {number} [options.tileWidth=256] - horizontal tile size
* @param {Boolean} [options.tileHeight=256] - vertical tile size * @param {number} [options.tileHeight=256] - vertical tile size
* @param {Number} [options.xres=1.0] - horizontal resolution in pixels/mm * @param {number} [options.xres=1.0] - horizontal resolution in pixels/mm
* @param {Number} [options.yres=1.0] - vertical resolution in pixels/mm * @param {number} [options.yres=1.0] - vertical resolution in pixels/mm
* @param {Boolean} [options.squash=false] - squash 8-bit images down to 1 bit * @param {number} [options.bitdepth=8] - reduce bitdepth to 1, 2 or 4 bit
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
@@ -407,8 +530,12 @@ function tiff (options) {
throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality); throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
} }
} }
if (is.defined(options.squash)) { if (is.defined(options.bitdepth)) {
this._setBooleanOption('tiffSquash', options.squash); 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 // tiling
if (is.defined(options.tile)) { if (is.defined(options.tile)) {
@@ -467,29 +594,44 @@ function tiff (options) {
return this._updateFormatOut('tiff', options); 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 {number} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
* @param {string} [options.chromaSubsampling='4:2:0'] - set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling, requires libvips v8.11.0
* @returns {Sharp}
* @throws {Error} Invalid options
*/
function avif (options) {
return this.heif({ ...options, compression: 'av1' });
}
/** /**
* Use these HEIF options for output image. * Use these HEIF options for output image.
* *
* Support for HEIF (HEIC/AVIF) is experimental. * Support for patent-encumbered HEIC images requires the use of a
* Do not use this in production systems. * globally-installed libvips compiled with support for libheif, libde265 and x265.
*
* Requires a custom, globally-installed libvips compiled with support for libheif.
*
* Most versions of libheif support only the patent-encumbered HEVC compression format.
* *
* @since 0.23.0 * @since 0.23.0
* *
* @param {Object} [options] - output options * @param {Object} [options] - output options
* @param {Number} [options.quality=80] - quality, integer 1-100 * @param {number} [options.quality=50] - quality, integer 1-100
* @param {Boolean} [options.compression='hevc'] - compression format: hevc, avc, jpeg, av1 * @param {string} [options.compression='av1'] - compression format: av1, hevc
* @param {Boolean} [options.lossless=false] - use lossless compression * @param {boolean} [options.lossless=false] - use lossless compression
* @param {number} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
* @param {string} [options.chromaSubsampling='4:2:0'] - set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling, requires libvips v8.11.0
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
function heif (options) { function heif (options) {
if (!this.constructor.format.heif.output.buffer) {
throw new Error('The heif operation requires libvips to have been installed with support for libheif');
}
if (is.object(options)) { if (is.object(options)) {
if (is.defined(options.quality)) { if (is.defined(options.quality)) {
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) { if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
@@ -506,10 +648,24 @@ function heif (options) {
} }
} }
if (is.defined(options.compression)) { if (is.defined(options.compression)) {
if (is.string(options.compression) && is.inArray(options.compression, ['hevc', 'avc', 'jpeg', 'av1'])) { if (is.string(options.compression) && is.inArray(options.compression, ['av1', 'hevc'])) {
this.options.heifCompression = options.compression; this.options.heifCompression = options.compression;
} else { } else {
throw is.invalidParameterError('compression', 'one of: hevc, avc, jpeg, av1', options.compression); 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);
}
}
if (is.defined(options.chromaSubsampling)) {
if (is.string(options.chromaSubsampling) && is.inArray(options.chromaSubsampling, ['4:2:0', '4:4:4'])) {
this.options.heifChromaSubsampling = options.chromaSubsampling;
} else {
throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling);
} }
} }
} }
@@ -532,7 +688,7 @@ function heif (options) {
* const data = await sharp('input.png') * const data = await sharp('input.png')
* .ensureAlpha() * .ensureAlpha()
* .extractChannel(3) * .extractChannel(3)
* .colourspace('b-w') * .toColourspace('b-w')
* .raw() * .raw()
* .toBuffer(); * .toBuffer();
* *
@@ -561,14 +717,17 @@ function raw () {
* }); * });
* *
* @param {Object} [options] * @param {Object} [options]
* @param {Number} [options.size=256] tile size in pixels, a value between 1 and 8192. * @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.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 {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|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 {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 {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.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file).
* @param {String} [options.layout='dz'] filesystem layout, possible values are `dz`, `zoomify` or `google`. * @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.
* @param {string} [options.id='https://example.com/iiif'] when `layout` is `iiif`, sets the `@id` attribute of `info.json`
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -603,10 +762,10 @@ function tile (options) {
} }
// Layout // Layout
if (is.defined(options.layout)) { if (is.defined(options.layout)) {
if (is.string(options.layout) && is.inArray(options.layout, ['dz', 'google', 'zoomify'])) { if (is.string(options.layout) && is.inArray(options.layout, ['dz', 'google', 'iiif', 'zoomify'])) {
this.options.tileLayout = options.layout; this.options.tileLayout = options.layout;
} else { } else {
throw is.invalidParameterError('layout', 'one of: dz, google, zoomify', options.layout); throw is.invalidParameterError('layout', 'one of: dz, google, iiif, zoomify', options.layout);
} }
} }
// Angle of rotation, // Angle of rotation,
@@ -637,6 +796,19 @@ function tile (options) {
} else if (is.defined(options.layout) && options.layout === 'google') { } else if (is.defined(options.layout) && options.layout === 'google') {
this.options.tileSkipBlanks = 5; 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);
}
// @id attribute for IIIF layout
if (is.defined(options.id)) {
if (is.string(options.id)) {
this.options.tileId = options.id;
} else {
throw is.invalidParameterError('id', 'string', options.id);
}
}
} }
// Format // Format
if (is.inArray(this.options.formatOut, ['jpeg', 'png', 'webp'])) { if (is.inArray(this.options.formatOut, ['jpeg', 'png', 'webp'])) {
@@ -651,9 +823,9 @@ function tile (options) {
* Update the output format unless options.force is false, * Update the output format unless options.force is false,
* in which case revert to input format. * in which case revert to input format.
* @private * @private
* @param {String} formatOut * @param {string} formatOut
* @param {Object} [options] * @param {Object} [options]
* @param {Boolean} [options.force=true] - force output format, otherwise attempt to use input format * @param {boolean} [options.force=true] - force output format, otherwise attempt to use input format
* @returns {Sharp} * @returns {Sharp}
*/ */
function _updateFormatOut (formatOut, options) { function _updateFormatOut (formatOut, options) {
@@ -664,10 +836,10 @@ function _updateFormatOut (formatOut, options) {
} }
/** /**
* Update a Boolean attribute of the this.options Object. * Update a boolean attribute of the this.options Object.
* @private * @private
* @param {String} key * @param {string} key
* @param {Boolean} val * @param {boolean} val
* @throws {Error} Invalid key * @throws {Error} Invalid key
*/ */
function _setBooleanOption (key, val) { function _setBooleanOption (key, val) {
@@ -795,7 +967,9 @@ module.exports = function (Sharp) {
png, png,
webp, webp,
tiff, tiff,
avif,
heif, heif,
gif,
raw, raw,
tile, tile,
// Private // Private

View File

@@ -13,7 +13,8 @@ module.exports = function () {
const platformId = [`${platform}${libc}`]; const platformId = [`${platform}${libc}`];
if (arch === 'arm') { if (arch === 'arm') {
platformId.push(`armv${env.npm_config_arm_version || process.config.variables.arm_version || '6'}`); 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') { } else if (arch === 'arm64') {
platformId.push(`arm64v${env.npm_config_arm_version || '8'}`); platformId.push(`arm64v${env.npm_config_arm_version || '8'}`);
} else { } else {

View File

@@ -96,8 +96,8 @@ function isRotationExpected (options) {
* Resize image to `width`, `height` or `width x height`. * 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: * When both a `width` and `height` are provided, the possible methods by which the image should **fit** these are:
* - `cover`: Crop to cover both provided dimensions (the default). * - `cover`: (default) Preserving aspect ratio, ensure the image covers both provided dimensions by cropping/clipping to fit.
* - `contain`: Embed within both provided dimensions. * - `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. * - `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. * - `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. * - `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.
@@ -190,8 +190,8 @@ function isRotationExpected (options) {
* .toBuffer() * .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} [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 {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 {Object} [options]
* @param {String} [options.width] - alternative means of specifying `width`. If both are present this take priority. * @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.height] - alternative means of specifying `height`. If both are present this take priority.
@@ -302,11 +302,11 @@ function resize (width, height, options) {
* }) * })
* ... * ...
* *
* @param {(Number|Object)} extend - single pixel count to add to all edges or an Object with per-edge counts * @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.top]
* @param {Number} [extend.left] * @param {number} [extend.left]
* @param {Number} [extend.bottom] * @param {number} [extend.bottom]
* @param {Number} [extend.right] * @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. * @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} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
@@ -336,7 +336,7 @@ function extend (extend) {
} }
/** /**
* Extract a region of the image. * Extract/crop a region of the image.
* *
* - Use `extract` before `resize` for pre-resize extraction. * - Use `extract` before `resize` for pre-resize extraction.
* - Use `extract` after `resize` for post-resize extraction. * - Use `extract` after `resize` for post-resize extraction.
@@ -358,10 +358,10 @@ function extend (extend) {
* }); * });
* *
* @param {Object} options - describes the region to extract using integral pixel values * @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.left - zero-indexed offset from left edge
* @param {Number} options.top - zero-indexed offset from top edge * @param {number} options.top - zero-indexed offset from top edge
* @param {Number} options.width - width of region to extract * @param {number} options.width - width of region to extract
* @param {Number} options.height - height of region to extract * @param {number} options.height - height of region to extract
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -386,9 +386,10 @@ function extract (options) {
* Trim "boring" pixels from all edges that contain values similar to the top-left pixel. * 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. * Images consisting entirely of a single colour will calculate "boring" using the alpha channel, if any.
* *
* The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` properties. * 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. * @param {number} [threshold=10] the allowed difference from the top-left pixel, a number greater than zero.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */

View File

@@ -1,6 +1,8 @@
'use strict'; 'use strict';
const events = require('events'); const events = require('events');
const detectLibc = require('detect-libc');
const is = require('./is'); const is = require('./is');
const sharp = require('../build/Release/sharp.node'); const sharp = require('../build/Release/sharp.node');
@@ -13,6 +15,26 @@ const sharp = require('../build/Release/sharp.node');
*/ */
const format = sharp.format(); 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. * An Object containing the version numbers of libvips and its dependencies.
* @member * @member
@@ -23,7 +45,7 @@ let versions = {
vips: sharp.libvipsVersion() vips: sharp.libvipsVersion()
}; };
try { try {
versions = require('../vendor/versions.json'); versions = require(`../vendor/${versions.vips}/versions.json`);
} catch (err) {} } catch (err) {}
/** /**
@@ -39,10 +61,10 @@ try {
* sharp.cache( { files: 0 } ); * sharp.cache( { files: 0 } );
* sharp.cache(false); * 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 {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.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.files=20] - is the maximum number of files to hold open
* @param {Number} [options.items=100] - is the maximum number of operations to cache * @param {number} [options.items=100] - is the maximum number of operations to cache
* @returns {Object} * @returns {Object}
*/ */
function cache (options) { function cache (options) {
@@ -64,8 +86,12 @@ cache(true);
/** /**
* Gets or, when a concurrency is provided, sets * Gets or, when a concurrency is provided, sets
* the number of threads _libvips'_ should create to process each image. * 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 default value is the number of CPU cores,
* except when using glibc-based Linux without jemalloc,
* where the default is `1` to help reduce memory fragmentation.
*
* A value of `0` will reset this to the number of CPU cores.
* *
* The maximum number of images that can be processed in parallel * The maximum number of images that can be processed in parallel
* is limited by libuv's `UV_THREADPOOL_SIZE` environment variable. * is limited by libuv's `UV_THREADPOOL_SIZE` environment variable.
@@ -77,12 +103,17 @@ cache(true);
* sharp.concurrency(2); // 2 * sharp.concurrency(2); // 2
* sharp.concurrency(0); // 4 * sharp.concurrency(0); // 4
* *
* @param {Number} [concurrency] * @param {number} [concurrency]
* @returns {Number} concurrency * @returns {number} concurrency
*/ */
function concurrency (concurrency) { function concurrency (concurrency) {
return sharp.concurrency(is.integer(concurrency) ? concurrency : null); return sharp.concurrency(is.integer(concurrency) ? concurrency : null);
} }
/* istanbul ignore next */
if (detectLibc.family === detectLibc.GLIBC && !sharp._isUsingJemalloc()) {
// Reduce default concurrency to 1 when using glibc memory allocator
sharp.concurrency(1);
}
/** /**
* An EventEmitter that emits a `change` event when a task is either: * An EventEmitter that emits a `change` event when a task is either:
@@ -124,8 +155,8 @@ function counters () {
* const simd = sharp.simd(false); * const simd = sharp.simd(false);
* // prevent libvips from using liborc at runtime * // prevent libvips from using liborc at runtime
* *
* @param {Boolean} [simd=true] * @param {boolean} [simd=true]
* @returns {Boolean} * @returns {boolean}
*/ */
function simd (simd) { function simd (simd) {
return sharp.simd(is.bool(simd) ? simd : null); return sharp.simd(is.bool(simd) ? simd : null);
@@ -137,15 +168,12 @@ simd(true);
* @private * @private
*/ */
module.exports = function (Sharp) { module.exports = function (Sharp) {
[ Sharp.cache = cache;
cache, Sharp.concurrency = concurrency;
concurrency, Sharp.counters = counters;
counters, Sharp.simd = simd;
simd
].forEach(function (f) {
Sharp[f.name] = f;
});
Sharp.format = format; Sharp.format = format;
Sharp.interpolators = interpolators;
Sharp.versions = versions; Sharp.versions = versions;
Sharp.queue = queue; Sharp.queue = queue;
}; };

View File

@@ -1,7 +1,7 @@
{ {
"name": "sharp", "name": "sharp",
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images", "description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, AVIF and TIFF images",
"version": "0.25.1", "version": "0.28.0-alpha1",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://github.com/lovell/sharp", "homepage": "https://github.com/lovell/sharp",
"contributors": [ "contributors": [
@@ -65,17 +65,26 @@
"Andargor <andargor@yahoo.com>", "Andargor <andargor@yahoo.com>",
"Paul Neave <paul.neave@gmail.com>", "Paul Neave <paul.neave@gmail.com>",
"Brendan Kennedy <brenwken@gmail.com>", "Brendan Kennedy <brenwken@gmail.com>",
"Brychan Bennett-Odlum <git@brychan.io>" "Brychan Bennett-Odlum <git@brychan.io>",
"Edward Silverton <e.silverton@gmail.com>",
"Roman Malieiev <aromaleev@gmail.com>",
"Tomas Szabo <tomas.szabo@deftomat.com>",
"Robert O'Rourke <robert@o-rourke.org>",
"Guillermo Alfonso Varela Chouciño <guillevch@gmail.com>",
"Christian Flintrup <chr@gigahost.dk>",
"Manan Jadhav <manan@motionden.com>",
"Leon Radley <leon@radley.se>",
"alza54 <alza54@thiocod.in>"
], ],
"scripts": { "scripts": {
"install": "(node install/libvips && node install/dll-copy && prebuild-install --runtime=napi) || (node-gyp rebuild && node install/dll-copy)", "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.*", "clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*",
"test": "semistandard && cpplint && npm run test-unit && npm run test-licensing && prebuild-ci", "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-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-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
"test-coverage": "./test/coverage/report.sh", "test-coverage": "./test/coverage/report.sh",
"test-leak": "./test/leak/leak.sh", "test-leak": "./test/leak/leak.sh",
"docs-build": "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", "docs-build": "documentation lint lib && node docs/build && node docs/search-index/build",
"docs-serve": "cd docs && npx serve", "docs-serve": "cd docs && npx serve",
"docs-publish": "cd docs && npx firebase-tools deploy --project pixelplumbing --only hosting:pixelplumbing-sharp" "docs-publish": "cd docs && npx firebase-tools deploy --project pixelplumbing --only hosting:pixelplumbing-sharp"
}, },
@@ -94,6 +103,7 @@
"jpeg", "jpeg",
"png", "png",
"webp", "webp",
"avif",
"tiff", "tiff",
"gif", "gif",
"svg", "svg",
@@ -107,35 +117,35 @@
"vips" "vips"
], ],
"dependencies": { "dependencies": {
"color": "^3.1.2", "color": "^3.1.3",
"detect-libc": "^1.0.3", "detect-libc": "^1.0.3",
"node-addon-api": "^2.0.0", "node-addon-api": "^3.1.0",
"npmlog": "^4.1.2", "prebuild-install": "^6.0.1",
"prebuild-install": "^5.3.3", "semver": "^7.3.4",
"semver": "^7.1.3",
"simple-get": "^3.1.0", "simple-get": "^3.1.0",
"tar": "^6.0.1", "tar-fs": "^2.1.1",
"tunnel-agent": "^0.6.0" "tunnel-agent": "^0.6.0"
}, },
"devDependencies": { "devDependencies": {
"async": "^3.2.0", "async": "^3.2.0",
"cc": "^2.0.1", "cc": "^3.0.1",
"decompress-zip": "^0.3.2", "decompress-zip": "^0.3.3",
"documentation": "^12.1.4", "documentation": "^13.1.1",
"exif-reader": "^1.0.3", "exif-reader": "^1.0.3",
"icc": "^1.0.0", "icc": "^2.0.0",
"license-checker": "^25.0.1", "license-checker": "^25.0.1",
"mocha": "^7.1.0", "mocha": "^8.3.1",
"mock-fs": "^4.11.0", "mock-fs": "^4.13.0",
"nyc": "^15.0.0", "nyc": "^15.1.0",
"prebuild": "^10.0.0", "prebuild": "^10.0.1",
"prebuild-ci": "^3.1.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"semistandard": "^14.2.0" "semistandard": "^16.0.0"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"config": { "config": {
"libvips": "8.9.1" "libvips": "8.10.6-alpha3",
"runtime": "napi",
"target": 3
}, },
"engines": { "engines": {
"node": ">=10" "node": ">=10"

View File

@@ -17,6 +17,7 @@
#include <string.h> #include <string.h>
#include <vector> #include <vector>
#include <queue> #include <queue>
#include <map>
#include <mutex> // NOLINT(build/c++11) #include <mutex> // NOLINT(build/c++11)
#include <napi.h> #include <napi.h>
@@ -41,6 +42,9 @@ namespace sharp {
int32_t AttrAsInt32(Napi::Object obj, std::string attr) { int32_t AttrAsInt32(Napi::Object obj, std::string attr) {
return obj.Get(attr).As<Napi::Number>().Int32Value(); 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) { double AttrAsDouble(Napi::Object obj, std::string attr) {
return obj.Get(attr).As<Napi::Number>().DoubleValue(); return obj.Get(attr).As<Napi::Number>().DoubleValue();
} }
@@ -50,13 +54,21 @@ namespace sharp {
bool AttrAsBool(Napi::Object obj, std::string attr) { bool AttrAsBool(Napi::Object obj, std::string attr) {
return obj.Get(attr).As<Napi::Boolean>().Value(); return obj.Get(attr).As<Napi::Boolean>().Value();
} }
std::vector<double> AttrAsRgba(Napi::Object obj, std::string attr) { std::vector<double> AttrAsVectorOfDouble(Napi::Object obj, std::string attr) {
Napi::Array background = obj.Get(attr).As<Napi::Array>(); Napi::Array napiArray = obj.Get(attr).As<Napi::Array>();
std::vector<double> rgba(background.Length()); std::vector<double> vectorOfDouble(napiArray.Length());
for (unsigned int i = 0; i < background.Length(); i++) { for (unsigned int i = 0; i < napiArray.Length(); i++) {
rgba[i] = AttrAsDouble(background, i); vectorOfDouble[i] = AttrAsDouble(napiArray, i);
} }
return rgba; 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 // Create an InputDescriptor instance from a Napi::Object describing an input image
@@ -88,12 +100,22 @@ namespace sharp {
if (HasAttr(input, "page")) { if (HasAttr(input, "page")) {
descriptor->page = AttrAsUint32(input, "page"); descriptor->page = AttrAsUint32(input, "page");
} }
// Multi-level input (OpenSlide)
if (HasAttr(input, "level")) {
descriptor->level = AttrAsUint32(input, "level");
}
// Create new image // Create new image
if (HasAttr(input, "createChannels")) { if (HasAttr(input, "createChannels")) {
descriptor->createChannels = AttrAsUint32(input, "createChannels"); descriptor->createChannels = AttrAsUint32(input, "createChannels");
descriptor->createWidth = AttrAsUint32(input, "createWidth"); descriptor->createWidth = AttrAsUint32(input, "createWidth");
descriptor->createHeight = AttrAsUint32(input, "createHeight"); descriptor->createHeight = AttrAsUint32(input, "createHeight");
descriptor->createBackground = AttrAsRgba(input, "createBackground"); if (HasAttr(input, "createNoiseType")) {
descriptor->createNoiseType = AttrAsStr(input, "createNoiseType");
descriptor->createNoiseMean = AttrAsDouble(input, "createNoiseMean");
descriptor->createNoiseSigma = AttrAsDouble(input, "createNoiseSigma");
} else {
descriptor->createBackground = AttrAsVectorOfDouble(input, "createBackground");
}
} }
// Limit input images to a given number of pixels, where pixels = width * height // Limit input images to a given number of pixels, where pixels = width * height
descriptor->limitInputPixels = AttrAsUint32(input, "limitInputPixels"); descriptor->limitInputPixels = AttrAsUint32(input, "limitInputPixels");
@@ -121,6 +143,9 @@ namespace sharp {
bool IsWebp(std::string const &str) { bool IsWebp(std::string const &str) {
return EndsWith(str, ".webp") || EndsWith(str, ".WEBP"); 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) { bool IsTiff(std::string const &str) {
return EndsWith(str, ".tif") || EndsWith(str, ".tiff") || EndsWith(str, ".TIF") || EndsWith(str, ".TIFF"); return EndsWith(str, ".tif") || EndsWith(str, ".tiff") || EndsWith(str, ".TIF") || EndsWith(str, ".TIFF");
} }
@@ -161,6 +186,7 @@ namespace sharp {
case ImageType::OPENSLIDE: id = "openslide"; break; case ImageType::OPENSLIDE: id = "openslide"; break;
case ImageType::PPM: id = "ppm"; break; case ImageType::PPM: id = "ppm"; break;
case ImageType::FITS: id = "fits"; break; case ImageType::FITS: id = "fits"; break;
case ImageType::EXR: id = "exr"; break;
case ImageType::VIPS: id = "vips"; break; case ImageType::VIPS: id = "vips"; break;
case ImageType::RAW: id = "raw"; break; case ImageType::RAW: id = "raw"; break;
case ImageType::UNKNOWN: id = "unknown"; break; case ImageType::UNKNOWN: id = "unknown"; break;
@@ -169,32 +195,50 @@ namespace sharp {
return id; return id;
} }
/**
* Regenerate this table with something like:
*
* $ vips -l foreign | grep -i load | awk '{ print $2, $1; }'
*
* Plus a bit of editing.
*/
std::map<std::string, ImageType> loaderToType = {
{ "VipsForeignLoadJpegFile", ImageType::JPEG },
{ "VipsForeignLoadJpegBuffer", ImageType::JPEG },
{ "VipsForeignLoadPngFile", ImageType::PNG },
{ "VipsForeignLoadPngBuffer", ImageType::PNG },
{ "VipsForeignLoadWebpFile", ImageType::WEBP },
{ "VipsForeignLoadWebpBuffer", ImageType::WEBP },
{ "VipsForeignLoadTiffFile", ImageType::TIFF },
{ "VipsForeignLoadTiffBuffer", ImageType::TIFF },
{ "VipsForeignLoadGifFile", ImageType::GIF },
{ "VipsForeignLoadGifBuffer", ImageType::GIF },
{ "VipsForeignLoadSvgFile", ImageType::SVG },
{ "VipsForeignLoadSvgBuffer", ImageType::SVG },
{ "VipsForeignLoadHeifFile", ImageType::HEIF },
{ "VipsForeignLoadHeifBuffer", ImageType::HEIF },
{ "VipsForeignLoadPdfFile", ImageType::PDF },
{ "VipsForeignLoadPdfBuffer", ImageType::PDF },
{ "VipsForeignLoadMagickFile", ImageType::MAGICK },
{ "VipsForeignLoadMagickBuffer", ImageType::MAGICK },
{ "VipsForeignLoadOpenslide", ImageType::OPENSLIDE },
{ "VipsForeignLoadPpmFile", ImageType::PPM },
{ "VipsForeignLoadFits", ImageType::FITS },
{ "VipsForeignLoadOpenexr", ImageType::EXR },
{ "VipsForeignLoadVips", ImageType::VIPS },
{ "VipsForeignLoadRaw", ImageType::RAW }
};
/* /*
Determine image format of a buffer. Determine image format of a buffer.
*/ */
ImageType DetermineImageType(void *buffer, size_t const length) { ImageType DetermineImageType(void *buffer, size_t const length) {
ImageType imageType = ImageType::UNKNOWN; ImageType imageType = ImageType::UNKNOWN;
char const *load = vips_foreign_find_load_buffer(buffer, length); char const *load = vips_foreign_find_load_buffer(buffer, length);
if (load != NULL) { if (load != nullptr) {
std::string const loader = load; auto it = loaderToType.find(load);
if (EndsWith(loader, "JpegBuffer")) { if (it != loaderToType.end()) {
imageType = ImageType::JPEG; imageType = it->second;
} 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, "HeifBuffer")) {
imageType = ImageType::HEIF;
} else if (EndsWith(loader, "PdfBuffer")) {
imageType = ImageType::PDF;
} else if (EndsWith(loader, "MagickBuffer")) {
imageType = ImageType::MAGICK;
} }
} }
return imageType; return imageType;
@@ -207,36 +251,12 @@ namespace sharp {
ImageType imageType = ImageType::UNKNOWN; ImageType imageType = ImageType::UNKNOWN;
char const *load = vips_foreign_find_load(file); char const *load = vips_foreign_find_load(file);
if (load != nullptr) { if (load != nullptr) {
std::string const loader = load; auto it = loaderToType.find(load);
if (EndsWith(loader, "JpegFile")) { if (it != loaderToType.end()) {
imageType = ImageType::JPEG; imageType = it->second;
} else if (EndsWith(loader, "PngFile")) {
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, "HeifFile")) {
imageType = ImageType::HEIF;
} 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, "Vips")) {
imageType = ImageType::VIPS;
} else if (EndsWith(loader, "Magick") || EndsWith(loader, "MagickFile")) {
imageType = ImageType::MAGICK;
} }
} else { } else {
if (EndsWith(vips::VError().what(), " not found\n")) { if (EndsWith(vips::VError().what(), " does not exist\n")) {
imageType = ImageType::MISSING; imageType = ImageType::MISSING;
} }
} }
@@ -248,6 +268,8 @@ namespace sharp {
*/ */
bool ImageTypeSupportsPage(ImageType imageType) { bool ImageTypeSupportsPage(ImageType imageType) {
return return
imageType == ImageType::WEBP ||
imageType == ImageType::MAGICK ||
imageType == ImageType::GIF || imageType == ImageType::GIF ||
imageType == ImageType::TIFF || imageType == ImageType::TIFF ||
imageType == ImageType::HEIF || imageType == ImageType::HEIF ||
@@ -279,6 +301,9 @@ namespace sharp {
vips::VOption *option = VImage::option() vips::VOption *option = VImage::option()
->set("access", descriptor->access) ->set("access", descriptor->access)
->set("fail", descriptor->failOnError); ->set("fail", descriptor->failOnError);
if (imageType == ImageType::SVG) {
option->set("unlimited", TRUE);
}
if (imageType == ImageType::SVG || imageType == ImageType::PDF) { if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
option->set("dpi", descriptor->density); option->set("dpi", descriptor->density);
} }
@@ -289,6 +314,9 @@ namespace sharp {
option->set("n", descriptor->pages); option->set("n", descriptor->pages);
option->set("page", descriptor->page); 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); image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option);
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) { if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
image = SetDensity(image, descriptor->density); image = SetDensity(image, descriptor->density);
@@ -303,15 +331,35 @@ namespace sharp {
} else { } else {
if (descriptor->createChannels > 0) { if (descriptor->createChannels > 0) {
// Create new image // Create new image
std::vector<double> background = { if (descriptor->createNoiseType == "gaussian") {
descriptor->createBackground[0], int const channels = descriptor->createChannels;
descriptor->createBackground[1], image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight);
descriptor->createBackground[2] std::vector<VImage> bands = {};
}; bands.reserve(channels);
if (descriptor->createChannels == 4) { for (int _band = 0; _band < channels; _band++) {
background.push_back(descriptor->createBackground[3]); bands.push_back(image.gaussnoise(
descriptor->createWidth,
descriptor->createHeight,
VImage::option()->set("mean", descriptor->createNoiseMean)->set("sigma", descriptor->createNoiseSigma)));
}
image = image.bandjoin(bands);
image = image.cast(VipsBandFormat::VIPS_FORMAT_UCHAR);
if (channels < 3) {
image = image.colourspace(VIPS_INTERPRETATION_B_W);
} else {
image = image.colourspace(VIPS_INTERPRETATION_sRGB);
}
} else {
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 = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight).new_from_image(background);
image.get_image()->Type = VIPS_INTERPRETATION_sRGB; image.get_image()->Type = VIPS_INTERPRETATION_sRGB;
imageType = ImageType::RAW; imageType = ImageType::RAW;
} else { } else {
@@ -325,6 +373,9 @@ namespace sharp {
vips::VOption *option = VImage::option() vips::VOption *option = VImage::option()
->set("access", descriptor->access) ->set("access", descriptor->access)
->set("fail", descriptor->failOnError); ->set("fail", descriptor->failOnError);
if (imageType == ImageType::SVG) {
option->set("unlimited", TRUE);
}
if (imageType == ImageType::SVG || imageType == ImageType::PDF) { if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
option->set("dpi", descriptor->density); option->set("dpi", descriptor->density);
} }
@@ -335,6 +386,9 @@ namespace sharp {
option->set("n", descriptor->pages); option->set("n", descriptor->pages);
option->set("page", descriptor->page); option->set("page", descriptor->page);
} }
if (imageType == ImageType::OPENSLIDE) {
option->set("level", descriptor->level);
}
image = VImage::new_from_file(descriptor->file.data(), option); image = VImage::new_from_file(descriptor->file.data(), option);
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) { if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
image = SetDensity(image, descriptor->density); image = SetDensity(image, descriptor->density);
@@ -367,12 +421,7 @@ namespace sharp {
Uses colour space interpretation with number of channels to guess this. Uses colour space interpretation with number of channels to guess this.
*/ */
bool HasAlpha(VImage image) { bool HasAlpha(VImage image) {
int const bands = image.bands(); return image.has_alpha();
VipsInterpretation const interpretation = image.interpretation();
return (
(bands == 2 && interpretation == VIPS_INTERPRETATION_B_W) ||
(bands == 4 && interpretation != VIPS_INTERPRETATION_CMYK) ||
(bands == 5 && interpretation == VIPS_INTERPRETATION_CMYK));
} }
/* /*
@@ -404,6 +453,38 @@ namespace sharp {
return copy; 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;
}
/* /*
Does this image have a non-default density? Does this image have a non-default density?
*/ */
@@ -434,14 +515,21 @@ namespace sharp {
Check the proposed format supports the current dimensions. Check the proposed format supports the current dimensions.
*/ */
void AssertImageTypeDimensions(VImage image, ImageType const imageType) { 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 (imageType == ImageType::JPEG) {
if (image.width() > 65535 || image.height() > 65535) { if (image.width() > 65535 || height > 65535) {
throw vips::VError("Processed image is too large for the JPEG format"); throw vips::VError("Processed image is too large for the JPEG format");
} }
} else if (imageType == ImageType::WEBP) { } else if (imageType == ImageType::WEBP) {
if (image.width() > 16383 || image.height() > 16383) { if (image.width() > 16383 || height > 16383) {
throw vips::VError("Processed image is too large for the WebP format"); 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");
}
} }
} }
@@ -598,26 +686,18 @@ namespace sharp {
int top = 0; int top = 0;
// assign only if valid // assign only if valid
if (x >= 0 && x < (inWidth - outWidth)) { if (x < (inWidth - outWidth)) {
left = x; left = x;
} else if (x >= (inWidth - outWidth)) { } else if (x >= (inWidth - outWidth)) {
left = inWidth - outWidth; left = inWidth - outWidth;
} }
if (y >= 0 && y < (inHeight - outHeight)) { if (y < (inHeight - outHeight)) {
top = y; top = y;
} else if (y >= (inHeight - outHeight)) { } else if (y >= (inHeight - outHeight)) {
top = inHeight - outHeight; top = inHeight - outHeight;
} }
// the resulting left and top could have been outside the image after calculation from bottom/right edges
if (left < 0) {
left = 0;
}
if (top < 0) {
top = 0;
}
return std::make_tuple(left, top); return std::make_tuple(left, top);
} }
@@ -704,4 +784,25 @@ namespace sharp {
return std::make_tuple(image, alphaColour); 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 } // namespace sharp

View File

@@ -24,8 +24,10 @@
// Verify platform and compiler compatibility // Verify platform and compiler compatibility
#if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 9)) #if (VIPS_MAJOR_VERSION < 8) || \
#error "libvips version 8.9.1+ is required - please see https://sharp.pixelplumbing.com/install" (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 10) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 10 && VIPS_MICRO_VERSION < 6)
#error "libvips version 8.10.6+ is required - please see https://sharp.pixelplumbing.com/install"
#endif #endif
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6))) #if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
@@ -57,10 +59,14 @@ namespace sharp {
int rawHeight; int rawHeight;
int pages; int pages;
int page; int page;
int level;
int createChannels; int createChannels;
int createWidth; int createWidth;
int createHeight; int createHeight;
std::vector<double> createBackground; std::vector<double> createBackground;
std::string createNoiseType;
double createNoiseMean;
double createNoiseSigma;
InputDescriptor(): InputDescriptor():
buffer(nullptr), buffer(nullptr),
@@ -75,10 +81,13 @@ namespace sharp {
rawHeight(0), rawHeight(0),
pages(1), pages(1),
page(0), page(0),
level(0),
createChannels(0), createChannels(0),
createWidth(0), createWidth(0),
createHeight(0), createHeight(0),
createBackground{ 0.0, 0.0, 0.0, 255.0 } {} createBackground{ 0.0, 0.0, 0.0, 255.0 },
createNoiseMean(0.0),
createNoiseSigma(0.0) {}
}; };
// Convenience methods to access the attributes of a Napi::Object // Convenience methods to access the attributes of a Napi::Object
@@ -86,10 +95,12 @@ namespace sharp {
std::string AttrAsStr(Napi::Object obj, std::string attr); std::string AttrAsStr(Napi::Object obj, std::string attr);
uint32_t AttrAsUint32(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, 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, std::string attr);
double AttrAsDouble(Napi::Object obj, unsigned int const attr); double AttrAsDouble(Napi::Object obj, unsigned int const attr);
bool AttrAsBool(Napi::Object obj, std::string attr); bool AttrAsBool(Napi::Object obj, std::string attr);
std::vector<double> AttrAsRgba(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 // Create an InputDescriptor instance from a Napi::Object describing an input image
InputDescriptor* CreateInputDescriptor(Napi::Object input); InputDescriptor* CreateInputDescriptor(Napi::Object input);
@@ -107,6 +118,7 @@ namespace sharp {
OPENSLIDE, OPENSLIDE,
PPM, PPM,
FITS, FITS,
EXR,
VIPS, VIPS,
RAW, RAW,
UNKNOWN, UNKNOWN,
@@ -123,6 +135,7 @@ namespace sharp {
bool IsJpeg(std::string const &str); bool IsJpeg(std::string const &str);
bool IsPng(std::string const &str); bool IsPng(std::string const &str);
bool IsWebp(std::string const &str); bool IsWebp(std::string const &str);
bool IsGif(std::string const &str);
bool IsTiff(std::string const &str); bool IsTiff(std::string const &str);
bool IsHeic(std::string const &str); bool IsHeic(std::string const &str);
bool IsHeif(std::string const &str); bool IsHeif(std::string const &str);
@@ -182,6 +195,12 @@ namespace sharp {
*/ */
VImage 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? Does this image have a non-default density?
*/ */
@@ -269,6 +288,16 @@ namespace sharp {
*/ */
std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> 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 } // namespace sharp
#endif // SRC_COMMON_H_ #endif // SRC_COMMON_H_

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
// bodies for vips operations // bodies for vips operations
// Wed 01 Jan 2020 12:22:12 PM CET // Sun 5 Jul 22:36:37 BST 2020
// this file is generated automatically, do not edit! // this file is generated automatically, do not edit!
VImage VImage::CMC2LCh( VOption *options ) const VImage VImage::CMC2LCh( VOption *options ) const
@@ -754,6 +754,18 @@ VImage VImage::csvload( const char *filename, VOption *options )
return( out ); return( out );
} }
VImage VImage::csvload_source( VSource source, VOption *options )
{
VImage out;
call( "csvload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
void VImage::csvsave( const char *filename, VOption *options ) const void VImage::csvsave( const char *filename, VOption *options ) const
{ {
call( "csvsave", call( "csvsave",
@@ -762,6 +774,14 @@ void VImage::csvsave( const char *filename, VOption *options ) const
set( "filename", filename ) ); set( "filename", filename ) );
} }
void VImage::csvsave_target( VTarget target, VOption *options ) const
{
call( "csvsave_target",
(options ? options : VImage::option())->
set( "in", *this )->
set( "target", target ) );
}
VImage VImage::dE00( VImage right, VOption *options ) const VImage VImage::dE00( VImage right, VOption *options ) const
{ {
VImage out; VImage out;
@@ -1218,6 +1238,18 @@ VImage VImage::gifload_buffer( VipsBlob *buffer, VOption *options )
return( out ); return( out );
} }
VImage VImage::gifload_source( VSource source, VOption *options )
{
VImage out;
call( "gifload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
VImage VImage::globalbalance( VOption *options ) const VImage VImage::globalbalance( VOption *options ) const
{ {
VImage out; VImage out;
@@ -1297,6 +1329,18 @@ VImage VImage::heifload_buffer( VipsBlob *buffer, VOption *options )
return( out ); return( out );
} }
VImage VImage::heifload_source( VSource source, VOption *options )
{
VImage out;
call( "heifload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
void VImage::heifsave( const char *filename, VOption *options ) const void VImage::heifsave( const char *filename, VOption *options ) const
{ {
call( "heifsave", call( "heifsave",
@@ -1317,6 +1361,14 @@ VipsBlob *VImage::heifsave_buffer( VOption *options ) const
return( buffer ); return( buffer );
} }
void VImage::heifsave_target( VTarget target, VOption *options ) const
{
call( "heifsave_target",
(options ? options : VImage::option())->
set( "in", *this )->
set( "target", target ) );
}
VImage VImage::hist_cum( VOption *options ) const VImage VImage::hist_cum( VOption *options ) const
{ {
VImage out; VImage out;
@@ -2028,6 +2080,18 @@ VImage VImage::matload( const char *filename, VOption *options )
return( out ); return( out );
} }
VImage VImage::matrixinvert( VOption *options ) const
{
VImage out;
call( "matrixinvert",
(options ? options : VImage::option())->
set( "in", *this )->
set( "out", &out ) );
return( out );
}
VImage VImage::matrixload( const char *filename, VOption *options ) VImage VImage::matrixload( const char *filename, VOption *options )
{ {
VImage out; VImage out;
@@ -2040,6 +2104,18 @@ VImage VImage::matrixload( const char *filename, VOption *options )
return( out ); return( out );
} }
VImage VImage::matrixload_source( VSource source, VOption *options )
{
VImage out;
call( "matrixload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
void VImage::matrixprint( VOption *options ) const void VImage::matrixprint( VOption *options ) const
{ {
call( "matrixprint", call( "matrixprint",
@@ -2055,6 +2131,14 @@ void VImage::matrixsave( const char *filename, VOption *options ) const
set( "filename", filename ) ); set( "filename", filename ) );
} }
void VImage::matrixsave_target( VTarget target, VOption *options ) const
{
call( "matrixsave_target",
(options ? options : VImage::option())->
set( "in", *this )->
set( "target", target ) );
}
double VImage::max( VOption *options ) const double VImage::max( VOption *options ) const
{ {
double out; double out;
@@ -2256,6 +2340,18 @@ VImage VImage::pdfload_buffer( VipsBlob *buffer, VOption *options )
return( out ); return( out );
} }
VImage VImage::pdfload_source( VSource source, VOption *options )
{
VImage out;
call( "pdfload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
int VImage::percent( double percent, VOption *options ) const int VImage::percent( double percent, VOption *options ) const
{ {
int threshold; int threshold;
@@ -2371,6 +2467,18 @@ VImage VImage::ppmload( const char *filename, VOption *options )
return( out ); return( out );
} }
VImage VImage::ppmload_source( VSource source, VOption *options )
{
VImage out;
call( "ppmload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
void VImage::ppmsave( const char *filename, VOption *options ) const void VImage::ppmsave( const char *filename, VOption *options ) const
{ {
call( "ppmsave", call( "ppmsave",
@@ -2379,6 +2487,14 @@ void VImage::ppmsave( const char *filename, VOption *options ) const
set( "filename", filename ) ); set( "filename", filename ) );
} }
void VImage::ppmsave_target( VTarget target, VOption *options ) const
{
call( "ppmsave_target",
(options ? options : VImage::option())->
set( "in", *this )->
set( "target", target ) );
}
VImage VImage::premultiply( VOption *options ) const VImage VImage::premultiply( VOption *options ) const
{ {
VImage out; VImage out;

View File

@@ -74,6 +74,18 @@ class MetadataWorker : public Napi::AsyncWorker {
if (image.get_typeof("heif-primary") == G_TYPE_INT) { if (image.get_typeof("heif-primary") == G_TYPE_INT) {
baton->pagePrimary = image.get_int("heif-primary"); baton->pagePrimary = image.get_int("heif-primary");
} }
if (image.get_typeof("heif-compression") == VIPS_TYPE_REF_STRING) {
baton->compression = image.get_string("heif-compression");
}
if (image.get_typeof("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); baton->hasProfile = sharp::HasProfile(image);
// Derived attributes // Derived attributes
baton->hasAlpha = sharp::HasAlpha(image); baton->hasAlpha = sharp::HasAlpha(image);
@@ -177,6 +189,20 @@ class MetadataWorker : public Napi::AsyncWorker {
if (baton->pagePrimary > -1) { if (baton->pagePrimary > -1) {
info.Set("pagePrimary", baton->pagePrimary); info.Set("pagePrimary", baton->pagePrimary);
} }
if (!baton->compression.empty()) {
info.Set("compression", baton->compression);
}
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("hasProfile", baton->hasProfile);
info.Set("hasAlpha", baton->hasAlpha); info.Set("hasAlpha", baton->hasAlpha);
if (baton->orientation > 0) { if (baton->orientation > 0) {
@@ -229,6 +255,7 @@ Napi::Value metadata(const Napi::CallbackInfo& info) {
// Join queue for worker thread // Join queue for worker thread
Napi::Function callback = info[1].As<Napi::Function>(); Napi::Function callback = info[1].As<Napi::Function>();
MetadataWorker *worker = new MetadataWorker(callback, baton, debuglog); MetadataWorker *worker = new MetadataWorker(callback, baton, debuglog);
worker->Receiver().Set("options", options);
worker->Queue(); worker->Queue();
// Increment queued task counter // Increment queued task counter

View File

@@ -39,6 +39,8 @@ struct MetadataBaton {
int loop; int loop;
std::vector<int> delay; std::vector<int> delay;
int pagePrimary; int pagePrimary;
std::string compression;
std::vector<std::pair<int, int>> levels;
bool hasProfile; bool hasProfile;
bool hasAlpha; bool hasAlpha;
int orientation; int orientation;

View File

@@ -27,29 +27,6 @@ using vips::VImage;
using vips::VError; using vips::VError;
namespace sharp { namespace sharp {
/*
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;
}
/* /*
* Tint an image using the specified chroma, preserving the original image luminance * Tint an image using the specified chroma, preserving the original image luminance
*/ */
@@ -172,8 +149,8 @@ namespace sharp {
*/ */
VImage Recomb(VImage image, std::unique_ptr<double[]> const &matrix) { VImage Recomb(VImage image, std::unique_ptr<double[]> const &matrix) {
double *m = matrix.get(); double *m = matrix.get();
image = image.colourspace(VIPS_INTERPRETATION_sRGB);
return image return image
.colourspace(VIPS_INTERPRETATION_sRGB)
.recomb(image.bands() == 3 .recomb(image.bands() == 3
? VImage::new_from_memory( ? VImage::new_from_memory(
m, 9 * sizeof(double), 3, 3, 1, VIPS_FORMAT_DOUBLE m, 9 * sizeof(double), 3, 3, 1, VIPS_FORMAT_DOUBLE

View File

@@ -25,16 +25,6 @@ using vips::VImage;
namespace sharp { namespace sharp {
/*
Removes alpha channel, if any.
*/
VImage RemoveAlpha(VImage image);
/*
Ensures alpha channel, if missing.
*/
VImage EnsureAlpha(VImage image);
/* /*
* Tint an image using the specified chroma, preserving the original image luminance * Tint an image using the specified chroma, preserving the original image luminance
*/ */

View File

@@ -485,6 +485,18 @@ class PipelineWorker : public Napi::AsyncWorker {
baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost); baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost);
} }
// Affine transform
if (baton->affineMatrix.size() > 0) {
std::vector<double> background;
std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground);
image = image.affine(baton->affineMatrix, VImage::option()->set("background", background)
->set("idx", baton->affineIdx)
->set("idy", baton->affineIdy)
->set("odx", baton->affineOdx)
->set("ody", baton->affineOdy)
->set("interpolate", baton->affineInterpolator));
}
// Extend edges // Extend edges
if (baton->extendTop > 0 || baton->extendBottom > 0 || baton->extendLeft > 0 || baton->extendRight > 0) { if (baton->extendTop > 0 || baton->extendBottom > 0 || baton->extendLeft > 0 || baton->extendRight > 0) {
std::vector<double> background; std::vector<double> background;
@@ -558,7 +570,7 @@ class PipelineWorker : public Napi::AsyncWorker {
int left; int left;
int top; int top;
compositeImage = compositeImage.replicate(across, down); compositeImage = compositeImage.replicate(across, down);
if (composite->left >= 0 && composite->top >= 0) { if (composite->hasOffset) {
std::tie(left, top) = sharp::CalculateCrop( std::tie(left, top) = sharp::CalculateCrop(
compositeImage.width(), compositeImage.height(), image.width(), image.height(), compositeImage.width(), compositeImage.height(), image.width(), image.height(),
composite->left, composite->top); composite->left, composite->top);
@@ -580,7 +592,7 @@ class PipelineWorker : public Napi::AsyncWorker {
// Calculate position // Calculate position
int left; int left;
int top; int top;
if (composite->left >= 0 && composite->top >= 0) { if (composite->hasOffset) {
// Composite image at given offsets // Composite image at given offsets
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(), std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
compositeImage.width(), compositeImage.height(), composite->left, composite->top); compositeImage.width(), compositeImage.height(), composite->left, composite->top);
@@ -645,8 +657,12 @@ class PipelineWorker : public Napi::AsyncWorker {
// Extract an image channel (aka vips band) // Extract an image channel (aka vips band)
if (baton->extractChannel > -1) { if (baton->extractChannel > -1) {
if (baton->extractChannel >= image.bands()) { if (baton->extractChannel >= image.bands()) {
(baton->err).append("Cannot extract channel from image. Too few channels in image."); if (baton->extractChannel == 3 && sharp::HasAlpha(image)) {
return Error(); baton->extractChannel = image.bands() - 1;
} else {
(baton->err).append("Cannot extract channel from image. Too few channels in image.");
return Error();
}
} }
VipsInterpretation const interpretation = sharp::Is16Bit(image.interpretation()) VipsInterpretation const interpretation = sharp::Is16Bit(image.interpretation())
? VIPS_INTERPRETATION_GREY16 ? VIPS_INTERPRETATION_GREY16
@@ -680,6 +696,15 @@ class PipelineWorker : public Napi::AsyncWorker {
} }
} }
// Apply output ICC profile
if (!baton->withMetadataIcc.empty()) {
image = image.icc_transform(
const_cast<char*>(baton->withMetadataIcc.data()),
VImage::option()
->set("input_profile", "srgb")
->set("intent", VIPS_INTENT_PERCEPTUAL));
}
// Override EXIF Orientation tag // Override EXIF Orientation tag
if (baton->withMetadata && baton->withMetadataOrientation != -1) { if (baton->withMetadata && baton->withMetadataOrientation != -1) {
image = sharp::SetExifOrientation(image, baton->withMetadataOrientation); image = sharp::SetExifOrientation(image, baton->withMetadataOrientation);
@@ -689,17 +714,29 @@ class PipelineWorker : public Napi::AsyncWorker {
baton->channels = image.bands(); baton->channels = image.bands();
baton->width = image.width(); baton->width = image.width();
baton->height = image.height(); baton->height = image.height();
bool const supportsGifOutput = vips_type_find("VipsOperation", "magicksave") != 0 &&
vips_type_find("VipsOperation", "magicksave_buffer") != 0;
image = sharp::SetAnimationProperties(
image,
baton->pageHeight,
baton->delay,
baton->loop);
// Output // Output
if (baton->fileOut.empty()) { if (baton->fileOut.empty()) {
// Buffer output // Buffer output
if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JPEG)) { if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JPEG)) {
// Write JPEG to buffer // Write JPEG to buffer
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG); sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
VipsArea *area = VIPS_AREA(image.jpegsave_buffer(VImage::option() VipsArea *area = reinterpret_cast<VipsArea*>(image.jpegsave_buffer(VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->jpegQuality) ->set("Q", baton->jpegQuality)
->set("interlace", baton->jpegProgressive) ->set("interlace", baton->jpegProgressive)
->set("no_subsample", baton->jpegChromaSubsampling == "4:4:4") ->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
? VIPS_FOREIGN_JPEG_SUBSAMPLE_OFF
: VIPS_FOREIGN_JPEG_SUBSAMPLE_ON)
->set("trellis_quant", baton->jpegTrellisQuantisation) ->set("trellis_quant", baton->jpegTrellisQuantisation)
->set("quant_table", baton->jpegQuantisationTable) ->set("quant_table", baton->jpegQuantisationTable)
->set("overshoot_deringing", baton->jpegOvershootDeringing) ->set("overshoot_deringing", baton->jpegOvershootDeringing)
@@ -716,11 +753,11 @@ class PipelineWorker : public Napi::AsyncWorker {
baton->channels = std::min(baton->channels, 3); baton->channels = std::min(baton->channels, 3);
} }
} else if (baton->formatOut == "png" || (baton->formatOut == "input" && } else if (baton->formatOut == "png" || (baton->formatOut == "input" &&
(inputImageType == sharp::ImageType::PNG || inputImageType == sharp::ImageType::GIF || (inputImageType == sharp::ImageType::PNG || (inputImageType == sharp::ImageType::GIF && !supportsGifOutput) ||
inputImageType == sharp::ImageType::SVG))) { inputImageType == sharp::ImageType::SVG))) {
// Write PNG to buffer // Write PNG to buffer
sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG); sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
VipsArea *area = VIPS_AREA(image.pngsave_buffer(VImage::option() VipsArea *area = reinterpret_cast<VipsArea*>(image.pngsave_buffer(VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("interlace", baton->pngProgressive) ->set("interlace", baton->pngProgressive)
->set("compression", baton->pngCompressionLevel) ->set("compression", baton->pngCompressionLevel)
@@ -738,7 +775,7 @@ class PipelineWorker : public Napi::AsyncWorker {
(baton->formatOut == "input" && inputImageType == sharp::ImageType::WEBP)) { (baton->formatOut == "input" && inputImageType == sharp::ImageType::WEBP)) {
// Write WEBP to buffer // Write WEBP to buffer
sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP); sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP);
VipsArea *area = VIPS_AREA(image.webpsave_buffer(VImage::option() VipsArea *area = reinterpret_cast<VipsArea*>(image.webpsave_buffer(VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->webpQuality) ->set("Q", baton->webpQuality)
->set("lossless", baton->webpLossless) ->set("lossless", baton->webpLossless)
@@ -751,6 +788,20 @@ class PipelineWorker : public Napi::AsyncWorker {
area->free_fn = nullptr; area->free_fn = nullptr;
vips_area_unref(area); vips_area_unref(area);
baton->formatOut = "webp"; baton->formatOut = "webp";
} else if (baton->formatOut == "gif" ||
(baton->formatOut == "input" && inputImageType == sharp::ImageType::GIF && supportsGifOutput)) {
// Write GIF to buffer
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
VipsArea *area = reinterpret_cast<VipsArea*>(image.magicksave_buffer(VImage::option()
->set("strip", !baton->withMetadata)
->set("optimize_gif_frames", TRUE)
->set("optimize_gif_transparency", TRUE)
->set("format", "gif")));
baton->bufferOut = static_cast<char*>(area->data);
baton->bufferOutLength = area->length;
area->free_fn = nullptr;
vips_area_unref(area);
baton->formatOut = "gif";
} else if (baton->formatOut == "tiff" || } else if (baton->formatOut == "tiff" ||
(baton->formatOut == "input" && inputImageType == sharp::ImageType::TIFF)) { (baton->formatOut == "input" && inputImageType == sharp::ImageType::TIFF)) {
// Write TIFF to buffer // Write TIFF to buffer
@@ -762,10 +813,10 @@ class PipelineWorker : public Napi::AsyncWorker {
if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) { if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) {
image = image.cast(VIPS_FORMAT_FLOAT); image = image.cast(VIPS_FORMAT_FLOAT);
} }
VipsArea *area = VIPS_AREA(image.tiffsave_buffer(VImage::option() VipsArea *area = reinterpret_cast<VipsArea*>(image.tiffsave_buffer(VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->tiffQuality) ->set("Q", baton->tiffQuality)
->set("squash", baton->tiffSquash) ->set("bitdepth", baton->tiffBitdepth)
->set("compression", baton->tiffCompression) ->set("compression", baton->tiffCompression)
->set("predictor", baton->tiffPredictor) ->set("predictor", baton->tiffPredictor)
->set("pyramid", baton->tiffPyramid) ->set("pyramid", baton->tiffPyramid)
@@ -782,10 +833,15 @@ class PipelineWorker : public Napi::AsyncWorker {
} else if (baton->formatOut == "heif" || } else if (baton->formatOut == "heif" ||
(baton->formatOut == "input" && inputImageType == sharp::ImageType::HEIF)) { (baton->formatOut == "input" && inputImageType == sharp::ImageType::HEIF)) {
// Write HEIF to buffer // Write HEIF to buffer
VipsArea *area = VIPS_AREA(image.heifsave_buffer(VImage::option() VipsArea *area = reinterpret_cast<VipsArea*>(image.heifsave_buffer(VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("compression", baton->heifCompression) ->set("compression", baton->heifCompression)
->set("Q", baton->heifQuality) ->set("Q", baton->heifQuality)
->set("speed", baton->heifSpeed)
#if defined(VIPS_TYPE_FOREIGN_SUBSAMPLE)
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
#endif
->set("lossless", baton->heifLossless))); ->set("lossless", baton->heifLossless)));
baton->bufferOut = static_cast<char*>(area->data); baton->bufferOut = static_cast<char*>(area->data);
baton->bufferOutLength = area->length; baton->bufferOutLength = area->length;
@@ -826,13 +882,16 @@ class PipelineWorker : public Napi::AsyncWorker {
bool const isJpeg = sharp::IsJpeg(baton->fileOut); bool const isJpeg = sharp::IsJpeg(baton->fileOut);
bool const isPng = sharp::IsPng(baton->fileOut); bool const isPng = sharp::IsPng(baton->fileOut);
bool const isWebp = sharp::IsWebp(baton->fileOut); bool const isWebp = sharp::IsWebp(baton->fileOut);
bool const isGif = sharp::IsGif(baton->fileOut);
bool const isTiff = sharp::IsTiff(baton->fileOut); bool const isTiff = sharp::IsTiff(baton->fileOut);
bool const isHeif = sharp::IsHeif(baton->fileOut); bool const isHeif = sharp::IsHeif(baton->fileOut);
bool const isDz = sharp::IsDz(baton->fileOut); bool const isDz = sharp::IsDz(baton->fileOut);
bool const isDzZip = sharp::IsDzZip(baton->fileOut); bool const isDzZip = sharp::IsDzZip(baton->fileOut);
bool const isV = sharp::IsV(baton->fileOut); bool const isV = sharp::IsV(baton->fileOut);
bool const mightMatchInput = baton->formatOut == "input"; bool const mightMatchInput = baton->formatOut == "input";
bool const willMatchInput = mightMatchInput && !(isJpeg || isPng || isWebp || isTiff || isDz || isDzZip || isV); bool const willMatchInput = mightMatchInput &&
!(isJpeg || isPng || isWebp || isGif || isTiff || isHeif || isDz || isDzZip || isV);
if (baton->formatOut == "jpeg" || (mightMatchInput && isJpeg) || if (baton->formatOut == "jpeg" || (mightMatchInput && isJpeg) ||
(willMatchInput && inputImageType == sharp::ImageType::JPEG)) { (willMatchInput && inputImageType == sharp::ImageType::JPEG)) {
// Write JPEG to file // Write JPEG to file
@@ -841,7 +900,9 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->jpegQuality) ->set("Q", baton->jpegQuality)
->set("interlace", baton->jpegProgressive) ->set("interlace", baton->jpegProgressive)
->set("no_subsample", baton->jpegChromaSubsampling == "4:4:4") ->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
? VIPS_FOREIGN_JPEG_SUBSAMPLE_OFF
: VIPS_FOREIGN_JPEG_SUBSAMPLE_ON)
->set("trellis_quant", baton->jpegTrellisQuantisation) ->set("trellis_quant", baton->jpegTrellisQuantisation)
->set("quant_table", baton->jpegQuantisationTable) ->set("quant_table", baton->jpegQuantisationTable)
->set("overshoot_deringing", baton->jpegOvershootDeringing) ->set("overshoot_deringing", baton->jpegOvershootDeringing)
@@ -850,8 +911,8 @@ class PipelineWorker : public Napi::AsyncWorker {
baton->formatOut = "jpeg"; baton->formatOut = "jpeg";
baton->channels = std::min(baton->channels, 3); baton->channels = std::min(baton->channels, 3);
} else if (baton->formatOut == "png" || (mightMatchInput && isPng) || (willMatchInput && } else if (baton->formatOut == "png" || (mightMatchInput && isPng) || (willMatchInput &&
(inputImageType == sharp::ImageType::PNG || inputImageType == sharp::ImageType::GIF || (inputImageType == sharp::ImageType::PNG || (inputImageType == sharp::ImageType::GIF && !supportsGifOutput) ||
inputImageType == sharp::ImageType::SVG))) { inputImageType == sharp::ImageType::SVG))) {
// Write PNG to file // Write PNG to file
sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG); sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option() image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
@@ -877,6 +938,16 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("reduction_effort", baton->webpReductionEffort) ->set("reduction_effort", baton->webpReductionEffort)
->set("alpha_q", baton->webpAlphaQuality)); ->set("alpha_q", baton->webpAlphaQuality));
baton->formatOut = "webp"; baton->formatOut = "webp";
} else if (baton->formatOut == "gif" || (mightMatchInput && isGif) ||
(willMatchInput && inputImageType == sharp::ImageType::GIF && supportsGifOutput)) {
// Write GIF to file
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
image.magicksave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata)
->set("optimize_gif_frames", TRUE)
->set("optimize_gif_transparency", TRUE)
->set("format", "gif"));
baton->formatOut = "gif";
} else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) || } else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
(willMatchInput && inputImageType == sharp::ImageType::TIFF)) { (willMatchInput && inputImageType == sharp::ImageType::TIFF)) {
// Write TIFF to file // Write TIFF to file
@@ -884,10 +955,14 @@ class PipelineWorker : public Napi::AsyncWorker {
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG); sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
baton->channels = std::min(baton->channels, 3); baton->channels = std::min(baton->channels, 3);
} }
// Cast pixel values to float, if required
if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) {
image = image.cast(VIPS_FORMAT_FLOAT);
}
image.tiffsave(const_cast<char*>(baton->fileOut.data()), VImage::option() image.tiffsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->tiffQuality) ->set("Q", baton->tiffQuality)
->set("squash", baton->tiffSquash) ->set("bitdepth", baton->tiffBitdepth)
->set("compression", baton->tiffCompression) ->set("compression", baton->tiffCompression)
->set("predictor", baton->tiffPredictor) ->set("predictor", baton->tiffPredictor)
->set("pyramid", baton->tiffPyramid) ->set("pyramid", baton->tiffPyramid)
@@ -900,13 +975,15 @@ class PipelineWorker : public Napi::AsyncWorker {
} else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) || } else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) ||
(willMatchInput && inputImageType == sharp::ImageType::HEIF)) { (willMatchInput && inputImageType == sharp::ImageType::HEIF)) {
// Write HEIF to file // Write HEIF to file
if (sharp::IsAvif(baton->fileOut)) {
baton->heifCompression = VIPS_FOREIGN_HEIF_COMPRESSION_AV1;
}
image.heifsave(const_cast<char*>(baton->fileOut.data()), VImage::option() image.heifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->heifQuality) ->set("Q", baton->heifQuality)
->set("compression", baton->heifCompression) ->set("compression", baton->heifCompression)
->set("speed", baton->heifSpeed)
#if defined(VIPS_TYPE_FOREIGN_SUBSAMPLE)
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
#endif
->set("lossless", baton->heifLossless)); ->set("lossless", baton->heifLossless));
baton->formatOut = "heif"; baton->formatOut = "heif";
} else if (baton->formatOut == "dz" || isDz || isDzZip) { } else if (baton->formatOut == "dz" || isDz || isDzZip) {
@@ -933,19 +1010,17 @@ class PipelineWorker : public Napi::AsyncWorker {
}; };
suffix = AssembleSuffixString(".webp", options); suffix = AssembleSuffixString(".webp", options);
} else { } else {
std::string extname = baton->tileLayout == VIPS_FOREIGN_DZ_LAYOUT_GOOGLE
|| baton->tileLayout == VIPS_FOREIGN_DZ_LAYOUT_ZOOMIFY
? ".jpg" : ".jpeg";
std::vector<std::pair<std::string, std::string>> options { std::vector<std::pair<std::string, std::string>> options {
{"Q", std::to_string(baton->jpegQuality)}, {"Q", std::to_string(baton->jpegQuality)},
{"interlace", baton->jpegProgressive ? "TRUE" : "FALSE"}, {"interlace", baton->jpegProgressive ? "TRUE" : "FALSE"},
{"no_subsample", baton->jpegChromaSubsampling == "4:4:4" ? "TRUE": "FALSE"}, {"subsample_mode", baton->jpegChromaSubsampling == "4:4:4" ? "off" : "on"},
{"trellis_quant", baton->jpegTrellisQuantisation ? "TRUE" : "FALSE"}, {"trellis_quant", baton->jpegTrellisQuantisation ? "TRUE" : "FALSE"},
{"quant_table", std::to_string(baton->jpegQuantisationTable)}, {"quant_table", std::to_string(baton->jpegQuantisationTable)},
{"overshoot_deringing", baton->jpegOvershootDeringing ? "TRUE": "FALSE"}, {"overshoot_deringing", baton->jpegOvershootDeringing ? "TRUE": "FALSE"},
{"optimize_scans", baton->jpegOptimiseScans ? "TRUE": "FALSE"}, {"optimize_scans", baton->jpegOptimiseScans ? "TRUE": "FALSE"},
{"optimize_coding", baton->jpegOptimiseCoding ? "TRUE": "FALSE"} {"optimize_coding", baton->jpegOptimiseCoding ? "TRUE": "FALSE"}
}; };
std::string extname = baton->tileLayout == VIPS_FOREIGN_DZ_LAYOUT_DZ ? ".jpeg" : ".jpg";
suffix = AssembleSuffixString(extname, options); suffix = AssembleSuffixString(extname, options);
} }
// Remove alpha channel from tile background if image does not contain an alpha channel // Remove alpha channel from tile background if image does not contain an alpha channel
@@ -962,6 +1037,8 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("suffix", const_cast<char*>(suffix.data())) ->set("suffix", const_cast<char*>(suffix.data()))
->set("angle", CalculateAngleRotation(baton->tileAngle)) ->set("angle", CalculateAngleRotation(baton->tileAngle))
->set("background", baton->tileBackground) ->set("background", baton->tileBackground)
->set("centre", baton->tileCentre)
->set("id", const_cast<char*>(baton->tileId.data()))
->set("skip_blanks", baton->tileSkipBlanks); ->set("skip_blanks", baton->tileSkipBlanks);
// libvips chooses a default depth based on layout. Instead of replicating that logic here by // libvips chooses a default depth based on layout. Instead of replicating that logic here by
// not passing anything - libvips will handle choice // not passing anything - libvips will handle choice
@@ -1189,6 +1266,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
composite->gravity = sharp::AttrAsUint32(compositeObject, "gravity"); composite->gravity = sharp::AttrAsUint32(compositeObject, "gravity");
composite->left = sharp::AttrAsInt32(compositeObject, "left"); composite->left = sharp::AttrAsInt32(compositeObject, "left");
composite->top = sharp::AttrAsInt32(compositeObject, "top"); composite->top = sharp::AttrAsInt32(compositeObject, "top");
composite->hasOffset = sharp::AttrAsBool(compositeObject, "hasOffset");
composite->tile = sharp::AttrAsBool(compositeObject, "tile"); composite->tile = sharp::AttrAsBool(compositeObject, "tile");
composite->premultiplied = sharp::AttrAsBool(compositeObject, "premultiplied"); composite->premultiplied = sharp::AttrAsBool(compositeObject, "premultiplied");
baton->composite.push_back(composite); baton->composite.push_back(composite);
@@ -1196,7 +1274,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
// Resize options // Resize options
baton->withoutEnlargement = sharp::AttrAsBool(options, "withoutEnlargement"); baton->withoutEnlargement = sharp::AttrAsBool(options, "withoutEnlargement");
baton->position = sharp::AttrAsInt32(options, "position"); baton->position = sharp::AttrAsInt32(options, "position");
baton->resizeBackground = sharp::AttrAsRgba(options, "resizeBackground"); baton->resizeBackground = sharp::AttrAsVectorOfDouble(options, "resizeBackground");
baton->kernel = sharp::AttrAsStr(options, "kernel"); baton->kernel = sharp::AttrAsStr(options, "kernel");
baton->fastShrinkOnLoad = sharp::AttrAsBool(options, "fastShrinkOnLoad"); baton->fastShrinkOnLoad = sharp::AttrAsBool(options, "fastShrinkOnLoad");
// Join Channel Options // Join Channel Options
@@ -1209,7 +1287,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
} }
// Operators // Operators
baton->flatten = sharp::AttrAsBool(options, "flatten"); baton->flatten = sharp::AttrAsBool(options, "flatten");
baton->flattenBackground = sharp::AttrAsRgba(options, "flattenBackground"); baton->flattenBackground = sharp::AttrAsVectorOfDouble(options, "flattenBackground");
baton->negate = sharp::AttrAsBool(options, "negate"); baton->negate = sharp::AttrAsBool(options, "negate");
baton->blurSigma = sharp::AttrAsDouble(options, "blurSigma"); baton->blurSigma = sharp::AttrAsDouble(options, "blurSigma");
baton->brightness = sharp::AttrAsDouble(options, "brightness"); baton->brightness = sharp::AttrAsDouble(options, "brightness");
@@ -1231,7 +1309,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->useExifOrientation = sharp::AttrAsBool(options, "useExifOrientation"); baton->useExifOrientation = sharp::AttrAsBool(options, "useExifOrientation");
baton->angle = sharp::AttrAsInt32(options, "angle"); baton->angle = sharp::AttrAsInt32(options, "angle");
baton->rotationAngle = sharp::AttrAsDouble(options, "rotationAngle"); baton->rotationAngle = sharp::AttrAsDouble(options, "rotationAngle");
baton->rotationBackground = sharp::AttrAsRgba(options, "rotationBackground"); baton->rotationBackground = sharp::AttrAsVectorOfDouble(options, "rotationBackground");
baton->rotateBeforePreExtract = sharp::AttrAsBool(options, "rotateBeforePreExtract"); baton->rotateBeforePreExtract = sharp::AttrAsBool(options, "rotateBeforePreExtract");
baton->flip = sharp::AttrAsBool(options, "flip"); baton->flip = sharp::AttrAsBool(options, "flip");
baton->flop = sharp::AttrAsBool(options, "flop"); baton->flop = sharp::AttrAsBool(options, "flop");
@@ -1239,8 +1317,15 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->extendBottom = sharp::AttrAsInt32(options, "extendBottom"); baton->extendBottom = sharp::AttrAsInt32(options, "extendBottom");
baton->extendLeft = sharp::AttrAsInt32(options, "extendLeft"); baton->extendLeft = sharp::AttrAsInt32(options, "extendLeft");
baton->extendRight = sharp::AttrAsInt32(options, "extendRight"); baton->extendRight = sharp::AttrAsInt32(options, "extendRight");
baton->extendBackground = sharp::AttrAsRgba(options, "extendBackground"); baton->extendBackground = sharp::AttrAsVectorOfDouble(options, "extendBackground");
baton->extractChannel = sharp::AttrAsInt32(options, "extractChannel"); baton->extractChannel = sharp::AttrAsInt32(options, "extractChannel");
baton->affineMatrix = sharp::AttrAsVectorOfDouble(options, "affineMatrix");
baton->affineBackground = sharp::AttrAsVectorOfDouble(options, "affineBackground");
baton->affineIdx = sharp::AttrAsDouble(options, "affineIdx");
baton->affineIdy = sharp::AttrAsDouble(options, "affineIdy");
baton->affineOdx = sharp::AttrAsDouble(options, "affineOdx");
baton->affineOdy = sharp::AttrAsDouble(options, "affineOdy");
baton->affineInterpolator = vips::VInterpolate::new_from_name(sharp::AttrAsStr(options, "affineInterpolator").data());
baton->removeAlpha = sharp::AttrAsBool(options, "removeAlpha"); baton->removeAlpha = sharp::AttrAsBool(options, "removeAlpha");
baton->ensureAlpha = sharp::AttrAsBool(options, "ensureAlpha"); baton->ensureAlpha = sharp::AttrAsBool(options, "ensureAlpha");
@@ -1280,6 +1365,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->fileOut = sharp::AttrAsStr(options, "fileOut"); baton->fileOut = sharp::AttrAsStr(options, "fileOut");
baton->withMetadata = sharp::AttrAsBool(options, "withMetadata"); baton->withMetadata = sharp::AttrAsBool(options, "withMetadata");
baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation"); baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation");
baton->withMetadataIcc = sharp::AttrAsStr(options, "withMetadataIcc");
// Format-specific // Format-specific
baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality"); baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality");
baton->jpegProgressive = sharp::AttrAsBool(options, "jpegProgressive"); baton->jpegProgressive = sharp::AttrAsBool(options, "jpegProgressive");
@@ -1304,7 +1390,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->webpReductionEffort = sharp::AttrAsUint32(options, "webpReductionEffort"); baton->webpReductionEffort = sharp::AttrAsUint32(options, "webpReductionEffort");
baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality"); baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality");
baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid"); baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid");
baton->tiffSquash = sharp::AttrAsBool(options, "tiffSquash"); baton->tiffBitdepth = sharp::AttrAsUint32(options, "tiffBitdepth");
baton->tiffTile = sharp::AttrAsBool(options, "tiffTile"); baton->tiffTile = sharp::AttrAsBool(options, "tiffTile");
baton->tiffTileWidth = sharp::AttrAsUint32(options, "tiffTileWidth"); baton->tiffTileWidth = sharp::AttrAsUint32(options, "tiffTileWidth");
baton->tiffTileHeight = sharp::AttrAsUint32(options, "tiffTileHeight"); baton->tiffTileHeight = sharp::AttrAsUint32(options, "tiffTileHeight");
@@ -1322,11 +1408,25 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->heifCompression = static_cast<VipsForeignHeifCompression>( baton->heifCompression = static_cast<VipsForeignHeifCompression>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION, vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
sharp::AttrAsStr(options, "heifCompression").data())); sharp::AttrAsStr(options, "heifCompression").data()));
baton->heifSpeed = sharp::AttrAsUint32(options, "heifSpeed");
baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling");
// Animated output
if (sharp::HasAttr(options, "pageHeight")) {
baton->pageHeight = sharp::AttrAsUint32(options, "pageHeight");
}
if (sharp::HasAttr(options, "loop")) {
baton->loop = sharp::AttrAsUint32(options, "loop");
}
if (sharp::HasAttr(options, "delay")) {
baton->delay = sharp::AttrAsInt32Vector(options, "delay");
}
// Tile output // Tile output
baton->tileSize = sharp::AttrAsUint32(options, "tileSize"); baton->tileSize = sharp::AttrAsUint32(options, "tileSize");
baton->tileOverlap = sharp::AttrAsUint32(options, "tileOverlap"); baton->tileOverlap = sharp::AttrAsUint32(options, "tileOverlap");
baton->tileAngle = sharp::AttrAsInt32(options, "tileAngle"); baton->tileAngle = sharp::AttrAsInt32(options, "tileAngle");
baton->tileBackground = sharp::AttrAsRgba(options, "tileBackground"); baton->tileBackground = sharp::AttrAsVectorOfDouble(options, "tileBackground");
baton->tileSkipBlanks = sharp::AttrAsInt32(options, "tileSkipBlanks"); baton->tileSkipBlanks = sharp::AttrAsInt32(options, "tileSkipBlanks");
baton->tileContainer = static_cast<VipsForeignDzContainer>( baton->tileContainer = static_cast<VipsForeignDzContainer>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_CONTAINER, vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_CONTAINER,
@@ -1338,6 +1438,8 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->tileDepth = static_cast<VipsForeignDzDepth>( baton->tileDepth = static_cast<VipsForeignDzDepth>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_DEPTH, vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_DEPTH,
sharp::AttrAsStr(options, "tileDepth").data())); sharp::AttrAsStr(options, "tileDepth").data()));
baton->tileCentre = sharp::AttrAsBool(options, "tileCentre");
baton->tileId = sharp::AttrAsStr(options, "tileId");
// Force random access for certain operations // Force random access for certain operations
if (baton->input->access == VIPS_ACCESS_SEQUENTIAL) { if (baton->input->access == VIPS_ACCESS_SEQUENTIAL) {
@@ -1362,6 +1464,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
// Join queue for worker thread // Join queue for worker thread
Napi::Function callback = info[1].As<Napi::Function>(); Napi::Function callback = info[1].As<Napi::Function>();
PipelineWorker *worker = new PipelineWorker(callback, baton, debuglog, queueListener); PipelineWorker *worker = new PipelineWorker(callback, baton, debuglog, queueListener);
worker->Receiver().Set("options", options);
worker->Queue(); worker->Queue();
// Increment queued task counter // Increment queued task counter

View File

@@ -40,6 +40,7 @@ struct Composite {
int gravity; int gravity;
int left; int left;
int top; int top;
bool hasOffset;
bool tile; bool tile;
bool premultiplied; bool premultiplied;
@@ -47,8 +48,9 @@ struct Composite {
input(nullptr), input(nullptr),
mode(VIPS_BLEND_MODE_OVER), mode(VIPS_BLEND_MODE_OVER),
gravity(0), gravity(0),
left(-1), left(0),
top(-1), top(0),
hasOffset(false),
tile(false), tile(false),
premultiplied(false) {} premultiplied(false) {}
}; };
@@ -79,6 +81,7 @@ struct PipelineBaton {
int cropOffsetLeft; int cropOffsetLeft;
int cropOffsetTop; int cropOffsetTop;
bool premultiplied; bool premultiplied;
bool tileCentre;
std::string kernel; std::string kernel;
bool fastShrinkOnLoad; bool fastShrinkOnLoad;
double tintA; double tintA;
@@ -118,6 +121,13 @@ struct PipelineBaton {
int extendRight; int extendRight;
std::vector<double> extendBackground; std::vector<double> extendBackground;
bool withoutEnlargement; bool withoutEnlargement;
std::vector<double> affineMatrix;
std::vector<double> affineBackground;
double affineIdx;
double affineIdy;
double affineOdx;
double affineOdy;
vips::VInterpolate affineInterpolator;
int jpegQuality; int jpegQuality;
bool jpegProgressive; bool jpegProgressive;
std::string jpegChromaSubsampling; std::string jpegChromaSubsampling;
@@ -143,7 +153,7 @@ struct PipelineBaton {
VipsForeignTiffCompression tiffCompression; VipsForeignTiffCompression tiffCompression;
VipsForeignTiffPredictor tiffPredictor; VipsForeignTiffPredictor tiffPredictor;
bool tiffPyramid; bool tiffPyramid;
bool tiffSquash; int tiffBitdepth;
bool tiffTile; bool tiffTile;
int tiffTileHeight; int tiffTileHeight;
int tiffTileWidth; int tiffTileWidth;
@@ -151,10 +161,13 @@ struct PipelineBaton {
double tiffYres; double tiffYres;
int heifQuality; int heifQuality;
VipsForeignHeifCompression heifCompression; VipsForeignHeifCompression heifCompression;
int heifSpeed;
std::string heifChromaSubsampling;
bool heifLossless; bool heifLossless;
std::string err; std::string err;
bool withMetadata; bool withMetadata;
int withMetadataOrientation; int withMetadataOrientation;
std::string withMetadataIcc;
std::unique_ptr<double[]> convKernel; std::unique_ptr<double[]> convKernel;
int convKernelWidth; int convKernelWidth;
int convKernelHeight; int convKernelHeight;
@@ -167,6 +180,9 @@ struct PipelineBaton {
bool removeAlpha; bool removeAlpha;
bool ensureAlpha; bool ensureAlpha;
VipsInterpretation colourspace; VipsInterpretation colourspace;
int pageHeight;
std::vector<int> delay;
int loop;
int tileSize; int tileSize;
int tileOverlap; int tileOverlap;
VipsForeignDzContainer tileContainer; VipsForeignDzContainer tileContainer;
@@ -176,6 +192,7 @@ struct PipelineBaton {
std::vector<double> tileBackground; std::vector<double> tileBackground;
int tileSkipBlanks; int tileSkipBlanks;
VipsForeignDzDepth tileDepth; VipsForeignDzDepth tileDepth;
std::string tileId;
std::unique_ptr<double[]> recombMatrix; std::unique_ptr<double[]> recombMatrix;
PipelineBaton(): PipelineBaton():
@@ -226,6 +243,13 @@ struct PipelineBaton {
extendRight(0), extendRight(0),
extendBackground{ 0.0, 0.0, 0.0, 255.0 }, extendBackground{ 0.0, 0.0, 0.0, 255.0 },
withoutEnlargement(false), withoutEnlargement(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), jpegQuality(80),
jpegProgressive(false), jpegProgressive(false),
jpegChromaSubsampling("4:2:0"), jpegChromaSubsampling("4:2:0"),
@@ -235,7 +259,7 @@ struct PipelineBaton {
jpegOptimiseScans(false), jpegOptimiseScans(false),
jpegOptimiseCoding(true), jpegOptimiseCoding(true),
pngProgressive(false), pngProgressive(false),
pngCompressionLevel(9), pngCompressionLevel(6),
pngAdaptiveFiltering(false), pngAdaptiveFiltering(false),
pngPalette(false), pngPalette(false),
pngQuality(100), pngQuality(100),
@@ -251,14 +275,16 @@ struct PipelineBaton {
tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG), tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG),
tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL), tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL),
tiffPyramid(false), tiffPyramid(false),
tiffSquash(false), tiffBitdepth(8),
tiffTile(false), tiffTile(false),
tiffTileHeight(256), tiffTileHeight(256),
tiffTileWidth(256), tiffTileWidth(256),
tiffXres(1.0), tiffXres(1.0),
tiffYres(1.0), tiffYres(1.0),
heifQuality(80), heifQuality(50),
heifCompression(VIPS_FOREIGN_HEIF_COMPRESSION_HEVC), heifCompression(VIPS_FOREIGN_HEIF_COMPRESSION_AV1),
heifSpeed(5),
heifChromaSubsampling("4:2:0"),
heifLossless(false), heifLossless(false),
withMetadata(false), withMetadata(false),
withMetadataOrientation(-1), withMetadataOrientation(-1),
@@ -273,6 +299,9 @@ struct PipelineBaton {
removeAlpha(false), removeAlpha(false),
ensureAlpha(false), ensureAlpha(false),
colourspace(VIPS_INTERPRETATION_LAST), colourspace(VIPS_INTERPRETATION_LAST),
pageHeight(0),
delay{-1},
loop(-1),
tileSize(256), tileSize(256),
tileOverlap(0), tileOverlap(0),
tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS), tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS),

View File

@@ -21,8 +21,15 @@
#include "utilities.h" #include "utilities.h"
#include "stats.h" #include "stats.h"
Napi::Object init(Napi::Env env, Napi::Object exports) { static void* sharp_vips_init(void*) {
g_setenv("VIPS_MIN_STACK_SIZE", "2m", FALSE);
vips_init("sharp"); vips_init("sharp");
return nullptr;
}
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), g_log_set_handler("VIPS", static_cast<GLogLevelFlags>(G_LOG_LEVEL_WARNING),
static_cast<GLogFunc>(sharp::VipsWarningCallback), nullptr); static_cast<GLogFunc>(sharp::VipsWarningCallback), nullptr);
@@ -37,6 +44,7 @@ Napi::Object init(Napi::Env env, Napi::Object exports) {
exports.Set("libvipsVersion", Napi::Function::New(env, libvipsVersion)); exports.Set("libvipsVersion", Napi::Function::New(env, libvipsVersion));
exports.Set("format", Napi::Function::New(env, format)); exports.Set("format", Napi::Function::New(env, format));
exports.Set("_maxColourDistance", Napi::Function::New(env, _maxColourDistance)); exports.Set("_maxColourDistance", Napi::Function::New(env, _maxColourDistance));
exports.Set("_isUsingJemalloc", Napi::Function::New(env, _isUsingJemalloc));
exports.Set("stats", Napi::Function::New(env, stats)); exports.Set("stats", Napi::Function::New(env, stats));
return exports; return exports;
} }

View File

@@ -75,8 +75,31 @@ class StatsWorker : public Napi::AsyncWorker {
baton->isOpaque = false; baton->isOpaque = false;
} }
} }
// Convert to greyscale
vips::VImage greyscale = image.colourspace(VIPS_INTERPRETATION_B_W)[0];
// Estimate entropy via histogram of greyscale value frequency // Estimate entropy via histogram of greyscale value frequency
baton->entropy = std::abs(image.colourspace(VIPS_INTERPRETATION_B_W)[0].hist_find().hist_entropy()); 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) { } catch (vips::VError const &err) {
(baton->err).append(err.what()); (baton->err).append(err.what());
} }
@@ -123,6 +146,12 @@ class StatsWorker : public Napi::AsyncWorker {
info.Set("channels", channels); info.Set("channels", channels);
info.Set("isOpaque", baton->isOpaque); info.Set("isOpaque", baton->isOpaque);
info.Set("entropy", baton->entropy); 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 }); Callback().MakeCallback(Receiver().Value(), { env.Null(), info });
} else { } else {
Callback().MakeCallback(Receiver().Value(), { Napi::Error::New(env, baton->err).Value() }); Callback().MakeCallback(Receiver().Value(), { Napi::Error::New(env, baton->err).Value() });
@@ -154,6 +183,7 @@ Napi::Value stats(const Napi::CallbackInfo& info) {
// Join queue for worker thread // Join queue for worker thread
Napi::Function callback = info[1].As<Napi::Function>(); Napi::Function callback = info[1].As<Napi::Function>();
StatsWorker *worker = new StatsWorker(callback, baton, debuglog); StatsWorker *worker = new StatsWorker(callback, baton, debuglog);
worker->Receiver().Set("options", options);
worker->Queue(); worker->Queue();
// Increment queued task counter // Increment queued task counter

View File

@@ -47,13 +47,21 @@ struct StatsBaton {
std::vector<ChannelStats> channelStats; std::vector<ChannelStats> channelStats;
bool isOpaque; bool isOpaque;
double entropy; double entropy;
double sharpness;
int dominantRed;
int dominantGreen;
int dominantBlue;
std::string err; std::string err;
StatsBaton(): StatsBaton():
input(nullptr), input(nullptr),
isOpaque(true), isOpaque(true),
entropy(0.0) entropy(0.0),
sharpness(0.0),
dominantRed(0),
dominantGreen(0),
dominantBlue(0)
{} {}
}; };

View File

@@ -225,3 +225,19 @@ Napi::Value _maxColourDistance(const Napi::CallbackInfo& info) {
return Napi::Number::New(env, maxColourDistance); return Napi::Number::New(env, maxColourDistance);
} }
#if defined(__GNUC__)
// mallctl will be resolved by the runtime linker when jemalloc is being used
extern "C" {
int mallctl(const char *name, void *oldp, size_t *oldlenp, void *newp, size_t newlen) __attribute__((weak));
}
Napi::Value _isUsingJemalloc(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
return Napi::Boolean::New(env, mallctl != nullptr);
}
#else
Napi::Value _isUsingJemalloc(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
return Napi::Boolean::New(env, false);
}
#endif

View File

@@ -24,5 +24,6 @@ Napi::Value simd(const Napi::CallbackInfo& info);
Napi::Value libvipsVersion(const Napi::CallbackInfo& info); Napi::Value libvipsVersion(const Napi::CallbackInfo& info);
Napi::Value format(const Napi::CallbackInfo& info); Napi::Value format(const Napi::CallbackInfo& info);
Napi::Value _maxColourDistance(const Napi::CallbackInfo& info); Napi::Value _maxColourDistance(const Napi::CallbackInfo& info);
Napi::Value _isUsingJemalloc(const Napi::CallbackInfo& info);
#endif // SRC_UTILITIES_H_ #endif // SRC_UTILITIES_H_

View File

@@ -8,16 +8,16 @@
"test": "node perf && node random && node parallel" "test": "node perf && node random && node parallel"
}, },
"devDependencies": { "devDependencies": {
"async": "^3.1.0", "async": "3.2.0",
"benchmark": "^2.1.4", "benchmark": "2.1.4",
"gm": "^1.23.1", "gm": "1.23.1",
"imagemagick": "^0.1.3", "imagemagick": "0.1.3",
"jimp": "^0.9.3", "jimp": "0.16.1",
"mapnik": "^4.3.1", "mapnik": "4.5.5",
"semver": "^7.1.1" "semver": "7.3.4"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
"node": ">=8.5.0" "node": "14"
} }
} }

View File

@@ -15,6 +15,10 @@ const jimp = require('jimp');
const fixtures = require('../fixtures'); const fixtures = require('../fixtures');
const outputJpg = fixtures.path('output.jpg');
const outputPng = fixtures.path('output.png');
const outputWebP = fixtures.path('output.webp');
const width = 720; const width = 720;
const height = 588; const height = 588;
@@ -56,7 +60,7 @@ async.series({
image image
.resize(width, height, jimp.RESIZE_BICUBIC) .resize(width, height, jimp.RESIZE_BICUBIC)
.quality(80) .quality(80)
.write(fixtures.outputJpg, function (err) { .write(outputJpg, function (err) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -77,7 +81,7 @@ async.series({
.resize(width, height, { .resize(width, height, {
scaling_method: mapnik.imageScaling.lanczos scaling_method: mapnik.imageScaling.lanczos
}) })
.save(fixtures.outputJpg, 'jpeg:quality=80', function (err) { .save(outputJpg, 'jpeg:quality=80', function (err) {
if (err) throw err; if (err) throw err;
deferred.resolve(); deferred.resolve();
}); });
@@ -105,7 +109,7 @@ async.series({
fn: function (deferred) { fn: function (deferred) {
imagemagick.resize({ imagemagick.resize({
srcPath: fixtures.inputJpg, srcPath: fixtures.inputJpg,
dstPath: fixtures.outputJpg, dstPath: outputJpg,
quality: 0.8, quality: 0.8,
width: width, width: width,
height: height, height: height,
@@ -128,7 +132,7 @@ async.series({
.filter('Lanczos') .filter('Lanczos')
.resize(width, height) .resize(width, height)
.quality(80) .quality(80)
.write(fixtures.outputJpg, function (err) { .write(outputJpg, function (err) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -159,7 +163,7 @@ async.series({
.filter('Lanczos') .filter('Lanczos')
.resize(width, height) .resize(width, height)
.quality(80) .quality(80)
.write(fixtures.outputJpg, function (err) { .write(outputJpg, function (err) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -190,7 +194,7 @@ async.series({
fn: function (deferred) { fn: function (deferred) {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height)
.toFile(fixtures.outputJpg, function (err) { .toFile(outputJpg, function (err) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -217,7 +221,7 @@ async.series({
fn: function (deferred) { fn: function (deferred) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(width, height) .resize(width, height)
.toFile(fixtures.outputJpg, function (err) { .toFile(outputJpg, function (err) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -229,7 +233,7 @@ async.series({
defer: true, defer: true,
fn: function (deferred) { fn: function (deferred) {
const readable = fs.createReadStream(fixtures.inputJpg); const readable = fs.createReadStream(fixtures.inputJpg);
const writable = fs.createWriteStream(fixtures.outputJpg); const writable = fs.createWriteStream(outputJpg);
writable.on('finish', function () { writable.on('finish', function () {
deferred.resolve(); deferred.resolve();
}); });
@@ -564,8 +568,9 @@ async.series({
}, },
// PNG // PNG
png: function (callback) { png: function (callback) {
const inputPngBuffer = fs.readFileSync(fixtures.inputPng); const inputPngBuffer = fs.readFileSync(fixtures.inputPngAlphaPremultiplicationLarge);
const pngSuite = new Benchmark.Suite('png'); const pngSuite = new Benchmark.Suite('png');
const minSamples = 64;
// jimp // jimp
pngSuite.add('jimp-buffer-buffer', { pngSuite.add('jimp-buffer-buffer', {
defer: true, defer: true,
@@ -576,6 +581,8 @@ async.series({
} else { } else {
image image
.resize(width, height) .resize(width, height)
.deflateLevel(6)
.filterType(0)
.getBuffer(jimp.MIME_PNG, function (err) { .getBuffer(jimp.MIME_PNG, function (err) {
if (err) { if (err) {
throw err; throw err;
@@ -589,13 +596,15 @@ async.series({
}).add('jimp-file-file', { }).add('jimp-file-file', {
defer: true, defer: true,
fn: function (deferred) { fn: function (deferred) {
jimp.read(fixtures.inputPng, function (err, image) { jimp.read(fixtures.inputPngAlphaPremultiplicationLarge, function (err, image) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
image image
.resize(width, height) .resize(width, height)
.write(fixtures.outputPng, function (err) { .deflateLevel(6)
.filterType(0)
.write(outputPng, function (err) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -610,7 +619,7 @@ async.series({
pngSuite.add('mapnik-file-file', { pngSuite.add('mapnik-file-file', {
defer: true, defer: true,
fn: function (deferred) { fn: function (deferred) {
mapnik.Image.open(fixtures.inputPng, function (err, img) { mapnik.Image.open(fixtures.inputPngAlphaPremultiplicationLarge, function (err, img) {
if (err) throw err; if (err) throw err;
img.premultiply(function (err, img) { img.premultiply(function (err, img) {
if (err) throw err; if (err) throw err;
@@ -620,7 +629,7 @@ async.series({
if (err) throw err; if (err) throw err;
img.demultiply(function (err, img) { img.demultiply(function (err, img) {
if (err) throw err; if (err) throw err;
img.save(fixtures.outputPng, 'png', function (err) { img.save(outputPng, 'png', function (err) {
if (err) throw err; if (err) throw err;
deferred.resolve(); deferred.resolve();
}); });
@@ -657,11 +666,15 @@ async.series({
defer: true, defer: true,
fn: function (deferred) { fn: function (deferred) {
imagemagick.resize({ imagemagick.resize({
srcPath: fixtures.inputPng, srcPath: fixtures.inputPngAlphaPremultiplicationLarge,
dstPath: fixtures.outputPng, dstPath: outputPng,
width: width, width: width,
height: height, height: height,
filter: 'Lanczos' filter: 'Lanczos',
customArgs: [
'-define', 'PNG:compression-level=6',
'-define', 'PNG:compression-filter=0'
]
}, function (err) { }, function (err) {
if (err) { if (err) {
throw err; throw err;
@@ -675,10 +688,12 @@ async.series({
pngSuite.add('gm-file-file', { pngSuite.add('gm-file-file', {
defer: true, defer: true,
fn: function (deferred) { fn: function (deferred) {
gm(fixtures.inputPng) gm(fixtures.inputPngAlphaPremultiplicationLarge)
.filter('Lanczos') .filter('Lanczos')
.resize(width, height) .resize(width, height)
.write(fixtures.outputPng, function (err) { .define('PNG:compression-level=6')
.define('PNG:compression-filter=0')
.write(outputPng, function (err) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -689,9 +704,11 @@ async.series({
}).add('gm-file-buffer', { }).add('gm-file-buffer', {
defer: true, defer: true,
fn: function (deferred) { fn: function (deferred) {
gm(fixtures.inputPng) gm(fixtures.inputPngAlphaPremultiplicationLarge)
.filter('Lanczos') .filter('Lanczos')
.resize(width, height) .resize(width, height)
.define('PNG:compression-level=6')
.define('PNG:compression-filter=0')
.toBuffer(function (err, buffer) { .toBuffer(function (err, buffer) {
if (err) { if (err) {
throw err; throw err;
@@ -705,10 +722,12 @@ async.series({
// sharp // sharp
pngSuite.add('sharp-buffer-file', { pngSuite.add('sharp-buffer-file', {
defer: true, defer: true,
minSamples,
fn: function (deferred) { fn: function (deferred) {
sharp(inputPngBuffer) sharp(inputPngBuffer)
.resize(width, height) .resize(width, height)
.toFile(fixtures.outputPng, function (err) { .png({ compressionLevel: 6 })
.toFile(outputPng, function (err) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -718,9 +737,11 @@ async.series({
} }
}).add('sharp-buffer-buffer', { }).add('sharp-buffer-buffer', {
defer: true, defer: true,
minSamples,
fn: function (deferred) { fn: function (deferred) {
sharp(inputPngBuffer) sharp(inputPngBuffer)
.resize(width, height) .resize(width, height)
.png({ compressionLevel: 6 })
.toBuffer(function (err, buffer) { .toBuffer(function (err, buffer) {
if (err) { if (err) {
throw err; throw err;
@@ -732,10 +753,12 @@ async.series({
} }
}).add('sharp-file-file', { }).add('sharp-file-file', {
defer: true, defer: true,
minSamples,
fn: function (deferred) { fn: function (deferred) {
sharp(fixtures.inputPng) sharp(fixtures.inputPngAlphaPremultiplicationLarge)
.resize(width, height) .resize(width, height)
.toFile(fixtures.outputPng, function (err) { .png({ compressionLevel: 6 })
.toFile(outputPng, function (err) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -745,9 +768,11 @@ async.series({
} }
}).add('sharp-file-buffer', { }).add('sharp-file-buffer', {
defer: true, defer: true,
minSamples,
fn: function (deferred) { fn: function (deferred) {
sharp(fixtures.inputPng) sharp(fixtures.inputPngAlphaPremultiplicationLarge)
.resize(width, height) .resize(width, height)
.png({ compressionLevel: 6 })
.toBuffer(function (err, buffer) { .toBuffer(function (err, buffer) {
if (err) { if (err) {
throw err; throw err;
@@ -759,10 +784,11 @@ async.series({
} }
}).add('sharp-progressive', { }).add('sharp-progressive', {
defer: true, defer: true,
minSamples,
fn: function (deferred) { fn: function (deferred) {
sharp(inputPngBuffer) sharp(inputPngBuffer)
.resize(width, height) .resize(width, height)
.png({ progressive: true }) .png({ compressionLevel: 6, progressive: true })
.toBuffer(function (err, buffer) { .toBuffer(function (err, buffer) {
if (err) { if (err) {
throw err; throw err;
@@ -774,10 +800,27 @@ async.series({
} }
}).add('sharp-adaptiveFiltering', { }).add('sharp-adaptiveFiltering', {
defer: true, defer: true,
minSamples,
fn: function (deferred) { fn: function (deferred) {
sharp(inputPngBuffer) sharp(inputPngBuffer)
.resize(width, height) .resize(width, height)
.png({ adaptiveFiltering: true }) .png({ adaptiveFiltering: true, compressionLevel: 6 })
.toBuffer(function (err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add('sharp-compressionLevel=9', {
defer: true,
minSamples,
fn: function (deferred) {
sharp(inputPngBuffer)
.resize(width, height)
.png({ compressionLevel: 9 })
.toBuffer(function (err, buffer) { .toBuffer(function (err, buffer) {
if (err) { if (err) {
throw err; throw err;
@@ -802,7 +845,7 @@ async.series({
fn: function (deferred) { fn: function (deferred) {
sharp(inputWebPBuffer) sharp(inputWebPBuffer)
.resize(width, height) .resize(width, height)
.toFile(fixtures.outputWebP, function (err) { .toFile(outputWebP, function (err) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -829,7 +872,7 @@ async.series({
fn: function (deferred) { fn: function (deferred) {
sharp(fixtures.inputWebP) sharp(fixtures.inputWebP)
.resize(width, height) .resize(width, height)
.toFile(fixtures.outputWebP, function (err) { .toFile(outputWebP, function (err) {
if (err) { if (err) {
throw err; throw err;
} else { } else {

View File

@@ -23,7 +23,7 @@ new Benchmark.Suite('random').add('imagemagick', {
fn: function (deferred) { fn: function (deferred) {
imagemagick.resize({ imagemagick.resize({
srcPath: fixtures.inputJpg, srcPath: fixtures.inputJpg,
dstPath: fixtures.outputJpg, dstPath: fixtures.path('output.jpg'),
quality: 0.8, quality: 0.8,
width: randomDimension(), width: randomDimension(),
height: randomDimension(), height: randomDimension(),

BIN
test/fixtures/animated-loop-3.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

BIN
test/fixtures/big-height.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

3
test/fixtures/circle.svg vendored Normal file
View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8">
<circle r="3.75" cx="4" cy="4" fill="deeppink" />
</svg>

After

Width:  |  Height:  |  Size: 122 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
test/fixtures/expected/circle.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
test/fixtures/expected/hilutite.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 KiB

BIN
test/fixtures/expected/icc-cmyk.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 943 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
test/fixtures/expected/tile_centered.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

BIN
test/fixtures/hilutite.icm vendored Normal file

Binary file not shown.

View File

@@ -93,6 +93,9 @@ module.exports = {
inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp
inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
inputWebPAnimated: getPath('rotating-squares.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
inputWebPAnimatedLoop3: getPath('animated-loop-3.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
inputWebPAnimatedBigHeight: getPath('big-height.webp'),
inputTiff: getPath('G31D.TIF'), // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm inputTiff: getPath('G31D.TIF'), // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm
inputTiffMultipage: getPath('G31D_MULTI.TIF'), // gm convert G31D.TIF -resize 50% G31D_2.TIF ; tiffcp G31D.TIF G31D_2.TIF G31D_MULTI.TIF inputTiffMultipage: getPath('G31D_MULTI.TIF'), // gm convert G31D.TIF -resize 50% G31D_2.TIF ; tiffcp G31D.TIF G31D_2.TIF G31D_MULTI.TIF
inputTiffCielab: getPath('cielab-dagams.tiff'), // https://github.com/lovell/sharp/issues/646 inputTiffCielab: getPath('cielab-dagams.tiff'), // https://github.com/lovell/sharp/issues/646
@@ -104,7 +107,9 @@ module.exports = {
inputGifAnimated: getPath('rotating-squares.gif'), // CC0 https://loading.io/spinner/blocks/-rotating-squares-preloader-gif inputGifAnimated: getPath('rotating-squares.gif'), // CC0 https://loading.io/spinner/blocks/-rotating-squares-preloader-gif
inputGifAnimatedLoop3: getPath('animated-loop-3.gif'), // CC-BY-SA-4.0 Petrus3743 https://commons.wikimedia.org/wiki/File:01-Goldener_Schnitt_Formel-Animation.gif inputGifAnimatedLoop3: getPath('animated-loop-3.gif'), // CC-BY-SA-4.0 Petrus3743 https://commons.wikimedia.org/wiki/File:01-Goldener_Schnitt_Formel-Animation.gif
inputSvg: getPath('check.svg'), // http://dev.w3.org/SVG/tools/svgweb/samples/svg-files/check.svg inputSvg: getPath('check.svg'), // http://dev.w3.org/SVG/tools/svgweb/samples/svg-files/check.svg
inputSvgSmallViewBox: getPath('circle.svg'),
inputSvgWithEmbeddedImages: getPath('struct-image-04-t.svg'), // https://dev.w3.org/SVG/profiles/1.2T/test/svg/struct-image-04-t.svg inputSvgWithEmbeddedImages: getPath('struct-image-04-t.svg'), // https://dev.w3.org/SVG/profiles/1.2T/test/svg/struct-image-04-t.svg
inputAvif: getPath('sdr_cosmos12920_cicp1-13-6_yuv444_full_qp10.avif'), // CC by-nc-nd https://github.com/AOMediaCodec/av1-avif/tree/master/testFiles/Netflix
inputJPGBig: getPath('flowers.jpeg'), inputJPGBig: getPath('flowers.jpeg'),
@@ -115,13 +120,6 @@ module.exports = {
inputV: getPath('vfile.v'), inputV: getPath('vfile.v'),
outputJpg: getPath('output.jpg'),
outputPng: getPath('output.png'),
outputWebP: getPath('output.webp'),
outputV: getPath('output.v'),
outputTiff: getPath('output.tiff'),
outputZoinks: getPath('output.zoinks'), // an 'unknown' file extension
testPattern: getPath('test-pattern.png'), testPattern: getPath('test-pattern.png'),
// Path for tests requiring human inspection // Path for tests requiring human inspection

BIN
test/fixtures/rotating-squares.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

View File

@@ -39,6 +39,16 @@
Memcheck:Cond Memcheck:Cond
obj:*/libjpeg.so* obj:*/libjpeg.so*
} }
{
value_jpeg_obj_static
Memcheck:Value8
obj:*/libvips.so*
}
{
cond_jpeg_obj_static
Memcheck:Cond
obj:*/libvips.so*
}
{ {
param_jpeg_jpeg_finish_compress param_jpeg_jpeg_finish_compress
Memcheck:Param Memcheck:Param
@@ -179,6 +189,24 @@
... ...
fun:gsf_output_write fun:gsf_output_write
} }
{
param_gsf_new_do_write
Memcheck:Param
write(buf)
...
fun:new_do_write
...
fun:gsf_output_close
}
{
param_gsf_output_write
Memcheck:Param
write(buf)
...
fun:new_do_write
...
fun:gsf_output_write
}
# fontconfig # fontconfig
{ {
@@ -208,6 +236,84 @@
fun:XML_ParseBuffer fun:XML_ParseBuffer
obj:*/libfontconfig.so.* obj:*/libfontconfig.so.*
} }
{
leak_fontconfig_FcInitLoadConfigAndFonts
Memcheck:Leak
match-leak-kinds: definite
fun:malloc
...
fun:XML_ParseBuffer
...
fun:FcInitLoadConfigAndFonts
}
# heif
{
cond_heif_encode_image
Memcheck:Cond
...
fun:heif_context_encode_image
}
{
value8_heif_encode_image
Memcheck:Value8
...
fun:heif_context_encode_image
}
{
cond_heif_aom_codec_encode
Memcheck:Cond
...
fun:aom_codec_encode
}
{
value8_heif_aom_codec_encode
Memcheck:Value8
...
fun:aom_codec_encode
}
{
value1_heif_aom_codec_encode
Memcheck:Value1
...
fun:aom_codec_encode
}
{
cond_heif_av1_encode_frame
Memcheck:Cond
...
fun:av1_encode_frame
}
{
value8_heif_av1_encode_frame
Memcheck:Value8
...
fun:av1_encode_frame
}
{
cond_heif_context_write
Memcheck:Cond
...
fun:heif_context_write
}
{
value8_heif_context_write
Memcheck:Value8
...
fun:heif_context_write
}
{
cond_heif_context_read
Memcheck:Cond
...
fun:heif_context_read_from_reader
}
{
value8_heif_context_read
Memcheck:Value8
...
fun:heif_context_read_from_reader
}
# libvips # libvips
{ {
@@ -296,6 +402,40 @@
... ...
fun:start_thread fun:start_thread
} }
{
cond_libvips_source_read
Memcheck:Cond
...
fun:vips_source_read
}
{
value8_libvips_source_read
Memcheck:Value8
...
fun:vips_source_read
}
{
cond_libvips_target_finish
Memcheck:Cond
...
fun:vips_target_finish
}
{
value8_libvips_target_finish
Memcheck:Value8
...
fun:vips_target_finish
}
{
value8_libvips_static
Memcheck:Value8
obj:*/libvips-cpp.so.*
}
{
cond_libvips_static
Memcheck:Cond
obj:*/libvips-cpp.so.*
}
{ {
leak_libvips_init leak_libvips_init
Memcheck:Leak Memcheck:Leak
@@ -304,6 +444,42 @@
... ...
fun:vips__init fun:vips__init
} }
{
leak_rsvg_static_data
Memcheck:Leak
match-leak-kinds: definite
fun:malloc
...
fun:rsvg_handle_new_from_stream_sync
}
{
leak_rsvg_rsvg_rust_handle_new_from_gfile_sync
Memcheck:Leak
match-leak-kinds: definite
fun:malloc
...
fun:rsvg_handle_new_from_gfile_sync
}
{
leak_rsvg_rust_handle_new_from_stream_sync
Memcheck:Leak
match-leak-kinds: possible
fun:malloc
...
fun:xmlParseElement
...
fun:rsvg_handle_new_from_stream_sync
}
{
leak_rsvg_rust_handle_new_from_gfile_sync
Memcheck:Leak
match-leak-kinds: possible
fun:malloc
...
fun:xmlParseElement
...
fun:rsvg_handle_new_from_gfile_sync
}
# libuv warnings # libuv warnings
{ {
@@ -418,6 +594,13 @@
... ...
fun:_ZN4node17CreateEnvironmentEPN2v87IsolateEP9uv_loop_sNS0_5LocalINS0_7ContextEEEiPKPKciSB_ fun:_ZN4node17CreateEnvironmentEPN2v87IsolateEP9uv_loop_sNS0_5LocalINS0_7ContextEEEiPKPKciSB_
} }
{
leak_nodejs_CreateEnvironment_IsolateData
Memcheck:Leak
match-leak-kinds: possible
...
fun:_ZN4node17CreateEnvironmentEPNS_11IsolateDataEN2v85LocalINS2_7ContextEEERKSt6vectorISsSaISsEESA_NS_16EnvironmentFlags5FlagsENS_8ThreadIdESt10unique_ptrINS_21InspectorParentHandleESt14default_deleteISF_EE
}
{ {
leak_nodejs_Environment_Start leak_nodejs_Environment_Start
Memcheck:Leak Memcheck:Leak
@@ -664,3 +847,27 @@
... ...
fun:_ZN12v8_inspector10toString16ERKNS_10StringViewE fun:_ZN12v8_inspector10toString16ERKNS_10StringViewE
} }
{
cond_v8_Builtins_InterpreterEntryTrampoline
Memcheck:Cond
...
fun:Builtins_InterpreterEntryTrampoline
}
{
cond_v8_ZN2v88internal18ArrayBufferSweeper9SweepFullEv
Memcheck:Cond
...
fun:_ZN2v88internal18ArrayBufferSweeper9SweepFullEv
}
{
cond_v8_ZN4node11Environment27RunAndClearNativeImmediatesEb
Memcheck:Cond
...
fun:_ZN4node11Environment27RunAndClearNativeImmediatesEb
}
{
cond_v8_ZN2v88internal18ArrayBufferSweeper10ReleaseAllEv
Memcheck:Cond
...
fun:_ZN2v88internal18ArrayBufferSweeper10ReleaseAllEv
}

175
test/unit/affine.js Normal file
View File

@@ -0,0 +1,175 @@
'use strict';
const assert = require('assert');
const sharp = require('../../');
const fixtures = require('../fixtures');
describe('Affine transform', () => {
describe('Invalid input', () => {
it('Missing matrix', () => {
assert.throws(() => {
sharp(fixtures.inputJpg)
.affine();
});
});
it('Invalid 1d matrix', () => {
assert.throws(() => {
sharp(fixtures.inputJpg)
.affine(['123', 123, 123, 123]);
});
});
it('Invalid 2d matrix', () => {
assert.throws(() => {
sharp(fixtures.inputJpg)
.affine([[123, 123], [null, 123]]);
});
});
it('Invalid options parameter type', () => {
assert.throws(() => {
sharp(fixtures.inputJpg)
.affine([[1, 0], [0, 1]], 'invalid options type');
});
});
it('Invalid background color', () => {
assert.throws(() => {
sharp(fixtures.inputJpg)
.affine([4, 4, 4, 4], { background: 'not a color' });
});
});
it('Invalid idx offset type', () => {
assert.throws(() => {
sharp(fixtures.inputJpg)
.affine([[4, 4], [4, 4]], { idx: 'invalid idx type' });
});
});
it('Invalid idy offset type', () => {
assert.throws(() => {
sharp(fixtures.inputJpg)
.affine([4, 4, 4, 4], { idy: 'invalid idy type' });
});
});
it('Invalid odx offset type', () => {
assert.throws(() => {
sharp(fixtures.inputJpg)
.affine([[4, 4], [4, 4]], { odx: 'invalid odx type' });
});
});
it('Invalid ody offset type', () => {
assert.throws(() => {
sharp(fixtures.inputJpg)
.affine([[4, 4], [4, 4]], { ody: 'invalid ody type' });
});
});
it('Invalid interpolator', () => {
assert.throws(() => {
sharp(fixtures.inputJpg)
.affine([[4, 4], [4, 4]], { interpolator: 'cubic' });
});
});
});
it('Applies identity matrix', done => {
const input = fixtures.inputJpg;
sharp(input)
.affine([[1, 0], [0, 1]])
.toBuffer((err, data) => {
if (err) throw err;
fixtures.assertSimilar(input, data, done);
});
});
it('Applies resize affine matrix', done => {
const input = fixtures.inputJpg;
const inputWidth = 2725;
const inputHeight = 2225;
sharp(input)
.affine([[0.2, 0], [0, 1.5]])
.toBuffer((err, data, info) => {
if (err) throw err;
fixtures.assertSimilar(input, data, done);
assert.strictEqual(info.width, Math.ceil(inputWidth * 0.2));
assert.strictEqual(info.height, Math.ceil(inputHeight * 1.5));
});
});
it('Resizes and applies affine transform', done => {
const input = fixtures.inputJpg;
sharp(input)
.resize(500, 500)
.affine([[0.5, 1], [1, 0.5]])
.toBuffer((err, data) => {
if (err) throw err;
fixtures.assertSimilar(data, fixtures.expected('affine-resize-expected.jpg'), done);
});
});
it('Extracts and applies affine transform', done => {
sharp(fixtures.inputJpg)
.extract({ left: 300, top: 300, width: 600, height: 600 })
.affine([0.3, 0, -0.5, 0.3])
.toBuffer((err, data) => {
if (err) throw err;
fixtures.assertSimilar(data, fixtures.expected('affine-extract-expected.jpg'), done);
});
});
it('Rotates and applies affine transform', done => {
sharp(fixtures.inputJpg320x240)
.rotate(90)
.affine([[-1.2, 0], [0, -1.2]])
.toBuffer((err, data) => {
if (err) throw err;
fixtures.assertSimilar(data, fixtures.expected('affine-rotate-expected.jpg'), done);
});
});
it('Extracts, rotates and applies affine transform', done => {
sharp(fixtures.inputJpg)
.extract({ left: 1000, top: 1000, width: 200, height: 200 })
.rotate(45, { background: 'blue' })
.affine([[2, 1], [2, -0.5]], { background: 'red' })
.toBuffer((err, data) => {
if (err) throw err;
fixtures.assertSimilar(fixtures.expected('affine-extract-rotate-expected.jpg'), data, done);
});
});
it('Applies affine transform with background color', done => {
sharp(fixtures.inputJpg320x240)
.rotate(180)
.affine([[-1.5, 1.2], [-1, 1]], { background: 'red' })
.toBuffer((err, data) => {
if (err) throw err;
fixtures.assertSimilar(fixtures.expected('affine-background-expected.jpg'), data, done);
});
});
it('Applies affine transform with background color and output offsets', done => {
sharp(fixtures.inputJpg320x240)
.rotate(180)
.affine([[-2, 1.5], [-1, 2]], { background: 'blue', odx: 40, ody: -100 })
.toBuffer((err, data) => {
if (err) throw err;
fixtures.assertSimilar(fixtures.expected('affine-background-output-offsets-expected.jpg'), data, done);
});
});
it('Applies affine transform with background color and all offsets', done => {
sharp(fixtures.inputJpg320x240)
.rotate(180)
.affine([[-1.2, 1.8], [-1, 2]], { background: 'yellow', idx: 10, idy: -40, odx: 10, ody: -50 })
.toBuffer((err, data) => {
if (err) throw err;
fixtures.assertSimilar(fixtures.expected('affine-background-all-offsets-expected.jpg'), data, done);
});
});
describe('Interpolations', () => {
const input = fixtures.inputJpg320x240;
const inputWidth = 320;
const inputHeight = 240;
for (const interp in sharp.interpolators) {
it(`Performs 2x upscale with ${interp} interpolation`, done => {
sharp(input)
.affine([[2, 0], [0, 2]], { interpolator: sharp.interpolators[interp] })
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual(info.width, Math.ceil(inputWidth * 2));
assert.strictEqual(info.height, Math.ceil(inputHeight * 2));
fixtures.assertSimilar(fixtures.expected(`affine-${sharp.interpolators[interp]}-2x-upscale-expected.jpg`), data, done);
});
});
}
});
});

84
test/unit/avif.js Normal file
View File

@@ -0,0 +1,84 @@
'use strict';
const assert = require('assert');
const sharp = require('../../');
const { inputAvif, inputJpg } = require('../fixtures');
describe('AVIF', () => {
it('called without options does not throw an error', () => {
assert.doesNotThrow(() => {
sharp().avif();
});
});
it('can convert AVIF to JPEG', async () => {
const data = await sharp(inputAvif)
.resize(32)
.jpeg()
.toBuffer();
const metadata = await sharp(data)
.metadata();
const { compression, size, ...metadataWithoutSize } = metadata;
assert.deepStrictEqual(metadataWithoutSize, {
channels: 3,
chromaSubsampling: '4:2:0',
density: 72,
depth: 'uchar',
format: 'jpeg',
hasAlpha: false,
hasProfile: false,
height: 13,
isProgressive: false,
space: 'srgb',
width: 32
});
});
it('can convert JPEG to AVIF', async () => {
const data = await sharp(inputJpg)
.resize(32)
.avif()
.toBuffer();
const metadata = await sharp(data)
.metadata();
const { compression, size, ...metadataWithoutSize } = metadata;
assert.deepStrictEqual(metadataWithoutSize, {
channels: 3,
depth: 'uchar',
format: 'heif',
hasAlpha: false,
hasProfile: false,
height: 26,
isProgressive: false,
pageHeight: 26,
pagePrimary: 0,
pages: 1,
space: 'srgb',
width: 32
});
});
it('can passthrough AVIF', async () => {
const data = await sharp(inputAvif)
.resize(32)
.toBuffer();
const metadata = await sharp(data)
.metadata();
const { compression, size, ...metadataWithoutSize } = metadata;
assert.deepStrictEqual(metadataWithoutSize, {
channels: 3,
depth: 'uchar',
format: 'heif',
hasAlpha: false,
hasProfile: false,
height: 12,
isProgressive: false,
pageHeight: 12,
pagePrimary: 0,
pages: 1,
space: 'srgb',
width: 32
});
});
});

View File

@@ -172,6 +172,24 @@ describe('composite', () => {
}); });
}); });
it('negative offset and gravity', done => {
sharp(fixtures.inputJpg)
.resize(400)
.composite([{
input: fixtures.inputPngWithTransparency16bit,
left: -10,
top: -10,
gravity: 4
}])
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
assert.strictEqual(3, info.channels);
fixtures.assertSimilar(
fixtures.expected('overlay-negative-offset-with-gravity.jpg'), data, done);
});
});
it('offset, gravity and tile', done => { it('offset, gravity and tile', done => {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(80) .resize(80)
@@ -333,13 +351,25 @@ describe('composite', () => {
it('invalid left', () => { it('invalid left', () => {
assert.throws(() => { assert.throws(() => {
sharp().composite([{ input: 'test', left: 0.5 }]); sharp().composite([{ input: 'test', left: 0.5 }]);
}, /Expected positive integer for left but received 0.5 of type number/); }, /Expected integer for left but received 0.5 of type number/);
assert.throws(() => {
sharp().composite([{ input: 'test', left: 'invalid' }]);
}, /Expected integer for left but received invalid of type string/);
assert.throws(() => {
sharp().composite([{ input: 'test', left: 'invalid', top: 10 }]);
}, /Expected integer for left but received invalid of type string/);
}); });
it('invalid top', () => { it('invalid top', () => {
assert.throws(() => { assert.throws(() => {
sharp().composite([{ input: 'test', top: -1 }]); sharp().composite([{ input: 'test', top: 0.5 }]);
}, /Expected positive integer for top but received -1 of type number/); }, /Expected integer for top but received 0.5 of type number/);
assert.throws(() => {
sharp().composite([{ input: 'test', top: 'invalid' }]);
}, /Expected integer for top but received invalid of type string/);
assert.throws(() => {
sharp().composite([{ input: 'test', top: 'invalid', left: 10 }]);
}, /Expected integer for top but received invalid of type string/);
}); });
it('left but no top', () => { it('left but no top', () => {

View File

@@ -80,6 +80,19 @@ describe('Image channel extraction', function () {
}); });
}); });
it('Alpha from 2-channel input', function (done) {
const output = fixtures.path('output.extract-alpha-2-channel.png');
sharp(fixtures.inputPngWithGreyAlpha)
.extractChannel('alpha')
.toColourspace('b-w')
.toFile(output, function (err, info) {
if (err) throw err;
assert.strictEqual(1, info.channels);
fixtures.assertMaxColourDistance(output, fixtures.expected('extract-alpha-2-channel.png'));
done();
});
});
it('Invalid channel number', function () { it('Invalid channel number', function () {
assert.throws(function () { assert.throws(function () {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
@@ -94,7 +107,7 @@ describe('Image channel extraction', function () {
}); });
}); });
it('Non-existant channel', function (done) { it('Non-existent channel', function (done) {
sharp(fixtures.inputPng) sharp(fixtures.inputPng)
.extractChannel(1) .extractChannel(1)
.toBuffer(function (err) { .toBuffer(function (err) {

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