Compare commits

...

844 Commits

Author SHA1 Message Date
Lovell Fuller
eefaa99872 Release v0.32.6 2023-09-18 20:33:39 +01:00
Lovell Fuller
dbce6fab79 Upgrade to libvips v8.14.5 2023-09-18 20:09:54 +01:00
Lovell Fuller
af0fcb37c2 Docs: changelog for #3799 2023-09-18 14:56:03 +01:00
Lovell Fuller
c6f54e59da Bump devDeps 2023-09-18 14:53:44 +01:00
ldrick
846563e45f TypeScript: add definitions for block and unblock (#3799) 2023-09-18 10:42:13 +01:00
Lovell Fuller
9c217ab580 Ensure withMetadata can add RGB16 profiles #3773 2023-08-31 12:49:50 +01:00
Lovell Fuller
e7381e522e Alternative fix for 4340d60, uses existing StaySequential 2023-08-31 12:09:11 +01:00
Lovell Fuller
4340d60ccf Ensure composite tile images fully decoded #3767 2023-08-31 09:04:51 +01:00
Lovell Fuller
7f64d464de Docs: add missing returns property to raw 2023-08-29 11:17:35 +01:00
Lovell Fuller
67e927bdb6 Docs: ensure all functions include method signature #3777 2023-08-29 11:16:18 +01:00
Lovell Fuller
9c7713ed54 Docs: remove mention of EXIF from flip/flop ops 2023-08-29 10:49:21 +01:00
Lovell Fuller
8be6da1def Docs: clarify when rotate op will remove EXIF Orientation 2023-08-29 10:19:07 +01:00
Lovell Fuller
95635683ac Ensure withMetadata skips default profile for RGB16 #3773 2023-08-24 18:13:00 +01:00
Lovell Fuller
44a0ee3fd3 Release v0.32.5 2023-08-15 19:29:42 +01:00
Lovell Fuller
ccd51c8cbf Upgrade to libvips v8.14.4 2023-08-15 16:40:22 +01:00
Lovell Fuller
bb7469b2d1 Ensure withMetadata adds default sRGB profile #3761 2023-08-15 13:02:20 +01:00
Kleis Auke Wolthuizen
a2cac61209 Simplify 90/270 orient-before-resize logic (#3762) 2023-08-15 07:56:07 +01:00
Lovell Fuller
5c19f6dd9b Ensure resize fit=inside respects 90/270 rotate #3756 2023-08-14 13:45:23 +01:00
Lovell Fuller
3d01775972 Docs: changelog entries for #3748 #3755 #3758 2023-08-14 13:33:13 +01:00
sho-xizz
87562a5111 TypeScript: Ensure WebpOptions minSize is boolean (#3758) 2023-08-09 13:45:10 +01:00
Kleis Auke Wolthuizen
2829e17743 Fix build with musl 1.2.4 (#3755) 2023-08-07 21:57:00 +01:00
pilotso11
ffefbd2ecc TypeScript: add missing WebpPresetEnum (#3748) 2023-08-04 10:51:06 +01:00
Kleis Auke Wolthuizen
bc8f983329 Tests: ensure Jimp benchmark uses bicubic as resizing kernel (#3745) 2023-07-30 11:25:45 +01:00
Kleis Auke Wolthuizen
440936a699 Tests: update benchmark deps and container (#3744)
Use Node 18.x in benchmark container
2023-07-30 11:24:27 +01:00
Lovell Fuller
0bc79cdb95 Docs: include paletteBitDepth metadata 2023-07-28 16:04:02 +01:00
Lovell Fuller
9a66e25f53 Docs: ensure resize fit image supports dark mode 2023-07-25 10:06:44 +01:00
Lovell Fuller
8370935ccf Docs: ensure 'fit' values are clearly separated
Smaller text, slightly closer to image, varied fill colour
2023-07-21 23:07:57 +01:00
Kleis Auke Wolthuizen
f908987f35 Docs: use SVG image for the resize fit property example (#3735) 2023-07-21 21:58:02 +01:00
Lovell Fuller
aea368a3a0 Release v0.32.4 2023-07-21 11:41:08 +01:00
Lovell Fuller
7ecbc20d3d Upgrade to libvips v8.14.3 2023-07-21 11:10:21 +01:00
Lovell Fuller
cb0e2a91c4 Bump dep 2023-07-19 16:55:12 +01:00
Lovell Fuller
739b317a6f Expose ability to (un)block libvips ops by name 2023-07-19 16:53:52 +01:00
Lovell Fuller
a0e1c39785 Release v0.32.3 2023-07-14 11:03:39 +01:00
Lovell Fuller
85b26dab68 Expose preset option for WebP output #3639 2023-07-12 19:12:04 +01:00
Lovell Fuller
66f7cef253 Docs: fix a few typos 2023-07-12 14:22:29 +01:00
Lovell Fuller
863174f201 CI: FreeBSD: Use 13.2 stable, upgrade to Node.js 20 2023-07-12 12:10:31 +01:00
Lovell Fuller
bcd865cc96 Ensure decoding remains sequential for all ops #3725 2023-07-12 11:35:59 +01:00
Lovell Fuller
16ea04fe80 Release v0.32.2 2023-07-11 11:47:37 +01:00
Lovell Fuller
9c547dc321 Use copy rather than cache to prevent affine overcompute
More predictable behaviour, see commit 14c3346 for context
2023-07-10 13:56:42 +01:00
Lovell Fuller
5522060e9e Limit HEIF output dimensions to 16384x16384
This is a slightly breaking change to sync with the behaviour of a
forthcoming libvips patch release. It also matches the libavif
limit. Having an arbitrary limit is safer than no limit.
2023-07-10 10:24:14 +01:00
Lovell Fuller
d2f0fa855b Tests: loosen threshold for affine rotate then extract
Ignores rounding errors under Rosetta emulation
2023-07-10 08:12:13 +01:00
Lovell Fuller
2bb3ea8170 Bump deps 2023-07-09 11:57:05 +01:00
Lovell Fuller
3434eef5b9 Guard use of smartcrop premultiplied option #3710 2023-07-09 09:57:20 +01:00
Lovell Fuller
2f67823c3d Allow seq read for EXIF-based auto-orient #3725 2023-07-09 09:26:58 +01:00
Lovell Fuller
38c760cdd7 Update to latest (temporary) prebuild patch 2023-07-09 09:10:24 +01:00
Lovell Fuller
14c3346800 Prevent over-compute in affine rotate #3722 2023-07-09 09:04:07 +01:00
Lovell Fuller
0da55bab7e Tests: remove unused dependency 2023-06-29 09:11:25 +01:00
Lovell Fuller
cfb659f576 Bump deps 2023-06-23 08:19:41 +01:00
Lovell Fuller
cc5ac5385f Docs: clarify use of extract before composite 2023-06-23 08:08:39 +01:00
Lovell Fuller
93fafb0c18 CI: Upgrade to latest git v2 within centos 7 containers 2023-06-05 12:32:47 +01:00
Lovell Fuller
41e3c8ca09 Temporarily use patched prebuild with node-gyp v9 2023-06-05 09:45:12 +01:00
Lovell Fuller
da61ea0199 Docs: changelog and credit for #3674 2023-06-05 09:35:07 +01:00
BJJ
7e6a70af44 Improve detection of jp2 filename extensions #3674 2023-06-05 09:31:25 +01:00
Lovell Fuller
f5845c7e61 Ensure exceptions are not thrown when terminating #3569 2023-06-03 11:51:44 +01:00
Lovell Fuller
eb1e53db83 Bump deps 2023-06-03 11:51:12 +01:00
Lovell Fuller
3340120aea Types: include base input options for composite #3669 2023-05-16 13:55:28 +01:00
Lovell Fuller
de0fc07092 Ensure same access method for all inputs #3669 2023-05-16 13:53:31 +01:00
Lovell Fuller
dc4b39f73f Docs: multi-page images cannot be flipped 2023-05-13 08:54:21 +01:00
Lovell Fuller
e873978e53 Docs: clarify which axis is used when mirroring 2023-05-11 10:24:24 +01:00
Lovell Fuller
5255964c79 Docs: ensure headings with digits appear 2023-04-27 10:23:28 +01:00
Lovell Fuller
dea319daf6 Release v0.32.1 2023-04-27 09:58:40 +01:00
Lovell Fuller
a2ca678854 Docs: clarify text align applies to multi-line 2023-04-27 09:00:11 +01:00
Lovell Fuller
e98993a6e2 Bump node-addon-api for Buffer::NewOrCopy 2023-04-23 15:43:54 +01:00
Lovell Fuller
90abd927c9 Install: coerce libc version to semver (refactor) 2023-04-23 11:54:41 +01:00
Lovell Fuller
4d7957a043 Install: coerce libc version to semver #3641 2023-04-23 11:37:43 +01:00
Lovell Fuller
bf9bb56367 Docs: fix affine interpolator example 2023-04-22 13:56:33 +01:00
Lovell Fuller
8408e99aa3 Ensure trim op works with CMYK input #3636 2023-04-20 10:49:39 +01:00
Lovell Fuller
a39f959dcc Docs: add security policy
- Latest version is supported
- Report vulnerabilities via e-mail
2023-04-20 10:46:04 +01:00
Lovell Fuller
d08baa20e6 Install: log possible error when removing vendor dir 2023-04-19 11:06:16 +01:00
Lovell Fuller
391018ad3d Bump semver dep 2023-04-19 11:04:03 +01:00
Lovell Fuller
afed876f90 Docs: ensure inclusion of jp2 function
A misplaced code coverage comment was preventing this.
See ef849fd for the original commit where this broke.
2023-04-17 20:55:12 +01:00
Lovell Fuller
d6b60a60c6 Docs: add example of how to set EXIF GPS metadata 2023-04-17 20:35:47 +01:00
Lovell Fuller
5f8646d937 Support modulate op with non-sRGB pipeline colourspace #3620 2023-04-17 19:53:48 +01:00
Lovell Fuller
b763801d68 Ensure profile-less CMYK roundtrip skips space conv #3620 2023-04-11 20:31:57 +01:00
Lovell Fuller
2e0f789c9b Tests: add retries to text test suite
as font discovery is occasionally slow
in Windows CI environment.
2023-04-09 21:42:09 +01:00
Lovell Fuller
a8645f0f38 Smartcrop performance can take future advantage of
https://github.com/libvips/libvips/commit/de43eea
2023-04-09 21:17:08 +01:00
Lovell Fuller
7b58ad9360 Docs: changelog entry for #3615 2023-04-07 12:23:21 +01:00
TomWis97
9ebbcc3701 Logging: fix notation of proxy URL (#3615) 2023-04-07 12:19:04 +01:00
Lovell Fuller
e87204b92c Doc update and changelog entry for #3461 2023-04-07 11:21:15 +01:00
Anton Marsden
a4c6eba7d4 Add unflatten operation to create an alpha channel (#3461) 2023-04-07 11:01:29 +01:00
Lovell Fuller
b9c3851515 Ensure linear op works with 16-bit input #3605 2023-04-01 12:08:14 +01:00
Lovell Fuller
97cf69c26a Ensure use of flip op forces random access read #3600 2023-03-31 09:04:22 +01:00
Lovell Fuller
d5be024bfd Bump devDeps 2023-03-28 14:39:25 +01:00
Lovell Fuller
de01fc44e7 Docs: ensure API fn name linking is consistent 2023-03-28 14:00:52 +01:00
Lovell Fuller
ca102ebd6c Docs: fix perf result copypasta
An ARM64 value was incorrectly using an AMD64 result
2023-03-28 12:08:07 +01:00
Lovell Fuller
b9d4c30a9f Release v0.32.0 2023-03-24 17:05:59 +00:00
Lovell Fuller
148760fe55 Docs: clarify resize reduction/enlargement options refer to scaling
Types: options can be passed as first resize parameter
2023-03-24 15:19:21 +00:00
Lovell Fuller
98ed237734 Docs: use only first year of copyright to match code 2023-03-24 09:59:36 +00:00
Lovell Fuller
b55e58f31e Trim space from end of libvips error messages 2023-03-24 09:58:21 +00:00
Lovell Fuller
0af070ed93 Docs: update performance results, include PNG-based task 2023-03-23 18:57:43 +00:00
Lovell Fuller
9fbb4fcaef Tests: bump benchmark deps 2023-03-22 11:03:16 +00:00
Lovell Fuller
6008ff8a08 Docs: tile-based output requires libgsf 2023-03-22 09:17:07 +00:00
Lovell Fuller
cd5e11bd50 Docs: ensure parameters are indexed as they now appear in a table 2023-03-22 09:04:54 +00:00
Lovell Fuller
08d6822265 Upgrade to libvips v8.14.2 2023-03-21 21:21:24 +00:00
Lovell Fuller
8b8a815fbb Tests: tile-based output optional, will require custom libvips
The prebuilt binaries provided by v0.32.0 will not support
tile-based output, which is (hopefully) a temporary situation
until upstream licensing issues are resolved.
2023-03-21 21:19:56 +00:00
Lovell Fuller
a44da850c1 Docs: add open graph title and image 2023-03-21 12:56:44 +00:00
Lovell Fuller
c5ef4677b1 Bump tsd dep to pick up TypeScript 5 improvements 2023-03-21 12:43:29 +00:00
Lovell Fuller
f8a430bdd3 Tests: reduce CPU cost of RGBA linear test, ~2s faster 2023-03-21 12:42:50 +00:00
Lovell Fuller
cd419a261b Docs: changelog and refresh for #3583 2023-03-21 10:16:31 +00:00
LachlanNewman
d7776e3b98 Add support to normalise for lower and upper percentiles (#3583) 2023-03-21 10:13:12 +00:00
Lovell Fuller
1eefd4e562 Docs: how to provide new integrity values for custom binaries 2023-03-17 09:25:24 +00:00
cychub
0a16d26ec7 Docs: fix sharp_binary_host example by adding version (#3568) 2023-03-12 12:58:08 +00:00
Lovell Fuller
fc03fba602 Docs: clarify metadata ignores chained ops 2023-03-10 13:35:06 +00:00
Lovell Fuller
c87fe512b4 Bump devDeps 2023-03-08 16:59:20 +00:00
Lovell Fuller
2eaab59c48 Docs: add note about API Gateway integration 2023-03-08 16:54:49 +00:00
Lovell Fuller
4ec883eaa0 Wrap all async JS callbacks, help avoid possible race #3569 2023-03-01 12:41:11 +00:00
Lovell Fuller
0063df4d4f Ensure clahe op uses random read, simplify validation 2023-02-28 21:59:31 +00:00
Lovell Fuller
6c61ad256f Ensure all source code files contain SPDX licence 2023-02-28 17:01:58 +00:00
Lovell Fuller
b90474affa Docs: clarify formats that support multi-page/anim 2023-02-28 14:39:49 +00:00
Lovell Fuller
34cbc6dec3 Docs: clarify that paths are relative to process working dir 2023-02-23 10:33:13 +00:00
Lovell Fuller
bb8de0cc26 Docs: refresh search index 2023-02-18 12:51:42 +00:00
Lovell Fuller
863e37455a Docs: changelog and credit for #3556 2023-02-18 12:50:58 +00:00
Tomasz Janowski
6f0e6f2e65 Add support to extend for extendWith, allows copy/mirror/repeat (#3556) 2023-02-17 14:01:24 +00:00
Lovell Fuller
ebf4ccd124 Bump deps 2023-02-12 19:49:32 +00:00
Lovell Fuller
b96c8e8ba4 Tests: use native fs.rm instead of rimraf 2023-02-12 19:32:00 +00:00
Lovell Fuller
42d2f07e44 Add ignoreIcc input option to ignore embedded ICC profile 2023-02-12 17:51:24 +00:00
Lovell Fuller
a2988c9edc macOS: use 10.13 as minimum to match prebuilt libvips
This allows clang to use SSE4 intrinsics
2023-02-12 16:10:21 +00:00
Lovell Fuller
24b3344937 Docs: changelog for #3548 2023-02-05 09:49:06 +00:00
Jérémy Lal
9608f219bd Add support for ArrayBuffer input (#3548) 2023-02-05 09:45:17 +00:00
Pascal Jufer
4798d9da64 Docs: clarify supported bit depth for AVIF images (#3541) 2023-02-02 17:47:07 +00:00
Lovell Fuller
8d8c6b70eb Prefer integer (un)premultiply for faster RGBA resize
Add changelog, loosen modulate test thresholds
2023-01-24 15:44:39 +00:00
Lovell Fuller
9e2207f376 Prefer integer (un)premultiply for faster RGBA resize 2023-01-24 15:24:58 +00:00
Lovell Fuller
802f560b9b Test: update benchmark dependencies 2023-01-24 15:21:31 +00:00
Lovell Fuller
a532659b0f Types: changes/additions relating to new v0.32.0 features
A separate commit is required as these were not part of the
initial definitions in the v0.31.3 snapshot.

From now on, new features and updates can include the relevant
TypeScript definition changes as part of the same
code/docs/tests commits.
2023-01-17 16:11:04 +00:00
Lovell Fuller
25c6da2bcd Docs: add a couple of missing params/props 2023-01-17 15:01:52 +00:00
Lovell Fuller
02f855d57a Expose own version as sharp.versions.sharp #3471 2023-01-17 09:56:58 +00:00
Lovell Fuller
c150263ef1 Respect fastShrinkOnLoad option for WebP input #3516 2023-01-17 09:39:23 +00:00
Lovell Fuller
9f79f80a93 Docs: fastShrinkOnLoad can round-down when auto-scaling 2023-01-16 12:06:50 +00:00
Lovell Fuller
069803b83d Docs: remove Heroku install section 2023-01-16 12:06:08 +00:00
Lovell Fuller
f79760b4f2 Docs: changelog and help for TypeScript defs #3369 #3370 2023-01-16 11:12:00 +00:00
Espen Hovlandsdal
aa5f0f4e40 Include and publish TypeScript definitions (#3370)
Definitions are a snapshot taken from `@types/sharp`,
which remain under the terms of MIT licensing.
2023-01-16 10:48:37 +00:00
Lovell Fuller
286a322622 Docs: changelog and doc refresh for #3470 2023-01-16 09:27:31 +00:00
Emanuel Jöbstl
6d404f4d2c Add coords to output when using attention based crop (#3470) 2023-01-16 09:20:42 +00:00
Lovell Fuller
bdc50e1d6e Unpin node-addon-api, cast CallbackInfo access to size_t
See https://github.com/nodejs/node-addon-api/pull/1253
2023-01-16 09:00:42 +00:00
Lovell Fuller
a9bd0e79f8 Pin node-addon-api to workaround possible bug in 5.1.0 2023-01-15 19:35:27 +00:00
Lovell Fuller
a1e464cc5e Switch to sequential read as default where possible 2023-01-15 18:43:50 +00:00
Lovell Fuller
081debd055 Reduce sharpen op max sigma from 10000 to 10 #3521 2023-01-10 16:29:40 +00:00
Lovell Fuller
ef849fd639 Docs: switch to well-maintained jsdoc2md for JSDoc parsing 2023-01-08 10:15:38 +00:00
Lovell Fuller
a42a975c46 Bump devDeps 2023-01-06 19:25:27 +00:00
Lovell Fuller
e8273580af Docs: add note about use of fastShrinkOnLoad with resize kernel 2023-01-06 19:24:32 +00:00
Lovell Fuller
5be36c2deb Install: log Rosetta detection, improve related docs 2023-01-04 21:11:21 +00:00
Kleis Auke Wolthuizen
6cda090ce2 Tests: remove ICC profile from CIELAB fixture (#3510)
This ICC profile is considered incompatible with this image.

See: https://github.com/libvips/libvips/issues/730
2023-01-01 21:12:29 +00:00
Lovell Fuller
eac6e8b261 Upgrade to libvips v8.14.0-rc1
- Replace GIF 'optimise' option with 'reuse'
- Add 'progressive' option to GIF
- Add 'wrap' option to text creation
- Add 'formatMagick' property to *magick input metadata
2022-12-29 15:53:50 +00:00
Lovell Fuller
844deaf480 Release v0.31.3 2022-12-21 15:57:10 +00:00
Lovell Fuller
efbb0c22fd Docs: add image with examples of resize fit property 2022-12-21 15:47:39 +00:00
Lovell Fuller
da0b594900 Docs: update benchmarks for latest versions, add ARM64 results 2022-12-20 19:49:29 +00:00
Lovell Fuller
78dada9126 Tests: skip mapnik and tensorflow for Docker-run benchmarks
Maintainance of mapnik seems to have stalled, no ARM64 support
Memory requirements of Tensorflow too high, hangs/crashes on AMD64
2022-12-20 18:20:59 +00:00
Lovell Fuller
15f5cd4671 Tests: move mapnik to optional deps
It does not currently support ARM64
2022-12-19 19:47:46 +00:00
Lovell Fuller
9eb2e94404 Tests: update benchmark dependencies 2022-12-17 14:29:11 +00:00
Lovell Fuller
e40b068628 Tests: update leak suppresions for latest dependencies 2022-12-14 21:57:42 +00:00
Lovell Fuller
2c46528269 Docs refresh 2022-12-14 16:17:42 +00:00
Lovell Fuller
584807b4f5 Add runtime detection of V8 memory cage #3384
When using the V8 memory cage, Buffers cannot be wrapped and then
later freed via a callback. When the cage is detected via a throw,
instead fall back to copying Buffer contents to V8 memory.

This approach will be used by Electron 21+ and you should expect
reduced performance and increased memory consumption/fragmentation.
2022-12-14 16:06:04 +00:00
Lovell Fuller
a7fa7014ef Add experimental support for JPEG-XL, requires libvips with libjxl
The prebuilt binaries do not include support for this format.
2022-12-13 21:55:17 +00:00
Lovell Fuller
f92e33fbff Bump devDeps 2022-12-13 10:31:06 +00:00
Lovell Fuller
0f1e7ef6f6 Install: add support for Linux with glibc patch version #3423 2022-12-09 12:03:41 +00:00
Lennart
89e204d824 Docs: clarify failOn property applies to decoding pixel values (#3481) 2022-12-08 16:13:18 +00:00
Lovell Fuller
2a71f1830f Expand range of sharpen params to match libvips #3427 2022-12-07 09:28:01 +00:00
Lovell Fuller
def99a294a Install: log proxy use, if any, to aid with debugging 2022-12-06 19:35:47 +00:00
Lovell Fuller
9d760f3958 Improve perf of ops that introduce non-opaque background #3465 2022-12-05 20:40:41 +00:00
Lovell Fuller
0265d305fe Ensure integral output of linear op #3468 2022-12-04 21:41:15 +00:00
Lovell Fuller
a472aea025 Ignore sequentialRead option for stats #3462 2022-11-20 21:30:45 +00:00
Lovell Fuller
01ffa80338 Improve extractChannel support for 16-bit output #3453 2022-11-15 15:00:32 +00:00
Lovell Fuller
789d4851ea Tests: remove flaky font assertions
Probably due to Windows CI env font discovery
2022-11-15 10:08:43 +00:00
Lovell Fuller
4490a93430 Tests: simplify beforeEach configuration
Remove legacy settings for previous CI providers/hardware
2022-11-15 09:54:29 +00:00
Ingvar Stepanyan
ac0dc10bd5 Tests: convert mocha hooks (#3450) 2022-11-15 08:58:09 +00:00
Lovell Fuller
5740f4545e Expose GIF opts: interFrameMaxError, interPaletteMaxError #3401 2022-11-14 16:09:52 +00:00
Lovell Fuller
a9d692fb43 Reduce chance of race condition in test for... race condition 2022-11-13 10:16:47 +00:00
Lovell Fuller
df971207b8 Prevent possible race condition when reading metadata #3451 2022-11-13 10:04:55 +00:00
Ingvar Stepanyan
3a64a0529a Tests: run in parallel, move settings to config file (#3449)
This allows to easily invoke Mocha alone via `npx mocha`, or for e.g. VSCode Test Explorer to find and run tests with the correct settings automatically.
2022-11-10 21:48:18 +00:00
Peter Whidden
76cda885fb Docs: fix minor typo in resize properties (#3444) 2022-11-09 08:44:05 +00:00
Ingvar Stepanyan
1a563360c6 Fix errors for missing OpenJPEG (#3442)
Fixes couple of minor issues with JP2 errors:

1. The tests passed as false-positives even if regex is changed to arbitary pattern, because the promise returned from `assert.rejects` was ignored and the test ended prematurely. This is fixed by removing `{ ... }` around the test function body.
2. This, in turn, hid an issue with `toFile` not throwing the expected error message which was instead propagating `Error: VipsOperation: class "jp2ksave" not found` from libvips. This is now fixed by manually checking the extension before calling into libvips.
3. Pre-creating error instances like `errJp2Save` did is sometimes tempting, but is problematic for debugging because it hides the actual stacktrace of the error (the stacktrace is collected at the moment of `new Error` creation). This is now turned into a function that creates error with the right stack.
2022-11-08 19:53:14 +00:00
Lovell Fuller
ca22af203f Docs: canvas on Windows uses MSVCRT, conflicts with UCRT 2022-11-07 21:14:51 +00:00
Lovell Fuller
9fa516e849 Release v0.31.2 2022-11-04 09:44:37 +00:00
Lovell Fuller
12f472126d CI: Only pin Python version on x64 macOS and Windows
See commit 18be09f
2022-11-03 14:49:12 +00:00
Lovell Fuller
18be09f1d7 CI: Pin Python to 3.10
Python 3.11 removes support for opening files in
'universal newline' mode (e.g. 'rU'), however older
versions of node-gyp such as v6 still use it.
2022-11-03 14:40:16 +00:00
Lovell Fuller
b3c3290f90 Upgrade to libvips v8.13.3 2022-11-03 14:09:23 +00:00
Lovell Fuller
123f95c85a Bump devDeps 2022-11-03 12:50:58 +00:00
Lovell Fuller
5b0fba4c01 Ensure auto-rotate always works without resize #3422 2022-11-02 13:59:34 +00:00
Lovell Fuller
37f7ccfff4 CI: upgrade to checkout v3 2022-10-17 16:05:30 +01:00
Lovell Fuller
51811d06e2 Bump deps 2022-10-17 16:05:03 +01:00
Lovell Fuller
181731f8f4 Tests: increase timeout to 30s
Font discovery appears to be slooow on Windows
2022-10-17 15:54:43 +01:00
Gino Emiliozzi
ae79d26ead Docs: help clarify 'fit' is option name, not value (#3410) 2022-10-17 15:26:59 +01:00
Lovell Fuller
eacb8337fa Ensure manual flip, rotate, resize op order #3391 2022-10-01 11:55:29 +01:00
Lovell Fuller
99bf279de8 Release v0.31.1 2022-09-29 14:51:45 +01:00
Lovell Fuller
1b0eb6ab53 Tests: add assertion to existing scenario #3357 2022-09-29 14:21:37 +01:00
Lovell Fuller
891cf67d0b Upgrade to libvips v8.13.2 2022-09-29 14:19:58 +01:00
Lovell Fuller
eaf8a86bf2 Tests: increase timeout to 20s
Ignore unit coverage of fn used at install time
2022-09-27 14:25:34 +01:00
Lovell Fuller
3400976d61 Tests: ignore a branch for coverage check 2022-09-27 14:10:49 +01:00
Lovell Fuller
2d49f0e93e Tests: require 100% branch coverage to pass
Remove old coverage tooling, coveralls
2022-09-27 13:49:42 +01:00
Lovell Fuller
b0c69f1ee9 CI: use clearer job names 2022-09-27 10:02:20 +01:00
Lovell Fuller
27d0c35a01 CI: use clearer job names 2022-09-27 09:23:30 +01:00
Alex
32a22b5420 CI: GitHub Workflows security hardening (#3377)
Signed-off-by: Alex <aleksandrosansan@gmail.com>
2022-09-26 11:25:49 +01:00
Lovell Fuller
d1004eed02 Ensure greyscale images can be trimmed #3386 2022-09-26 10:15:25 +01:00
Lovell Fuller
70e6bb0162 Ensure close event occurs after end event #3313 2022-09-20 08:52:40 +01:00
Lovell Fuller
32aa3b4b20 Tests: bump/pin benchmark dependencies 2022-09-19 16:21:24 +01:00
Lovell Fuller
df24b30755 Tests: add tfjs to benchmark tests 2022-09-19 16:20:23 +01:00
Lovell Fuller
4de74bea94 Tests: remove assertions from benchmark code 2022-09-19 16:09:31 +01:00
Lovell Fuller
28b87db760 Ensure AVIF output is always 8-bit #3358 2022-09-14 13:33:47 +01:00
Lovell Fuller
fbd4970b57 Ensure auto-rotation works with shrink-on-load #3352
Fixes regression in 0.31.0
2022-09-07 14:17:40 +01:00
Lovell Fuller
f5da147a58 Docs: changelog and credit for #3349 2022-09-07 13:31:26 +01:00
Marcos Casagrande
eee0dd36d9 Ensure limitInputPixels uses uint64 (#3349) 2022-09-06 09:05:51 +01:00
Lovell Fuller
f7b29d7b59 CI: ensure prebuilds use Node API version 7 2022-09-05 10:14:40 +01:00
Lovell Fuller
0b806187fc Release v0.31.0 2022-09-05 09:55:27 +01:00
Lovell Fuller
c1393daa70 Expose unlimited option for HEIF input 2022-09-05 09:19:53 +01:00
Lovell Fuller
31c1cfb049 Docs: add note about GIF optimisation 2022-09-05 09:19:53 +01:00
Lovell Fuller
afc35c2208 Docs: update benchmark results for libvips v8.13.1 2022-09-05 09:19:53 +01:00
Lovell Fuller
6eb2add3bf Docs: refresh for 5cdb2b8 2022-09-04 13:17:02 +01:00
Lovell Fuller
5cdb2b83d5 Upgrade to libvips v8.13.1 2022-09-04 12:42:27 +01:00
Lovell Fuller
1eb66c0944 Tests: relax truncated PNG test assertion 2022-09-04 11:19:01 +01:00
Lovell Fuller
55c4d8807c Improve normalise op with use of histogram #200 2022-09-04 10:31:43 +01:00
Lovell Fuller
9a54a034e1 Tests: ensure truncated PNG test handles possible race 2022-09-04 10:28:24 +01:00
Lovell Fuller
f5109560d6 Standardise approach for string to enum conversion 2022-09-02 12:20:09 +01:00
Lovell Fuller
953a94885b Tests: run benchmarks in container via Docker 2022-09-01 14:58:39 +01:00
Lovell Fuller
0e3bd46ca3 Docs: clarify that metadata respects page/pages 2022-09-01 09:58:17 +01:00
Lovell Fuller
4b38f56d02 Docs: add avif and heif examples 2022-09-01 09:57:50 +01:00
Lovell Fuller
0fe857c5ac Docs: move serverless-esbuild to bundlers section 2022-08-24 17:42:56 +01:00
A. Sayef Reyadh
1bf06bd5b4 Docs: add info for serverless-esbuild users (#3235) 2022-08-24 17:37:58 +01:00
Lovell Fuller
6e3f4c3c92 Docs: changelog for #3332 2022-08-23 13:00:02 +01:00
Lovell Fuller
8583eb1235 Tests: update leak suppressions for latest versions 2022-08-23 12:32:39 +01:00
Mart
c3a852eecf Add trim option to provide a specific background colour (#3332)
Co-authored-by: Mart Jansink <mart@cinemait.nl>
2022-08-23 12:28:02 +01:00
Lovell Fuller
3a44748f49 Ensure PNG bitdepth can be set for non-palette output #3322 2022-08-22 14:57:12 +01:00
Lovell Fuller
e1bc8674fd Docs: clarify composite operation ordering 2022-08-21 19:53:04 +01:00
Lovell Fuller
a618ce7a15 Ensure image is unpremultiplied before composite #3334 2022-08-21 17:51:05 +01:00
Lovell Fuller
a44168c8c7 Docs: changelog and credit for #3303 2022-08-20 10:27:31 +01:00
Anton Marsden
74e3f73934 Expand linear operation to allow use of per-channel arrays #3303 2022-08-20 10:27:04 +01:00
Lovell Fuller
b9261c243c Bump devDeps, requires doc refresh 2022-08-20 09:23:24 +01:00
Lovell Fuller
cc9f91f37c Docs: note about macos fontconfig vs coretext 2022-08-18 16:41:33 +01:00
Lovell Fuller
212a6e7519 Ensure op ordering is respected where possible #3319
Emit warnings when previous ops might be ignored
Flip and flop now occur before rotate, if any
2022-08-18 16:41:33 +01:00
Kid
e547eaa180 Docs: fix broken link to Got stream documentation (#3323) 2022-08-07 14:25:36 +01:00
Lionel Tzatzkin
9a0d9eed74 Docs: correct syntax in sharpen example (#3310) 2022-07-29 12:11:16 +01:00
Lovell Fuller
dd56a9699e Docs: changelog and credit for #3252 2022-07-25 12:19:25 +01:00
brahima
ea7cf2a2ef Expose vips_text to create an image containing rendered text (#3252) 2022-07-25 11:32:10 +01:00
Lovell Fuller
76c4c51e2a Remove previously-deprecated reductionEffort and speed options 2022-07-24 11:18:16 +01:00
Lovell Fuller
b46ab510da Add Buffer and Stream support to tile output #2238 2022-07-24 11:06:41 +01:00
Lovell Fuller
3e327a586c Docs: add section about font discovery 2022-07-23 16:31:00 +01:00
Lovell Fuller
974fab946e Bump devDeps 2022-07-23 16:21:42 +01:00
Lovell Fuller
f998a8f249 Upgrade to libvips v8.13.0 2022-07-23 10:04:14 +01:00
Lovell Fuller
be331e958e CI: upgrade to macOS 11 2022-07-22 23:20:33 +01:00
Lovell Fuller
254944f8ab CI: install Noto font via package manager 2022-07-18 13:35:22 +01:00
Lovell Fuller
f1e640d231 Tests: catch potential failures during tile unzip 2022-07-14 11:13:59 +01:00
Lovell Fuller
c295f06a6f Ensure only props owned by EXIF Object are parsed #3292 2022-07-13 21:33:06 +01:00
Lovell Fuller
6288c7bced Expose reoptimise palette option for GIF output 2022-07-12 21:12:31 +01:00
Lovell Fuller
d247c02762 Add mixed and minSize animation options for WebP output 2022-07-12 14:51:03 +01:00
Lovell Fuller
1b84ccbbe9 Ensure fileSuffix supports suffix-less loaders 2022-07-11 20:56:19 +01:00
Lovell Fuller
e4160c684d Docs: add timeout example 2022-07-11 11:12:32 +01:00
Lovell Fuller
905518fab0 Add input fileSuffix and output alias to format #2642 2022-07-11 10:45:19 +01:00
Lovell Fuller
8ff33763ce Ensure OpenSlide+FITS input works with custom libvips #3226 2022-07-08 22:25:39 +01:00
Lovell Fuller
cbf741cac7 Ensure trim is no-op when it would reduce to nothing #3223 2022-07-08 21:06:58 +01:00
Lovell Fuller
6c2e2be41d CI: upgrade Linux arm64 environment to Node.js 14/18 2022-07-05 19:42:05 +01:00
Lovell Fuller
e0d3c6e05d Use bounding box of alpha+non-alpha for trim op #2166 2022-07-05 18:19:17 +01:00
Lovell Fuller
e3cab7f10f CI: fix path to gcc toolset 2022-06-29 12:40:04 +01:00
Lovell Fuller
204463ffbb CI: ensure gcc 11 toolset is available on the PATH 2022-06-29 12:26:16 +01:00
Lovell Fuller
1bcd3700c5 CI: Node.js 18 does not support CentOS 7, upgrade to Rocky 8 2022-06-29 12:18:41 +01:00
Lovell Fuller
c99a11cff5 CI: add Node.js 18, remove 12 2022-06-29 11:52:41 +01:00
Lovell Fuller
81c74f57e0 Re-introduce support for greyscale ICC profiles #3114 2022-06-29 11:41:36 +01:00
Lovell Fuller
7a8ab452c5 Add support for WebP and PackBits compression with TIFF output #3198 2022-06-29 11:35:58 +01:00
Lovell Fuller
bb91912883 Drop support for Node.js 12, now requires >= 14.15.0 2022-06-29 10:32:25 +01:00
Kleis Auke Wolthuizen
afc4c5bf79 Upgrade to libvips v8.13.0-rc1 (#3230)
* Switch from decompress-zip to extract-zip

The former seems to hang when unzipping a ZIP64 file that uses
the general purpose bit flag 3 as file entry.

See: https://github.com/thejoshwolfe/yauzl#no-streaming-unzip-api

* Prefer to call via static member instead

Makes it clearer that a static method is being called.

* `flatten-orange.jpg`: save without chroma subsampling

To ensure no down-scaling of the Cr/Cb channels.
2022-06-26 22:39:29 +01:00
Lovell Fuller
e40a881ab4 Release v0.30.7 2022-06-22 16:44:16 +01:00
Lovell Fuller
c1b13adac3 Bump deps 2022-06-22 11:53:22 +01:00
Lovell Fuller
29e09898f7 Docs: add examples of custom binary locations 2022-06-22 11:50:30 +01:00
Lovell Fuller
853a20358e Install: add help for possible worker thread problem #3268 2022-06-21 08:22:05 +01:00
Lovell Fuller
8bb30d7801 Docs: changelog and credit #3261 #3267 2022-06-21 07:35:28 +01:00
Blayne Chard
a333b87f5d Prevent upsampling via libwebp (#3267) 2022-06-20 10:49:53 +01:00
AlexanderTheGrey
4662527a17 Allow WebP encoding effort of 0 (#3261) 2022-06-17 08:22:51 +01:00
Lovell Fuller
b10d8f89ca Docs: add example of multi-arch within same install tree 2022-06-10 12:51:44 +01:00
Oleg Andreyev
f903e1465e Docs: clarify wording of resize background option 2022-06-08 12:56:29 +01:00
Lovell Fuller
a75718565c Ensure composite can tile with outside resize #3227 2022-06-08 12:39:00 +01:00
Fonger
4d82331bf6 docs(input): correct getNormalSize with EXIF orientation example (#3241) 2022-05-31 08:59:15 +01:00
Lovell Fuller
b91875d3d9 Release v0.30.6 2022-05-30 08:56:14 +01:00
Lovell Fuller
a0568ec0c3 Allow values for limitInputPixels larger than 32-bit #3238 2022-05-28 08:35:17 +01:00
Lovell Fuller
48e3ea5e29 Ensure brew-installed vips can be detected #3239 2022-05-26 22:58:24 +01:00
Lovell Fuller
93b29057e4 Docs: changelog and credit for #3160 2022-05-24 21:14:13 +01:00
Lovell Fuller
db654de385 Release v0.30.5 2022-05-23 12:19:02 +01:00
Lovell Fuller
a6aeef612b Install: pass PKG_CONFIG_PATH via env rather than substitution 2022-05-23 12:12:19 +01:00
Lovell Fuller
7bf6cbd669 Docs: correct links to libvips documentation 2022-05-22 12:14:24 +01:00
Lovell Fuller
04c31b35a7 Install: warn about filesystem owner running npm v8+ as root 2022-05-15 19:43:45 +01:00
Lovell Fuller
ee9cdb6598 Bump deps 2022-05-15 17:20:33 +01:00
Lovell Fuller
8960eb8309 Docs: changelog entry for #3218 2022-05-15 17:12:51 +01:00
jakob0fischl
54d9dc46f5 Fix rotate-then-extract for EXIF orientation 2 (#3218) 2022-05-15 12:26:57 +01:00
Joona Heinikoski
51b4a7c564 Add support for --libc flag to improve cross-platform install (#3160)
This deprecates the libc-as-suffix approach of --platform=linuxmusl
2022-04-28 11:24:45 +01:00
Lovell Fuller
5b03579e5c Docs: more details about concurrency, parallelism, threads 2022-04-25 14:40:55 +01:00
Lovell Fuller
58c2af3251 Docs: improve output format info for toBuffer 2022-04-25 11:41:06 +01:00
Lovell Fuller
ee948ac6fa Docs: changelog and credit for #3196 2022-04-25 11:33:05 +01:00
Ankur Parihar
66a3ce5e55 Allow installation of prebuilt libvips binary from filesystem (#3196) 2022-04-24 18:47:57 +01:00
David Humphrey
75e5afcd42 Docs: fix typo in gif example (#3201) 2022-04-22 16:09:28 +01:00
Lovell Fuller
d396a4e65d Release v0.30.4 2022-04-18 10:14:31 +01:00
Lovell Fuller
ae1dbcdf4e Bump deps 2022-04-18 10:11:30 +01:00
Lovell Fuller
4c29368b51 Docs: EXIF metadata unsupported for TIFF output #3074 2022-04-18 09:53:35 +01:00
Lovell Fuller
36e55969d2 Docs: mention npm's foreground-scripts option to aid debugging 2022-04-18 09:51:27 +01:00
Lovell Fuller
985e881e7a Bump deps 2022-04-05 21:14:09 +01:00
Lovell Fuller
0b116715aa Docs: changelog for #3178 2022-04-05 21:11:06 +01:00
Chris Hranj
9deac83322 Add missing file name to 'Input file is missing' error message (#3178) 2022-04-05 11:35:01 +01:00
Lovell Fuller
5d36f5f699 Improve error message for SVG render above limit #3167 2022-04-04 14:20:04 +01:00
Lovell Fuller
926572b41e Control sensitivity to invalid images via failOn
Deprecates failOnError, equivalent to failOn=warning
2022-04-04 12:27:45 +01:00
Lovell Fuller
d0c8e95641 Docs: expand info about use with worker threads 2022-03-30 10:09:15 +01:00
Lovell Fuller
b0ca23c3e7 Docs: changelog and credit for #3146 2022-03-28 21:32:38 +01:00
codepage949
c3a0d5f5d0 Add support for TypedArray input with byteOffset and length 2022-03-27 20:18:58 +01:00
Lovell Fuller
1d36936954 Ensure create has correct bit depth and colourspace #3139 2022-03-22 19:48:02 +00:00
Lovell Fuller
b609df4b48 Release v0.30.3 2022-03-14 11:54:15 +00:00
Lovell Fuller
c567d3b9ab Bump devDeps 2022-03-14 11:52:45 +00:00
Marvin ROGER
27d9fe2a4e Add additional debugging hint on symbol error (#3132) 2022-03-14 10:19:02 +00:00
Lovell Fuller
ac883c5215 Docs: composite image supports the animated property #3135 2022-03-14 08:53:00 +00:00
Lovell Fuller
e8720c9374 Docs: use SVG favicon with a PNG fallback
Remove unused apple-touch icons
2022-03-13 14:22:05 +00:00
Lovell Fuller
42e45d842a Docs: add more examples (composite, greyscale, tint) 2022-03-12 20:33:15 +00:00
Lovell Fuller
72fd8abe2c Docs: add section on (preventing) bundling with esbuild 2022-03-12 13:17:36 +00:00
Lovell Fuller
ac18bbbc7c Ensure backwards-compatibility of JSDoc introduced in ea599ad 2022-03-11 19:07:11 +00:00
Lovell Fuller
fcbe4e1e01 Tests: remove possible race condition
Simplify JP2 quality size check
2022-03-09 19:55:32 +00:00
Lovell Fuller
9280742385 CI: update FreeBSD environment 2022-03-09 19:26:43 +00:00
Lovell Fuller
7a1a1cf9e8 Docs: add/correct some operation examples 2022-03-09 19:18:41 +00:00
Lovell Fuller
ea599ade10 Allow sharpen options to be provided as an Object
Also exposes x1, y2, y3 parameters #2561 #2935
2022-03-09 19:07:08 +00:00
Lovell Fuller
1de49f3ed8 Docs: refresh for metadata example #3127 2022-03-09 16:02:27 +00:00
Rasmus Schultz
4ac65054bc Docs: add example of how to get the right-side-up width/height (#3127)
See #3124
2022-03-09 15:59:09 +00:00
Lovell Fuller
23033e2050 Prevent double unpremultiply with some composite blends 2022-03-04 23:17:07 +00:00
Lovell Fuller
dd3b78272a Docs: remove experimental status from existing stats properties 2022-03-04 07:58:03 +00:00
Lovell Fuller
80d169b7c2 Release v0.30.2 2022-03-02 11:24:35 +00:00
Lovell Fuller
003279a0b0 CI: switch 32-bit Windows from Appveyor to Actions 2022-03-02 11:16:21 +00:00
Lovell Fuller
af80d7e389 Improve error message for missing file that might be SVG 2022-03-02 09:58:55 +00:00
Lovell Fuller
21a960796c Ignore greyscale ICC profiles due to lcms bug #3112 2022-02-28 11:28:08 +00:00
Lovell Fuller
fc3b4a683d Expand pkgconfig search path for wider BSD support #3106 2022-02-27 09:39:21 +00:00
Lovell Fuller
808133e7dc Docs: changelog entry for #3110 2022-02-26 19:40:17 +00:00
Lovell Fuller
801b6fea6c Bump devDeps 2022-02-26 19:39:46 +00:00
Kleis Auke Wolthuizen
c2ecde6a16 Windows: ensure C++ runtime is linked statically (#3110)
And remove the empty invalid parameter handler, which should
be present in the C layer instead.

This partially reverts commit
659cdabd8e,
the added test case in that commit is still preserved.
2022-02-26 19:15:37 +00:00
Lovell Fuller
55efe5602b Bump deps 2022-02-16 19:12:27 +00:00
Lovell Fuller
c62002554b Improve performance and accuracy of multi-image composite #2286 2022-02-16 19:04:23 +00:00
Lovell Fuller
7f83ecd255 Issue templates: small formatting fixes 2022-02-15 10:54:36 +00:00
Lovell Fuller
dc5f4dcd28 Issue templates: improve guidance, increase filtering 2022-02-15 10:50:26 +00:00
Lovell Fuller
735793ba99 Release v0.30.1 2022-02-09 16:38:21 +00:00
Lovell Fuller
47792df689 Docs: update interpolator links 2022-02-09 15:45:05 +00:00
Lovell Fuller
5c6cdfaece Docs: changelog entry for #3083 2022-02-09 15:27:52 +00:00
Kleis Auke Wolthuizen
115a6b10f6 Ensure affineInterpolator is correctly finalised (#3083) 2022-02-09 14:45:11 +00:00
Lovell Fuller
4feee506cf Docs: changelog entry for #3081 2022-02-08 22:08:07 +00:00
Kleis Auke Wolthuizen
83db5f8a2a Ensure withoutReduction does not interfere with contain/crop/embed (#3081) 2022-02-08 21:22:23 +00:00
Kleis Auke Wolthuizen
7eb5efa3a3 Update tests and comments after 7faacd9 (#3080) 2022-02-08 21:12:38 +00:00
Lovell Fuller
5a9f89fe06 Docs: update references to default branch 2022-02-08 21:01:40 +00:00
Lovell Fuller
02e0c2dfc9 Tests: arm64 requires slight threshold increase 2022-02-08 21:00:30 +00:00
Lovell Fuller
968d9d7008 Bump devDeps 2022-02-08 18:29:25 +00:00
Kleis Auke Wolthuizen
7faacd91b0 Avoid fastShrinkOnLoad workaround 2022-02-08 18:20:25 +00:00
Lovell Fuller
154eaff4ec Issue template: include npm v8+ 2022-02-08 18:20:11 +00:00
Lovell Fuller
424660278d Allow use of toBuffer+toFile w/ same instance #3044 2022-02-03 22:01:46 +00:00
Lovell Fuller
2b01951306 Skip JPEG shrink-on-load for known rounding errors #3066
Co-authored-by: Kleis Auke Wolthuizen <github@kleisauke.nl>
2022-02-03 21:19:48 +00:00
Lovell Fuller
83bb6a4554 Release v0.30.0 2022-02-01 20:36:12 +00:00
Lovell Fuller
c72d42816d Docs: update performance test results for next release 2022-02-01 18:19:35 +00:00
Lovell Fuller
35a81a7af2 Upgrade to libvips v8.12.2 2022-02-01 10:52:02 +00:00
Lovell Fuller
9dc8db4370 Upgrade to libvips v8.12.2 2022-01-29 16:46:07 +00:00
Lovell Fuller
47ae1f52db Docs: metadata returns resolutionUnit, if present 2022-01-29 15:24:44 +00:00
Lovell Fuller
ec17d7f580 Bump deps: prebuild, for latest detect-libc 2022-01-29 15:11:50 +00:00
Lovell Fuller
da5453a7c0 Docs: changelog and refresh for #3023 2022-01-29 14:59:20 +00:00
ompal
f7bed69ffb Add resolutionUnit to metadata and as tiff option #3023
Co-authored-by: Lovell Fuller <github@lovell.info>
2022-01-29 14:31:47 +00:00
Kleis Auke Wolthuizen
7aa340232e Ensure delays of the origin image is preserved (#3062)
By not initializing the delay vector, since we assume it's empty by default.

See: #3061.
2022-01-28 11:50:31 +00:00
Kleis Auke Wolthuizen
68823a5edb Take page parameter into account when all frames are read (#3059) 2022-01-27 21:42:08 +00:00
Simon
1c3ba303ea Update simple-get version (#3057)
Co-authored-by: Simon Tannai <stannai@socialkitchen.fr>
2022-01-27 10:57:56 +00:00
Lovell Fuller
76f8112952 Docs: npm v8 2022-01-27 09:24:29 +00:00
Lovell Fuller
24150eac89 Docs: Linux x64 binaries require CPU with SSE4.2
Intel has offered this since 2010, AMD since 2011
2022-01-24 13:51:19 +00:00
Lovell Fuller
51121a1440 Bump deps 2022-01-24 10:29:43 +00:00
Lovell Fuller
3b370b6c01 CI: add Rosetta-emulated x64 2022-01-24 10:19:04 +00:00
Lovell Fuller
884947a069 Upgrade to modern detect-libc 2022-01-19 11:27:24 +00:00
Lovell Fuller
f8340e1a82 Bump deps 2022-01-18 15:52:26 +00:00
Lovell Fuller
5101f4e79c CI: Fix linux-arm64v8 docker permission problems with npm v8 2022-01-18 15:50:25 +00:00
Lovell Fuller
161d127bf3 Docs refresh 2022-01-18 10:29:18 +00:00
Lovell Fuller
638d540371 Docs: mild blur is a box filter, add examples 2022-01-18 10:29:10 +00:00
Lovell Fuller
d8f1298511 Ensure rotate-then-extract works with EXIF mirroring #3024 2022-01-08 18:48:11 +00:00
Lovell Fuller
d67e09ba7c Add support for IIIF v3 tile-based output 2022-01-07 20:45:48 +00:00
Lovell Fuller
4c3a8a7007 Docs: fix example formatting, remove outdated warning 2021-12-21 12:31:29 +00:00
Lovell Fuller
92399ee5e2 Docs: correct gif resize example 2021-12-13 18:26:35 +00:00
Lovell Fuller
96992845ed Install: include platformAndArch in help after failure 2021-12-12 21:12:47 +00:00
Lovell Fuller
2b8e4d20de Docs: clarify that metadata is based on input, not output 2021-12-12 20:38:23 +00:00
Lovell Fuller
dd6583044b Docs: changelog and credit for #3006 2021-12-12 19:29:46 +00:00
Chris Banks
446e4e3c3a Add withoutReduction option to resize operation (#3006) 2021-12-12 19:10:56 +00:00
Lovell Fuller
3b492ea423 Install: verify prebuilt binaries with Subresource Integrity check 2021-12-12 18:56:46 +00:00
Lovell Fuller
3da258f6fb Expose platform-arch of vendored binaries #2928 2021-12-12 13:38:00 +00:00
Lovell Fuller
9755629cfd Ensure ops without multi-page support reject (#3010) 2021-12-12 09:14:26 +00:00
Lovell Fuller
5bb6702717 Bump dep: color 2021-12-11 21:51:23 +00:00
Lovell Fuller
8c0660d71e Docs: changelog and refresh for #2789 2021-12-11 21:41:41 +00:00
Kleis Auke Wolthuizen
513fb40f40 Improve multi-frame image resizing (#2789)
* Ports vips_thumbnail logic to sharp 
* Deprecates the pageHeight output option for WebP/GIF
2021-12-10 20:32:04 +00:00
Kleis Auke Wolthuizen
659cdabd8e Windows: make invalid param errors recoverable by default (#3002)
* Tests: ensure writing to missing directory fails

Co-authored-by: Lovell Fuller <github@lovell.info>
2021-12-06 10:08:32 +00:00
Lovell Fuller
add4c7928f Upgrade to libvips v8.12.1 2021-11-25 22:05:21 +00:00
Lovell Fuller
336856dfc2 Tests: add PNG palette decode/encode roundtrip 2021-11-25 10:29:28 +00:00
Lovell Fuller
e1ba2a7fd8 Standardise HEIF effort option name, deprecate speed 2021-11-24 19:54:04 +00:00
Lovell Fuller
2b1f5cbe07 Standardise WebP effort option name, deprecate reductionEffort 2021-11-24 18:54:30 +00:00
Lovell Fuller
72025051c5 Expose control over CPU effort for palette PNG #2541 2021-11-24 17:12:53 +00:00
Lovell Fuller
140eeebb3d Docs refresh 2021-11-24 12:35:40 +00:00
Lovell Fuller
407bfcb42a Expose unlimited option for SVG and PNG input #2984 2021-11-24 12:35:20 +00:00
Lovell Fuller
549219f32a Tests: only need to assert 'mean' of 16-bit PNG roundtrip 2021-11-23 12:35:47 +00:00
Lovell Fuller
afab0d34dd Docs: changelog and credit for #2958 2021-11-23 12:21:36 +00:00
Max Gordon
2deced0fb9 Ensure 16-bit PNG output uses correct bitdepth #2958 2021-11-23 12:19:41 +00:00
Lovell Fuller
2a0077c481 Upgrade to libvips v8.12.0 2021-11-21 12:03:12 +00:00
Lovell Fuller
f7f3e43490 Add support for GIF output using cgif in prebuilt binaries 2021-11-21 10:22:34 +00:00
Lovell Fuller
b876abaf88 Tests: ensure median default window is covered 2021-11-17 13:58:56 +00:00
Lovell Fuller
dcd9a3c558 Docs: changelog and credit for #2976 2021-11-17 11:42:30 +00:00
Drian Naude
a06b8c296a Properly emit close events for duplex streams (#2976) 2021-11-17 11:19:58 +00:00
DSRKafuU
602f988aba Docs: Update Chinese mirror hostname (#2979) 2021-11-17 11:17:37 +00:00
Lovell Fuller
079bd7b9f5 Upgrade to libvips v8.12.0-rc1 2021-11-16 10:28:38 +00:00
Lovell Fuller
1ff84b20b7 Release v0.29.3 2021-11-14 11:40:19 +00:00
Lovell Fuller
97655d2dfd Bump deps 2021-11-14 09:17:44 +00:00
Michael B. Klein
d10d7b02d4 Docs: remove duplicate entry for mbklein (#2971) 2021-11-11 19:10:44 +00:00
Lovell Fuller
2ffdae2914 Docs: changelog and credit for #2952 2021-11-08 19:43:49 +00:00
Michael B. Klein
342de36973 Impute TIFF xres/yres from withMetadata({density}) 2021-11-08 19:43:42 +00:00
Lovell Fuller
b33231d4bd Ensure correct dimensions when contain 1px image #2951 2021-11-07 16:35:30 +00:00
Lovell Fuller
319db21f29 Release v0.29.2 2021-10-21 09:15:21 +01:00
Lovell Fuller
d359331426 Remove animation props from single page images #2890 2021-10-18 20:27:10 +01:00
Lovell Fuller
7ae151362b Bump devDeps 2021-10-17 15:17:50 +01:00
Lovell Fuller
648a1e05da Throw error rather than exit for invalid binaries #2931 2021-10-17 15:14:40 +01:00
Lovell Fuller
b9f211fe34 Docs: changelog for #2918 2021-10-17 15:11:38 +01:00
Dmitri Pyatkov
e475d9e47f Improve error message on Windows for version conflict (#2918) 2021-10-17 14:10:28 +01:00
Lovell Fuller
f37ca8249a Bump deps 2021-09-22 11:41:22 +01:00
Lovell Fuller
1dd4be670d Add timeout function to limit processing time 2021-09-22 10:33:59 +01:00
Lovell Fuller
197d4cf835 Docs: changelog and credit for #2893 2021-09-22 10:31:12 +01:00
Lovell Fuller
83eed86b53 Docs: clarify prebuilt libc support on ARMv6/v7 2021-09-22 10:08:52 +01:00
Lovell Fuller
bbf612cb9e Replace use of deprecated util.inherits 2021-09-22 10:08:44 +01:00
Erlend
2679bb567b Allow use of 'tif' to select TIFF output (#2893) 2021-09-16 18:49:14 +01:00
Lovell Fuller
481e350f39 Ensure 'versions' is populated from vendored libvips 2021-09-07 11:21:00 +01:00
Lovell Fuller
50c7a08754 Release v0.29.1 2021-09-07 10:23:50 +01:00
Lovell Fuller
9a0bb60737 Bump deps 2021-09-07 10:21:51 +01:00
Lovell Fuller
deb5d81221 Docs: changelog entries for #2878 #2879 2021-09-06 16:30:31 +01:00
Espen Hovlandsdal
916b04dbac Allow using speed 9 for AVIF/HEIC encoding (#2879) 2021-09-06 16:23:02 +01:00
Espen Hovlandsdal
52307fad5d Resolve paths before comparing input/output destination (#2878)
This fixes an issue where if you try to write to the same destination as the
input file but you are not using absolute (or the same relative path) for both
the input and output, sharp/vips might produce errors such as:

someFile.jpg: unable to open for write
unix error: No such file or directory
2021-09-06 16:21:43 +01:00
Lovell Fuller
afb21135c2 Docs: add changelog entry for #2868 2021-09-05 09:35:46 +01:00
Zaruike
b7fbffb3f7 Add support for libvips compiled with OpenJPEG 2021-09-05 09:32:02 +01:00
Lovell Fuller
5d98bcd8d8 Remove unsupported animation props from AVIF #2870 2021-09-05 08:46:15 +01:00
Lovell Fuller
e044788f63 Docs: changelog and credit for #2846 2021-08-30 20:31:10 +01:00
Tenpi
4a9267ce12 Add lightness option to modulate operation 2021-08-30 20:22:41 +01:00
Lovell Fuller
104464c2e0 Ensure images with P3 profiles retain full gamut #2862 2021-08-30 17:15:17 +01:00
Lovell Fuller
60adc110f5 Ensure background is premultiplied when compositing #2858 2021-08-29 16:40:40 +01:00
Paul Straw
2031d7d112 Ensure compatibility with ImageMagick 7 (#2865) 2021-08-28 20:17:44 +01:00
Lovell Fuller
3402656ec5 Set PNG bitdepth based on number of colours #2855
Removes use of deprecated libvips API
2021-08-26 22:05:29 +01:00
Lovell Fuller
4e84f743e4 Docs: toFile expects directory structure to exist 2021-08-20 09:22:22 +01:00
Lovell Fuller
17e50de5f0 Docs: serve docute from same hostname
Cheapo corporate web proxies ignore CSP and rewrite HTML
2021-08-19 18:58:17 +01:00
Lovell Fuller
978a788f40 CI: ensure Linux ARM64 prebuild token is passed into container 2021-08-17 14:49:57 +01:00
Lovell Fuller
6e91d55971 CI: ensure Linux ARM64 prebuild token is passed into container 2021-08-17 14:33:29 +01:00
Lovell Fuller
d4ce0a1e36 Update prebuild include regex 2021-08-17 11:23:24 +01:00
Lovell Fuller
148608b377 Release v0.29.0 2021-08-17 11:16:04 +01:00
Lovell Fuller
f725f4acb7 Update performance test results 2021-08-17 10:23:45 +01:00
Lovell Fuller
d07a549438 Tests: add squoosh-cli and squoosh-lib to performance benchmarks 2021-08-16 20:33:33 +01:00
Lovell Fuller
551441cedd Bench: bump deps 2021-08-16 19:36:42 +01:00
Lovell Fuller
46c14e939b Tests: add a few new leak suppressions 2021-08-16 19:01:39 +01:00
Lovell Fuller
6084647795 Upgrade to libvips v8.11.3 2021-08-15 19:45:08 +01:00
Lovell Fuller
e0a598ae62 Bump deps 2021-08-15 19:28:49 +01:00
Lovell Fuller
28833eb04a Upgrade to libvips v8.11.3-alpha1 2021-08-15 08:35:27 +01:00
Lovell Fuller
b24c9c86d1 Docs: changelog and credit for #2762 2021-08-03 15:28:50 +01:00
Mart
b7add480c7 Add support for bit depth with raw input and output (#2762)
* Determine input raw pixel depth from the given typed array
* Allow pixel depth to be set on raw output
2021-08-03 14:52:54 +01:00
Lovell Fuller
eabb671b10 Docs: minimum Node.js version requirement of 12.13.0 2021-08-03 10:25:04 +01:00
Lovell Fuller
513ed02b76 Docs: changelog entry for #2808 2021-08-02 21:37:54 +01:00
Espen Hovlandsdal
b7ddbe71f7 Add support for negating only non-alpha channels
Fixes #1035
2021-08-02 21:19:56 +01:00
Lovell Fuller
21d1a7ca62 CI: Add darwin-arm64 via MacStadium-based runner 2021-07-23 10:58:00 +01:00
Lovell Fuller
4c2d28a7ad Bump dep: color 2021-07-19 15:30:12 +01:00
Lovell Fuller
2afec9e3ed Docs: rebuild to include commit 6979042 2021-07-19 15:30:01 +01:00
reiv
69790421b7 Docs: add stats usage note and example (#2803) (#2804)
Clarify that stats are derived from the original input image and that operations need to be buffered in order to obtain stats from the resulting image.
2021-07-19 15:25:36 +01:00
Lovell Fuller
3f08f6a359 Add default background metadata for PNG and GIF images 2021-07-19 14:55:22 +01:00
Lovell Fuller
719c2db8da Bump devDeps 2021-07-14 19:33:24 +01:00
Lovell Fuller
a9aa55c32d Ensure pipelineColourspace is applied to all inputs #2704 2021-07-14 19:22:31 +01:00
Lovell Fuller
3f6d9d6ee9 Upgrade to libvips v8.11.2-alpha1 2021-07-14 13:45:47 +01:00
Lovell Fuller
b32568159f Docs: changelog and credit for #2704 2021-07-14 13:45:47 +01:00
Daiz
bb48d0d857 Add pipelineColourspace operator 2021-07-14 13:45:47 +01:00
Lovell Fuller
536412515f Allow installation directory to contain spaces
This previously worked, but regressed in v0.26.0.
2021-07-14 13:45:47 +01:00
Lovell Fuller
fcc6eaadd3 Tests: reduce WebP CPU time, ~2s faster 2021-07-14 13:45:47 +01:00
Lovell Fuller
7dc78e8796 Bump deps 2021-07-14 13:45:47 +01:00
Lovell Fuller
c65de3fe6d Default to single-channel output from extractChannel #2658 2021-07-14 13:45:47 +01:00
Lovell Fuller
d000f57773 Add compression property to HEIF image metadata #2504 2021-07-14 13:45:47 +01:00
Lovell Fuller
75cddbdb6d Default AVIF encoding to 4:4:4 chroma subsampling #2562 2021-07-14 13:45:47 +01:00
Lovell Fuller
e418d91511 Test: correct coverage syntax 2021-07-14 13:45:47 +01:00
Lovell Fuller
6c2e6c5432 Install: multiple platform-arch binaries in same tree 2021-07-14 13:45:47 +01:00
Lovell Fuller
8c6d9fdc62 Install: always use Brotli-compressed tarballs 2021-07-14 13:45:47 +01:00
Lovell Fuller
468e95427e CI: migrate ARM64 builds from TravisCI to CircleCI #2665 2021-07-14 13:45:47 +01:00
Lovell Fuller
6d7a5ace6b Drop support for Node.js 10, upgrade to Node-API v5 2021-07-14 13:45:47 +01:00
Lovell Fuller
cbaec198a5 Upgrade to libvips v8.11.0-rc1 2021-07-14 13:45:47 +01:00
Lovell Fuller
7467fa8b50 Docs: serve markdown files via Firebase instead of jsdelivr
Might make a few corporate web proxies happier
2021-06-21 17:10:44 +01:00
Lovell Fuller
61640fb5c7 CI: use gcc 10 on Linux x64 2021-06-03 18:52:52 +01:00
Lovell Fuller
19cb4b62b0 Docs: add security-related response headers 2021-06-02 09:12:02 +01:00
Lovell Fuller
81ee8bc30f Docs: make keyword search case insensitive 2021-06-02 09:11:08 +01:00
Lovell Fuller
9f384e1c6c Release v0.28.3 2021-05-24 16:26:34 +01:00
Lovell Fuller
35e8c8b25e Docs: ensure ops without examples are indexed 2021-05-24 16:24:04 +01:00
Lovell Fuller
dc53f1baff Bump deps 2021-05-24 16:08:10 +01:00
Lovell Fuller
70139600b5 Docs: fix CLAHE link 2021-05-23 18:49:54 +01:00
Lovell Fuller
1b4d1521e0 Docs: cross-link removeAlpha and flatten 2021-05-23 18:46:23 +01:00
Lovell Fuller
ed3377cb2d Docs: add parameter names to search keywords 2021-05-23 18:02:18 +01:00
Lovell Fuller
d72852b3aa Docs: changelog entry for #2726 2021-05-23 17:45:43 +01:00
Brad Parham
4b6b6189bf Add contrast limiting adaptive histogram equalization (CLAHE) operator (#2726) 2021-05-23 17:36:04 +01:00
Lovell Fuller
b69a54fc75 Ensure presence of libvips before invoking node-gyp 2021-05-16 19:40:43 +01:00
Lovell Fuller
81e388a4cc Docs: composite supports failOnError and limitInputPixels 2021-05-15 14:52:33 +01:00
Lovell Fuller
5bd5e5052a Skip shrink-on-load for multi-page WebP #2714 2021-05-15 14:13:16 +01:00
Lovell Fuller
a2d3fa729f Release v0.28.2 2021-05-10 17:59:50 +01:00
Lovell Fuller
cb6811bc47 CI: FreeBSD skip notifications 2021-05-10 17:44:15 +01:00
Lovell Fuller
53c6e80869 Docs: refresh index 2021-05-10 16:03:40 +01:00
Lovell Fuller
e71dca586c Bump devDeps 2021-05-10 16:03:08 +01:00
Lovell Fuller
b3cd48db5f Docs: add section about cross-platform installation 2021-05-10 14:41:28 +01:00
Sebastian
476448b9d4 Install: allow cross-libc via sharp-install-force flag (#2692) 2021-05-10 09:55:20 +01:00
Lovell Fuller
070534df5b Docs: changelog for #2685 2021-05-03 19:48:15 +01:00
Michael Nutt
9a1e8ed574 Add premultiplied boolean flag for raw pixel data input (#2685) 2021-05-03 19:30:37 +01:00
Lovell Fuller
309918a878 Move lint-related tasks to dedicated script entry 2021-05-03 10:04:19 +01:00
Lovell Fuller
cac83b94c1 Bump deps and docs refresh 2021-05-03 10:01:43 +01:00
Lovell Fuller
9c06df08a1 Docs: changelog entry for #2687 2021-05-03 10:01:17 +01:00
Jacob
52e4543d31 Detect empty input and throw a helpful error (#2687) 2021-05-03 09:29:51 +01:00
Lovell Fuller
a688468378 CI: replace Node.js 15 with 16 2021-05-01 16:24:47 +01:00
Lovell Fuller
e1760d64fb Tests: updates so latest libvips master branch passes 2021-05-01 15:25:57 +01:00
Lovell Fuller
84d4e3cf8f Require specific semver functions, aids tree-shaking 2021-04-30 20:42:46 +01:00
Raj Rajhans
f8a76372ad Docs: rewrite sentence to avoid grammatical ambiguity (#2668) 2021-04-21 14:10:24 +01:00
Lovell Fuller
4237f5520f Allow withMetadata to set density #967 2021-04-17 13:46:54 +01:00
Lovell Fuller
8c0c01c702 Docs: changelog entry for #2664 2021-04-17 09:11:48 +01:00
msalettes
9c100830e0 Allow escaped proxy credentials (#2664) 2021-04-17 08:49:07 +01:00
Lovell Fuller
ed5d753b89 Skip shrink-on-load where one dimension <4px #2653 2021-04-07 21:26:16 +01:00
Timo Hausmann
d1ca756bd8 Docs: correct flatten example to use object instead of string (#2654) 2021-04-06 17:21:14 +01:00
Lovell Fuller
16cf9f0ef2 Release v0.28.1 2021-04-05 12:28:16 +01:00
Lovell Fuller
133f69d66c Bump prebuild-install (for sharp_local_prebuilds) 2021-04-05 12:26:28 +01:00
Lovell Fuller
bc60daff9e Allow EXIF metadata to be set/update #650 2021-04-05 11:39:53 +01:00
Lovell Fuller
43a085d1ae Add support for OME-TIFF subIFDs #2557 2021-04-02 08:04:21 +01:00
Lovell Fuller
8c33d0aa56 Allow ensureAlpha to set alpha transparency level #2634 2021-04-01 21:14:06 +01:00
Lovell Fuller
fe0767df13 Install: log errors with more obvious prefix 2021-04-01 16:20:58 +01:00
Lovell Fuller
08a25a0c8f Docs: add animated WebP example #2648 2021-04-01 16:04:46 +01:00
Lovell Fuller
cd410080bd Docs: remove reference to specific Lambda runtime 2021-03-29 20:16:07 +01:00
Lovell Fuller
7555378e3b Release v0.28.0 2021-03-29 14:10:34 +01:00
Lovell Fuller
80c95ee66a Docs: libvips tarballs are a bit smaller now 2021-03-29 12:16:48 +01:00
Lovell Fuller
31563b210d Ensure GIF input will work with future libvips v8.11.0 2021-03-29 12:16:10 +01:00
Lovell Fuller
861cd93324 Pre-release v0.28.0-beta1 2021-03-27 07:11:34 +00:00
Lovell Fuller
abb344bb1a Upgrade to libvips v8.10.6 2021-03-26 21:57:12 +00:00
Lovell Fuller
6147491d9e Extend: default missing edge props to zero #2578 2021-03-25 16:34:02 +00:00
Lovell Fuller
f1f18fbb4a Docs: clarify that flatten removes alpha channel #2601 2021-03-25 14:38:55 +00:00
Lovell Fuller
9fc611f257 Docs: changelog entries for #2594 #2608 2021-03-22 20:30:46 +00:00
SHG42
34a2e14a14 Fix erroneous top/left clipping in composite #2571
Fixes bug where certain input values for top/left parameters
in composite can conflict with clipping logic, resulting in
inaccurate alignment in output.
2021-03-22 18:27:49 +00:00
Lovell Fuller
83fe65b9e9 Docs: include more relevant content in search index 2021-03-21 20:59:05 +00:00
Lovell Fuller
ec26c8aa49 Docs: ensure toBuffer pixel example works #2624 2021-03-21 20:54:09 +00:00
Lovell Fuller
da43a3055f Docs: correct typo in description of threshold operation 2021-03-21 20:51:30 +00:00
Lovell Fuller
a38126c82f Ensure composite replicates correct tiles with centre gravity #2626 2021-03-20 13:24:04 +00:00
Lovell Fuller
cb592ce588 Tests: add case for SVG with truncated embedded PNG 2021-03-18 19:34:56 +00:00
Lovell Fuller
d69c58a6da Docs: add section about Linux memory allocators 2021-03-18 19:34:07 +00:00
Lovell Fuller
bdb1986e08 Tests: run in parallel again 2021-03-17 23:25:34 +00:00
Lovell Fuller
55356c78a8 Docs: refresh markdown 2021-03-15 20:24:53 +00:00
Lovell Fuller
a0f55252b1 Tests: a few more speed improvements 2021-03-15 20:24:13 +00:00
Lovell Fuller
013f5cffa9 Tests: refactor modulate suite, ~20x faster 2021-03-15 18:20:06 +00:00
Lovell Fuller
d5d008f568 Docs: reorder readme sections 2021-03-15 13:07:16 +00:00
Lovell Fuller
3b02134cdc Tests: update latest benchmark test results 2021-03-14 21:10:26 +00:00
Lovell Fuller
a57d7b51b1 Tests: match concurrency with CPU count 2021-03-14 19:51:45 +00:00
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
Lovell Fuller
24285bb0e0 Release v0.25.1 2020-03-07 21:21:44 +00:00
Lovell Fuller
bf75501262 Ensure prebuilt binary based on N-API version 2020-03-07 20:59:25 +00:00
Lovell Fuller
9e214a17f0 Release v0.25.1 2020-03-07 19:12:32 +00:00
Lovell Fuller
0aae1ecd9b Ensure prebuilt binaries use N-API v4 #2117 2020-03-07 18:52:44 +00:00
Lovell Fuller
062f990315 Release v0.25.0 2020-03-07 12:44:42 +00:00
Lovell Fuller
22685e44c0 Upgrade to libvips v8.9.1 2020-03-07 10:15:51 +00:00
Lovell Fuller
8d66433e7c Docs: 32-bit Windows is supported (again) 2020-03-06 21:33:15 +00:00
Lovell Fuller
82863f48f6 Update issue templates to ask common follow-up questions 2020-03-06 21:12:59 +00:00
Lovell Fuller
51b14329d6 Simplify and future-proof tile option parsing 2020-03-06 20:06:41 +00:00
Lovell Fuller
14dc7681ed Upgrade to libvips v8.9.0-alpha2 prebuild
Improve tests for 32-bit ARM
2020-03-04 23:17:23 +00:00
Lovell Fuller
258c9e86eb Improve docs relating to single-channel raw pixel output 2020-03-01 14:22:49 +00:00
Joe Flateau
03dad5f2b2 Docs: clarify stats.isOpaque (#2094) 2020-02-24 20:35:36 +00:00
Lovell Fuller
86264e7733 Docs: update Lambda section to use Node.js 12 2020-02-24 20:31:17 +00:00
Lovell Fuller
95eae905f0 Ensure limitInputPixels, sequentialRead for composite op #2099 2020-02-24 16:02:36 +00:00
Lovell Fuller
a90dbcc808 Docs: improve a11y and metadata 2020-02-24 09:24:27 +00:00
Lovell Fuller
e9b21f2211 Ensure correct ordering of rotate-then-trim ops #2087 2020-02-23 16:59:22 +00:00
Lovell Fuller
409e5174bb Add support for 32-bit Windows (win32-ia32) 2020-02-19 23:00:23 +00:00
Lovell Fuller
8b3c0daab2 Upgrade to libvips v8.9.1-alpha1 prebuild 2020-02-19 11:41:18 +00:00
Lovell Fuller
c17807c995 Remove previously deprecated limitInputPixels, sequentialRead 2020-02-15 20:01:46 +00:00
Lovell Fuller
4abb4edf64 Migrate internals to N-API #1282 2020-02-15 19:38:15 +00:00
Lovell Fuller
d5ecc537af Release v0.24.1 2020-02-15 13:48:46 +00:00
Lovell Fuller
c7a49054fd Docs: update libvips URLs for band format/interpretation 2020-02-15 13:43:47 +00:00
Lovell Fuller
a2314c4aa0 Ensure RGBA LZW TIFF info.channel count #2064 2020-02-15 11:46:13 +00:00
Lovell Fuller
1717173f17 Tests: tighten composite offset thresholds 2020-02-15 10:53:30 +00:00
Lovell Fuller
e44c12f029 Bump dependencies, tar is now node>=10 2020-02-15 10:53:00 +00:00
Lovell Fuller
1a98c390fc Prevent sequentialRead for EXIF-based rotate op #2042 2020-01-20 21:50:43 +00:00
Lovell Fuller
91902740e4 Attempt to detect out-of-date homebrew-managed vips 2020-01-19 19:58:24 +00:00
Lovell Fuller
6aa6a93b44 Docs: add details of ignore-scripts to installation guide 2020-01-19 19:27:50 +00:00
Lovell Fuller
b4135ac9b3 Docs: fix any remaining redirects 2020-01-16 20:57:02 +00:00
Lovell Fuller
78906e6551 Update any remaining documentation links 2020-01-16 20:52:19 +00:00
Lovell Fuller
ba29ba1ab7 Release v0.24.0 2020-01-16 19:11:34 +00:00
Lovell Fuller
e0fa15f5cb Update performance test results 2020-01-16 18:56:25 +00:00
Lovell Fuller
82a1ff359d Update dev and bench dependencies 2020-01-15 21:17:25 +00:00
Lovell Fuller
18e1f10a94 Add support for input with 16-bit RGB profile #2037 2020-01-15 21:16:11 +00:00
Lovell Fuller
4828a17643 Tests: add fontconfig static data leak suppression 2020-01-15 21:08:45 +00:00
Lovell Fuller
3b4f95597a Prevent use of sequentialRead for rotate ops #2016 2020-01-14 08:34:54 +00:00
Lovell Fuller
bd52e93fca Deprecate limitInputPixels and sequentialRead, move to input options 2020-01-12 19:59:39 +00:00
Lovell Fuller
6fdc79d569 Docs: ARM64 has prebuilt libvips, not sharp 2020-01-12 10:50:14 +00:00
Lovell Fuller
7dbad7206e Drop support for undef input where opts also provided #1768 2020-01-11 12:02:31 +00:00
Lovell Fuller
a8a0c1e935 Update doc links contained within code
Throw rather than exit if require fails
2020-01-10 16:29:40 +00:00
Lovell Fuller
00bcf60566 Docs: metadata delay/loop properties 2020-01-09 23:04:06 +00:00
Lovell Fuller
4a745f2d2e Expose delay/loop metadata for animated images #1905 2020-01-09 22:51:08 +00:00
Lovell Fuller
057074b238 Docs: improve header layout on narrow screen devices 2020-01-08 23:13:33 +00:00
Lovell Fuller
4b8cc13a05 Add 2020 to list of copyright years 2020-01-08 21:20:53 +00:00
Lovell Fuller
a4e8586b59 Docs: rewrite installation guide 2020-01-08 21:04:16 +00:00
Lovell Fuller
095b37db6a Rename OS X as macOS
Bump CI to latest xcode available on maxOS 10.13
2020-01-08 21:03:50 +00:00
Lovell Fuller
efff650314 Upgrade to libvips v8.9.0 2020-01-08 16:18:43 +00:00
Lovell Fuller
0764ca8fde Changelog entry and credit for #2024 2020-01-08 16:04:42 +00:00
Brychan
403160434b Correctly bind max width and height values when using withoutEnlargement (#2024) 2020-01-07 12:10:35 +00:00
Lovell Fuller
730ab14faa Docs: correct SVG logo URL 2020-01-06 17:51:11 +00:00
Lovell Fuller
51d70c4574 Docs: switch hosting from Readthedocs to Firebase 2020-01-06 14:48:35 +00:00
Lovell Fuller
765ccd2aaa Docs: add icons 2020-01-06 14:44:07 +00:00
Lovell Fuller
5399332f81 Docs: improve section nesting levels 2020-01-06 14:26:54 +00:00
Lovell Fuller
43b7f9fc0e Docs: inject API section name as title 2020-01-04 11:11:27 +00:00
Sebin Benjamin
3925689435 Docs: initial setup for docute documentation site generator 2020-01-04 10:59:08 +00:00
Lovell Fuller
96a994a4c0 Move functions to improve logical ordering of docs 2020-01-03 20:58:57 +00:00
Lovell Fuller
84c20373ec Update leak test suppressions 2020-01-03 20:47:01 +00:00
Lovell Fuller
a216d2945b Upgrade to libvips v8.9.0-rc4, drop support for Node.js 8 2020-01-03 20:26:55 +00:00
Lovell Fuller
755a0caf3d Fix 16-bit, 2-channel PNG w/ ICC profile support #2013 2019-12-20 17:19:33 +00:00
Lovell Fuller
703d90e663 Docs: add example of using metadata and resize to scale 2019-12-16 21:15:49 +00:00
Lovell Fuller
e230ce41d7 Docs: remove references to unmaintained Docker images 2019-12-16 21:07:31 +00:00
Lovell Fuller
596b38a3bb Release v0.23.4 2019-12-05 10:00:29 +00:00
Lovell Fuller
d31a91a599 Expose raw TIFFTAG_PHOTOSHOP metadata #1600 2019-11-29 13:05:07 +00:00
Lovell Fuller
400ef71b6f Handle zero-length Buffers in Node.js v13.2.0+
These now use nullptr internally - see
https://github.com/nodejs/node/pull/30339
2019-11-28 21:20:32 +00:00
Lovell Fuller
bb15cd9067 Improve thread safety with copy-on-write for metadata #1986 2019-11-27 23:15:56 +00:00
Lovell Fuller
6ee6a226e1 Docs: add FreeBSD build status 2019-11-19 11:16:48 +00:00
Lovell Fuller
94d51a94c8 Docs: recommend jemalloc buildpack for Heroku 2019-11-19 11:13:25 +00:00
Lovell Fuller
d0feb4156c Release v0.23.3 2019-11-17 15:42:07 +00:00
Lovell Fuller
0d1278dade Improve C++ linting, move exceptions inline 2019-11-14 22:06:38 +00:00
Lovell Fuller
1b401b1195 Add FreeBSD to CI via Cirrus #1953 2019-11-14 21:55:20 +00:00
Lovell Fuller
11daa3b4d1 Tests: flatten to mid-grey before generating fingerprint 2019-11-14 13:18:14 +00:00
Lovell Fuller
88a3919ce0 Docs refresh 2019-11-14 11:32:49 +00:00
Lovell Fuller
c41b87303d Ensure trim op supports image-in-alpha #1597 2019-11-14 11:29:45 +00:00
Lovell Fuller
833aaead56 Ensure modulate can co-exist with other colour ops #1958 2019-11-11 22:16:28 +00:00
Lovell Fuller
ff98d2e44a Docs: using custom binaries with prebuild-install 2019-11-07 20:00:03 +00:00
Lovell Fuller
fcf05f608a Changelog entry for #1952 2019-11-07 19:59:18 +00:00
Pouya Eghbali
9baf38db44 Allow compilation of v0.23.x on FreeBSD and variants (#1952) 2019-11-06 23:11:44 +00:00
Lovell Fuller
69050ef1c8 Add funding link to libvips' Open Collective 2019-11-05 21:51:14 +00:00
Lovell Fuller
b35b9f7850 Ensure Travis does not cache builds #1948 2019-11-01 16:17:36 +00:00
Lovell Fuller
500ae97cac Changelog entry for #1921 2019-11-01 16:15:02 +00:00
Brendan Kennedy
d5b7040557 Ensure tile overlap option works as expected (#1921) 2019-10-30 20:02:07 +00:00
Lovell Fuller
ca52894651 Release v0.23.2 2019-10-28 18:21:11 +00:00
Lovell Fuller
4009acdd30 Docs: support added for Node.js 13 2019-10-28 18:19:33 +00:00
Lovell Fuller
147c93ecd3 Tests: increase coverage for jpeg-related logic 2019-10-27 20:03:10 +00:00
Lovell Fuller
8e04e4b07f Tests: add coverage for tiff quality option 2019-10-27 19:54:23 +00:00
Lovell Fuller
e7413ea1e5 Tests: increase coverage for metadata-related logic 2019-10-27 19:32:48 +00:00
Lovell Fuller
220bb03a32 Switch from excl npmignore to incl package.json files 2019-10-27 18:22:10 +00:00
Lovell Fuller
20f512fe5f Bump dependency patch versions 2019-10-26 23:15:41 +01:00
Lovell Fuller
efb3523eaa Remove duplicate validation from resize background 2019-10-26 23:13:12 +01:00
Lovell Fuller
2f2276e091 Changelog entries for #1924 #1932 2019-10-26 22:53:39 +01:00
Paul Neave
08a6597626 Add background option to tile output operation (#1924) 2019-10-25 14:30:33 +01:00
Nicolas Stepien
d82a6ee4fc Add Node.js 13 to CI (#1932) 2019-10-23 19:28:19 +01:00
Lovell Fuller
e627f6d68d Docs: clarify that input 'path' refers to the filesystem 2019-10-05 08:43:40 +01:00
Lovell Fuller
e650f58bd8 Improve error messaging for root/sudo permission problems 2019-10-04 12:14:08 +01:00
Lovell Fuller
5a9b6c8afd Tighten validation of page/pages constructor options 2019-10-03 16:41:32 +01:00
Lovell Fuller
075771d1e9 Improve error messaging for 404 errors on non-standard platforms 2019-10-03 15:32:15 +01:00
Lovell Fuller
4fcf091fef Bump tar dep (appears to be non-breaking despite major increment) 2019-10-03 15:30:56 +01:00
Marc Bornträger
0e66454fe4 Docs: Simplify Alpine Linux info, uses 3.10 instead of edge 2019-10-01 12:00:38 +01:00
310 changed files with 20826 additions and 5866 deletions

79
.circleci/config.yml Normal file
View File

@@ -0,0 +1,79 @@
version: 2.1
workflows:
build:
jobs:
- linux-arm64-glibc-node-14:
filters:
tags:
only: /^v.*/
- linux-arm64-musl-node-14:
filters:
tags:
only: /^v.*/
- linux-arm64-glibc-node-18:
filters:
tags:
only: /^v.*/
- linux-arm64-musl-node-18:
filters:
tags:
only: /^v.*/
jobs:
linux-arm64-glibc-node-14:
resource_class: arm.medium
machine:
image: ubuntu-2004:current
steps:
- checkout
- run: |
sudo docker run -dit --name sharp --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp arm64v8/debian:bullseye
sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python3 curl fonts-noto-core"
sudo docker exec sharp sh -c "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"
- run: sudo docker exec sharp sh -c "npm install --build-from-source --unsafe-perm"
- run: sudo docker exec sharp sh -c "npm test"
- run: "[[ -n $CIRCLE_TAG ]] && sudo docker exec --env prebuild_upload sharp sh -c \"npx prebuild --runtime napi --target 7 --upload=$prebuild_upload\" || true"
linux-arm64-glibc-node-18:
resource_class: arm.medium
machine:
image: ubuntu-2004:current
steps:
- checkout
- run: |
sudo docker run -dit --name sharp --workdir /mnt/sharp arm64v8/debian:bullseye
sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python3 curl fonts-noto-core"
sudo docker exec sharp sh -c "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_18.x sid main' >/etc/apt/sources.list.d/nodesource.list"
sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs"
sudo docker exec sharp sh -c "mkdir -p /mnt/sharp"
sudo docker cp . sharp:/mnt/sharp/.
- run: sudo docker exec sharp sh -c "npm install --build-from-source"
- run: sudo docker exec sharp sh -c "npm test"
linux-arm64-musl-node-14:
resource_class: arm.medium
machine:
image: ubuntu-2004:current
steps:
- checkout
- run: |
sudo docker run -dit --name sharp --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:14-alpine3.12
sudo docker exec sharp sh -c "apk add build-base git python3 font-noto --update-cache"
- run: sudo docker exec sharp sh -c "npm install --build-from-source --unsafe-perm"
- run: sudo docker exec sharp sh -c "npm test"
- run: "[[ -n $CIRCLE_TAG ]] && sudo docker exec --env prebuild_upload sharp sh -c \"npx prebuild --runtime napi --target 7 --upload=$prebuild_upload\" || true"
linux-arm64-musl-node-18:
resource_class: arm.medium
machine:
image: ubuntu-2004:current
steps:
- checkout
- run: |
sudo docker run -dit --name sharp --workdir /mnt/sharp node:18-alpine3.14
sudo docker exec sharp sh -c "apk add build-base git python3 font-noto --update-cache"
sudo docker exec sharp sh -c "mkdir -p /mnt/sharp"
sudo docker cp . sharp:/mnt/sharp/.
- run: sudo docker exec sharp sh -c "npm install --build-from-source"
- run: sudo docker exec sharp sh -c "npm test"

16
.cirrus.yml Normal file
View File

@@ -0,0 +1,16 @@
freebsd_instance:
image_family: freebsd-13-2
task:
name: FreeBSD
env:
IGNORE_OSVERSION: yes
skip_notifications: true
prerequisites_script:
- pkg update -f
- pkg upgrade -y
- pkg install -y devel/git devel/pkgconf graphics/vips www/node20 www/npm
install_script:
- npm install --build-from-source --unsafe-perm
test_script:
- npm test

View File

@@ -16,37 +16,33 @@ If a [similar request](https://github.com/lovell/sharp/labels/enhancement) exist
it's probably fastest to add a comment to it about your requirement.
Implementation is usually straightforward if libvips
[already supports](https://libvips.github.io/libvips/API/current/func-list.html)
[already supports](https://www.libvips.org/API/current/func-list.html)
the feature you need.
## Submit a Pull Request to fix a bug
Thank you! To prevent the problem occurring again, please add unit tests that would have failed.
Please select the `master` branch as the destination for your Pull Request so your fix can be included in the next minor release.
Please select the `main` branch as the destination for your Pull Request so your fix can be included in the next minor release.
Please squash your changes into a single commit using a command like `git rebase -i upstream/master`.
Please squash your changes into a single commit using a command like `git rebase -i upstream/main`.
To test C++ changes, you can compile the module using `npm install` 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
Please add JavaScript [unit tests](https://github.com/lovell/sharp/tree/master/test/unit) to cover your new feature.
Please add JavaScript [unit tests](https://github.com/lovell/sharp/tree/main/test/unit) to cover your new feature.
A test coverage report for the JavaScript code is generated in the `coverage/lcov-report` directory.
Please also update the [TypeScript definitions](https://github.com/lovell/sharp/tree/main/lib/index.d.ts), along with the [type definition tests](https://github.com/lovell/sharp/tree/main/test/types/sharp.test-d.ts).
Where possible, the functional tests use gradient-based perceptual hashes
based on [dHash](http://www.hackerfactor.com/blog/index.php?/archives/529-Kind-of-Like-That.html)
to compare expected vs actual images.
You deserve to add your details to the [list of contributors](https://github.com/lovell/sharp/blob/master/package.json#L5).
You deserve to add your details to the [list of contributors](https://github.com/lovell/sharp/blob/main/package.json#L5).
Any change that modifies the existing public API should be added to the relevant work-in-progress branch for inclusion in the next major release.
| Release | WIP branch |
| ------: | :--------- |
| v0.24.0 | wit |
| v0.25.0 | yield |
Please squash your changes into a single commit using a command like `git rebase -i upstream/<wip-branch>`.
### Add a new public method
@@ -71,7 +67,7 @@ The public API is documented with [JSDoc](http://usejsdoc.org/) annotated commen
These can be converted to Markdown by running:
```sh
npm run docs
npm run docs-build
```
Please include documentation updates in any Pull Request that modifies the public API.
@@ -98,5 +94,5 @@ Please feel free to ask any questions via a
[new issue](https://github.com/lovell/sharp/issues/new).
If you're unable to post details publicly, please
[e-mail](https://github.com/lovell/sharp/blob/master/package.json#L5)
[e-mail](https://github.com/lovell/sharp/blob/main/package.json#L5)
for private, paid consulting.

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,18 +1,28 @@
---
name: Feature request
about: Suggest an idea
title: ''
labels: enhancement
assignees: ''
---
What are you trying to achieve?
## Feature request
Have you searched for similar feature requests?
### What are you trying to achieve?
What would you expect the API to look like?
<!-- Please provide context here. -->
What alternatives have you considered?
### When you searched for similar feature requests, what did you find that might be related?
Is there a sample image that helps explain?
<!-- Please demonstrate your research here. -->
### What would you expect the API to look like?
<!-- Please provide your suggestions here. -->
### What alternatives have you considered?
<!-- Please provide your ideas here. -->
### Please provide sample image(s) that help explain this feature
<!-- Please provide links to one or more images here. -->

View File

@@ -1,18 +1,45 @@
---
name: Installation
about: Something went wrong **installing** sharp
title: ''
about: Something went wrong during either 'npm install sharp' or 'require("sharp")'
labels: installation
assignees: ''
---
Did you see the [documentation relating to installation](https://sharp.pixelplumbing.com/en/stable/install/)?
<!-- Please try to answer as many of these questions as possible. -->
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?
## Possible install-time or require-time problem
If you're (mis)using `sudo npm install` have you tried with the `sudo npm install --unsafe-perm` flag?
<!-- Please place an [x] in the box to confirm. -->
What is the complete output of running `npm install --verbose sharp`? Have you checked this output for useful error messages?
- [ ] I have read the [documentation relating to installation](https://sharp.pixelplumbing.com/install).
- [ ] I have ensured that 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.
What is the output of running `npx envinfo --binaries --languages --system --utilities`?
### Are you using the latest version of sharp?
<!-- Please place an [x] in the box to confirm. -->
- [ ] I am using the latest version of `sharp` as reported by `npm view sharp dist-tags.latest`.
If you cannot confirm this, please upgrade to the latest version and try again before opening an issue.
If you are using another package which depends on a version of `sharp` that is not the latest, please open an issue against that package instead.
### Is this a problem with filesystem permissions?
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 or later, 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?
### What is the complete output of running `npm install --verbose --foreground-scripts sharp` in an empty directory?
<details>
<!-- Please provide output of `npm install --verbose --foreground-scripts sharp` in an empty directory here. -->
</details>
### What is the output of running `npx envinfo --binaries --system --npmPackages=sharp --npmGlobalPackages=sharp`?
<!-- Please provide output of `npx envinfo --binaries --system --npmPackages=sharp --npmGlobalPackages=sharp` here. -->

View File

@@ -1,20 +1,49 @@
---
name: Possible bug
about: Something unexpected occurred **using** sharp
title: ''
about: Installation of sharp was successful but then something unexpected occurred using one of its features
labels: triage
assignees: ''
---
<!-- If this issue relates to installation, please use https://github.com/lovell/sharp/issues/new?labels=installation&template=installation.md instead. -->
What is the output of running `npx envinfo --binaries --languages --system --utilities`?
## Possible bug
What are the steps to reproduce?
### Is this a possible bug in a feature of sharp, unrelated to installation?
What is the expected behaviour?
<!-- Please place an [x] in the box to confirm. -->
Are you able to provide a standalone code sample, without other dependencies, that demonstrates this problem?
- [ ] Running `npm install sharp` completes without error.
- [ ] Running `node -e "require('sharp')"` completes without error.
Are you able to provide a sample image that helps explain the problem?
If you cannot confirm both of these, please open an [installation issue](https://github.com/lovell/sharp/issues/new?labels=installation&template=installation.md) instead.
### Are you using the latest version of sharp?
<!-- Please place an [x] in the box to confirm. -->
- [ ] I am using the latest version of `sharp` as reported by `npm view sharp dist-tags.latest`.
If you cannot confirm this, please upgrade to the latest version and try again before opening an issue.
If you are using another package which depends on a version of `sharp` that is not the latest, please open an issue against that package instead.
### What is the output of running `npx envinfo --binaries --system --npmPackages=sharp --npmGlobalPackages=sharp`?
<!-- Please provide output of the above command here. -->
### What are the steps to reproduce?
<!-- Please enter steps to reproduce here. -->
### What is the expected behaviour?
<!-- Please enter the expected behaviour here. -->
### Please provide a minimal, standalone code sample, without other dependencies, that demonstrates this problem
<!-- Please provide either formatted code or a link to a repo/gist that allows someone else to reproduce here. -->
### Please provide sample image(s) that help explain this problem
<!-- Please provide links to one or more images here. -->

View File

@@ -1,18 +1,26 @@
---
name: Question
about: For help understanding an existing feature
title: ''
labels: question
assignees: ''
---
<!-- If this issue relates to installation, please use https://github.com/lovell/sharp/issues/new?labels=installation&template=installation.md instead. -->
What are you trying to achieve?
## Question about an existing feature
Have you searched for similar questions?
### What are you trying to achieve?
Are you able to provide a standalone code sample that demonstrates this question?
<!-- Please provide context here. -->
Are you able to provide a sample image that helps explain the question?
### When you searched for similar issues, what did you find that might be related?
<!-- Please demonstrate your research here. -->
### Please provide a minimal, standalone code sample, without other dependencies, that demonstrates this question
<!-- Please provide either formatted code or a link to a repo/gist that helps someone else understand here. -->
### Please provide sample image(s) that help explain this question
<!-- Please provide links to one or more images here. -->

18
.github/SECURITY.md vendored Normal file
View File

@@ -0,0 +1,18 @@
# Security Policy
## Supported Versions
The latest version of `sharp` as published to npm
and reported by `npm view sharp dist-tags.latest`
is supported with security updates.
## Reporting a Vulnerability
Please use
[e-mail](https://github.com/lovell/sharp/blob/main/package.json#L5)
to report a vulnerability.
You can expect a response within 48 hours
if you are a human reporting a genuine issue.
Thank you in advance.

40
.github/workflows/ci-darwin-arm64v8.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
name: CI (MacStadium)
on:
- push
- pull_request
permissions: {}
jobs:
CI:
permissions:
contents: write # for npx prebuild to make release
name: Node.js ${{ matrix.nodejs_version }} ${{ matrix.nodejs_arch }} ${{ matrix.prebuild && '- prebuild' }}
runs-on: macos-m1
strategy:
fail-fast: false
matrix:
include:
- nodejs_version: 14
nodejs_arch: x64
- nodejs_version: 18
nodejs_arch: arm64
prebuild: true
defaults:
run:
shell: /usr/bin/arch -arch arm64e /bin/bash -l {0}
steps:
- name: Dependencies (Node.js)
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.nodejs_version }}
architecture: ${{ matrix.nodejs_arch }}
- name: Checkout
uses: actions/checkout@v3
- name: Install
run: npm install --build-from-source --unsafe-perm
- name: Test
run: npm test
- name: Prebuild
if: matrix.prebuild && startsWith(github.ref, 'refs/tags/')
env:
prebuild_upload: ${{ secrets.GITHUB_TOKEN }}
run: npx prebuild --runtime napi --target 7

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

@@ -0,0 +1,106 @@
name: CI (GitHub)
on:
- push
- pull_request
permissions: {}
jobs:
CI:
permissions:
contents: write # for npx prebuild to make release
name: ${{ matrix.container || matrix.os }} - Node.js ${{ matrix.nodejs_version }} ${{ matrix.nodejs_arch }} ${{ matrix.prebuild && '- prebuild' }}
runs-on: ${{ matrix.os }}
container: ${{ matrix.container }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-22.04
container: centos:7
nodejs_version: 14
prebuild: true
- os: ubuntu-22.04
container: centos:7
nodejs_version: 16
- os: ubuntu-22.04
container: rockylinux:8
nodejs_version: 18
- os: ubuntu-22.04
container: node:14-alpine3.12
prebuild: true
- os: ubuntu-22.04
container: node:16-alpine3.12
- os: ubuntu-22.04
container: node:18-alpine3.14
- os: macos-11
nodejs_version: 14
prebuild: true
nodejs_arch: x64
- os: macos-11
nodejs_version: 16
nodejs_arch: x64
- os: macos-11
nodejs_version: 18
nodejs_arch: x64
- os: windows-2019
nodejs_version: 14
nodejs_arch: x86
prebuild: true
- os: windows-2019
nodejs_version: 16
nodejs_arch: x86
- os: windows-2019
nodejs_version: 18
nodejs_arch: x86
- os: windows-2019
nodejs_version: 14
nodejs_arch: x64
prebuild: true
- os: windows-2019
nodejs_version: 16
nodejs_arch: x64
- os: windows-2019
nodejs_version: 18
nodejs_arch: x64
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 https://packages.endpointdev.com/rhel/7/os/x86_64/endpoint-repo.x86_64.rpm
yum install -y centos-release-scl
yum install -y devtoolset-11-gcc-c++ make git python3 nodejs fontconfig google-noto-sans-fonts
echo "/opt/rh/devtoolset-11/root/usr/bin" >> $GITHUB_PATH
- name: Dependencies (Rocky Linux glibc)
if: contains(matrix.container, 'rockylinux')
run: |
curl -sL https://rpm.nodesource.com/setup_${{ matrix.nodejs_version }}.x | bash -
dnf install -y gcc-toolset-11-gcc-c++ make git python3 nodejs fontconfig google-noto-sans-fonts
echo "/opt/rh/gcc-toolset-11/root/usr/bin" >> $GITHUB_PATH
- name: Dependencies (Linux musl)
if: contains(matrix.container, 'alpine')
run: apk add build-base git python3 font-noto --update-cache
- name: Dependencies (Python 3.10 - macOS, Windows)
if: contains(matrix.os, 'macos') || contains(matrix.os, 'windows')
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Dependencies (Node.js - macOS, Windows)
if: contains(matrix.os, 'macos') || contains(matrix.os, 'windows')
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.nodejs_version }}
architecture: ${{ matrix.nodejs_arch }}
- name: Checkout
uses: actions/checkout@v3
- 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: Prebuild
if: matrix.prebuild && startsWith(github.ref, 'refs/tags/')
env:
prebuild_upload: ${{ secrets.GITHUB_TOKEN }}
run: npx prebuild --runtime napi --target 7

2
.gitignore vendored
View File

@@ -3,6 +3,7 @@ node_modules
/coverage
test/bench/node_modules
test/fixtures/output*
test/fixtures/vips-properties.xml
test/leak/libvips.supp
test/saliency/report.json
test/saliency/Image*
@@ -15,3 +16,4 @@ vendor
.vscode/
package-lock.json
.idea
.firebase

7
.mocharc.jsonc Normal file
View File

@@ -0,0 +1,7 @@
{
"parallel": true,
"slow": 1000,
"timeout": 30000,
"require": "./test/beforeEach.js",
"spec": "./test/unit/*.js"
}

View File

@@ -1,16 +0,0 @@
build
node_modules
coverage
.editorconfig
.gitattributes
.gitignore
test
.travis.yml
appveyor.yml
mkdocs.yml
docs/css/
vendor
.prebuildrc
.nyc_output
.github/
.vscode/

View File

@@ -1,4 +1,4 @@
{
"include-regex": "(sharp\\.node|libvips-cpp\\.dll)",
"include-regex": "(sharp-.+\\.node|libvips-cpp\\.dll)",
"strip": true
}

View File

@@ -1,68 +0,0 @@
matrix:
include:
- name: "Linux (glibc) - Node 8"
os: linux
dist: trusty
sudo: false
language: node_js
node_js: "8"
- name: "Linux (glibc) - Node 10"
os: linux
dist: trusty
sudo: false
language: node_js
node_js: "10"
- name: "Linux (glibc) - Node 12"
os: linux
dist: trusty
sudo: false
language: node_js
node_js: "12"
after_success:
- npm install coveralls
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
- name: "Linux (musl) - Node 8"
os: linux
dist: trusty
sudo: true
language: minimal
before_install:
- sudo docker run -dit --name sharp --env CI --env PREBUILD_TOKEN --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:8-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) - Node 10"
os: linux
dist: trusty
sudo: true
language: minimal
before_install:
- sudo docker run -dit --name sharp --env CI --env PREBUILD_TOKEN --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:10-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) - Node 12"
os: linux
dist: trusty
sudo: true
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: "OS X - Node 8"
os: osx
osx_image: xcode9.2
language: node_js
node_js: "8"
- name: "OS X - Node 10"
os: osx
osx_image: xcode9.2
language: node_js
node_js: "10"
- name: "OS X - Node 12"
os: osx
osx_image: xcode9.2
language: node_js
node_js: "12"

View File

@@ -1,14 +1,10 @@
# 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">
```sh
npm install sharp
```
<img src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
The typical use case for this high speed Node.js module
is to convert large images in common formats to
smaller, web-friendly JPEG, PNG and WebP images of varying dimensions.
smaller, web-friendly JPEG, PNG, WebP, GIF and AVIF images of varying dimensions.
Resizing an image is typically 4x-5x faster than using the
quickest ImageMagick and GraphicsMagick settings
@@ -20,12 +16,23 @@ Lanczos resampling ensures quality is not sacrificed for speed.
As well as image resizing, operations such as
rotation, extraction, compositing and gamma correction are available.
Most modern 64-bit OS X, Windows and Linux systems running
Node versions 8, 10 and 12
Most modern macOS, Windows and Linux systems running Node.js >= 14.15.0
do not require any additional install or runtime dependencies.
## Documentation
Visit [sharp.pixelplumbing.com](https://sharp.pixelplumbing.com/) for complete
[installation instructions](https://sharp.pixelplumbing.com/install),
[API documentation](https://sharp.pixelplumbing.com/api-constructor),
[benchmark tests](https://sharp.pixelplumbing.com/performance) and
[changelog](https://sharp.pixelplumbing.com/changelog).
## Examples
```sh
npm install sharp
```
```javascript
const sharp = require('sharp');
```
@@ -44,6 +51,7 @@ sharp(inputBuffer)
sharp('input.jpg')
.rotate()
.resize(200)
.jpeg({ mozjpeg: true })
.toBuffer()
.then( data => { ... })
.catch( err => { ... });
@@ -85,24 +93,14 @@ readableStream
.pipe(writableStream);
```
[![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.svg?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master)
## Contributing
### Documentation
Visit [sharp.pixelplumbing.com](https://sharp.pixelplumbing.com/) for complete
[installation instructions](https://sharp.pixelplumbing.com/page/install),
[API documentation](https://sharp.pixelplumbing.com/page/api),
[benchmark tests](https://sharp.pixelplumbing.com/page/performance) and
[changelog](https://sharp.pixelplumbing.com/page/changelog).
### Contributing
A [guide for contributors](https://github.com/lovell/sharp/blob/master/.github/CONTRIBUTING.md)
A [guide for contributors](https://github.com/lovell/sharp/blob/main/.github/CONTRIBUTING.md)
covers reporting bugs, requesting features and submitting code changes.
### Licensing
## Licensing
Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
Copyright 2013 Lovell Fuller and others.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -1,15 +0,0 @@
os: Visual Studio 2015
version: "{build}"
build: off
platform: x64
environment:
matrix:
- nodejs_version: "8"
- nodejs_version: "10"
- nodejs_version: "12"
install:
- ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) x64
- npm install -g npm@6
- npm install
test_script:
- npm test

View File

@@ -1,4 +1,9 @@
{
'variables': {
'vips_version': '<!(node -p "require(\'./lib/libvips\').minimumLibvipsVersion")',
'platform_and_arch': '<!(node -p "require(\'./lib/platform\')()")',
'sharp_vendor_dir': './vendor/<(vips_version)/<(platform_and_arch)'
},
'targets': [{
'target_name': 'libvips-cpp',
'conditions': [
@@ -10,25 +15,46 @@
'_ALLOW_KEYWORD_MACROS'
],
'sources': [
'src/libvips/cplusplus/VConnection.cpp',
'src/libvips/cplusplus/VError.cpp',
'src/libvips/cplusplus/VImage.cpp',
'src/libvips/cplusplus/VInterpolate.cpp',
'src/libvips/cplusplus/VImage.cpp'
'src/libvips/cplusplus/VRegion.cpp'
],
'include_dirs': [
'vendor/include',
'vendor/include/glib-2.0',
'vendor/lib/glib-2.0/include'
],
'libraries': [
'../vendor/lib/libvips.lib',
'../vendor/lib/libglib-2.0.lib',
'../vendor/lib/libgobject-2.0.lib'
'<(sharp_vendor_dir)/include',
'<(sharp_vendor_dir)/include/glib-2.0',
'<(sharp_vendor_dir)/lib/glib-2.0/include'
],
'link_settings': {
'library_dirs': ['<(sharp_vendor_dir)/lib'],
'libraries': [
'libvips.lib',
'libglib-2.0.lib',
'libgobject-2.0.lib'
],
},
'configurations': {
'Release': {
'msvs_settings': {
'VCCLCompilerTool': {
'ExceptionHandling': 1
'ExceptionHandling': 1,
'Optimization': 1,
'WholeProgramOptimization': 'true'
},
'VCLibrarianTool': {
'AdditionalOptions': [
'/LTCG:INCREMENTAL'
]
},
'VCLinkerTool': {
'ImageHasSafeExceptionHandlers': 'false',
'OptimizeReferences': 2,
'EnableCOMDATFolding': 2,
'LinkIncremental': 1,
'AdditionalOptions': [
'/LTCG:INCREMENTAL'
]
}
},
'msvs_disabled_warnings': [
@@ -42,16 +68,22 @@
}]
]
}, {
'target_name': 'sharp',
'target_name': 'sharp-<(platform_and_arch)',
'defines': [
'NAPI_VERSION=7',
'NODE_ADDON_API_DISABLE_DEPRECATED',
'NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS'
],
'dependencies': [
'<!(node -p "require(\'node-addon-api\').gyp")',
'libvips-cpp'
],
'variables': {
'runtime_link%': 'shared',
'conditions': [
['OS != "win"', {
'pkg_config_path': '<!(node -e "console.log(require(\'./lib/libvips\').pkgConfigPath())")',
'use_global_libvips': '<!(node -e "console.log(Boolean(require(\'./lib/libvips\').useGlobalLibvips()).toString())")'
'pkg_config_path': '<!(node -p "require(\'./lib/libvips\').pkgConfigPath()")',
'use_global_libvips': '<!(node -p "Boolean(require(\'./lib/libvips\').useGlobalLibvips()).toString()")'
}, {
'pkg_config_path': '',
'use_global_libvips': ''
@@ -64,11 +96,11 @@
'src/stats.cc',
'src/operations.cc',
'src/pipeline.cc',
'src/sharp.cc',
'src/utilities.cc'
'src/utilities.cc',
'src/sharp.cc'
],
'include_dirs': [
'<!(node -e "require(\'nan\')")'
'<!(node -p "require(\'node-addon-api\').include_dir")',
],
'conditions': [
['use_global_libvips == "true"', {
@@ -90,9 +122,9 @@
}, {
# Use pre-built libvips stored locally within node_modules
'include_dirs': [
'vendor/include',
'vendor/include/glib-2.0',
'vendor/lib/glib-2.0/include'
'<(sharp_vendor_dir)/include',
'<(sharp_vendor_dir)/include/glib-2.0',
'<(sharp_vendor_dir)/lib/glib-2.0/include'
],
'conditions': [
['OS == "win"', {
@@ -100,65 +132,43 @@
'_ALLOW_KEYWORD_MACROS',
'_FILE_OFFSET_BITS=64'
],
'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'
]
}
}],
['OS == "mac"', {
'libraries': [
'../vendor/lib/libvips-cpp.42.dylib',
'../vendor/lib/libvips.42.dylib',
'../vendor/lib/libglib-2.0.0.dylib',
'../vendor/lib/libgobject-2.0.0.dylib',
# Ensure runtime linking is relative to sharp.node
'-rpath \'@loader_path/../../vendor/lib\''
]
'link_settings': {
'library_dirs': ['../<(sharp_vendor_dir)/lib'],
'libraries': [
'libvips-cpp.42.dylib'
]
},
'xcode_settings': {
'OTHER_LDFLAGS': [
# Ensure runtime linking is relative to sharp.node
'-Wl,-rpath,\'@loader_path/../../<(sharp_vendor_dir)/lib\''
]
}
}],
['OS == "linux"', {
'defines': [
'_GLIBCXX_USE_CXX11_ABI=0'
'_GLIBCXX_USE_CXX11_ABI=1'
],
'libraries': [
'../vendor/lib/libvips-cpp.so',
'../vendor/lib/libvips.so',
'../vendor/lib/libglib-2.0.so',
'../vendor/lib/libgobject-2.0.so',
# Dependencies of dependencies, included for openSUSE support
'../vendor/lib/libcairo.so',
'../vendor/lib/libcroco-0.6.so',
'../vendor/lib/libexif.so',
'../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\''
]
'link_settings': {
'library_dirs': ['../<(sharp_vendor_dir)/lib'],
'libraries': [
'-l:libvips-cpp.so.42'
],
'ldflags': [
# Ensure runtime linking is relative to sharp.node
'-Wl,-s -Wl,--disable-new-dtags -Wl,-rpath=\'$$ORIGIN/../../<(sharp_vendor_dir)/lib\''
]
}
}]
]
}]
@@ -167,32 +177,58 @@
'-std=c++0x',
'-fexceptions',
'-Wall',
'-O3'
'-Os'
],
'xcode_settings': {
'CLANG_CXX_LANGUAGE_STANDARD': 'c++11',
'CLANG_CXX_LIBRARY': 'libc++',
'MACOSX_DEPLOYMENT_TARGET': '10.7',
'MACOSX_DEPLOYMENT_TARGET': '10.13',
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
'GCC_ENABLE_CPP_RTTI': 'YES',
'OTHER_CPLUSPLUSFLAGS': [
'-fexceptions',
'-Wall',
'-O3'
'-Oz'
]
},
'configurations': {
'Release': {
'cflags_cc': [
'-Wno-cast-function-type'
],
'msvs_settings': {
'VCCLCompilerTool': {
'ExceptionHandling': 1
}
},
'msvs_disabled_warnings': [
4275
'conditions': [
['OS == "linux"', {
'cflags_cc': [
'-Wno-cast-function-type'
]
}],
['target_arch == "arm"', {
'cflags_cc': [
'-Wno-psabi'
]
}],
['OS == "win"', {
'msvs_settings': {
'VCCLCompilerTool': {
'ExceptionHandling': 1,
'Optimization': 1,
'WholeProgramOptimization': 'true'
},
'VCLibrarianTool': {
'AdditionalOptions': [
'/LTCG:INCREMENTAL'
]
},
'VCLinkerTool': {
'ImageHasSafeExceptionHandlers': 'false',
'OptimizeReferences': 2,
'EnableCOMDATFolding': 2,
'LinkIncremental': 1,
'AdditionalOptions': [
'/LTCG:INCREMENTAL'
]
}
},
'msvs_disabled_warnings': [
4275
]
}]
]
}
},

88
docs/README.md Normal file
View File

@@ -0,0 +1,88 @@
# sharp
<img src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
The typical use case for this high speed Node.js module
is to convert large images in common formats to
smaller, web-friendly JPEG, PNG, WebP, GIF and AVIF images of varying dimensions.
Resizing an image is typically 4x-5x faster than using the
quickest ImageMagick and GraphicsMagick settings
due to its use of [libvips](https://github.com/libvips/libvips).
Colour spaces, embedded ICC profiles and alpha transparency channels are all handled correctly.
Lanczos resampling ensures quality is not sacrificed for speed.
As well as image resizing, operations such as
rotation, extraction, compositing and gamma correction are available.
Most modern macOS, Windows and Linux systems running Node.js >= 14.15.0
do not require any additional install or runtime dependencies.
### Formats
This module supports reading JPEG, PNG, WebP, GIF, AVIF, TIFF and SVG images.
Output images can be in JPEG, PNG, WebP, GIF, AVIF and TIFF formats as well as uncompressed raw pixel data.
Streams, Buffer objects and the filesystem can be used for input and output.
A single input Stream can be split into multiple processing pipelines and output Streams.
Deep Zoom image pyramids can be generated,
suitable for use with "slippy map" tile viewers like
[OpenSeadragon](https://github.com/openseadragon/openseadragon).
### Fast
This module is powered by the blazingly fast
[libvips](https://github.com/libvips/libvips) image processing library,
originally created in 1989 at Birkbeck College
and currently maintained by a small team led by
[John Cupitt](https://github.com/jcupitt).
Only small regions of uncompressed image data
are held in memory and processed at a time,
taking full advantage of multiple CPU cores and L1/L2/L3 cache.
Everything remains non-blocking thanks to _libuv_,
no child processes are spawned and Promises/async/await are supported.
### Optimal
The features of `mozjpeg` and `pngquant` can be used
to optimise the file size of JPEG and PNG images respectively,
without having to invoke separate `imagemin` processes.
Huffman tables are optimised when generating JPEG output images
without having to use separate command line tools like
[jpegoptim](https://github.com/tjko/jpegoptim) and
[jpegtran](http://jpegclub.org/jpegtran/).
PNG filtering is disabled by default,
which for diagrams and line art often produces the same result
as [pngcrush](https://pmt.sourceforge.io/pngcrush/).
The file size of animated GIF output is optimised
without having to use separate command line tools such as
[gifsicle](https://www.lcdf.org/gifsicle/).
### Contributing
A [guide for contributors](https://github.com/lovell/sharp/blob/main/.github/CONTRIBUTING.md)
covers reporting bugs, requesting features and submitting code changes.
### Licensing
Copyright 2013 Lovell Fuller and others.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
[https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -1,12 +1,13 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## removeAlpha
> removeAlpha() ⇒ <code>Sharp</code>
Remove alpha channel, if any. This is a no-op if the image does not have an alpha channel.
### Examples
See also [flatten](/api-operation#flatten).
```javascript
**Example**
```js
sharp('rgba.png')
.removeAlpha()
.toFile('rgb.png', function(err, info) {
@@ -14,81 +15,117 @@ sharp('rgba.png')
});
```
Returns **Sharp**
## ensureAlpha
> ensureAlpha([alpha]) ⇒ <code>Sharp</code>
Ensure alpha channel, if missing. The added alpha channel will be fully opaque. This is a no-op if the image already has an alpha channel.
Ensure the output image has an alpha transparency channel.
If missing, the added alpha channel will have the specified
transparency level, defaulting to fully-opaque (1).
This is a no-op if the image already has an alpha channel.
### Examples
```javascript
sharp('rgb.jpg')
**Throws**:
- <code>Error</code> Invalid alpha transparency level
**Since**: 0.21.2
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [alpha] | <code>number</code> | <code>1</code> | alpha transparency level (0=fully-transparent, 1=fully-opaque) |
**Example**
```js
// rgba.png will be a 4 channel image with a fully-opaque alpha channel
await sharp('rgb.jpg')
.ensureAlpha()
.toFile('rgba.png', function(err, info) {
// rgba.png is a 4 channel image with a fully opaque alpha channel
});
.toFile('rgba.png')
```
**Example**
```js
// rgba is a 4 channel image with a fully-transparent alpha channel
const rgba = await sharp(rgb)
.ensureAlpha(0)
.toBuffer();
```
Returns **Sharp**
## extractChannel
> extractChannel(channel) ⇒ <code>Sharp</code>
Extract a single channel from a multi-channel image.
### 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.
**Throws**:
### Examples
- <code>Error</code> Invalid channel
```javascript
sharp(input)
| Param | Type | Description |
| --- | --- | --- |
| channel | <code>number</code> \| <code>string</code> | zero-indexed channel/band number to extract, or `red`, `green`, `blue` or `alpha`. |
**Example**
```js
// green.jpg is a greyscale image containing the green channel of the input
await sharp(input)
.extractChannel('green')
.toFile('input_green.jpg', function(err, info) {
// info.channels === 1
// input_green.jpg contains the green channel of the input image
});
.toFile('green.jpg');
```
**Example**
```js
// red1 is the red value of the first pixel, red2 the second pixel etc.
const [red1, red2, ...] = await sharp(input)
.extractChannel(0)
.raw()
.toBuffer();
```
- Throws **[Error][3]** Invalid channel
Returns **Sharp**
## joinChannel
> joinChannel(images, options) ⇒ <code>Sharp</code>
Join one or more channels to the image.
The meaning of the added channels depends on the output colourspace, set with `toColourspace()`.
By default the output image will be web-friendly sRGB, with additional channels interpreted as alpha channels.
Channel ordering follows vips convention:
- sRGB: 0: Red, 1: Green, 2: Blue, 3: Alpha.
- CMYK: 0: Magenta, 1: Cyan, 2: Yellow, 3: Black, 4: Alpha.
- sRGB: 0: Red, 1: Green, 2: Blue, 3: Alpha.
- CMYK: 0: Magenta, 1: Cyan, 2: Yellow, 3: Black, 4: Alpha.
Buffers may be any of the image formats supported by sharp: JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data.
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.
### Parameters
- `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.
**Throws**:
- <code>Error</code> Invalid parameters
- Throws **[Error][3]** Invalid parameters
| Param | Type | Description |
| --- | --- | --- |
| images | <code>Array.&lt;(string\|Buffer)&gt;</code> \| <code>string</code> \| <code>Buffer</code> | one or more images (file paths, Buffers). |
| options | <code>Object</code> | image options, see `sharp()` constructor. |
Returns **Sharp**
## bandbool
> bandbool(boolOp) ⇒ <code>Sharp</code>
Perform a bitwise boolean operation on all input image channels (bands) to produce a single channel output image.
### Parameters
- `boolOp` **[String][2]** one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
**Throws**:
### Examples
- <code>Error</code> Invalid parameters
```javascript
| Param | Type | Description |
| --- | --- | --- |
| boolOp | <code>string</code> | one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively. |
**Example**
```js
sharp('3-channel-rgb-input.png')
.bandbool(sharp.bool.and)
.toFile('1-channel-output.png', function (err, info) {
@@ -96,20 +133,4 @@ sharp('3-channel-rgb-input.png')
// If `I(1,1) = [247, 170, 14] = [0b11110111, 0b10101010, 0b00001111]`
// then `O(1,1) = 0b11110111 & 0b10101010 & 0b00001111 = 0b00000010 = 2`.
});
```
- Throws **[Error][3]** Invalid parameters
Returns **Sharp**
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[5]: https://nodejs.org/api/buffer.html
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
```

View File

@@ -1,20 +1,29 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## tint
> tint(rgb) ⇒ <code>Sharp</code>
Tint the image using the provided chroma while preserving the image luminance.
An alpha channel may be present and will be unchanged by the operation.
### Parameters
- `rgb` **([String][1] \| [Object][2])** parsed by the [color][3] module to extract chroma values.
**Throws**:
- <code>Error</code> Invalid parameter
- Throws **[Error][4]** Invalid parameter
| Param | Type | Description |
| --- | --- | --- |
| rgb | <code>string</code> \| <code>Object</code> | parsed by the [color](https://www.npmjs.org/package/color) module to extract chroma values. |
**Example**
```js
const output = await sharp(input)
.tint({ r: 255, g: 240, b: 16 })
.toBuffer();
```
Returns **Sharp**
## greyscale
> greyscale([greyscale]) ⇒ <code>Sharp</code>
Convert to 8-bit greyscale; 256 shades of grey.
This is a linear operation. If the input image is in a non-linear colour space such as sRGB, use `gamma()` with `greyscale()` for the best results.
@@ -23,57 +32,116 @@ This may be overridden by other sharp operations such as `toColourspace('b-w')`,
which will produce an output image containing one color channel.
An alpha channel may be present, and will be unchanged by the operation.
### Parameters
- `greyscale` **[Boolean][5]** (optional, default `true`)
Returns **Sharp**
| Param | Type | Default |
| --- | --- | --- |
| [greyscale] | <code>Boolean</code> | <code>true</code> |
**Example**
```js
const output = await sharp(input).greyscale().toBuffer();
```
## grayscale
> grayscale([grayscale]) ⇒ <code>Sharp</code>
Alternative spelling of `greyscale`.
### Parameters
- `grayscale` **[Boolean][5]** (optional, default `true`)
Returns **Sharp**
| Param | Type | Default |
| --- | --- | --- |
| [grayscale] | <code>Boolean</code> | <code>true</code> |
## pipelineColourspace
> pipelineColourspace([colourspace]) ⇒ <code>Sharp</code>
Set the pipeline colourspace.
The input image will be converted to the provided colourspace at the start of the pipeline.
All operations will use this colourspace before converting to the output colourspace,
as defined by [toColourspace](#tocolourspace).
This feature is experimental and has not yet been fully-tested with all operations.
**Throws**:
- <code>Error</code> Invalid parameters
**Since**: 0.29.0
| Param | Type | Description |
| --- | --- | --- |
| [colourspace] | <code>string</code> | pipeline colourspace e.g. `rgb16`, `scrgb`, `lab`, `grey16` [...](https://github.com/libvips/libvips/blob/41cff4e9d0838498487a00623462204eb10ee5b8/libvips/iofuncs/enumtypes.c#L774) |
**Example**
```js
// Run pipeline in 16 bits per channel RGB while converting final result to 8 bits per channel sRGB.
await sharp(input)
.pipelineColourspace('rgb16')
.toColourspace('srgb')
.toFile('16bpc-pipeline-to-8bpc-output.png')
```
## pipelineColorspace
> pipelineColorspace([colorspace]) ⇒ <code>Sharp</code>
Alternative spelling of `pipelineColourspace`.
**Throws**:
- <code>Error</code> Invalid parameters
| Param | Type | Description |
| --- | --- | --- |
| [colorspace] | <code>string</code> | pipeline colorspace. |
## toColourspace
> toColourspace([colourspace]) ⇒ <code>Sharp</code>
Set the output colourspace.
By default output image will be web-friendly sRGB, with additional channels interpreted as alpha channels.
### Parameters
- `colourspace` **[String][1]?** output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...][6]
**Throws**:
- <code>Error</code> Invalid parameters
- Throws **[Error][4]** Invalid parameters
| Param | Type | Description |
| --- | --- | --- |
| [colourspace] | <code>string</code> | output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/libvips/libvips/blob/3c0bfdf74ce1dc37a6429bed47fa76f16e2cd70a/libvips/iofuncs/enumtypes.c#L777-L794) |
**Example**
```js
// Output 16 bits per pixel RGB
await sharp(input)
.toColourspace('rgb16')
.toFile('16-bpp.png')
```
Returns **Sharp**
## toColorspace
> toColorspace([colorspace]) ⇒ <code>Sharp</code>
Alternative spelling of `toColourspace`.
### Parameters
- `colorspace` **[String][1]?** output colorspace.
**Throws**:
- <code>Error</code> Invalid parameters
- Throws **[Error][4]** Invalid parameters
Returns **Sharp**
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[3]: https://www.npmjs.org/package/color
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[6]: https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568
| Param | Type | Description |
| --- | --- | --- |
| [colorspace] | <code>string</code> | output colorspace. |

View File

@@ -1,12 +1,14 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## composite
> composite(images) ⇒ <code>Sharp</code>
Composite image(s) over the processed (resized, extracted etc.) image.
The images to composite must be the same size or smaller than the processed image.
If both `top` and `left` options are provided, they take precedence over `gravity`.
Any resize, rotate or extract operations in the same processing pipeline
will always be applied to the input image before composition.
The `blend` option can be one of `clear`, `source`, `over`, `in`, `out`, `atop`,
`dest`, `dest-over`, `dest-in`, `dest-out`, `dest-atop`,
`xor`, `add`, `saturate`, `multiply`, `screen`, `overlay`, `darken`, `lighten`,
@@ -14,33 +16,70 @@ The `blend` option can be one of `clear`, `source`, `over`, `in`, `out`, `atop`,
`hard-light`, `soft-light`, `difference`, `exclusion`.
More information about blend modes can be found at
[https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode][1]
and [https://www.cairographics.org/operators/][2]
https://www.libvips.org/API/current/libvips-conversion.html#VipsBlendMode
and https://www.cairographics.org/operators/
### Parameters
- `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.create` **[Object][4]?** describes a blank overlay to be created.
- `images[].input.create.width` **[Number][7]?**
- `images[].input.create.height` **[Number][7]?**
- `images[].input.create.channels` **[Number][7]?** 3-4
- `images[].input.create.background` **([String][6] \| [Object][4])?** parsed by the [color][8] module to extract values for red, green, blue and alpha.
- `images[].blend` **[String][6]** how to blend this image with the image below. (optional, default `'over'`)
- `images[].gravity` **[String][6]** gravity at which to place the overlay. (optional, default `'centre'`)
- `images[].top` **[Number][7]?** the pixel offset from the top edge.
- `images[].left` **[Number][7]?** the pixel offset from the left edge.
- `images[].tile` **[Boolean][9]** set to true to repeat the overlay image across the entire image with the given `gravity`. (optional, default `false`)
- `images[].premultiplied` **[Boolean][9]** set to true to avoid premultipling the image below. Equivalent to the `--premultiplied` vips option. (optional, default `false`)
- `images[].density` **[Number][7]** number representing the DPI for vector overlay image. (optional, default `72`)
- `images[].raw` **[Object][4]?** describes overlay when using raw pixel data.
- `images[].raw.width` **[Number][7]?**
- `images[].raw.height` **[Number][7]?**
- `images[].raw.channels` **[Number][7]?**
**Throws**:
### Examples
- <code>Error</code> Invalid parameters
```javascript
**Since**: 0.22.0
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| images | <code>Array.&lt;Object&gt;</code> | | Ordered list of images to composite |
| [images[].input] | <code>Buffer</code> \| <code>String</code> | | Buffer containing image data, String containing the path to an image file, or Create object (see below) |
| [images[].input.create] | <code>Object</code> | | describes a blank overlay to be created. |
| [images[].input.create.width] | <code>Number</code> | | |
| [images[].input.create.height] | <code>Number</code> | | |
| [images[].input.create.channels] | <code>Number</code> | | 3-4 |
| [images[].input.create.background] | <code>String</code> \| <code>Object</code> | | parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. |
| [images[].input.text] | <code>Object</code> | | describes a new text image to be created. |
| [images[].input.text.text] | <code>string</code> | | text to render as a UTF-8 string. It can contain Pango markup, for example `<i>Le</i>Monde`. |
| [images[].input.text.font] | <code>string</code> | | font name to render with. |
| [images[].input.text.fontfile] | <code>string</code> | | absolute filesystem path to a font file that can be used by `font`. |
| [images[].input.text.width] | <code>number</code> | <code>0</code> | integral number of pixels to word-wrap at. Lines of text wider than this will be broken at word boundaries. |
| [images[].input.text.height] | <code>number</code> | <code>0</code> | integral number of pixels high. When defined, `dpi` will be ignored and the text will automatically fit the pixel resolution defined by `width` and `height`. Will be ignored if `width` is not specified or set to 0. |
| [images[].input.text.align] | <code>string</code> | <code>&quot;&#x27;left&#x27;&quot;</code> | text alignment (`'left'`, `'centre'`, `'center'`, `'right'`). |
| [images[].input.text.justify] | <code>boolean</code> | <code>false</code> | set this to true to apply justification to the text. |
| [images[].input.text.dpi] | <code>number</code> | <code>72</code> | the resolution (size) at which to render the text. Does not take effect if `height` is specified. |
| [images[].input.text.rgba] | <code>boolean</code> | <code>false</code> | set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for Pango markup features like `<span foreground="red">Red!</span>`. |
| [images[].input.text.spacing] | <code>number</code> | <code>0</code> | text line height in points. Will use the font line height if none is specified. |
| [images[].blend] | <code>String</code> | <code>&#x27;over&#x27;</code> | how to blend this image with the image below. |
| [images[].gravity] | <code>String</code> | <code>&#x27;centre&#x27;</code> | gravity at which to place the overlay. |
| [images[].top] | <code>Number</code> | | the pixel offset from the top edge. |
| [images[].left] | <code>Number</code> | | the pixel offset from the left edge. |
| [images[].tile] | <code>Boolean</code> | <code>false</code> | set to true to repeat the overlay image across the entire image with the given `gravity`. |
| [images[].premultiplied] | <code>Boolean</code> | <code>false</code> | set to true to avoid premultiplying the image below. Equivalent to the `--premultiplied` vips option. |
| [images[].density] | <code>Number</code> | <code>72</code> | number representing the DPI for vector overlay image. |
| [images[].raw] | <code>Object</code> | | describes overlay when using raw pixel data. |
| [images[].raw.width] | <code>Number</code> | | |
| [images[].raw.height] | <code>Number</code> | | |
| [images[].raw.channels] | <code>Number</code> | | |
| [images[].animated] | <code>boolean</code> | <code>false</code> | Set to `true` to read all frames/pages of an animated image. |
| [images[].failOn] | <code>string</code> | <code>&quot;&#x27;warning&#x27;&quot;</code> | @see [constructor parameters](/api-constructor#parameters) |
| [images[].limitInputPixels] | <code>number</code> \| <code>boolean</code> | <code>268402689</code> | @see [constructor parameters](/api-constructor#parameters) |
**Example**
```js
await sharp(background)
.composite([
{ input: layer1, gravity: 'northwest' },
{ input: layer2, gravity: 'southeast' },
])
.toFile('combined.png');
```
**Example**
```js
const output = await sharp('input.gif', { animated: true })
.composite([
{ input: 'overlay.png', tile: true, blend: 'saturate' }
])
.toBuffer();
```
**Example**
```js
sharp('input.png')
.rotate(180)
.resize(300)
@@ -55,28 +94,4 @@ sharp('input.png')
// onto orange background, composited with overlay.png with SE gravity,
// sharpened, with metadata, 90% quality WebP image data. Phew!
});
```
- Throws **[Error][10]** Invalid parameters
Returns **Sharp**
[1]: https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode
[2]: https://www.cairographics.org/operators/
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[5]: https://nodejs.org/api/buffer.html
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[8]: https://www.npmjs.org/package/color
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
```

View File

@@ -1,32 +1,71 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## Sharp
> Sharp
### Parameters
- `input` **([Buffer][1] \| [String][2])?** if present, can be
a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
a String containing the path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
- `options` **[Object][3]?** if present, is an Object with optional attributes.
- `options.failOnError` **[Boolean][4]** by default halt processing and raise an error when loading invalid images.
Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid. (optional, default `true`)
- `options.density` **[Number][5]** number representing the DPI for vector images. (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.page` **[Number][5]** page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based. (optional, default `0`)
- `options.raw` **[Object][3]?** describes raw pixel input image data. See `raw()` for pixel ordering.
- `options.raw.width` **[Number][5]?**
- `options.raw.height` **[Number][5]?**
- `options.raw.channels` **[Number][5]?** 1-4
- `options.create` **[Object][3]?** describes a new image to be created.
- `options.create.width` **[Number][5]?**
- `options.create.height` **[Number][5]?**
- `options.create.channels` **[Number][5]?** 3-4
- `options.create.background` **([String][2] \| [Object][3])?** parsed by the [color][6] module to extract values for red, green, blue and alpha.
**Emits**: <code>Sharp#event:info</code>, <code>Sharp#event:warning</code>
<a name="new_Sharp_new"></a>
### Examples
### new
> new Sharp([input], [options])
```javascript
Constructor factory to create an instance of `sharp`, to which further methods are chained.
JPEG, PNG, WebP, GIF, AVIF or TIFF format image data can be streamed out from this object.
When using Stream based output, derived attributes are available from the `info` event.
Non-critical problems encountered during processing are emitted as `warning` events.
Implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.
**Throws**:
- <code>Error</code> Invalid parameters
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [input] | <code>Buffer</code> \| <code>ArrayBuffer</code> \| <code>Uint8Array</code> \| <code>Uint8ClampedArray</code> \| <code>Int8Array</code> \| <code>Uint16Array</code> \| <code>Int16Array</code> \| <code>Uint32Array</code> \| <code>Int32Array</code> \| <code>Float32Array</code> \| <code>Float64Array</code> \| <code>string</code> | | if present, can be a Buffer / ArrayBuffer / Uint8Array / Uint8ClampedArray containing JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image data, or a TypedArray containing raw pixel image data, or a String containing the filesystem path to an JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image file. JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present. |
| [options] | <code>Object</code> | | if present, is an Object with optional attributes. |
| [options.failOn] | <code>string</code> | <code>&quot;&#x27;warning&#x27;&quot;</code> | when to abort processing of invalid pixel data, one of (in order of sensitivity): 'none' (least), 'truncated', 'error' or 'warning' (most), higher levels imply lower levels, invalid metadata will always abort. |
| [options.limitInputPixels] | <code>number</code> \| <code>boolean</code> | <code>268402689</code> | Do not process input images where the number of pixels (width x height) exceeds this limit. Assumes image dimensions contained in the input metadata can be trusted. An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF). |
| [options.unlimited] | <code>boolean</code> | <code>false</code> | Set this to `true` to remove safety features that help prevent memory exhaustion (JPEG, PNG, SVG, HEIF). |
| [options.sequentialRead] | <code>boolean</code> | <code>true</code> | Set this to `false` to use random access rather than sequential read. Some operations will do this automatically. |
| [options.density] | <code>number</code> | <code>72</code> | number representing the DPI for vector images in the range 1 to 100000. |
| [options.ignoreIcc] | <code>number</code> | <code>false</code> | should the embedded ICC profile, if any, be ignored. |
| [options.pages] | <code>number</code> | <code>1</code> | Number of pages to extract for multi-page input (GIF, WebP, TIFF), use -1 for all pages. |
| [options.page] | <code>number</code> | <code>0</code> | Page number to start extracting from for multi-page input (GIF, WebP, TIFF), zero based. |
| [options.subifd] | <code>number</code> | <code>-1</code> | subIFD (Sub Image File Directory) to extract for OME-TIFF, defaults to main image. |
| [options.level] | <code>number</code> | <code>0</code> | level to extract from a multi-level input (OpenSlide), zero based. |
| [options.animated] | <code>boolean</code> | <code>false</code> | Set to `true` to read all frames/pages of an animated image (GIF, WebP, TIFF), equivalent of setting `pages` to `-1`. |
| [options.raw] | <code>Object</code> | | describes raw pixel input image data. See `raw()` for pixel ordering. |
| [options.raw.width] | <code>number</code> | | integral number of pixels wide. |
| [options.raw.height] | <code>number</code> | | integral number of pixels high. |
| [options.raw.channels] | <code>number</code> | | integral number of channels, between 1 and 4. |
| [options.raw.premultiplied] | <code>boolean</code> | | specifies that the raw input has already been premultiplied, set to `true` to avoid sharp premultiplying the image. (optional, default `false`) |
| [options.create] | <code>Object</code> | | describes a new image to be created. |
| [options.create.width] | <code>number</code> | | integral number of pixels wide. |
| [options.create.height] | <code>number</code> | | integral number of pixels high. |
| [options.create.channels] | <code>number</code> | | integral number of channels, either 3 (RGB) or 4 (RGBA). |
| [options.create.background] | <code>string</code> \| <code>Object</code> | | parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. |
| [options.create.noise] | <code>Object</code> | | describes a noise to be created. |
| [options.create.noise.type] | <code>string</code> | | type of generated noise, currently only `gaussian` is supported. |
| [options.create.noise.mean] | <code>number</code> | | mean of pixels in generated noise. |
| [options.create.noise.sigma] | <code>number</code> | | standard deviation of pixels in generated noise. |
| [options.text] | <code>Object</code> | | describes a new text image to be created. |
| [options.text.text] | <code>string</code> | | text to render as a UTF-8 string. It can contain Pango markup, for example `<i>Le</i>Monde`. |
| [options.text.font] | <code>string</code> | | font name to render with. |
| [options.text.fontfile] | <code>string</code> | | absolute filesystem path to a font file that can be used by `font`. |
| [options.text.width] | <code>number</code> | <code>0</code> | Integral number of pixels to word-wrap at. Lines of text wider than this will be broken at word boundaries. |
| [options.text.height] | <code>number</code> | <code>0</code> | Maximum integral number of pixels high. When defined, `dpi` will be ignored and the text will automatically fit the pixel resolution defined by `width` and `height`. Will be ignored if `width` is not specified or set to 0. |
| [options.text.align] | <code>string</code> | <code>&quot;&#x27;left&#x27;&quot;</code> | Alignment style for multi-line text (`'left'`, `'centre'`, `'center'`, `'right'`). |
| [options.text.justify] | <code>boolean</code> | <code>false</code> | set this to true to apply justification to the text. |
| [options.text.dpi] | <code>number</code> | <code>72</code> | the resolution (size) at which to render the text. Does not take effect if `height` is specified. |
| [options.text.rgba] | <code>boolean</code> | <code>false</code> | set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for pango markup features like `<span foreground="red">Red!</span>`. |
| [options.text.spacing] | <code>number</code> | <code>0</code> | text line height in points. Will use the font line height if none is specified. |
| [options.text.wrap] | <code>string</code> | <code>&quot;&#x27;word&#x27;&quot;</code> | word wrapping style when width is provided, one of: 'word', 'char', 'charWord' (prefer char, fallback to word) or 'none'. |
**Example**
```js
sharp('input.jpg')
.resize(300, 200)
.toFile('output.jpg', function(err) {
@@ -34,8 +73,8 @@ sharp('input.jpg')
// containing a scaled and cropped version of input.jpg
});
```
```javascript
**Example**
```js
// Read image data from readableStream,
// resize to 300 pixels wide,
// emit an 'info' event with calculated dimensions
@@ -47,9 +86,9 @@ var transformer = sharp()
});
readableStream.pipe(transformer).pipe(writableStream);
```
```javascript
// Create a blank 300x200 PNG image of semi-transluent red pixels
**Example**
```js
// Create a blank 300x200 PNG image of semi-translucent red pixels
sharp({
create: {
width: 300,
@@ -62,60 +101,128 @@ sharp({
.toBuffer()
.then( ... );
```
- Throws **[Error][7]** Invalid parameters
Returns **[Sharp][8]**
### format
An Object containing nested boolean values representing the available input and output formats/methods.
#### Examples
```javascript
console.log(sharp.format);
**Example**
```js
// Convert an animated GIF to an animated WebP
await sharp('in.gif', { animated: true }).toFile('out.webp');
```
Returns **[Object][3]**
### versions
An Object containing the version numbers of libvips and its dependencies.
#### Examples
```javascript
console.log(sharp.versions);
```
## queue
An EventEmitter that emits a `change` event when a task is either:
- queued, waiting for _libuv_ to provide a worker thread
- complete
### Examples
```javascript
sharp.queue.on('change', function(queueLength) {
console.log('Queue contains ' + queueLength + ' task(s)');
**Example**
```js
// Read a raw array of pixels and save it to a png
const input = Uint8Array.from([255, 255, 255, 0, 0, 0]); // or Uint8ClampedArray
const image = sharp(input, {
// because the input does not contain its dimensions or how many channels it has
// we need to specify it in the constructor options
raw: {
width: 2,
height: 1,
channels: 3
}
});
await image.toFile('my-two-pixels.png');
```
**Example**
```js
// Generate RGB Gaussian noise
await sharp({
create: {
width: 300,
height: 200,
channels: 3,
noise: {
type: 'gaussian',
mean: 128,
sigma: 30
}
}
}).toFile('noise.png');
```
**Example**
```js
// Generate an image from text
await sharp({
text: {
text: 'Hello, world!',
width: 400, // max width
height: 300 // max height
}
}).toFile('text_bw.png');
```
**Example**
```js
// Generate an rgba image from text using pango markup and font
await sharp({
text: {
text: '<span foreground="red">Red!</span><span background="cyan">blue</span>',
font: 'sans',
rgba: true,
dpi: 300
}
}).toFile('text_rgba.png');
```
[1]: https://nodejs.org/api/buffer.html
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
## clone
> clone() ⇒ [<code>Sharp</code>](#Sharp)
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
Take a "snapshot" of the Sharp instance, returning a new instance.
Cloned instances inherit the input of their parent instance.
This allows multiple output Streams and therefore multiple processing pipelines to share a single input Stream.
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
**Example**
```js
const pipeline = sharp().rotate();
pipeline.clone().resize(800, 600).pipe(firstWritableStream);
pipeline.clone().extract({ left: 20, top: 20, width: 100, height: 100 }).pipe(secondWritableStream);
readableStream.pipe(pipeline);
// firstWritableStream receives auto-rotated, resized readableStream
// secondWritableStream receives auto-rotated, extracted region of readableStream
```
**Example**
```js
// Create a pipeline that will download an image, resize it and format it to different files
// Using Promises to know when the pipeline is complete
const fs = require("fs");
const got = require("got");
const sharpStream = sharp({ failOn: 'none' });
[6]: https://www.npmjs.org/package/color
const promises = [];
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
promises.push(
sharpStream
.clone()
.jpeg({ quality: 100 })
.toFile("originalFile.jpg")
);
[8]: #sharp
promises.push(
sharpStream
.clone()
.resize({ width: 500 })
.jpeg({ quality: 80 })
.toFile("optimized-500.jpg")
);
promises.push(
sharpStream
.clone()
.resize({ width: 500 })
.webp({ quality: 80 })
.toFile("optimized-500.webp")
);
// https://github.com/sindresorhus/got/blob/main/documentation/3-streams.md
got.stream("https://www.example.com/some-file.jpg").pipe(sharpStream);
Promise.all(promises)
.then(res => { console.log("Done!", res); })
.catch(err => {
console.error("Error processing files, let's clean it up", err);
try {
fs.unlinkSync("originalFile.jpg");
fs.unlinkSync("optimized-500.jpg");
fs.unlinkSync("optimized-500.webp");
} catch (e) {}
});
```

View File

@@ -1,57 +1,60 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## clone
Take a "snapshot" of the Sharp instance, returning a new instance.
Cloned instances inherit the input of their parent instance.
This allows multiple output Streams and therefore multiple processing pipelines to share a single input Stream.
### Examples
```javascript
const pipeline = sharp().rotate();
pipeline.clone().resize(800, 600).pipe(firstWritableStream);
pipeline.clone().extract({ left: 20, top: 20, width: 100, height: 100 }).pipe(secondWritableStream);
readableStream.pipe(pipeline);
// firstWritableStream receives auto-rotated, resized readableStream
// secondWritableStream receives auto-rotated, extracted region of readableStream
```
Returns **Sharp**
## metadata
> metadata([callback]) ⇒ <code>Promise.&lt;Object&gt;</code> \| <code>Sharp</code>
Fast access to (uncached) image metadata without decoding any compressed pixel data.
This is read from the header of the input image.
It does not take into consideration any operations to be applied to the output image,
such as resize or rotate.
Dimensions in the response will respect the `page` and `pages` properties of the
[constructor parameters](/api-constructor#parameters).
Fast access to (uncached) image metadata without decoding any compressed image data.
A `Promise` is returned when `callback` is not provided.
- `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
- `size`: Total size of image in bytes, for Stream and Buffer input only
- `width`: Number of pixels wide (EXIF orientation is not taken into consideration)
- `height`: Number of pixels high (EXIF orientation is not taken into consideration)
- `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...][1]
- `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
- `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...][2]
- `density`: Number of pixels per inch (DPI), if present
- `chromaSubsampling`: String containing JPEG chroma subsampling, `4:2:0` or `4:4:4` for RGB, `4:2:0:4` or `4:4:4:4` for CMYK
- `isProgressive`: Boolean indicating whether the image is interlaced using a progressive scan
- `pages`: Number of pages/frames contained within the image, with support for TIFF, HEIF, PDF, animated GIF and animated WebP
- `pageHeight`: Number of pixels high each page in this PDF image will be.
- `pagePrimary`: Number of the primary page in a HEIF image
- `hasProfile`: Boolean indicating the presence of an embedded ICC profile
- `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
- `orientation`: Number value of the EXIF Orientation header, if present
- `exif`: Buffer containing raw EXIF data, if present
- `icc`: Buffer containing raw [ICC][3] profile data, if present
- `iptc`: Buffer containing raw IPTC data, if present
- `xmp`: Buffer containing raw XMP data, if present
- `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
- `size`: Total size of image in bytes, for Stream and Buffer input only
- `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below)
- `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below)
- `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://www.libvips.org/API/current/VipsImage.html#VipsInterpretation)
- `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
- `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://www.libvips.org/API/current/VipsImage.html#VipsBandFormat)
- `density`: Number of pixels per inch (DPI), if present
- `chromaSubsampling`: String containing JPEG chroma subsampling, `4:2:0` or `4:4:4` for RGB, `4:2:0:4` or `4:4:4:4` for CMYK
- `isProgressive`: Boolean indicating whether the image is interlaced using a progressive scan
- `pages`: Number of pages/frames contained within the image, with support for TIFF, HEIF, PDF, animated GIF and animated WebP
- `pageHeight`: Number of pixels high each page in a multi-page image will be.
- `paletteBitDepth`: Bit depth of palette-based image (GIF, PNG).
- `loop`: Number of times to loop an animated image, zero refers to a continuous loop.
- `delay`: Delay in ms between each page in an animated image, provided as an array of integers.
- `pagePrimary`: Number of the primary page in a HEIF image
- `levels`: Details of each level in a multi-level image provided as an array of objects, requires libvips compiled with support for OpenSlide
- `subifds`: Number of Sub Image File Directories in an OME-TIFF image
- `background`: Default background colour, if present, for PNG (bKGD) and GIF images, either an RGB Object or a single greyscale value
- `compression`: The encoder used to compress an HEIF file, `av1` (AVIF) or `hevc` (HEIC)
- `resolutionUnit`: The unit of resolution (density), either `inch` or `cm`, if present
- `hasProfile`: Boolean indicating the presence of an embedded ICC profile
- `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
- `orientation`: Number value of the EXIF Orientation header, if present
- `exif`: Buffer containing raw EXIF data, if present
- `icc`: Buffer containing raw [ICC](https://www.npmjs.com/package/icc) profile data, if present
- `iptc`: Buffer containing raw IPTC data, if present
- `xmp`: Buffer containing raw XMP data, if present
- `tifftagPhotoshop`: Buffer containing raw TIFFTAG_PHOTOSHOP data, if present
- `formatMagick`: String containing format for images loaded via *magick
### Parameters
- `callback` **[Function][4]?** called with the arguments `(err, metadata)`
### Examples
| Param | Type | Description |
| --- | --- | --- |
| [callback] | <code>function</code> | called with the arguments `(err, metadata)` |
```javascript
**Example**
```js
const metadata = await sharp(input).metadata();
```
**Example**
```js
const image = sharp(inputJpg);
image
.metadata()
@@ -65,35 +68,53 @@ image
// data contains a WebP image half the width and height of the original JPEG
});
```
**Example**
```js
// Based on EXIF rotation metadata, get the right-side-up width and height:
const size = getNormalSize(await sharp(input).metadata());
function getNormalSize({ width, height, orientation }) {
return (orientation || 0) >= 5
? { width: height, height: width }
: { width, height };
}
```
Returns **([Promise][5]&lt;[Object][6]> | Sharp)**
## stats
> stats([callback]) ⇒ <code>Promise.&lt;Object&gt;</code>
Access to pixel-derived image statistics for every channel in the image.
A `Promise` is returned when `callback` is not provided.
- `channels`: Array of channel statistics for each channel in the image. Each channel statistic contains
- `min` (minimum value in the channel)
- `max` (maximum value in the channel)
- `sum` (sum of all values in a channel)
- `squaresSum` (sum of squared values in a channel)
- `mean` (mean of the values in a channel)
- `stdev` (standard deviation for the values in a channel)
- `minX` (x-coordinate of one of the pixel where the minimum lies)
- `minY` (y-coordinate of one of the pixel where the minimum lies)
- `maxX` (x-coordinate of one of the pixel where the maximum lies)
- `maxY` (y-coordinate of one of the pixel where the maximum lies)
- `isOpaque`: Value to identify if the image is opaque or transparent, based on the presence and use of alpha channel
- `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental)
- `channels`: Array of channel statistics for each channel in the image. Each channel statistic contains
- `min` (minimum value in the channel)
- `max` (maximum value in the channel)
- `sum` (sum of all values in a channel)
- `squaresSum` (sum of squared values in a channel)
- `mean` (mean of the values in a channel)
- `stdev` (standard deviation for the values in a channel)
- `minX` (x-coordinate of one of the pixel where the minimum lies)
- `minY` (y-coordinate of one of the pixel where the minimum lies)
- `maxX` (x-coordinate of one of the pixel where the maximum lies)
- `maxY` (y-coordinate of one of the pixel where the maximum lies)
- `isOpaque`: Is the image fully opaque? Will be `true` if the image has no alpha channel or if every pixel is fully opaque.
- `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any.
- `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any.
- `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram.
### Parameters
**Note**: Statistics are derived from the original input image. Any operations performed on the image must first be
written to a buffer in order to run `stats` on the result (see third example).
- `callback` **[Function][4]?** called with the arguments `(err, stats)`
### Examples
```javascript
| Param | Type | Description |
| --- | --- | --- |
| [callback] | <code>function</code> | called with the arguments `(err, stats)` |
**Example**
```js
const image = sharp(inputJpg);
image
.stats()
@@ -101,51 +122,16 @@ image
// stats contains the channel-wise statistics array and the isOpaque value
});
```
Returns **[Promise][5]&lt;[Object][6]>**
## limitInputPixels
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.
The default limit is 268402689 (0x3FFF x 0x3FFF) pixels.
### Parameters
- `limit` **([Number][7] \| [Boolean][8])** an integral Number of pixels, zero or false to remove limit, true to use default limit.
- Throws **[Error][9]** Invalid limit
Returns **Sharp**
## sequentialRead
An advanced setting that switches the libvips access method to `VIPS_ACCESS_SEQUENTIAL`.
This will reduce memory usage and can improve performance on some systems.
The default behaviour _before_ function call is `false`, meaning the libvips access method is not sequential.
### Parameters
- `sequentialRead` **[Boolean][8]** (optional, default `true`)
Returns **Sharp**
[1]: https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L636
[2]: https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L672
[3]: https://www.npmjs.com/package/icc
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
**Example**
```js
const { entropy, sharpness, dominant } = await sharp(input).stats();
const { r, g, b } = dominant;
```
**Example**
```js
const image = sharp(input);
// store intermediate result
const part = await image.extract(region).toBuffer();
// create new instance to obtain statistics of extracted region
const stats = await sharp(part).stats();
```

View File

@@ -1,12 +1,11 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## rotate
> rotate([angle], [options]) ⇒ <code>Sharp</code>
Rotate the output image by either an explicit angle
or auto-orient based on the EXIF `Orientation` tag.
If an angle is provided, it is converted to a valid positive degree rotation.
For example, `-450` will produce a 270deg rotation.
For example, `-450` will produce a 270 degree rotation.
When rotating by an angle other than a multiple of 90,
the background colour can be provided with the `background` option.
@@ -14,20 +13,28 @@ the background colour can be provided with the `background` option.
If no angle is provided, it is determined from the EXIF data.
Mirroring is supported and may infer the use of a flip operation.
The use of `rotate` implies the removal of the EXIF `Orientation` tag, if any.
The use of `rotate` without an angle will remove the EXIF `Orientation` tag, if any.
Method order is important when both rotating and extracting regions,
for example `rotate(x).extract(y)` will produce a different result to `extract(y).rotate(x)`.
Only one rotation can occur per pipeline.
Previous calls to `rotate` in the same pipeline will be ignored.
### Parameters
Method order is important when rotating, resizing and/or extracting regions,
for example `.rotate(x).extract(y)` will produce a different result to `.extract(y).rotate(x)`.
- `angle` **[Number][1]** angle of rotation. (optional, default `auto`)
- `options` **[Object][2]?** if present, is an Object with optional attributes.
- `options.background` **([String][3] \| [Object][2])** parsed by the [color][4] module to extract values for red, green, blue and alpha. (optional, default `"#000000"`)
### Examples
**Throws**:
```javascript
- <code>Error</code> Invalid parameters
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [angle] | <code>number</code> | <code>auto</code> | angle of rotation. |
| [options] | <code>Object</code> | | if present, is an Object with optional attributes. |
| [options.background] | <code>string</code> \| <code>Object</code> | <code>&quot;\&quot;#000000\&quot;&quot;</code> | parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. |
**Example**
```js
const pipeline = sharp()
.rotate()
.resize(null, 200)
@@ -38,92 +45,273 @@ const pipeline = sharp()
});
readableStream.pipe(pipeline);
```
**Example**
```js
const rotateThenResize = await sharp(input)
.rotate(90)
.resize({ width: 16, height: 8, fit: 'fill' })
.toBuffer();
const resizeThenRotate = await sharp(input)
.resize({ width: 16, height: 8, fit: 'fill' })
.rotate(90)
.toBuffer();
```
- Throws **[Error][5]** Invalid parameters
Returns **Sharp**
## flip
> flip([flip]) ⇒ <code>Sharp</code>
Flip the image about the vertical Y axis. This always occurs after rotation, if any.
The use of `flip` implies the removal of the EXIF `Orientation` tag, if any.
Mirror the image vertically (up-down) about the x-axis.
This always occurs before rotation, if any.
### Parameters
This operation does not work correctly with multi-page images.
- `flip` **[Boolean][6]** (optional, default `true`)
Returns **Sharp**
| Param | Type | Default |
| --- | --- | --- |
| [flip] | <code>Boolean</code> | <code>true</code> |
**Example**
```js
const output = await sharp(input).flip().toBuffer();
```
## flop
> flop([flop]) ⇒ <code>Sharp</code>
Flop the image about the horizontal X axis. This always occurs after rotation, if any.
The use of `flop` implies the removal of the EXIF `Orientation` tag, if any.
Mirror the image horizontally (left-right) about the y-axis.
This always occurs before rotation, if any.
### Parameters
- `flop` **[Boolean][6]** (optional, default `true`)
Returns **Sharp**
| Param | Type | Default |
| --- | --- | --- |
| [flop] | <code>Boolean</code> | <code>true</code> |
**Example**
```js
const output = await sharp(input).flop().toBuffer();
```
## affine
> affine(matrix, [options]) ⇒ <code>Sharp</code>
Perform an affine transform on an image. This operation will always occur after resizing, extraction and rotation, if any.
You must provide an array of length 4 or a 2x2 affine transformation matrix.
By default, new pixels are filled with a black background. You can provide a background color with the `background` option.
A particular interpolator may also be specified. Set the `interpolator` option to an attribute of the `sharp.interpolators` Object e.g. `sharp.interpolators.nohalo`.
In the case of a 2x2 matrix, the transform is:
- X = `matrix[0, 0]` \* (x + `idx`) + `matrix[0, 1]` \* (y + `idy`) + `odx`
- Y = `matrix[1, 0]` \* (x + `idx`) + `matrix[1, 1]` \* (y + `idy`) + `ody`
where:
- x and y are the coordinates in input image.
- X and Y are the coordinates in output image.
- (0,0) is the upper left corner.
**Throws**:
- <code>Error</code> Invalid parameters
**Since**: 0.27.0
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| matrix | <code>Array.&lt;Array.&lt;number&gt;&gt;</code> \| <code>Array.&lt;number&gt;</code> | | affine transformation matrix |
| [options] | <code>Object</code> | | if present, is an Object with optional attributes. |
| [options.background] | <code>String</code> \| <code>Object</code> | <code>&quot;#000000&quot;</code> | parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. |
| [options.idx] | <code>Number</code> | <code>0</code> | input horizontal offset |
| [options.idy] | <code>Number</code> | <code>0</code> | input vertical offset |
| [options.odx] | <code>Number</code> | <code>0</code> | output horizontal offset |
| [options.ody] | <code>Number</code> | <code>0</code> | output vertical offset |
| [options.interpolator] | <code>String</code> | <code>sharp.interpolators.bicubic</code> | interpolator |
**Example**
```js
const pipeline = sharp()
.affine([[1, 0.3], [0.1, 0.7]], {
background: 'white',
interpolator: sharp.interpolators.nohalo
})
.toBuffer((err, outputBuffer, info) => {
// outputBuffer contains the transformed image
// info.width and info.height contain the new dimensions
});
inputStream
.pipe(pipeline);
```
## sharpen
> sharpen([options], [flat], [jagged]) ⇒ <code>Sharp</code>
Sharpen the image.
When used without parameters, performs a fast, mild sharpen of the output image.
When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space.
Separate control over the level of sharpening in "flat" and "jagged" areas is available.
Fine-grained control over the level of sharpening in "flat" (m1) and "jagged" (m2) areas is available.
### Parameters
- `sigma` **[Number][1]?** the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
- `flat` **[Number][1]** the level of sharpening to apply to "flat" areas. (optional, default `1.0`)
- `jagged` **[Number][1]** the level of sharpening to apply to "jagged" areas. (optional, default `2.0`)
See [libvips sharpen](https://www.libvips.org/API/current/libvips-convolution.html#vips-sharpen) operation.
- Throws **[Error][5]** Invalid parameters
**Throws**:
- <code>Error</code> Invalid parameters
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [options] | <code>Object</code> \| <code>number</code> | | if present, is an Object with attributes |
| [options.sigma] | <code>number</code> | | the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`, between 0.000001 and 10 |
| [options.m1] | <code>number</code> | <code>1.0</code> | the level of sharpening to apply to "flat" areas, between 0 and 1000000 |
| [options.m2] | <code>number</code> | <code>2.0</code> | the level of sharpening to apply to "jagged" areas, between 0 and 1000000 |
| [options.x1] | <code>number</code> | <code>2.0</code> | threshold between "flat" and "jagged", between 0 and 1000000 |
| [options.y2] | <code>number</code> | <code>10.0</code> | maximum amount of brightening, between 0 and 1000000 |
| [options.y3] | <code>number</code> | <code>20.0</code> | maximum amount of darkening, between 0 and 1000000 |
| [flat] | <code>number</code> | | (deprecated) see `options.m1`. |
| [jagged] | <code>number</code> | | (deprecated) see `options.m2`. |
**Example**
```js
const data = await sharp(input).sharpen().toBuffer();
```
**Example**
```js
const data = await sharp(input).sharpen({ sigma: 2 }).toBuffer();
```
**Example**
```js
const data = await sharp(input)
.sharpen({
sigma: 2,
m1: 0,
m2: 3,
x1: 3,
y2: 15,
y3: 15,
})
.toBuffer();
```
Returns **Sharp**
## median
> median([size]) ⇒ <code>Sharp</code>
Apply median filter.
When used without parameters the default window is 3x3.
### Parameters
- `size` **[Number][1]** square mask size: size x size (optional, default `3`)
**Throws**:
- <code>Error</code> Invalid parameters
- Throws **[Error][5]** Invalid parameters
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [size] | <code>number</code> | <code>3</code> | square mask size: size x size |
**Example**
```js
const output = await sharp(input).median().toBuffer();
```
**Example**
```js
const output = await sharp(input).median(5).toBuffer();
```
Returns **Sharp**
## blur
> blur([sigma]) ⇒ <code>Sharp</code>
Blur the image.
When used without parameters, performs a fast, mild blur of the output image.
When used without parameters, performs a fast 3x3 box blur (equivalent to a box linear filter).
When a `sigma` is provided, performs a slower, more accurate Gaussian blur.
### Parameters
- `sigma` **[Number][1]?** a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
**Throws**:
- <code>Error</code> Invalid parameters
- Throws **[Error][5]** Invalid parameters
| Param | Type | Description |
| --- | --- | --- |
| [sigma] | <code>number</code> | a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`. |
**Example**
```js
const boxBlurred = await sharp(input)
.blur()
.toBuffer();
```
**Example**
```js
const gaussianBlurred = await sharp(input)
.blur(5)
.toBuffer();
```
Returns **Sharp**
## flatten
> flatten([options]) ⇒ <code>Sharp</code>
Merge alpha transparency channel, if any, with a background.
Merge alpha transparency channel, if any, with a background, then remove the alpha channel.
### Parameters
See also [removeAlpha](/api-channel#removealpha).
- `options` **[Object][2]?**
- `options.background` **([String][3] \| [Object][2])** background colour, parsed by the [color][4] module, defaults to black. (optional, default `{r:0,g:0,b:0}`)
Returns **Sharp**
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [options] | <code>Object</code> | | |
| [options.background] | <code>string</code> \| <code>Object</code> | <code>&quot;{r: 0, g: 0, b: 0}&quot;</code> | background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black. |
**Example**
```js
await sharp(rgbaInput)
.flatten({ background: '#F0A703' })
.toBuffer();
```
## unflatten
> unflatten()
Ensure the image has an alpha channel
with all white pixel values made fully transparent.
Existing alpha channel values for non-white pixels remain unchanged.
This feature is experimental and the API may change.
**Since**: 0.32.1
**Example**
```js
await sharp(rgbInput)
.unflatten()
.toBuffer();
```
**Example**
```js
await sharp(rgbInput)
.threshold(128, { grayscale: false }) // converter bright pixels to white
.unflatten()
.toBuffer();
```
## gamma
> gamma([gamma], [gammaOut]) ⇒ <code>Sharp</code>
Apply a gamma correction by reducing the encoding (darken) pre-resize at a factor of `1/gamma`
then increasing the encoding (brighten) post-resize at a factor of `gamma`.
@@ -133,62 +321,153 @@ when applying a gamma correction.
Supply a second argument to use a different output gamma value, otherwise the first value is used in both cases.
### Parameters
- `gamma` **[Number][1]** value between 1.0 and 3.0. (optional, default `2.2`)
- `gammaOut` **[Number][1]?** value between 1.0 and 3.0. (optional, defaults to same as `gamma`)
**Throws**:
- <code>Error</code> Invalid parameters
- Throws **[Error][5]** Invalid parameters
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [gamma] | <code>number</code> | <code>2.2</code> | value between 1.0 and 3.0. |
| [gammaOut] | <code>number</code> | | value between 1.0 and 3.0. (optional, defaults to same as `gamma`) |
Returns **Sharp**
## negate
> negate([options]) ⇒ <code>Sharp</code>
Produce the "negative" of the image.
### Parameters
- `negate` **[Boolean][6]** (optional, default `true`)
Returns **Sharp**
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [options] | <code>Object</code> | | |
| [options.alpha] | <code>Boolean</code> | <code>true</code> | Whether or not to negate any alpha channel |
**Example**
```js
const output = await sharp(input)
.negate()
.toBuffer();
```
**Example**
```js
const output = await sharp(input)
.negate({ alpha: false })
.toBuffer();
```
## normalise
> normalise([options]) ⇒ <code>Sharp</code>
Enhance output image contrast by stretching its luminance to cover the full dynamic range.
Enhance output image contrast by stretching its luminance to cover a full dynamic range.
### Parameters
Uses a histogram-based approach, taking a default range of 1% to 99% to reduce sensitivity to noise at the extremes.
- `normalise` **[Boolean][6]** (optional, default `true`)
Luminance values below the `lower` percentile will be underexposed by clipping to zero.
Luminance values above the `upper` percentile will be overexposed by clipping to the max pixel value.
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [options] | <code>Object</code> | | |
| [options.lower] | <code>number</code> | <code>1</code> | Percentile below which luminance values will be underexposed. |
| [options.upper] | <code>number</code> | <code>99</code> | Percentile above which luminance values will be overexposed. |
**Example**
```js
const output = await sharp(input)
.normalise()
.toBuffer();
```
**Example**
```js
const output = await sharp(input)
.normalise({ lower: 0, upper: 100 })
.toBuffer();
```
Returns **Sharp**
## normalize
> normalize([options]) ⇒ <code>Sharp</code>
Alternative spelling of normalise.
### Parameters
- `normalize` **[Boolean][6]** (optional, default `true`)
Returns **Sharp**
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [options] | <code>Object</code> | | |
| [options.lower] | <code>number</code> | <code>1</code> | Percentile below which luminance values will be underexposed. |
| [options.upper] | <code>number</code> | <code>99</code> | Percentile above which luminance values will be overexposed. |
**Example**
```js
const output = await sharp(input)
.normalize()
.toBuffer();
```
## clahe
> clahe(options) ⇒ <code>Sharp</code>
Perform contrast limiting adaptive histogram equalization
[CLAHE](https://en.wikipedia.org/wiki/Adaptive_histogram_equalization#Contrast_Limited_AHE).
This will, in general, enhance the clarity of the image by bringing out darker details.
**Throws**:
- <code>Error</code> Invalid parameters
**Since**: 0.28.3
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| options | <code>Object</code> | | |
| options.width | <code>number</code> | | Integral width of the search window, in pixels. |
| options.height | <code>number</code> | | Integral height of the search window, in pixels. |
| [options.maxSlope] | <code>number</code> | <code>3</code> | Integral level of brightening, between 0 and 100, where 0 disables contrast limiting. |
**Example**
```js
const output = await sharp(input)
.clahe({
width: 3,
height: 3,
})
.toBuffer();
```
## convolve
> convolve(kernel) ⇒ <code>Sharp</code>
Convolve the image with the specified kernel.
### Parameters
- `kernel` **[Object][2]**
- `kernel.width` **[Number][1]** width of the kernel in pixels.
- `kernel.height` **[Number][1]** width of the kernel in pixels.
- `kernel.kernel` **[Array][7]&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.offset` **[Number][1]** the offset of the kernel in pixels. (optional, default `0`)
**Throws**:
### Examples
- <code>Error</code> Invalid parameters
```javascript
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| kernel | <code>Object</code> | | |
| kernel.width | <code>number</code> | | width of the kernel in pixels. |
| kernel.height | <code>number</code> | | height of the kernel in pixels. |
| kernel.kernel | <code>Array.&lt;number&gt;</code> | | Array of length `width*height` containing the kernel values. |
| [kernel.scale] | <code>number</code> | <code>sum</code> | the scale of the kernel in pixels. |
| [kernel.offset] | <code>number</code> | <code>0</code> | the offset of the kernel in pixels. |
**Example**
```js
sharp(input)
.convolve({
width: 3,
@@ -202,74 +481,107 @@ sharp(input)
});
```
- Throws **[Error][5]** Invalid parameters
Returns **Sharp**
## threshold
> threshold([threshold], [options]) ⇒ <code>Sharp</code>
Any pixel value greather than or equal to the threshold value will be set to 255, otherwise it will be set to 0.
### Parameters
- `threshold` **[Number][1]** a value in the range 0-255 representing the level at which the threshold will be applied. (optional, default `128`)
- `options` **[Object][2]?**
- `options.greyscale` **[Boolean][6]** convert to single channel greyscale. (optional, default `true`)
- `options.grayscale` **[Boolean][6]** alternative spelling for greyscale. (optional, default `true`)
Any pixel value greater than or equal to the threshold value will be set to 255, otherwise it will be set to 0.
- Throws **[Error][5]** Invalid parameters
**Throws**:
- <code>Error</code> Invalid parameters
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [threshold] | <code>number</code> | <code>128</code> | a value in the range 0-255 representing the level at which the threshold will be applied. |
| [options] | <code>Object</code> | | |
| [options.greyscale] | <code>Boolean</code> | <code>true</code> | convert to single channel greyscale. |
| [options.grayscale] | <code>Boolean</code> | <code>true</code> | alternative spelling for greyscale. |
Returns **Sharp**
## boolean
> boolean(operand, operator, [options]) ⇒ <code>Sharp</code>
Perform a bitwise boolean operation with operand image.
This operation creates an output image where each pixel is the result of
the selected bitwise boolean `operation` between the corresponding pixels of the input images.
### Parameters
- `operand` **([Buffer][8] \| [String][3])** Buffer containing image data or String containing the path to an image file.
- `operator` **[String][3]** one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
- `options` **[Object][2]?**
- `options.raw` **[Object][2]?** describes operand when using raw pixel data.
- `options.raw.width` **[Number][1]?**
- `options.raw.height` **[Number][1]?**
- `options.raw.channels` **[Number][1]?**
**Throws**:
- <code>Error</code> Invalid parameters
- Throws **[Error][5]** Invalid parameters
| Param | Type | Description |
| --- | --- | --- |
| operand | <code>Buffer</code> \| <code>string</code> | Buffer containing image data or string containing the path to an image file. |
| operator | <code>string</code> | one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively. |
| [options] | <code>Object</code> | |
| [options.raw] | <code>Object</code> | describes operand when using raw pixel data. |
| [options.raw.width] | <code>number</code> | |
| [options.raw.height] | <code>number</code> | |
| [options.raw.channels] | <code>number</code> | |
Returns **Sharp**
## linear
> linear([a], [b]) ⇒ <code>Sharp</code>
Apply the linear formula a \* input + b to the image (levels adjustment)
Apply the linear formula `a` * input + `b` to the image to adjust image levels.
### Parameters
- `a` **[Number][1]** multiplier (optional, default `1.0`)
- `b` **[Number][1]** offset (optional, default `0.0`)
When a single number is provided, it will be used for all image channels.
When an array of numbers is provided, the array length must match the number of channels.
- Throws **[Error][5]** Invalid parameters
**Throws**:
- <code>Error</code> Invalid parameters
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [a] | <code>number</code> \| <code>Array.&lt;number&gt;</code> | <code>[]</code> | multiplier |
| [b] | <code>number</code> \| <code>Array.&lt;number&gt;</code> | <code>[]</code> | offset |
**Example**
```js
await sharp(input)
.linear(0.5, 2)
.toBuffer();
```
**Example**
```js
await sharp(rgbInput)
.linear(
[0.25, 0.5, 0.75],
[150, 100, 50]
)
.toBuffer();
```
Returns **Sharp**
## recomb
> recomb(inputMatrix) ⇒ <code>Sharp</code>
Recomb the image with the specified matrix.
Recombine the image with the specified matrix.
### Parameters
- `inputMatrix`
- `3x3` **[Array][7]&lt;[Array][7]&lt;[Number][1]>>** Recombination matrix
**Throws**:
### Examples
- <code>Error</code> Invalid parameters
```javascript
**Since**: 0.21.1
| Param | Type | Description |
| --- | --- | --- |
| inputMatrix | <code>Array.&lt;Array.&lt;number&gt;&gt;</code> | 3x3 Recombination matrix |
**Example**
```js
sharp(input)
.recomb([
[0.3588, 0.7044, 0.1368],
@@ -278,62 +590,65 @@ sharp(input)
])
.raw()
.toBuffer(function(err, data, info) {
// data contains the raw pixel data after applying the recomb
// data contains the raw pixel data after applying the matrix
// With this example input, a sepia filter has been applied
});
```
- Throws **[Error][5]** Invalid parameters
Returns **Sharp**
## modulate
> modulate([options]) ⇒ <code>Sharp</code>
Transforms the image using brightness, saturation and hue rotation.
Transforms the image using brightness, saturation, hue rotation, and lightness.
Brightness and lightness both operate on luminance, with the difference being that
brightness is multiplicative whereas lightness is additive.
### Parameters
- `options` **[Object][2]?**
- `options.brightness` **[Number][1]?** Brightness multiplier
- `options.saturation` **[Number][1]?** Saturation multiplier
- `options.hue` **[Number][1]?** Degrees for hue rotation
**Since**: 0.22.1
### Examples
| Param | Type | Description |
| --- | --- | --- |
| [options] | <code>Object</code> | |
| [options.brightness] | <code>number</code> | Brightness multiplier |
| [options.saturation] | <code>number</code> | Saturation multiplier |
| [options.hue] | <code>number</code> | Degrees for hue rotation |
| [options.lightness] | <code>number</code> | Lightness addend |
```javascript
sharp(input)
**Example**
```js
// increase brightness by a factor of 2
const output = await sharp(input)
.modulate({
brightness: 2 // increase lightness by a factor of 2
});
sharp(input)
brightness: 2
})
.toBuffer();
```
**Example**
```js
// hue-rotate by 180 degrees
const output = await sharp(input)
.modulate({
hue: 180 // hue-rotate by 180 degrees
});
// decreate brightness and saturation while also hue-rotating by 90 degrees
sharp(input)
hue: 180
})
.toBuffer();
```
**Example**
```js
// increase lightness by +50
const output = await sharp(input)
.modulate({
lightness: 50
})
.toBuffer();
```
**Example**
```js
// decrease brightness and saturation while also hue-rotating by 90 degrees
const output = await sharp(input)
.modulate({
brightness: 0.5,
saturation: 0.5,
hue: 90
});
```
Returns **Sharp**
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[4]: https://www.npmjs.org/package/color
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[8]: https://nodejs.org/api/buffer.html
hue: 90,
})
.toBuffer();
```

View File

@@ -1,140 +1,232 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## toFile
> toFile(fileOut, [callback]) ⇒ <code>Promise.&lt;Object&gt;</code>
Write output image data to a file.
If an explicit output format is not selected, it will be inferred from the extension,
with JPEG, PNG, WebP, TIFF, DZI, and libvips' V format supported.
with JPEG, PNG, WebP, AVIF, TIFF, GIF, DZI, and libvips' V format supported.
Note that raw pixel data is only supported for buffer output.
By default all metadata will be removed, which includes EXIF-based orientation.
See [withMetadata][1] for control over this.
See [withMetadata](#withmetadata) for control over this.
The caller is responsible for ensuring directory structures and permissions exist.
A `Promise` is returned when `callback` is not provided.
### Parameters
- `fileOut` **[String][2]** the path to write the image data to.
- `callback` **[Function][3]?** called on completion with two arguments `(err, info)`.
`info` contains the output image `format`, `size` (bytes), `width`, `height`,
`channels` and `premultiplied` (indicating if premultiplication was used).
When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
**Returns**: <code>Promise.&lt;Object&gt;</code> - - when no callback is provided
**Throws**:
### Examples
- <code>Error</code> Invalid parameters
```javascript
| Param | Type | Description |
| --- | --- | --- |
| fileOut | <code>string</code> | the path to write the image data to. |
| [callback] | <code>function</code> | called on completion with two arguments `(err, info)`. `info` contains the output image `format`, `size` (bytes), `width`, `height`, `channels` and `premultiplied` (indicating if premultiplication was used). When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`. When using the attention crop strategy also contains `attentionX` and `attentionY`, the focal point of the cropped region. May also contain `textAutofitDpi` (dpi the font was rendered at) if image was created from text. |
**Example**
```js
sharp(input)
.toFile('output.png', (err, info) => { ... });
```
```javascript
**Example**
```js
sharp(input)
.toFile('output.png')
.then(info => { ... })
.catch(err => { ... });
```
- Throws **[Error][4]** Invalid parameters
Returns **[Promise][5]&lt;[Object][6]>** when no callback is provided
## toBuffer
> toBuffer([options], [callback]) ⇒ <code>Promise.&lt;Buffer&gt;</code>
Write output to a Buffer.
JPEG, PNG, WebP, TIFF and RAW output are supported.
JPEG, PNG, WebP, AVIF, TIFF, GIF 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.
Use [toFormat](#toformat) or one of the format-specific functions such as [jpeg](#jpeg), [png](#png) etc. to set the output format.
If no explicit format is set, the output format will match the input image, except SVG input which becomes PNG output.
By default all metadata will be removed, which includes EXIF-based orientation.
See [withMetadata][1] for control over this.
See [withMetadata](#withmetadata) for control over this.
`callback`, if present, gets three arguments `(err, data, info)` where:
- `err` is an error, if any.
- `data` is the output image data.
- `info` contains the output image `format`, `size` (bytes), `width`, `height`,
`channels` and `premultiplied` (indicating if premultiplication was used).
When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
- `err` is an error, if any.
- `data` is the output image data.
- `info` contains the output image `format`, `size` (bytes), `width`, `height`,
`channels` and `premultiplied` (indicating if premultiplication was used).
When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
May also contain `textAutofitDpi` (dpi the font was rendered at) if image was created from text.
A `Promise` is returned when `callback` is not provided.
### Parameters
- `options` **[Object][6]?**
- `options.resolveWithObject` **[Boolean][7]?** Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
- `callback` **[Function][3]?**
**Returns**: <code>Promise.&lt;Buffer&gt;</code> - - when no callback is provided
### Examples
| Param | Type | Description |
| --- | --- | --- |
| [options] | <code>Object</code> | |
| [options.resolveWithObject] | <code>boolean</code> | Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`. |
| [callback] | <code>function</code> | |
```javascript
**Example**
```js
sharp(input)
.toBuffer((err, data, info) => { ... });
```
```javascript
**Example**
```js
sharp(input)
.toBuffer()
.then(data => { ... })
.catch(err => { ... });
```
```javascript
**Example**
```js
sharp(input)
.png()
.toBuffer({ resolveWithObject: true })
.then(({ data, info }) => { ... })
.catch(err => { ... });
```
**Example**
```js
const { data, info } = await sharp('my-image.jpg')
// output the raw pixels
.raw()
.toBuffer({ resolveWithObject: true });
// create a more type safe way to work with the raw pixel data
// this will not copy the data, instead it will change `data`s underlying ArrayBuffer
// so `data` and `pixelArray` point to the same memory location
const pixelArray = new Uint8ClampedArray(data.buffer);
// When you are done changing the pixelArray, sharp takes the `pixelArray` as an input
const { width, height, channels } = info;
await sharp(pixelArray, { raw: { width, height, channels } })
.toFile('my-changed-image.jpg');
```
Returns **[Promise][5]&lt;[Buffer][8]>** when no callback is provided
## withMetadata
> withMetadata([options]) ⇒ <code>Sharp</code>
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.
This will also convert to and add a web-friendly sRGB ICC profile if appropriate,
unless a custom output profile is provided.
### Parameters
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.
- `options` **[Object][6]?**
- `options.orientation` **[Number][9]?** value between 1 and 8, used to update the EXIF `Orientation` tag.
EXIF metadata is unsupported for TIFF output.
### Examples
```javascript
**Throws**:
- <code>Error</code> Invalid parameters
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [options] | <code>Object</code> | | |
| [options.orientation] | <code>number</code> | | value between 1 and 8, used to update the EXIF `Orientation` tag. |
| [options.icc] | <code>string</code> | <code>&quot;&#x27;srgb&#x27;&quot;</code> | Filesystem path to output ICC profile, relative to `process.cwd()`, defaults to built-in sRGB. |
| [options.exif] | <code>Object.&lt;Object&gt;</code> | <code>{}</code> | Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data. |
| [options.density] | <code>number</code> | | Number of pixels per inch (DPI). |
**Example**
```js
sharp('input.jpg')
.withMetadata()
.toFile('output-with-metadata.jpg')
.then(info => { ... });
```
**Example**
```js
// Set output EXIF metadata
const data = await sharp(input)
.withMetadata({
exif: {
IFD0: {
Copyright: 'The National Gallery'
},
IFD3: {
GPSLatitudeRef: 'N',
GPSLatitude: '51/1 30/1 3230/100',
GPSLongitudeRef: 'W',
GPSLongitude: '0/1 7/1 4366/100'
}
}
})
.toBuffer();
```
**Example**
```js
// Set output metadata to 96 DPI
const data = await sharp(input)
.withMetadata({ density: 96 })
.toBuffer();
```
- Throws **[Error][4]** Invalid parameters
Returns **Sharp**
## toFormat
> toFormat(format, options) ⇒ <code>Sharp</code>
Force output to a given format.
**Throws**:
- <code>Error</code> unsupported format or options
| Param | Type | Description |
| --- | --- | --- |
| format | <code>string</code> \| <code>Object</code> | as a string or an Object with an 'id' attribute |
| options | <code>Object</code> | output options |
**Example**
```js
// Convert any input to PNG output
const data = await sharp(input)
.toFormat('png')
.toBuffer();
```
## jpeg
> jpeg([options]) ⇒ <code>Sharp</code>
Use these JPEG options for output image.
### Parameters
- `options` **[Object][6]?** output options
- `options.quality` **[Number][9]** quality, integer 1-100 (optional, default `80`)
- `options.progressive` **[Boolean][7]** use progressive (interlace) scan (optional, default `false`)
- `options.chromaSubsampling` **[String][2]** set to '4:4:4' to prevent chroma subsampling when quality &lt;= 90 (optional, default `'4:2:0'`)
- `options.trellisQuantisation` **[Boolean][7]** apply trellis quantisation, requires libvips compiled with support for mozjpeg (optional, default `false`)
- `options.overshootDeringing` **[Boolean][7]** apply overshoot deringing, requires libvips compiled with support for mozjpeg (optional, default `false`)
- `options.optimiseScans` **[Boolean][7]** optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg (optional, default `false`)
- `options.optimizeScans` **[Boolean][7]** alternative spelling of optimiseScans (optional, default `false`)
- `options.optimiseCoding` **[Boolean][7]** optimise Huffman coding tables (optional, default `true`)
- `options.optimizeCoding` **[Boolean][7]** alternative spelling of optimiseCoding (optional, default `true`)
- `options.quantisationTable` **[Number][9]** quantization table to use, integer 0-8, requires libvips compiled with support for mozjpeg (optional, default `0`)
- `options.quantizationTable` **[Number][9]** alternative spelling of quantisationTable (optional, default `0`)
- `options.force` **[Boolean][7]** force JPEG output, otherwise attempt to use input format (optional, default `true`)
**Throws**:
### Examples
- <code>Error</code> Invalid options
```javascript
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [options] | <code>Object</code> | | output options |
| [options.quality] | <code>number</code> | <code>80</code> | quality, integer 1-100 |
| [options.progressive] | <code>boolean</code> | <code>false</code> | use progressive (interlace) scan |
| [options.chromaSubsampling] | <code>string</code> | <code>&quot;&#x27;4:2:0&#x27;&quot;</code> | set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling |
| [options.optimiseCoding] | <code>boolean</code> | <code>true</code> | optimise Huffman coding tables |
| [options.optimizeCoding] | <code>boolean</code> | <code>true</code> | alternative spelling of optimiseCoding |
| [options.mozjpeg] | <code>boolean</code> | <code>false</code> | use mozjpeg defaults, equivalent to `{ trellisQuantisation: true, overshootDeringing: true, optimiseScans: true, quantisationTable: 3 }` |
| [options.trellisQuantisation] | <code>boolean</code> | <code>false</code> | apply trellis quantisation |
| [options.overshootDeringing] | <code>boolean</code> | <code>false</code> | apply overshoot deringing |
| [options.optimiseScans] | <code>boolean</code> | <code>false</code> | optimise progressive scans, forces progressive |
| [options.optimizeScans] | <code>boolean</code> | <code>false</code> | alternative spelling of optimiseScans |
| [options.quantisationTable] | <code>number</code> | <code>0</code> | quantization table to use, integer 0-8 |
| [options.quantizationTable] | <code>number</code> | <code>0</code> | alternative spelling of quantisationTable |
| [options.force] | <code>boolean</code> | <code>true</code> | force JPEG output, otherwise attempt to use input format |
**Example**
```js
// Convert any input to very high quality JPEG output
const data = await sharp(input)
.jpeg({
@@ -143,190 +235,428 @@ const data = await sharp(input)
})
.toBuffer();
```
**Example**
```js
// Use mozjpeg to reduce output JPEG file size (slower)
const data = await sharp(input)
.jpeg({ mozjpeg: true })
.toBuffer();
```
- Throws **[Error][4]** Invalid options
Returns **Sharp**
## png
> png([options]) ⇒ <code>Sharp</code>
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.
Set `palette` to `true` for slower, indexed PNG output.
### Parameters
- `options` **[Object][6]?**
- `options.progressive` **[Boolean][7]** use progressive (interlace) scan (optional, default `false`)
- `options.compressionLevel` **[Number][9]** zlib compression level, 0-9 (optional, default `9`)
- `options.adaptiveFiltering` **[Boolean][7]** use adaptive row filtering (optional, default `false`)
- `options.palette` **[Boolean][7]** quantise to a palette-based image with alpha transparency support, requires libvips compiled with support for libimagequant (optional, default `false`)
- `options.quality` **[Number][9]** use the lowest number of colours needed to achieve given quality, requires libvips compiled with support for libimagequant (optional, default `100`)
- `options.colours` **[Number][9]** maximum number of palette entries, requires libvips compiled with support for libimagequant (optional, default `256`)
- `options.colors` **[Number][9]** alternative spelling of `options.colours`, requires libvips compiled with support for libimagequant (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.force` **[Boolean][7]** force PNG output, otherwise attempt to use input format (optional, default `true`)
**Throws**:
### Examples
- <code>Error</code> Invalid options
```javascript
// Convert any input to PNG output
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [options] | <code>Object</code> | | |
| [options.progressive] | <code>boolean</code> | <code>false</code> | use progressive (interlace) scan |
| [options.compressionLevel] | <code>number</code> | <code>6</code> | zlib compression level, 0 (fastest, largest) to 9 (slowest, smallest) |
| [options.adaptiveFiltering] | <code>boolean</code> | <code>false</code> | use adaptive row filtering |
| [options.palette] | <code>boolean</code> | <code>false</code> | quantise to a palette-based image with alpha transparency support |
| [options.quality] | <code>number</code> | <code>100</code> | use the lowest number of colours needed to achieve given quality, sets `palette` to `true` |
| [options.effort] | <code>number</code> | <code>7</code> | CPU effort, between 1 (fastest) and 10 (slowest), sets `palette` to `true` |
| [options.colours] | <code>number</code> | <code>256</code> | maximum number of palette entries, sets `palette` to `true` |
| [options.colors] | <code>number</code> | <code>256</code> | alternative spelling of `options.colours`, sets `palette` to `true` |
| [options.dither] | <code>number</code> | <code>1.0</code> | level of Floyd-Steinberg error diffusion, sets `palette` to `true` |
| [options.force] | <code>boolean</code> | <code>true</code> | force PNG output, otherwise attempt to use input format |
**Example**
```js
// Convert any input to full colour PNG output
const data = await sharp(input)
.png()
.toBuffer();
```
**Example**
```js
// Convert any input to indexed PNG output (slower)
const data = await sharp(input)
.png({ palette: true })
.toBuffer();
```
- Throws **[Error][4]** Invalid options
Returns **Sharp**
## webp
> webp([options]) ⇒ <code>Sharp</code>
Use these WebP options for output image.
### Parameters
- `options` **[Object][6]?** output options
- `options.quality` **[Number][9]** quality, integer 1-100 (optional, default `80`)
- `options.alphaQuality` **[Number][9]** quality of alpha layer, integer 0-100 (optional, default `100`)
- `options.lossless` **[Boolean][7]** use lossless compression mode (optional, default `false`)
- `options.nearLossless` **[Boolean][7]** use near_lossless compression mode (optional, default `false`)
- `options.smartSubsample` **[Boolean][7]** use high quality chroma subsampling (optional, default `false`)
- `options.reductionEffort` **[Number][9]** level of CPU effort to reduce file size, integer 0-6 (optional, default `4`)
- `options.force` **[Boolean][7]** force WebP output, otherwise attempt to use input format (optional, default `true`)
**Throws**:
### Examples
- <code>Error</code> Invalid options
```javascript
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [options] | <code>Object</code> | | output options |
| [options.quality] | <code>number</code> | <code>80</code> | quality, integer 1-100 |
| [options.alphaQuality] | <code>number</code> | <code>100</code> | quality of alpha layer, integer 0-100 |
| [options.lossless] | <code>boolean</code> | <code>false</code> | use lossless compression mode |
| [options.nearLossless] | <code>boolean</code> | <code>false</code> | use near_lossless compression mode |
| [options.smartSubsample] | <code>boolean</code> | <code>false</code> | use high quality chroma subsampling |
| [options.preset] | <code>string</code> | <code>&quot;&#x27;default&#x27;&quot;</code> | named preset for preprocessing/filtering, one of: default, photo, picture, drawing, icon, text |
| [options.effort] | <code>number</code> | <code>4</code> | CPU effort, between 0 (fastest) and 6 (slowest) |
| [options.loop] | <code>number</code> | <code>0</code> | number of animation iterations, use 0 for infinite animation |
| [options.delay] | <code>number</code> \| <code>Array.&lt;number&gt;</code> | | delay(s) between animation frames (in milliseconds) |
| [options.minSize] | <code>boolean</code> | <code>false</code> | prevent use of animation key frames to minimise file size (slow) |
| [options.mixed] | <code>boolean</code> | <code>false</code> | allow mixture of lossy and lossless animation frames (slow) |
| [options.force] | <code>boolean</code> | <code>true</code> | force WebP output, otherwise attempt to use input format |
**Example**
```js
// Convert any input to lossless WebP output
const data = await sharp(input)
.webp({ lossless: true })
.toBuffer();
```
**Example**
```js
// Optimise the file size of an animated WebP
const outputWebp = await sharp(inputWebp, { animated: true })
.webp({ effort: 6 })
.toBuffer();
```
- Throws **[Error][4]** Invalid options
Returns **Sharp**
## gif
> gif([options]) ⇒ <code>Sharp</code>
Use these GIF options for the output image.
The first entry in the palette is reserved for transparency.
The palette of the input image will be re-used if possible.
**Throws**:
- <code>Error</code> Invalid options
**Since**: 0.30.0
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [options] | <code>Object</code> | | output options |
| [options.reuse] | <code>boolean</code> | <code>true</code> | re-use existing palette, otherwise generate new (slow) |
| [options.progressive] | <code>boolean</code> | <code>false</code> | use progressive (interlace) scan |
| [options.colours] | <code>number</code> | <code>256</code> | maximum number of palette entries, including transparency, between 2 and 256 |
| [options.colors] | <code>number</code> | <code>256</code> | alternative spelling of `options.colours` |
| [options.effort] | <code>number</code> | <code>7</code> | CPU effort, between 1 (fastest) and 10 (slowest) |
| [options.dither] | <code>number</code> | <code>1.0</code> | level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most) |
| [options.interFrameMaxError] | <code>number</code> | <code>0</code> | maximum inter-frame error for transparency, between 0 (lossless) and 32 |
| [options.interPaletteMaxError] | <code>number</code> | <code>3</code> | maximum inter-palette error for palette reuse, between 0 and 256 |
| [options.loop] | <code>number</code> | <code>0</code> | number of animation iterations, use 0 for infinite animation |
| [options.delay] | <code>number</code> \| <code>Array.&lt;number&gt;</code> | | delay(s) between animation frames (in milliseconds) |
| [options.force] | <code>boolean</code> | <code>true</code> | force GIF output, otherwise attempt to use input format |
**Example**
```js
// Convert PNG to GIF
await sharp(pngBuffer)
.gif()
.toBuffer();
```
**Example**
```js
// Convert animated WebP to animated GIF
await sharp('animated.webp', { animated: true })
.toFile('animated.gif');
```
**Example**
```js
// Create a 128x128, cropped, non-dithered, animated thumbnail of an animated GIF
const out = await sharp('in.gif', { animated: true })
.resize({ width: 128, height: 128 })
.gif({ dither: 0 })
.toBuffer();
```
**Example**
```js
// Lossy file size reduction of animated GIF
await sharp('in.gif', { animated: true })
.gif({ interFrameMaxError: 8 })
.toFile('optim.gif');
```
## jp2
> jp2([options]) ⇒ <code>Sharp</code>
Use these JP2 options for output image.
Requires libvips compiled with support for OpenJPEG.
The prebuilt binaries do not include this - see
[installing a custom libvips](https://sharp.pixelplumbing.com/install#custom-libvips).
**Throws**:
- <code>Error</code> Invalid options
**Since**: 0.29.1
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [options] | <code>Object</code> | | output options |
| [options.quality] | <code>number</code> | <code>80</code> | quality, integer 1-100 |
| [options.lossless] | <code>boolean</code> | <code>false</code> | use lossless compression mode |
| [options.tileWidth] | <code>number</code> | <code>512</code> | horizontal tile size |
| [options.tileHeight] | <code>number</code> | <code>512</code> | vertical tile size |
| [options.chromaSubsampling] | <code>string</code> | <code>&quot;&#x27;4:4:4&#x27;&quot;</code> | set to '4:2:0' to use chroma subsampling |
**Example**
```js
// Convert any input to lossless JP2 output
const data = await sharp(input)
.jp2({ lossless: true })
.toBuffer();
```
**Example**
```js
// Convert any input to very high quality JP2 output
const data = await sharp(input)
.jp2({
quality: 100,
chromaSubsampling: '4:4:4'
})
.toBuffer();
```
## tiff
> tiff([options]) ⇒ <code>Sharp</code>
Use these TIFF options for output image.
### Parameters
The `density` can be set in pixels/inch via [withMetadata](#withmetadata)
instead of providing `xres` and `yres` in pixels/mm.
- `options` **[Object][6]?** output options
- `options.quality` **[Number][9]** quality, integer 1-100 (optional, default `80`)
- `options.force` **[Boolean][7]** force TIFF output, otherwise attempt to use input format (optional, default `true`)
- `options.compression` **[Boolean][7]** compression options: lzw, deflate, jpeg, ccittfax4 (optional, default `'jpeg'`)
- `options.predictor` **[Boolean][7]** compression predictor options: none, horizontal, float (optional, default `'horizontal'`)
- `options.pyramid` **[Boolean][7]** write an image pyramid (optional, default `false`)
- `options.tile` **[Boolean][7]** write a tiled tiff (optional, default `false`)
- `options.tileWidth` **[Boolean][7]** horizontal tile size (optional, default `256`)
- `options.tileHeight` **[Boolean][7]** vertical tile size (optional, default `256`)
- `options.xres` **[Number][9]** horizontal resolution in pixels/mm (optional, default `1.0`)
- `options.yres` **[Number][9]** vertical resolution in pixels/mm (optional, default `1.0`)
- `options.squash` **[Boolean][7]** squash 8-bit images down to 1 bit (optional, default `false`)
### Examples
**Throws**:
```javascript
- <code>Error</code> Invalid options
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [options] | <code>Object</code> | | output options |
| [options.quality] | <code>number</code> | <code>80</code> | quality, integer 1-100 |
| [options.force] | <code>boolean</code> | <code>true</code> | force TIFF output, otherwise attempt to use input format |
| [options.compression] | <code>string</code> | <code>&quot;&#x27;jpeg&#x27;&quot;</code> | compression options: none, jpeg, deflate, packbits, ccittfax4, lzw, webp, zstd, jp2k |
| [options.predictor] | <code>string</code> | <code>&quot;&#x27;horizontal&#x27;&quot;</code> | compression predictor options: none, horizontal, float |
| [options.pyramid] | <code>boolean</code> | <code>false</code> | write an image pyramid |
| [options.tile] | <code>boolean</code> | <code>false</code> | write a tiled tiff |
| [options.tileWidth] | <code>number</code> | <code>256</code> | horizontal tile size |
| [options.tileHeight] | <code>number</code> | <code>256</code> | vertical tile size |
| [options.xres] | <code>number</code> | <code>1.0</code> | horizontal resolution in pixels/mm |
| [options.yres] | <code>number</code> | <code>1.0</code> | vertical resolution in pixels/mm |
| [options.resolutionUnit] | <code>string</code> | <code>&quot;&#x27;inch&#x27;&quot;</code> | resolution unit options: inch, cm |
| [options.bitdepth] | <code>number</code> | <code>8</code> | reduce bitdepth to 1, 2 or 4 bit |
**Example**
```js
// Convert SVG input to LZW-compressed, 1 bit per pixel TIFF output
sharp('input.svg')
.tiff({
compression: 'lzw',
squash: true
bitdepth: 1
})
.toFile('1-bpp-output.tiff')
.then(info => { ... });
```
- Throws **[Error][4]** Invalid options
Returns **Sharp**
## avif
> avif([options]) ⇒ <code>Sharp</code>
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.
AVIF image sequences are not supported.
**Throws**:
- <code>Error</code> Invalid options
**Since**: 0.27.0
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [options] | <code>Object</code> | | output options |
| [options.quality] | <code>number</code> | <code>50</code> | quality, integer 1-100 |
| [options.lossless] | <code>boolean</code> | <code>false</code> | use lossless compression |
| [options.effort] | <code>number</code> | <code>4</code> | CPU effort, between 0 (fastest) and 9 (slowest) |
| [options.chromaSubsampling] | <code>string</code> | <code>&quot;&#x27;4:4:4&#x27;&quot;</code> | set to '4:2:0' to use chroma subsampling |
**Example**
```js
const data = await sharp(input)
.avif({ effort: 2 })
.toBuffer();
```
**Example**
```js
const data = await sharp(input)
.avif({ lossless: true })
.toBuffer();
```
## heif
> heif([options]) ⇒ <code>Sharp</code>
Use these HEIF options for output image.
Support for HEIF (HEIC/AVIF) is experimental.
Do not use this in production systems.
Requires a custom, globally-installed libvips compiled with support for libheif.
Most versions of libheif support only the patent-encumbered HEVC compression format.
### Parameters
- `options` **[Object][6]?** output options
- `options.quality` **[Number][9]** quality, integer 1-100 (optional, default `80`)
- `options.compression` **[Boolean][7]** compression format: hevc, avc, jpeg, av1 (optional, default `'hevc'`)
- `options.lossless` **[Boolean][7]** use lossless compression (optional, default `false`)
Support for patent-encumbered HEIC images using `hevc` compression requires the use of a
globally-installed libvips compiled with support for libheif, libde265 and x265.
- Throws **[Error][4]** Invalid options
**Throws**:
- <code>Error</code> Invalid options
**Since**: 0.23.0
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [options] | <code>Object</code> | | output options |
| [options.quality] | <code>number</code> | <code>50</code> | quality, integer 1-100 |
| [options.compression] | <code>string</code> | <code>&quot;&#x27;av1&#x27;&quot;</code> | compression format: av1, hevc |
| [options.lossless] | <code>boolean</code> | <code>false</code> | use lossless compression |
| [options.effort] | <code>number</code> | <code>4</code> | CPU effort, between 0 (fastest) and 9 (slowest) |
| [options.chromaSubsampling] | <code>string</code> | <code>&quot;&#x27;4:4:4&#x27;&quot;</code> | set to '4:2:0' to use chroma subsampling |
**Example**
```js
const data = await sharp(input)
.heif({ compression: 'hevc' })
.toBuffer();
```
## jxl
> jxl([options]) ⇒ <code>Sharp</code>
Use these JPEG-XL (JXL) options for output image.
This feature is experimental, please do not use in production systems.
Requires libvips compiled with support for libjxl.
The prebuilt binaries do not include this - see
[installing a custom libvips](https://sharp.pixelplumbing.com/install#custom-libvips).
Image metadata (EXIF, XMP) is unsupported.
**Throws**:
- <code>Error</code> Invalid options
**Since**: 0.31.3
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [options] | <code>Object</code> | | output options |
| [options.distance] | <code>number</code> | <code>1.0</code> | maximum encoding error, between 0 (highest quality) and 15 (lowest quality) |
| [options.quality] | <code>number</code> | | calculate `distance` based on JPEG-like quality, between 1 and 100, overrides distance if specified |
| [options.decodingTier] | <code>number</code> | <code>0</code> | target decode speed tier, between 0 (highest quality) and 4 (lowest quality) |
| [options.lossless] | <code>boolean</code> | <code>false</code> | use lossless compression |
| [options.effort] | <code>number</code> | <code>7</code> | CPU effort, between 3 (fastest) and 9 (slowest) |
Returns **Sharp**
## raw
> raw([options]) ⇒ <code>Sharp</code>
Force output to be raw, uncompressed uint8 pixel data.
Force output to be raw, uncompressed pixel data.
Pixel ordering is left-to-right, top-to-bottom, without padding.
Channel ordering will be RGB or RGBA for non-greyscale colourspaces.
### Examples
```javascript
// Extract raw RGB pixel data from JPEG input
**Throws**:
- <code>Error</code> Invalid options
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [options] | <code>Object</code> | | output options |
| [options.depth] | <code>string</code> | <code>&quot;&#x27;uchar&#x27;&quot;</code> | bit depth, one of: char, uchar (default), short, ushort, int, uint, float, complex, double, dpcomplex |
**Example**
```js
// Extract raw, unsigned 8-bit RGB pixel data from JPEG input
const { data, info } = await sharp('input.jpg')
.raw()
.toBuffer({ resolveWithObject: true });
```
Returns **Sharp**
## toFormat
Force output to a given format.
### Parameters
- `format` **([String][2] \| [Object][6])** as a String or an Object with an 'id' attribute
- `options` **[Object][6]** output options
### Examples
```javascript
// Convert any input to PNG output
const data = await sharp(input)
.toFormat('png')
**Example**
```js
// Extract alpha channel as raw, unsigned 16-bit pixel data from PNG input
const data = await sharp('input.png')
.ensureAlpha()
.extractChannel(3)
.toColourspace('b-w')
.raw({ depth: 'ushort' })
.toBuffer();
```
- Throws **[Error][4]** unsupported format or options
Returns **Sharp**
## tile
> tile([options]) ⇒ <code>Sharp</code>
Use tile-based deep zoom (image pyramid) output.
Set the format and options for tile images via the `toFormat`, `jpeg`, `png` or `webp` functions.
Use a `.zip` or `.szi` file extension with `toFile` to write to a compressed archive file format.
Warning: multiple sharp instances concurrently producing tile output can expose a possible race condition in some versions of libgsf.
The container will be set to `zip` when the output is a Buffer or Stream, otherwise it will default to `fs`.
### Parameters
Requires libvips compiled with support for libgsf.
The prebuilt binaries do not include this - see
[installing a custom libvips](https://sharp.pixelplumbing.com/install#custom-libvips).
- `options` **[Object][6]?**
- `options.size` **[Number][9]** tile size in pixels, a value between 1 and 8192. (optional, default `256`)
- `options.overlap` **[Number][9]** tile overlap in pixels, a value between 0 and 8192. (optional, default `0`)
- `options.angle` **[Number][9]** tile angle of rotation, must be a multiple of 90. (optional, default `0`)
- `options.depth` **[String][2]?** how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
- `options.skipBlanks` **[Number][9]** threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images (optional, default `-1`)
- `options.container` **[String][2]** tile container, with value `fs` (filesystem) or `zip` (compressed file). (optional, default `'fs'`)
- `options.layout` **[String][2]** filesystem layout, possible values are `dz`, `zoomify` or `google`. (optional, default `'dz'`)
### Examples
**Throws**:
```javascript
- <code>Error</code> Invalid parameters
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [options] | <code>Object</code> | | |
| [options.size] | <code>number</code> | <code>256</code> | tile size in pixels, a value between 1 and 8192. |
| [options.overlap] | <code>number</code> | <code>0</code> | tile overlap in pixels, a value between 0 and 8192. |
| [options.angle] | <code>number</code> | <code>0</code> | tile angle of rotation, must be a multiple of 90. |
| [options.background] | <code>string</code> \| <code>Object</code> | <code>&quot;{r: 255, g: 255, b: 255, alpha: 1}&quot;</code> | background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to white without transparency. |
| [options.depth] | <code>string</code> | | how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout. |
| [options.skipBlanks] | <code>number</code> | <code>-1</code> | threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images |
| [options.container] | <code>string</code> | <code>&quot;&#x27;fs&#x27;&quot;</code> | tile container, with value `fs` (filesystem) or `zip` (compressed file). |
| [options.layout] | <code>string</code> | <code>&quot;&#x27;dz&#x27;&quot;</code> | filesystem layout, possible values are `dz`, `iiif`, `iiif3`, `zoomify` or `google`. |
| [options.centre] | <code>boolean</code> | <code>false</code> | centre image in tile. |
| [options.center] | <code>boolean</code> | <code>false</code> | alternative spelling of centre. |
| [options.id] | <code>string</code> | <code>&quot;&#x27;https://example.com/iiif&#x27;&quot;</code> | when `layout` is `iiif`/`iiif3`, sets the `@id`/`id` attribute of `info.json` |
| [options.basename] | <code>string</code> | | the name of the directory within the zip file when container is `zip`. |
**Example**
```js
sharp('input.tiff')
.png()
.tile({
@@ -337,25 +667,47 @@ sharp('input.tiff')
// output_files contains 512x512 tiles grouped by zoom level
});
```
**Example**
```js
const zipFileWithTiles = await sharp(input)
.tile({ basename: "tiles" })
.toBuffer();
```
**Example**
```js
const iiififier = sharp().tile({ layout: "iiif" });
readableStream
.pipe(iiififier)
.pipe(writeableStream);
```
- Throws **[Error][4]** Invalid parameters
Returns **Sharp**
## timeout
> timeout(options) ⇒ <code>Sharp</code>
[1]: #withmetadata
Set a timeout for processing, in seconds.
Use a value of zero to continue processing indefinitely, the default behaviour.
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
The clock starts when libvips opens an input image for processing.
Time spent waiting for a libuv thread to become available is not included.
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
**Since**: 0.29.2
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise
| Param | Type | Description |
| --- | --- | --- |
| options | <code>Object</code> | |
| options.seconds | <code>number</code> | Number of seconds after which processing will be stopped |
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[8]: https://nodejs.org/api/buffer.html
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
**Example**
```js
// Ensure processing takes no longer than 3 seconds
try {
const data = await sharp(input)
.blur(1000)
.timeout({ seconds: 3 })
.toBuffer();
} catch (err) {
if (err.message.includes('timeout')) { ... }
}
```

View File

@@ -1,56 +1,64 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## resize
> resize([width], [height], [options]) ⇒ <code>Sharp</code>
Resize image to `width`, `height` or `width x height`.
When both a `width` and `height` are provided, the possible methods by which the image should **fit** these are:
- `cover`: (default) Preserving aspect ratio, attempt to ensure the image covers both provided dimensions by cropping/clipping to fit.
- `contain`: Preserving aspect ratio, contain within both provided dimensions using "letterboxing" where necessary.
- `fill`: Ignore the aspect ratio of the input and stretch to both provided dimensions.
- `inside`: Preserving aspect ratio, resize the image to be as large as possible while ensuring its dimensions are less than or equal to both those specified.
- `outside`: Preserving aspect ratio, resize the image to be as small as possible while ensuring its dimensions are greater than or equal to both those specified.
- `cover`: Crop to cover both provided dimensions (the default).
- `contain`: Embed within 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.
- `outside`: Preserving aspect ratio, resize the image to be as small as possible while ensuring its dimensions are greater than or equal to both those specified.
Some of these values are based on the [object-fit][1] CSS property.
Some of these values are based on the [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) CSS property.
When using a `fit` of `cover` or `contain`, the default **position** is `centre`. Other options are:
<img alt="Examples of various values for the fit property when resizing" width="100%" style="aspect-ratio: 998/243" src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/api-resize-fit.svg">
- `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`.
- `sharp.gravity`: `north`, `northeast`, `east`, `southeast`, `south`, `southwest`, `west`, `northwest`, `center` or `centre`.
- `sharp.strategy`: `cover` only, dynamically crop using either the `entropy` or `attention` strategy.
Some of these values are based on the [object-position][2] CSS property.
When using a **fit** of `cover` or `contain`, the default **position** is `centre`. Other options are:
- `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`.
- `sharp.gravity`: `north`, `northeast`, `east`, `southeast`, `south`, `southwest`, `west`, `northwest`, `center` or `centre`.
- `sharp.strategy`: `cover` only, dynamically crop using either the `entropy` or `attention` strategy.
Some of these values are based on the [object-position](https://developer.mozilla.org/en-US/docs/Web/CSS/object-position) CSS property.
The experimental strategy-based approach resizes so one dimension is at its target length
then repeatedly ranks edge regions, discarding the edge with the lowest score based on the selected strategy.
- `entropy`: focus on the region with the highest [Shannon entropy][3].
- `attention`: focus on the region with the highest luminance frequency, colour saturation and presence of skin tones.
- `entropy`: focus on the region with the highest [Shannon entropy](https://en.wikipedia.org/wiki/Entropy_%28information_theory%29).
- `attention`: focus on the region with the highest luminance frequency, colour saturation and presence of skin tones.
Possible interpolation kernels are:
- `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation).
- `cubic`: Use a [Catmull-Rom spline](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline).
- `mitchell`: Use a [Mitchell-Netravali spline](https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf).
- `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`.
- `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
- `nearest`: Use [nearest neighbour interpolation][4].
- `cubic`: Use a [Catmull-Rom spline][5].
- `mitchell`: Use a [Mitchell-Netravali spline][6].
- `lanczos2`: Use a [Lanczos kernel][7] with `a=2`.
- `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
Only one resize can occur per pipeline.
Previous calls to `resize` in the same pipeline will be ignored.
### Parameters
- `width` **[Number][8]?** pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height.
- `height` **[Number][8]?** pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
- `options` **[Object][9]?**
- `options.width` **[String][10]?** alternative means of specifying `width`. If both are present this take priority.
- `options.height` **[String][10]?** alternative means of specifying `height`. If both are present this take priority.
- `options.fit` **[String][10]** how the image should be resized to fit both provided dimensions, one of `cover`, `contain`, `fill`, `inside` or `outside`. (optional, default `'cover'`)
- `options.position` **[String][10]** position, gravity or strategy to use when `fit` is `cover` or `contain`. (optional, default `'centre'`)
- `options.background` **([String][10] \| [Object][9])** background colour when using a `fit` of `contain`, parsed by the [color][11] module, defaults to black without transparency. (optional, default `{r:0,g:0,b:0,alpha:1}`)
- `options.kernel` **[String][10]** the kernel to use for image reduction. (optional, default `'lanczos3'`)
- `options.withoutEnlargement` **[Boolean][12]** do not enlarge if the width _or_ height are already less than the specified dimensions, equivalent to GraphicsMagick's `>` geometry option. (optional, default `false`)
- `options.fastShrinkOnLoad` **[Boolean][12]** take greater advantage of the JPEG and WebP shrink-on-load feature, which can lead to a slight moiré pattern on some images. (optional, default `true`)
**Throws**:
### Examples
- <code>Error</code> Invalid parameters
```javascript
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [width] | <code>number</code> | | How many pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height. |
| [height] | <code>number</code> | | How many pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width. |
| [options] | <code>Object</code> | | |
| [options.width] | <code>number</code> | | An alternative means of specifying `width`. If both are present this takes priority. |
| [options.height] | <code>number</code> | | An alternative means of specifying `height`. If both are present this takes priority. |
| [options.fit] | <code>String</code> | <code>&#x27;cover&#x27;</code> | How the image should be resized/cropped to fit the target dimension(s), one of `cover`, `contain`, `fill`, `inside` or `outside`. |
| [options.position] | <code>String</code> | <code>&#x27;centre&#x27;</code> | A position, gravity or strategy to use when `fit` is `cover` or `contain`. |
| [options.background] | <code>String</code> \| <code>Object</code> | <code>{r: 0, g: 0, b: 0, alpha: 1}</code> | background colour when `fit` is `contain`, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency. |
| [options.kernel] | <code>String</code> | <code>&#x27;lanczos3&#x27;</code> | The kernel to use for image reduction. Use the `fastShrinkOnLoad` option to control kernel vs shrink-on-load. |
| [options.withoutEnlargement] | <code>Boolean</code> | <code>false</code> | Do not scale up if the width *or* height are already less than the target dimensions, equivalent to GraphicsMagick's `>` geometry option. This may result in output dimensions smaller than the target dimensions. |
| [options.withoutReduction] | <code>Boolean</code> | <code>false</code> | Do not scale down if the width *or* height are already greater than the target dimensions, equivalent to GraphicsMagick's `<` geometry option. This may still result in a crop to reach the target dimensions. |
| [options.fastShrinkOnLoad] | <code>Boolean</code> | <code>true</code> | Take greater advantage of the JPEG and WebP shrink-on-load feature, which can lead to a slight moiré pattern or round-down of an auto-scaled dimension. |
**Example**
```js
sharp(input)
.resize({ width: 100 })
.toBuffer()
@@ -58,8 +66,8 @@ sharp(input)
// 100 pixels wide, auto-scaled height
});
```
```javascript
**Example**
```js
sharp(input)
.resize({ height: 100 })
.toBuffer()
@@ -67,8 +75,8 @@ sharp(input)
// 100 pixels high, auto-scaled width
});
```
```javascript
**Example**
```js
sharp(input)
.resize(200, 300, {
kernel: sharp.kernel.nearest,
@@ -83,8 +91,8 @@ sharp(input)
// contained within the north-east corner of a semi-transparent white canvas
});
```
```javascript
**Example**
```js
const transformer = sharp()
.resize({
width: 200,
@@ -98,8 +106,8 @@ readableStream
.pipe(transformer)
.pipe(writableStream);
```
```javascript
**Example**
```js
sharp(input)
.resize(200, 200, {
fit: sharp.fit.inside,
@@ -113,28 +121,57 @@ sharp(input)
// and no larger than the input image
});
```
**Example**
```js
sharp(input)
.resize(200, 200, {
fit: sharp.fit.outside,
withoutReduction: true
})
.toFormat('jpeg')
.toBuffer()
.then(function(outputBuffer) {
// outputBuffer contains JPEG image data
// of at least 200 pixels wide and 200 pixels high while maintaining aspect ratio
// and no smaller than the input image
});
```
**Example**
```js
const scaleByHalf = await sharp(input)
.metadata()
.then(({ width }) => sharp(input)
.resize(Math.round(width * 0.5))
.toBuffer()
);
```
- Throws **[Error][13]** Invalid parameters
Returns **Sharp**
## extend
> extend(extend) ⇒ <code>Sharp</code>
Extends/pads the edges of the image with the provided background colour.
Extend / pad / extrude one or more edges of the image with either
the provided background colour or pixels derived from the image.
This operation will always occur after resizing and extraction, if any.
### Parameters
- `extend` **([Number][8] \| [Object][9])** single pixel count to add to all edges or an Object with per-edge counts
- `extend.top` **[Number][8]?**
- `extend.left` **[Number][8]?**
- `extend.bottom` **[Number][8]?**
- `extend.right` **[Number][8]?**
- `extend.background` **([String][10] \| [Object][9])** background colour, parsed by the [color][11] module, defaults to black without transparency. (optional, default `{r:0,g:0,b:0,alpha:1}`)
**Throws**:
### Examples
- <code>Error</code> Invalid parameters
```javascript
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| extend | <code>number</code> \| <code>Object</code> | | single pixel count to add to all edges or an Object with per-edge counts |
| [extend.top] | <code>number</code> | <code>0</code> | |
| [extend.left] | <code>number</code> | <code>0</code> | |
| [extend.bottom] | <code>number</code> | <code>0</code> | |
| [extend.right] | <code>number</code> | <code>0</code> | |
| [extend.extendWith] | <code>String</code> | <code>&#x27;background&#x27;</code> | populate new pixels using this method, one of: background, copy, repeat, mirror. |
| [extend.background] | <code>String</code> \| <code>Object</code> | <code>{r: 0, g: 0, b: 0, alpha: 1}</code> | background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency. |
**Example**
```js
// Resize to 140 pixels wide, then add 10 transparent pixels
// to the top, left and right edges and 20 to the bottom edge
sharp(input)
@@ -148,38 +185,61 @@ sharp(input)
})
...
```
**Example**
```js
// Add a row of 10 red pixels to the bottom
sharp(input)
.extend({
bottom: 10,
background: 'red'
})
...
```
**Example**
```js
// Extrude image by 8 pixels to the right, mirroring existing right hand edge
sharp(input)
.extend({
right: 8,
background: 'mirror'
})
...
```
- Throws **[Error][13]** Invalid parameters
Returns **Sharp**
## extract
> extract(options) ⇒ <code>Sharp</code>
Extract a region of the image.
Extract/crop a region of the image.
- Use `extract` before `resize` for pre-resize extraction.
- Use `extract` after `resize` for post-resize extraction.
- Use `extract` before and after for both.
- Use `extract` before `resize` for pre-resize extraction.
- Use `extract` after `resize` for post-resize extraction.
- Use `extract` before and after for both.
### Parameters
- `options` **[Object][9]** describes the region to extract using integral pixel values
- `options.left` **[Number][8]** zero-indexed offset from left edge
- `options.top` **[Number][8]** zero-indexed offset from top edge
- `options.width` **[Number][8]** width of region to extract
- `options.height` **[Number][8]** height of region to extract
**Throws**:
### Examples
- <code>Error</code> Invalid parameters
```javascript
| Param | Type | Description |
| --- | --- | --- |
| options | <code>Object</code> | describes the region to extract using integral pixel values |
| options.left | <code>number</code> | zero-indexed offset from left edge |
| options.top | <code>number</code> | zero-indexed offset from top edge |
| options.width | <code>number</code> | width of region to extract |
| options.height | <code>number</code> | height of region to extract |
**Example**
```js
sharp(input)
.extract({ left: left, top: top, width: width, height: height })
.toFile(output, function(err) {
// Extract a region of the input image, saving in the same format.
});
```
```javascript
**Example**
```js
sharp(input)
.extract({ left: leftOffsetPre, top: topOffsetPre, width: widthPre, height: heightPre })
.resize(width, height)
@@ -189,46 +249,67 @@ sharp(input)
});
```
- Throws **[Error][13]** Invalid parameters
Returns **Sharp**
## trim
> trim(trim) ⇒ <code>Sharp</code>
Trim "boring" pixels from all edges that contain values similar to the top-left pixel.
The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` properties.
Trim pixels from all edges that contain values similar to the given background colour, which defaults to that of the top-left pixel.
### Parameters
Images with an alpha channel will use the combined bounding box of alpha and non-alpha channels.
- `threshold` **[Number][8]** the allowed difference from the top-left pixel, a number greater than zero. (optional, default `10`)
If the result of this operation would trim an image to nothing then no change is made.
The `info` response Object, obtained from callback of `.toFile()` or `.toBuffer()`,
will contain `trimOffsetLeft` and `trimOffsetTop` properties.
- Throws **[Error][13]** Invalid parameters
**Throws**:
Returns **Sharp**
- <code>Error</code> Invalid parameters
[1]: https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit
[2]: https://developer.mozilla.org/en-US/docs/Web/CSS/object-position
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| trim | <code>string</code> \| <code>number</code> \| <code>Object</code> | | the specific background colour to trim, the threshold for doing so or an Object with both. |
| [trim.background] | <code>string</code> \| <code>Object</code> | <code>&quot;&#x27;top-left pixel&#x27;&quot;</code> | background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to that of the top-left pixel. |
| [trim.threshold] | <code>number</code> | <code>10</code> | the allowed difference from the above colour, a positive number. |
[3]: https://en.wikipedia.org/wiki/Entropy_%28information_theory%29
[4]: http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation
[5]: https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline
[6]: https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf
[7]: https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[11]: https://www.npmjs.org/package/color
[12]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[13]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
**Example**
```js
// Trim pixels with a colour similar to that of the top-left pixel.
sharp(input)
.trim()
.toFile(output, function(err, info) {
...
});
```
**Example**
```js
// Trim pixels with the exact same colour as that of the top-left pixel.
sharp(input)
.trim(0)
.toFile(output, function(err, info) {
...
});
```
**Example**
```js
// Trim only pixels with a similar colour to red.
sharp(input)
.trim("#FF0000")
.toFile(output, function(err, info) {
...
});
```
**Example**
```js
// Trim all "yellow-ish" pixels, being more lenient with the higher threshold.
sharp(input)
.trim({
background: "yellow",
threshold: 42,
})
.toFile(output, function(err, info) {
...
});
```

View File

@@ -1,75 +1,166 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## versions
> versions
An Object containing the version numbers of sharp, libvips and its dependencies.
**Example**
```js
console.log(sharp.versions);
```
## interpolators
> interpolators : <code>enum</code>
An Object containing the available interpolators and their proper values
**Read only**: true
**Properties**
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| nearest | <code>string</code> | <code>&quot;nearest&quot;</code> | [Nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation). Suitable for image enlargement only. |
| bilinear | <code>string</code> | <code>&quot;bilinear&quot;</code> | [Bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation). Faster than bicubic but with less smooth results. |
| bicubic | <code>string</code> | <code>&quot;bicubic&quot;</code> | [Bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) (the default). |
| locallyBoundedBicubic | <code>string</code> | <code>&quot;lbb&quot;</code> | [LBB interpolation](https://github.com/libvips/libvips/blob/master/libvips/resample/lbb.cpp#L100). Prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" but typically reduces performance by a factor of 2. |
| nohalo | <code>string</code> | <code>&quot;nohalo&quot;</code> | [Nohalo interpolation](http://eprints.soton.ac.uk/268086/). Prevents acutance but typically reduces performance by a factor of 3. |
| vertexSplitQuadraticBasisSpline | <code>string</code> | <code>&quot;vsqbs&quot;</code> | [VSQBS interpolation](https://github.com/libvips/libvips/blob/master/libvips/resample/vsqbs.cpp#L48). Prevents "staircasing" when enlarging. |
## format
> format ⇒ <code>Object</code>
An Object containing nested boolean values representing the available input and output formats/methods.
**Example**
```js
console.log(sharp.format);
```
## vendor
> vendor
An Object containing the platform and architecture
of the current and installed vendored binaries.
**Example**
```js
console.log(sharp.vendor);
```
## queue
> queue
An EventEmitter that emits a `change` event when a task is either:
- queued, waiting for _libuv_ to provide a worker thread
- complete
**Example**
```js
sharp.queue.on('change', function(queueLength) {
console.log('Queue contains ' + queueLength + ' task(s)');
});
```
## cache
> cache([options]) ⇒ <code>Object</code>
Gets or, when options are provided, sets the limits of _libvips'_ operation cache.
Existing entries in the cache will be trimmed after any change in limits.
This method always returns cache statistics,
useful for determining how much working memory is required for a particular task.
### 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.memory` **[Number][3]** 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.items` **[Number][3]** is the maximum number of operations to cache (optional, default `100`)
### Examples
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [options] | <code>Object</code> \| <code>boolean</code> | <code>true</code> | Object with the following attributes, or boolean where true uses default cache settings and false removes all caching |
| [options.memory] | <code>number</code> | <code>50</code> | is the maximum memory in MB to use for this cache |
| [options.files] | <code>number</code> | <code>20</code> | is the maximum number of files to hold open |
| [options.items] | <code>number</code> | <code>100</code> | is the maximum number of operations to cache |
```javascript
**Example**
```js
const stats = sharp.cache();
```
```javascript
**Example**
```js
sharp.cache( { items: 200 } );
sharp.cache( { files: 0 } );
sharp.cache(false);
```
Returns **[Object][1]**
## concurrency
> concurrency([concurrency]) ⇒ <code>number</code>
Gets or, when a concurrency is provided, sets
the number of threads _libvips'_ should create to process each image.
The default value is the number of CPU cores.
A value of `0` will reset to this default.
The maximum number of images that can be processed in parallel
is limited by libuv's `UV_THREADPOOL_SIZE` environment variable.
the maximum number of threads _libvips_ should use to process _each image_.
These are from a thread pool managed by glib,
which helps avoid the overhead of creating new threads.
This method always returns the current concurrency.
### Parameters
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.
- `concurrency` **[Number][3]?**
A value of `0` will reset this to the number of CPU cores.
### Examples
Some image format libraries spawn additional threads,
e.g. libaom manages its own 4 threads when encoding AVIF images,
and these are independent of the value set here.
```javascript
The maximum number of images that sharp can process in parallel
is controlled by libuv's `UV_THREADPOOL_SIZE` environment variable,
which defaults to 4.
https://nodejs.org/api/cli.html#uv_threadpool_sizesize
For example, by default, a machine with 8 CPU cores will process
4 images in parallel and use up to 8 threads per image,
so there will be up to 32 concurrent threads.
**Returns**: <code>number</code> - concurrency
| Param | Type |
| --- | --- |
| [concurrency] | <code>number</code> |
**Example**
```js
const threads = sharp.concurrency(); // 4
sharp.concurrency(2); // 2
sharp.concurrency(0); // 4
```
Returns **[Number][3]** concurrency
## counters
> counters() ⇒ <code>Object</code>
Provides access to internal task counters.
- queue is the number of tasks this module has queued waiting for _libuv_ to provide a worker thread from its pool.
- process is the number of resize tasks currently being processed.
- queue is the number of tasks this module has queued waiting for _libuv_ to provide a worker thread from its pool.
- process is the number of resize tasks currently being processed.
### Examples
```javascript
**Example**
```js
const counters = sharp.counters(); // { queue: 2, process: 4 }
```
Returns **[Object][1]**
## simd
> simd([simd]) ⇒ <code>boolean</code>
Get and set use of SIMD vector unit instructions.
Requires libvips to have been compiled with liborc support.
@@ -77,26 +168,78 @@ Requires libvips to have been compiled with liborc support.
Improves the performance of `resize`, `blur` and `sharpen` operations
by taking advantage of the SIMD vector unit of the CPU, e.g. Intel SSE and ARM NEON.
### Parameters
- `simd` **[Boolean][2]** (optional, default `true`)
### Examples
| Param | Type | Default |
| --- | --- | --- |
| [simd] | <code>boolean</code> | <code>true</code> |
```javascript
**Example**
```js
const simd = sharp.simd();
// simd is `true` if the runtime use of liborc is currently enabled
```
```javascript
**Example**
```js
const simd = sharp.simd(false);
// prevent libvips from using liborc at runtime
```
Returns **[Boolean][2]**
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
## block
> block(options)
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
Block libvips operations at runtime.
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
This is in addition to the `VIPS_BLOCK_UNTRUSTED` environment variable,
which when set will block all "untrusted" operations.
**Since**: 0.32.4
| Param | Type | Description |
| --- | --- | --- |
| options | <code>Object</code> | |
| options.operation | <code>Array.&lt;string&gt;</code> | List of libvips low-level operation names to block. |
**Example** *(Block all TIFF input.)*
```js
sharp.block({
operation: ['VipsForeignLoadTiff']
});
```
## unblock
> unblock(options)
Unblock libvips operations at runtime.
This is useful for defining a list of allowed operations.
**Since**: 0.32.4
| Param | Type | Description |
| --- | --- | --- |
| options | <code>Object</code> | |
| options.operation | <code>Array.&lt;string&gt;</code> | List of libvips low-level operation names to unblock. |
**Example** *(Block all input except WebP from the filesystem.)*
```js
sharp.block({
operation: ['VipsForeignLoad']
});
sharp.unblock({
operation: ['VipsForeignLoadWebpFile']
});
```
**Example** *(Block all input except JPEG and PNG from a Buffer or Stream.)*
```js
sharp.block({
operation: ['VipsForeignLoad']
});
sharp.unblock({
operation: ['VipsForeignLoadJpegBuffer', 'VipsForeignLoadPngBuffer']
});
```

38
docs/build.js Normal file
View File

@@ -0,0 +1,38 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
const fs = require('fs').promises;
const path = require('path');
const jsdoc2md = require('jsdoc-to-markdown');
[
'constructor',
'input',
'resize',
'composite',
'operation',
'colour',
'channel',
'output',
'utility'
].forEach(async (m) => {
const input = path.join('lib', `${m}.js`);
const output = path.join('docs', `api-${m}.md`);
const ast = await jsdoc2md.getTemplateData({ files: input });
const markdown = await jsdoc2md.render({
data: ast,
'global-index-format': 'none',
'module-index-format': 'none'
});
const cleanMarkdown = markdown
.replace(/(## )([A-Za-z0-9]+)([^\n]*)/g, '$1$2\n> $2$3\n') // simplify headings to match those of documentationjs, ensures existing URLs work
.replace(/<a name="[A-Za-z0-9+]+"><\/a>/g, '') // remove anchors, let docute add these (at markdown to HTML render time)
.replace(/\*\*Kind\*\*: global[^\n]+/g, '') // remove all "global" Kind labels (requires JSDoc refactoring)
.trim();
await fs.writeFile(output, cleanMarkdown);
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +0,0 @@
/* Nest document subheadings in navigation */
ul.subnav ul:not(.subnav) {
padding-left: 2em;
font-size: 80%;
}

1
docs/docute.min.js vendored Normal file

File diff suppressed because one or more lines are too long

116
docs/firebase.json Normal file
View File

@@ -0,0 +1,116 @@
{
"hosting": {
"site": "pixelplumbing-sharp",
"public": ".",
"ignore": [
".*",
"build.js",
"firebase.json",
"image/**",
"search-index/**"
],
"headers": [
{
"source": "**",
"headers": [
{ "key": "Cache-Control", "value": "max-age=86400" },
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "X-Frame-Options", "value": "SAMEORIGIN" }
]
}
],
"redirects": [
{
"source": "**/install/**",
"destination": "/install",
"type": 301
},
{
"source": "/page/install",
"destination": "/install",
"type": 301
},
{
"source": "**/api-constructor/**",
"destination": "/api-constructor",
"type": 301
},
{
"source": "**/api-input/**",
"destination": "/api-input",
"type": 301
},
{
"source": "**/api-output/**",
"destination": "/api-output",
"type": 301
},
{
"source": "**/api-resize/**",
"destination": "/api-resize",
"type": 301
},
{
"source": "**/api-compsite/**",
"destination": "/api-compsite",
"type": 301
},
{
"source": "**/api-operation/**",
"destination": "/api-operation",
"type": 301
},
{
"source": "**/api-colour/**",
"destination": "/api-colour",
"type": 301
},
{
"source": "**/api-channel/**",
"destination": "/api-channel",
"type": 301
},
{
"source": "**/api-utility/**",
"destination": "/api-utility",
"type": 301
},
{
"source": "/page/api",
"destination": "/api-constructor",
"type": 301
},
{
"source": "**/performance/**",
"destination": "/performance",
"type": 301
},
{
"source": "/page/performance",
"destination": "/performance",
"type": 301
},
{
"source": "**/changelog/**",
"destination": "/changelog",
"type": 301
},
{
"source": "/page/changelog",
"destination": "/changelog",
"type": 301
},
{
"source": "/en/**",
"destination": "/",
"type": 301
}
],
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
]
}
}

277
docs/humans.txt Normal file
View File

@@ -0,0 +1,277 @@
/* THANKS */
Name: John Cupitt
GitHub: https://github.com/jcupitt
Name: Pierre Inglebert
GitHub: https://github.com/pierreinglebert
Name: Jonathan Ong
GitHub: https://github.com/jonathanong
Name: Chanon Sajjamanochai
GitHub: https://github.com/chanon
Name: Juliano Julio
GitHub: https://github.com/julianojulio
Name: Daniel Gasienica
GitHub: https://github.com/gasi
Name: Julian Walker
GitHub: https://github.com/julianwa
Name: Amit Pitaru
GitHub: https://github.com/apitaru
Name: Brandon Aaron
GitHub: https://github.com/brandonaaron
Name: Andreas Lind
GitHub: https://github.com/papandreouGitHub:
Name: Maurus Cuelenaere
GitHub: https://github.com/mcuelenaere
Name: Linus Unnebäck
GitHub: https://github.com/LinusU
Name: Victor Mateevitsi
GitHub: https://github.com/mvictoras
Name: Alaric Holloway
GitHub: https://github.com/skedastik
Name: Bernhard K. Weisshuhn
GitHub: https://github.com/bkw
Name: David A. Carley
GitHub: https://github.com/dacarley
Name: John Tobin
GitHub: https://github.com/jtobinisaniceguy
Name: Kenton Gray
GitHub: https://github.com/kentongray
Name: Felix Bünemann
GitHub: https://github.com/felixbuenemann
Name: Samy Al Zahrani
GitHub: https://github.com/salzhrani
Name: Chintan Thakkar
GitHub: https://github.com/lemnisk8
Name: F. Orlando Galashan
GitHub: https://github.com/frulo
Name: Kleis Auke Wolthuizen
GitHub: https://github.com/kleisauke
Name: Matt Hirsch
GitHub: https://github.com/mhirsch
Name: Rahul Nanwani
GitHub: https://github.com/rnanwani
Name: Matthias Thoemmes
GitHub: https://github.com/cmtt
Name: Patrick Paskaris
GitHub: https://github.com/ppaskaris
Name: Jérémy Lal
GitHub: https://github.com/kapouer
Name: Alice Monday
GitHub: https://github.com/alice0meta
Name: Kristo Jorgenson
GitHub: https://github.com/kristojorg
Name: Yves Bos
GitHub: https://github.com/YvesBos
Name: Nicolas Coden
GitHub: https://github.com/ncoden
Name: Matt Parrish
GitHub: https://github.com/pbomb
Name: Matthew McEachen
GitHub: https://github.com/mceachen
Name: Jarda Kotěšovec
GitHub: https://github.com/jardakotesovec
Name: Kenric D'Souza
GitHub: https://github.com/AzureByte
Name: Oleh Aleinyk
GitHub: https://github.com/oaleynik
Name: Marcel Bretschneider
GitHub: https://github.com/3epnm
Name: Andrea Bianco
GitHub: https://github.com/BiancoA
Name: Rik Heywood
GitHub: https://github.com/rikh42
Name: Thomas Parisot
GitHub: https://github.com/oncletom
Name: Nathan Graves
GitHub: https://github.com/woolite64
Name: Tom Lokhorst
GitHub: https://github.com/tomlokhorst
Name: Espen Hovlandsdal
GitHub: https://github.com/rexxars
Name: Sylvain Dumont
GitHub: https://github.com/sylvaindumont
Name: Alun Davies
GitHub: https://github.com/alundavies
Name: Aidan Hoolachan
GitHub: https://github.com/ajhool
Name: Axel Eirola
GitHub: https://github.com/aeirola
Name: Freezy
GitHub: https://github.com/freezy
Name: Julian Aubourg
GitHub: https://github.com/jaubourg
Name: Keith Belovay
GitHub: https://github.com/fromkeith
Name: Michael B. Klein
GitHub: https://github.com/mbklein
Name: Jakub Michálek
GitHub: https://github.com/Goues
Name: Ilya Ovdin
GitHub: https://github.com/iovdin
Name: Andargor
GitHub: https://github.com/Andargor
Name: Nicolas Stepien
GitHub: https://github.com/MayhemYDG
Name: Paul Neave
GitHub: https://github.com/neave
Name: Brendan Kennedy
GitHub: https://github.com/rustyguts
Name: Brychan Bennett-Odlum
GitHub: https://github.com/BrychanOdlum
Name: Edward Silverton
GitHub: https://github.com/edsilv
Name: Dumitru Deveatii
GitHub: https://github.com/dimadeveatii
Name: Roland Asmann
GitHub: https://github.com/malice00
Name: Roman Malieiev
GitHub: https://github.com/romaleev
Name: Jerome Vouillon
GitHub: https://github.com/vouillon
Name: Tomáš Szabo
GitHub: https://github.com/deftomat
Name: Robert O'Rourke
GitHub: https://github.com/roborourke
Name: Denis Soldatov
GitHub: https://github.com/derom
Name: Stefan Probst
GitHub: https://github.com/stefanprobst
Name: Thomas Beiganz
GitHub: https://github.com/beig
Name: Florian Busch
GitHub: https://github.com/florian-busch
Name: Matthieu Salettes
GitHub: https://github.com/msalettes
Name: Taneli Vatanen
GitHub: https://github.com/Daiz
Name: Mart Jansink
GitHub: https://github.com/mart-jansink
Name: Tenpi
GitHub: https://github.com/Tenpi
Name: Zaruike
GitHub: https://github.com/Zaruike
Name: Erlend F
GitHub: https://github.com/erf
Name: Drian Naude
GitHub: https://github.com/driannaude
Name: Max Gordon
GitHub: https://github.com/gforge
Name: Chris Banks
GitHub: https://github.com/christopherbradleybanks
Name: codepage949
GitHub: https://github.com/codepage949
Name: Chris Hranj
GitHub: https://github.com/Brodan
Name: Ankur Parihar
GitHub: https://github.com/ankurparihar
Name: Joona Heinikoski
GitHub: https://github.com/joonamo
Name: AlexanderTheGrey
GitHub: https://github.com/AlexanderTheGrey
Name: Blayne Chard
GitHub: https://github.com/blacha
Name: Brahim
GitHub: https://github.com/brahima
Name: Anton Marsden
GitHub: https://github.com/antonmarsden
Name: Marcos Casagrande
GitHub: https://github.com/marcosc90
Name: Emanuel Jöbstl
GitHub: https://github.com/ejoebstl
Name: Tomasz Janowski
GitHub: https://github.com/janaz
Name: Lachlan Newman
GitHub: https://github.com/LachlanNewman
Name: BJJ
GitHub: https://github.com/bianjunjie1981

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="998" height="243" viewBox="0 0 998 243">
<defs>
<g id="placeholder">
<rect width="180" height="128" fill="#64bed8"/>
<circle cx="61.1" cy="36.8" r="19.3" fill="#ffefa9"/>
<circle cx="61.1" cy="36.8" r="18.1" fill="#fdda42"/>
<path d="m67.2 34.7 15.2 46L90 57.9l30.4 38 7.8-15.2 7.5 15.4H44z" fill="#6a696f"/>
<path d="m82.4 80.7-15.2-46-.3 69h22.9z" fill="#474749"/>
<path d="m90.1 58 12.2 15 18.2 23-13.9.1z" fill="#474749"/>
<path d="M135.8 96H131l-2.8-15.3z" fill="#474749"/>
<path d="M35.2 96h107.1c0 1.7-1.4 3.2-3.2 3.2H38.4a3.2 3.2 0 0 1-3.2-3.2z" fill="#b9c861"/>
<path d="m67.2 34.7-.1 31-6.2-3-5.3 2.7z" fill="#fff"/>
<path d="m67.2 34.7 7.6 23-7.7 8z" fill="#b3b1b4"/>
<rect width="30.8" height="7.7" x="71.1" y="27.2" rx="2.8" ry="4.1" fill="#fff"/>
<rect width="30.8" height="7.7" x="82.2" y="34.8" rx="2.8" ry="4.1" fill="#fff"/>
<rect width="30.8" height="7.7" x="36.2" y="19.6" rx="2.8" ry="4.1" fill="#fff"/>
<path d="m89.6 72.8-7.2 7.9L90 57.9l10 23z" fill="#fff"/>
<path d="m90.1 58 10 23 2.2-8z" fill="#b3b1b4"/>
<path d="M131.2 85.2 137 68l9 17.2-8 6z" fill="#8da128"/>
<rect width="109.4" height="6.8" x="33.9" y="99.1" rx="13.2" ry="11.4" fill="#22b0d6"/>
<path d="m137 68-5.8 17.2 6.8 6.1.3-13.7z" fill="#727d2e"/>
<rect width="83.3" height="6.8" x="50.8" y="103.6" rx="10" ry="11.4" fill="#22b0d6"/>
<rect width=".7" height="18.4" x="138" y="77.6" fill="#585657"/>
<rect width=".5" height="5.2" x="2" y="-161.3" fill="#585657" transform="rotate(120)"/>
<rect width=".5" height="5.3" x="5.5" y="-163.3" fill="#585657" transform="rotate(120)"/>
<rect width=".5" height="4.8" x="-142.4" y="77.7" fill="#585657" transform="rotate(240)"/>
<rect width=".5" height="5.1" x="-146" y="75.6" fill="#585657" transform="rotate(240)"/>
</g>
<pattern id="img" height="100%" width="100%" viewBox="0 0 180 128">
<use xlink:href="#placeholder"/>
</pattern>
<pattern id="img-fill" width="100%" height="100%" viewBox="0 0 180 128" preserveAspectRatio="none">
<use xlink:href="#placeholder"/>
</pattern>
</defs>
<rect x="0" y="0" width="998" height="243" fill="#ddd"/>
<g id="cover">
<rect x="22" y="28" width="180" height="132" fill="url(#img)"/>
<rect x="48" y="30" width="128" height="128" fill="none" stroke="#000" stroke-width="4"/>
<text x="112" y="85%" dominant-baseline="middle" text-anchor="middle" font-family="sans" font-size="32" font-weight="bold">cover</text>
</g>
<g id="contain">
<rect x="240" y="30" width="128" height="128" fill="url(#img)" stroke="#000" stroke-width="4"/>
<text x="304" y="85%" dominant-baseline="middle" text-anchor="middle" font-family="sans" font-size="32" font-weight="bold" fill="#555">contain</text>
</g>
<g id="fill">
<rect x="432" y="30" width="128" height="128" fill="url(#img-fill)" stroke="#000" stroke-width="4"/>
<text x="496" y="85%" dominant-baseline="middle" text-anchor="middle" font-family="sans" font-size="32" font-weight="bold">fill</text>
</g>
<g id="inside">
<rect x="624" y="48" width="128" height="92" fill="url(#img)" stroke="#000" stroke-width="4"/>
<rect x="624" y="30" width="128" height="128" fill="none" stroke="#000" stroke-width="4" stroke-dasharray="12 4" stroke-dashoffset="6"/>
<text x="688" y="85%" dominant-baseline="middle" text-anchor="middle" font-family="sans" font-size="32" font-weight="bold" fill="#555">inside</text>
</g>
<g id="outside">
<rect x="792" y="30" width="176" height="128" fill="url(#img)" stroke="#000" stroke-width="4"/>
<rect x="816" y="30" width="128" height="128" fill="none" stroke="#000" stroke-width="4" stroke-dasharray="12 4" stroke-dashoffset="-2"/>
<text x="880" y="85%" dominant-baseline="middle" text-anchor="middle" font-family="sans" font-size="32" font-weight="bold">outside</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 652 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

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">
<!-- 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="#090" stroke-width="80" d="M451.441 610.246V438.66l178.725-23.91M269.688 112.59v171.58L90.964 308.093"/>
</svg>

Before

Width:  |  Height:  |  Size: 592 B

After

Width:  |  Height:  |  Size: 508 B

206
docs/index.html Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,147 +0,0 @@
# sharp
<img src="image/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
The typical use case for this high speed Node.js module
is to convert large images in common formats to
smaller, web-friendly JPEG, PNG and WebP images of varying dimensions.
Resizing an image is typically 4x-5x faster than using the
quickest ImageMagick and GraphicsMagick settings
due to its use of [libvips](https://github.com/libvips/libvips).
Colour spaces, embedded ICC profiles and alpha transparency channels are all handled correctly.
Lanczos resampling ensures quality is not sacrificed for speed.
As well as image resizing, operations such as
rotation, extraction, compositing and gamma correction are available.
Most modern 64-bit OS X, Windows and Linux systems running
Node versions 8, 10 and 12
do not require any additional install or runtime dependencies.
[![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.png?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master)
### Formats
This module supports reading JPEG, PNG, WebP, TIFF, GIF and SVG images.
Output images can be in JPEG, PNG, WebP and TIFF formats as well as uncompressed raw pixel data.
Streams, Buffer objects and the filesystem can be used for input and output.
A single input Stream can be split into multiple processing pipelines and output Streams.
Deep Zoom image pyramids can be generated,
suitable for use with "slippy map" tile viewers like
[OpenSeadragon](https://github.com/openseadragon/openseadragon)
and [Leaflet](https://github.com/turban/Leaflet.Zoomify).
### Fast
This module is powered by the blazingly fast
[libvips](https://github.com/libvips/libvips) image processing library,
originally created in 1989 at Birkbeck College
and currently maintained by
[John Cupitt](https://github.com/jcupitt).
Only small regions of uncompressed image data
are held in memory and processed at a time,
taking full advantage of multiple CPU cores and L1/L2/L3 cache.
Everything remains non-blocking thanks to _libuv_,
no child processes are spawned and Promises/async/await are supported.
### Optimal
Huffman tables are optimised when generating JPEG output images
without having to use separate command line tools like
[jpegoptim](https://github.com/tjko/jpegoptim) and
[jpegtran](http://jpegclub.org/jpegtran/).
PNG filtering is disabled by default,
which for diagrams and line art often produces the same result
as [pngcrush](https://pmt.sourceforge.io/pngcrush/).
### Contributing
A [guide for contributors](https://github.com/lovell/sharp/blob/master/.github/CONTRIBUTING.md)
covers reporting bugs, requesting features and submitting code changes.
### Credits
This module would never have been possible without
the help and code contributions of the following people:
* [John Cupitt](https://github.com/jcupitt)
* [Pierre Inglebert](https://github.com/pierreinglebert)
* [Jonathan Ong](https://github.com/jonathanong)
* [Chanon Sajjamanochai](https://github.com/chanon)
* [Juliano Julio](https://github.com/julianojulio)
* [Daniel Gasienica](https://github.com/gasi)
* [Julian Walker](https://github.com/julianwa)
* [Amit Pitaru](https://github.com/apitaru)
* [Brandon Aaron](https://github.com/brandonaaron)
* [Andreas Lind](https://github.com/papandreou)
* [Maurus Cuelenaere](https://github.com/mcuelenaere)
* [Linus Unnebäck](https://github.com/LinusU)
* [Victor Mateevitsi](https://github.com/mvictoras)
* [Alaric Holloway](https://github.com/skedastik)
* [Bernhard K. Weisshuhn](https://github.com/bkw)
* [David A. Carley](https://github.com/dacarley)
* [John Tobin](https://github.com/jtobinisaniceguy)
* [Kenton Gray](https://github.com/kentongray)
* [Felix Bünemann](https://github.com/felixbuenemann)
* [Samy Al Zahrani](https://github.com/salzhrani)
* [Chintan Thakkar](https://github.com/lemnisk8)
* [F. Orlando Galashan](https://github.com/frulo)
* [Kleis Auke Wolthuizen](https://github.com/kleisauke)
* [Matt Hirsch](https://github.com/mhirsch)
* [Rahul Nanwani](https://github.com/rnanwani)
* [Matthias Thoemmes](https://github.com/cmtt)
* [Patrick Paskaris](https://github.com/ppaskaris)
* [Jérémy Lal](https://github.com/kapouer)
* [Alice Monday](https://github.com/alice0meta)
* [Kristo Jorgenson](https://github.com/kristojorg)
* [Yves Bos](https://github.com/YvesBos)
* [Nicolas Coden](https://github.com/ncoden)
* [Matt Parrish](https://github.com/pbomb)
* [Matthew McEachen](https://github.com/mceachen)
* [Jarda Kotěšovec](https://github.com/jardakotesovec)
* [Kenric D'Souza](https://github.com/AzureByte)
* [Oleh Aleinyk](https://github.com/oaleynik)
* [Marcel Bretschneider](https://github.com/3epnm)
* [Andrea Bianco](https://github.com/BiancoA)
* [Rik Heywood](https://github.com/rikh42)
* [Thomas Parisot](https://github.com/oncletom)
* [Nathan Graves](https://github.com/woolite64)
* [Tom Lokhorst](https://github.com/tomlokhorst)
* [Espen Hovlandsdal](https://github.com/rexxars)
* [Sylvain Dumont](https://github.com/sylvaindumont)
* [Alun Davies](https://github.com/alundavies)
* [Aidan Hoolachan](https://github.com/ajhool)
* [Axel Eirola](https://github.com/aeirola)
* [Freezy](https://github.com/freezy)
* [Julian Aubourg](https://github.com/jaubourg)
* [Keith Belovay](https://github.com/fromkeith)
* [Michael B. Klein](https://github.com/mbklein)
* [Jakub Michálek](https://github.com/Goues)
* [Ilya Ovdin](https://github.com/iovdin)
* [Andargor](https://github.com/Andargor)
Thank you!
### Licensing
Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
[https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -10,106 +10,208 @@ yarn add sharp
## Prerequisites
* Node.js v8.5.0+
* Node.js >= 14.15.0
### Building from source
## Prebuilt binaries
Pre-compiled binaries for sharp are provided for use with
Node versions 8, 10 and 12 on
64-bit Windows, OS X and Linux platforms.
Ready-compiled sharp and libvips binaries are provided for use on the most common platforms:
Sharp will be built from source at install time when:
* macOS x64 (>= 10.13)
* macOS ARM64
* Linux x64 (glibc >= 2.17, musl >= 1.1.24, CPU with SSE4.2)
* Linux ARM64 (glibc >= 2.17, musl >= 1.1.24)
* Windows x64
* Windows x86
* a globally-installed libvips is detected,
* pre-compiled binaries do not exist for the current platform and Node version, or
A ~7MB tarball containing libvips and its most commonly used dependencies
is downloaded via HTTPS, verified via Subresource Integrity
and decompressed into `node_modules/sharp/vendor` during `npm install`.
This provides support for the
JPEG, PNG, WebP, AVIF (limited to 8-bit depth), TIFF, GIF and SVG (input) image formats.
The following platforms have prebuilt libvips but not sharp:
* Linux ARMv7 (glibc >= 2.28)
* Linux ARMv6 (glibc >= 2.28)
* Windows ARM64
The following platforms require compilation of both libvips and sharp from source:
* Linux x86
* Linux ARMv7 (glibc <= 2.27, musl)
* Linux ARMv6 (glibc <= 2.27, musl)
* Linux PowerPC
* FreeBSD
* OpenBSD
## Common problems
The architecture and platform of Node.js used for `npm install`
must be the same as the architecture and platform of Node.js used at runtime.
See the [cross-platform](#cross-platform) section if this is not the case.
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 or later, 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.
Check the output of running `npm install --verbose --foreground-scripts sharp` for useful error messages.
## Apple M1
Prebuilt sharp and libvips binaries have been provided for macOS on ARM64 since sharp v0.29.0.
## Cross-platform
At `npm install` time, prebuilt binaries are automatically selected for the
current OS platform and CPU architecture, where available.
The target platform and/or architecture can be manually selected using the following flags.
```sh
npm install --platform=... --arch=... --arm-version=... sharp
```
* `--platform`: one of `linux`, `darwin` or `win32`.
* `--arch`: one of `x64`, `ia32`, `arm` or `arm64`.
* `--arm-version`: one of `6`, `7` or `8` (`arm` defaults to `6`, `arm64` defaults to `8`).
* `--libc`: one of `glibc` or `musl`. This option only works with platform `linux`, defaults to `glibc`
* `--sharp-install-force`: skip version compatibility and Subresource Integrity checks.
These values can also be set via environment variables,
`npm_config_platform`, `npm_config_arch`, `npm_config_arm_version`, `npm_config_libc`
and `SHARP_INSTALL_FORCE` respectively.
For example, if the target machine has a 64-bit ARM CPU and is running Alpine Linux,
use the following flags:
```sh
npm install --arch=arm64 --platform=linux --libc=musl sharp
```
If the current machine is Alpine Linux and the target machine is Debian Linux on x64 cpu,
use the following flags:
```sh
npm install --arch=x64 --platform=linux --libc=glibc sharp
```
Multiple platforms and architectures can be supported within the same installation tree.
The following example for macOS installs x64 binaries then adds (via a rebuild) arm64 binaries:
```sh
npm install --platform=darwin --arch=x64 sharp
npm rebuild --platform=darwin --arch=arm64 sharp
```
## Custom libvips
To use a custom, globally-installed version of libvips instead of the provided binaries,
make sure it is at least the version listed under `config.libvips` in the `package.json` file
and that it can be located using `pkg-config --modversion vips-cpp`.
For help compiling libvips and its dependencies, please see
[building libvips from source](https://www.libvips.org/install.html#building-libvips-from-source).
The use of a globally-installed libvips is unsupported on Windows
and on macOS when running Node.js under Rosetta.
## Building from source
This module will be compiled from source at `npm install` time when:
* a globally-installed libvips is detected (set the `SHARP_IGNORE_GLOBAL_LIBVIPS` environment variable to skip this),
* prebuilt sharp binaries do not exist for the current platform, or
* when the `npm install --build-from-source` flag is used.
Building from source requires:
* C++11 compatible compiler such as gcc 4.8+, clang 3.0+ or MSVC 2013+
* [node-gyp](https://github.com/nodejs/node-gyp#installation) and its dependencies (includes Python 2.7)
* C++11 compiler
* [node-gyp](https://github.com/nodejs/node-gyp#installation) and its dependencies
## libvips
## Custom prebuilt binaries
### Linux
This is an advanced approach that most people will not require.
[![Ubuntu 16.04 Build Status](https://travis-ci.org/lovell/sharp.png?branch=master)](https://travis-ci.org/lovell/sharp)
### Prebuilt sharp binaries
libvips and its dependencies are fetched and stored within `node_modules/sharp/vendor` during `npm install`.
This involves an automated HTTPS download of approximately 10MB.
To install the prebuilt sharp binaries from a custom URL,
set the `sharp_binary_host` npm config option
or the `npm_config_sharp_binary_host` environment variable.
Most Linux-based (glibc, musl) operating systems running on x64 and ARMv6+ CPUs should "just work", e.g.:
To install the prebuilt sharp binaries from a directory on the local filesystem,
set the `sharp_local_prebuilds` npm config option
or the `npm_config_sharp_local_prebuilds` environment variable.
* Debian 8+
* Ubuntu 14.04+
* Red Hat Enterprise 7+
* CentOS 7+
* Alpine 3.10+
* Fedora 21+
* openSUSE 13.2+
* Archlinux
* Raspbian Jessie
* Amazon Linux
* Solus
URL example:
if `sharp_binary_host` is set to `https://hostname/path`
and the sharp version is `1.2.3` then the resultant URL will be
`https://hostname/path/v1.2.3/sharp-v1.2.3-napi-v5-platform-arch.tar.gz`.
To use a globally-installed version of libvips instead of the provided binaries,
make sure it is at least the version listed under `config.libvips` in the `package.json` file
and that it can be located using `pkg-config --modversion vips-cpp`.
Filename example:
if `sharp_local_prebuilds` is set to `/path`
and the sharp version is `1.2.3` then the resultant filename will be
`/path/sharp-v1.2.3-napi-v5-platform-arch.tar.gz`.
If you are using non-standard paths (anything other than `/usr` or `/usr/local`),
you might need to set `PKG_CONFIG_PATH` during `npm install`
and `LD_LIBRARY_PATH` at runtime.
### Prebuilt libvips binaries
This allows the use of newer versions of libvips with older versions of sharp.
To install the prebuilt libvips binaries from a custom URL,
set the `sharp_libvips_binary_host` npm config option
or the `npm_config_sharp_libvips_binary_host` environment variable.
For 32-bit Intel CPUs and older Linux-based operating systems such as
those based on Red Hat Enterprise 6 (e.g. CentOS 6)
compiling libvips from source is recommended.
To install the prebuilt libvips binaries from a directory on the local filesystem,
set the `sharp_libvips_local_prebuilds` npm config option
or the `npm_config_sharp_libvips_local_prebuilds` environment variable.
[https://libvips.github.io/libvips/install.html#building-libvips-from-a-source-tarball](https://libvips.github.io/libvips/install.html#building-libvips-from-a-source-tarball)
The version subpath and filename are appended to these.
#### Alpine Linux
URL example:
if `sharp_libvips_binary_host` is set to `https://hostname/path`
and the libvips version is `4.5.6` then the resultant URL will be
`https://hostname/path/v4.5.6/libvips-4.5.6-platform-arch.tar.br`.
libvips is available in the
[community repository](https://pkgs.alpinelinux.org/packages?name=vips-dev):
Filename example:
if `sharp_libvips_local_prebuilds` is set to `/path`
and the libvips version is `4.5.6` then the resultant filename will be
`/path/v4.5.6/libvips-4.5.6-platform-arch.tar.br`.
See the Chinese mirror below for a further example.
If these binaries are modified, new integrity hashes can be provided
at install time via `npm_package_config_integrity_platform_arch`
environment variables, for example set
`npm_package_config_integrity_linux_x64` to `sha512-abc...`.
The integrity hash of a file can be generated via:
```sh
apk add vips-dev fftw-dev build-base --update-cache \
--repository https://alpine.global.ssl.fastly.net/alpine/edge/community/ \
--repository https://alpine.global.ssl.fastly.net/alpine/edge/main
sha512sum libvips-x.y.z-platform-arch.tar.br | cut -f1 -d' ' | xxd -r -p | base64 -w 0
```
The smaller stack size of musl libc means
libvips may need to be used without a cache
via `sharp.cache(false)` to avoid a stack overflow.
## Chinese mirror
### Mac OS
A mirror site based in China, provided by Alibaba, contains binaries for both sharp and libvips.
[![OS X 10.12 Build Status](https://travis-ci.org/lovell/sharp.png?branch=master)](https://travis-ci.org/lovell/sharp)
To use this either set the following configuration:
libvips and its dependencies are fetched and stored within `node_modules/sharp/vendor` during `npm install`.
This involves an automated HTTPS download of approximately 8MB.
```sh
npm config set sharp_binary_host "https://npmmirror.com/mirrors/sharp"
npm config set sharp_libvips_binary_host "https://npmmirror.com/mirrors/sharp-libvips"
npm install sharp
```
To use your own version of libvips instead of the provided binaries, make sure it is
at least the version listed under `config.libvips` in the `package.json` file and
that it can be located using `pkg-config --modversion vips-cpp`.
or set the following environment variables:
### Windows x64
```sh
npm_config_sharp_binary_host="https://npmmirror.com/mirrors/sharp" \
npm_config_sharp_libvips_binary_host="https://npmmirror.com/mirrors/sharp-libvips" \
npm install sharp
```
[![Windows x64 Build Status](https://ci.appveyor.com/api/projects/status/pgtul704nkhhg6sg)](https://ci.appveyor.com/project/lovell/sharp)
## FreeBSD
libvips and its dependencies are fetched and stored within `node_modules\sharp\vendor` during `npm install`.
This involves an automated HTTPS download of approximately 10MB.
If you are having issues during installation consider removing the directory
`C:\Users\[user]\AppData\Roaming\npm-cache\_libvips`.
Only 64-bit (x64) `node.exe` is supported.
### FreeBSD
libvips must be installed before `npm install` is run.
This can be achieved via package or ports:
The `vips` package must be installed before `npm install` is run.
```sh
pkg install -y pkgconf vips
@@ -119,169 +221,151 @@ pkg install -y pkgconf vips
cd /usr/ports/graphics/vips/ && make install clean
```
FreeBSD's gcc v4 and v5 need `CXXFLAGS=-D_GLIBCXX_USE_C99` set for C++11 support due to
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=193528
## Linux memory allocator
### Heroku
The default memory allocator on most glibc-based Linux systems
(e.g. Debian, Red Hat) is unsuitable for long-running, multi-threaded
processes that involve lots of small memory allocations.
Set [NODE_MODULES_CACHE](https://devcenter.heroku.com/articles/nodejs-support#cache-behavior)
to `false` when using the `yarn` package manager.
For this reason, by default, sharp will limit the use of thread-based
[concurrency](api-utility#concurrency) when the glibc allocator is
detected at runtime.
### Docker
To help avoid fragmentation and improve performance on these systems,
the use of an alternative memory allocator such as
[jemalloc](https://github.com/jemalloc/jemalloc) is recommended.
[Marc Bachmann](https://github.com/marcbachmann) maintains an
[Ubuntu-based Dockerfile for libvips](https://github.com/marcbachmann/dockerfile-libvips).
```sh
docker pull marcbachmann/libvips
```
[Will Jordan](https://github.com/wjordan) maintains an
[Alpine-based Dockerfile for libvips](https://github.com/wjordan/dockerfile-libvips).
```sh
docker pull wjordan/libvips
```
[Tailor Brands](https://github.com/TailorBrands) maintain
[Debian-based Dockerfiles for libvips and nodejs](https://github.com/TailorBrands/docker-libvips).
```sh
docker pull tailor/docker-libvips
```
### AWS Lambda
Set the Lambda runtime to `nodejs10.x`.
The binaries in the `node_modules` directory of the
[deployment package](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-create-deployment-pkg.html)
must be for the Linux x64 platform/architecture.
On non-Linux machines such as OS X and Windows run the following:
Those using musl-based Linux (e.g. Alpine) and non-Linux systems are
unaffected.
## AWS Lambda
The `node_modules` directory of the
[deployment package](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-package.html)
must include binaries for the Linux x64 platform.
When building your deployment package on machines other than Linux x64 (glibc),
run the following additional command after `npm install`:
```sh
npm install
rm -rf node_modules/sharp
npm install --arch=x64 --platform=linux --target=10.15.0 sharp
```
Alternatively a Docker container closely matching the Lambda runtime can be used:
```sh
rm -rf node_modules/sharp
docker run -v "$PWD":/var/task lambci/lambda:build-nodejs10.x npm install sharp
SHARP_IGNORE_GLOBAL_LIBVIPS=1 npm install --arch=x64 --platform=linux --libc=glibc sharp
```
To get the best performance select the largest memory available.
A 1536 MB function provides ~12x more CPU time than a 128 MB function.
### NW.js
When integrating with AWS API Gateway, ensure it is configured with the relevant
[binary media types](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-payload-encodings.html).
Run the `nw-gyp` tool after installation.
## Bundlers
```sh
cd node-modules/sharp
nw-gyp rebuild --arch=x64 --target=[your nw version]
node node_modules/sharp/install/dll-copy
### webpack
Ensure sharp is excluded from bundling via the
[externals](https://webpack.js.org/configuration/externals/)
configuration.
```js
externals: {
'sharp': 'commonjs sharp'
}
```
[http://docs.nwjs.io/en/latest/For%20Users/Advanced/Use%20Native%20Node%20Modules/](http://docs.nwjs.io/en/latest/For%20Users/Advanced/Use%20Native%20Node%20Modules/)
### esbuild
### Build tools
Ensure sharp is excluded from bundling via the
[external](https://esbuild.github.io/api/#external)
configuration.
* [gulp-responsive](https://www.npmjs.com/package/gulp-responsive)
* [grunt-sharp](https://www.npmjs.com/package/grunt-sharp)
### Coding tools
* [Sharp TypeScript Types](https://www.npmjs.com/package/@types/sharp)
### CLI tools
* [sharp-cli](https://www.npmjs.com/package/sharp-cli)
### Security
Many users of this module process untrusted, user-supplied images,
but there are aspects of security to consider when doing so.
It is possible to compile libvips with support for various third-party image loaders.
Each of these libraries has undergone differing levels of security testing.
Whilst tools such as [American Fuzzy Lop](http://lcamtuf.coredump.cx/afl/)
and [Valgrind](http://valgrind.org/) have been used to test
the most popular web-based formats, as well as libvips itself,
you are advised to perform your own testing and sandboxing.
### Pre-compiled libvips binaries
This module will attempt to download a pre-compiled bundle of libvips
and its dependencies on Linux and Windows machines under either of these
conditions:
1. If a global installation of libvips that meets the
minimum version requirement cannot be found;
1. If `SHARP_IGNORE_GLOBAL_LIBVIPS` environment variable is set.
```sh
SHARP_IGNORE_GLOBAL_LIBVIPS=1 npm install sharp
```js
buildSync({
entryPoints: ['app.js'],
bundle: true,
platform: 'node',
external: ['sharp'],
})
```
Should you need to manually download and inspect these files,
you can do so via
[https://github.com/lovell/sharp-libvips/releases](https://github.com/lovell/sharp-libvips/releases)
Should you wish to install these from your own location,
set the `sharp_dist_base_url` npm config option, e.g.
```sh
npm config set sharp_dist_base_url "https://hostname/path/"
npm install sharp
esbuild app.js --bundle --platform=node --external:sharp
```
or set the `SHARP_DIST_BASE_URL` environment variable, e.g.
For `serverless-esbuild`, ensure platform-specific binaries are installed
via the `serverless.yml` configuration.
```sh
SHARP_DIST_BASE_URL="https://hostname/path/" npm install sharp
```yaml
custom:
esbuild:
external:
- sharp
packagerOptions:
scripts:
- npm install --arch=x64 --platform=linux sharp
```
to use `https://hostname/path/libvips-x.y.z-platform.tar.gz`.
## TypeScript
### Licences
TypeScript definitions are published as part of
the `sharp` package from v0.32.0.
This module is licensed under the terms of the
[Apache 2.0 Licence](https://github.com/lovell/sharp/blob/master/LICENSE).
Previously these were available via the `@types/sharp` package,
which is now deprecated.
The libraries downloaded and used by this module
are done so under the terms of the following licences,
all of which are compatible with the Apache 2.0 Licence.
When using Typescript, please ensure `devDependencies` includes
the `@types/node` package.
Use of libraries under the terms of the LGPLv3 is via the
"any later version" clause of the LGPLv2 or LGPLv2.1.
## Fonts
| Library | Used under the terms of |
|---------------|----------------------------------------------------------------------------------------------------------|
| cairo | Mozilla Public License 2.0 |
| expat | MIT Licence |
| fontconfig | [fontconfig Licence](https://cgit.freedesktop.org/fontconfig/tree/COPYING) (BSD-like) |
| freetype | [freetype Licence](http://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/docs/FTL.TXT) (BSD-like) |
| fribidi | LGPLv3 |
| gettext | LGPLv3 |
| giflib | MIT Licence |
| glib | LGPLv3 |
| harfbuzz | MIT Licence |
| lcms | MIT Licence |
| libcroco | LGPLv3 |
| libexif | LGPLv3 |
| libffi | MIT Licence |
| libgsf | LGPLv3 |
| libjpeg-turbo | [zlib License, IJG License](https://github.com/libjpeg-turbo/libjpeg-turbo/blob/master/LICENSE.md) |
| libpng | [libpng License](http://www.libpng.org/pub/png/src/libpng-LICENSE.txt) |
| librsvg | LGPLv3 |
| libtiff | [libtiff License](http://www.libtiff.org/misc.html) (BSD-like) |
| libvips | LGPLv3 |
| libwebp | New BSD License |
| libxml2 | MIT Licence |
| pango | LGPLv3 |
| pixman | MIT Licence |
| zlib | [zlib Licence](https://github.com/madler/zlib/blob/master/zlib.h) |
When creating text images or rendering SVG images that contain text elements,
`fontconfig` is used to find the relevant fonts.
On Windows and macOS systems, all system fonts are available for use.
On macOS systems using Homebrew, you may need to set the
`PANGOCAIRO_BACKEND` environment variable to a value of `fontconfig`
to ensure it is used for font discovery instead of Core Text.
On Linux systems, fonts that include the relevant
[`fontconfig` configuration](https://www.freedesktop.org/software/fontconfig/fontconfig-user.html)
when installed via package manager are available for use.
If `fontconfig` configuration is not found, the following error will occur:
```
Fontconfig error: Cannot load default config file
```
In serverless environments where there is no control over font packages,
use the `FONTCONFIG_PATH` environment variable to point to a custom location.
Embedded SVG fonts are unsupported.
## Worker threads
On some platforms, including glibc-based Linux,
the main thread must call `require('sharp')`
_before_ worker threads are created.
This is to ensure shared libraries remain loaded in memory
until after all threads are complete.
Without this, the following error may occur:
```
Module did not self-register
```
## Known conflicts
### Canvas and Windows
The prebuilt binaries provided by `canvas` for Windows
from v2.7.0 onwards depend on the Visual C++ Runtime (MSVCRT).
These conflict with the binaries provided by sharp,
which depend on the more modern Universal C Runtime (UCRT).
See [Automattic/node-canvas#2155](https://github.com/Automattic/node-canvas/issues/2155).
If both modules are used in the same Windows process, the following error will occur:
```
The specified procedure could not be found.
```

View File

@@ -1,68 +1,111 @@
# Performance
### Test environment
* AWS EC2 eu-west-1 [c5.large](https://aws.amazon.com/ec2/instance-types/c5/) (2x Xeon Platinum 8124M CPU @ 3.00GHz)
* Ubuntu 18.04 (hvm-ssd/ubuntu-bionic-18.04-amd64-server-20180912 ami-00035f41c82244dab)
* Node.js v12.10.0
### The contenders
* [jimp](https://www.npmjs.com/package/jimp) v0.8.4 - 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.
* [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.
* sharp v0.23.1 / libvips v8.8.1 - Caching within libvips disabled to ensure a fair comparison.
### The task
Decompress a 2725x2225 JPEG image,
resize to 720x588 using Lanczos 3 resampling (where available),
then compress to JPEG at a "quality" setting of 80.
### Results
| Module | Input | Output | Ops/sec | Speed-up |
| :----------------- | :----- | :----- | ------: | -------: |
| jimp | buffer | buffer | 0.66 | 1.0 |
| mapnik | buffer | buffer | 3.31 | 5.0 |
| gm | buffer | buffer | 3.79 | 5.7 |
| gm | file | file | 3.82 | 5.8 |
| imagemagick | file | file | 4.17 | 6.3 |
| sharp | stream | stream | 25.81 | 39.1 |
| sharp | file | file | 26.76 | 40.5 |
| sharp | buffer | buffer | 28.06 | 42.5 |
A test to benchmark the performance of this module relative to alternatives.
Greater libvips performance can be expected with caching enabled (default)
and using 8+ core machines, especially those with larger L1/L2 CPU caches.
The I/O limits of the relevant (de)compression library will generally determine maximum throughput.
### Benchmark test prerequisites
## Contenders
Requires _ImageMagick_, _GraphicsMagick_ and _Mapnik_:
* [jimp](https://www.npmjs.com/package/jimp) v0.22.7 - Image processing in pure JavaScript.
* [imagemagick](https://www.npmjs.com/package/imagemagick) v0.1.3 - Supports filesystem only and "*has been unmaintained for a long time*".
* [gm](https://www.npmjs.com/package/gm) v1.25.0 - Fully featured wrapper around GraphicsMagick's `gm` command line utility.
* [@squoosh/lib](https://www.npmjs.com/package/@squoosh/lib) v0.4.0 - Image libraries transpiled to WebAssembly, includes GPLv3 code, but "*Project no longer maintained*".
* [@squoosh/cli](https://www.npmjs.com/package/@squoosh/cli) v0.7.3 - Command line wrapper around `@squoosh/lib`, avoids GPLv3 by spawning process, but "*Project no longer maintained*".
* sharp v0.32.0 / libvips v8.14.2 - Caching within libvips disabled to ensure a fair comparison.
```sh
brew install imagemagick
brew install graphicsmagick
brew install mapnik
```
## Environment
```sh
sudo apt-get install imagemagick libmagick++-dev graphicsmagick libmapnik-dev
```
### AMD64
```sh
sudo yum install ImageMagick-devel ImageMagick-c++-devel GraphicsMagick mapnik-devel
```
* AWS EC2 us-east-2 [c6a.xlarge](https://aws.amazon.com/ec2/instance-types/c6a/) (4x AMD EPYC 7R13)
* Ubuntu 22.04 20230303 (ami-0122295b0eb922138)
* Node.js 16.19.1
### Running the benchmark test
### ARM64
* AWS EC2 us-east-2 [c7g.xlarge](https://aws.amazon.com/ec2/instance-types/c7g/) (4x ARM Graviton3)
* Ubuntu 22.04 20230303 (ami-0af198159897e7a29)
* Node.js 16.19.1
## Task: JPEG
Decompress a 2725x2225 JPEG image,
resize to 720x588 using Lanczos 3 resampling (where available),
then compress to JPEG at a "quality" setting of 80.
Note: jimp does not support Lanczos 3, bicubic resampling used instead.
#### Results: JPEG (AMD64)
| Module | Input | Output | Ops/sec | Speed-up |
| :----------------- | :----- | :----- | ------: | -------: |
| jimp | buffer | buffer | 0.84 | 1.0 |
| squoosh-cli | file | file | 1.07 | 1.3 |
| squoosh-lib | buffer | buffer | 1.82 | 2.2 |
| gm | buffer | buffer | 8.41 | 10.0 |
| gm | file | file | 8.45 | 10.0 |
| imagemagick | file | file | 8.77 | 10.4 |
| sharp | stream | stream | 36.36 | 43.3 |
| sharp | file | file | 38.67 | 46.0 |
| sharp | buffer | buffer | 39.44 | 47.0 |
#### Results: JPEG (ARM64)
| Module | Input | Output | Ops/sec | Speed-up |
| :----------------- | :----- | :----- | ------: | -------: |
| jimp | buffer | buffer | 1.02 | 1.0 |
| squoosh-cli | file | file | 1.11 | 1.1 |
| squoosh-lib | buffer | buffer | 2.08 | 2.0 |
| gm | buffer | buffer | 8.80 | 8.6 |
| gm | file | file | 10.05 | 9.9 |
| imagemagick | file | file | 10.28 | 10.1 |
| sharp | stream | stream | 26.87 | 26.3 |
| sharp | file | file | 27.88 | 27.3 |
| sharp | buffer | buffer | 28.40 | 27.8 |
## Task: PNG
Decompress a 2048x1536 RGBA PNG image,
premultiply the alpha channel,
resize to 720x540 using Lanczos 3 resampling (where available),
unpremultiply then compress as PNG with a "default" zlib compression level of 6
and without adaptive filtering.
Note: jimp does not support premultiply/unpremultiply.
### Results: PNG (AMD64)
| Module | Input | Output | Ops/sec | Speed-up |
| :----------------- | :----- | :----- | ------: | -------: |
| squoosh-cli | file | file | 0.40 | 1.0 |
| squoosh-lib | buffer | buffer | 0.47 | 1.2 |
| gm | file | file | 6.47 | 16.2 |
| jimp | buffer | buffer | 6.60 | 16.5 |
| imagemagick | file | file | 7.08 | 17.7 |
| sharp | file | file | 17.80 | 44.5 |
| sharp | buffer | buffer | 18.02 | 45.0 |
### Results: PNG (ARM64)
| Module | Input | Output | Ops/sec | Speed-up |
| :----------------- | :----- | :----- | ------: | -------: |
| squoosh-cli | file | file | 0.40 | 1.0 |
| squoosh-lib | buffer | buffer | 0.48 | 1.2 |
| gm | file | file | 7.20 | 18.0 |
| jimp | buffer | buffer | 7.62 | 19.1 |
| imagemagick | file | file | 7.96 | 19.9 |
| sharp | file | file | 12.97 | 32.4 |
| sharp | buffer | buffer | 13.12 | 32.8 |
## Running the benchmark test
Requires Docker.
```sh
git clone https://github.com/lovell/sharp.git
cd sharp
npm install
cd test/bench
npm install
npm test
cd sharp/test/bench
./run-with-docker.sh
```

2
docs/robots.txt Normal file
View File

@@ -0,0 +1,2 @@
User-agent: *
Disallow:

1
docs/search-index.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,64 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
const fs = require('fs');
const path = require('path');
const { extractDescription, extractKeywords, extractParameters } = require('./extract');
const searchIndex = [];
// Install
const contents = fs.readFileSync(path.join(__dirname, '..', 'install.md'), 'utf8');
const matches = contents.matchAll(
/## (?<title>[A-Za-z0-9 ]+)\n\n(?<body>[^#]+)/gs
);
for (const match of matches) {
const { title, body } = match.groups;
const description = extractDescription(body);
searchIndex.push({
t: title,
d: description,
k: extractKeywords(`${title} ${description}`),
l: `/install#${title.toLowerCase().replace(/ /g, '-')}`
});
}
// API
[
'constructor',
'input',
'output',
'resize',
'composite',
'operation',
'channel',
'colour',
'utility'
].forEach((section) => {
const contents = fs.readFileSync(path.join(__dirname, '..', `api-${section}.md`), 'utf8');
const matches = contents.matchAll(
/## (?<title>[A-Za-z]+)\n[^\n]+\n(?<firstparagraph>.+?)\n\n.+?(?<parameters>\| Param .+?\n\n)?\*\*Example/gs
);
for (const match of matches) {
const { title, firstparagraph, parameters } = match.groups;
const description = firstparagraph.startsWith('###')
? 'Constructor'
: extractDescription(firstparagraph);
const parameterNames = parameters ? extractParameters(parameters) : '';
searchIndex.push({
t: title,
d: description,
k: extractKeywords(`${title} ${description} ${parameterNames}`),
l: `/api-${section}#${title.toLowerCase()}`
});
}
});
fs.writeFileSync(
path.join(__dirname, '..', 'search-index.json'),
JSON.stringify(searchIndex)
);

View File

@@ -0,0 +1,34 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
const stopWords = require('./stop-words');
const extractDescription = (str) =>
str
.replace(/### Examples.*/sg, '')
.replace(/\(http[^)]+/g, '')
.replace(/\s+/g, ' ')
.replace(/[^A-Za-z0-9_/\-,. ]/g, '')
.replace(/\s+/g, ' ')
.substring(0, 200)
.trim();
const extractParameters = (str) =>
[...str.matchAll(/options\.(?<name>[^.`\] ]+)/gs)]
.map((match) => match.groups.name)
.map((name) => name.replace(/([A-Z])/g, ' $1').toLowerCase())
.join(' ');
const extractKeywords = (str) =>
[
...new Set(
str
.split(/[ -/]/)
.map((word) => word.toLowerCase().replace(/[^a-z]/g, ''))
.filter((word) => word.length > 2 && word.length < 15 && !stopWords.includes(word))
)
].join(' ');
module.exports = { extractDescription, extractKeywords, extractParameters };

View File

@@ -0,0 +1,135 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
module.exports = [
'about',
'after',
'all',
'allows',
'already',
'also',
'alternative',
'always',
'and',
'any',
'are',
'available',
'based',
'been',
'before',
'best',
'both',
'call',
'callback',
'can',
'containing',
'contains',
'created',
'current',
'date',
'default',
'does',
'each',
'either',
'ensure',
'entirely',
'etc',
'every',
'except',
'following',
'for',
'from',
'get',
'gets',
'given',
'has',
'have',
'how',
'image',
'implies',
'include',
'including',
'involve',
'its',
'last',
'least',
'lots',
'make',
'may',
'more',
'most',
'much',
'must',
'non',
'not',
'now',
'occur',
'occurs',
'one',
'options',
'other',
'out',
'over',
'part',
'perform',
'performs',
'please',
'pre',
'previously',
'produce',
'provide',
'provided',
'ready',
'requires',
'requiresharp',
'returned',
'run',
'same',
'see',
'set',
'sets',
'sharp',
'should',
'since',
'site',
'some',
'specified',
'spelling',
'such',
'support',
'supported',
'sure',
'take',
'task',
'than',
'that',
'the',
'their',
'then',
'there',
'therefore',
'these',
'this',
'under',
'unless',
'unmaintained',
'unsuitable',
'until',
'use',
'used',
'using',
'value',
'values',
'via',
'were',
'when',
'which',
'while',
'will',
'with',
'without',
'you',
'your'
];

14
install/can-compile.js Normal file
View File

@@ -0,0 +1,14 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
const libvips = require('../lib/libvips');
try {
if (!(libvips.useGlobalLibvips() || libvips.hasVendoredLibvips())) {
process.exitCode = 1;
}
} catch (err) {
process.exitCode = 1;
}

View File

@@ -1,21 +1,26 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
const fs = require('fs');
const path = require('path');
const libvips = require('../lib/libvips');
const npmLog = require('npmlog');
const platform = require('../lib/platform');
if (process.platform === 'win32') {
const buildDir = path.join(__dirname, '..', 'build');
const buildReleaseDir = path.join(buildDir, 'Release');
npmLog.info('sharp', `Creating ${buildReleaseDir}`);
const minimumLibvipsVersion = libvips.minimumLibvipsVersion;
const platformAndArch = platform();
if (platformAndArch.startsWith('win32')) {
const buildReleaseDir = path.join(__dirname, '..', 'build', 'Release');
libvips.log(`Creating ${buildReleaseDir}`);
try {
libvips.mkdirSync(buildDir);
libvips.mkdirSync(buildReleaseDir);
} catch (err) {}
const vendorLibDir = path.join(__dirname, '..', 'vendor', 'lib');
npmLog.info('sharp', `Copying DLLs from ${vendorLibDir} to ${buildReleaseDir}`);
const vendorLibDir = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, platformAndArch, 'lib');
libvips.log(`Copying DLLs from ${vendorLibDir} to ${buildReleaseDir}`);
try {
fs
.readdirSync(vendorLibDir)
@@ -29,6 +34,7 @@ if (process.platform === 'win32') {
);
});
} catch (err) {
npmLog.error('sharp', err.message);
libvips.log(err);
process.exit(1);
}
}

View File

@@ -1,106 +1,220 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
const fs = require('fs');
const os = require('os');
const path = require('path');
const stream = require('stream');
const zlib = require('zlib');
const { createHash } = require('crypto');
const detectLibc = require('detect-libc');
const npmLog = require('npmlog');
const semver = require('semver');
const semverCoerce = require('semver/functions/coerce');
const semverLessThan = require('semver/functions/lt');
const semverSatisfies = require('semver/functions/satisfies');
const simpleGet = require('simple-get');
const tar = require('tar');
const tarFs = require('tar-fs');
const agent = require('../lib/agent');
const libvips = require('../lib/libvips');
const platform = require('../lib/platform');
const minimumLibvipsVersion = libvips.minimumLibvipsVersion;
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${minimumLibvipsVersion}/`;
const minimumGlibcVersionByArch = {
arm: '2.28',
arm64: '2.17',
x64: '2.17'
};
const hasSharpPrebuild = [
'darwin-x64',
'darwin-arm64',
'linux-arm64',
'linux-x64',
'linuxmusl-x64',
'linuxmusl-arm64',
'win32-ia32',
'win32-x64'
];
const { minimumLibvipsVersion, minimumLibvipsVersionLabelled } = libvips;
const localLibvipsDir = process.env.npm_config_sharp_libvips_local_prebuilds || '';
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 installationForced = !!(process.env.npm_config_sharp_install_force || process.env.SHARP_INSTALL_FORCE);
const fail = function (err) {
npmLog.error('sharp', err.message);
npmLog.info('sharp', 'Attempting to build from source via node-gyp but this may fail due to the above error');
npmLog.info('sharp', 'Please see https://sharp.pixelplumbing.com/page/install for required dependencies');
libvips.log(err);
if (err.code === 'EACCES') {
libvips.log('Are you trying to install as a root or sudo user?');
libvips.log('- For npm <= v6, try again with the "--unsafe-perm" flag');
libvips.log('- For npm >= v8, the user must own the directory "npm install" is run in');
}
libvips.log('Please see https://sharp.pixelplumbing.com/install for required dependencies');
process.exit(1);
};
const extractTarball = function (tarPath) {
const vendorPath = path.join(__dirname, '..', 'vendor');
libvips.mkdirSync(vendorPath);
tar
.extract({
file: tarPath,
cwd: vendorPath,
strict: true
})
.catch(function (err) {
if (/unexpected end of file/.test(err.message)) {
npmLog.error('sharp', `Please delete ${tarPath} as it is not a valid tarball`);
const handleError = function (err) {
if (installationForced) {
libvips.log(`Installation warning: ${err.message}`);
} else {
throw err;
}
};
const verifyIntegrity = function (platformAndArch) {
const expected = libvips.integrity(platformAndArch);
if (installationForced || !expected) {
libvips.log(`Integrity check skipped for ${platformAndArch}`);
return new stream.PassThrough();
}
const hash = createHash('sha512');
return new stream.Transform({
transform: function (chunk, _encoding, done) {
hash.update(chunk);
done(null, chunk);
},
flush: function (done) {
const digest = `sha512-${hash.digest('base64')}`;
if (expected !== digest) {
try {
libvips.removeVendoredLibvips();
} catch (err) {
libvips.log(err.message);
}
libvips.log(`Integrity expected: ${expected}`);
libvips.log(`Integrity received: ${digest}`);
done(new Error(`Integrity check failed for ${platformAndArch}`));
} else {
libvips.log(`Integrity check passed for ${platformAndArch}`);
done();
}
fail(err);
});
}
});
};
const extractTarball = function (tarPath, platformAndArch) {
const versionedVendorPath = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, platformAndArch);
libvips.mkdirSync(versionedVendorPath);
const ignoreVendorInclude = hasSharpPrebuild.includes(platformAndArch) && !process.env.npm_config_build_from_source;
const ignore = function (name) {
return ignoreVendorInclude && name.includes('include/');
};
stream.pipeline(
fs.createReadStream(tarPath),
verifyIntegrity(platformAndArch),
new zlib.BrotliDecompress(),
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);
}
}
);
};
try {
const useGlobalLibvips = libvips.useGlobalLibvips();
if (useGlobalLibvips) {
const globalLibvipsVersion = libvips.globalLibvipsVersion();
npmLog.info('sharp', `Detected globally-installed libvips v${globalLibvipsVersion}`);
npmLog.info('sharp', 'Building from source via node-gyp');
libvips.log(`Detected globally-installed libvips v${globalLibvipsVersion}`);
libvips.log('Building from source via node-gyp');
process.exit(1);
} else if (libvips.hasVendoredLibvips()) {
npmLog.info('sharp', `Using existing vendored libvips v${minimumLibvipsVersion}`);
libvips.log(`Using existing vendored libvips v${minimumLibvipsVersion}`);
} else {
// Is this arch/platform supported?
const arch = process.env.npm_config_arch || process.arch;
const platformAndArch = platform();
if (platformAndArch === 'win32-ia32') {
throw new Error('Windows x86 (32-bit) node.exe is not supported');
}
if (arch === 'ia32') {
if (arch === 'ia32' && !platformAndArch.startsWith('win32')) {
throw new Error(`Intel Architecture 32-bit systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
}
if (platformAndArch === 'freebsd-x64' || platformAndArch === 'openbsd-x64' || platformAndArch === 'sunos-x64') {
throw new Error(`BSD/SunOS systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
}
if (detectLibc.family === detectLibc.GLIBC && detectLibc.version && semver.lt(`${detectLibc.version}.0`, '2.17.0')) {
throw new Error(`Use with glibc version ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`);
// Linux libc version check
const libcVersionRaw = detectLibc.versionSync();
if (libcVersionRaw) {
const libcFamily = detectLibc.familySync();
const libcVersion = semverCoerce(libcVersionRaw).version;
if (libcFamily === detectLibc.GLIBC && minimumGlibcVersionByArch[arch]) {
if (semverLessThan(libcVersion, semverCoerce(minimumGlibcVersionByArch[arch]).version)) {
handleError(new Error(`Use with glibc ${libcVersionRaw} requires manual installation of libvips >= ${minimumLibvipsVersion}`));
}
}
if (libcFamily === detectLibc.MUSL) {
if (semverLessThan(libcVersion, '1.1.24')) {
handleError(new Error(`Use with musl ${libcVersionRaw} requires manual installation of libvips >= ${minimumLibvipsVersion}`));
}
}
}
// Node.js minimum version check
const supportedNodeVersion = process.env.npm_package_engines_node || require('../package.json').engines.node;
if (!semverSatisfies(process.versions.node, supportedNodeVersion)) {
handleError(new Error(`Expected Node.js version ${supportedNodeVersion} but found ${process.versions.node}`));
}
// Download to per-process temporary file
const tarFilename = ['libvips', minimumLibvipsVersion, platformAndArch].join('-') + '.tar.gz';
const tarFilename = ['libvips', minimumLibvipsVersionLabelled, platformAndArch].join('-') + '.tar.br';
const tarPathCache = path.join(libvips.cachePath(), tarFilename);
if (fs.existsSync(tarPathCache)) {
npmLog.info('sharp', `Using cached ${tarPathCache}`);
extractTarball(tarPathCache);
libvips.log(`Using cached ${tarPathCache}`);
extractTarball(tarPathCache, platformAndArch);
} else if (localLibvipsDir) {
// If localLibvipsDir is given try to use binaries from local directory
const tarPathLocal = path.join(path.resolve(localLibvipsDir), `v${minimumLibvipsVersionLabelled}`, tarFilename);
libvips.log(`Using local libvips from ${tarPathLocal}`);
extractTarball(tarPathLocal, platformAndArch);
} else {
const tarPathTemp = path.join(os.tmpdir(), `${process.pid}-${tarFilename}`);
const tmpFile = fs.createWriteStream(tarPathTemp);
const url = distBaseUrl + tarFilename;
npmLog.info('sharp', `Downloading ${url}`);
simpleGet({ url: url, agent: agent() }, function (err, response) {
libvips.log(`Downloading ${url}`);
simpleGet({ url: url, agent: agent(libvips.log) }, function (err, response) {
if (err) {
throw err;
fail(err);
} else if (response.statusCode === 404) {
fail(new Error(`Prebuilt libvips ${minimumLibvipsVersion} binaries are not yet available for ${platformAndArch}`));
} else if (response.statusCode !== 200) {
fail(new Error(`Status ${response.statusCode} ${response.statusMessage}`));
} else {
const tarPathTemp = path.join(os.tmpdir(), `${process.pid}-${tarFilename}`);
const tmpFileStream = fs.createWriteStream(tarPathTemp);
response
.on('error', function (err) {
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
try {
fs.unlinkSync(tarPathTemp);
} catch (e) {}
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, platformAndArch);
});
}
if (response.statusCode !== 200) {
throw new Error(`Status ${response.statusCode}`);
}
response
.on('error', fail)
.pipe(tmpFile);
});
tmpFile
.on('error', fail)
.on('close', function () {
try {
// Attempt to rename
fs.renameSync(tarPathTemp, tarPathCache);
} catch (err) {
// Fall back to copy and unlink
fs.copyFileSync(tarPathTemp, tarPathCache);
fs.unlinkSync(tarPathTemp);
}
extractTarball(tarPathCache);
});
}
}
} catch (err) {

View File

@@ -1,3 +1,6 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
const url = require('url');
@@ -18,15 +21,16 @@ function env (key) {
return process.env[key];
}
module.exports = function () {
module.exports = function (log) {
try {
const proxy = new url.URL(proxies.map(env).find(is.string));
const tunnel = proxy.protocol === 'https:'
? tunnelAgent.httpsOverHttps
: tunnelAgent.httpsOverHttp;
const proxyAuth = proxy.username && proxy.password
? `${proxy.username}:${proxy.password}`
? `${decodeURIComponent(proxy.username)}:${decodeURIComponent(proxy.password)}`
: null;
log(`Via proxy ${proxy.protocol}//${proxy.hostname}:${proxy.port} ${proxyAuth ? 'with' : 'no'} credentials`);
return tunnel({
proxy: {
port: Number(proxy.port),

View File

@@ -1,3 +1,6 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
const is = require('./is');
@@ -15,6 +18,8 @@ const bool = {
/**
* Remove alpha channel, if any. This is a no-op if the image does not have an alpha channel.
*
* See also {@link /api-operation#flatten|flatten}.
*
* @example
* sharp('rgba.png')
* .removeAlpha()
@@ -30,19 +35,39 @@ function removeAlpha () {
}
/**
* Ensure alpha channel, if missing. The added alpha channel will be fully opaque. This is a no-op if the image already has an alpha channel.
* Ensure the output image has an alpha transparency channel.
* If missing, the added alpha channel will have the specified
* transparency level, defaulting to fully-opaque (1).
* This is a no-op if the image already has an alpha channel.
*
* @since 0.21.2
*
* @example
* sharp('rgb.jpg')
* // rgba.png will be a 4 channel image with a fully-opaque alpha channel
* await sharp('rgb.jpg')
* .ensureAlpha()
* .toFile('rgba.png', function(err, info) {
* // rgba.png is a 4 channel image with a fully opaque alpha channel
* });
* .toFile('rgba.png')
*
* @example
* // rgba is a 4 channel image with a fully-transparent alpha channel
* const rgba = await sharp(rgb)
* .ensureAlpha(0)
* .toBuffer();
*
* @param {number} [alpha=1] - alpha transparency level (0=fully-transparent, 1=fully-opaque)
* @returns {Sharp}
* @throws {Error} Invalid alpha transparency level
*/
function ensureAlpha () {
this.options.ensureAlpha = true;
function ensureAlpha (alpha) {
if (is.defined(alpha)) {
if (is.number(alpha) && is.inRange(alpha, 0, 1)) {
this.options.ensureAlpha = alpha;
} else {
throw is.invalidParameterError('alpha', 'number between 0 and 1', alpha);
}
} else {
this.options.ensureAlpha = 1;
}
return this;
}
@@ -50,29 +75,31 @@ function ensureAlpha () {
* Extract a single channel from a multi-channel image.
*
* @example
* sharp(input)
* // green.jpg is a greyscale image containing the green channel of the input
* await sharp(input)
* .extractChannel('green')
* .toFile('input_green.jpg', function(err, info) {
* // info.channels === 1
* // input_green.jpg contains the green channel of the input image
* });
* .toFile('green.jpg');
*
* @param {Number|String} channel - zero-indexed band number to extract, or `red`, `green` or `blue` as alternative to `0`, `1` or `2` respectively.
* @example
* // red1 is the red value of the first pixel, red2 the second pixel etc.
* const [red1, red2, ...] = await sharp(input)
* .extractChannel(0)
* .raw()
* .toBuffer();
*
* @param {number|string} channel - zero-indexed channel/band number to extract, or `red`, `green`, `blue` or `alpha`.
* @returns {Sharp}
* @throws {Error} Invalid channel
*/
function extractChannel (channel) {
if (channel === 'red') {
channel = 0;
} else if (channel === 'green') {
channel = 1;
} else if (channel === 'blue') {
channel = 2;
const channelMap = { red: 0, green: 1, blue: 2, alpha: 3 };
if (Object.keys(channelMap).includes(channel)) {
channel = channelMap[channel];
}
if (is.integer(channel) && is.inRange(channel, 0, 4)) {
this.options.extractChannel = channel;
} else {
throw is.invalidParameterError('channel', 'integer or one of: red, green, blue', channel);
throw is.invalidParameterError('channel', 'integer or one of: red, green, blue, alpha', channel);
}
return this;
}
@@ -85,10 +112,10 @@ function extractChannel (channel) {
* - sRGB: 0: Red, 1: Green, 2: Blue, 3: Alpha.
* - CMYK: 0: Magenta, 1: Cyan, 2: Yellow, 3: Black, 4: Alpha.
*
* Buffers may be any of the image formats supported by sharp: JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data.
* Buffers may be any of the image formats supported by sharp.
* For raw pixel input, the `options` object should contain a `raw` attribute, which follows the format of the attribute of the same name in the `sharp()` constructor.
*
* @param {Array<String|Buffer>|String|Buffer} images - one or more images (file paths, Buffers).
* @param {Array<string|Buffer>|string|Buffer} images - one or more images (file paths, Buffers).
* @param {Object} options - image options, see `sharp()` constructor.
* @returns {Sharp}
* @throws {Error} Invalid parameters
@@ -116,7 +143,7 @@ function joinChannel (images, options) {
* // 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}
* @throws {Error} Invalid parameters
*/

View File

@@ -1,3 +1,6 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
const color = require('color');
@@ -19,7 +22,12 @@ const colourspace = {
* Tint the image using the provided chroma while preserving the image luminance.
* An alpha channel may be present and will be unchanged by the operation.
*
* @param {String|Object} rgb - parsed by the [color](https://www.npmjs.org/package/color) module to extract chroma values.
* @example
* const output = await sharp(input)
* .tint({ r: 255, g: 240, b: 16 })
* .toBuffer();
*
* @param {string|Object} rgb - parsed by the [color](https://www.npmjs.org/package/color) module to extract chroma values.
* @returns {Sharp}
* @throws {Error} Invalid parameter
*/
@@ -37,6 +45,10 @@ function tint (rgb) {
* This may be overridden by other sharp operations such as `toColourspace('b-w')`,
* which will produce an output image containing one color channel.
* An alpha channel may be present, and will be unchanged by the operation.
*
* @example
* const output = await sharp(input).greyscale().toBuffer();
*
* @param {Boolean} [greyscale=true]
* @returns {Sharp}
*/
@@ -54,10 +66,57 @@ function grayscale (grayscale) {
return this.greyscale(grayscale);
}
/**
* Set the pipeline colourspace.
*
* The input image will be converted to the provided colourspace at the start of the pipeline.
* All operations will use this colourspace before converting to the output colourspace,
* as defined by {@link #tocolourspace|toColourspace}.
*
* This feature is experimental and has not yet been fully-tested with all operations.
*
* @since 0.29.0
*
* @example
* // Run pipeline in 16 bits per channel RGB while converting final result to 8 bits per channel sRGB.
* await sharp(input)
* .pipelineColourspace('rgb16')
* .toColourspace('srgb')
* .toFile('16bpc-pipeline-to-8bpc-output.png')
*
* @param {string} [colourspace] - pipeline colourspace e.g. `rgb16`, `scrgb`, `lab`, `grey16` [...](https://github.com/libvips/libvips/blob/41cff4e9d0838498487a00623462204eb10ee5b8/libvips/iofuncs/enumtypes.c#L774)
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
function pipelineColourspace (colourspace) {
if (!is.string(colourspace)) {
throw is.invalidParameterError('colourspace', 'string', colourspace);
}
this.options.colourspaceInput = colourspace;
return this;
}
/**
* Alternative spelling of `pipelineColourspace`.
* @param {string} [colorspace] - pipeline colorspace.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
function pipelineColorspace (colorspace) {
return this.pipelineColourspace(colorspace);
}
/**
* Set the output colourspace.
* By default output image will be web-friendly sRGB, with additional channels interpreted as alpha channels.
* @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/3c0bfdf74ce1dc37a6429bed47fa76f16e2cd70a/libvips/iofuncs/enumtypes.c#L777-L794)
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -71,7 +130,7 @@ function toColourspace (colourspace) {
/**
* Alternative spelling of `toColourspace`.
* @param {String} [colorspace] - output colorspace.
* @param {string} [colorspace] - output colorspace.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -82,8 +141,8 @@ function toColorspace (colorspace) {
/**
* Update a colour attribute of the this.options Object.
* @private
* @param {String} key
* @param {String|Object} value
* @param {string} key
* @param {string|Object} value
* @throws {Error} Invalid value
*/
function _setBackgroundColourOption (key, value) {
@@ -112,6 +171,8 @@ module.exports = function (Sharp) {
tint,
greyscale,
grayscale,
pipelineColourspace,
pipelineColorspace,
toColourspace,
toColorspace,
// Private

View File

@@ -1,3 +1,6 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
const is = require('./is');
@@ -43,6 +46,9 @@ const blend = {
* The images to composite must be the same size or smaller than the processed image.
* If both `top` and `left` options are provided, they take precedence over `gravity`.
*
* Any resize, rotate or extract operations in the same processing pipeline
* will always be applied to the input image before composition.
*
* The `blend` option can be one of `clear`, `source`, `over`, `in`, `out`, `atop`,
* `dest`, `dest-over`, `dest-in`, `dest-out`, `dest-atop`,
* `xor`, `add`, `saturate`, `multiply`, `screen`, `overlay`, `darken`, `lighten`,
@@ -50,9 +56,26 @@ const blend = {
* `hard-light`, `soft-light`, `difference`, `exclusion`.
*
* More information about blend modes can be found at
* https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode
* https://www.libvips.org/API/current/libvips-conversion.html#VipsBlendMode
* and https://www.cairographics.org/operators/
*
* @since 0.22.0
*
* @example
* await sharp(background)
* .composite([
* { input: layer1, gravity: 'northwest' },
* { input: layer2, gravity: 'southeast' },
* ])
* .toFile('combined.png');
*
* @example
* const output = await sharp('input.gif', { animated: true })
* .composite([
* { input: 'overlay.png', tile: true, blend: 'saturate' }
* ])
* .toBuffer();
*
* @example
* sharp('input.png')
* .rotate(180)
@@ -70,23 +93,37 @@ const blend = {
* });
*
* @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 {Number} [images[].input.create.width]
* @param {Number} [images[].input.create.height]
* @param {Number} [images[].input.create.channels] - 3-4
* @param {String|Object} [images[].input.create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
* @param {Object} [images[].input.text] - describes a new text image to be created.
* @param {string} [images[].input.text.text] - text to render as a UTF-8 string. It can contain Pango markup, for example `<i>Le</i>Monde`.
* @param {string} [images[].input.text.font] - font name to render with.
* @param {string} [images[].input.text.fontfile] - absolute filesystem path to a font file that can be used by `font`.
* @param {number} [images[].input.text.width=0] - integral number of pixels to word-wrap at. Lines of text wider than this will be broken at word boundaries.
* @param {number} [images[].input.text.height=0] - integral number of pixels high. When defined, `dpi` will be ignored and the text will automatically fit the pixel resolution defined by `width` and `height`. Will be ignored if `width` is not specified or set to 0.
* @param {string} [images[].input.text.align='left'] - text alignment (`'left'`, `'centre'`, `'center'`, `'right'`).
* @param {boolean} [images[].input.text.justify=false] - set this to true to apply justification to the text.
* @param {number} [images[].input.text.dpi=72] - the resolution (size) at which to render the text. Does not take effect if `height` is specified.
* @param {boolean} [images[].input.text.rgba=false] - set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for Pango markup features like `<span foreground="red">Red!</span>`.
* @param {number} [images[].input.text.spacing=0] - text line height in points. Will use the font line height if none is specified.
* @param {String} [images[].blend='over'] - how to blend this image with the image below.
* @param {String} [images[].gravity='centre'] - gravity at which to place the overlay.
* @param {Number} [images[].top] - the pixel offset from the top edge.
* @param {Number} [images[].left] - the pixel offset from the left edge.
* @param {Boolean} [images[].tile=false] - set to true to repeat the overlay image across the entire image with the given `gravity`.
* @param {Boolean} [images[].premultiplied=false] - set to true to avoid premultipling the image below. Equivalent to the `--premultiplied` vips option.
* @param {Boolean} [images[].premultiplied=false] - set to true to avoid premultiplying the image below. Equivalent to the `--premultiplied` vips option.
* @param {Number} [images[].density=72] - number representing the DPI for vector overlay image.
* @param {Object} [images[].raw] - describes overlay when using raw pixel data.
* @param {Number} [images[].raw.width]
* @param {Number} [images[].raw.height]
* @param {Number} [images[].raw.channels]
* @param {boolean} [images[].animated=false] - Set to `true` to read all frames/pages of an animated image.
* @param {string} [images[].failOn='warning'] - @see {@link /api-constructor#parameters|constructor parameters}
* @param {number|boolean} [images[].limitInputPixels=268402689] - @see {@link /api-constructor#parameters|constructor parameters}
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -98,14 +135,14 @@ function composite (images) {
if (!is.object(image)) {
throw is.invalidParameterError('image to composite', 'object', image);
}
const { raw, density } = image;
const inputOptions = (raw || density) ? { raw, density } : undefined;
const inputOptions = this._inputOptionsFromObject(image);
const composite = {
input: this._createInputDescriptor(image.input, inputOptions, { allowStream: false }),
blend: 'over',
tile: false,
left: -1,
top: -1,
left: 0,
top: 0,
hasOffset: false,
gravity: 0,
premultiplied: false
};
@@ -124,21 +161,23 @@ function composite (images) {
}
}
if (is.defined(image.left)) {
if (is.integer(image.left) && image.left >= 0) {
if (is.integer(image.left)) {
composite.left = image.left;
} else {
throw is.invalidParameterError('left', 'positive integer', image.left);
throw is.invalidParameterError('left', 'integer', image.left);
}
}
if (is.defined(image.top)) {
if (is.integer(image.top) && image.top >= 0) {
if (is.integer(image.top)) {
composite.top = image.top;
} 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');
} else {
composite.hasOffset = is.integer(image.top) && is.integer(image.left);
}
if (is.defined(image.gravity)) {
if (is.integer(image.gravity) && is.inRange(image.gravity, 0, 8)) {
@@ -156,7 +195,6 @@ function composite (images) {
throw is.invalidParameterError('premultiplied', 'boolean', image.premultiplied);
}
}
return composite;
});
return this;

View File

@@ -1,47 +1,33 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
const util = require('util');
const stream = require('stream');
const events = require('events');
const is = require('./is');
require('./libvips').hasVendoredLibvips();
let sharp;
/* istanbul ignore next */
try {
sharp = require('../build/Release/sharp.node');
} catch (err) {
// Bail early if bindings aren't available
const help = ['', 'Something went wrong installing the "sharp" module', '', err.message, ''];
if (/NODE_MODULE_VERSION/.test(err.message)) {
help.push('- Ensure the version of Node.js used at install time matches that used at runtime');
} else if (/invalid ELF header/.test(err.message)) {
help.push(`- Ensure "${process.platform}" is used at install time as well as runtime`);
} else {
help.push('- Remove the "node_modules/sharp" directory, run "npm install" and look for errors');
}
help.push(
'- Consult the installation documentation at https://sharp.pixelplumbing.com/en/stable/install/',
'- Search for this error at https://github.com/lovell/sharp/issues', ''
);
console.error(help.join('\n'));
process.exit(1);
}
require('./sharp');
// Use NODE_DEBUG=sharp to enable libvips warnings
const debuglog = util.debuglog('sharp');
/**
* @class Sharp
*
* Constructor factory to create an instance of `sharp`, to which further methods are chained.
*
* JPEG, PNG, WebP or TIFF format image data can be streamed out from this object.
* JPEG, PNG, WebP, GIF, AVIF or TIFF format image data can be streamed out from this object.
* When using Stream based output, derived attributes are available from the `info` event.
*
* Non-critical problems encountered during processing are emitted as `warning` events.
*
* Implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.
*
* @constructs Sharp
*
* @emits Sharp#info
* @emits Sharp#warning
*
* @example
* sharp('input.jpg')
* .resize(300, 200)
@@ -63,7 +49,7 @@ const debuglog = util.debuglog('sharp');
* readableStream.pipe(transformer).pipe(writableStream);
*
* @example
* // Create a blank 300x200 PNG image of semi-transluent red pixels
* // Create a blank 300x200 PNG image of semi-translucent red pixels
* sharp({
* create: {
* width: 300,
@@ -76,25 +62,106 @@ const debuglog = util.debuglog('sharp');
* .toBuffer()
* .then( ... );
*
* @param {(Buffer|String)} [input] - if present, can be
* a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
* a String containing the path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
* JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
* @example
* // Convert an animated GIF to an animated WebP
* await sharp('in.gif', { animated: true }).toFile('out.webp');
*
* @example
* // Read a raw array of pixels and save it to a png
* const input = Uint8Array.from([255, 255, 255, 0, 0, 0]); // or Uint8ClampedArray
* const image = sharp(input, {
* // because the input does not contain its dimensions or how many channels it has
* // we need to specify it in the constructor options
* raw: {
* width: 2,
* height: 1,
* channels: 3
* }
* });
* await image.toFile('my-two-pixels.png');
*
* @example
* // Generate RGB Gaussian noise
* await sharp({
* create: {
* width: 300,
* height: 200,
* channels: 3,
* noise: {
* type: 'gaussian',
* mean: 128,
* sigma: 30
* }
* }
* }).toFile('noise.png');
*
* @example
* // Generate an image from text
* await sharp({
* text: {
* text: 'Hello, world!',
* width: 400, // max width
* height: 300 // max height
* }
* }).toFile('text_bw.png');
*
* @example
* // Generate an rgba image from text using pango markup and font
* await sharp({
* text: {
* text: '<span foreground="red">Red!</span><span background="cyan">blue</span>',
* font: 'sans',
* rgba: true,
* dpi: 300
* }
* }).toFile('text_rgba.png');
*
* @param {(Buffer|ArrayBuffer|Uint8Array|Uint8ClampedArray|Int8Array|Uint16Array|Int16Array|Uint32Array|Int32Array|Float32Array|Float64Array|string)} [input] - if present, can be
* a Buffer / ArrayBuffer / Uint8Array / Uint8ClampedArray containing JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image data, or
* a TypedArray containing raw pixel image data, or
* a String containing the filesystem path to an JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image file.
* JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
* @param {Object} [options] - if present, is an Object with optional attributes.
* @param {Boolean} [options.failOnError=true] - by default halt processing and raise an error when loading invalid images.
* Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid.
* @param {Number} [options.density=72] - number representing the DPI for vector images.
* @param {Number} [options.pages=1] - number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages.
* @param {Number} [options.page=0] - page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based.
* @param {string} [options.failOn='warning'] - when to abort processing of invalid pixel data, one of (in order of sensitivity): 'none' (least), 'truncated', 'error' or 'warning' (most), higher levels imply lower levels, invalid metadata will always abort.
* @param {number|boolean} [options.limitInputPixels=268402689] - Do not process input images where the number of pixels
* (width x height) exceeds this limit. Assumes image dimensions contained in the input metadata can be trusted.
* An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF).
* @param {boolean} [options.unlimited=false] - Set this to `true` to remove safety features that help prevent memory exhaustion (JPEG, PNG, SVG, HEIF).
* @param {boolean} [options.sequentialRead=true] - Set this to `false` to use random access rather than sequential read. Some operations will do this automatically.
* @param {number} [options.density=72] - number representing the DPI for vector images in the range 1 to 100000.
* @param {number} [options.ignoreIcc=false] - should the embedded ICC profile, if any, be ignored.
* @param {number} [options.pages=1] - Number of pages to extract for multi-page input (GIF, WebP, TIFF), use -1 for all pages.
* @param {number} [options.page=0] - Page number to start extracting from for multi-page input (GIF, WebP, TIFF), zero based.
* @param {number} [options.subifd=-1] - subIFD (Sub Image File Directory) to extract for OME-TIFF, defaults to main image.
* @param {number} [options.level=0] - level to extract from a multi-level input (OpenSlide), zero based.
* @param {boolean} [options.animated=false] - Set to `true` to read all frames/pages of an animated image (GIF, WebP, TIFF), equivalent of setting `pages` to `-1`.
* @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering.
* @param {Number} [options.raw.width]
* @param {Number} [options.raw.height]
* @param {Number} [options.raw.channels] - 1-4
* @param {number} [options.raw.width] - integral number of pixels wide.
* @param {number} [options.raw.height] - integral number of pixels high.
* @param {number} [options.raw.channels] - integral number of channels, between 1 and 4.
* @param {boolean} [options.raw.premultiplied] - specifies that the raw input has already been premultiplied, set to `true`
* to avoid sharp premultiplying the image. (optional, default `false`)
* @param {Object} [options.create] - describes a new image to be created.
* @param {Number} [options.create.width]
* @param {Number} [options.create.height]
* @param {Number} [options.create.channels] - 3-4
* @param {String|Object} [options.create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
* @param {number} [options.create.width] - integral number of pixels wide.
* @param {number} [options.create.height] - integral number of pixels high.
* @param {number} [options.create.channels] - integral number of channels, either 3 (RGB) or 4 (RGBA).
* @param {string|Object} [options.create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
* @param {Object} [options.create.noise] - describes a noise to be created.
* @param {string} [options.create.noise.type] - type of generated noise, currently only `gaussian` is supported.
* @param {number} [options.create.noise.mean] - mean of pixels in generated noise.
* @param {number} [options.create.noise.sigma] - standard deviation of pixels in generated noise.
* @param {Object} [options.text] - describes a new text image to be created.
* @param {string} [options.text.text] - text to render as a UTF-8 string. It can contain Pango markup, for example `<i>Le</i>Monde`.
* @param {string} [options.text.font] - font name to render with.
* @param {string} [options.text.fontfile] - absolute filesystem path to a font file that can be used by `font`.
* @param {number} [options.text.width=0] - Integral number of pixels to word-wrap at. Lines of text wider than this will be broken at word boundaries.
* @param {number} [options.text.height=0] - Maximum integral number of pixels high. When defined, `dpi` will be ignored and the text will automatically fit the pixel resolution defined by `width` and `height`. Will be ignored if `width` is not specified or set to 0.
* @param {string} [options.text.align='left'] - Alignment style for multi-line text (`'left'`, `'centre'`, `'center'`, `'right'`).
* @param {boolean} [options.text.justify=false] - set this to true to apply justification to the text.
* @param {number} [options.text.dpi=72] - the resolution (size) at which to render the text. Does not take effect if `height` is specified.
* @param {boolean} [options.text.rgba=false] - set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for pango markup features like `<span foreground="red">Red!</span>`.
* @param {number} [options.text.spacing=0] - text line height in points. Will use the font line height if none is specified.
* @param {string} [options.text.wrap='word'] - word wrapping style when width is provided, one of: 'word', 'char', 'charWord' (prefer char, fallback to word) or 'none'.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -107,9 +174,6 @@ const Sharp = function (input, options) {
}
stream.Duplex.call(this);
this.options = {
// input options
sequentialRead: false,
limitInputPixels: Math.pow(0x3FFF, 2),
// resize options
topOffsetPre: -1,
leftOffsetPre: -1,
@@ -136,7 +200,16 @@ const Sharp = function (input, options) {
extendLeft: 0,
extendRight: 0,
extendBackground: [0, 0, 0, 255],
extendWith: 'background',
withoutEnlargement: false,
withoutReduction: false,
affineMatrix: [],
affineBackground: [0, 0, 0, 255],
affineIdx: 0,
affineIdy: 0,
affineOdx: 0,
affineOdy: 0,
affineInterpolator: this.constructor.interpolators.bilinear,
kernel: 'lanczos3',
fastShrinkOnLoad: true,
// operations
@@ -144,29 +217,42 @@ const Sharp = function (input, options) {
tintB: 128,
flatten: false,
flattenBackground: [0, 0, 0],
unflatten: false,
negate: false,
negateAlpha: true,
medianSize: 0,
blurSigma: 0,
sharpenSigma: 0,
sharpenFlat: 1,
sharpenJagged: 2,
sharpenM1: 1,
sharpenM2: 2,
sharpenX1: 2,
sharpenY2: 10,
sharpenY3: 20,
threshold: 0,
thresholdGrayscale: true,
trimBackground: [],
trimThreshold: 0,
gamma: 0,
gammaOut: 0,
greyscale: false,
normalise: 0,
normalise: false,
normaliseLower: 1,
normaliseUpper: 99,
claheWidth: 0,
claheHeight: 0,
claheMaxSlope: 3,
brightness: 1,
saturation: 1,
hue: 0,
lightness: 0,
booleanBufferIn: null,
booleanFileIn: '',
joinChannelIn: [],
extractChannel: -1,
removeAlpha: false,
ensureAlpha: false,
ensureAlpha: -1,
colourspace: 'srgb',
colourspaceInput: 'last',
composite: [],
// output
fileOut: '',
@@ -174,6 +260,9 @@ const Sharp = function (input, options) {
streamOut: false,
withMetadata: false,
withMetadataOrientation: -1,
withMetadataDensity: 0,
withMetadataIcc: '',
withMetadataStrs: {},
resolveWithObject: false,
// output format
jpegQuality: 80,
@@ -185,81 +274,163 @@ const Sharp = function (input, options) {
jpegOptimiseCoding: true,
jpegQuantisationTable: 0,
pngProgressive: false,
pngCompressionLevel: 9,
pngCompressionLevel: 6,
pngAdaptiveFiltering: false,
pngPalette: false,
pngQuality: 100,
pngColours: 256,
pngEffort: 7,
pngBitdepth: 8,
pngDither: 1,
jp2Quality: 80,
jp2TileHeight: 512,
jp2TileWidth: 512,
jp2Lossless: false,
jp2ChromaSubsampling: '4:4:4',
webpQuality: 80,
webpAlphaQuality: 100,
webpLossless: false,
webpNearLossless: false,
webpSmartSubsample: false,
webpReductionEffort: 4,
webpPreset: 'default',
webpEffort: 4,
webpMinSize: false,
webpMixed: false,
gifBitdepth: 8,
gifEffort: 7,
gifDither: 1,
gifInterFrameMaxError: 0,
gifInterPaletteMaxError: 3,
gifReuse: true,
gifProgressive: false,
tiffQuality: 80,
tiffCompression: 'jpeg',
tiffPredictor: 'horizontal',
tiffPyramid: false,
tiffSquash: false,
tiffBitdepth: 8,
tiffTile: false,
tiffTileHeight: 256,
tiffTileWidth: 256,
tiffXres: 1.0,
tiffYres: 1.0,
heifQuality: 80,
tiffResolutionUnit: 'inch',
heifQuality: 50,
heifLossless: false,
heifCompression: 'hevc',
heifCompression: 'av1',
heifEffort: 4,
heifChromaSubsampling: '4:4:4',
jxlDistance: 1,
jxlDecodingTier: 0,
jxlEffort: 7,
jxlLossless: false,
rawDepth: 'uchar',
tileSize: 256,
tileOverlap: 0,
tileContainer: 'fs',
tileLayout: 'dz',
tileFormat: 'last',
tileDepth: 'last',
tileAngle: 0,
tileSkipBlanks: -1,
linearA: 1,
linearB: 0,
tileBackground: [255, 255, 255, 255],
tileCentre: false,
tileId: 'https://example.com/iiif',
tileBasename: '',
timeoutSeconds: 0,
linearA: [],
linearB: [],
// Function to notify of libvips warnings
debuglog: debuglog,
debuglog: warning => {
this.emit('warning', warning);
debuglog(warning);
},
// Function to notify of queue length changes
queueListener: function (queueLength) {
queue.emit('change', queueLength);
Sharp.queue.emit('change', queueLength);
}
};
this.options.input = this._createInputDescriptor(input, options, { allowStream: true });
return this;
};
util.inherits(Sharp, stream.Duplex);
Object.setPrototypeOf(Sharp.prototype, stream.Duplex.prototype);
Object.setPrototypeOf(Sharp, stream.Duplex);
/**
* An EventEmitter that emits a `change` event when a task is either:
* - queued, waiting for _libuv_ to provide a worker thread
* - complete
* @member
* Take a "snapshot" of the Sharp instance, returning a new instance.
* Cloned instances inherit the input of their parent instance.
* This allows multiple output Streams and therefore multiple processing pipelines to share a single input Stream.
*
* @example
* sharp.queue.on('change', function(queueLength) {
* console.log('Queue contains ' + queueLength + ' task(s)');
* });
*/
const queue = new events.EventEmitter();
Sharp.queue = queue;
/**
* An Object containing nested boolean values representing the available input and output formats/methods.
* const pipeline = sharp().rotate();
* pipeline.clone().resize(800, 600).pipe(firstWritableStream);
* pipeline.clone().extract({ left: 20, top: 20, width: 100, height: 100 }).pipe(secondWritableStream);
* readableStream.pipe(pipeline);
* // firstWritableStream receives auto-rotated, resized readableStream
* // secondWritableStream receives auto-rotated, extracted region of readableStream
*
* @example
* console.log(sharp.format);
* @returns {Object}
* // Create a pipeline that will download an image, resize it and format it to different files
* // Using Promises to know when the pipeline is complete
* const fs = require("fs");
* const got = require("got");
* const sharpStream = sharp({ failOn: 'none' });
*
* const promises = [];
*
* promises.push(
* sharpStream
* .clone()
* .jpeg({ quality: 100 })
* .toFile("originalFile.jpg")
* );
*
* promises.push(
* sharpStream
* .clone()
* .resize({ width: 500 })
* .jpeg({ quality: 80 })
* .toFile("optimized-500.jpg")
* );
*
* promises.push(
* sharpStream
* .clone()
* .resize({ width: 500 })
* .webp({ quality: 80 })
* .toFile("optimized-500.webp")
* );
*
* // https://github.com/sindresorhus/got/blob/main/documentation/3-streams.md
* got.stream("https://www.example.com/some-file.jpg").pipe(sharpStream);
*
* Promise.all(promises)
* .then(res => { console.log("Done!", res); })
* .catch(err => {
* console.error("Error processing files, let's clean it up", err);
* try {
* fs.unlinkSync("originalFile.jpg");
* fs.unlinkSync("optimized-500.jpg");
* fs.unlinkSync("optimized-500.webp");
* } catch (e) {}
* });
*
* @returns {Sharp}
*/
Sharp.format = sharp.format();
/**
* An Object containing the version numbers of libvips and its dependencies.
* @member
* @example
* console.log(sharp.versions);
*/
Sharp.versions = {
vips: sharp.libvipsVersion()
};
try {
Sharp.versions = require('../vendor/versions.json');
} catch (err) {}
function clone () {
// Clone existing options
const clone = this.constructor.call();
clone.options = Object.assign({}, this.options);
// Pass 'finish' event to clone for Stream-based input
if (this._isStreamInput()) {
this.on('finish', () => {
// Clone inherits input data
this._flattenBufferIn();
clone.options.bufferIn = this.options.bufferIn;
clone.emit('finish');
});
}
return clone;
}
Object.assign(Sharp.prototype, { clone });
/**
* Export constructor.

1661
lib/index.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,6 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
const Sharp = require('./constructor');

View File

@@ -1,49 +1,140 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
const color = require('color');
const is = require('./is');
const sharp = require('../build/Release/sharp.node');
const sharp = require('./sharp');
/**
* Justication alignment
* @member
* @private
*/
const align = {
left: 'low',
center: 'centre',
centre: 'centre',
right: 'high'
};
/**
* Extract input options, if any, from an object.
* @private
*/
function _inputOptionsFromObject (obj) {
const { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd } = obj;
return [raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd].some(is.defined)
? { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd }
: undefined;
}
/**
* Create Object containing input and input-related options.
* @private
*/
function _createInputDescriptor (input, inputOptions, containerOptions) {
const inputDescriptor = { failOnError: true };
const inputDescriptor = {
failOn: 'warning',
limitInputPixels: Math.pow(0x3FFF, 2),
ignoreIcc: false,
unlimited: false,
sequentialRead: true
};
if (is.string(input)) {
// filesystem
inputDescriptor.file = input;
} else if (is.buffer(input)) {
// Buffer
if (input.length === 0) {
throw Error('Input Buffer is empty');
}
inputDescriptor.buffer = input;
} else if (is.arrayBuffer(input)) {
if (input.byteLength === 0) {
throw Error('Input bit Array is empty');
}
inputDescriptor.buffer = Buffer.from(input, 0, input.byteLength);
} else if (is.typedArray(input)) {
if (input.length === 0) {
throw Error('Input Bit Array is empty');
}
inputDescriptor.buffer = Buffer.from(input.buffer, input.byteOffset, input.byteLength);
} else if (is.plainObject(input) && !is.defined(inputOptions)) {
// Plain Object descriptor, e.g. create
inputOptions = input;
if (is.plainObject(inputOptions.raw) || is.bool(inputOptions.failOnError)) {
// Raw Stream
if (_inputOptionsFromObject(inputOptions)) {
// Stream with options
inputDescriptor.buffer = [];
}
} else if (!is.defined(input) && is.object(containerOptions) && containerOptions.allowStream) {
// Stream
} else if (!is.defined(input) && !is.defined(inputOptions) && is.object(containerOptions) && containerOptions.allowStream) {
// Stream without options
inputDescriptor.buffer = [];
} else {
throw new Error('Unsupported input ' + typeof input);
throw new Error(`Unsupported input '${input}' of type ${typeof input}${
is.defined(inputOptions) ? ` when also providing options of type ${typeof inputOptions}` : ''
}`);
}
if (is.object(inputOptions)) {
// Fail on error
// Deprecated: failOnError
if (is.defined(inputOptions.failOnError)) {
if (is.bool(inputOptions.failOnError)) {
inputDescriptor.failOnError = inputOptions.failOnError;
inputDescriptor.failOn = inputOptions.failOnError ? 'warning' : 'none';
} else {
throw is.invalidParameterError('failOnError', 'boolean', inputOptions.failOnError);
}
}
// failOn
if (is.defined(inputOptions.failOn)) {
if (is.string(inputOptions.failOn) && is.inArray(inputOptions.failOn, ['none', 'truncated', 'error', 'warning'])) {
inputDescriptor.failOn = inputOptions.failOn;
} else {
throw is.invalidParameterError('failOn', 'one of: none, truncated, error, warning', inputOptions.failOn);
}
}
// Density
if (is.defined(inputOptions.density)) {
if (is.inRange(inputOptions.density, 1, 2400)) {
if (is.inRange(inputOptions.density, 1, 100000)) {
inputDescriptor.density = inputOptions.density;
} else {
throw is.invalidParameterError('density', 'number between 1 and 2400', inputOptions.density);
throw is.invalidParameterError('density', 'number between 1 and 100000', inputOptions.density);
}
}
// Ignore embeddded ICC profile
if (is.defined(inputOptions.ignoreIcc)) {
if (is.bool(inputOptions.ignoreIcc)) {
inputDescriptor.ignoreIcc = inputOptions.ignoreIcc;
} else {
throw is.invalidParameterError('ignoreIcc', 'boolean', inputOptions.ignoreIcc);
}
}
// limitInputPixels
if (is.defined(inputOptions.limitInputPixels)) {
if (is.bool(inputOptions.limitInputPixels)) {
inputDescriptor.limitInputPixels = inputOptions.limitInputPixels
? Math.pow(0x3FFF, 2)
: 0;
} else if (is.integer(inputOptions.limitInputPixels) && is.inRange(inputOptions.limitInputPixels, 0, Number.MAX_SAFE_INTEGER)) {
inputDescriptor.limitInputPixels = inputOptions.limitInputPixels;
} else {
throw is.invalidParameterError('limitInputPixels', 'positive integer', inputOptions.limitInputPixels);
}
}
// unlimited
if (is.defined(inputOptions.unlimited)) {
if (is.bool(inputOptions.unlimited)) {
inputDescriptor.unlimited = inputOptions.unlimited;
} else {
throw is.invalidParameterError('unlimited', 'boolean', inputOptions.unlimited);
}
}
// sequentialRead
if (is.defined(inputOptions.sequentialRead)) {
if (is.bool(inputOptions.sequentialRead)) {
inputDescriptor.sequentialRead = inputOptions.sequentialRead;
} else {
throw is.invalidParameterError('sequentialRead', 'boolean', inputOptions.sequentialRead);
}
}
// Raw pixel input
@@ -57,19 +148,78 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
inputDescriptor.rawWidth = inputOptions.raw.width;
inputDescriptor.rawHeight = inputOptions.raw.height;
inputDescriptor.rawChannels = inputOptions.raw.channels;
inputDescriptor.rawPremultiplied = !!inputOptions.raw.premultiplied;
switch (input.constructor) {
case Uint8Array:
case Uint8ClampedArray:
inputDescriptor.rawDepth = 'uchar';
break;
case Int8Array:
inputDescriptor.rawDepth = 'char';
break;
case Uint16Array:
inputDescriptor.rawDepth = 'ushort';
break;
case Int16Array:
inputDescriptor.rawDepth = 'short';
break;
case Uint32Array:
inputDescriptor.rawDepth = 'uint';
break;
case Int32Array:
inputDescriptor.rawDepth = 'int';
break;
case Float32Array:
inputDescriptor.rawDepth = 'float';
break;
case Float64Array:
inputDescriptor.rawDepth = 'double';
break;
default:
inputDescriptor.rawDepth = 'uchar';
break;
}
} else {
throw new Error('Expected width, height and channels for raw pixel input');
}
}
// Multi-page input (GIF, TIFF, PDF)
if (is.defined(inputOptions.animated)) {
if (is.bool(inputOptions.animated)) {
inputDescriptor.pages = inputOptions.animated ? -1 : 1;
} else {
throw is.invalidParameterError('animated', 'boolean', inputOptions.animated);
}
}
if (is.defined(inputOptions.pages)) {
if (is.integer(inputOptions.pages) && is.inRange(inputOptions.pages, -1, 100000)) {
inputDescriptor.pages = inputOptions.pages;
} else {
throw is.invalidParameterError('pages', 'integer between -1 and 100000', inputOptions.pages);
}
}
if (is.defined(inputOptions.page)) {
if (is.integer(inputOptions.page) && is.inRange(inputOptions.page, 0, 100000)) {
inputDescriptor.page = inputOptions.page;
} else {
throw is.invalidParameterError('page', 'integer between 0 and 100000', inputOptions.page);
}
}
// Multi-level input (OpenSlide)
if (is.defined(inputOptions.level)) {
if (is.integer(inputOptions.level) && is.inRange(inputOptions.level, 0, 256)) {
inputDescriptor.level = inputOptions.level;
} else {
throw is.invalidParameterError('level', 'integer between 0 and 256', inputOptions.level);
}
}
// Sub Image File Directory (TIFF)
if (is.defined(inputOptions.subifd)) {
if (is.integer(inputOptions.subifd) && is.inRange(inputOptions.subifd, -1, 100000)) {
inputDescriptor.subifd = inputOptions.subifd;
} else {
throw is.invalidParameterError('subifd', 'integer between -1 and 100000', inputOptions.subifd);
}
}
// Create new image
@@ -78,22 +228,132 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
is.object(inputOptions.create) &&
is.integer(inputOptions.create.width) && inputOptions.create.width > 0 &&
is.integer(inputOptions.create.height) && inputOptions.create.height > 0 &&
is.integer(inputOptions.create.channels) && is.inRange(inputOptions.create.channels, 3, 4) &&
is.defined(inputOptions.create.background)
is.integer(inputOptions.create.channels)
) {
inputDescriptor.createWidth = inputOptions.create.width;
inputDescriptor.createHeight = inputOptions.create.height;
inputDescriptor.createChannels = inputOptions.create.channels;
const background = color(inputOptions.create.background);
inputDescriptor.createBackground = [
background.red(),
background.green(),
background.blue(),
Math.round(background.alpha() * 255)
];
// Noise
if (is.defined(inputOptions.create.noise)) {
if (!is.object(inputOptions.create.noise)) {
throw new Error('Expected noise to be an object');
}
if (!is.inArray(inputOptions.create.noise.type, ['gaussian'])) {
throw new Error('Only gaussian noise is supported at the moment');
}
if (!is.inRange(inputOptions.create.channels, 1, 4)) {
throw is.invalidParameterError('create.channels', 'number between 1 and 4', inputOptions.create.channels);
}
inputDescriptor.createNoiseType = inputOptions.create.noise.type;
if (is.number(inputOptions.create.noise.mean) && is.inRange(inputOptions.create.noise.mean, 0, 10000)) {
inputDescriptor.createNoiseMean = inputOptions.create.noise.mean;
} else {
throw is.invalidParameterError('create.noise.mean', 'number between 0 and 10000', inputOptions.create.noise.mean);
}
if (is.number(inputOptions.create.noise.sigma) && is.inRange(inputOptions.create.noise.sigma, 0, 10000)) {
inputDescriptor.createNoiseSigma = inputOptions.create.noise.sigma;
} else {
throw is.invalidParameterError('create.noise.sigma', 'number between 0 and 10000', inputOptions.create.noise.sigma);
}
} else if (is.defined(inputOptions.create.background)) {
if (!is.inRange(inputOptions.create.channels, 3, 4)) {
throw is.invalidParameterError('create.channels', 'number between 3 and 4', inputOptions.create.channels);
}
const background = color(inputOptions.create.background);
inputDescriptor.createBackground = [
background.red(),
background.green(),
background.blue(),
Math.round(background.alpha() * 255)
];
} else {
throw new Error('Expected valid noise or background to create a new input image');
}
delete inputDescriptor.buffer;
} else {
throw new Error('Expected 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');
}
}
// Create a new image with text
if (is.defined(inputOptions.text)) {
if (is.object(inputOptions.text) && is.string(inputOptions.text.text)) {
inputDescriptor.textValue = inputOptions.text.text;
if (is.defined(inputOptions.text.height) && is.defined(inputOptions.text.dpi)) {
throw new Error('Expected only one of dpi or height');
}
if (is.defined(inputOptions.text.font)) {
if (is.string(inputOptions.text.font)) {
inputDescriptor.textFont = inputOptions.text.font;
} else {
throw is.invalidParameterError('text.font', 'string', inputOptions.text.font);
}
}
if (is.defined(inputOptions.text.fontfile)) {
if (is.string(inputOptions.text.fontfile)) {
inputDescriptor.textFontfile = inputOptions.text.fontfile;
} else {
throw is.invalidParameterError('text.fontfile', 'string', inputOptions.text.fontfile);
}
}
if (is.defined(inputOptions.text.width)) {
if (is.number(inputOptions.text.width)) {
inputDescriptor.textWidth = inputOptions.text.width;
} else {
throw is.invalidParameterError('text.textWidth', 'number', inputOptions.text.width);
}
}
if (is.defined(inputOptions.text.height)) {
if (is.number(inputOptions.text.height)) {
inputDescriptor.textHeight = inputOptions.text.height;
} else {
throw is.invalidParameterError('text.height', 'number', inputOptions.text.height);
}
}
if (is.defined(inputOptions.text.align)) {
if (is.string(inputOptions.text.align) && is.string(this.constructor.align[inputOptions.text.align])) {
inputDescriptor.textAlign = this.constructor.align[inputOptions.text.align];
} else {
throw is.invalidParameterError('text.align', 'valid alignment', inputOptions.text.align);
}
}
if (is.defined(inputOptions.text.justify)) {
if (is.bool(inputOptions.text.justify)) {
inputDescriptor.textJustify = inputOptions.text.justify;
} else {
throw is.invalidParameterError('text.justify', 'boolean', inputOptions.text.justify);
}
}
if (is.defined(inputOptions.text.dpi)) {
if (is.number(inputOptions.text.dpi) && is.inRange(inputOptions.text.dpi, 1, 100000)) {
inputDescriptor.textDpi = inputOptions.text.dpi;
} else {
throw is.invalidParameterError('text.dpi', 'number between 1 and 100000', inputOptions.text.dpi);
}
}
if (is.defined(inputOptions.text.rgba)) {
if (is.bool(inputOptions.text.rgba)) {
inputDescriptor.textRgba = inputOptions.text.rgba;
} else {
throw is.invalidParameterError('text.rgba', 'bool', inputOptions.text.rgba);
}
}
if (is.defined(inputOptions.text.spacing)) {
if (is.number(inputOptions.text.spacing)) {
inputDescriptor.textSpacing = inputOptions.text.spacing;
} else {
throw is.invalidParameterError('text.spacing', 'number', inputOptions.text.spacing);
}
}
if (is.defined(inputOptions.text.wrap)) {
if (is.string(inputOptions.text.wrap) && is.inArray(inputOptions.text.wrap, ['word', 'char', 'wordChar', 'none'])) {
inputDescriptor.textWrap = inputOptions.text.wrap;
} else {
throw is.invalidParameterError('text.wrap', 'one of: word, char, wordChar, none', inputOptions.text.wrap);
}
}
delete inputDescriptor.buffer;
} else {
throw new Error('Expected a valid string to create an image with text.');
}
}
} else if (is.defined(inputOptions)) {
@@ -106,7 +366,7 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
* Handle incoming Buffer chunk on Writable Stream.
* @private
* @param {Buffer} chunk
* @param {String} encoding - unused
* @param {string} encoding - unused
* @param {Function} callback
*/
function _write (chunk, encoding, callback) {
@@ -142,60 +402,45 @@ function _flattenBufferIn () {
/**
* Are we expecting Stream-based input?
* @private
* @returns {Boolean}
* @returns {boolean}
*/
function _isStreamInput () {
return Array.isArray(this.options.input.buffer);
}
/**
* Take a "snapshot" of the Sharp instance, returning a new instance.
* Cloned instances inherit the input of their parent instance.
* This allows multiple output Streams and therefore multiple processing pipelines to share a single input Stream.
* Fast access to (uncached) image metadata without decoding any compressed pixel data.
*
* @example
* const pipeline = sharp().rotate();
* pipeline.clone().resize(800, 600).pipe(firstWritableStream);
* pipeline.clone().extract({ left: 20, top: 20, width: 100, height: 100 }).pipe(secondWritableStream);
* readableStream.pipe(pipeline);
* // firstWritableStream receives auto-rotated, resized readableStream
* // secondWritableStream receives auto-rotated, extracted region of readableStream
* This is read from the header of the input image.
* It does not take into consideration any operations to be applied to the output image,
* such as resize or rotate.
*
* Dimensions in the response will respect the `page` and `pages` properties of the
* {@link /api-constructor#parameters|constructor parameters}.
*
* @returns {Sharp}
*/
function clone () {
// Clone existing options
const clone = this.constructor.call();
clone.options = Object.assign({}, this.options);
// Pass 'finish' event to clone for Stream-based input
if (this._isStreamInput()) {
this.on('finish', () => {
// Clone inherits input data
this._flattenBufferIn();
clone.options.bufferIn = this.options.bufferIn;
clone.emit('finish');
});
}
return clone;
}
/**
* Fast access to (uncached) image metadata without decoding any compressed image data.
* A `Promise` is returned when `callback` is not provided.
*
* - `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
* - `size`: Total size of image in bytes, for Stream and Buffer input only
* - `width`: Number of pixels wide (EXIF orientation is not taken into consideration)
* - `height`: Number of pixels high (EXIF orientation is not taken into consideration)
* - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L636)
* - `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below)
* - `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below)
* - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://www.libvips.org/API/current/VipsImage.html#VipsInterpretation)
* - `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
* - `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L672)
* - `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://www.libvips.org/API/current/VipsImage.html#VipsBandFormat)
* - `density`: Number of pixels per inch (DPI), if present
* - `chromaSubsampling`: String containing JPEG chroma subsampling, `4:2:0` or `4:4:4` for RGB, `4:2:0:4` or `4:4:4:4` for CMYK
* - `isProgressive`: Boolean indicating whether the image is interlaced using a progressive scan
* - `pages`: Number of pages/frames contained within the image, with support for TIFF, HEIF, PDF, animated GIF and animated WebP
* - `pageHeight`: Number of pixels high each page in this PDF image will be.
* - `pageHeight`: Number of pixels high each page in a multi-page image will be.
* - `paletteBitDepth`: Bit depth of palette-based image (GIF, PNG).
* - `loop`: Number of times to loop an animated image, zero refers to a continuous loop.
* - `delay`: Delay in ms between each page in an animated image, provided as an array of integers.
* - `pagePrimary`: Number of the primary page in a HEIF image
* - `levels`: Details of each level in a multi-level image provided as an array of objects, requires libvips compiled with support for OpenSlide
* - `subifds`: Number of Sub Image File Directories in an OME-TIFF image
* - `background`: Default background colour, if present, for PNG (bKGD) and GIF images, either an RGB Object or a single greyscale value
* - `compression`: The encoder used to compress an HEIF file, `av1` (AVIF) or `hevc` (HEIC)
* - `resolutionUnit`: The unit of resolution (density), either `inch` or `cm`, if present
* - `hasProfile`: Boolean indicating the presence of an embedded ICC profile
* - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
* - `orientation`: Number value of the EXIF Orientation header, if present
@@ -203,6 +448,11 @@ function clone () {
* - `icc`: Buffer containing raw [ICC](https://www.npmjs.com/package/icc) profile data, if present
* - `iptc`: Buffer containing raw IPTC data, if present
* - `xmp`: Buffer containing raw XMP data, if present
* - `tifftagPhotoshop`: Buffer containing raw TIFFTAG_PHOTOSHOP data, if present
* - `formatMagick`: String containing format for images loaded via *magick
*
* @example
* const metadata = await sharp(input).metadata();
*
* @example
* const image = sharp(inputJpg);
@@ -218,6 +468,17 @@ function clone () {
* // data contains a WebP image half the width and height of the original JPEG
* });
*
* @example
* // Based on EXIF rotation metadata, get the right-side-up width and height:
*
* const size = getNormalSize(await sharp(input).metadata());
*
* function getNormalSize({ width, height, orientation }) {
* return (orientation || 0) >= 5
* ? { width: height, height: width }
* : { width, height };
* }
*
* @param {Function} [callback] - called with the arguments `(err, metadata)`
* @returns {Promise<Object>|Sharp}
*/
@@ -235,7 +496,7 @@ function metadata (callback) {
} else {
if (this._isStreamInput()) {
return new Promise((resolve, reject) => {
this.on('finish', () => {
const finished = () => {
this._flattenBufferIn();
sharp.metadata(this.options, (err, metadata) => {
if (err) {
@@ -244,7 +505,12 @@ function metadata (callback) {
resolve(metadata);
}
});
});
};
if (this.writableFinished) {
finished();
} else {
this.once('finish', finished);
}
});
} else {
return new Promise((resolve, reject) => {
@@ -275,8 +541,13 @@ function metadata (callback) {
* - `minY` (y-coordinate of one of the pixel where the minimum lies)
* - `maxX` (x-coordinate of one of the pixel where the maximum lies)
* - `maxY` (y-coordinate of one of the pixel where the maximum lies)
* - `isOpaque`: Value to identify if the image is opaque or transparent, based on the presence and use of alpha channel
* - `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental)
* - `isOpaque`: Is the image fully opaque? Will be `true` if the image has no alpha channel or if every pixel is fully opaque.
* - `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any.
* - `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any.
* - `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram.
*
* **Note**: Statistics are derived from the original input image. Any operations performed on the image must first be
* written to a buffer in order to run `stats` on the result (see third example).
*
* @example
* const image = sharp(inputJpg);
@@ -286,6 +557,17 @@ function metadata (callback) {
* // stats contains the channel-wise statistics array and the isOpaque value
* });
*
* @example
* const { entropy, sharpness, dominant } = await sharp(input).stats();
* const { r, g, b } = dominant;
*
* @example
* const image = sharp(input);
* // store intermediate result
* const part = await image.extract(region).toBuffer();
* // create new instance to obtain statistics of extracted region
* const stats = await sharp(part).stats();
*
* @param {Function} [callback] - called with the arguments `(err, stats)`
* @returns {Promise<Object>}
*/
@@ -328,43 +610,6 @@ function stats (callback) {
}
}
/**
* 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.
* The default limit is 268402689 (0x3FFF x 0x3FFF) pixels.
* @param {(Number|Boolean)} limit - an integral Number of pixels, zero or false to remove limit, true to use default limit.
* @returns {Sharp}
* @throws {Error} Invalid limit
*/
function limitInputPixels (limit) {
// if we pass in false we represent the integer as 0 to disable
if (limit === false) {
limit = 0;
} else if (limit === true) {
limit = Math.pow(0x3FFF, 2);
}
if (is.integer(limit) && limit >= 0) {
this.options.limitInputPixels = limit;
} else {
throw is.invalidParameterError('limitInputPixels', 'integer', limit);
}
return this;
}
/**
* An advanced setting that switches the libvips access method to `VIPS_ACCESS_SEQUENTIAL`.
* This will reduce memory usage and can improve performance on some systems.
*
* The default behaviour *before* function call is `false`, meaning the libvips access method is not sequential.
*
* @param {Boolean} [sequentialRead=true]
* @returns {Sharp}
*/
function sequentialRead (sequentialRead) {
this.options.sequentialRead = is.bool(sequentialRead) ? sequentialRead : true;
return this;
}
/**
* Decorate the Sharp prototype with input-related functions.
* @private
@@ -372,15 +617,15 @@ function sequentialRead (sequentialRead) {
module.exports = function (Sharp) {
Object.assign(Sharp.prototype, {
// Private
_inputOptionsFromObject,
_createInputDescriptor,
_write,
_flattenBufferIn,
_isStreamInput,
// Public
clone,
metadata,
stats,
limitInputPixels,
sequentialRead
stats
});
// Class attributes
Sharp.align = align;
};

View File

@@ -1,3 +1,6 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
/**
@@ -21,7 +24,7 @@ const object = function (val) {
* @private
*/
const plainObject = function (val) {
return object(val) && Object.prototype.toString.call(val) === '[object Object]';
return Object.prototype.toString.call(val) === '[object Object]';
};
/**
@@ -45,7 +48,38 @@ const bool = function (val) {
* @private
*/
const buffer = function (val) {
return object(val) && val instanceof Buffer;
return val instanceof Buffer;
};
/**
* Is this value a typed array object?. E.g. Uint8Array or Uint8ClampedArray?
* @private
*/
const typedArray = function (val) {
if (defined(val)) {
switch (val.constructor) {
case Uint8Array:
case Uint8ClampedArray:
case Int8Array:
case Uint16Array:
case Int16Array:
case Uint32Array:
case Int32Array:
case Float32Array:
case Float64Array:
return true;
}
}
return false;
};
/**
* Is this value an ArrayBuffer object?
* @private
*/
const arrayBuffer = function (val) {
return val instanceof ArrayBuffer;
};
/**
@@ -69,7 +103,7 @@ const number = function (val) {
* @private
*/
const integer = function (val) {
return number(val) && val % 1 === 0;
return Number.isInteger(val);
};
/**
@@ -85,14 +119,14 @@ const inRange = function (val, min, max) {
* @private
*/
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.
*
* @param {String} name - parameter name.
* @param {String} expected - description of the type/value/range expected.
* @param {string} name - parameter name.
* @param {string} expected - description of the type/value/range expected.
* @param {*} actual - the value received.
* @returns {Error} Containing the formatted message.
* @private
@@ -110,6 +144,8 @@ module.exports = {
fn: fn,
bool: bool,
buffer: buffer,
typedArray: typedArray,
arrayBuffer: arrayBuffer,
string: string,
number: number,
integer: integer,

View File

@@ -1,26 +1,35 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
const fs = require('fs');
const os = require('os');
const path = require('path');
const spawnSync = require('child_process').spawnSync;
const semver = require('semver');
const semverCoerce = require('semver/functions/coerce');
const semverGreaterThanOrEqualTo = require('semver/functions/gte');
const platform = require('./platform');
const { config } = require('../package.json');
const env = process.env;
const minimumLibvipsVersion = env.npm_package_config_libvips || /* istanbul ignore next */
require('../package.json').config.libvips;
const minimumLibvipsVersionLabelled = env.npm_package_config_libvips || /* istanbul ignore next */
config.libvips;
const minimumLibvipsVersion = semverCoerce(minimumLibvipsVersionLabelled).version;
const spawnSyncOptions = {
encoding: 'utf8',
shell: true
};
const vendorPath = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, platform());
const mkdirSync = function (dirPath) {
try {
fs.mkdirSync(dirPath);
fs.mkdirSync(dirPath, { recursive: true });
} catch (err) {
/* istanbul ignore if */
/* istanbul ignore next */
if (err.code !== 'EEXIST') {
throw err;
}
@@ -36,47 +45,67 @@ const cachePath = function () {
return libvipsCachePath;
};
const integrity = function (platformAndArch) {
return env[`npm_package_config_integrity_${platformAndArch.replace('-', '_')}`] || config.integrity[platformAndArch];
};
const log = function (item) {
if (item instanceof Error) {
console.error(`sharp: Installation error: ${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 () {
if (process.platform !== 'win32') {
const globalLibvipsVersion = spawnSync(`PKG_CONFIG_PATH="${pkgConfigPath()}" pkg-config --modversion vips-cpp`, spawnSyncOptions).stdout || '';
return globalLibvipsVersion.trim();
const globalLibvipsVersion = spawnSync('pkg-config --modversion vips-cpp', {
...spawnSyncOptions,
env: {
...env,
PKG_CONFIG_PATH: pkgConfigPath()
}
}).stdout;
/* istanbul ignore next */
return (globalLibvipsVersion || '').trim();
} else {
return '';
}
};
const hasVendoredLibvips = function () {
const currentPlatformId = platform();
const vendorPath = path.join(__dirname, '..', 'vendor');
let vendorVersionId;
let vendorPlatformId;
try {
vendorVersionId = require(path.join(vendorPath, 'versions.json')).vips;
vendorPlatformId = require(path.join(vendorPath, 'platform.json'));
} 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 */
if (vendorPlatformId) {
/* istanbul ignore else */
if (currentPlatformId === vendorPlatformId) {
return true;
} else {
throw new Error(`'${vendorPlatformId}' binaries cannot be used on the '${currentPlatformId}' platform. Please remove the 'node_modules/sharp/vendor' directory and run 'npm install'.`);
}
} else {
return false;
}
return fs.existsSync(vendorPath);
};
/* istanbul ignore next */
const removeVendoredLibvips = function () {
fs.rmSync(vendorPath, { recursive: true, maxRetries: 3, force: true });
};
/* istanbul ignore next */
const pkgConfigPath = function () {
if (process.platform !== 'win32') {
const brewPkgConfigPath = spawnSync('which brew >/dev/null 2>&1 && eval $(brew --env) && echo $PKG_CONFIG_LIBDIR', spawnSyncOptions).stdout || '';
return [brewPkgConfigPath.trim(), env.PKG_CONFIG_PATH, '/usr/local/lib/pkgconfig', '/usr/lib/pkgconfig']
.filter(function (p) { return !!p; })
.join(':');
const brewPkgConfigPath = spawnSync(
'which brew >/dev/null 2>&1 && brew environment --plain | grep PKG_CONFIG_LIBDIR | cut -d" " -f2',
spawnSyncOptions
).stdout || '';
return [
brewPkgConfigPath.trim(),
env.PKG_CONFIG_PATH,
'/usr/local/lib/pkgconfig',
'/usr/lib/pkgconfig',
'/usr/local/libdata/pkgconfig',
'/usr/libdata/pkgconfig'
].filter(Boolean).join(':');
} else {
return '';
}
@@ -86,18 +115,26 @@ const useGlobalLibvips = function () {
if (Boolean(env.SHARP_IGNORE_GLOBAL_LIBVIPS) === true) {
return false;
}
/* istanbul ignore next */
if (isRosetta()) {
log('Detected Rosetta, skipping search for globally-installed libvips');
return false;
}
const globalVipsVersion = globalLibvipsVersion();
return !!globalVipsVersion && /* istanbul ignore next */
semver.gte(globalVipsVersion, minimumLibvipsVersion);
semverGreaterThanOrEqualTo(globalVipsVersion, minimumLibvipsVersion);
};
module.exports = {
minimumLibvipsVersion: minimumLibvipsVersion,
cachePath: cachePath,
globalLibvipsVersion: globalLibvipsVersion,
hasVendoredLibvips: hasVendoredLibvips,
pkgConfigPath: pkgConfigPath,
useGlobalLibvips: useGlobalLibvips,
mkdirSync: mkdirSync
minimumLibvipsVersion,
minimumLibvipsVersionLabelled,
cachePath,
integrity,
log,
globalLibvipsVersion,
hasVendoredLibvips,
removeVendoredLibvips,
pkgConfigPath,
useGlobalLibvips,
mkdirSync
};

View File

@@ -1,3 +1,6 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
const color = require('color');
@@ -8,7 +11,7 @@ const is = require('./is');
* or auto-orient based on the EXIF `Orientation` tag.
*
* If an angle is provided, it is converted to a valid positive degree rotation.
* For example, `-450` will produce a 270deg rotation.
* For example, `-450` will produce a 270 degree rotation.
*
* When rotating by an angle other than a multiple of 90,
* the background colour can be provided with the `background` option.
@@ -16,10 +19,13 @@ const is = require('./is');
* If no angle is provided, it is determined from the EXIF data.
* Mirroring is supported and may infer the use of a flip operation.
*
* The use of `rotate` implies the removal of the EXIF `Orientation` tag, if any.
* The use of `rotate` without an angle will remove the EXIF `Orientation` tag, if any.
*
* Method order is important when both rotating and extracting regions,
* for example `rotate(x).extract(y)` will produce a different result to `extract(y).rotate(x)`.
* Only one rotation can occur per pipeline.
* Previous calls to `rotate` in the same pipeline will be ignored.
*
* Method order is important when rotating, resizing and/or extracting regions,
* for example `.rotate(x).extract(y)` will produce a different result to `.extract(y).rotate(x)`.
*
* @example
* const pipeline = sharp()
@@ -32,13 +38,26 @@ const is = require('./is');
* });
* readableStream.pipe(pipeline);
*
* @param {Number} [angle=auto] angle of rotation.
* @example
* const rotateThenResize = await sharp(input)
* .rotate(90)
* .resize({ width: 16, height: 8, fit: 'fill' })
* .toBuffer();
* const resizeThenRotate = await sharp(input)
* .resize({ width: 16, height: 8, fit: 'fill' })
* .rotate(90)
* .toBuffer();
*
* @param {number} [angle=auto] angle of rotation.
* @param {Object} [options] - if present, is an Object with optional attributes.
* @param {String|Object} [options.background="#000000"] parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
* @param {string|Object} [options.background="#000000"] parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
function rotate (angle, options) {
if (this.options.useExifOrientation || this.options.angle || this.options.rotationAngle) {
this.options.debuglog('ignoring previous rotate options');
}
if (!is.defined(angle)) {
this.options.useExifOrientation = true;
} else if (is.integer(angle) && !(angle % 90)) {
@@ -61,8 +80,14 @@ function rotate (angle, options) {
}
/**
* Flip the image about the vertical Y axis. This always occurs after rotation, if any.
* The use of `flip` implies the removal of the EXIF `Orientation` tag, if any.
* Mirror the image vertically (up-down) about the x-axis.
* This always occurs before rotation, if any.
*
* This operation does not work correctly with multi-page images.
*
* @example
* const output = await sharp(input).flip().toBuffer();
*
* @param {Boolean} [flip=true]
* @returns {Sharp}
*/
@@ -72,8 +97,12 @@ function flip (flip) {
}
/**
* Flop the image about the horizontal X axis. This always occurs after rotation, if any.
* The use of `flop` implies the removal of the EXIF `Orientation` tag, if any.
* Mirror the image horizontally (left-right) about the y-axis.
* This always occurs before rotation, if any.
*
* @example
* const output = await sharp(input).flop().toBuffer();
*
* @param {Boolean} [flop=true]
* @returns {Sharp}
*/
@@ -83,45 +112,211 @@ function flop (flop) {
}
/**
* Sharpen the image.
* When used without parameters, performs a fast, mild sharpen of the output image.
* When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space.
* Separate control over the level of sharpening in "flat" and "jagged" areas is available.
* Perform an affine transform on an image. This operation will always occur after resizing, extraction and rotation, if any.
*
* @param {Number} [sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
* @param {Number} [flat=1.0] - the level of sharpening to apply to "flat" areas.
* @param {Number} [jagged=2.0] - the level of sharpening to apply to "jagged" areas.
* You must provide an array of length 4 or a 2x2 affine transformation matrix.
* By default, new pixels are filled with a black background. You can provide a background color with the `background` option.
* A particular interpolator may also be specified. Set the `interpolator` option to an attribute of the `sharp.interpolators` Object e.g. `sharp.interpolators.nohalo`.
*
* In the case of a 2x2 matrix, the transform is:
* - X = `matrix[0, 0]` \* (x + `idx`) + `matrix[0, 1]` \* (y + `idy`) + `odx`
* - Y = `matrix[1, 0]` \* (x + `idx`) + `matrix[1, 1]` \* (y + `idy`) + `ody`
*
* where:
* - x and y are the coordinates in input image.
* - X and Y are the coordinates in output image.
* - (0,0) is the upper left corner.
*
* @since 0.27.0
*
* @example
* const pipeline = sharp()
* .affine([[1, 0.3], [0.1, 0.7]], {
* background: 'white',
* interpolator: sharp.interpolators.nohalo
* })
* .toBuffer((err, outputBuffer, info) => {
* // outputBuffer contains the transformed image
* // info.width and info.height contain the new dimensions
* });
*
* inputStream
* .pipe(pipeline);
*
* @param {Array<Array<number>>|Array<number>} matrix - affine transformation matrix
* @param {Object} [options] - if present, is an Object with optional attributes.
* @param {String|Object} [options.background="#000000"] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
* @param {Number} [options.idx=0] - input horizontal offset
* @param {Number} [options.idy=0] - input vertical offset
* @param {Number} [options.odx=0] - output horizontal offset
* @param {Number} [options.ody=0] - output vertical offset
* @param {String} [options.interpolator=sharp.interpolators.bicubic] - interpolator
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
function sharpen (sigma, flat, jagged) {
if (!is.defined(sigma)) {
function affine (matrix, options) {
const flatMatrix = [].concat(...matrix);
if (flatMatrix.length === 4 && flatMatrix.every(is.number)) {
this.options.affineMatrix = flatMatrix;
} else {
throw is.invalidParameterError('matrix', '1x4 or 2x2 array', matrix);
}
if (is.defined(options)) {
if (is.object(options)) {
this._setBackgroundColourOption('affineBackground', options.background);
if (is.defined(options.idx)) {
if (is.number(options.idx)) {
this.options.affineIdx = options.idx;
} else {
throw is.invalidParameterError('options.idx', 'number', options.idx);
}
}
if (is.defined(options.idy)) {
if (is.number(options.idy)) {
this.options.affineIdy = options.idy;
} else {
throw is.invalidParameterError('options.idy', 'number', options.idy);
}
}
if (is.defined(options.odx)) {
if (is.number(options.odx)) {
this.options.affineOdx = options.odx;
} else {
throw is.invalidParameterError('options.odx', 'number', options.odx);
}
}
if (is.defined(options.ody)) {
if (is.number(options.ody)) {
this.options.affineOdy = options.ody;
} else {
throw is.invalidParameterError('options.ody', 'number', options.ody);
}
}
if (is.defined(options.interpolator)) {
if (is.inArray(options.interpolator, Object.values(this.constructor.interpolators))) {
this.options.affineInterpolator = options.interpolator;
} else {
throw is.invalidParameterError('options.interpolator', 'valid interpolator name', options.interpolator);
}
}
} else {
throw is.invalidParameterError('options', 'object', options);
}
}
return this;
}
/**
* Sharpen the image.
*
* When used without parameters, performs a fast, mild sharpen of the output image.
*
* When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space.
* Fine-grained control over the level of sharpening in "flat" (m1) and "jagged" (m2) areas is available.
*
* See {@link https://www.libvips.org/API/current/libvips-convolution.html#vips-sharpen|libvips sharpen} operation.
*
* @example
* const data = await sharp(input).sharpen().toBuffer();
*
* @example
* const data = await sharp(input).sharpen({ sigma: 2 }).toBuffer();
*
* @example
* const data = await sharp(input)
* .sharpen({
* sigma: 2,
* m1: 0,
* m2: 3,
* x1: 3,
* y2: 15,
* y3: 15,
* })
* .toBuffer();
*
* @param {Object|number} [options] - if present, is an Object with attributes
* @param {number} [options.sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`, between 0.000001 and 10
* @param {number} [options.m1=1.0] - the level of sharpening to apply to "flat" areas, between 0 and 1000000
* @param {number} [options.m2=2.0] - the level of sharpening to apply to "jagged" areas, between 0 and 1000000
* @param {number} [options.x1=2.0] - threshold between "flat" and "jagged", between 0 and 1000000
* @param {number} [options.y2=10.0] - maximum amount of brightening, between 0 and 1000000
* @param {number} [options.y3=20.0] - maximum amount of darkening, between 0 and 1000000
* @param {number} [flat] - (deprecated) see `options.m1`.
* @param {number} [jagged] - (deprecated) see `options.m2`.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
function sharpen (options, flat, jagged) {
if (!is.defined(options)) {
// No arguments: default to mild sharpen
this.options.sharpenSigma = -1;
} else if (is.bool(sigma)) {
// Boolean argument: apply mild sharpen?
this.options.sharpenSigma = sigma ? -1 : 0;
} else if (is.number(sigma) && is.inRange(sigma, 0.01, 10000)) {
// Numeric argument: specific sigma
this.options.sharpenSigma = sigma;
// Control over flat areas
} else if (is.bool(options)) {
// Deprecated boolean argument: apply mild sharpen?
this.options.sharpenSigma = options ? -1 : 0;
} else if (is.number(options) && is.inRange(options, 0.01, 10000)) {
// Deprecated numeric argument: specific sigma
this.options.sharpenSigma = options;
// Deprecated control over flat areas
if (is.defined(flat)) {
if (is.number(flat) && is.inRange(flat, 0, 10000)) {
this.options.sharpenFlat = flat;
this.options.sharpenM1 = flat;
} else {
throw is.invalidParameterError('flat', 'number between 0 and 10000', flat);
}
}
// Control over jagged areas
// Deprecated control over jagged areas
if (is.defined(jagged)) {
if (is.number(jagged) && is.inRange(jagged, 0, 10000)) {
this.options.sharpenJagged = jagged;
this.options.sharpenM2 = jagged;
} else {
throw is.invalidParameterError('jagged', 'number between 0 and 10000', jagged);
}
}
} else if (is.plainObject(options)) {
if (is.number(options.sigma) && is.inRange(options.sigma, 0.000001, 10)) {
this.options.sharpenSigma = options.sigma;
} else {
throw is.invalidParameterError('options.sigma', 'number between 0.000001 and 10', options.sigma);
}
if (is.defined(options.m1)) {
if (is.number(options.m1) && is.inRange(options.m1, 0, 1000000)) {
this.options.sharpenM1 = options.m1;
} else {
throw is.invalidParameterError('options.m1', 'number between 0 and 1000000', options.m1);
}
}
if (is.defined(options.m2)) {
if (is.number(options.m2) && is.inRange(options.m2, 0, 1000000)) {
this.options.sharpenM2 = options.m2;
} else {
throw is.invalidParameterError('options.m2', 'number between 0 and 1000000', options.m2);
}
}
if (is.defined(options.x1)) {
if (is.number(options.x1) && is.inRange(options.x1, 0, 1000000)) {
this.options.sharpenX1 = options.x1;
} else {
throw is.invalidParameterError('options.x1', 'number between 0 and 1000000', options.x1);
}
}
if (is.defined(options.y2)) {
if (is.number(options.y2) && is.inRange(options.y2, 0, 1000000)) {
this.options.sharpenY2 = options.y2;
} else {
throw is.invalidParameterError('options.y2', 'number between 0 and 1000000', options.y2);
}
}
if (is.defined(options.y3)) {
if (is.number(options.y3) && is.inRange(options.y3, 0, 1000000)) {
this.options.sharpenY3 = options.y3;
} else {
throw is.invalidParameterError('options.y3', 'number between 0 and 1000000', options.y3);
}
}
} else {
throw is.invalidParameterError('sigma', 'number between 0.01 and 10000', sigma);
throw is.invalidParameterError('sigma', 'number between 0.01 and 10000', options);
}
return this;
}
@@ -129,7 +324,14 @@ function sharpen (sigma, flat, jagged) {
/**
* Apply median filter.
* When used without parameters the default window is 3x3.
* @param {Number} [size=3] square mask size: size x size
*
* @example
* const output = await sharp(input).median().toBuffer();
*
* @example
* const output = await sharp(input).median(5).toBuffer();
*
* @param {number} [size=3] square mask size: size x size
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -148,9 +350,22 @@ function median (size) {
/**
* Blur the image.
* When used without parameters, performs a fast, mild blur of the output image.
*
* When used without parameters, performs a fast 3x3 box blur (equivalent to a box linear filter).
*
* When a `sigma` is provided, performs a slower, more accurate Gaussian blur.
* @param {Number} [sigma] a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
*
* @example
* const boxBlurred = await sharp(input)
* .blur()
* .toBuffer();
*
* @example
* const gaussianBlurred = await sharp(input)
* .blur(5)
* .toBuffer();
*
* @param {number} [sigma] a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -171,9 +386,17 @@ function blur (sigma) {
}
/**
* Merge alpha transparency channel, if any, with a background.
* Merge alpha transparency channel, if any, with a background, then remove the alpha channel.
*
* See also {@link /api-channel#removealpha|removeAlpha}.
*
* @example
* await sharp(rgbaInput)
* .flatten({ background: '#F0A703' })
* .toBuffer();
*
* @param {Object} [options]
* @param {String|Object} [options.background={r: 0, g: 0, b: 0}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black.
* @param {string|Object} [options.background={r: 0, g: 0, b: 0}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black.
* @returns {Sharp}
*/
function flatten (options) {
@@ -184,6 +407,32 @@ function flatten (options) {
return this;
}
/**
* Ensure the image has an alpha channel
* with all white pixel values made fully transparent.
*
* Existing alpha channel values for non-white pixels remain unchanged.
*
* This feature is experimental and the API may change.
*
* @since 0.32.1
*
* @example
* await sharp(rgbInput)
* .unflatten()
* .toBuffer();
*
* @example
* await sharp(rgbInput)
* .threshold(128, { grayscale: false }) // converter bright pixels to white
* .unflatten()
* .toBuffer();
*/
function unflatten () {
this.options.unflatten = true;
return this;
}
/**
* Apply a gamma correction by reducing the encoding (darken) pre-resize at a factor of `1/gamma`
* then increasing the encoding (brighten) post-resize at a factor of `gamma`.
@@ -193,8 +442,8 @@ function flatten (options) {
*
* Supply a second argument to use a different output gamma value, otherwise the first value is used in both cases.
*
* @param {Number} [gamma=2.2] value between 1.0 and 3.0.
* @param {Number} [gammaOut] value between 1.0 and 3.0. (optional, defaults to same as `gamma`)
* @param {number} [gamma=2.2] value between 1.0 and 3.0.
* @param {number} [gammaOut] value between 1.0 and 3.0. (optional, defaults to same as `gamma`)
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -220,31 +469,144 @@ function gamma (gamma, gammaOut) {
/**
* Produce the "negative" of the image.
* @param {Boolean} [negate=true]
*
* @example
* const output = await sharp(input)
* .negate()
* .toBuffer();
*
* @example
* const output = await sharp(input)
* .negate({ alpha: false })
* .toBuffer();
*
* @param {Object} [options]
* @param {Boolean} [options.alpha=true] Whether or not to negate any alpha channel
* @returns {Sharp}
*/
function negate (negate) {
this.options.negate = is.bool(negate) ? negate : true;
function negate (options) {
this.options.negate = is.bool(options) ? options : true;
if (is.plainObject(options) && 'alpha' in options) {
if (!is.bool(options.alpha)) {
throw is.invalidParameterError('alpha', 'should be boolean value', options.alpha);
} else {
this.options.negateAlpha = options.alpha;
}
}
return this;
}
/**
* Enhance output image contrast by stretching its luminance to cover the full dynamic range.
* @param {Boolean} [normalise=true]
* Enhance output image contrast by stretching its luminance to cover a full dynamic range.
*
* Uses a histogram-based approach, taking a default range of 1% to 99% to reduce sensitivity to noise at the extremes.
*
* Luminance values below the `lower` percentile will be underexposed by clipping to zero.
* Luminance values above the `upper` percentile will be overexposed by clipping to the max pixel value.
*
* @example
* const output = await sharp(input)
* .normalise()
* .toBuffer();
*
* @example
* const output = await sharp(input)
* .normalise({ lower: 0, upper: 100 })
* .toBuffer();
*
* @param {Object} [options]
* @param {number} [options.lower=1] - Percentile below which luminance values will be underexposed.
* @param {number} [options.upper=99] - Percentile above which luminance values will be overexposed.
* @returns {Sharp}
*/
function normalise (normalise) {
this.options.normalise = is.bool(normalise) ? normalise : true;
function normalise (options) {
if (is.plainObject(options)) {
if (is.defined(options.lower)) {
if (is.number(options.lower) && is.inRange(options.lower, 0, 99)) {
this.options.normaliseLower = options.lower;
} else {
throw is.invalidParameterError('lower', 'number between 0 and 99', options.lower);
}
}
if (is.defined(options.upper)) {
if (is.number(options.upper) && is.inRange(options.upper, 1, 100)) {
this.options.normaliseUpper = options.upper;
} else {
throw is.invalidParameterError('upper', 'number between 1 and 100', options.upper);
}
}
}
if (this.options.normaliseLower >= this.options.normaliseUpper) {
throw is.invalidParameterError('range', 'lower to be less than upper',
`${this.options.normaliseLower} >= ${this.options.normaliseUpper}`);
}
this.options.normalise = true;
return this;
}
/**
* Alternative spelling of normalise.
* @param {Boolean} [normalize=true]
*
* @example
* const output = await sharp(input)
* .normalize()
* .toBuffer();
*
* @param {Object} [options]
* @param {number} [options.lower=1] - Percentile below which luminance values will be underexposed.
* @param {number} [options.upper=99] - Percentile above which luminance values will be overexposed.
* @returns {Sharp}
*/
function normalize (normalize) {
return this.normalise(normalize);
function normalize (options) {
return this.normalise(options);
}
/**
* Perform contrast limiting adaptive histogram equalization
* {@link https://en.wikipedia.org/wiki/Adaptive_histogram_equalization#Contrast_Limited_AHE|CLAHE}.
*
* This will, in general, enhance the clarity of the image by bringing out darker details.
*
* @since 0.28.3
*
* @example
* const output = await sharp(input)
* .clahe({
* width: 3,
* height: 3,
* })
* .toBuffer();
*
* @param {Object} options
* @param {number} options.width - Integral width of the search window, in pixels.
* @param {number} options.height - Integral height of the search window, in pixels.
* @param {number} [options.maxSlope=3] - Integral level of brightening, between 0 and 100, where 0 disables contrast limiting.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
function clahe (options) {
if (is.plainObject(options)) {
if (is.integer(options.width) && options.width > 0) {
this.options.claheWidth = options.width;
} else {
throw is.invalidParameterError('width', 'integer greater than zero', options.width);
}
if (is.integer(options.height) && options.height > 0) {
this.options.claheHeight = options.height;
} else {
throw is.invalidParameterError('height', 'integer greater than zero', options.height);
}
if (is.defined(options.maxSlope)) {
if (is.integer(options.maxSlope) && is.inRange(options.maxSlope, 0, 100)) {
this.options.claheMaxSlope = options.maxSlope;
} else {
throw is.invalidParameterError('maxSlope', 'integer between 0 and 100', options.maxSlope);
}
}
} else {
throw is.invalidParameterError('options', 'plain object', options);
}
return this;
}
/**
@@ -264,11 +626,11 @@ function normalize (normalize) {
* });
*
* @param {Object} kernel
* @param {Number} kernel.width - width of the kernel in pixels.
* @param {Number} kernel.height - width of the kernel in pixels.
* @param {Array<Number>} kernel.kernel - Array of length `width*height` containing the kernel values.
* @param {Number} [kernel.scale=sum] - the scale of the kernel in pixels.
* @param {Number} [kernel.offset=0] - the offset of the kernel in pixels.
* @param {number} kernel.width - width of the kernel in pixels.
* @param {number} kernel.height - height of the kernel in pixels.
* @param {Array<number>} kernel.kernel - Array of length `width*height` containing the kernel values.
* @param {number} [kernel.scale=sum] - the scale of the kernel in pixels.
* @param {number} [kernel.offset=0] - the offset of the kernel in pixels.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -299,8 +661,8 @@ 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.
* @param {Number} [threshold=128] - a value in the range 0-255 representing the level at which the threshold will be applied.
* Any pixel value greater than or equal to the threshold value will be set to 255, otherwise it will be set to 0.
* @param {number} [threshold=128] - a value in the range 0-255 representing the level at which the threshold will be applied.
* @param {Object} [options]
* @param {Boolean} [options.greyscale=true] - convert to single channel greyscale.
* @param {Boolean} [options.grayscale=true] - alternative spelling for greyscale.
@@ -331,13 +693,13 @@ function threshold (threshold, options) {
* This operation creates an output image where each pixel is the result of
* the selected bitwise boolean `operation` between the corresponding pixels of the input images.
*
* @param {Buffer|String} operand - Buffer containing image data or String containing the path to an image file.
* @param {String} operator - one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
* @param {Buffer|string} operand - Buffer containing image data or string containing the path to an image file.
* @param {string} operator - one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
* @param {Object} [options]
* @param {Object} [options.raw] - describes operand when using raw pixel data.
* @param {Number} [options.raw.width]
* @param {Number} [options.raw.height]
* @param {Number} [options.raw.channels]
* @param {number} [options.raw.width]
* @param {number} [options.raw.height]
* @param {number} [options.raw.channels]
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -352,32 +714,63 @@ function boolean (operand, operator, options) {
}
/**
* Apply the linear formula a * input + b to the image (levels adjustment)
* @param {Number} [a=1.0] multiplier
* @param {Number} [b=0.0] offset
* Apply the linear formula `a` * input + `b` to the image to adjust image levels.
*
* When a single number is provided, it will be used for all image channels.
* When an array of numbers is provided, the array length must match the number of channels.
*
* @example
* await sharp(input)
* .linear(0.5, 2)
* .toBuffer();
*
* @example
* await sharp(rgbInput)
* .linear(
* [0.25, 0.5, 0.75],
* [150, 100, 50]
* )
* .toBuffer();
*
* @param {(number|number[])} [a=[]] multiplier
* @param {(number|number[])} [b=[]] offset
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
function linear (a, b) {
if (!is.defined(a) && is.number(b)) {
a = 1.0;
} else if (is.number(a) && !is.defined(b)) {
b = 0.0;
}
if (!is.defined(a)) {
this.options.linearA = 1.0;
this.options.linearA = [];
} else if (is.number(a)) {
this.options.linearA = [a];
} else if (Array.isArray(a) && a.length && a.every(is.number)) {
this.options.linearA = a;
} else {
throw is.invalidParameterError('a', 'numeric', a);
throw is.invalidParameterError('a', 'number or array of numbers', a);
}
if (!is.defined(b)) {
this.options.linearB = 0.0;
this.options.linearB = [];
} else if (is.number(b)) {
this.options.linearB = [b];
} else if (Array.isArray(b) && b.length && b.every(is.number)) {
this.options.linearB = b;
} else {
throw is.invalidParameterError('b', 'numeric', b);
throw is.invalidParameterError('b', 'number or array of numbers', b);
}
if (this.options.linearA.length !== this.options.linearB.length) {
throw new Error('Expected a and b to be arrays of the same length');
}
return this;
}
/**
* Recomb the image with the specified matrix.
* Recombine the image with the specified matrix.
*
* @since 0.21.1
*
* @example
* sharp(input)
@@ -388,11 +781,11 @@ function linear (a, b) {
* ])
* .raw()
* .toBuffer(function(err, data, info) {
* // data contains the raw pixel data after applying the recomb
* // data contains the raw pixel data after applying the matrix
* // 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}
* @throws {Error} Invalid parameters
*/
@@ -414,31 +807,51 @@ function recomb (inputMatrix) {
}
/**
* Transforms the image using brightness, saturation and hue rotation.
* Transforms the image using brightness, saturation, hue rotation, and lightness.
* Brightness and lightness both operate on luminance, with the difference being that
* brightness is multiplicative whereas lightness is additive.
*
* @since 0.22.1
*
* @example
* sharp(input)
* // increase brightness by a factor of 2
* const output = await sharp(input)
* .modulate({
* brightness: 2 // increase lightness by a factor of 2
* });
* brightness: 2
* })
* .toBuffer();
*
* sharp(input)
* @example
* // hue-rotate by 180 degrees
* const output = await sharp(input)
* .modulate({
* hue: 180 // hue-rotate by 180 degrees
* });
* hue: 180
* })
* .toBuffer();
*
* // decreate brightness and saturation while also hue-rotating by 90 degrees
* sharp(input)
* @example
* // increase lightness by +50
* const output = await sharp(input)
* .modulate({
* lightness: 50
* })
* .toBuffer();
*
* @example
* // decrease brightness and saturation while also hue-rotating by 90 degrees
* const output = await sharp(input)
* .modulate({
* brightness: 0.5,
* saturation: 0.5,
* hue: 90
* });
* hue: 90,
* })
* .toBuffer();
*
* @param {Object} [options]
* @param {Number} [options.brightness] Brightness multiplier
* @param {Number} [options.saturation] Saturation multiplier
* @param {Number} [options.hue] Degrees for hue rotation
* @param {number} [options.brightness] Brightness multiplier
* @param {number} [options.saturation] Saturation multiplier
* @param {number} [options.hue] Degrees for hue rotation
* @param {number} [options.lightness] Lightness addend
* @returns {Sharp}
*/
function modulate (options) {
@@ -466,6 +879,13 @@ function modulate (options) {
throw is.invalidParameterError('hue', 'number', options.hue);
}
}
if ('lightness' in options) {
if (is.number(options.lightness)) {
this.options.lightness = options.lightness;
} else {
throw is.invalidParameterError('lightness', 'number', options.lightness);
}
}
return this;
}
@@ -478,14 +898,17 @@ module.exports = function (Sharp) {
rotate,
flip,
flop,
affine,
sharpen,
median,
blur,
flatten,
unflatten,
gamma,
negate,
normalise,
normalize,
clahe,
convolve,
threshold,
boolean,

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,6 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
const detectLibc = require('detect-libc');
@@ -7,13 +10,16 @@ const env = process.env;
module.exports = function () {
const arch = env.npm_config_arch || process.arch;
const platform = env.npm_config_platform || process.platform;
/* istanbul ignore next */
const libc = (platform === 'linux' && detectLibc.isNonGlibcLinux) ? detectLibc.family : '';
const libc = process.env.npm_config_libc ||
/* istanbul ignore next */
(detectLibc.isNonGlibcLinuxSync() ? detectLibc.familySync() : '');
const libcId = platform !== 'linux' || libc === detectLibc.GLIBC ? '' : libc;
const platformId = [`${platform}${libc}`];
const platformId = [`${platform}${libcId}`];
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') {
platformId.push(`arm64v${env.npm_config_arm_version || '8'}`);
} else {

View File

@@ -1,3 +1,6 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
const is = require('./is');
@@ -36,6 +39,18 @@ const position = {
'left top': 8
};
/**
* How to extend the image.
* @member
* @private
*/
const extendWith = {
background: 'background',
copy: 'copy',
repeat: 'repeat',
mirror: 'mirror'
};
/**
* Strategies for automagic cover behaviour.
* @member
@@ -85,21 +100,39 @@ const mapFitToCanvas = {
outside: 'min'
};
/**
* @private
*/
function isRotationExpected (options) {
return (options.angle % 360) !== 0 || options.useExifOrientation === true || options.rotationAngle !== 0;
}
/**
* @private
*/
function isResizeExpected (options) {
return options.width !== -1 || options.height !== -1;
}
/**
* Resize image to `width`, `height` or `width x height`.
*
* When both a `width` and `height` are provided, the possible methods by which the image should **fit** these are:
* - `cover`: Crop to cover both provided dimensions (the default).
* - `contain`: Embed within both provided dimensions.
* - `cover`: (default) Preserving aspect ratio, attempt to ensure the image covers both provided dimensions by cropping/clipping to fit.
* - `contain`: Preserving aspect ratio, contain within both provided dimensions using "letterboxing" where necessary.
* - `fill`: Ignore the aspect ratio of the input and stretch to both provided dimensions.
* - `inside`: Preserving aspect ratio, resize the image to be as large as possible while ensuring its dimensions are less than or equal to both those specified.
* - `outside`: Preserving aspect ratio, resize the image to be as small as possible while ensuring its dimensions are greater than or equal to both those specified.
*
* Some of these values are based on the [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) CSS property.
*
* When using a `fit` of `cover` or `contain`, the default **position** is `centre`. Other options are:
* <img alt="Examples of various values for the fit property when resizing" width="100%" style="aspect-ratio: 998/243" src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/api-resize-fit.svg">
*
* When using a **fit** of `cover` or `contain`, the default **position** is `centre`. Other options are:
* - `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`.
* - `sharp.gravity`: `north`, `northeast`, `east`, `southeast`, `south`, `southwest`, `west`, `northwest`, `center` or `centre`.
* - `sharp.strategy`: `cover` only, dynamically crop using either the `entropy` or `attention` strategy.
*
* Some of these values are based on the [object-position](https://developer.mozilla.org/en-US/docs/Web/CSS/object-position) CSS property.
*
* The experimental strategy-based approach resizes so one dimension is at its target length
@@ -114,6 +147,9 @@ const mapFitToCanvas = {
* - `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`.
* - `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
*
* Only one resize can occur per pipeline.
* Previous calls to `resize` in the same pipeline will be ignored.
*
* @example
* sharp(input)
* .resize({ width: 100 })
@@ -173,28 +209,54 @@ const mapFitToCanvas = {
* // and no larger than the input image
* });
*
* @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.
* @example
* sharp(input)
* .resize(200, 200, {
* fit: sharp.fit.outside,
* withoutReduction: true
* })
* .toFormat('jpeg')
* .toBuffer()
* .then(function(outputBuffer) {
* // outputBuffer contains JPEG image data
* // of at least 200 pixels wide and 200 pixels high while maintaining aspect ratio
* // and no smaller than the input image
* });
*
* @example
* const scaleByHalf = await sharp(input)
* .metadata()
* .then(({ width }) => sharp(input)
* .resize(Math.round(width * 0.5))
* .toBuffer()
* );
*
* @param {number} [width] - How many pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height.
* @param {number} [height] - How many pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
* @param {Object} [options]
* @param {String} [options.width] - alternative means of specifying `width`. If both are present this take priority.
* @param {String} [options.height] - alternative means of specifying `height`. If both are present this take priority.
* @param {String} [options.fit='cover'] - how the image should be resized to fit both provided dimensions, one of `cover`, `contain`, `fill`, `inside` or `outside`.
* @param {String} [options.position='centre'] - position, gravity or strategy to use when `fit` is `cover` or `contain`.
* @param {String|Object} [options.background={r: 0, g: 0, b: 0, alpha: 1}] - background colour when using a `fit` of `contain`, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency.
* @param {String} [options.kernel='lanczos3'] - the kernel to use for image reduction.
* @param {Boolean} [options.withoutEnlargement=false] - do not enlarge if the width *or* height are already less than the specified dimensions, equivalent to GraphicsMagick's `>` geometry option.
* @param {Boolean} [options.fastShrinkOnLoad=true] - take greater advantage of the JPEG and WebP shrink-on-load feature, which can lead to a slight moiré pattern on some images.
* @param {number} [options.width] - An alternative means of specifying `width`. If both are present this takes priority.
* @param {number} [options.height] - An alternative means of specifying `height`. If both are present this takes priority.
* @param {String} [options.fit='cover'] - How the image should be resized/cropped to fit the target dimension(s), one of `cover`, `contain`, `fill`, `inside` or `outside`.
* @param {String} [options.position='centre'] - A position, gravity or strategy to use when `fit` is `cover` or `contain`.
* @param {String|Object} [options.background={r: 0, g: 0, b: 0, alpha: 1}] - background colour when `fit` is `contain`, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency.
* @param {String} [options.kernel='lanczos3'] - The kernel to use for image reduction. Use the `fastShrinkOnLoad` option to control kernel vs shrink-on-load.
* @param {Boolean} [options.withoutEnlargement=false] - Do not scale up if the width *or* height are already less than the target dimensions, equivalent to GraphicsMagick's `>` geometry option. This may result in output dimensions smaller than the target dimensions.
* @param {Boolean} [options.withoutReduction=false] - Do not scale down if the width *or* height are already greater than the target dimensions, equivalent to GraphicsMagick's `<` geometry option. This may still result in a crop to reach the target dimensions.
* @param {Boolean} [options.fastShrinkOnLoad=true] - Take greater advantage of the JPEG and WebP shrink-on-load feature, which can lead to a slight moiré pattern or round-down of an auto-scaled dimension.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
function resize (width, height, options) {
if (is.defined(width)) {
if (is.object(width) && !is.defined(options)) {
options = width;
} else if (is.integer(width) && width > 0) {
this.options.width = width;
function resize (widthOrOptions, height, options) {
if (isResizeExpected(this.options)) {
this.options.debuglog('ignoring previous resize options');
}
if (is.defined(widthOrOptions)) {
if (is.object(widthOrOptions) && !is.defined(options)) {
options = widthOrOptions;
} else if (is.integer(widthOrOptions) && widthOrOptions > 0) {
this.options.width = widthOrOptions;
} else {
throw is.invalidParameterError('width', 'positive integer', width);
throw is.invalidParameterError('width', 'positive integer', widthOrOptions);
}
} else {
this.options.width = -1;
@@ -246,9 +308,7 @@ function resize (width, height, options) {
}
}
// Background
if (is.defined(options.background)) {
this._setBackgroundColourOption('resizeBackground', options.background);
}
this._setBackgroundColourOption('resizeBackground', options.background);
// Kernel
if (is.defined(options.kernel)) {
if (is.string(kernel[options.kernel])) {
@@ -261,16 +321,24 @@ function resize (width, height, options) {
if (is.defined(options.withoutEnlargement)) {
this._setBooleanOption('withoutEnlargement', options.withoutEnlargement);
}
// Without reduction
if (is.defined(options.withoutReduction)) {
this._setBooleanOption('withoutReduction', options.withoutReduction);
}
// Shrink on load
if (is.defined(options.fastShrinkOnLoad)) {
this._setBooleanOption('fastShrinkOnLoad', options.fastShrinkOnLoad);
}
}
if (isRotationExpected(this.options) && isResizeExpected(this.options)) {
this.options.rotateBeforePreExtract = true;
}
return this;
}
/**
* Extends/pads the edges of the image with the provided background colour.
* Extend / pad / extrude one or more edges of the image with either
* the provided background colour or pixels derived from the image.
* This operation will always occur after resizing and extraction, if any.
*
* @example
@@ -287,11 +355,30 @@ 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} [extend.top]
* @param {Number} [extend.left]
* @param {Number} [extend.bottom]
* @param {Number} [extend.right]
* @example
* // Add a row of 10 red pixels to the bottom
* sharp(input)
* .extend({
* bottom: 10,
* background: 'red'
* })
* ...
*
* @example
* // Extrude image by 8 pixels to the right, mirroring existing right hand edge
* sharp(input)
* .extend({
* right: 8,
* background: 'mirror'
* })
* ...
*
* @param {(number|Object)} extend - single pixel count to add to all edges or an Object with per-edge counts
* @param {number} [extend.top=0]
* @param {number} [extend.left=0]
* @param {number} [extend.bottom=0]
* @param {number} [extend.right=0]
* @param {String} [extend.extendWith='background'] - populate new pixels using this method, one of: background, copy, repeat, mirror.
* @param {String|Object} [extend.background={r: 0, g: 0, b: 0, alpha: 1}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency.
* @returns {Sharp}
* @throws {Error} Invalid parameters
@@ -302,18 +389,43 @@ function extend (extend) {
this.options.extendBottom = extend;
this.options.extendLeft = extend;
this.options.extendRight = extend;
} else if (
is.object(extend) &&
is.integer(extend.top) && extend.top >= 0 &&
is.integer(extend.bottom) && extend.bottom >= 0 &&
is.integer(extend.left) && extend.left >= 0 &&
is.integer(extend.right) && extend.right >= 0
) {
this.options.extendTop = extend.top;
this.options.extendBottom = extend.bottom;
this.options.extendLeft = extend.left;
this.options.extendRight = extend.right;
} else if (is.object(extend)) {
if (is.defined(extend.top)) {
if (is.integer(extend.top) && extend.top >= 0) {
this.options.extendTop = extend.top;
} else {
throw is.invalidParameterError('top', 'positive integer', extend.top);
}
}
if (is.defined(extend.bottom)) {
if (is.integer(extend.bottom) && extend.bottom >= 0) {
this.options.extendBottom = extend.bottom;
} else {
throw is.invalidParameterError('bottom', 'positive integer', extend.bottom);
}
}
if (is.defined(extend.left)) {
if (is.integer(extend.left) && extend.left >= 0) {
this.options.extendLeft = extend.left;
} else {
throw is.invalidParameterError('left', 'positive integer', extend.left);
}
}
if (is.defined(extend.right)) {
if (is.integer(extend.right) && extend.right >= 0) {
this.options.extendRight = extend.right;
} else {
throw is.invalidParameterError('right', 'positive integer', extend.right);
}
}
this._setBackgroundColourOption('extendBackground', extend.background);
if (is.defined(extend.extendWith)) {
if (is.string(extendWith[extend.extendWith])) {
this.options.extendWith = extendWith[extend.extendWith];
} else {
throw is.invalidParameterError('extendWith', 'one of: background, copy, repeat, mirror', extend.extendWith);
}
}
} else {
throw is.invalidParameterError('extend', 'integer or object', extend);
}
@@ -321,7 +433,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` after `resize` for post-resize extraction.
@@ -343,15 +455,18 @@ function extend (extend) {
* });
*
* @param {Object} options - describes the region to extract using integral pixel values
* @param {Number} options.left - zero-indexed offset from left edge
* @param {Number} options.top - zero-indexed offset from top edge
* @param {Number} options.width - width of region to extract
* @param {Number} options.height - height of region to extract
* @param {number} options.left - zero-indexed offset from left edge
* @param {number} options.top - zero-indexed offset from top edge
* @param {number} options.width - width of region to extract
* @param {number} options.height - height of region to extract
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
function extract (options) {
const suffix = this.options.width === -1 && this.options.height === -1 ? 'Pre' : 'Post';
const suffix = isResizeExpected(this.options) || this.options.widthPre !== -1 ? 'Post' : 'Pre';
if (this.options[`width${suffix}`] !== -1) {
this.options.debuglog('ignoring previous extract options');
}
['left', 'top', 'width', 'height'].forEach(function (name) {
const value = options[name];
if (is.integer(value) && value >= 0) {
@@ -361,26 +476,88 @@ function extract (options) {
}
}, this);
// Ensure existing rotation occurs before pre-resize extraction
if (suffix === 'Pre' && ((this.options.angle % 360) !== 0 || this.options.useExifOrientation === true || this.options.rotationAngle !== 0)) {
this.options.rotateBeforePreExtract = true;
if (isRotationExpected(this.options) && !isResizeExpected(this.options)) {
if (this.options.widthPre === -1 || this.options.widthPost === -1) {
this.options.rotateBeforePreExtract = true;
}
}
return this;
}
/**
* Trim "boring" pixels from all edges that contain values similar to the top-left pixel.
* The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` properties.
* @param {Number} [threshold=10] the allowed difference from the top-left pixel, a number greater than zero.
* Trim pixels from all edges that contain values similar to the given background colour, which defaults to that of the top-left pixel.
*
* Images with an alpha channel will use the combined bounding box of alpha and non-alpha channels.
*
* If the result of this operation would trim an image to nothing then no change is made.
*
* The `info` response Object, obtained from callback of `.toFile()` or `.toBuffer()`,
* will contain `trimOffsetLeft` and `trimOffsetTop` properties.
*
* @example
* // Trim pixels with a colour similar to that of the top-left pixel.
* sharp(input)
* .trim()
* .toFile(output, function(err, info) {
* ...
* });
* @example
* // Trim pixels with the exact same colour as that of the top-left pixel.
* sharp(input)
* .trim(0)
* .toFile(output, function(err, info) {
* ...
* });
* @example
* // Trim only pixels with a similar colour to red.
* sharp(input)
* .trim("#FF0000")
* .toFile(output, function(err, info) {
* ...
* });
* @example
* // Trim all "yellow-ish" pixels, being more lenient with the higher threshold.
* sharp(input)
* .trim({
* background: "yellow",
* threshold: 42,
* })
* .toFile(output, function(err, info) {
* ...
* });
*
* @param {string|number|Object} trim - the specific background colour to trim, the threshold for doing so or an Object with both.
* @param {string|Object} [trim.background='top-left pixel'] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to that of the top-left pixel.
* @param {number} [trim.threshold=10] - the allowed difference from the above colour, a positive number.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
function trim (threshold) {
if (!is.defined(threshold)) {
function trim (trim) {
if (!is.defined(trim)) {
this.options.trimThreshold = 10;
} else if (is.number(threshold) && threshold > 0) {
this.options.trimThreshold = threshold;
} else if (is.string(trim)) {
this._setBackgroundColourOption('trimBackground', trim);
this.options.trimThreshold = 10;
} else if (is.number(trim)) {
if (trim >= 0) {
this.options.trimThreshold = trim;
} else {
throw is.invalidParameterError('threshold', 'positive number', trim);
}
} else if (is.object(trim)) {
this._setBackgroundColourOption('trimBackground', trim.background);
if (!is.defined(trim.threshold)) {
this.options.trimThreshold = 10;
} else if (is.number(trim.threshold) && trim.threshold >= 0) {
this.options.trimThreshold = trim.threshold;
} else {
throw is.invalidParameterError('threshold', 'positive number', trim);
}
} else {
throw is.invalidParameterError('threshold', 'number greater than zero', threshold);
throw is.invalidParameterError('trim', 'string, number or object', trim);
}
if (isRotationExpected(this.options)) {
this.options.rotateBeforePreExtract = true;
}
return this;
}

38
lib/sharp.js Normal file
View File

@@ -0,0 +1,38 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
const platformAndArch = require('./platform')();
/* istanbul ignore next */
try {
module.exports = require(`../build/Release/sharp-${platformAndArch}.node`);
} catch (err) {
// Bail early if bindings aren't available
const help = ['', 'Something went wrong installing the "sharp" module', '', err.message, '', 'Possible solutions:'];
if (/dylib/.test(err.message) && /Incompatible library version/.test(err.message)) {
help.push('- Update Homebrew: "brew update && brew upgrade vips"');
} else {
const [platform, arch] = platformAndArch.split('-');
if (platform === 'linux' && /Module did not self-register/.test(err.message)) {
help.push('- Using worker threads? See https://sharp.pixelplumbing.com/install#worker-threads');
}
help.push(
'- Install with verbose logging and look for errors: "npm install --ignore-scripts=false --foreground-scripts --verbose sharp"',
`- Install for the current ${platformAndArch} runtime: "npm install --platform=${platform} --arch=${arch} sharp"`
);
}
help.push(
'- Consult the installation documentation: https://sharp.pixelplumbing.com/install'
);
// Check loaded
if (process.platform === 'win32' || /symbol/.test(err.message)) {
const loadedModule = Object.keys(require.cache).find((i) => /[\\/]build[\\/]Release[\\/]sharp(.*)\.node$/.test(i));
if (loadedModule) {
const [, loadedPackage] = loadedModule.match(/node_modules[\\/]([^\\/]+)[\\/]/);
help.push(`- Ensure the version of sharp aligns with the ${loadedPackage} package: "npm ls sharp"`);
}
}
throw new Error(help.join('\n'));
}

View File

@@ -1,7 +1,78 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
const fs = require('fs');
const path = require('path');
const events = require('events');
const detectLibc = require('detect-libc');
const is = require('./is');
const sharp = require('../build/Release/sharp.node');
const platformAndArch = require('./platform')();
const sharp = require('./sharp');
/**
* An Object containing nested boolean values representing the available input and output formats/methods.
* @member
* @example
* console.log(sharp.format);
* @returns {Object}
*/
const format = sharp.format();
format.heif.output.alias = ['avif', 'heic'];
format.jpeg.output.alias = ['jpe', 'jpg'];
format.tiff.output.alias = ['tif'];
format.jp2k.output.alias = ['j2c', 'j2k', 'jp2', 'jpx'];
/**
* An Object containing the available interpolators and their proper values
* @readonly
* @enum {string}
*/
const interpolators = {
/** [Nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation). Suitable for image enlargement only. */
nearest: 'nearest',
/** [Bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation). Faster than bicubic but with less smooth results. */
bilinear: 'bilinear',
/** [Bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) (the default). */
bicubic: 'bicubic',
/** [LBB interpolation](https://github.com/libvips/libvips/blob/master/libvips/resample/lbb.cpp#L100). Prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" but typically reduces performance by a factor of 2. */
locallyBoundedBicubic: 'lbb',
/** [Nohalo interpolation](http://eprints.soton.ac.uk/268086/). Prevents acutance but typically reduces performance by a factor of 3. */
nohalo: 'nohalo',
/** [VSQBS interpolation](https://github.com/libvips/libvips/blob/master/libvips/resample/vsqbs.cpp#L48). Prevents "staircasing" when enlarging. */
vertexSplitQuadraticBasisSpline: 'vsqbs'
};
/**
* An Object containing the version numbers of sharp, libvips and its dependencies.
* @member
* @example
* console.log(sharp.versions);
*/
let versions = {
vips: sharp.libvipsVersion()
};
try {
versions = require(`../vendor/${versions.vips}/${platformAndArch}/versions.json`);
} catch (_err) { /* ignore */ }
versions.sharp = require('../package.json').version;
/**
* An Object containing the platform and architecture
* of the current and installed vendored binaries.
* @member
* @example
* console.log(sharp.vendor);
*/
const vendor = {
current: platformAndArch,
installed: []
};
try {
vendor.installed = fs.readdirSync(path.join(__dirname, `../vendor/${versions.vips}`));
} catch (_err) { /* ignore */ }
/**
* Gets or, when options are provided, sets the limits of _libvips'_ operation cache.
@@ -16,10 +87,10 @@ const sharp = require('../build/Release/sharp.node');
* sharp.cache( { files: 0 } );
* sharp.cache(false);
*
* @param {Object|Boolean} [options=true] - Object with the following attributes, or Boolean where true uses default cache settings and false removes all caching
* @param {Number} [options.memory=50] - is the maximum memory in MB to use for this cache
* @param {Number} [options.files=20] - is the maximum number of files to hold open
* @param {Number} [options.items=100] - is the maximum number of operations to cache
* @param {Object|boolean} [options=true] - Object with the following attributes, or boolean where true uses default cache settings and false removes all caching
* @param {number} [options.memory=50] - is the maximum memory in MB to use for this cache
* @param {number} [options.files=20] - is the maximum number of files to hold open
* @param {number} [options.items=100] - is the maximum number of operations to cache
* @returns {Object}
*/
function cache (options) {
@@ -40,26 +111,60 @@ cache(true);
/**
* Gets or, when a concurrency is provided, sets
* the number of threads _libvips'_ should create to process each image.
* The default value is the number of CPU cores.
* A value of `0` will reset to this default.
*
* The maximum number of images that can be processed in parallel
* is limited by libuv's `UV_THREADPOOL_SIZE` environment variable.
* the maximum number of threads _libvips_ should use to process _each image_.
* These are from a thread pool managed by glib,
* which helps avoid the overhead of creating new threads.
*
* This method always returns the current concurrency.
*
* The default value is the number of CPU cores,
* except when using glibc-based Linux without jemalloc,
* where the default is `1` to help reduce memory fragmentation.
*
* A value of `0` will reset this to the number of CPU cores.
*
* Some image format libraries spawn additional threads,
* e.g. libaom manages its own 4 threads when encoding AVIF images,
* and these are independent of the value set here.
*
* The maximum number of images that sharp can process in parallel
* is controlled by libuv's `UV_THREADPOOL_SIZE` environment variable,
* which defaults to 4.
*
* https://nodejs.org/api/cli.html#uv_threadpool_sizesize
*
* For example, by default, a machine with 8 CPU cores will process
* 4 images in parallel and use up to 8 threads per image,
* so there will be up to 32 concurrent threads.
*
* @example
* const threads = sharp.concurrency(); // 4
* sharp.concurrency(2); // 2
* sharp.concurrency(0); // 4
*
* @param {Number} [concurrency]
* @returns {Number} concurrency
* @param {number} [concurrency]
* @returns {number} concurrency
*/
function concurrency (concurrency) {
return sharp.concurrency(is.integer(concurrency) ? concurrency : null);
}
/* istanbul ignore next */
if (detectLibc.familySync() === detectLibc.GLIBC && !sharp._isUsingJemalloc()) {
// Reduce default concurrency to 1 when using glibc memory allocator
sharp.concurrency(1);
}
/**
* An EventEmitter that emits a `change` event when a task is either:
* - queued, waiting for _libuv_ to provide a worker thread
* - complete
* @member
* @example
* sharp.queue.on('change', function(queueLength) {
* console.log('Queue contains ' + queueLength + ' task(s)');
* });
*/
const queue = new events.EventEmitter();
/**
* Provides access to internal task counters.
@@ -89,25 +194,94 @@ function counters () {
* const simd = sharp.simd(false);
* // prevent libvips from using liborc at runtime
*
* @param {Boolean} [simd=true]
* @returns {Boolean}
* @param {boolean} [simd=true]
* @returns {boolean}
*/
function simd (simd) {
return sharp.simd(is.bool(simd) ? simd : null);
}
simd(true);
/**
* Block libvips operations at runtime.
*
* This is in addition to the `VIPS_BLOCK_UNTRUSTED` environment variable,
* which when set will block all "untrusted" operations.
*
* @since 0.32.4
*
* @example <caption>Block all TIFF input.</caption>
* sharp.block({
* operation: ['VipsForeignLoadTiff']
* });
*
* @param {Object} options
* @param {Array<string>} options.operation - List of libvips low-level operation names to block.
*/
function block (options) {
if (is.object(options)) {
if (Array.isArray(options.operation) && options.operation.every(is.string)) {
sharp.block(options.operation, true);
} else {
throw is.invalidParameterError('operation', 'Array<string>', options.operation);
}
} else {
throw is.invalidParameterError('options', 'object', options);
}
}
/**
* Unblock libvips operations at runtime.
*
* This is useful for defining a list of allowed operations.
*
* @since 0.32.4
*
* @example <caption>Block all input except WebP from the filesystem.</caption>
* sharp.block({
* operation: ['VipsForeignLoad']
* });
* sharp.unblock({
* operation: ['VipsForeignLoadWebpFile']
* });
*
* @example <caption>Block all input except JPEG and PNG from a Buffer or Stream.</caption>
* sharp.block({
* operation: ['VipsForeignLoad']
* });
* sharp.unblock({
* operation: ['VipsForeignLoadJpegBuffer', 'VipsForeignLoadPngBuffer']
* });
*
* @param {Object} options
* @param {Array<string>} options.operation - List of libvips low-level operation names to unblock.
*/
function unblock (options) {
if (is.object(options)) {
if (Array.isArray(options.operation) && options.operation.every(is.string)) {
sharp.block(options.operation, false);
} else {
throw is.invalidParameterError('operation', 'Array<string>', options.operation);
}
} else {
throw is.invalidParameterError('options', 'object', options);
}
}
/**
* Decorate the Sharp class with utility-related functions.
* @private
*/
module.exports = function (Sharp) {
[
cache,
concurrency,
counters,
simd
].forEach(function (f) {
Sharp[f.name] = f;
});
Sharp.cache = cache;
Sharp.concurrency = concurrency;
Sharp.counters = counters;
Sharp.simd = simd;
Sharp.format = format;
Sharp.interpolators = interpolators;
Sharp.versions = versions;
Sharp.vendor = vendor;
Sharp.queue = queue;
Sharp.block = block;
Sharp.unblock = unblock;
};

View File

@@ -1,27 +0,0 @@
site_name: sharp
site_url: https://sharp.pixelplumbing.com/
repo_url: https://github.com/lovell/sharp
site_description: High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images
copyright: <a href="https://pixelplumbing.com/">pixelplumbing.com</a>
google_analytics: ['UA-13034748-12', 'sharp.pixelplumbing.com']
theme: readthedocs
extra_css:
- css/extra.css
markdown_extensions:
- toc:
permalink: True
pages:
- Home: index.md
- Installation: install.md
- API:
- Constructor: api-constructor.md
- Input: api-input.md
- Output: api-output.md
- "Resizing images": api-resize.md
- "Compositing images": api-composite.md
- "Image operations": api-operation.md
- "Colour manipulation": api-colour.md
- "Channel manipulation": api-channel.md
- Utilities: api-utility.md
- Performance: performance.md
- Changelog: changelog.md

View File

@@ -1,7 +1,7 @@
{
"name": "sharp",
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images",
"version": "0.23.1",
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, GIF, AVIF and TIFF images",
"version": "0.32.6",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://github.com/lovell/sharp",
"contributors": [
@@ -62,19 +62,53 @@
"Michael B. Klein <mbklein@gmail.com>",
"Jordan Prudhomme <jordan@raboland.fr>",
"Ilya Ovdin <iovdin@gmail.com>",
"Andargor <andargor@yahoo.com>"
"Andargor <andargor@yahoo.com>",
"Paul Neave <paul.neave@gmail.com>",
"Brendan Kennedy <brenwken@gmail.com>",
"Brychan Bennett-Odlum <git@brychan.io>",
"Edward Silverton <e.silverton@gmail.com>",
"Roman Malieiev <aromaleev@gmail.com>",
"Tomas Szabo <tomas.szabo@deftomat.com>",
"Robert O'Rourke <robert@o-rourke.org>",
"Guillermo Alfonso Varela Chouciño <guillevch@gmail.com>",
"Christian Flintrup <chr@gigahost.dk>",
"Manan Jadhav <manan@motionden.com>",
"Leon Radley <leon@radley.se>",
"alza54 <alza54@thiocod.in>",
"Jacob Smith <jacob@frende.me>",
"Michael Nutt <michael@nutt.im>",
"Brad Parham <baparham@gmail.com>",
"Taneli Vatanen <taneli.vatanen@gmail.com>",
"Joris Dugué <zaruike10@gmail.com>",
"Chris Banks <christopher.bradley.banks@gmail.com>",
"Ompal Singh <ompal.hitm09@gmail.com>",
"Brodan <christopher.hranj@gmail.com",
"Ankur Parihar <ankur.github@gmail.com>",
"Brahim Ait elhaj <brahima@gmail.com>",
"Mart Jansink <m.jansink@gmail.com>",
"Lachlan Newman <lachnewman007@gmail.com>"
],
"scripts": {
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node install/can-compile && node-gyp rebuild && node install/dll-copy)",
"clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*",
"test": "semistandard && cc && npm run test-unit && npm run test-licensing && prebuild-ci",
"test-unit": "nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js",
"test": "npm run test-lint && npm run test-unit && npm run test-licensing && npm run test-types",
"test-lint": "semistandard && cpplint",
"test-unit": "nyc --reporter=lcov --reporter=text --check-coverage --branches=100 mocha",
"test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
"test-coverage": "./test/coverage/report.sh",
"test-leak": "./test/leak/leak.sh",
"docs": "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"
"test-types": "tsd",
"docs-build": "node docs/build && node docs/search-index/build",
"docs-serve": "cd docs && npx serve",
"docs-publish": "cd docs && npx firebase-tools deploy --project pixelplumbing --only hosting:pixelplumbing-sharp"
},
"main": "lib/index.js",
"types": "lib/index.d.ts",
"files": [
"binding.gyp",
"install/**",
"lib/**",
"src/**"
],
"repository": {
"type": "git",
"url": "git://github.com/lovell/sharp"
@@ -83,9 +117,11 @@
"jpeg",
"png",
"webp",
"avif",
"tiff",
"gif",
"svg",
"jp2",
"dzi",
"image",
"resize",
@@ -96,38 +132,60 @@
"vips"
],
"dependencies": {
"color": "^3.1.2",
"detect-libc": "^1.0.3",
"nan": "^2.14.0",
"npmlog": "^4.1.2",
"prebuild-install": "^5.3.2",
"semver": "^6.3.0",
"simple-get": "^3.1.0",
"tar": "^4.4.13",
"color": "^4.2.3",
"detect-libc": "^2.0.2",
"node-addon-api": "^6.1.0",
"prebuild-install": "^7.1.1",
"semver": "^7.5.4",
"simple-get": "^4.0.1",
"tar-fs": "^3.0.4",
"tunnel-agent": "^0.6.0"
},
"devDependencies": {
"async": "^3.1.0",
"cc": "^1.0.2",
"decompress-zip": "^0.3.2",
"documentation": "^12.1.2",
"exif-reader": "^1.0.2",
"icc": "^1.0.0",
"@types/node": "*",
"async": "^3.2.4",
"cc": "^3.0.1",
"exif-reader": "^1.2.0",
"extract-zip": "^2.0.1",
"icc": "^3.0.0",
"jsdoc-to-markdown": "^8.0.0",
"license-checker": "^25.0.1",
"mocha": "^6.2.0",
"mock-fs": "^4.10.1",
"nyc": "^14.1.1",
"prebuild": "^9.1.0",
"prebuild-ci": "^3.1.0",
"rimraf": "^3.0.0",
"semistandard": "^14.2.0"
"mocha": "^10.2.0",
"mock-fs": "^5.2.0",
"nyc": "^15.1.0",
"prebuild": "^12.0.0",
"semistandard": "^16.0.1",
"tsd": "^0.29.0"
},
"license": "Apache-2.0",
"config": {
"libvips": "8.8.1"
"libvips": "8.14.5",
"integrity": {
"darwin-arm64v8": "sha512-1QZzICfCJd4wAO0P6qmYI5e5VFMt9iCE4QgefI8VMMbdSzjIXA9L/ARN6pkMQPZ3h20Y9RtJ2W1skgCsvCIccw==",
"darwin-x64": "sha512-sMIKMYXsdU9FlIfztj6Kt/SfHlhlDpP0Ups7ftVFqwjaszmYmpI9y/d/q3mLb4jrzuSiSUEislSWCwBnW7MPTw==",
"linux-arm64v8": "sha512-CD8owELzkDumaom+O3jJ8fKamILAQdj+//KK/VNcHK3sngUcFpdjx36C8okwbux9sml/T7GTB/gzpvReDrAejQ==",
"linux-armv6": "sha512-wk6IPHatDFVWKJy7lI1TJezHGHPQut1wF2bwx256KlZwXUQU3fcVcMpV1zxXjgLFewHq2+uhyMkoSGBPahWzlA==",
"linux-armv7": "sha512-HEZC9KYtkmBK5rUR2MqBhrVarnQVZ/TwLUeLkKq0XuoM2pc/eXI6N0Fh5NGEFwdXI2XE8g1ySf+OYS6DDi+xCQ==",
"linux-x64": "sha512-SlFWrITSW5XVUkaFPQOySAaSGXnhkGJCj8X2wGYYta9hk5piZldQyMp4zwy0z6UeRu1qKTKtZvmq28W3Gnh9xA==",
"linuxmusl-arm64v8": "sha512-ga9iX7WUva3sG/VsKkOD318InLlCfPIztvzCZKZ2/+izQXRbQi8VoXWMHgEN4KHACv45FTl7mJ/8CRqUzhS8wQ==",
"linuxmusl-x64": "sha512-yeaHnpfee1hrZLok2l4eFceHzlfq8gN3QOu0R4Mh8iMK5O5vAUu97bdtxeZZeJJvHw8tfh2/msGi0qysxKN8bw==",
"win32-arm64v8": "sha512-kR91hy9w1+GEXK56hLh51+hBCBo7T+ijM4Slkmvb/2PsYZySq5H7s61n99iDYl6kTJP2y9sW5Xcvm3uuXDaDgg==",
"win32-ia32": "sha512-HrnofEbzHNpHJ0vVnjsTj5yfgVdcqdWshXuwFO2zc8xlEjA83BvXZ0lVj9MxPxkxJ2ta+/UlLr+CFzc5bOceMw==",
"win32-x64": "sha512-BwKckinJZ0Fu/EcunqiLPwOLEBWp4xf8GV7nvmVuKKz5f6B+GxoA2k9aa2wueqv4r4RJVgV/aWXZWFKOIjre/Q=="
},
"runtime": "napi",
"target": 7
},
"engines": {
"node": ">=8.5.0"
"node": ">=14.15.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"binary": {
"napi_versions": [
7
]
},
"semistandard": {
"env": [
@@ -137,10 +195,10 @@
"cc": {
"linelength": "120",
"filter": [
"build/c++11",
"build/include",
"runtime/indentation_namespace",
"runtime/references"
"build/include"
]
},
"tsd": {
"directory": "test/types/"
}
}

View File

@@ -1,27 +1,15 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
#include <cstdlib>
#include <string>
#include <string.h>
#include <vector>
#include <queue>
#include <mutex>
#include <map>
#include <mutex> // NOLINT(build/c++11)
#include <node.h>
#include <node_buffer.h>
#include <nan.h>
#include <napi.h>
#include <vips/vips8>
#include "common.h"
@@ -30,61 +18,150 @@ using vips::VImage;
namespace sharp {
// Convenience methods to access the attributes of a v8::Object
bool HasAttr(v8::Local<v8::Object> obj, std::string attr) {
return Nan::Has(obj, Nan::New(attr).ToLocalChecked()).FromJust();
// Convenience methods to access the attributes of a Napi::Object
bool HasAttr(Napi::Object obj, std::string attr) {
return obj.Has(attr);
}
std::string AttrAsStr(v8::Local<v8::Object> obj, std::string attr) {
return *Nan::Utf8String(Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked());
std::string AttrAsStr(Napi::Object obj, std::string attr) {
return obj.Get(attr).As<Napi::String>();
}
std::vector<double> AttrAsRgba(v8::Local<v8::Object> obj, std::string attr) {
v8::Local<v8::Object> background = AttrAs<v8::Object>(obj, attr);
std::vector<double> rgba(4);
for (unsigned int i = 0; i < 4; i++) {
rgba[i] = AttrTo<double>(background, i);
std::string AttrAsStr(Napi::Object obj, unsigned int const attr) {
return obj.Get(attr).As<Napi::String>();
}
uint32_t AttrAsUint32(Napi::Object obj, std::string attr) {
return obj.Get(attr).As<Napi::Number>().Uint32Value();
}
int32_t AttrAsInt32(Napi::Object obj, std::string attr) {
return obj.Get(attr).As<Napi::Number>().Int32Value();
}
int32_t AttrAsInt32(Napi::Object obj, unsigned int const attr) {
return obj.Get(attr).As<Napi::Number>().Int32Value();
}
int64_t AttrAsInt64(Napi::Object obj, std::string attr) {
return obj.Get(attr).As<Napi::Number>().Int64Value();
}
double AttrAsDouble(Napi::Object obj, std::string attr) {
return obj.Get(attr).As<Napi::Number>().DoubleValue();
}
double AttrAsDouble(Napi::Object obj, unsigned int const attr) {
return obj.Get(attr).As<Napi::Number>().DoubleValue();
}
bool AttrAsBool(Napi::Object obj, std::string attr) {
return obj.Get(attr).As<Napi::Boolean>().Value();
}
std::vector<double> AttrAsVectorOfDouble(Napi::Object obj, std::string attr) {
Napi::Array napiArray = obj.Get(attr).As<Napi::Array>();
std::vector<double> vectorOfDouble(napiArray.Length());
for (unsigned int i = 0; i < napiArray.Length(); i++) {
vectorOfDouble[i] = AttrAsDouble(napiArray, i);
}
return 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 v8::Object describing an input image
InputDescriptor* CreateInputDescriptor(
v8::Local<v8::Object> input, std::vector<v8::Local<v8::Object>> &buffersToPersist
) {
Nan::HandleScope();
// Create an InputDescriptor instance from a Napi::Object describing an input image
InputDescriptor* CreateInputDescriptor(Napi::Object input) {
InputDescriptor *descriptor = new InputDescriptor;
if (HasAttr(input, "file")) {
descriptor->file = AttrAsStr(input, "file");
} else if (HasAttr(input, "buffer")) {
v8::Local<v8::Object> buffer = AttrAs<v8::Object>(input, "buffer");
descriptor->bufferLength = node::Buffer::Length(buffer);
descriptor->buffer = node::Buffer::Data(buffer);
buffersToPersist.push_back(buffer);
Napi::Buffer<char> buffer = input.Get("buffer").As<Napi::Buffer<char>>();
descriptor->bufferLength = buffer.Length();
descriptor->buffer = buffer.Data();
descriptor->isBuffer = TRUE;
}
descriptor->failOnError = AttrTo<bool>(input, "failOnError");
descriptor->failOn = AttrAsEnum<VipsFailOn>(input, "failOn", VIPS_TYPE_FAIL_ON);
// Density for vector-based input
if (HasAttr(input, "density")) {
descriptor->density = AttrTo<double>(input, "density");
descriptor->density = AttrAsDouble(input, "density");
}
// Should we ignore any embedded ICC profile
if (HasAttr(input, "ignoreIcc")) {
descriptor->ignoreIcc = AttrAsBool(input, "ignoreIcc");
}
// Raw pixel input
if (HasAttr(input, "rawChannels")) {
descriptor->rawChannels = AttrTo<uint32_t>(input, "rawChannels");
descriptor->rawWidth = AttrTo<uint32_t>(input, "rawWidth");
descriptor->rawHeight = AttrTo<uint32_t>(input, "rawHeight");
descriptor->rawDepth = AttrAsEnum<VipsBandFormat>(input, "rawDepth", VIPS_TYPE_BAND_FORMAT);
descriptor->rawChannels = AttrAsUint32(input, "rawChannels");
descriptor->rawWidth = AttrAsUint32(input, "rawWidth");
descriptor->rawHeight = AttrAsUint32(input, "rawHeight");
descriptor->rawPremultiplied = AttrAsBool(input, "rawPremultiplied");
}
// Multi-page input (GIF, TIFF, PDF)
if (HasAttr(input, "pages")) {
descriptor->pages = AttrTo<int32_t>(input, "pages");
descriptor->pages = AttrAsInt32(input, "pages");
}
if (HasAttr(input, "page")) {
descriptor->page = AttrTo<uint32_t>(input, "page");
descriptor->page = AttrAsUint32(input, "page");
}
// Multi-level input (OpenSlide)
if (HasAttr(input, "level")) {
descriptor->level = AttrAsUint32(input, "level");
}
// subIFD (OME-TIFF)
if (HasAttr(input, "subifd")) {
descriptor->subifd = AttrAsInt32(input, "subifd");
}
// Create new image
if (HasAttr(input, "createChannels")) {
descriptor->createChannels = AttrTo<uint32_t>(input, "createChannels");
descriptor->createWidth = AttrTo<uint32_t>(input, "createWidth");
descriptor->createHeight = AttrTo<uint32_t>(input, "createHeight");
descriptor->createBackground = AttrAsRgba(input, "createBackground");
descriptor->createChannels = AttrAsUint32(input, "createChannels");
descriptor->createWidth = AttrAsUint32(input, "createWidth");
descriptor->createHeight = AttrAsUint32(input, "createHeight");
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");
}
}
// Create new image with text
if (HasAttr(input, "textValue")) {
descriptor->textValue = AttrAsStr(input, "textValue");
if (HasAttr(input, "textFont")) {
descriptor->textFont = AttrAsStr(input, "textFont");
}
if (HasAttr(input, "textFontfile")) {
descriptor->textFontfile = AttrAsStr(input, "textFontfile");
}
if (HasAttr(input, "textWidth")) {
descriptor->textWidth = AttrAsUint32(input, "textWidth");
}
if (HasAttr(input, "textHeight")) {
descriptor->textHeight = AttrAsUint32(input, "textHeight");
}
if (HasAttr(input, "textAlign")) {
descriptor->textAlign = AttrAsEnum<VipsAlign>(input, "textAlign", VIPS_TYPE_ALIGN);
}
if (HasAttr(input, "textJustify")) {
descriptor->textJustify = AttrAsBool(input, "textJustify");
}
if (HasAttr(input, "textDpi")) {
descriptor->textDpi = AttrAsUint32(input, "textDpi");
}
if (HasAttr(input, "textRgba")) {
descriptor->textRgba = AttrAsBool(input, "textRgba");
}
if (HasAttr(input, "textSpacing")) {
descriptor->textSpacing = AttrAsUint32(input, "textSpacing");
}
if (HasAttr(input, "textWrap")) {
descriptor->textWrap = AttrAsEnum<VipsTextWrap>(input, "textWrap", VIPS_TYPE_TEXT_WRAP);
}
}
// Limit input images to a given number of pixels, where pixels = width * height
descriptor->limitInputPixels = static_cast<uint64_t>(AttrAsInt64(input, "limitInputPixels"));
// Allow switch from random to sequential access
descriptor->access = AttrAsBool(input, "sequentialRead") ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
// Remove safety features and allow unlimited input
descriptor->unlimited = AttrAsBool(input, "unlimited");
return descriptor;
}
@@ -107,6 +184,13 @@ namespace sharp {
bool IsWebp(std::string const &str) {
return EndsWith(str, ".webp") || EndsWith(str, ".WEBP");
}
bool IsGif(std::string const &str) {
return EndsWith(str, ".gif") || EndsWith(str, ".GIF");
}
bool IsJp2(std::string const &str) {
return EndsWith(str, ".jp2") || EndsWith(str, ".jpx") || EndsWith(str, ".j2k") || EndsWith(str, ".j2c")
|| EndsWith(str, ".JP2") || EndsWith(str, ".JPX") || EndsWith(str, ".J2K") || EndsWith(str, ".J2C");
}
bool IsTiff(std::string const &str) {
return EndsWith(str, ".tif") || EndsWith(str, ".tiff") || EndsWith(str, ".TIF") || EndsWith(str, ".TIFF");
}
@@ -119,6 +203,9 @@ namespace sharp {
bool IsAvif(std::string const &str) {
return EndsWith(str, ".avif") || EndsWith(str, ".AVIF");
}
bool IsJxl(std::string const &str) {
return EndsWith(str, ".jxl") || EndsWith(str, ".JXL");
}
bool IsDz(std::string const &str) {
return EndsWith(str, ".dzi") || EndsWith(str, ".DZI");
}
@@ -129,6 +216,13 @@ namespace sharp {
return EndsWith(str, ".v") || EndsWith(str, ".V") || EndsWith(str, ".vips") || EndsWith(str, ".VIPS");
}
/*
Trim space from end of string.
*/
std::string TrimEnd(std::string const &str) {
return str.substr(0, str.find_last_not_of(" \n\r\f") + 1);
}
/*
Provide a string identifier for the given image type.
*/
@@ -140,6 +234,7 @@ namespace sharp {
case ImageType::WEBP: id = "webp"; break;
case ImageType::TIFF: id = "tiff"; break;
case ImageType::GIF: id = "gif"; break;
case ImageType::JP2: id = "jp2"; break;
case ImageType::SVG: id = "svg"; break;
case ImageType::HEIF: id = "heif"; break;
case ImageType::PDF: id = "pdf"; break;
@@ -147,6 +242,8 @@ namespace sharp {
case ImageType::OPENSLIDE: id = "openslide"; break;
case ImageType::PPM: id = "ppm"; break;
case ImageType::FITS: id = "fits"; break;
case ImageType::EXR: id = "exr"; break;
case ImageType::JXL: id = "jxl"; break;
case ImageType::VIPS: id = "vips"; break;
case ImageType::RAW: id = "raw"; break;
case ImageType::UNKNOWN: id = "unknown"; break;
@@ -155,32 +252,59 @@ namespace sharp {
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 },
{ "VipsForeignLoadNsgifFile", ImageType::GIF },
{ "VipsForeignLoadNsgifBuffer", ImageType::GIF },
{ "VipsForeignLoadJp2kBuffer", ImageType::JP2 },
{ "VipsForeignLoadJp2kFile", ImageType::JP2 },
{ "VipsForeignLoadSvgFile", ImageType::SVG },
{ "VipsForeignLoadSvgBuffer", ImageType::SVG },
{ "VipsForeignLoadHeifFile", ImageType::HEIF },
{ "VipsForeignLoadHeifBuffer", ImageType::HEIF },
{ "VipsForeignLoadPdfFile", ImageType::PDF },
{ "VipsForeignLoadPdfBuffer", ImageType::PDF },
{ "VipsForeignLoadMagickFile", ImageType::MAGICK },
{ "VipsForeignLoadMagickBuffer", ImageType::MAGICK },
{ "VipsForeignLoadMagick7File", ImageType::MAGICK },
{ "VipsForeignLoadMagick7Buffer", ImageType::MAGICK },
{ "VipsForeignLoadOpenslideFile", ImageType::OPENSLIDE },
{ "VipsForeignLoadPpmFile", ImageType::PPM },
{ "VipsForeignLoadFitsFile", ImageType::FITS },
{ "VipsForeignLoadOpenexr", ImageType::EXR },
{ "VipsForeignLoadJxlFile", ImageType::JXL },
{ "VipsForeignLoadJxlBuffer", ImageType::JXL },
{ "VipsForeignLoadVips", ImageType::VIPS },
{ "VipsForeignLoadVipsFile", ImageType::VIPS },
{ "VipsForeignLoadRaw", ImageType::RAW }
};
/*
Determine image format of a buffer.
*/
ImageType DetermineImageType(void *buffer, size_t const length) {
ImageType imageType = ImageType::UNKNOWN;
char const *load = vips_foreign_find_load_buffer(buffer, length);
if (load != NULL) {
std::string const loader = load;
if (EndsWith(loader, "JpegBuffer")) {
imageType = ImageType::JPEG;
} else if (EndsWith(loader, "PngBuffer")) {
imageType = ImageType::PNG;
} else if (EndsWith(loader, "WebpBuffer")) {
imageType = ImageType::WEBP;
} else if (EndsWith(loader, "TiffBuffer")) {
imageType = ImageType::TIFF;
} else if (EndsWith(loader, "GifBuffer")) {
imageType = ImageType::GIF;
} else if (EndsWith(loader, "SvgBuffer")) {
imageType = ImageType::SVG;
} else if (EndsWith(loader, "HeifBuffer")) {
imageType = ImageType::HEIF;
} else if (EndsWith(loader, "PdfBuffer")) {
imageType = ImageType::PDF;
} else if (EndsWith(loader, "MagickBuffer")) {
imageType = ImageType::MAGICK;
if (load != nullptr) {
auto it = loaderToType.find(load);
if (it != loaderToType.end()) {
imageType = it->second;
}
}
return imageType;
@@ -193,36 +317,12 @@ namespace sharp {
ImageType imageType = ImageType::UNKNOWN;
char const *load = vips_foreign_find_load(file);
if (load != nullptr) {
std::string const loader = load;
if (EndsWith(loader, "JpegFile")) {
imageType = ImageType::JPEG;
} else if (EndsWith(loader, "Png")) {
imageType = ImageType::PNG;
} else if (EndsWith(loader, "WebpFile")) {
imageType = ImageType::WEBP;
} else if (EndsWith(loader, "Openslide")) {
imageType = ImageType::OPENSLIDE;
} else if (EndsWith(loader, "TiffFile")) {
imageType = ImageType::TIFF;
} else if (EndsWith(loader, "GifFile")) {
imageType = ImageType::GIF;
} else if (EndsWith(loader, "SvgFile")) {
imageType = ImageType::SVG;
} else if (EndsWith(loader, "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;
auto it = loaderToType.find(load);
if (it != loaderToType.end()) {
imageType = it->second;
}
} else {
if (EndsWith(vips::VError().what(), " not found\n")) {
if (EndsWith(vips::VError().what(), " does not exist\n")) {
imageType = ImageType::MISSING;
}
}
@@ -234,28 +334,45 @@ namespace sharp {
*/
bool ImageTypeSupportsPage(ImageType imageType) {
return
imageType == ImageType::WEBP ||
imageType == ImageType::MAGICK ||
imageType == ImageType::GIF ||
imageType == ImageType::JP2 ||
imageType == ImageType::TIFF ||
imageType == ImageType::HEIF ||
imageType == ImageType::PDF;
}
/*
Does this image type support removal of safety limits?
*/
bool ImageTypeSupportsUnlimited(ImageType imageType) {
return
imageType == ImageType::JPEG ||
imageType == ImageType::PNG ||
imageType == ImageType::SVG ||
imageType == ImageType::HEIF;
}
/*
Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data)
*/
std::tuple<VImage, ImageType> OpenInput(InputDescriptor *descriptor, VipsAccess accessMethod) {
std::tuple<VImage, ImageType> OpenInput(InputDescriptor *descriptor) {
VImage image;
ImageType imageType;
if (descriptor->buffer != nullptr) {
if (descriptor->isBuffer) {
if (descriptor->rawChannels > 0) {
// Raw, uncompressed pixel data
image = VImage::new_from_memory(descriptor->buffer, descriptor->bufferLength,
descriptor->rawWidth, descriptor->rawHeight, descriptor->rawChannels, VIPS_FORMAT_UCHAR);
descriptor->rawWidth, descriptor->rawHeight, descriptor->rawChannels, descriptor->rawDepth);
if (descriptor->rawChannels < 3) {
image.get_image()->Type = VIPS_INTERPRETATION_B_W;
} else {
image.get_image()->Type = VIPS_INTERPRETATION_sRGB;
}
if (descriptor->rawPremultiplied) {
image = image.unpremultiply();
}
imageType = ImageType::RAW;
} else {
// Compressed data
@@ -263,8 +380,11 @@ namespace sharp {
if (imageType != ImageType::UNKNOWN) {
try {
vips::VOption *option = VImage::option()
->set("access", accessMethod)
->set("fail", descriptor->failOnError);
->set("access", descriptor->access)
->set("fail_on", descriptor->failOn);
if (descriptor->unlimited && ImageTypeSupportsUnlimited(imageType)) {
option->set("unlimited", TRUE);
}
if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
option->set("dpi", descriptor->density);
}
@@ -275,9 +395,15 @@ namespace sharp {
option->set("n", descriptor->pages);
option->set("page", descriptor->page);
}
if (imageType == ImageType::OPENSLIDE) {
option->set("level", descriptor->level);
}
if (imageType == ImageType::TIFF) {
option->set("subifd", descriptor->subifd);
}
image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option);
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
SetDensity(image, descriptor->density);
image = SetDensity(image, descriptor->density);
}
} catch (vips::VError const &err) {
throw vips::VError(std::string("Input buffer has corrupt header: ") + err.what());
@@ -287,30 +413,82 @@ namespace sharp {
}
}
} else {
if (descriptor->createChannels > 0) {
int const channels = descriptor->createChannels;
if (channels > 0) {
// Create new image
std::vector<double> background = {
descriptor->createBackground[0],
descriptor->createBackground[1],
descriptor->createBackground[2]
};
if (descriptor->createChannels == 4) {
background.push_back(descriptor->createBackground[3]);
if (descriptor->createNoiseType == "gaussian") {
std::vector<VImage> bands = {};
bands.reserve(channels);
for (int _band = 0; _band < channels; _band++) {
bands.push_back(VImage::gaussnoise(descriptor->createWidth, descriptor->createHeight, VImage::option()
->set("mean", descriptor->createNoiseMean)
->set("sigma", descriptor->createNoiseSigma)));
}
image = VImage::bandjoin(bands).copy(VImage::option()->set("interpretation",
channels < 3 ? VIPS_INTERPRETATION_B_W: VIPS_INTERPRETATION_sRGB));
} else {
std::vector<double> background = {
descriptor->createBackground[0],
descriptor->createBackground[1],
descriptor->createBackground[2]
};
if (channels == 4) {
background.push_back(descriptor->createBackground[3]);
}
image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight)
.copy(VImage::option()->set("interpretation",
channels < 3 ? VIPS_INTERPRETATION_B_W : VIPS_INTERPRETATION_sRGB))
.new_from_image(background);
}
image = image.cast(VIPS_FORMAT_UCHAR);
imageType = ImageType::RAW;
} else if (descriptor->textValue.length() > 0) {
// Create a new image with text
vips::VOption *textOptions = VImage::option()
->set("align", descriptor->textAlign)
->set("justify", descriptor->textJustify)
->set("rgba", descriptor->textRgba)
->set("spacing", descriptor->textSpacing)
->set("wrap", descriptor->textWrap)
->set("autofit_dpi", &descriptor->textAutofitDpi);
if (descriptor->textWidth > 0) {
textOptions->set("width", descriptor->textWidth);
}
// Ignore dpi if height is set
if (descriptor->textWidth > 0 && descriptor->textHeight > 0) {
textOptions->set("height", descriptor->textHeight);
} else if (descriptor->textDpi > 0) {
textOptions->set("dpi", descriptor->textDpi);
}
if (descriptor->textFont.length() > 0) {
textOptions->set("font", const_cast<char*>(descriptor->textFont.data()));
}
if (descriptor->textFontfile.length() > 0) {
textOptions->set("fontfile", const_cast<char*>(descriptor->textFontfile.data()));
}
image = VImage::text(const_cast<char *>(descriptor->textValue.data()), textOptions);
if (!descriptor->textRgba) {
image = image.copy(VImage::option()->set("interpretation", VIPS_INTERPRETATION_B_W));
}
image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight).new_from_image(background);
image.get_image()->Type = VIPS_INTERPRETATION_sRGB;
imageType = ImageType::RAW;
} else {
// From filesystem
imageType = DetermineImageType(descriptor->file.data());
if (imageType == ImageType::MISSING) {
throw vips::VError("Input file is missing");
if (descriptor->file.find("<svg") != std::string::npos) {
throw vips::VError("Input file is missing, did you mean "
"sharp(Buffer.from('" + descriptor->file.substr(0, 8) + "...')?");
}
throw vips::VError("Input file is missing: " + descriptor->file);
}
if (imageType != ImageType::UNKNOWN) {
try {
vips::VOption *option = VImage::option()
->set("access", accessMethod)
->set("fail", descriptor->failOnError);
->set("access", descriptor->access)
->set("fail_on", descriptor->failOn);
if (descriptor->unlimited && ImageTypeSupportsUnlimited(imageType)) {
option->set("unlimited", TRUE);
}
if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
option->set("dpi", descriptor->density);
}
@@ -321,9 +499,15 @@ namespace sharp {
option->set("n", descriptor->pages);
option->set("page", descriptor->page);
}
if (imageType == ImageType::OPENSLIDE) {
option->set("level", descriptor->level);
}
if (imageType == ImageType::TIFF) {
option->set("subifd", descriptor->subifd);
}
image = VImage::new_from_file(descriptor->file.data(), option);
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
SetDensity(image, descriptor->density);
image = SetDensity(image, descriptor->density);
}
} catch (vips::VError const &err) {
throw vips::VError(std::string("Input file has corrupt header: ") + err.what());
@@ -333,6 +517,12 @@ namespace sharp {
}
}
}
// Limit input images to a given number of pixels, where pixels = width * height
if (descriptor->limitInputPixels > 0 &&
static_cast<uint64_t>(image.width()) * image.height() > descriptor->limitInputPixels) {
throw vips::VError("Input image exceeds pixel limit");
}
return std::make_tuple(image, imageType);
}
@@ -348,12 +538,7 @@ namespace sharp {
Uses colour space interpretation with number of channels to guess this.
*/
bool HasAlpha(VImage image) {
int const bands = image.bands();
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));
return image.has_alpha();
}
/*
@@ -370,15 +555,66 @@ namespace sharp {
/*
Set EXIF Orientation of image.
*/
void SetExifOrientation(VImage image, int const orientation) {
image.set(VIPS_META_ORIENTATION, orientation);
VImage SetExifOrientation(VImage image, int const orientation) {
VImage copy = image.copy();
copy.set(VIPS_META_ORIENTATION, orientation);
return copy;
}
/*
Remove EXIF Orientation from image.
*/
void RemoveExifOrientation(VImage image) {
vips_image_remove(image.get_image(), VIPS_META_ORIENTATION);
VImage RemoveExifOrientation(VImage image) {
VImage copy = image.copy();
copy.remove(VIPS_META_ORIENTATION);
copy.remove("exif-ifd0-Orientation");
return copy;
}
/*
Set animation properties if necessary.
*/
VImage SetAnimationProperties(VImage image, int nPages, int pageHeight, std::vector<int> delay, int loop) {
bool hasDelay = !delay.empty();
// Avoid a copy if none of the animation properties are needed.
if (nPages == 1 && !hasDelay && loop == -1) return image;
if (delay.size() == 1) {
// We have just one delay, repeat that value for all frames.
delay.insert(delay.end(), nPages - 1, delay[0]);
}
// Attaching metadata, need to copy the image.
VImage copy = image.copy();
// Only set page-height if we have more than one page, or this could
// accidentally turn into an animated image later.
if (nPages > 1) copy.set(VIPS_META_PAGE_HEIGHT, pageHeight);
if (hasDelay) copy.set("delay", delay);
if (loop != -1) copy.set("loop", loop);
return copy;
}
/*
Remove animation properties from image.
*/
VImage RemoveAnimationProperties(VImage image) {
VImage copy = image.copy();
copy.remove(VIPS_META_PAGE_HEIGHT);
copy.remove("delay");
copy.remove("loop");
return copy;
}
/*
Remove GIF palette from image.
*/
VImage RemoveGifPalette(VImage image) {
VImage copy = image.copy();
copy.remove("gif-palette");
return copy;
}
/*
@@ -398,36 +634,54 @@ namespace sharp {
/*
Set pixels/mm resolution based on a pixels/inch density.
*/
void SetDensity(VImage image, const double density) {
VImage SetDensity(VImage image, const double density) {
const double pixelsPerMm = density / 25.4;
image.set("Xres", pixelsPerMm);
image.set("Yres", pixelsPerMm);
image.set(VIPS_META_RESOLUTION_UNIT, "in");
VImage copy = image.copy();
copy.get_image()->Xres = pixelsPerMm;
copy.get_image()->Yres = pixelsPerMm;
return copy;
}
/*
Multi-page images can have a page height. Fetch it, and sanity check it.
If page-height is not set, it defaults to the image height
*/
int GetPageHeight(VImage image) {
return vips_image_get_page_height(image.get_image());
}
/*
Check the proposed format supports the current dimensions.
*/
void AssertImageTypeDimensions(VImage image, ImageType const imageType) {
const int height = image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT
? image.get_int(VIPS_META_PAGE_HEIGHT)
: image.height();
if (imageType == ImageType::JPEG) {
if (image.width() > 65535 || image.height() > 65535) {
if (image.width() > 65535 || height > 65535) {
throw vips::VError("Processed image is too large for the JPEG format");
}
} else if (imageType == ImageType::WEBP) {
if (image.width() > 16383 || image.height() > 16383) {
if (image.width() > 16383 || height > 16383) {
throw vips::VError("Processed image is too large for the WebP format");
}
} else if (imageType == ImageType::GIF) {
if (image.width() > 65535 || height > 65535) {
throw vips::VError("Processed image is too large for the GIF format");
}
} else if (imageType == ImageType::HEIF) {
if (image.width() > 16384 || height > 16384) {
throw vips::VError("Processed image is too large for the HEIF format");
}
}
}
/*
Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows
*/
void FreeCallback(char* data, void* hint) {
if (data != nullptr) {
g_free(data);
}
}
std::function<void(void*, char*)> FreeCallback = [](void*, char* data) {
g_free(data);
};
/*
Temporary buffer of warnings
@@ -456,6 +710,32 @@ namespace sharp {
return warning;
}
/*
Attach an event listener for progress updates, used to detect timeout
*/
void SetTimeout(VImage image, int const seconds) {
if (seconds > 0) {
VipsImage *im = image.get_image();
if (im->progress_signal == NULL) {
int *timeout = VIPS_NEW(im, int);
*timeout = seconds;
g_signal_connect(im, "eval", G_CALLBACK(VipsProgressCallBack), timeout);
vips_image_set_progress(im, TRUE);
}
}
}
/*
Event listener for progress updates, used to detect timeout
*/
void VipsProgressCallBack(VipsImage *im, VipsProgress *progress, int *timeout) {
if (*timeout > 0 && progress->run >= *timeout) {
vips_image_set_kill(im, TRUE);
vips_error("timeout", "%d%% complete", progress->percent);
*timeout = 0;
}
}
/*
Calculate the (left, top) coordinates of the output image
within the input image, applying the given gravity during an embed.
@@ -575,26 +855,18 @@ namespace sharp {
int top = 0;
// assign only if valid
if (x >= 0 && x < (inWidth - outWidth)) {
if (x < (inWidth - outWidth)) {
left = x;
} else if (x >= (inWidth - outWidth)) {
left = inWidth - outWidth;
}
if (y >= 0 && y < (inHeight - outHeight)) {
if (y < (inHeight - outHeight)) {
top = y;
} else if (y >= (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);
}
@@ -613,42 +885,30 @@ namespace sharp {
return Is16Bit(interpretation) ? 65535.0 : 255.0;
}
/*
Get boolean operation type from string
*/
VipsOperationBoolean GetBooleanOperation(std::string const opStr) {
return static_cast<VipsOperationBoolean>(
vips_enum_from_nick(nullptr, VIPS_TYPE_OPERATION_BOOLEAN, opStr.data()));
}
/*
Get interpretation type from string
*/
VipsInterpretation GetInterpretation(std::string const typeStr) {
return static_cast<VipsInterpretation>(
vips_enum_from_nick(nullptr, VIPS_TYPE_INTERPRETATION, typeStr.data()));
}
/*
Convert RGBA value to another colourspace
*/
std::vector<double> GetRgbaAsColourspace(std::vector<double> const rgba, VipsInterpretation const interpretation) {
std::vector<double> GetRgbaAsColourspace(std::vector<double> const rgba,
VipsInterpretation const interpretation, bool premultiply) {
int const bands = static_cast<int>(rgba.size());
if (bands < 3 || interpretation == VIPS_INTERPRETATION_sRGB || interpretation == VIPS_INTERPRETATION_RGB) {
if (bands < 3) {
return rgba;
} else {
VImage pixel = VImage::new_matrix(1, 1);
pixel.set("bands", bands);
pixel = pixel.new_from_image(rgba);
pixel = pixel.colourspace(interpretation, VImage::option()->set("source_space", VIPS_INTERPRETATION_sRGB));
return pixel(0, 0);
}
VImage pixel = VImage::new_matrix(1, 1);
pixel.set("bands", bands);
pixel = pixel
.new_from_image(rgba)
.colourspace(interpretation, VImage::option()->set("source_space", VIPS_INTERPRETATION_sRGB));
if (premultiply) {
pixel = pixel.premultiply();
}
return pixel(0, 0);
}
/*
Apply the alpha channel to a given colour
*/
std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour) {
std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour, bool premultiply) {
// Scale up 8-bit values to match 16-bit input image
double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
// Create alphaColour colour
@@ -672,13 +932,111 @@ namespace sharp {
alphaColour.push_back(colour[3] * multiplier);
}
// Ensure alphaColour colour uses correct colourspace
alphaColour = sharp::GetRgbaAsColourspace(alphaColour, image.interpretation());
alphaColour = sharp::GetRgbaAsColourspace(alphaColour, image.interpretation(), premultiply);
// Add non-transparent alpha channel, if required
if (colour[3] < 255.0 && !HasAlpha(image)) {
image = image.bandjoin(
VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier));
VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier).cast(image.format()));
}
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, double const value) {
if (!HasAlpha(image)) {
std::vector<double> alpha;
alpha.push_back(value * sharp::MaximumImageAlpha(image.interpretation()));
image = image.bandjoin_const(alpha);
}
return image;
}
std::pair<double, double> ResolveShrink(int width, int height, int targetWidth, int targetHeight,
Canvas canvas, bool withoutEnlargement, bool withoutReduction) {
double hshrink = 1.0;
double vshrink = 1.0;
if (targetWidth > 0 && targetHeight > 0) {
// Fixed width and height
hshrink = static_cast<double>(width) / targetWidth;
vshrink = static_cast<double>(height) / targetHeight;
switch (canvas) {
case Canvas::CROP:
case Canvas::MIN:
if (hshrink < vshrink) {
vshrink = hshrink;
} else {
hshrink = vshrink;
}
break;
case Canvas::EMBED:
case Canvas::MAX:
if (hshrink > vshrink) {
vshrink = hshrink;
} else {
hshrink = vshrink;
}
break;
case Canvas::IGNORE_ASPECT:
break;
}
} else if (targetWidth > 0) {
// Fixed width
hshrink = static_cast<double>(width) / targetWidth;
if (canvas != Canvas::IGNORE_ASPECT) {
// Auto height
vshrink = hshrink;
}
} else if (targetHeight > 0) {
// Fixed height
vshrink = static_cast<double>(height) / targetHeight;
if (canvas != Canvas::IGNORE_ASPECT) {
// Auto width
hshrink = vshrink;
}
}
// We should not reduce or enlarge the output image, if
// withoutReduction or withoutEnlargement is specified.
if (withoutReduction) {
// Equivalent of VIPS_SIZE_UP
hshrink = std::min(1.0, hshrink);
vshrink = std::min(1.0, vshrink);
} else if (withoutEnlargement) {
// Equivalent of VIPS_SIZE_DOWN
hshrink = std::max(1.0, hshrink);
vshrink = std::max(1.0, vshrink);
}
// We don't want to shrink so much that we send an axis to 0
hshrink = std::min(hshrink, static_cast<double>(width));
vshrink = std::min(vshrink, static_cast<double>(height));
return std::make_pair(hshrink, vshrink);
}
/*
Ensure decoding remains sequential.
*/
VImage StaySequential(VImage image, VipsAccess access, bool condition) {
if (access == VIPS_ACCESS_SEQUENTIAL && condition) {
return image.copy_memory();
}
return image;
}
} // namespace sharp

View File

@@ -1,16 +1,5 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
#ifndef SRC_COMMON_H_
#define SRC_COMMON_H_
@@ -19,23 +8,24 @@
#include <tuple>
#include <vector>
#include <node.h>
#include <nan.h>
#include <napi.h>
#include <vips/vips8>
// Verify platform and compiler compatibility
#if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 8))
#error libvips version 8.8.0+ is required - see sharp.pixelplumbing.com/page/install
#if (VIPS_MAJOR_VERSION < 8) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 14) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 14 && VIPS_MICRO_VERSION < 5)
#error "libvips version 8.14.5+ is required - please see https://sharp.pixelplumbing.com/install"
#endif
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
#error GCC version 4.6+ is required for C++11 features - see sharp.pixelplumbing.com/page/install#prerequisites
#error "GCC version 4.6+ is required for C++11 features - please see https://sharp.pixelplumbing.com/install"
#endif
#if (defined(__clang__) && defined(__has_feature))
#if (!__has_feature(cxx_range_for))
#error clang version 3.0+ is required for C++11 features - see sharp.pixelplumbing.com/page/install#prerequisites
#error "clang version 3.0+ is required for C++11 features - please see https://sharp.pixelplumbing.com/install"
#endif
#endif
@@ -43,61 +33,108 @@ using vips::VImage;
namespace sharp {
struct InputDescriptor {
struct InputDescriptor { // NOLINT(runtime/indentation_namespace)
std::string name;
std::string file;
char *buffer;
bool failOnError;
VipsFailOn failOn;
uint64_t limitInputPixels;
bool unlimited;
VipsAccess access;
size_t bufferLength;
bool isBuffer;
double density;
bool ignoreIcc;
VipsBandFormat rawDepth;
int rawChannels;
int rawWidth;
int rawHeight;
bool rawPremultiplied;
int pages;
int page;
int level;
int subifd;
int createChannels;
int createWidth;
int createHeight;
std::vector<double> createBackground;
std::string createNoiseType;
double createNoiseMean;
double createNoiseSigma;
std::string textValue;
std::string textFont;
std::string textFontfile;
int textWidth;
int textHeight;
VipsAlign textAlign;
bool textJustify;
int textDpi;
bool textRgba;
int textSpacing;
VipsTextWrap textWrap;
int textAutofitDpi;
InputDescriptor():
buffer(nullptr),
failOnError(TRUE),
failOn(VIPS_FAIL_ON_WARNING),
limitInputPixels(0x3FFF * 0x3FFF),
unlimited(FALSE),
access(VIPS_ACCESS_RANDOM),
bufferLength(0),
isBuffer(FALSE),
density(72.0),
ignoreIcc(FALSE),
rawDepth(VIPS_FORMAT_UCHAR),
rawChannels(0),
rawWidth(0),
rawHeight(0),
rawPremultiplied(false),
pages(1),
page(0),
level(0),
subifd(-1),
createChannels(0),
createWidth(0),
createHeight(0),
createBackground{ 0.0, 0.0, 0.0, 255.0 } {}
createBackground{ 0.0, 0.0, 0.0, 255.0 },
createNoiseMean(0.0),
createNoiseSigma(0.0),
textWidth(0),
textHeight(0),
textAlign(VIPS_ALIGN_LOW),
textJustify(FALSE),
textDpi(72),
textRgba(FALSE),
textSpacing(0),
textWrap(VIPS_TEXT_WRAP_WORD),
textAutofitDpi(0) {}
};
// Convenience methods to access the attributes of a v8::Object
bool HasAttr(v8::Local<v8::Object> obj, std::string attr);
std::string AttrAsStr(v8::Local<v8::Object> obj, std::string attr);
std::vector<double> AttrAsRgba(v8::Local<v8::Object> obj, std::string attr);
template<typename T> v8::Local<T> AttrAs(v8::Local<v8::Object> obj, std::string attr) {
return Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked().As<T>();
}
template<typename T> T AttrTo(v8::Local<v8::Object> obj, std::string attr) {
return Nan::To<T>(Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked()).FromJust();
}
template<typename T> T AttrTo(v8::Local<v8::Object> obj, int attr) {
return Nan::To<T>(Nan::Get(obj, attr).ToLocalChecked()).FromJust();
// Convenience methods to access the attributes of a Napi::Object
bool HasAttr(Napi::Object obj, std::string attr);
std::string AttrAsStr(Napi::Object obj, std::string attr);
std::string AttrAsStr(Napi::Object obj, unsigned int const attr);
uint32_t AttrAsUint32(Napi::Object obj, std::string attr);
int32_t AttrAsInt32(Napi::Object obj, std::string attr);
int32_t AttrAsInt32(Napi::Object obj, unsigned int const attr);
double AttrAsDouble(Napi::Object obj, std::string attr);
double AttrAsDouble(Napi::Object obj, unsigned int const attr);
bool AttrAsBool(Napi::Object obj, std::string attr);
std::vector<double> AttrAsVectorOfDouble(Napi::Object obj, std::string attr);
std::vector<int32_t> AttrAsInt32Vector(Napi::Object obj, std::string attr);
template <class T> T AttrAsEnum(Napi::Object obj, std::string attr, GType type) {
return static_cast<T>(
vips_enum_from_nick(nullptr, type, AttrAsStr(obj, attr).data()));
}
// Create an InputDescriptor instance from a v8::Object describing an input image
InputDescriptor* CreateInputDescriptor(
v8::Local<v8::Object> input, std::vector<v8::Local<v8::Object>> &buffersToPersist);
// Create an InputDescriptor instance from a Napi::Object describing an input image
InputDescriptor* CreateInputDescriptor(Napi::Object input);
enum class ImageType {
JPEG,
PNG,
WEBP,
JP2,
TIFF,
GIF,
SVG,
@@ -107,12 +144,22 @@ namespace sharp {
OPENSLIDE,
PPM,
FITS,
EXR,
JXL,
VIPS,
RAW,
UNKNOWN,
MISSING
};
enum class Canvas {
CROP,
EMBED,
MAX,
MIN,
IGNORE_ASPECT
};
// How many tasks are in the queue?
extern volatile int counterQueue;
@@ -123,14 +170,22 @@ namespace sharp {
bool IsJpeg(std::string const &str);
bool IsPng(std::string const &str);
bool IsWebp(std::string const &str);
bool IsJp2(std::string const &str);
bool IsGif(std::string const &str);
bool IsTiff(std::string const &str);
bool IsHeic(std::string const &str);
bool IsHeif(std::string const &str);
bool IsAvif(std::string const &str);
bool IsJxl(std::string const &str);
bool IsDz(std::string const &str);
bool IsDzZip(std::string const &str);
bool IsV(std::string const &str);
/*
Trim space from end of string.
*/
std::string TrimEnd(std::string const &str);
/*
Provide a string identifier for the given image type.
*/
@@ -151,10 +206,15 @@ namespace sharp {
*/
bool ImageTypeSupportsPage(ImageType imageType);
/*
Does this image type support removal of safety limits?
*/
bool ImageTypeSupportsUnlimited(ImageType imageType);
/*
Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data)
*/
std::tuple<VImage, ImageType> OpenInput(InputDescriptor *descriptor, VipsAccess accessMethod);
std::tuple<VImage, ImageType> OpenInput(InputDescriptor *descriptor);
/*
Does this image have an embedded profile?
@@ -175,12 +235,27 @@ namespace sharp {
/*
Set EXIF Orientation of image.
*/
void SetExifOrientation(VImage image, int const orientation);
VImage SetExifOrientation(VImage image, int const orientation);
/*
Remove EXIF Orientation from image.
*/
void RemoveExifOrientation(VImage image);
VImage RemoveExifOrientation(VImage image);
/*
Set animation properties if necessary.
*/
VImage SetAnimationProperties(VImage image, int nPages, int pageHeight, std::vector<int> delay, int loop);
/*
Remove animation properties from image.
*/
VImage RemoveAnimationProperties(VImage image);
/*
Remove GIF palette from image.
*/
VImage RemoveGifPalette(VImage image);
/*
Does this image have a non-default density?
@@ -195,7 +270,13 @@ namespace sharp {
/*
Set pixels/mm resolution based on a pixels/inch density.
*/
void SetDensity(VImage image, const double density);
VImage SetDensity(VImage image, const double density);
/*
Multi-page images can have a page height. Fetch it, and sanity check it.
If page-height is not set, it defaults to the image height
*/
int GetPageHeight(VImage image);
/*
Check the proposed format supports the current dimensions.
@@ -205,7 +286,7 @@ namespace sharp {
/*
Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows
*/
void FreeCallback(char* data, void* hint);
extern std::function<void(void*, char*)> FreeCallback;
/*
Called with warnings from the glib-registered "VIPS" domain
@@ -217,6 +298,16 @@ namespace sharp {
*/
std::string VipsWarningPop();
/*
Attach an event listener for progress updates, used to detect timeout
*/
void SetTimeout(VImage image, int const timeoutSeconds);
/*
Event listener for progress updates, used to detect timeout
*/
void VipsProgressCallBack(VipsImage *image, VipsProgress *progress, int *timeoutSeconds);
/*
Calculate the (left, top) coordinates of the output image
within the input image, applying the given gravity during an embed.
@@ -249,25 +340,37 @@ namespace sharp {
*/
double MaximumImageAlpha(VipsInterpretation const interpretation);
/*
Get boolean operation type from string
*/
VipsOperationBoolean GetBooleanOperation(std::string const opStr);
/*
Get interpretation type from string
*/
VipsInterpretation GetInterpretation(std::string const typeStr);
/*
Convert RGBA value to another colourspace
*/
std::vector<double> GetRgbaAsColourspace(std::vector<double> const rgba, VipsInterpretation const interpretation);
std::vector<double> GetRgbaAsColourspace(std::vector<double> const rgba,
VipsInterpretation const interpretation, bool premultiply);
/*
Apply the alpha channel to a given colour
*/
std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour);
std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour, bool premultiply);
/*
Removes alpha channel, if any.
*/
VImage RemoveAlpha(VImage image);
/*
Ensures alpha channel, if missing.
*/
VImage EnsureAlpha(VImage image, double const value);
/*
Calculate the horizontal and vertical shrink factors, taking the canvas mode into account.
*/
std::pair<double, double> ResolveShrink(int width, int height, int targetWidth, int targetHeight,
Canvas canvas, bool withoutEnlargement, bool withoutReduction);
/*
Ensure decoding remains sequential.
*/
VImage StaySequential(VImage image, VipsAccess access, bool condition = TRUE);
} // namespace sharp

View File

@@ -0,0 +1,151 @@
/* Object part of the VSource and VTarget class
*/
/*
Copyright (C) 1991-2001 The National Gallery
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
*/
/*
These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/vips8>
#include <vips/debug.h>
/*
#define VIPS_DEBUG
#define VIPS_DEBUG_VERBOSE
*/
VIPS_NAMESPACE_START
VSource
VSource::new_from_descriptor( int descriptor )
{
VipsSource *input;
if( !(input = vips_source_new_from_descriptor( descriptor )) )
throw VError();
VSource out( input );
return( out );
}
VSource
VSource::new_from_file( const char *filename )
{
VipsSource *input;
if( !(input = vips_source_new_from_file( filename )) )
throw VError();
VSource out( input );
return( out );
}
VSource
VSource::new_from_blob( VipsBlob *blob )
{
VipsSource *input;
if( !(input = vips_source_new_from_blob( blob )) )
throw VError();
VSource out( input );
return( out );
}
VSource
VSource::new_from_memory( const void *data,
size_t size )
{
VipsSource *input;
if( !(input = vips_source_new_from_memory( data, size )) )
throw VError();
VSource out( input );
return( out );
}
VSource
VSource::new_from_options( const char *options )
{
VipsSource *input;
if( !(input = vips_source_new_from_options( options )) )
throw VError();
VSource out( input );
return( out );
}
VTarget
VTarget::new_to_descriptor( int descriptor )
{
VipsTarget *output;
if( !(output = vips_target_new_to_descriptor( descriptor )) )
throw VError();
VTarget out( output );
return( out );
}
VTarget
VTarget::new_to_file( const char *filename )
{
VipsTarget *output;
if( !(output = vips_target_new_to_file( filename )) )
throw VError();
VTarget out( output );
return( out );
}
VTarget
VTarget::new_to_memory()
{
VipsTarget *output;
if( !(output = vips_target_new_to_memory()) )
throw VError();
VTarget out( output );
return( out );
}
VIPS_NAMESPACE_END

View File

@@ -30,7 +30,6 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/intl.h>
#include <vips/vips8>

File diff suppressed because it is too large Load Diff

View File

@@ -31,7 +31,6 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/intl.h>
#include <vips/vips8>
@@ -60,17 +59,4 @@ VInterpolate::new_from_name( const char *name, VOption *options )
return( out );
}
VOption *
VOption::set( const char *name, VInterpolate value )
{
Pair *pair = new Pair( name );
pair->input = true;
g_value_init( &pair->value, VIPS_TYPE_INTERPOLATE );
g_value_set_object( &pair->value, value.get_interpolate() );
options.push_back( pair );
return( this );
}
VIPS_NAMESPACE_END

View File

@@ -0,0 +1,27 @@
// Object part of VRegion class
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/vips8>
#include <vips/debug.h>
VIPS_NAMESPACE_START
VRegion
VRegion::new_from_image( VImage image )
{
VipsRegion *region;
if( !(region = vips_region_new( image.get_image() )) ) {
throw VError();
}
VRegion out( region );
return( out );
}
VIPS_NAMESPACE_END

View File

@@ -1,5 +1,4 @@
// bodies for vips operations
// Wed Apr 24 15:50:21 CEST 2019
// this file is generated automatically, do not edit!
VImage VImage::CMC2LCh( VOption *options ) const
@@ -491,6 +490,19 @@ VImage VImage::canny( VOption *options ) const
return( out );
}
VImage VImage::case_image( std::vector<VImage> cases, VOption *options ) const
{
VImage out;
call( "case",
(options ? options : VImage::option())->
set( "index", *this )->
set( "out", &out )->
set( "cases", cases ) );
return( out );
}
VImage VImage::cast( VipsBandFormat format, VOption *options ) const
{
VImage out;
@@ -741,6 +753,18 @@ VImage VImage::csvload( const char *filename, VOption *options )
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
{
call( "csvsave",
@@ -749,6 +773,14 @@ void VImage::csvsave( const char *filename, VOption *options ) const
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 out;
@@ -910,6 +942,14 @@ VipsBlob *VImage::dzsave_buffer( VOption *options ) const
return( buffer );
}
void VImage::dzsave_target( VTarget target, VOption *options ) const
{
call( "dzsave_target",
(options ? options : VImage::option())->
set( "in", *this )->
set( "target", target ) );
}
VImage VImage::embed( int x, int y, int width, int height, VOption *options ) const
{
VImage out;
@@ -1032,6 +1072,18 @@ VImage VImage::fitsload( const char *filename, VOption *options )
return( out );
}
VImage VImage::fitsload_source( VSource source, VOption *options )
{
VImage out;
call( "fitsload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
void VImage::fitssave( const char *filename, VOption *options ) const
{
call( "fitssave",
@@ -1205,6 +1257,46 @@ VImage VImage::gifload_buffer( VipsBlob *buffer, VOption *options )
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 );
}
void VImage::gifsave( const char *filename, VOption *options ) const
{
call( "gifsave",
(options ? options : VImage::option())->
set( "in", *this )->
set( "filename", filename ) );
}
VipsBlob *VImage::gifsave_buffer( VOption *options ) const
{
VipsBlob *buffer;
call( "gifsave_buffer",
(options ? options : VImage::option())->
set( "in", *this )->
set( "buffer", &buffer ) );
return( buffer );
}
void VImage::gifsave_target( VTarget target, VOption *options ) const
{
call( "gifsave_target",
(options ? options : VImage::option())->
set( "in", *this )->
set( "target", target ) );
}
VImage VImage::globalbalance( VOption *options ) const
{
VImage out;
@@ -1284,6 +1376,18 @@ VImage VImage::heifload_buffer( VipsBlob *buffer, VOption *options )
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
{
call( "heifsave",
@@ -1304,6 +1408,14 @@ VipsBlob *VImage::heifsave_buffer( VOption *options ) const
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 out;
@@ -1591,6 +1703,70 @@ VImage VImage::join( VImage in2, VipsDirection direction, VOption *options ) con
return( out );
}
VImage VImage::jp2kload( const char *filename, VOption *options )
{
VImage out;
call( "jp2kload",
(options ? options : VImage::option())->
set( "out", &out )->
set( "filename", filename ) );
return( out );
}
VImage VImage::jp2kload_buffer( VipsBlob *buffer, VOption *options )
{
VImage out;
call( "jp2kload_buffer",
(options ? options : VImage::option())->
set( "out", &out )->
set( "buffer", buffer ) );
return( out );
}
VImage VImage::jp2kload_source( VSource source, VOption *options )
{
VImage out;
call( "jp2kload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
void VImage::jp2ksave( const char *filename, VOption *options ) const
{
call( "jp2ksave",
(options ? options : VImage::option())->
set( "in", *this )->
set( "filename", filename ) );
}
VipsBlob *VImage::jp2ksave_buffer( VOption *options ) const
{
VipsBlob *buffer;
call( "jp2ksave_buffer",
(options ? options : VImage::option())->
set( "in", *this )->
set( "buffer", &buffer ) );
return( buffer );
}
void VImage::jp2ksave_target( VTarget target, VOption *options ) const
{
call( "jp2ksave_target",
(options ? options : VImage::option())->
set( "in", *this )->
set( "target", target ) );
}
VImage VImage::jpegload( const char *filename, VOption *options )
{
VImage out;
@@ -1615,6 +1791,18 @@ VImage VImage::jpegload_buffer( VipsBlob *buffer, VOption *options )
return( out );
}
VImage VImage::jpegload_source( VSource source, VOption *options )
{
VImage out;
call( "jpegload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
void VImage::jpegsave( const char *filename, VOption *options ) const
{
call( "jpegsave",
@@ -1642,6 +1830,78 @@ void VImage::jpegsave_mime( VOption *options ) const
set( "in", *this ) );
}
void VImage::jpegsave_target( VTarget target, VOption *options ) const
{
call( "jpegsave_target",
(options ? options : VImage::option())->
set( "in", *this )->
set( "target", target ) );
}
VImage VImage::jxlload( const char *filename, VOption *options )
{
VImage out;
call( "jxlload",
(options ? options : VImage::option())->
set( "out", &out )->
set( "filename", filename ) );
return( out );
}
VImage VImage::jxlload_buffer( VipsBlob *buffer, VOption *options )
{
VImage out;
call( "jxlload_buffer",
(options ? options : VImage::option())->
set( "out", &out )->
set( "buffer", buffer ) );
return( out );
}
VImage VImage::jxlload_source( VSource source, VOption *options )
{
VImage out;
call( "jxlload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
void VImage::jxlsave( const char *filename, VOption *options ) const
{
call( "jxlsave",
(options ? options : VImage::option())->
set( "in", *this )->
set( "filename", filename ) );
}
VipsBlob *VImage::jxlsave_buffer( VOption *options ) const
{
VipsBlob *buffer;
call( "jxlsave_buffer",
(options ? options : VImage::option())->
set( "in", *this )->
set( "buffer", &buffer ) );
return( buffer );
}
void VImage::jxlsave_target( VTarget target, VOption *options ) const
{
call( "jxlsave_target",
(options ? options : VImage::option())->
set( "in", *this )->
set( "target", target ) );
}
VImage VImage::labelregions( VOption *options ) const
{
VImage mask;
@@ -1995,6 +2255,18 @@ VImage VImage::matload( const char *filename, VOption *options )
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 out;
@@ -2007,6 +2279,18 @@ VImage VImage::matrixload( const char *filename, VOption *options )
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
{
call( "matrixprint",
@@ -2022,6 +2306,14 @@ void VImage::matrixsave( const char *filename, VOption *options ) const
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 out;
@@ -2167,6 +2459,18 @@ VImage VImage::niftiload( const char *filename, VOption *options )
return( out );
}
VImage VImage::niftiload_source( VSource source, VOption *options )
{
VImage out;
call( "niftiload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
void VImage::niftisave( const char *filename, VOption *options ) const
{
call( "niftisave",
@@ -2199,6 +2503,18 @@ VImage VImage::openslideload( const char *filename, VOption *options )
return( out );
}
VImage VImage::openslideload_source( VSource source, VOption *options )
{
VImage out;
call( "openslideload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
VImage VImage::pdfload( const char *filename, VOption *options )
{
VImage out;
@@ -2223,6 +2539,18 @@ VImage VImage::pdfload_buffer( VipsBlob *buffer, VOption *options )
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 threshold;
@@ -2286,6 +2614,18 @@ VImage VImage::pngload_buffer( VipsBlob *buffer, VOption *options )
return( out );
}
VImage VImage::pngload_source( VSource source, VOption *options )
{
VImage out;
call( "pngload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
void VImage::pngsave( const char *filename, VOption *options ) const
{
call( "pngsave",
@@ -2306,6 +2646,14 @@ VipsBlob *VImage::pngsave_buffer( VOption *options ) const
return( buffer );
}
void VImage::pngsave_target( VTarget target, VOption *options ) const
{
call( "pngsave_target",
(options ? options : VImage::option())->
set( "in", *this )->
set( "target", target ) );
}
VImage VImage::ppmload( const char *filename, VOption *options )
{
VImage out;
@@ -2318,6 +2666,18 @@ VImage VImage::ppmload( const char *filename, VOption *options )
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
{
call( "ppmsave",
@@ -2326,6 +2686,14 @@ void VImage::ppmsave( const char *filename, VOption *options ) const
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 out;
@@ -2413,6 +2781,30 @@ VImage VImage::radload( const char *filename, VOption *options )
return( out );
}
VImage VImage::radload_buffer( VipsBlob *buffer, VOption *options )
{
VImage out;
call( "radload_buffer",
(options ? options : VImage::option())->
set( "out", &out )->
set( "buffer", buffer ) );
return( out );
}
VImage VImage::radload_source( VSource source, VOption *options )
{
VImage out;
call( "radload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
void VImage::radsave( const char *filename, VOption *options ) const
{
call( "radsave",
@@ -2433,6 +2825,14 @@ VipsBlob *VImage::radsave_buffer( VOption *options ) const
return( buffer );
}
void VImage::radsave_target( VTarget target, VOption *options ) const
{
call( "radsave_target",
(options ? options : VImage::option())->
set( "in", *this )->
set( "target", target ) );
}
VImage VImage::rank( int width, int height, int index, VOption *options ) const
{
VImage out;
@@ -2977,6 +3377,30 @@ VImage VImage::svgload_buffer( VipsBlob *buffer, VOption *options )
return( out );
}
VImage VImage::svgload_source( VSource source, VOption *options )
{
VImage out;
call( "svgload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
VImage VImage::switch_image( std::vector<VImage> tests, VOption *options )
{
VImage out;
call( "switch",
(options ? options : VImage::option())->
set( "out", &out )->
set( "tests", tests ) );
return( out );
}
void VImage::system( const char *cmd_format, VOption *options )
{
call( "system",
@@ -3035,6 +3459,19 @@ VImage VImage::thumbnail_image( int width, VOption *options ) const
return( out );
}
VImage VImage::thumbnail_source( VSource source, int width, VOption *options )
{
VImage out;
call( "thumbnail_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source )->
set( "width", width ) );
return( out );
}
VImage VImage::tiffload( const char *filename, VOption *options )
{
VImage out;
@@ -3059,6 +3496,18 @@ VImage VImage::tiffload_buffer( VipsBlob *buffer, VOption *options )
return( out );
}
VImage VImage::tiffload_source( VSource source, VOption *options )
{
VImage out;
call( "tiffload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
void VImage::tiffsave( const char *filename, VOption *options ) const
{
call( "tiffsave",
@@ -3079,6 +3528,14 @@ VipsBlob *VImage::tiffsave_buffer( VOption *options ) const
return( buffer );
}
void VImage::tiffsave_target( VTarget target, VOption *options ) const
{
call( "tiffsave_target",
(options ? options : VImage::option())->
set( "in", *this )->
set( "target", target ) );
}
VImage VImage::tilecache( VOption *options ) const
{
VImage out;
@@ -3138,6 +3595,18 @@ VImage VImage::vipsload( const char *filename, VOption *options )
return( out );
}
VImage VImage::vipsload_source( VSource source, VOption *options )
{
VImage out;
call( "vipsload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
void VImage::vipssave( const char *filename, VOption *options ) const
{
call( "vipssave",
@@ -3146,6 +3615,14 @@ void VImage::vipssave( const char *filename, VOption *options ) const
set( "filename", filename ) );
}
void VImage::vipssave_target( VTarget target, VOption *options ) const
{
call( "vipssave_target",
(options ? options : VImage::option())->
set( "in", *this )->
set( "target", target ) );
}
VImage VImage::webpload( const char *filename, VOption *options )
{
VImage out;
@@ -3170,6 +3647,18 @@ VImage VImage::webpload_buffer( VipsBlob *buffer, VOption *options )
return( out );
}
VImage VImage::webpload_source( VSource source, VOption *options )
{
VImage out;
call( "webpload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
void VImage::webpsave( const char *filename, VOption *options ) const
{
call( "webpsave",
@@ -3190,6 +3679,21 @@ VipsBlob *VImage::webpsave_buffer( VOption *options ) const
return( buffer );
}
void VImage::webpsave_mime( VOption *options ) const
{
call( "webpsave_mime",
(options ? options : VImage::option())->
set( "in", *this ) );
}
void VImage::webpsave_target( VTarget target, VOption *options ) const
{
call( "webpsave_target",
(options ? options : VImage::option())->
set( "in", *this )->
set( "target", target ) );
}
VImage VImage::worley( int width, int height, VOption *options )
{
VImage out;

View File

@@ -1,42 +1,19 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
#include <numeric>
#include <vector>
#include <node.h>
#include <nan.h>
#include <napi.h>
#include <vips/vips8>
#include "common.h"
#include "metadata.h"
class MetadataWorker : public Nan::AsyncWorker {
class MetadataWorker : public Napi::AsyncWorker {
public:
MetadataWorker(
Nan::Callback *callback, MetadataBaton *baton, Nan::Callback *debuglog,
std::vector<v8::Local<v8::Object>> const buffersToPersist) :
Nan::AsyncWorker(callback, "sharp:MetadataWorker"),
baton(baton), debuglog(debuglog),
buffersToPersist(buffersToPersist) {
// Protect Buffer objects from GC, keyed on index
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
SaveToPersistent(index, buffer);
return index + 1;
});
}
MetadataWorker(Napi::Function callback, MetadataBaton *baton, Napi::Function debuglog) :
Napi::AsyncWorker(callback), baton(baton), debuglog(Napi::Persistent(debuglog)) {}
~MetadataWorker() {}
void Execute() {
@@ -46,7 +23,7 @@ class MetadataWorker : public Nan::AsyncWorker {
vips::VImage image;
sharp::ImageType imageType = sharp::ImageType::UNKNOWN;
try {
std::tie(image, imageType) = OpenInput(baton->input, VIPS_ACCESS_SEQUENTIAL);
std::tie(image, imageType) = OpenInput(baton->input);
} catch (vips::VError const &err) {
(baton->err).append(err.what());
}
@@ -77,10 +54,40 @@ class MetadataWorker : public Nan::AsyncWorker {
if (image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT) {
baton->pageHeight = image.get_int(VIPS_META_PAGE_HEIGHT);
}
if (image.get_typeof("loop") == G_TYPE_INT) {
baton->loop = image.get_int("loop");
}
if (image.get_typeof("delay") == VIPS_TYPE_ARRAY_INT) {
baton->delay = image.get_array_int("delay");
}
if (image.get_typeof("heif-primary") == G_TYPE_INT) {
baton->pagePrimary = image.get_int("heif-primary");
}
if (image.get_typeof("heif-compression") == VIPS_TYPE_REF_STRING) {
baton->compression = image.get_string("heif-compression");
}
if (image.get_typeof(VIPS_META_RESOLUTION_UNIT) == VIPS_TYPE_REF_STRING) {
baton->resolutionUnit = image.get_string(VIPS_META_RESOLUTION_UNIT);
}
if (image.get_typeof("magick-format") == VIPS_TYPE_REF_STRING) {
baton->formatMagick = image.get_string("magick-format");
}
if (image.get_typeof("openslide.level-count") == VIPS_TYPE_REF_STRING) {
int const levels = std::stoi(image.get_string("openslide.level-count"));
for (int l = 0; l < levels; l++) {
std::string prefix = "openslide.level[" + std::to_string(l) + "].";
int const width = std::stoi(image.get_string((prefix + "width").data()));
int const height = std::stoi(image.get_string((prefix + "height").data()));
baton->levels.push_back(std::pair<int, int>(width, height));
}
}
if (image.get_typeof(VIPS_META_N_SUBIFDS) == G_TYPE_INT) {
baton->subifds = image.get_int(VIPS_META_N_SUBIFDS);
}
baton->hasProfile = sharp::HasProfile(image);
if (image.get_typeof("background") == VIPS_TYPE_ARRAY_DOUBLE) {
baton->background = image.get_array_double("background");
}
// Derived attributes
baton->hasAlpha = sharp::HasAlpha(image);
baton->orientation = sharp::ExifOrientation(image);
@@ -116,6 +123,14 @@ class MetadataWorker : public Nan::AsyncWorker {
memcpy(baton->xmp, xmp, xmpLength);
baton->xmpLength = xmpLength;
}
// TIFFTAG_PHOTOSHOP
if (image.get_typeof(VIPS_META_PHOTOSHOP_NAME) == VIPS_TYPE_BLOB) {
size_t tifftagPhotoshopLength;
void const *tifftagPhotoshop = image.get_blob(VIPS_META_PHOTOSHOP_NAME, &tifftagPhotoshopLength);
baton->tifftagPhotoshop = static_cast<char *>(g_malloc(tifftagPhotoshopLength));
memcpy(baton->tifftagPhotoshop, tifftagPhotoshop, tifftagPhotoshopLength);
baton->tifftagPhotoshopLength = tifftagPhotoshopLength;
}
}
// Clean up
@@ -123,123 +138,150 @@ class MetadataWorker : public Nan::AsyncWorker {
vips_thread_shutdown();
}
void HandleOKCallback() {
using Nan::New;
using Nan::Set;
Nan::HandleScope();
v8::Local<v8::Value> argv[2] = { Nan::Null(), Nan::Null() };
if (!baton->err.empty()) {
argv[0] = Nan::Error(baton->err.data());
} else {
// Metadata Object
v8::Local<v8::Object> info = New<v8::Object>();
Set(info, New("format").ToLocalChecked(), New<v8::String>(baton->format).ToLocalChecked());
if (baton->input->bufferLength > 0) {
Set(info, New("size").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(baton->input->bufferLength)));
}
Set(info, New("width").ToLocalChecked(), New<v8::Uint32>(baton->width));
Set(info, New("height").ToLocalChecked(), New<v8::Uint32>(baton->height));
Set(info, New("space").ToLocalChecked(), New<v8::String>(baton->space).ToLocalChecked());
Set(info, New("channels").ToLocalChecked(), New<v8::Uint32>(baton->channels));
Set(info, New("depth").ToLocalChecked(), New<v8::String>(baton->depth).ToLocalChecked());
if (baton->density > 0) {
Set(info, New("density").ToLocalChecked(), New<v8::Uint32>(baton->density));
}
if (!baton->chromaSubsampling.empty()) {
Set(info,
New("chromaSubsampling").ToLocalChecked(),
New<v8::String>(baton->chromaSubsampling).ToLocalChecked());
}
Set(info, New("isProgressive").ToLocalChecked(), New<v8::Boolean>(baton->isProgressive));
if (baton->paletteBitDepth > 0) {
Set(info, New("paletteBitDepth").ToLocalChecked(), New<v8::Uint32>(baton->paletteBitDepth));
}
if (baton->pages > 0) {
Set(info, New("pages").ToLocalChecked(), New<v8::Uint32>(baton->pages));
}
if (baton->pageHeight > 0) {
Set(info, New("pageHeight").ToLocalChecked(), New<v8::Uint32>(baton->pageHeight));
}
if (baton->pagePrimary > -1) {
Set(info, New("pagePrimary").ToLocalChecked(), New<v8::Uint32>(baton->pagePrimary));
}
Set(info, New("hasProfile").ToLocalChecked(), New<v8::Boolean>(baton->hasProfile));
Set(info, New("hasAlpha").ToLocalChecked(), New<v8::Boolean>(baton->hasAlpha));
if (baton->orientation > 0) {
Set(info, New("orientation").ToLocalChecked(), New<v8::Uint32>(baton->orientation));
}
if (baton->exifLength > 0) {
Set(info,
New("exif").ToLocalChecked(),
Nan::NewBuffer(baton->exif, baton->exifLength, sharp::FreeCallback, nullptr).ToLocalChecked());
}
if (baton->iccLength > 0) {
Set(info,
New("icc").ToLocalChecked(),
Nan::NewBuffer(baton->icc, baton->iccLength, sharp::FreeCallback, nullptr).ToLocalChecked());
}
if (baton->iptcLength > 0) {
Set(info,
New("iptc").ToLocalChecked(),
Nan::NewBuffer(baton->iptc, baton->iptcLength, sharp::FreeCallback, nullptr).ToLocalChecked());
}
if (baton->xmpLength > 0) {
Set(info,
New("xmp").ToLocalChecked(),
Nan::NewBuffer(baton->xmp, baton->xmpLength, sharp::FreeCallback, nullptr).ToLocalChecked());
}
argv[1] = info;
}
// Dispose of Persistent wrapper around input Buffers so they can be garbage collected
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
GetFromPersistent(index);
return index + 1;
});
delete baton->input;
delete baton;
void OnOK() {
Napi::Env env = Env();
Napi::HandleScope scope(env);
// Handle warnings
std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) {
v8::Local<v8::Value> message[1] = { New(warning).ToLocalChecked() };
debuglog->Call(1, message, async_resource);
debuglog.MakeCallback(Receiver().Value(), { Napi::String::New(env, warning) });
warning = sharp::VipsWarningPop();
}
// Return to JavaScript
callback->Call(2, argv, async_resource);
if (baton->err.empty()) {
Napi::Object info = Napi::Object::New(env);
info.Set("format", baton->format);
if (baton->input->bufferLength > 0) {
info.Set("size", baton->input->bufferLength);
}
info.Set("width", baton->width);
info.Set("height", baton->height);
info.Set("space", baton->space);
info.Set("channels", baton->channels);
info.Set("depth", baton->depth);
if (baton->density > 0) {
info.Set("density", baton->density);
}
if (!baton->chromaSubsampling.empty()) {
info.Set("chromaSubsampling", baton->chromaSubsampling);
}
info.Set("isProgressive", baton->isProgressive);
if (baton->paletteBitDepth > 0) {
info.Set("paletteBitDepth", baton->paletteBitDepth);
}
if (baton->pages > 0) {
info.Set("pages", baton->pages);
}
if (baton->pageHeight > 0) {
info.Set("pageHeight", baton->pageHeight);
}
if (baton->loop >= 0) {
info.Set("loop", baton->loop);
}
if (!baton->delay.empty()) {
int i = 0;
Napi::Array delay = Napi::Array::New(env, static_cast<size_t>(baton->delay.size()));
for (int const d : baton->delay) {
delay.Set(i++, d);
}
info.Set("delay", delay);
}
if (baton->pagePrimary > -1) {
info.Set("pagePrimary", baton->pagePrimary);
}
if (!baton->compression.empty()) {
info.Set("compression", baton->compression);
}
if (!baton->resolutionUnit.empty()) {
info.Set("resolutionUnit", baton->resolutionUnit == "in" ? "inch" : baton->resolutionUnit);
}
if (!baton->formatMagick.empty()) {
info.Set("formatMagick", baton->formatMagick);
}
if (!baton->levels.empty()) {
int i = 0;
Napi::Array levels = Napi::Array::New(env, static_cast<size_t>(baton->levels.size()));
for (std::pair<int, int> const &l : baton->levels) {
Napi::Object level = Napi::Object::New(env);
level.Set("width", l.first);
level.Set("height", l.second);
levels.Set(i++, level);
}
info.Set("levels", levels);
}
if (baton->subifds > 0) {
info.Set("subifds", baton->subifds);
}
if (!baton->background.empty()) {
if (baton->background.size() == 3) {
Napi::Object background = Napi::Object::New(env);
background.Set("r", baton->background[0]);
background.Set("g", baton->background[1]);
background.Set("b", baton->background[2]);
info.Set("background", background);
} else {
info.Set("background", baton->background[0]);
}
}
info.Set("hasProfile", baton->hasProfile);
info.Set("hasAlpha", baton->hasAlpha);
if (baton->orientation > 0) {
info.Set("orientation", baton->orientation);
}
if (baton->exifLength > 0) {
info.Set("exif", Napi::Buffer<char>::NewOrCopy(env, baton->exif, baton->exifLength, sharp::FreeCallback));
}
if (baton->iccLength > 0) {
info.Set("icc", Napi::Buffer<char>::NewOrCopy(env, baton->icc, baton->iccLength, sharp::FreeCallback));
}
if (baton->iptcLength > 0) {
info.Set("iptc", Napi::Buffer<char>::NewOrCopy(env, baton->iptc, baton->iptcLength, sharp::FreeCallback));
}
if (baton->xmpLength > 0) {
info.Set("xmp", Napi::Buffer<char>::NewOrCopy(env, baton->xmp, baton->xmpLength, sharp::FreeCallback));
}
if (baton->tifftagPhotoshopLength > 0) {
info.Set("tifftagPhotoshop",
Napi::Buffer<char>::NewOrCopy(env, baton->tifftagPhotoshop,
baton->tifftagPhotoshopLength, sharp::FreeCallback));
}
Callback().MakeCallback(Receiver().Value(), { env.Null(), info });
} else {
Callback().MakeCallback(Receiver().Value(), { Napi::Error::New(env, sharp::TrimEnd(baton->err)).Value() });
}
delete baton->input;
delete baton;
}
private:
MetadataBaton* baton;
Nan::Callback *debuglog;
std::vector<v8::Local<v8::Object>> buffersToPersist;
Napi::FunctionReference debuglog;
};
/*
metadata(options, callback)
*/
NAN_METHOD(metadata) {
// Input Buffers must not undergo GC compaction during processing
std::vector<v8::Local<v8::Object>> buffersToPersist;
Napi::Value metadata(const Napi::CallbackInfo& info) {
// V8 objects are converted to non-V8 types held in the baton struct
MetadataBaton *baton = new MetadataBaton;
v8::Local<v8::Object> options = info[0].As<v8::Object>();
Napi::Object options = info[size_t(0)].As<Napi::Object>();
// Input
baton->input = sharp::CreateInputDescriptor(sharp::AttrAs<v8::Object>(options, "input"), buffersToPersist);
baton->input = sharp::CreateInputDescriptor(options.Get("input").As<Napi::Object>());
// Function to notify of libvips warnings
Nan::Callback *debuglog = new Nan::Callback(sharp::AttrAs<v8::Function>(options, "debuglog"));
Napi::Function debuglog = options.Get("debuglog").As<Napi::Function>();
// Join queue for worker thread
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>());
Nan::AsyncQueueWorker(new MetadataWorker(callback, baton, debuglog, buffersToPersist));
Napi::Function callback = info[size_t(1)].As<Napi::Function>();
MetadataWorker *worker = new MetadataWorker(callback, baton, debuglog);
worker->Receiver().Set("options", options);
worker->Queue();
// Increment queued task counter
g_atomic_int_inc(&sharp::counterQueue);
return info.Env().Undefined();
}

View File

@@ -1,22 +1,11 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
#ifndef SRC_METADATA_H_
#define SRC_METADATA_H_
#include <string>
#include <nan.h>
#include <napi.h>
#include "./common.h"
@@ -36,7 +25,15 @@ struct MetadataBaton {
int paletteBitDepth;
int pages;
int pageHeight;
int loop;
std::vector<int> delay;
int pagePrimary;
std::string compression;
std::string resolutionUnit;
std::string formatMagick;
std::vector<std::pair<int, int>> levels;
int subifds;
std::vector<double> background;
bool hasProfile;
bool hasAlpha;
int orientation;
@@ -48,6 +45,8 @@ struct MetadataBaton {
size_t iptcLength;
char *xmp;
size_t xmpLength;
char *tifftagPhotoshop;
size_t tifftagPhotoshopLength;
std::string err;
MetadataBaton():
@@ -60,7 +59,9 @@ struct MetadataBaton {
paletteBitDepth(0),
pages(0),
pageHeight(0),
loop(-1),
pagePrimary(-1),
subifds(0),
hasProfile(false),
hasAlpha(false),
orientation(0),
@@ -71,9 +72,11 @@ struct MetadataBaton {
iptc(nullptr),
iptcLength(0),
xmp(nullptr),
xmpLength(0) {}
xmpLength(0),
tifftagPhotoshop(nullptr),
tifftagPhotoshopLength(0) {}
};
NAN_METHOD(metadata);
Napi::Value metadata(const Napi::CallbackInfo& info);
#endif // SRC_METADATA_H_

View File

@@ -1,23 +1,11 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
#include <algorithm>
#include <functional>
#include <memory>
#include <tuple>
#include <vector>
#include <vips/vips8>
#include "common.h"
@@ -27,29 +15,6 @@ using vips::VImage;
using vips::VError;
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
*/
@@ -80,7 +45,7 @@ namespace sharp {
/*
* Stretch luminance to cover full dynamic range.
*/
VImage Normalise(VImage image) {
VImage Normalise(VImage image, int const lower, int const upper) {
// Get original colourspace
VipsInterpretation typeBeforeNormalize = image.interpretation();
if (typeBeforeNormalize == VIPS_INTERPRETATION_RGB) {
@@ -90,11 +55,12 @@ namespace sharp {
VImage lab = image.colourspace(VIPS_INTERPRETATION_LAB);
// Extract luminance
VImage luminance = lab[0];
// Find luminance range
VImage stats = luminance.stats();
double min = stats(0, 0)[0];
double max = stats(1, 0)[0];
if (min != max) {
int const min = lower == 0 ? luminance.min() : luminance.percent(lower);
int const max = upper == 100 ? luminance.max() : luminance.percent(upper);
if (std::abs(max - min) > 1) {
// Extract chroma
VImage chroma = lab.extract_band(1, VImage::option()->set("n", 2));
// Calculate multiplication factor and addition
@@ -115,6 +81,13 @@ namespace sharp {
return image;
}
/*
* Contrast limiting adapative histogram equalization (CLAHE)
*/
VImage Clahe(VImage image, int const width, int const height, int const maxSlope) {
return image.hist_local(width, height, VImage::option()->set("max_slope", maxSlope));
}
/*
* Gamma encoding/decoding
*/
@@ -128,6 +101,32 @@ namespace sharp {
}
}
/*
* Flatten image to remove alpha channel
*/
VImage Flatten(VImage image, std::vector<double> flattenBackground) {
double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
std::vector<double> background {
flattenBackground[0] * multiplier,
flattenBackground[1] * multiplier,
flattenBackground[2] * multiplier
};
return image.flatten(VImage::option()->set("background", background));
}
/**
* Produce the "negative" of the image.
*/
VImage Negate(VImage image, bool const negateAlpha) {
if (HasAlpha(image) && !negateAlpha) {
// Separate alpha channel
VImage alpha = image[image.bands() - 1];
return RemoveAlpha(image).invert().bandjoin(alpha);
} else {
return image.invert();
}
}
/*
* Gaussian blur. Use sigma of -1.0 for fast blur.
*/
@@ -172,8 +171,8 @@ namespace sharp {
*/
VImage Recomb(VImage image, std::unique_ptr<double[]> const &matrix) {
double *m = matrix.get();
image = image.colourspace(VIPS_INTERPRETATION_sRGB);
return image
.colourspace(VIPS_INTERPRETATION_sRGB)
.recomb(image.bands() == 3
? VImage::new_from_memory(
m, 9 * sizeof(double), 3, 3, 1, VIPS_FORMAT_DOUBLE
@@ -185,25 +184,36 @@ namespace sharp {
0.0, 0.0, 0.0, 1.0));
}
VImage Modulate(VImage image, double const brightness, double const saturation, int const hue) {
VImage Modulate(VImage image, double const brightness, double const saturation,
int const hue, double const lightness) {
VipsInterpretation colourspaceBeforeModulate = image.interpretation();
if (HasAlpha(image)) {
// Separate alpha channel
VImage alpha = image[image.bands() - 1];
return RemoveAlpha(image)
.colourspace(VIPS_INTERPRETATION_LCH)
.linear({brightness, saturation, 1}, {0, 0, static_cast<double>(hue)})
.linear(
{ brightness, saturation, 1},
{ lightness, 0.0, static_cast<double>(hue) }
)
.colourspace(colourspaceBeforeModulate)
.bandjoin(alpha);
} else {
return image
.colourspace(VIPS_INTERPRETATION_LCH)
.linear({brightness, saturation, 1}, {0, 0, static_cast<double>(hue)});
.linear(
{ brightness, saturation, 1 },
{ lightness, 0.0, static_cast<double>(hue) }
)
.colourspace(colourspaceBeforeModulate);
}
}
/*
* Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
*/
VImage Sharpen(VImage image, double const sigma, double const flat, double const jagged) {
VImage Sharpen(VImage image, double const sigma, double const m1, double const m2,
double const x1, double const y2, double const y3) {
if (sigma == -1.0) {
// Fast, mild sharpen
VImage sharpen = VImage::new_matrixv(3, 3,
@@ -218,8 +228,14 @@ namespace sharp {
if (colourspaceBeforeSharpen == VIPS_INTERPRETATION_RGB) {
colourspaceBeforeSharpen = VIPS_INTERPRETATION_sRGB;
}
return image.sharpen(
VImage::option()->set("sigma", sigma)->set("m1", flat)->set("m2", jagged))
return image
.sharpen(VImage::option()
->set("sigma", sigma)
->set("m1", m1)
->set("m2", m2)
->set("x1", x1)
->set("y2", y2)
->set("y3", y3))
.colourspace(colourspaceBeforeSharpen);
}
}
@@ -249,35 +265,190 @@ namespace sharp {
/*
Trim an image
*/
VImage Trim(VImage image, double const threshold) {
VImage Trim(VImage image, std::vector<double> background, double threshold) {
if (image.width() < 3 && image.height() < 3) {
throw VError("Image to trim must be at least 3x3 pixels");
}
// Top-left pixel provides the background colour
VImage background = image.extract_area(0, 0, 1, 1);
if (HasAlpha(background)) {
background = background.flatten();
if (background.size() == 0) {
// Top-left pixel provides the default background colour if none is given
background = image.extract_area(0, 0, 1, 1)(0, 0);
} else if (sharp::Is16Bit(image.interpretation())) {
for (size_t i = 0; i < background.size(); i++) {
background[i] *= 256.0;
}
threshold *= 256.0;
}
int top, width, height;
int const left = image.find_trim(&top, &width, &height, VImage::option()
->set("background", background(0, 0))
std::vector<double> backgroundAlpha({ background.back() });
if (HasAlpha(image)) {
background.pop_back();
} else {
background.resize(image.bands());
}
int left, top, width, height;
left = image.find_trim(&top, &width, &height, VImage::option()
->set("background", background)
->set("threshold", threshold));
if (width == 0 || height == 0) {
throw VError("Unexpected error while trimming. Try to lower the tolerance");
if (HasAlpha(image)) {
// Search alpha channel (A)
int leftA, topA, widthA, heightA;
VImage alpha = image[image.bands() - 1];
leftA = alpha.find_trim(&topA, &widthA, &heightA, VImage::option()
->set("background", backgroundAlpha)
->set("threshold", threshold));
if (widthA > 0 && heightA > 0) {
if (width > 0 && height > 0) {
// Combined bounding box (B)
int const leftB = std::min(left, leftA);
int const topB = std::min(top, topA);
int const widthB = std::max(left + width, leftA + widthA) - leftB;
int const heightB = std::max(top + height, topA + heightA) - topB;
return image.extract_area(leftB, topB, widthB, heightB);
} else {
// Use alpha only
return image.extract_area(leftA, topA, widthA, heightA);
}
}
}
return image.extract_area(left, top, width, height);
if (width > 0 && height > 0) {
return image.extract_area(left, top, width, height);
}
return image;
}
/*
* Calculate (a * in + b)
*/
VImage Linear(VImage image, double const a, double const b) {
if (HasAlpha(image)) {
VImage Linear(VImage image, std::vector<double> const a, std::vector<double> const b) {
size_t const bands = static_cast<size_t>(image.bands());
if (a.size() > bands) {
throw VError("Band expansion using linear is unsupported");
}
bool const uchar = !Is16Bit(image.interpretation());
if (HasAlpha(image) && a.size() != bands && (a.size() == 1 || a.size() == bands - 1 || bands - 1 == 1)) {
// Separate alpha channel
VImage alpha = image[image.bands() - 1];
return RemoveAlpha(image).linear(a, b).bandjoin(alpha);
VImage alpha = image[bands - 1];
return RemoveAlpha(image).linear(a, b, VImage::option()->set("uchar", uchar)).bandjoin(alpha);
} else {
return image.linear(a, b);
return image.linear(a, b, VImage::option()->set("uchar", uchar));
}
}
/*
* Unflatten
*/
VImage Unflatten(VImage image) {
if (HasAlpha(image)) {
VImage alpha = image[image.bands() - 1];
VImage noAlpha = RemoveAlpha(image);
return noAlpha.bandjoin(alpha & (noAlpha.colourspace(VIPS_INTERPRETATION_B_W) < 255));
} else {
return image.bandjoin(image.colourspace(VIPS_INTERPRETATION_B_W) < 255);
}
}
/*
* Ensure the image is in a given colourspace
*/
VImage EnsureColourspace(VImage image, VipsInterpretation colourspace) {
if (colourspace != VIPS_INTERPRETATION_LAST && image.interpretation() != colourspace) {
image = image.colourspace(colourspace,
VImage::option()->set("source_space", image.interpretation()));
}
return image;
}
/*
* Split and crop each frame, reassemble, and update pageHeight.
*/
VImage CropMultiPage(VImage image, int left, int top, int width, int height,
int nPages, int *pageHeight) {
if (top == 0 && height == *pageHeight) {
// Fast path; no need to adjust the height of the multi-page image
return image.extract_area(left, 0, width, image.height());
} else {
std::vector<VImage> pages;
pages.reserve(nPages);
// Split the image into cropped frames
for (int i = 0; i < nPages; i++) {
pages.push_back(
image.extract_area(left, *pageHeight * i + top, width, height));
}
// Reassemble the frames into a tall, thin image
VImage assembled = VImage::arrayjoin(pages,
VImage::option()->set("across", 1));
// Update the page height
*pageHeight = height;
return assembled;
}
}
/*
* Split into frames, embed each frame, reassemble, and update pageHeight.
*/
VImage EmbedMultiPage(VImage image, int left, int top, int width, int height,
VipsExtend extendWith, std::vector<double> background, int nPages, int *pageHeight) {
if (top == 0 && height == *pageHeight) {
// Fast path; no need to adjust the height of the multi-page image
return image.embed(left, 0, width, image.height(), VImage::option()
->set("extend", extendWith)
->set("background", background));
} else if (left == 0 && width == image.width()) {
// Fast path; no need to adjust the width of the multi-page image
std::vector<VImage> pages;
pages.reserve(nPages);
// Rearrange the tall image into a vertical grid
image = image.grid(*pageHeight, nPages, 1);
// Do the embed on the wide image
image = image.embed(0, top, image.width(), height, VImage::option()
->set("extend", extendWith)
->set("background", background));
// Split the wide image into frames
for (int i = 0; i < nPages; i++) {
pages.push_back(
image.extract_area(width * i, 0, width, height));
}
// Reassemble the frames into a tall, thin image
VImage assembled = VImage::arrayjoin(pages,
VImage::option()->set("across", 1));
// Update the page height
*pageHeight = height;
return assembled;
} else {
std::vector<VImage> pages;
pages.reserve(nPages);
// Split the image into frames
for (int i = 0; i < nPages; i++) {
pages.push_back(
image.extract_area(0, *pageHeight * i, image.width(), *pageHeight));
}
// Embed each frame in the target size
for (int i = 0; i < nPages; i++) {
pages[i] = pages[i].embed(left, top, width, height, VImage::option()
->set("extend", extendWith)
->set("background", background));
}
// Reassemble the frames into a tall, thin image
VImage assembled = VImage::arrayjoin(pages,
VImage::option()->set("across", 1));
// Update the page height
*pageHeight = height;
return assembled;
}
}
} // namespace sharp

View File

@@ -1,16 +1,5 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
#ifndef SRC_OPERATIONS_H_
#define SRC_OPERATIONS_H_
@@ -25,16 +14,6 @@ using vips::VImage;
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
*/
@@ -43,13 +22,28 @@ namespace sharp {
/*
* Stretch luminance to cover full dynamic range.
*/
VImage Normalise(VImage image);
VImage Normalise(VImage image, int const lower, int const upper);
/*
* Contrast limiting adapative histogram equalization (CLAHE)
*/
VImage Clahe(VImage image, int const width, int const height, int const maxSlope);
/*
* Gamma encoding/decoding
*/
VImage Gamma(VImage image, double const exponent);
/*
* Flatten image to remove alpha channel
*/
VImage Flatten(VImage image, std::vector<double> flattenBackground);
/*
* Produce the "negative" of the image.
*/
VImage Negate(VImage image, bool const negateAlpha);
/*
* Gaussian blur. Use sigma of -1.0 for fast blur.
*/
@@ -64,7 +58,8 @@ namespace sharp {
/*
* Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
*/
VImage Sharpen(VImage image, double const sigma, double const flat, double const jagged);
VImage Sharpen(VImage image, double const sigma, double const m1, double const m2,
double const x1, double const y2, double const y3);
/*
Threshold an image
@@ -84,12 +79,17 @@ namespace sharp {
/*
Trim an image
*/
VImage Trim(VImage image, double const threshold);
VImage Trim(VImage image, std::vector<double> background, double const threshold);
/*
* Linear adjustment (a * in + b)
*/
VImage Linear(VImage image, double const a, double const b);
VImage Linear(VImage image, std::vector<double> const a, std::vector<double> const b);
/*
* Unflatten
*/
VImage Unflatten(VImage image);
/*
* Recomb with a Matrix of the given bands/channel size.
@@ -98,9 +98,27 @@ namespace sharp {
VImage Recomb(VImage image, std::unique_ptr<double[]> const &matrix);
/*
* Modulate brightness, saturation and hue
* Modulate brightness, saturation, hue and lightness
*/
VImage Modulate(VImage image, double const brightness, double const saturation, int const hue);
VImage Modulate(VImage image, double const brightness, double const saturation,
int const hue, double const lightness);
/*
* Ensure the image is in a given colourspace
*/
VImage EnsureColourspace(VImage image, VipsInterpretation colourspace);
/*
* Split and crop each frame, reassemble, and update pageHeight.
*/
VImage CropMultiPage(VImage image, int left, int top, int width, int height,
int nPages, int *pageHeight);
/*
* Split into frames, embed each frame, reassemble, and update pageHeight.
*/
VImage EmbedMultiPage(VImage image, int left, int top, int width, int height,
VipsExtend extendWith, std::vector<double> background, int nPages, int *pageHeight);
} // namespace sharp

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +1,5 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
#ifndef SRC_PIPELINE_H_
#define SRC_PIPELINE_H_
@@ -18,21 +7,14 @@
#include <memory>
#include <string>
#include <vector>
#include <unordered_map>
#include <nan.h>
#include <napi.h>
#include <vips/vips8>
#include "./common.h"
NAN_METHOD(pipeline);
enum class Canvas {
CROP,
EMBED,
MAX,
MIN,
IGNORE_ASPECT
};
Napi::Value pipeline(const Napi::CallbackInfo& info);
struct Composite {
sharp::InputDescriptor *input;
@@ -40,6 +22,7 @@ struct Composite {
int gravity;
int left;
int top;
bool hasOffset;
bool tile;
bool premultiplied;
@@ -47,15 +30,15 @@ struct Composite {
input(nullptr),
mode(VIPS_BLEND_MODE_OVER),
gravity(0),
left(-1),
top(-1),
left(0),
top(0),
hasOffset(false),
tile(false),
premultiplied(false) {}
};
struct PipelineBaton {
sharp::InputDescriptor *input;
int limitInputPixels;
std::string formatOut;
std::string fileOut;
void *bufferOut;
@@ -73,39 +56,55 @@ struct PipelineBaton {
int width;
int height;
int channels;
Canvas canvas;
VipsKernel kernel;
sharp::Canvas canvas;
int position;
std::vector<double> resizeBackground;
bool hasCropOffset;
int cropOffsetLeft;
int cropOffsetTop;
bool hasAttentionCenter;
int attentionX;
int attentionY;
bool premultiplied;
std::string kernel;
bool tileCentre;
bool fastShrinkOnLoad;
double tintA;
double tintB;
bool flatten;
std::vector<double> flattenBackground;
bool unflatten;
bool negate;
bool negateAlpha;
double blurSigma;
double brightness;
double saturation;
int hue;
double lightness;
int medianSize;
double sharpenSigma;
double sharpenFlat;
double sharpenJagged;
double sharpenM1;
double sharpenM2;
double sharpenX1;
double sharpenY2;
double sharpenY3;
int threshold;
bool thresholdGrayscale;
std::vector<double> trimBackground;
double trimThreshold;
int trimOffsetLeft;
int trimOffsetTop;
double linearA;
double linearB;
std::vector<double> linearA;
std::vector<double> linearB;
double gamma;
double gammaOut;
bool greyscale;
bool normalise;
int normaliseLower;
int normaliseUpper;
int claheWidth;
int claheHeight;
int claheMaxSlope;
bool useExifOrientation;
int angle;
double rotationAngle;
@@ -118,8 +117,16 @@ struct PipelineBaton {
int extendLeft;
int extendRight;
std::vector<double> extendBackground;
VipsExtend extendWith;
bool withoutEnlargement;
VipsAccess accessMethod;
bool withoutReduction;
std::vector<double> affineMatrix;
std::vector<double> affineBackground;
double affineIdx;
double affineIdy;
double affineOdx;
double affineOdy;
std::string affineInterpolator;
int jpegQuality;
bool jpegProgressive;
std::string jpegChromaSubsampling;
@@ -133,30 +140,58 @@ struct PipelineBaton {
bool pngAdaptiveFiltering;
bool pngPalette;
int pngQuality;
int pngColours;
int pngEffort;
int pngBitdepth;
double pngDither;
int jp2Quality;
bool jp2Lossless;
int jp2TileHeight;
int jp2TileWidth;
std::string jp2ChromaSubsampling;
int webpQuality;
int webpAlphaQuality;
bool webpNearLossless;
bool webpLossless;
bool webpSmartSubsample;
int webpReductionEffort;
VipsForeignWebpPreset webpPreset;
int webpEffort;
bool webpMinSize;
bool webpMixed;
int gifBitdepth;
int gifEffort;
double gifDither;
double gifInterFrameMaxError;
double gifInterPaletteMaxError;
bool gifReuse;
bool gifProgressive;
int tiffQuality;
VipsForeignTiffCompression tiffCompression;
VipsForeignTiffPredictor tiffPredictor;
bool tiffPyramid;
bool tiffSquash;
int tiffBitdepth;
bool tiffTile;
int tiffTileHeight;
int tiffTileWidth;
double tiffXres;
double tiffYres;
VipsForeignTiffResunit tiffResolutionUnit;
int heifQuality;
int heifCompression; // TODO(libvips 8.9.0): VipsForeignHeifCompression
VipsForeignHeifCompression heifCompression;
int heifEffort;
std::string heifChromaSubsampling;
bool heifLossless;
double jxlDistance;
int jxlDecodingTier;
int jxlEffort;
bool jxlLossless;
VipsBandFormat rawDepth;
std::string err;
bool withMetadata;
int withMetadataOrientation;
double withMetadataDensity;
std::string withMetadataIcc;
std::unordered_map<std::string, std::string> withMetadataStrs;
int timeoutSeconds;
std::unique_ptr<double[]> convKernel;
int convKernelWidth;
int convKernelHeight;
@@ -167,55 +202,76 @@ struct PipelineBaton {
VipsOperationBoolean bandBoolOp;
int extractChannel;
bool removeAlpha;
bool ensureAlpha;
double ensureAlpha;
VipsInterpretation colourspaceInput;
VipsInterpretation colourspace;
std::vector<int> delay;
int loop;
int tileSize;
int tileOverlap;
VipsForeignDzContainer tileContainer;
VipsForeignDzLayout tileLayout;
std::string tileFormat;
int tileAngle;
std::vector<double> tileBackground;
int tileSkipBlanks;
VipsForeignDzDepth tileDepth;
std::string tileId;
std::string tileBasename;
std::unique_ptr<double[]> recombMatrix;
PipelineBaton():
input(nullptr),
limitInputPixels(0),
bufferOutLength(0),
topOffsetPre(-1),
topOffsetPost(-1),
channels(0),
canvas(Canvas::CROP),
kernel(VIPS_KERNEL_LANCZOS3),
canvas(sharp::Canvas::CROP),
position(0),
resizeBackground{ 0.0, 0.0, 0.0, 255.0 },
hasCropOffset(false),
cropOffsetLeft(0),
cropOffsetTop(0),
hasAttentionCenter(false),
attentionX(0),
attentionY(0),
premultiplied(false),
tintA(128.0),
tintB(128.0),
flatten(false),
flattenBackground{ 0.0, 0.0, 0.0 },
unflatten(false),
negate(false),
negateAlpha(true),
blurSigma(0.0),
brightness(1.0),
saturation(1.0),
hue(0),
lightness(0),
medianSize(0),
sharpenSigma(0.0),
sharpenFlat(1.0),
sharpenJagged(2.0),
sharpenM1(1.0),
sharpenM2(2.0),
sharpenX1(2.0),
sharpenY2(10.0),
sharpenY3(20.0),
threshold(0),
thresholdGrayscale(true),
trimBackground{},
trimThreshold(0.0),
trimOffsetLeft(0),
trimOffsetTop(0),
linearA(1.0),
linearB(0.0),
linearA{},
linearB{},
gamma(0.0),
greyscale(false),
normalise(false),
normaliseLower(1),
normaliseUpper(99),
claheWidth(0),
claheHeight(0),
claheMaxSlope(3),
useExifOrientation(false),
angle(0),
rotationAngle(0.0),
@@ -227,7 +283,16 @@ struct PipelineBaton {
extendLeft(0),
extendRight(0),
extendBackground{ 0.0, 0.0, 0.0, 255.0 },
extendWith(VIPS_EXTEND_BACKGROUND),
withoutEnlargement(false),
withoutReduction(false),
affineMatrix{ 1.0, 0.0, 0.0, 1.0 },
affineBackground{ 0.0, 0.0, 0.0, 255.0 },
affineIdx(0),
affineIdy(0),
affineOdx(0),
affineOdy(0),
affineInterpolator("bicubic"),
jpegQuality(80),
jpegProgressive(false),
jpegChromaSubsampling("4:2:0"),
@@ -237,33 +302,59 @@ struct PipelineBaton {
jpegOptimiseScans(false),
jpegOptimiseCoding(true),
pngProgressive(false),
pngCompressionLevel(9),
pngCompressionLevel(6),
pngAdaptiveFiltering(false),
pngPalette(false),
pngQuality(100),
pngColours(256),
pngEffort(7),
pngBitdepth(8),
pngDither(1.0),
jp2Quality(80),
jp2Lossless(false),
jp2TileHeight(512),
jp2TileWidth(512),
jp2ChromaSubsampling("4:4:4"),
webpQuality(80),
webpAlphaQuality(100),
webpNearLossless(false),
webpLossless(false),
webpSmartSubsample(false),
webpReductionEffort(4),
webpPreset(VIPS_FOREIGN_WEBP_PRESET_DEFAULT),
webpEffort(4),
webpMinSize(false),
webpMixed(false),
gifBitdepth(8),
gifEffort(7),
gifDither(1.0),
gifInterFrameMaxError(0.0),
gifInterPaletteMaxError(3.0),
gifReuse(true),
gifProgressive(false),
tiffQuality(80),
tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG),
tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL),
tiffPyramid(false),
tiffSquash(false),
tiffBitdepth(8),
tiffTile(false),
tiffTileHeight(256),
tiffTileWidth(256),
tiffXres(1.0),
tiffYres(1.0),
heifQuality(80),
heifCompression(1), // TODO(libvips 8.9.0): VIPS_FOREIGN_HEIF_COMPRESSION_HEVC
tiffResolutionUnit(VIPS_FOREIGN_TIFF_RESUNIT_INCH),
heifQuality(50),
heifCompression(VIPS_FOREIGN_HEIF_COMPRESSION_AV1),
heifEffort(4),
heifChromaSubsampling("4:4:4"),
heifLossless(false),
jxlDistance(1.0),
jxlDecodingTier(0),
jxlEffort(7),
jxlLossless(false),
rawDepth(VIPS_FORMAT_UCHAR),
withMetadata(false),
withMetadataOrientation(-1),
withMetadataDensity(0.0),
timeoutSeconds(0),
convKernelWidth(0),
convKernelHeight(0),
convKernelScale(0.0),
@@ -273,13 +364,16 @@ struct PipelineBaton {
bandBoolOp(VIPS_OPERATION_BOOLEAN_LAST),
extractChannel(-1),
removeAlpha(false),
ensureAlpha(false),
ensureAlpha(-1.0),
colourspaceInput(VIPS_INTERPRETATION_LAST),
colourspace(VIPS_INTERPRETATION_LAST),
loop(-1),
tileSize(256),
tileOverlap(0),
tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS),
tileLayout(VIPS_FOREIGN_DZ_LAYOUT_DZ),
tileAngle(0),
tileBackground{ 255.0, 255.0, 255.0, 255.0 },
tileSkipBlanks(-1),
tileDepth(VIPS_FOREIGN_DZ_DEPTH_LAST) {}
};

View File

@@ -1,19 +1,7 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
#include <node.h>
#include <nan.h>
#include <napi.h>
#include <vips/vips8>
#include "common.h"
@@ -22,33 +10,32 @@
#include "utilities.h"
#include "stats.h"
NAN_MODULE_INIT(init) {
static void* sharp_vips_init(void*) {
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),
static_cast<GLogFunc>(sharp::VipsWarningCallback), nullptr);
// Methods available to JavaScript
Nan::Set(target, Nan::New("metadata").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(metadata)).ToLocalChecked());
Nan::Set(target, Nan::New("pipeline").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(pipeline)).ToLocalChecked());
Nan::Set(target, Nan::New("cache").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(cache)).ToLocalChecked());
Nan::Set(target, Nan::New("concurrency").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(concurrency)).ToLocalChecked());
Nan::Set(target, Nan::New("counters").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(counters)).ToLocalChecked());
Nan::Set(target, Nan::New("simd").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(simd)).ToLocalChecked());
Nan::Set(target, Nan::New("libvipsVersion").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(libvipsVersion)).ToLocalChecked());
Nan::Set(target, Nan::New("format").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(format)).ToLocalChecked());
Nan::Set(target, Nan::New("_maxColourDistance").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(_maxColourDistance)).ToLocalChecked());
Nan::Set(target, Nan::New("stats").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(stats)).ToLocalChecked());
exports.Set("metadata", Napi::Function::New(env, metadata));
exports.Set("pipeline", Napi::Function::New(env, pipeline));
exports.Set("cache", Napi::Function::New(env, cache));
exports.Set("concurrency", Napi::Function::New(env, concurrency));
exports.Set("counters", Napi::Function::New(env, counters));
exports.Set("simd", Napi::Function::New(env, simd));
exports.Set("libvipsVersion", Napi::Function::New(env, libvipsVersion));
exports.Set("format", Napi::Function::New(env, format));
exports.Set("block", Napi::Function::New(env, block));
exports.Set("_maxColourDistance", Napi::Function::New(env, _maxColourDistance));
exports.Set("_isUsingJemalloc", Napi::Function::New(env, _isUsingJemalloc));
exports.Set("stats", Napi::Function::New(env, stats));
return exports;
}
NAN_MODULE_WORKER_ENABLED(sharp, init)
NODE_API_MODULE(sharp, init)

View File

@@ -1,43 +1,20 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
#include <numeric>
#include <vector>
#include <iostream>
#include <node.h>
#include <nan.h>
#include <napi.h>
#include <vips/vips8>
#include "common.h"
#include "stats.h"
class StatsWorker : public Nan::AsyncWorker {
class StatsWorker : public Napi::AsyncWorker {
public:
StatsWorker(
Nan::Callback *callback, StatsBaton *baton, Nan::Callback *debuglog,
std::vector<v8::Local<v8::Object>> const buffersToPersist) :
Nan::AsyncWorker(callback, "sharp:StatsWorker"),
baton(baton), debuglog(debuglog),
buffersToPersist(buffersToPersist) {
// Protect Buffer objects from GC, keyed on index
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
SaveToPersistent(index, buffer);
return index + 1;
});
}
StatsWorker(Napi::Function callback, StatsBaton *baton, Napi::Function debuglog) :
Napi::AsyncWorker(callback), baton(baton), debuglog(Napi::Persistent(debuglog)) {}
~StatsWorker() {}
const int STAT_MIN_INDEX = 0;
@@ -54,15 +31,11 @@ class StatsWorker : public Nan::AsyncWorker {
void Execute() {
// Decrement queued task counter
g_atomic_int_dec_and_test(&sharp::counterQueue);
using Nan::New;
using Nan::Set;
using sharp::MaximumImageAlpha;
vips::VImage image;
sharp::ImageType imageType = sharp::ImageType::UNKNOWN;
try {
std::tie(image, imageType) = OpenInput(baton->input, baton->accessMethod);
std::tie(image, imageType) = OpenInput(baton->input);
} catch (vips::VError const &err) {
(baton->err).append(err.what());
}
@@ -71,25 +44,51 @@ class StatsWorker : public Nan::AsyncWorker {
vips::VImage stats = image.stats();
int const bands = image.bands();
for (int b = 1; b <= bands; b++) {
ChannelStats cStats(static_cast<int>(stats.getpoint(STAT_MIN_INDEX, b).front()),
static_cast<int>(stats.getpoint(STAT_MAX_INDEX, b).front()),
stats.getpoint(STAT_SUM_INDEX, b).front(), stats.getpoint(STAT_SQ_SUM_INDEX, b).front(),
stats.getpoint(STAT_MEAN_INDEX, b).front(), stats.getpoint(STAT_STDEV_INDEX, b).front(),
static_cast<int>(stats.getpoint(STAT_MINX_INDEX, b).front()),
static_cast<int>(stats.getpoint(STAT_MINY_INDEX, b).front()),
static_cast<int>(stats.getpoint(STAT_MAXX_INDEX, b).front()),
static_cast<int>(stats.getpoint(STAT_MAXY_INDEX, b).front()));
ChannelStats cStats(
static_cast<int>(stats.getpoint(STAT_MIN_INDEX, b).front()),
static_cast<int>(stats.getpoint(STAT_MAX_INDEX, b).front()),
stats.getpoint(STAT_SUM_INDEX, b).front(),
stats.getpoint(STAT_SQ_SUM_INDEX, b).front(),
stats.getpoint(STAT_MEAN_INDEX, b).front(),
stats.getpoint(STAT_STDEV_INDEX, b).front(),
static_cast<int>(stats.getpoint(STAT_MINX_INDEX, b).front()),
static_cast<int>(stats.getpoint(STAT_MINY_INDEX, b).front()),
static_cast<int>(stats.getpoint(STAT_MAXX_INDEX, b).front()),
static_cast<int>(stats.getpoint(STAT_MAXY_INDEX, b).front()));
baton->channelStats.push_back(cStats);
}
// Image is not opaque when alpha layer is present and contains a non-mamixa value
if (sharp::HasAlpha(image)) {
double const minAlpha = static_cast<double>(stats.getpoint(STAT_MIN_INDEX, bands).front());
if (minAlpha != MaximumImageAlpha(image.interpretation())) {
if (minAlpha != sharp::MaximumImageAlpha(image.interpretation())) {
baton->isOpaque = false;
}
}
// Convert to greyscale
vips::VImage greyscale = image.colourspace(VIPS_INTERPRETATION_B_W)[0];
// Estimate entropy via histogram of greyscale value frequency
baton->entropy = std::abs(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) {
(baton->err).append(err.what());
}
@@ -100,93 +99,85 @@ class StatsWorker : public Nan::AsyncWorker {
vips_thread_shutdown();
}
void HandleOKCallback() {
using Nan::New;
using Nan::Set;
Nan::HandleScope();
v8::Local<v8::Value> argv[2] = { Nan::Null(), Nan::Null() };
if (!baton->err.empty()) {
argv[0] = Nan::Error(baton->err.data());
} else {
// Stats Object
v8::Local<v8::Object> info = New<v8::Object>();
v8::Local<v8::Array> channels = New<v8::Array>();
std::vector<ChannelStats>::iterator it;
int i = 0;
for (it = baton->channelStats.begin(); it < baton->channelStats.end(); it++, i++) {
v8::Local<v8::Object> channelStat = New<v8::Object>();
Set(channelStat, New("min").ToLocalChecked(), New<v8::Number>(it->min));
Set(channelStat, New("max").ToLocalChecked(), New<v8::Number>(it->max));
Set(channelStat, New("sum").ToLocalChecked(), New<v8::Number>(it->sum));
Set(channelStat, New("squaresSum").ToLocalChecked(), New<v8::Number>(it->squaresSum));
Set(channelStat, New("mean").ToLocalChecked(), New<v8::Number>(it->mean));
Set(channelStat, New("stdev").ToLocalChecked(), New<v8::Number>(it->stdev));
Set(channelStat, New("minX").ToLocalChecked(), New<v8::Number>(it->minX));
Set(channelStat, New("minY").ToLocalChecked(), New<v8::Number>(it->minY));
Set(channelStat, New("maxX").ToLocalChecked(), New<v8::Number>(it->maxX));
Set(channelStat, New("maxY").ToLocalChecked(), New<v8::Number>(it->maxY));
Set(channels, i, channelStat);
}
Set(info, New("channels").ToLocalChecked(), channels);
Set(info, New("isOpaque").ToLocalChecked(), New<v8::Boolean>(baton->isOpaque));
Set(info, New("entropy").ToLocalChecked(), New<v8::Number>(baton->entropy));
argv[1] = info;
}
// Dispose of Persistent wrapper around input Buffers so they can be garbage collected
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
GetFromPersistent(index);
return index + 1;
});
delete baton->input;
delete baton;
void OnOK() {
Napi::Env env = Env();
Napi::HandleScope scope(env);
// Handle warnings
std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) {
v8::Local<v8::Value> message[1] = { New(warning).ToLocalChecked() };
debuglog->Call(1, message, async_resource);
debuglog.MakeCallback(Receiver().Value(), { Napi::String::New(env, warning) });
warning = sharp::VipsWarningPop();
}
// Return to JavaScript
callback->Call(2, argv, async_resource);
if (baton->err.empty()) {
// Stats Object
Napi::Object info = Napi::Object::New(env);
Napi::Array channels = Napi::Array::New(env);
std::vector<ChannelStats>::iterator it;
int i = 0;
for (it = baton->channelStats.begin(); it < baton->channelStats.end(); it++, i++) {
Napi::Object channelStat = Napi::Object::New(env);
channelStat.Set("min", it->min);
channelStat.Set("max", it->max);
channelStat.Set("sum", it->sum);
channelStat.Set("squaresSum", it->squaresSum);
channelStat.Set("mean", it->mean);
channelStat.Set("stdev", it->stdev);
channelStat.Set("minX", it->minX);
channelStat.Set("minY", it->minY);
channelStat.Set("maxX", it->maxX);
channelStat.Set("maxY", it->maxY);
channels.Set(i, channelStat);
}
info.Set("channels", channels);
info.Set("isOpaque", baton->isOpaque);
info.Set("entropy", baton->entropy);
info.Set("sharpness", baton->sharpness);
Napi::Object dominant = Napi::Object::New(env);
dominant.Set("r", baton->dominantRed);
dominant.Set("g", baton->dominantGreen);
dominant.Set("b", baton->dominantBlue);
info.Set("dominant", dominant);
Callback().MakeCallback(Receiver().Value(), { env.Null(), info });
} else {
Callback().MakeCallback(Receiver().Value(), { Napi::Error::New(env, sharp::TrimEnd(baton->err)).Value() });
}
delete baton->input;
delete baton;
}
private:
StatsBaton* baton;
Nan::Callback *debuglog;
std::vector<v8::Local<v8::Object>> buffersToPersist;
Napi::FunctionReference debuglog;
};
/*
stats(options, callback)
*/
NAN_METHOD(stats) {
using sharp::AttrTo;
// Input Buffers must not undergo GC compaction during processing
std::vector<v8::Local<v8::Object>> buffersToPersist;
Napi::Value stats(const Napi::CallbackInfo& info) {
// V8 objects are converted to non-V8 types held in the baton struct
StatsBaton *baton = new StatsBaton;
v8::Local<v8::Object> options = info[0].As<v8::Object>();
Napi::Object options = info[size_t(0)].As<Napi::Object>();
// Input
baton->input = sharp::CreateInputDescriptor(sharp::AttrAs<v8::Object>(options, "input"), buffersToPersist);
baton->accessMethod = AttrTo<bool>(options, "sequentialRead") ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
baton->input = sharp::CreateInputDescriptor(options.Get("input").As<Napi::Object>());
baton->input->access = VIPS_ACCESS_RANDOM;
// Function to notify of libvips warnings
Nan::Callback *debuglog = new Nan::Callback(sharp::AttrAs<v8::Function>(options, "debuglog"));
Napi::Function debuglog = options.Get("debuglog").As<Napi::Function>();
// Join queue for worker thread
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>());
Nan::AsyncQueueWorker(new StatsWorker(callback, baton, debuglog, buffersToPersist));
Napi::Function callback = info[size_t(1)].As<Napi::Function>();
StatsWorker *worker = new StatsWorker(callback, baton, debuglog);
worker->Receiver().Set("options", options);
worker->Queue();
// Increment queued task counter
g_atomic_int_inc(&sharp::counterQueue);
return info.Env().Undefined();
}

View File

@@ -1,22 +1,11 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
#ifndef SRC_STATS_H_
#define SRC_STATS_H_
#include <string>
#include <nan.h>
#include <napi.h>
#include "./common.h"
@@ -33,12 +22,8 @@ struct ChannelStats {
int maxX;
int maxY;
ChannelStats():
min(0), max(0), sum(0), squaresSum(0), mean(0), stdev(0)
, minX(0), minY(0), maxX(0), maxY(0) {}
ChannelStats(int minVal, int maxVal, double sumVal, double squaresSumVal,
double meanVal, double stdevVal, int minXVal, int minYVal, int maxXVal, int maxYVal):
ChannelStats(int minVal, int maxVal, double sumVal, double squaresSumVal,
double meanVal, double stdevVal, int minXVal, int minYVal, int maxXVal, int maxYVal):
min(minVal), max(maxVal), sum(sumVal), squaresSum(squaresSumVal),
mean(meanVal), stdev(stdevVal), minX(minXVal), minY(minYVal), maxX(maxXVal), maxY(maxYVal) {}
};
@@ -46,22 +31,29 @@ struct ChannelStats {
struct StatsBaton {
// Input
sharp::InputDescriptor *input;
VipsAccess accessMethod;
// Output
std::vector<ChannelStats> channelStats;
bool isOpaque;
double entropy;
double sharpness;
int dominantRed;
int dominantGreen;
int dominantBlue;
std::string err;
StatsBaton():
input(nullptr),
isOpaque(true),
entropy(0.0)
entropy(0.0),
sharpness(0.0),
dominantRed(0),
dominantGreen(0),
dominantBlue(0)
{}
};
NAN_METHOD(stats);
Napi::Value stats(const Napi::CallbackInfo& info);
#endif // SRC_STATS_H_

View File

@@ -1,22 +1,10 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
#include <cmath>
#include <string>
#include <node.h>
#include <nan.h>
#include <napi.h>
#include <vips/vips8>
#include <vips/vector.h>
@@ -24,183 +12,167 @@
#include "operations.h"
#include "utilities.h"
using v8::Boolean;
using v8::Integer;
using v8::Local;
using v8::Number;
using v8::Object;
using v8::String;
using Nan::HandleScope;
using Nan::New;
using Nan::Set;
using Nan::ThrowError;
using Nan::To;
using Nan::Utf8String;
/*
Get and set cache limits
*/
NAN_METHOD(cache) {
HandleScope();
Napi::Value cache(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
// Set memory limit
if (info[0]->IsInt32()) {
vips_cache_set_max_mem(To<int32_t>(info[0]).FromJust() * 1048576);
if (info[size_t(0)].IsNumber()) {
vips_cache_set_max_mem(info[size_t(0)].As<Napi::Number>().Int32Value() * 1048576);
}
// Set file limit
if (info[1]->IsInt32()) {
vips_cache_set_max_files(To<int32_t>(info[1]).FromJust());
if (info[size_t(1)].IsNumber()) {
vips_cache_set_max_files(info[size_t(1)].As<Napi::Number>().Int32Value());
}
// Set items limit
if (info[2]->IsInt32()) {
vips_cache_set_max(To<int32_t>(info[2]).FromJust());
if (info[size_t(2)].IsNumber()) {
vips_cache_set_max(info[size_t(2)].As<Napi::Number>().Int32Value());
}
// Get memory stats
Local<Object> memory = New<Object>();
Set(memory, New("current").ToLocalChecked(),
New<Integer>(static_cast<int>(round(vips_tracked_get_mem() / 1048576))));
Set(memory, New("high").ToLocalChecked(),
New<Integer>(static_cast<int>(round(vips_tracked_get_mem_highwater() / 1048576))));
Set(memory, New("max").ToLocalChecked(),
New<Integer>(static_cast<int>(round(vips_cache_get_max_mem() / 1048576))));
Napi::Object memory = Napi::Object::New(env);
memory.Set("current", round(vips_tracked_get_mem() / 1048576));
memory.Set("high", round(vips_tracked_get_mem_highwater() / 1048576));
memory.Set("max", round(vips_cache_get_max_mem() / 1048576));
// Get file stats
Local<Object> files = New<Object>();
Set(files, New("current").ToLocalChecked(), New<Integer>(vips_tracked_get_files()));
Set(files, New("max").ToLocalChecked(), New<Integer>(vips_cache_get_max_files()));
Napi::Object files = Napi::Object::New(env);
files.Set("current", vips_tracked_get_files());
files.Set("max", vips_cache_get_max_files());
// Get item stats
Local<Object> items = New<Object>();
Set(items, New("current").ToLocalChecked(), New<Integer>(vips_cache_get_size()));
Set(items, New("max").ToLocalChecked(), New<Integer>(vips_cache_get_max()));
Napi::Object items = Napi::Object::New(env);
items.Set("current", vips_cache_get_size());
items.Set("max", vips_cache_get_max());
Local<Object> cache = New<Object>();
Set(cache, New("memory").ToLocalChecked(), memory);
Set(cache, New("files").ToLocalChecked(), files);
Set(cache, New("items").ToLocalChecked(), items);
info.GetReturnValue().Set(cache);
Napi::Object cache = Napi::Object::New(env);
cache.Set("memory", memory);
cache.Set("files", files);
cache.Set("items", items);
return cache;
}
/*
Get and set size of thread pool
*/
NAN_METHOD(concurrency) {
HandleScope();
Napi::Value concurrency(const Napi::CallbackInfo& info) {
// Set concurrency
if (info[0]->IsInt32()) {
vips_concurrency_set(To<int32_t>(info[0]).FromJust());
if (info[size_t(0)].IsNumber()) {
vips_concurrency_set(info[size_t(0)].As<Napi::Number>().Int32Value());
}
// Get concurrency
info.GetReturnValue().Set(New<Integer>(vips_concurrency_get()));
return Napi::Number::New(info.Env(), vips_concurrency_get());
}
/*
Get internal counters (queued tasks, processing tasks)
*/
NAN_METHOD(counters) {
using sharp::counterProcess;
using sharp::counterQueue;
HandleScope();
Local<Object> counters = New<Object>();
Set(counters, New("queue").ToLocalChecked(), New<Integer>(counterQueue));
Set(counters, New("process").ToLocalChecked(), New<Integer>(counterProcess));
info.GetReturnValue().Set(counters);
Napi::Value counters(const Napi::CallbackInfo& info) {
Napi::Object counters = Napi::Object::New(info.Env());
counters.Set("queue", sharp::counterQueue);
counters.Set("process", sharp::counterProcess);
return counters;
}
/*
Get and set use of SIMD vector unit instructions
*/
NAN_METHOD(simd) {
HandleScope();
Napi::Value simd(const Napi::CallbackInfo& info) {
// Set state
if (info[0]->IsBoolean()) {
vips_vector_set_enabled(To<bool>(info[0]).FromJust());
if (info[size_t(0)].IsBoolean()) {
vips_vector_set_enabled(info[size_t(0)].As<Napi::Boolean>().Value());
}
// Get state
info.GetReturnValue().Set(New<Boolean>(vips_vector_isenabled()));
return Napi::Boolean::New(info.Env(), vips_vector_isenabled());
}
/*
Get libvips version
*/
NAN_METHOD(libvipsVersion) {
HandleScope();
Napi::Value libvipsVersion(const Napi::CallbackInfo& info) {
char version[9];
g_snprintf(version, sizeof(version), "%d.%d.%d", vips_version(0), vips_version(1), vips_version(2));
info.GetReturnValue().Set(New(version).ToLocalChecked());
return Napi::String::New(info.Env(), version);
}
/*
Get available input/output file/buffer/stream formats
*/
NAN_METHOD(format) {
HandleScope();
// Attribute names
Local<String> attrId = New("id").ToLocalChecked();
Local<String> attrInput = New("input").ToLocalChecked();
Local<String> attrOutput = New("output").ToLocalChecked();
Local<String> attrFile = New("file").ToLocalChecked();
Local<String> attrBuffer = New("buffer").ToLocalChecked();
Local<String> attrStream = New("stream").ToLocalChecked();
// Which load/save operations are available for each compressed format?
Local<Object> format = New<Object>();
Napi::Value format(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
Napi::Object format = Napi::Object::New(env);
for (std::string const f : {
"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz",
"ppm", "fits", "gif", "svg", "heif", "pdf", "vips"
"ppm", "fits", "gif", "svg", "heif", "pdf", "vips", "jp2k", "jxl"
}) {
// Input
Local<Boolean> hasInputFile =
New<Boolean>(vips_type_find("VipsOperation", (f + "load").c_str()));
Local<Boolean> hasInputBuffer =
New<Boolean>(vips_type_find("VipsOperation", (f + "load_buffer").c_str()));
Local<Object> input = New<Object>();
Set(input, attrFile, hasInputFile);
Set(input, attrBuffer, hasInputBuffer);
Set(input, attrStream, hasInputBuffer);
const VipsObjectClass *oc = vips_class_find("VipsOperation", (f + "load").c_str());
Napi::Boolean hasInputFile = Napi::Boolean::New(env, oc);
Napi::Boolean hasInputBuffer =
Napi::Boolean::New(env, vips_type_find("VipsOperation", (f + "load_buffer").c_str()));
Napi::Object input = Napi::Object::New(env);
input.Set("file", hasInputFile);
input.Set("buffer", hasInputBuffer);
input.Set("stream", hasInputBuffer);
if (hasInputFile) {
const VipsForeignClass *fc = VIPS_FOREIGN_CLASS(oc);
if (fc->suffs) {
Napi::Array fileSuffix = Napi::Array::New(env);
const char **suffix = fc->suffs;
for (int i = 0; *suffix; i++, suffix++) {
fileSuffix.Set(i, Napi::String::New(env, *suffix));
}
input.Set("fileSuffix", fileSuffix);
}
}
// Output
Local<Boolean> hasOutputFile =
New<Boolean>(vips_type_find("VipsOperation", (f + "save").c_str()));
Local<Boolean> hasOutputBuffer =
New<Boolean>(vips_type_find("VipsOperation", (f + "save_buffer").c_str()));
Local<Object> output = New<Object>();
Set(output, attrFile, hasOutputFile);
Set(output, attrBuffer, hasOutputBuffer);
Set(output, attrStream, hasOutputBuffer);
Napi::Boolean hasOutputFile =
Napi::Boolean::New(env, vips_type_find("VipsOperation", (f + "save").c_str()));
Napi::Boolean hasOutputBuffer =
Napi::Boolean::New(env, vips_type_find("VipsOperation", (f + "save_buffer").c_str()));
Napi::Object output = Napi::Object::New(env);
output.Set("file", hasOutputFile);
output.Set("buffer", hasOutputBuffer);
output.Set("stream", hasOutputBuffer);
// Other attributes
Local<Object> container = New<Object>();
Local<String> formatId = New(f).ToLocalChecked();
Set(container, attrId, formatId);
Set(container, attrInput, input);
Set(container, attrOutput, output);
Napi::Object container = Napi::Object::New(env);
container.Set("id", f);
container.Set("input", input);
container.Set("output", output);
// Add to set of formats
Set(format, formatId, container);
format.Set(f, container);
}
// Raw, uncompressed data
Local<Object> raw = New<Object>();
Local<String> rawId = New("raw").ToLocalChecked();
Set(raw, attrId, rawId);
Set(format, rawId, raw);
Local<Boolean> supported = New<Boolean>(true);
Local<Boolean> unsupported = New<Boolean>(false);
Local<Object> rawInput = New<Object>();
Set(rawInput, attrFile, unsupported);
Set(rawInput, attrBuffer, supported);
Set(rawInput, attrStream, supported);
Set(raw, attrInput, rawInput);
Local<Object> rawOutput = New<Object>();
Set(rawOutput, attrFile, unsupported);
Set(rawOutput, attrBuffer, supported);
Set(rawOutput, attrStream, supported);
Set(raw, attrOutput, rawOutput);
Napi::Boolean supported = Napi::Boolean::New(env, true);
Napi::Boolean unsupported = Napi::Boolean::New(env, false);
Napi::Object rawInput = Napi::Object::New(env);
rawInput.Set("file", unsupported);
rawInput.Set("buffer", supported);
rawInput.Set("stream", supported);
Napi::Object rawOutput = Napi::Object::New(env);
rawOutput.Set("file", unsupported);
rawOutput.Set("buffer", supported);
rawOutput.Set("stream", supported);
Napi::Object raw = Napi::Object::New(env);
raw.Set("id", "raw");
raw.Set("input", rawInput);
raw.Set("output", rawOutput);
format.Set("raw", raw);
info.GetReturnValue().Set(format);
return format;
}
/*
(Un)block libvips operations at runtime.
*/
void block(const Napi::CallbackInfo& info) {
Napi::Array ops = info[size_t(0)].As<Napi::Array>();
bool const state = info[size_t(1)].As<Napi::Boolean>().Value();
for (unsigned int i = 0; i < ops.Length(); i++) {
vips_operation_block_set(ops.Get(i).As<Napi::String>().Utf8Value().c_str(), state);
}
}
/*
@@ -208,65 +180,75 @@ NAN_METHOD(format) {
Calculates the maximum colour distance using the DE2000 algorithm
between two images of the same dimensions and number of channels.
*/
NAN_METHOD(_maxColourDistance) {
using vips::VImage;
using vips::VError;
using sharp::DetermineImageType;
using sharp::ImageType;
using sharp::HasAlpha;
HandleScope();
Napi::Value _maxColourDistance(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
// Open input files
VImage image1;
ImageType imageType1 = DetermineImageType(*Utf8String(info[0]));
if (imageType1 != ImageType::UNKNOWN) {
sharp::ImageType imageType1 = sharp::DetermineImageType(info[size_t(0)].As<Napi::String>().Utf8Value().data());
if (imageType1 != sharp::ImageType::UNKNOWN) {
try {
image1 = VImage::new_from_file(*Utf8String(info[0]));
image1 = VImage::new_from_file(info[size_t(0)].As<Napi::String>().Utf8Value().c_str());
} catch (...) {
return ThrowError("Input file 1 has corrupt header");
throw Napi::Error::New(env, "Input file 1 has corrupt header");
}
} else {
return ThrowError("Input file 1 is of an unsupported image format");
throw Napi::Error::New(env, "Input file 1 is of an unsupported image format");
}
VImage image2;
ImageType imageType2 = DetermineImageType(*Utf8String(info[1]));
if (imageType2 != ImageType::UNKNOWN) {
sharp::ImageType imageType2 = sharp::DetermineImageType(info[size_t(1)].As<Napi::String>().Utf8Value().data());
if (imageType2 != sharp::ImageType::UNKNOWN) {
try {
image2 = VImage::new_from_file(*Utf8String(info[1]));
image2 = VImage::new_from_file(info[size_t(1)].As<Napi::String>().Utf8Value().c_str());
} catch (...) {
return ThrowError("Input file 2 has corrupt header");
throw Napi::Error::New(env, "Input file 2 has corrupt header");
}
} else {
return ThrowError("Input file 2 is of an unsupported image format");
throw Napi::Error::New(env, "Input file 2 is of an unsupported image format");
}
// Ensure same number of channels
if (image1.bands() != image2.bands()) {
return ThrowError("mismatchedBands");
throw Napi::Error::New(env, "mismatchedBands");
}
// Ensure same dimensions
if (image1.width() != image2.width() || image1.height() != image2.height()) {
return ThrowError("mismatchedDimensions");
throw Napi::Error::New(env, "mismatchedDimensions");
}
double maxColourDistance;
try {
// Premultiply and remove alpha
if (HasAlpha(image1)) {
if (sharp::HasAlpha(image1)) {
image1 = image1.premultiply().extract_band(1, VImage::option()->set("n", image1.bands() - 1));
}
if (HasAlpha(image2)) {
if (sharp::HasAlpha(image2)) {
image2 = image2.premultiply().extract_band(1, VImage::option()->set("n", image2.bands() - 1));
}
// Calculate colour distance
maxColourDistance = image1.dE00(image2).max();
} catch (VError const &err) {
return ThrowError(err.what());
} catch (vips::VError const &err) {
throw Napi::Error::New(env, err.what());
}
// Clean up libvips' per-request data and threads
vips_error_clear();
vips_thread_shutdown();
info.GetReturnValue().Set(New<Number>(maxColourDistance));
return Napi::Number::New(env, maxColourDistance);
}
#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

@@ -1,28 +1,19 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
#ifndef SRC_UTILITIES_H_
#define SRC_UTILITIES_H_
#include <nan.h>
#include <napi.h>
NAN_METHOD(cache);
NAN_METHOD(concurrency);
NAN_METHOD(counters);
NAN_METHOD(simd);
NAN_METHOD(libvipsVersion);
NAN_METHOD(format);
NAN_METHOD(_maxColourDistance);
Napi::Value cache(const Napi::CallbackInfo& info);
Napi::Value concurrency(const Napi::CallbackInfo& info);
Napi::Value counters(const Napi::CallbackInfo& info);
Napi::Value simd(const Napi::CallbackInfo& info);
Napi::Value libvipsVersion(const Napi::CallbackInfo& info);
Napi::Value format(const Napi::CallbackInfo& info);
void block(const Napi::CallbackInfo& info);
Napi::Value _maxColourDistance(const Napi::CallbackInfo& info);
Napi::Value _isUsingJemalloc(const Napi::CallbackInfo& info);
#endif // SRC_UTILITIES_H_

24
test/beforeEach.js Normal file
View File

@@ -0,0 +1,24 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
const sharp = require('../');
const usingCache = !process.env.G_DEBUG;
const usingSimd = !process.env.VIPS_NOVECTOR;
const concurrency = Number(process.env.VIPS_CONCURRENCY) || 0;
exports.mochaHooks = {
beforeEach () {
sharp.cache(usingCache);
sharp.simd(usingSimd);
sharp.concurrency(concurrency);
},
afterEach () {
if (global.gc) {
global.gc();
}
}
};

31
test/bench/Dockerfile Normal file
View File

@@ -0,0 +1,31 @@
FROM ubuntu:22.04
ARG BRANCH=main
# Install basic dependencies
RUN apt-get -y update && apt-get install -y build-essential curl git
# Install latest Node.js LTS
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash -
RUN apt-get install -y nodejs
# Install benchmark dependencies
RUN apt-get install -y imagemagick libmagick++-dev graphicsmagick
# Install sharp
RUN mkdir /tmp/sharp
RUN cd /tmp && git clone --single-branch --branch $BRANCH https://github.com/lovell/sharp.git
RUN cd /tmp/sharp && npm install --build-from-source
# Install benchmark test
RUN cd /tmp/sharp/test/bench && npm install --omit optional
RUN cat /etc/os-release | grep VERSION=
RUN node -v
WORKDIR /tmp/sharp/test/bench
# Workaround for: https://github.com/emscripten-core/emscripten/pull/16917
# This could be removed once Squoosh is an optional dependency.
ENV NODE_OPTIONS="--no-experimental-fetch"
CMD [ "node", "perf" ]

View File

@@ -7,17 +7,21 @@
"scripts": {
"test": "node perf && node random && node parallel"
},
"devDependencies": {
"async": "^3.1.0",
"benchmark": "^2.1.4",
"gm": "^1.23.1",
"imagemagick": "^0.1.3",
"jimp": "^0.8.4",
"mapnik": "^4.3.1",
"semver": "^6.3.0"
"dependencies": {
"@squoosh/cli": "0.7.3",
"@squoosh/lib": "0.5.3",
"async": "3.2.4",
"benchmark": "2.1.4",
"gm": "1.25.0",
"imagemagick": "0.1.3",
"jimp": "0.22.10"
},
"optionalDependencies": {
"@tensorflow/tfjs-node": "4.9.0",
"mapnik": "4.5.9"
},
"license": "Apache-2.0",
"engines": {
"node": ">=8.5.0"
"node": "16"
}
}

View File

@@ -1,3 +1,6 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
process.env.UV_THREADPOOL_SIZE = 64;

View File

@@ -1,26 +1,48 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
const os = require('os');
const fs = require('fs');
const { exec } = require('child_process');
const async = require('async');
const assert = require('assert');
const Benchmark = require('benchmark');
const safeRequire = (name) => {
try {
return require(name);
} catch (err) {}
return null;
};
// Contenders
const sharp = require('../../');
const gm = require('gm');
const imagemagick = require('imagemagick');
const mapnik = require('mapnik');
const mapnik = safeRequire('mapnik');
const jimp = require('jimp');
const squoosh = require('@squoosh/lib');
process.env.TF_CPP_MIN_LOG_LEVEL = 1;
const tfjs = safeRequire('@tensorflow/tfjs-node');
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 height = 588;
const heightPng = 540;
// Disable libvips cache to ensure tests are as fair as they can be
sharp.cache(false);
// Spawn one thread per CPU
sharp.concurrency(os.cpus().length);
async.series({
jpeg: function (callback) {
const inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);
@@ -56,7 +78,7 @@ async.series({
image
.resize(width, height, jimp.RESIZE_BICUBIC)
.quality(80)
.write(fixtures.outputJpg, function (err) {
.write(outputJpg, function (err) {
if (err) {
throw err;
} else {
@@ -67,8 +89,67 @@ async.series({
});
}
});
// squoosh-cli
jpegSuite.add('squoosh-cli-file-file', {
defer: true,
fn: function (deferred) {
exec(`./node_modules/.bin/squoosh-cli \
--output-dir ${os.tmpdir()} \
--resize '{"enabled":true,"width":${width},"height":${height},"method":"lanczos3","premultiply":false,"linearRGB":false}' \
--mozjpeg '{"quality":80,"progressive":false,"optimize_coding":true,"quant_table":0,"trellis_multipass":false,"chroma_subsample":2,"separate_chroma_quality":false}' \
"${fixtures.inputJpg}"`, function (err) {
if (err) {
throw err;
}
deferred.resolve();
});
}
});
// squoosh-lib (GPLv3)
jpegSuite.add('squoosh-lib-buffer-buffer', {
defer: true,
fn: function (deferred) {
const pool = new squoosh.ImagePool(os.cpus().length);
const image = pool.ingestImage(inputJpgBuffer);
image.decoded
.then(function () {
return image.preprocess({
resize: {
enabled: true,
width,
height,
method: 'lanczos3',
premultiply: false,
linearRGB: false
}
});
})
.then(function () {
return image.encode({
mozjpeg: {
quality: 80,
progressive: false,
optimize_coding: true,
quant_table: 0,
trellis_multipass: false,
chroma_subsample: 2,
separate_chroma_quality: false
}
});
})
.then(function () {
return pool.close();
})
.then(function () {
return image.encodedWith.mozjpeg;
})
.then(function () {
deferred.resolve();
});
}
});
// mapnik
jpegSuite.add('mapnik-file-file', {
mapnik && jpegSuite.add('mapnik-file-file', {
defer: true,
fn: function (deferred) {
mapnik.Image.open(fixtures.inputJpg, function (err, img) {
@@ -77,7 +158,7 @@ async.series({
.resize(width, height, {
scaling_method: mapnik.imageScaling.lanczos
})
.save(fixtures.outputJpg, 'jpeg:quality=80', function (err) {
.save(outputJpg, 'jpeg:quality=80', function (err) {
if (err) throw err;
deferred.resolve();
});
@@ -105,7 +186,7 @@ async.series({
fn: function (deferred) {
imagemagick.resize({
srcPath: fixtures.inputJpg,
dstPath: fixtures.outputJpg,
dstPath: outputJpg,
quality: 0.8,
width: width,
height: height,
@@ -128,7 +209,7 @@ async.series({
.filter('Lanczos')
.resize(width, height)
.quality(80)
.write(fixtures.outputJpg, function (err) {
.write(outputJpg, function (err) {
if (err) {
throw err;
} else {
@@ -143,11 +224,10 @@ async.series({
.filter('Lanczos')
.resize(width, height)
.quality(80)
.toBuffer(function (err, buffer) {
.toBuffer(function (err) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
@@ -159,7 +239,7 @@ async.series({
.filter('Lanczos')
.resize(width, height)
.quality(80)
.write(fixtures.outputJpg, function (err) {
.write(outputJpg, function (err) {
if (err) {
throw err;
} else {
@@ -174,23 +254,40 @@ async.series({
.filter('Lanczos')
.resize(width, height)
.quality(80)
.toBuffer(function (err, buffer) {
.toBuffer(function (err) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
});
// tfjs
tfjs && jpegSuite.add('tfjs-node-buffer-buffer', {
defer: true,
fn: function (deferred) {
const decoded = tfjs.node.decodeJpeg(inputJpgBuffer);
const resized = tfjs.image.resizeBilinear(decoded, [height, width]);
tfjs
.node
.encodeJpeg(resized, 'rgb', 80)
.then(function () {
deferred.resolve();
tfjs.disposeVariables();
})
.catch(function (err) {
throw err;
});
}
});
// sharp
jpegSuite.add('sharp-buffer-file', {
defer: true,
fn: function (deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.toFile(fixtures.outputJpg, function (err) {
.toFile(outputJpg, function (err) {
if (err) {
throw err;
} else {
@@ -203,11 +300,10 @@ async.series({
fn: function (deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.toBuffer(function (err, buffer) {
.toBuffer(function (err) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
@@ -217,7 +313,7 @@ async.series({
fn: function (deferred) {
sharp(fixtures.inputJpg)
.resize(width, height)
.toFile(fixtures.outputJpg, function (err) {
.toFile(outputJpg, function (err) {
if (err) {
throw err;
} else {
@@ -229,7 +325,7 @@ async.series({
defer: true,
fn: function (deferred) {
const readable = fs.createReadStream(fixtures.inputJpg);
const writable = fs.createWriteStream(fixtures.outputJpg);
const writable = fs.createWriteStream(outputJpg);
writable.on('finish', function () {
deferred.resolve();
});
@@ -242,11 +338,10 @@ async.series({
fn: function (deferred) {
sharp(fixtures.inputJpg)
.resize(width, height)
.toBuffer(function (err, buffer) {
.toBuffer(function (err) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
@@ -257,8 +352,7 @@ async.series({
sharp(inputJpgBuffer)
.resize(width, height)
.toBuffer()
.then(function (buffer) {
assert.notStrictEqual(null, buffer);
.then(function () {
deferred.resolve();
})
.catch(function (err) {
@@ -281,11 +375,10 @@ async.series({
sharp(inputJpgBuffer)
.resize(width, height)
.sharpen()
.toBuffer(function (err, buffer) {
.toBuffer(function (err) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
@@ -296,11 +389,10 @@ async.series({
sharp(inputJpgBuffer)
.resize(width, height)
.sharpen(3, 1, 3)
.toBuffer(function (err, buffer) {
.toBuffer(function (err) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
@@ -311,11 +403,10 @@ async.series({
sharp(inputJpgBuffer)
.resize(width, height)
.blur()
.toBuffer(function (err, buffer) {
.toBuffer(function (err) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
@@ -326,11 +417,10 @@ async.series({
sharp(inputJpgBuffer)
.resize(width, height)
.blur(3)
.toBuffer(function (err, buffer) {
.toBuffer(function (err) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
@@ -341,11 +431,10 @@ async.series({
sharp(inputJpgBuffer)
.resize(width, height)
.gamma()
.toBuffer(function (err, buffer) {
.toBuffer(function (err) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
@@ -356,11 +445,10 @@ async.series({
sharp(inputJpgBuffer)
.resize(width, height)
.normalise()
.toBuffer(function (err, buffer) {
.toBuffer(function (err) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
@@ -371,11 +459,10 @@ async.series({
sharp(inputJpgBuffer)
.resize(width, height)
.greyscale()
.toBuffer(function (err, buffer) {
.toBuffer(function (err) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
@@ -387,11 +474,10 @@ async.series({
.resize(width, height)
.gamma()
.greyscale()
.toBuffer(function (err, buffer) {
.toBuffer(function (err) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
@@ -402,11 +488,10 @@ async.series({
sharp(inputJpgBuffer)
.resize(width, height)
.jpeg({ progressive: true })
.toBuffer(function (err, buffer) {
.toBuffer(function (err) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
@@ -417,11 +502,10 @@ async.series({
sharp(inputJpgBuffer)
.resize(width, height)
.jpeg({ chromaSubsampling: '4:4:4' })
.toBuffer(function (err, buffer) {
.toBuffer(function (err) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
@@ -432,11 +516,10 @@ async.series({
sharp(inputJpgBuffer)
.rotate(90)
.resize(width, height)
.toBuffer(function (err, buffer) {
.toBuffer(function (err) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
@@ -447,27 +530,24 @@ async.series({
sharp.simd(false);
sharp(inputJpgBuffer)
.resize(width, height)
.toBuffer(function (err, buffer) {
.toBuffer(function (err) {
sharp.simd(true);
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add('sharp-sequentialRead', {
}).add('sharp-random-access-read', {
defer: true,
fn: function (deferred) {
sharp(inputJpgBuffer)
.sequentialRead()
sharp(inputJpgBuffer, { sequentialRead: false })
.resize(width, height)
.toBuffer(function (err, buffer) {
.toBuffer(function (err) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
@@ -480,11 +560,10 @@ async.series({
fit: 'cover',
position: sharp.strategy.entropy
})
.toBuffer(function (err, buffer) {
.toBuffer(function (err) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
@@ -497,11 +576,10 @@ async.series({
fit: 'cover',
position: sharp.strategy.attention
})
.toBuffer(function (err, buffer) {
.toBuffer(function (err) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
@@ -520,11 +598,10 @@ async.series({
fn: function (deferred) {
sharp(inputJpgBuffer)
.resize(width, height, { kernel: 'cubic' })
.toBuffer(function (err, buffer) {
.toBuffer(function (err) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
@@ -534,11 +611,10 @@ async.series({
fn: function (deferred) {
sharp(inputJpgBuffer)
.resize(width, height, { kernel: 'lanczos2' })
.toBuffer(function (err, buffer) {
.toBuffer(function (err) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
@@ -548,11 +624,10 @@ async.series({
fn: function (deferred) {
sharp(inputJpgBuffer)
.resize(width, height, { kernel: 'lanczos3' })
.toBuffer(function (err, buffer) {
.toBuffer(function (err) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
@@ -565,8 +640,9 @@ async.series({
},
// PNG
png: function (callback) {
const inputPngBuffer = fs.readFileSync(fixtures.inputPng);
const inputPngBuffer = fs.readFileSync(fixtures.inputPngAlphaPremultiplicationLarge);
const pngSuite = new Benchmark.Suite('png');
const minSamples = 64;
// jimp
pngSuite.add('jimp-buffer-buffer', {
defer: true,
@@ -576,7 +652,9 @@ async.series({
throw err;
} else {
image
.resize(width, height)
.resize(width, heightPng, jimp.RESIZE_BICUBIC)
.deflateLevel(6)
.filterType(0)
.getBuffer(jimp.MIME_PNG, function (err) {
if (err) {
throw err;
@@ -590,13 +668,15 @@ async.series({
}).add('jimp-file-file', {
defer: true,
fn: function (deferred) {
jimp.read(fixtures.inputPng, function (err, image) {
jimp.read(fixtures.inputPngAlphaPremultiplicationLarge, function (err, image) {
if (err) {
throw err;
} else {
image
.resize(width, height)
.write(fixtures.outputPng, function (err) {
.resize(width, heightPng, jimp.RESIZE_BICUBIC)
.deflateLevel(6)
.filterType(0)
.write(outputPng, function (err) {
if (err) {
throw err;
} else {
@@ -607,21 +687,74 @@ async.series({
});
}
});
// mapnik
pngSuite.add('mapnik-file-file', {
// squoosh-cli
pngSuite.add('squoosh-cli-file-file', {
defer: true,
fn: function (deferred) {
mapnik.Image.open(fixtures.inputPng, function (err, img) {
exec(`./node_modules/.bin/squoosh-cli \
--output-dir ${os.tmpdir()} \
--resize '{"enabled":true,"width":${width},"height":${heightPng},"method":"lanczos3","premultiply":true,"linearRGB":false}' \
--oxipng '{"level":1}' \
"${fixtures.inputPngAlphaPremultiplicationLarge}"`, function (err) {
if (err) {
throw err;
}
deferred.resolve();
});
}
});
// squoosh-lib (GPLv3)
pngSuite.add('squoosh-lib-buffer-buffer', {
defer: true,
fn: function (deferred) {
const pool = new squoosh.ImagePool(os.cpus().length);
const image = pool.ingestImage(inputPngBuffer);
image.decoded
.then(function () {
return image.preprocess({
resize: {
enabled: true,
width,
height: heightPng,
method: 'lanczos3',
premultiply: true,
linearRGB: false
}
});
})
.then(function () {
return image.encode({
oxipng: {
level: 1
}
});
})
.then(function () {
return pool.close();
})
.then(function () {
return image.encodedWith.oxipng;
})
.then(function () {
deferred.resolve();
});
}
});
// mapnik
mapnik && pngSuite.add('mapnik-file-file', {
defer: true,
fn: function (deferred) {
mapnik.Image.open(fixtures.inputPngAlphaPremultiplicationLarge, function (err, img) {
if (err) throw err;
img.premultiply(function (err, img) {
if (err) throw err;
img.resize(width, height, {
img.resize(width, heightPng, {
scaling_method: mapnik.imageScaling.lanczos
}, function (err, img) {
if (err) throw err;
img.demultiply(function (err, img) {
if (err) throw err;
img.save(fixtures.outputPng, 'png', function (err) {
img.save(outputPng, 'png32:f=no:z=6', function (err) {
if (err) throw err;
deferred.resolve();
});
@@ -637,13 +770,13 @@ async.series({
if (err) throw err;
img.premultiply(function (err, img) {
if (err) throw err;
img.resize(width, height, {
img.resize(width, heightPng, {
scaling_method: mapnik.imageScaling.lanczos
}, function (err, img) {
if (err) throw err;
img.demultiply(function (err, img) {
if (err) throw err;
img.encode('png', function (err) {
img.encode('png32:f=no:z=6', function (err) {
if (err) throw err;
deferred.resolve();
});
@@ -658,11 +791,15 @@ async.series({
defer: true,
fn: function (deferred) {
imagemagick.resize({
srcPath: fixtures.inputPng,
dstPath: fixtures.outputPng,
srcPath: fixtures.inputPngAlphaPremultiplicationLarge,
dstPath: outputPng,
width: width,
height: height,
filter: 'Lanczos'
height: heightPng,
filter: 'Lanczos',
customArgs: [
'-define', 'PNG:compression-level=6',
'-define', 'PNG:compression-filter=0'
]
}, function (err) {
if (err) {
throw err;
@@ -676,10 +813,12 @@ async.series({
pngSuite.add('gm-file-file', {
defer: true,
fn: function (deferred) {
gm(fixtures.inputPng)
gm(fixtures.inputPngAlphaPremultiplicationLarge)
.filter('Lanczos')
.resize(width, height)
.write(fixtures.outputPng, function (err) {
.resize(width, heightPng)
.define('PNG:compression-level=6')
.define('PNG:compression-filter=0')
.write(outputPng, function (err) {
if (err) {
throw err;
} else {
@@ -690,14 +829,15 @@ async.series({
}).add('gm-file-buffer', {
defer: true,
fn: function (deferred) {
gm(fixtures.inputPng)
gm(fixtures.inputPngAlphaPremultiplicationLarge)
.filter('Lanczos')
.resize(width, height)
.toBuffer(function (err, buffer) {
.resize(width, heightPng)
.define('PNG:compression-level=6')
.define('PNG:compression-filter=0')
.toBuffer(function (err) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
@@ -706,10 +846,12 @@ async.series({
// sharp
pngSuite.add('sharp-buffer-file', {
defer: true,
minSamples,
fn: function (deferred) {
sharp(inputPngBuffer)
.resize(width, height)
.toFile(fixtures.outputPng, function (err) {
.resize(width, heightPng)
.png({ compressionLevel: 6 })
.toFile(outputPng, function (err) {
if (err) {
throw err;
} else {
@@ -719,24 +861,27 @@ async.series({
}
}).add('sharp-buffer-buffer', {
defer: true,
minSamples,
fn: function (deferred) {
sharp(inputPngBuffer)
.resize(width, height)
.toBuffer(function (err, buffer) {
.resize(width, heightPng)
.png({ compressionLevel: 6 })
.toBuffer(function (err, data) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add('sharp-file-file', {
defer: true,
minSamples,
fn: function (deferred) {
sharp(fixtures.inputPng)
.resize(width, height)
.toFile(fixtures.outputPng, function (err) {
sharp(fixtures.inputPngAlphaPremultiplicationLarge)
.resize(width, heightPng)
.png({ compressionLevel: 6 })
.toFile(outputPng, function (err) {
if (err) {
throw err;
} else {
@@ -746,44 +891,60 @@ async.series({
}
}).add('sharp-file-buffer', {
defer: true,
minSamples,
fn: function (deferred) {
sharp(fixtures.inputPng)
.resize(width, height)
.toBuffer(function (err, buffer) {
sharp(fixtures.inputPngAlphaPremultiplicationLarge)
.resize(width, heightPng)
.png({ compressionLevel: 6 })
.toBuffer(function (err) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add('sharp-progressive', {
defer: true,
minSamples,
fn: function (deferred) {
sharp(inputPngBuffer)
.resize(width, height)
.png({ progressive: true })
.toBuffer(function (err, buffer) {
.resize(width, heightPng)
.png({ compressionLevel: 6, progressive: true })
.toBuffer(function (err) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add('sharp-adaptiveFiltering', {
defer: true,
minSamples,
fn: function (deferred) {
sharp(inputPngBuffer)
.resize(width, height)
.png({ adaptiveFiltering: true })
.toBuffer(function (err, buffer) {
.resize(width, heightPng)
.png({ adaptiveFiltering: true, compressionLevel: 6 })
.toBuffer(function (err) {
if (err) {
throw err;
} else {
deferred.resolve();
}
});
}
}).add('sharp-compressionLevel=9', {
defer: true,
minSamples,
fn: function (deferred) {
sharp(inputPngBuffer)
.resize(width, heightPng)
.png({ compressionLevel: 9 })
.toBuffer(function (err) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
@@ -803,7 +964,7 @@ async.series({
fn: function (deferred) {
sharp(inputWebPBuffer)
.resize(width, height)
.toFile(fixtures.outputWebP, function (err) {
.toFile(outputWebP, function (err) {
if (err) {
throw err;
} else {
@@ -816,11 +977,10 @@ async.series({
fn: function (deferred) {
sharp(inputWebPBuffer)
.resize(width, height)
.toBuffer(function (err, buffer) {
.toBuffer(function (err) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
@@ -830,7 +990,7 @@ async.series({
fn: function (deferred) {
sharp(fixtures.inputWebP)
.resize(width, height)
.toFile(fixtures.outputWebP, function (err) {
.toFile(outputWebP, function (err) {
if (err) {
throw err;
} else {
@@ -843,11 +1003,10 @@ async.series({
fn: function (deferred) {
sharp(fixtures.inputWebP)
.resize(width, height)
.toBuffer(function (err, buffer) {
.toBuffer(function (err) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
@@ -859,7 +1018,9 @@ async.series({
}).run();
}
}, function (err, results) {
assert(!err, err);
if (err) {
throw err;
}
Object.keys(results).forEach(function (format) {
if (results[format].toString().substr(0, 5) !== 'sharp') {
console.log('sharp was slower than ' + results[format] + ' for ' + format);

View File

@@ -1,3 +1,6 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
const imagemagick = require('imagemagick');
@@ -23,7 +26,7 @@ new Benchmark.Suite('random').add('imagemagick', {
fn: function (deferred) {
imagemagick.resize({
srcPath: fixtures.inputJpg,
dstPath: fixtures.outputJpg,
dstPath: fixtures.path('output.jpg'),
quality: 0.8,
width: randomDimension(),
height: randomDimension(),

13
test/bench/run-with-docker.sh Executable file
View File

@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -e
if ! type docker >/dev/null; then
echo "Please install docker"
exit 1
fi
BRANCH=$(git branch --show-current)
echo "Running sharp performance tests using $BRANCH branch"
docker build --build-arg "BRANCH=$BRANCH" -t sharp-test-bench .
docker run --rm -it sharp-test-bench

View File

@@ -1,6 +0,0 @@
#!/bin/sh
CPPFLAGS="--coverage" LDFLAGS="--coverage" npm rebuild
npm test
geninfo --no-external --base-directory src --output-file coverage/sharp.info build/Release/obj.target/sharp/src
genhtml --title sharp --demangle-cpp --output-directory coverage/sharp coverage/*.info

BIN
test/fixtures/16-bit-grey-alpha.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
test/fixtures/65536-uint32-limit.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 510 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

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