Compare commits

...

178 Commits

Author SHA1 Message Date
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
110 changed files with 5262 additions and 2883 deletions

View File

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

View File

@@ -42,11 +42,6 @@ You deserve to add your details to the [list of contributors](https://github.com
Any change that modifies the existing public API should be added to the relevant work-in-progress branch for inclusion in the next major release. Any change that modifies the existing public API should be added to the relevant work-in-progress branch for inclusion in the next major release.
| Release | WIP branch |
| ------: | :--------- |
| v0.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>`. Please squash your changes into a single commit using a command like `git rebase -i upstream/<wip-branch>`.
### Add a new public method ### Add a new public method
@@ -71,7 +66,7 @@ The public API is documented with [JSDoc](http://usejsdoc.org/) annotated commen
These can be converted to Markdown by running: These can be converted to Markdown by running:
```sh ```sh
npm run docs npm run docs-build
``` ```
Please include documentation updates in any Pull Request that modifies the public API. Please include documentation updates in any Pull Request that modifies the public API.

View File

@@ -1,9 +1,7 @@
--- ---
name: Feature request name: Feature request
about: Suggest an idea about: Suggest an idea
title: ''
labels: enhancement labels: enhancement
assignees: ''
--- ---

View File

@@ -1,18 +1,20 @@
--- ---
name: Installation name: Installation
about: Something went wrong **installing** sharp about: Something went wrong during either 'npm install sharp' or 'require("sharp")'
title: ''
labels: installation labels: installation
assignees: ''
--- ---
Did you see the [documentation relating to installation](https://sharp.pixelplumbing.com/en/stable/install/)? Did you see the [documentation relating to installation](https://sharp.pixelplumbing.com/install)?
Have you ensured the platform and version of Node.js used for `npm install` is the same as the platform and version of Node.js used at runtime? Have you ensured the architecture and platform of Node.js used for `npm install` is the same as the architecture and platform of Node.js used at runtime?
Are you using the latest version? Is the version currently in use as reported by `npm ls sharp` the same as the latest version as reported by `npm view sharp dist-tags.latest`?
If you are installing as a `root` or `sudo` user, have you tried with the `npm install --unsafe-perm` flag? If you are installing as a `root` or `sudo` user, have you tried with the `npm install --unsafe-perm` flag?
If you are using the `ignore-scripts` feature of `npm`, have you tried with the `npm install --ignore-scripts=false` flag?
What is the complete output of running `npm install --verbose sharp`? Have you checked this output for useful error messages? What is the complete output of running `npm install --verbose sharp`? Have you checked this output for useful error messages?
What is the output of running `npx envinfo --binaries --languages --system --utilities`? What is the output of running `npx envinfo --binaries --system`?

View File

@@ -1,20 +1,20 @@
--- ---
name: Possible bug name: Possible bug
about: Something unexpected occurred **using** sharp about: Installation of sharp was successful but then something unexpected occurred using one of its features
title: ''
labels: triage labels: triage
assignees: ''
--- ---
<!-- If this issue relates to installation, please use https://github.com/lovell/sharp/issues/new?labels=installation&template=installation.md instead. --> <!-- 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`? Are you using the latest version? Is the version currently in use as reported by `npm ls sharp` the same as the latest version as reported by `npm view sharp dist-tags.latest`?
What are the steps to reproduce? What are the steps to reproduce?
What is the expected behaviour? What is the expected behaviour?
Are you able to provide a standalone code sample, without other dependencies, that demonstrates this problem? Are you able to provide a minimal, standalone code sample, without other dependencies, that demonstrates this problem?
Are you able to provide a sample image that helps explain the problem? Are you able to provide a sample image that helps explain the problem?
What is the output of running `npx envinfo --binaries --system`?

View File

@@ -1,9 +1,7 @@
--- ---
name: Question name: Question
about: For help understanding an existing feature about: For help understanding an existing feature
title: ''
labels: question labels: question
assignees: ''
--- ---
@@ -13,6 +11,6 @@ What are you trying to achieve?
Have you searched for similar questions? Have you searched for similar questions?
Are you able to provide a standalone code sample that demonstrates this question? Are you able to provide a minimal, standalone code sample that demonstrates this question?
Are you able to provide a sample image that helps explain the question? Are you able to provide a sample image that helps explain the question?

1
.gitignore vendored
View File

@@ -15,3 +15,4 @@ vendor
.vscode/ .vscode/
package-lock.json package-lock.json
.idea .idea
.firebase

View File

@@ -1,91 +1,139 @@
matrix: jobs:
include: include:
- name: "Linux (glibc) - Node 8" - name: "Linux x64 (CentOS 7, glibc 2.17) - Node.js 10"
os: linux os: linux
dist: trusty dist: bionic
sudo: false language: shell
language: node_js before_install:
node_js: "8" - sudo docker run -dit --name sharp --env CI --env TRAVIS_TAG --env prebuild_upload --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp centos:7
- name: "Linux (glibc) - Node 10" - sudo docker exec sharp bash -c "curl -sL https://rpm.nodesource.com/setup_10.x | bash -"
- sudo docker exec sharp yum install -y gcc-c++ make git nodejs
install: sudo docker exec sharp bash -c "npm install --unsafe-perm"
script: sudo docker exec sharp bash -c "npm test"
- name: "Linux x64 (CentOS 7, glibc 2.17) - Node.js 12"
os: linux os: linux
dist: trusty dist: bionic
sudo: false language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp centos:7
- sudo docker exec sharp bash -c "curl -sL https://rpm.nodesource.com/setup_12.x | bash -"
- sudo docker exec sharp yum install -y gcc-c++ make git nodejs
install: sudo docker exec sharp bash -c "npm install --unsafe-perm"
script: sudo docker exec sharp bash -c "npm test"
- name: "Linux x64 (CentOS 7, glibc 2.17) - Node.js 14"
os: linux
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp centos:7
- sudo docker exec sharp bash -c "curl -sL https://rpm.nodesource.com/setup_14.x | bash -"
- sudo docker exec sharp yum install -y gcc-c++ make git nodejs
install: sudo docker exec sharp bash -c "npm install --unsafe-perm"
script: sudo docker exec sharp bash -c "npm test"
- name: "Linux x64 (Alpine 3.9, musl 1.1.20) - Node.js 10"
os: linux
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --env TRAVIS_TAG --env prebuild_upload --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:10.17.0-alpine3.9 # https://github.com/nodejs/docker-node/issues/1158
- sudo docker exec sharp apk add build-base git python2 --update-cache
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
- name: "Linux x64 (Alpine 3.11, musl 1.1.20) - Node.js 12"
os: linux
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:12.0-alpine
- sudo docker exec sharp apk add build-base git python2 --update-cache
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
- name: "Linux x64 (Alpine 3.11, musl 1.1.20) - Node.js 14"
os: linux
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:14.0-alpine
- sudo docker exec sharp apk add build-base git python2 --update-cache
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
- name: "Linux ARM64v8 (Debian 11, glibc 2.29) - Node.js 10"
arch: arm64
os: linux
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --env TRAVIS_TAG --env prebuild_upload --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 python2 curl"
- sudo docker exec sharp sh -c "curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -"
- sudo docker exec sharp sh -c "echo 'deb https://deb.nodesource.com/node_10.x sid main' >/etc/apt/sources.list.d/nodesource.list"
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs=10.*"
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
- name: "Linux ARM64v8 (Debian 11, glibc 2.29) - Node.js 12"
arch: arm64
os: linux
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --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 python2 curl"
- sudo docker exec sharp sh -c "curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -"
- sudo docker exec sharp sh -c "echo 'deb https://deb.nodesource.com/node_12.x sid main' >/etc/apt/sources.list.d/nodesource.list"
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs"
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
- name: "Linux ARM64v8 (Debian 11, glibc 2.29) - Node.js 14"
arch: arm64
os: linux
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --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 python2 curl"
- sudo docker exec sharp sh -c "curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -"
- sudo docker exec sharp sh -c "echo 'deb https://deb.nodesource.com/node_14.x sid main' >/etc/apt/sources.list.d/nodesource.list"
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs"
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
- name: "macOS (10.13) - Node.js 10"
os: osx
osx_image: xcode10.1
language: node_js language: node_js
node_js: "10" node_js: "10"
- name: "Linux (glibc) - Node 12"
os: linux - name: "macOS (10.13) - Node.js 12"
dist: trusty os: osx
sudo: false osx_image: xcode10.1
language: node_js language: node_js
node_js: "12" node_js: "12"
- name: "Linux (glibc) - Node 13" before_install: unset prebuild_upload
- name: "macOS (10.13) - Node.js 14"
os: osx
osx_image: xcode10.1
language: node_js
node_js: "14"
before_install: unset prebuild_upload
- name: "Unit test coverage report"
os: linux os: linux
dist: trusty dist: bionic
sudo: false
language: node_js language: node_js
node_js: "13" node_js: "13"
before_install: unset prebuild_upload
after_success: after_success:
- npm install coveralls - npm install coveralls
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js - 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: "Linux (musl) - Node 13"
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:13.0-alpine
- sudo docker exec sharp apk add build-base git python2 --update-cache
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
- name: "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"
- name: "OS X - Node 13"
os: osx
osx_image: xcode10
language: node_js
node_js: "13"
cache: cache:
npm: false npm: false

View File

@@ -1,10 +1,6 @@
# sharp # sharp
<img src="https://raw.githubusercontent.com/lovell/sharp/master/docs/image/sharp-logo.svg?sanitize=true" width="160" height="160" alt="sharp logo" align="right"> <img src="https://cdn.jsdelivr.net/gh/lovell/sharp@master/docs/image/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
```sh
npm install sharp
```
The typical use case for this high speed Node.js module The typical use case for this high speed Node.js module
is to convert large images in common formats to is to convert large images in common formats to
@@ -20,12 +16,15 @@ Lanczos resampling ensures quality is not sacrificed for speed.
As well as image resizing, operations such as As well as image resizing, operations such as
rotation, extraction, compositing and gamma correction are available. rotation, extraction, compositing and gamma correction are available.
Most modern 64-bit OS X, Windows and Linux systems running Most modern macOS, Windows and Linux systems running Node.js v10.16.0+
Node versions 8, 10, 12 and 13
do not require any additional install or runtime dependencies. do not require any additional install or runtime dependencies.
## Examples ## Examples
```sh
npm install sharp
```
```javascript ```javascript
const sharp = require('sharp'); const sharp = require('sharp');
``` ```
@@ -86,14 +85,15 @@ readableStream
``` ```
[![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.svg?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master) [![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.svg?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master)
[![N-API v3](https://img.shields.io/badge/N--API-v3-green.svg)](https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api_version_matrix)
### Documentation ### Documentation
Visit [sharp.pixelplumbing.com](https://sharp.pixelplumbing.com/) for complete Visit [sharp.pixelplumbing.com](https://sharp.pixelplumbing.com/) for complete
[installation instructions](https://sharp.pixelplumbing.com/page/install), [installation instructions](https://sharp.pixelplumbing.com/install),
[API documentation](https://sharp.pixelplumbing.com/page/api), [API documentation](https://sharp.pixelplumbing.com/api-constructor),
[benchmark tests](https://sharp.pixelplumbing.com/page/performance) and [benchmark tests](https://sharp.pixelplumbing.com/performance) and
[changelog](https://sharp.pixelplumbing.com/page/changelog). [changelog](https://sharp.pixelplumbing.com/changelog).
### Contributing ### Contributing
@@ -102,7 +102,7 @@ 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, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@@ -1,16 +1,26 @@
os: Visual Studio 2015 os: Visual Studio 2017
version: "{build}" version: "{build}"
build: off build: off
platform: x64
environment: environment:
matrix: matrix:
- nodejs_version: "8"
- nodejs_version: "10" - nodejs_version: "10"
platform: x86
- nodejs_version: "10"
platform: x64
- nodejs_version: "12" - nodejs_version: "12"
- nodejs_version: "13" platform: x86
prebuild_upload: ""
- nodejs_version: "12"
platform: x64
prebuild_upload: ""
- nodejs_version: "14.2.0"
platform: x86
prebuild_upload: ""
- nodejs_version: "14"
platform: x64
prebuild_upload: ""
install: install:
- ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) x64 - ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) $env:platform
- npm install -g npm@6
- npm install - npm install
test_script: test_script:
- npm test - npm test

View File

@@ -1,4 +1,8 @@
{ {
'variables': {
'vips_version': '<!(node -p "require(\'./lib/libvips\').minimumLibvipsVersion")',
'sharp_vendor_dir': '<(module_root_dir)/vendor/<(vips_version)'
},
'targets': [{ 'targets': [{
'target_name': 'libvips-cpp', 'target_name': 'libvips-cpp',
'conditions': [ 'conditions': [
@@ -11,24 +15,43 @@
], ],
'sources': [ 'sources': [
'src/libvips/cplusplus/VError.cpp', 'src/libvips/cplusplus/VError.cpp',
'src/libvips/cplusplus/VConnection.cpp',
'src/libvips/cplusplus/VInterpolate.cpp', 'src/libvips/cplusplus/VInterpolate.cpp',
'src/libvips/cplusplus/VImage.cpp' 'src/libvips/cplusplus/VImage.cpp'
], ],
'include_dirs': [ 'include_dirs': [
'vendor/include', '<(sharp_vendor_dir)/include',
'vendor/include/glib-2.0', '<(sharp_vendor_dir)/include/glib-2.0',
'vendor/lib/glib-2.0/include' '<(sharp_vendor_dir)/lib/glib-2.0/include'
],
'libraries': [
'../vendor/lib/libvips.lib',
'../vendor/lib/libglib-2.0.lib',
'../vendor/lib/libgobject-2.0.lib'
], ],
'link_settings': {
'library_dirs': ['<(sharp_vendor_dir)/lib'],
'libraries': [
'libvips.lib',
'libglib-2.0.lib',
'libgobject-2.0.lib'
],
},
'configurations': { 'configurations': {
'Release': { 'Release': {
'msvs_settings': { 'msvs_settings': {
'VCCLCompilerTool': { 'VCCLCompilerTool': {
'ExceptionHandling': 1 'ExceptionHandling': 1,
'WholeProgramOptimization': 'true'
},
'VCLibrarianTool': {
'AdditionalOptions': [
'/LTCG:INCREMENTAL'
]
},
'VCLinkerTool': {
'ImageHasSafeExceptionHandlers': 'false',
'OptimizeReferences': 2,
'EnableCOMDATFolding': 2,
'LinkIncremental': 1,
'AdditionalOptions': [
'/LTCG:INCREMENTAL'
]
} }
}, },
'msvs_disabled_warnings': [ 'msvs_disabled_warnings': [
@@ -43,15 +66,19 @@
] ]
}, { }, {
'target_name': 'sharp', 'target_name': 'sharp',
'defines': [
'NAPI_VERSION=3'
],
'dependencies': [ 'dependencies': [
'<!(node -p "require(\'node-addon-api\').gyp")',
'libvips-cpp' 'libvips-cpp'
], ],
'variables': { 'variables': {
'runtime_link%': 'shared', 'runtime_link%': 'shared',
'conditions': [ 'conditions': [
['OS != "win"', { ['OS != "win"', {
'pkg_config_path': '<!(node -e "console.log(require(\'./lib/libvips\').pkgConfigPath())")', 'pkg_config_path': '<!(node -p "require(\'./lib/libvips\').pkgConfigPath()")',
'use_global_libvips': '<!(node -e "console.log(Boolean(require(\'./lib/libvips\').useGlobalLibvips()).toString())")' 'use_global_libvips': '<!(node -p "Boolean(require(\'./lib/libvips\').useGlobalLibvips()).toString()")'
}, { }, {
'pkg_config_path': '', 'pkg_config_path': '',
'use_global_libvips': '' 'use_global_libvips': ''
@@ -64,11 +91,11 @@
'src/stats.cc', 'src/stats.cc',
'src/operations.cc', 'src/operations.cc',
'src/pipeline.cc', 'src/pipeline.cc',
'src/sharp.cc', 'src/utilities.cc',
'src/utilities.cc' 'src/sharp.cc'
], ],
'include_dirs': [ 'include_dirs': [
'<!(node -e "require(\'nan\')")' '<!(node -p "require(\'node-addon-api\').include_dir")',
], ],
'conditions': [ 'conditions': [
['use_global_libvips == "true"', { ['use_global_libvips == "true"', {
@@ -90,9 +117,9 @@
}, { }, {
# Use pre-built libvips stored locally within node_modules # Use pre-built libvips stored locally within node_modules
'include_dirs': [ 'include_dirs': [
'vendor/include', '<(sharp_vendor_dir)/include',
'vendor/include/glib-2.0', '<(sharp_vendor_dir)/include/glib-2.0',
'vendor/lib/glib-2.0/include' '<(sharp_vendor_dir)/lib/glib-2.0/include'
], ],
'conditions': [ 'conditions': [
['OS == "win"', { ['OS == "win"', {
@@ -100,65 +127,45 @@
'_ALLOW_KEYWORD_MACROS', '_ALLOW_KEYWORD_MACROS',
'_FILE_OFFSET_BITS=64' '_FILE_OFFSET_BITS=64'
], ],
'libraries': [ 'link_settings': {
'../vendor/lib/libvips.lib', 'library_dirs': ['<(sharp_vendor_dir)/lib'],
'../vendor/lib/libglib-2.0.lib', 'libraries': [
'../vendor/lib/libgobject-2.0.lib' 'libvips.lib',
] 'libglib-2.0.lib',
'libgobject-2.0.lib'
]
}
}], }],
['OS == "mac"', { ['OS == "mac"', {
'libraries': [ 'link_settings': {
'../vendor/lib/libvips-cpp.42.dylib', 'library_dirs': ['<(sharp_vendor_dir)/lib'],
'../vendor/lib/libvips.42.dylib', 'libraries': [
'../vendor/lib/libglib-2.0.0.dylib', 'libvips-cpp.42.dylib',
'../vendor/lib/libgobject-2.0.0.dylib', 'libvips.42.dylib'
# Ensure runtime linking is relative to sharp.node ]
'-rpath \'@loader_path/../../vendor/lib\'' },
] 'xcode_settings': {
'OTHER_LDFLAGS': [
# Ensure runtime linking is relative to sharp.node
'-Wl,-rpath,\'@loader_path/../../vendor/<(vips_version)/lib\''
]
}
}], }],
['OS == "linux"', { ['OS == "linux"', {
'defines': [ 'defines': [
'_GLIBCXX_USE_CXX11_ABI=0' '_GLIBCXX_USE_CXX11_ABI=0'
], ],
'libraries': [ 'link_settings': {
'../vendor/lib/libvips-cpp.so', 'library_dirs': ['<(sharp_vendor_dir)/lib'],
'../vendor/lib/libvips.so', 'libraries': [
'../vendor/lib/libglib-2.0.so', '-l:libvips-cpp.so.42',
'../vendor/lib/libgobject-2.0.so', '-l:libvips.so.42'
# Dependencies of dependencies, included for openSUSE support ],
'../vendor/lib/libcairo.so', 'ldflags': [
'../vendor/lib/libcroco-0.6.so', # Ensure runtime linking is relative to sharp.node
'../vendor/lib/libexif.so', '-Wl,-s -Wl,--disable-new-dtags -Wl,-rpath=\'$$ORIGIN/../../vendor/<(vips_version)/lib\''
'../vendor/lib/libexpat.so', ]
'../vendor/lib/libffi.so', }
'../vendor/lib/libfontconfig.so',
'../vendor/lib/libfreetype.so',
'../vendor/lib/libfribidi.so',
'../vendor/lib/libgdk_pixbuf-2.0.so',
'../vendor/lib/libgif.so',
'../vendor/lib/libgio-2.0.so',
'../vendor/lib/libgmodule-2.0.so',
'../vendor/lib/libgsf-1.so',
'../vendor/lib/libgthread-2.0.so',
'../vendor/lib/libharfbuzz.so',
'../vendor/lib/libjpeg.so',
'../vendor/lib/liblcms2.so',
'../vendor/lib/liborc-0.4.so',
'../vendor/lib/libpango-1.0.so',
'../vendor/lib/libpangocairo-1.0.so',
'../vendor/lib/libpangoft2-1.0.so',
'../vendor/lib/libpixman-1.so',
'../vendor/lib/libpng.so',
'../vendor/lib/librsvg-2.so',
'../vendor/lib/libtiff.so',
'../vendor/lib/libwebp.so',
'../vendor/lib/libwebpdemux.so',
'../vendor/lib/libwebpmux.so',
'../vendor/lib/libxml2.so',
'../vendor/lib/libz.so',
# Ensure runtime linking is relative to sharp.node
'-Wl,--disable-new-dtags -Wl,-rpath=\'$${ORIGIN}/../../vendor/lib\''
]
}] }]
] ]
}] }]
@@ -171,8 +178,7 @@
], ],
'xcode_settings': { 'xcode_settings': {
'CLANG_CXX_LANGUAGE_STANDARD': 'c++11', 'CLANG_CXX_LANGUAGE_STANDARD': 'c++11',
'CLANG_CXX_LIBRARY': 'libc++', 'MACOSX_DEPLOYMENT_TARGET': '10.9',
'MACOSX_DEPLOYMENT_TARGET': '10.7',
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
'GCC_ENABLE_CPP_RTTI': 'YES', 'GCC_ENABLE_CPP_RTTI': 'YES',
'OTHER_CPLUSPLUSFLAGS': [ 'OTHER_CPLUSPLUSFLAGS': [
@@ -189,10 +195,30 @@
'-Wno-cast-function-type' '-Wno-cast-function-type'
] ]
}], }],
['target_arch == "arm"', {
'cflags_cc': [
'-Wno-psabi'
]
}],
['OS == "win"', { ['OS == "win"', {
'msvs_settings': { 'msvs_settings': {
'VCCLCompilerTool': { 'VCCLCompilerTool': {
'ExceptionHandling': 1 'ExceptionHandling': 1,
'WholeProgramOptimization': 'true'
},
'VCLibrarianTool': {
'AdditionalOptions': [
'/LTCG:INCREMENTAL'
]
},
'VCLinkerTool': {
'ImageHasSafeExceptionHandlers': 'false',
'OptimizeReferences': 2,
'EnableCOMDATFolding': 2,
'LinkIncremental': 1,
'AdditionalOptions': [
'/LTCG:INCREMENTAL'
]
} }
}, },
'msvs_disabled_warnings': [ 'msvs_disabled_warnings': [

80
docs/README.md Normal file
View File

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

View File

@@ -32,22 +32,27 @@ sharp('rgb.jpg')
Returns **Sharp** Returns **Sharp**
**Meta**
- **since**: 0.21.2
## extractChannel ## extractChannel
Extract a single channel from a multi-channel image. Extract a single channel from a multi-channel image.
### Parameters ### Parameters
- `channel` **([Number][1] \| [String][2])** zero-indexed band number to extract, or `red`, `green` or `blue` as alternative to `0`, `1` or `2` respectively. - `channel` **([number][1] \| [string][2])** zero-indexed channel/band number to extract, or `red`, `green`, `blue` or `alpha`.
### Examples ### Examples
```javascript ```javascript
sharp(input) sharp(input)
.extractChannel('green') .extractChannel('green')
.toFile('input_green.jpg', function(err, info) { .toColourspace('b-w')
.toFile('green.jpg', function(err, info) {
// info.channels === 1 // info.channels === 1
// input_green.jpg contains the green channel of the input image // green.jpg is a greyscale image containing the green channel of the input
}); });
``` ```
@@ -70,7 +75,7 @@ For raw pixel input, the `options` object should contain a `raw` attribute, whic
### Parameters ### Parameters
- `images` **([Array][4]&lt;([String][2] \| [Buffer][5])> | [String][2] \| [Buffer][5])** one or more images (file paths, Buffers). - `images` **([Array][4]&lt;([string][2] \| [Buffer][5])> | [string][2] \| [Buffer][5])** one or more images (file paths, Buffers).
- `options` **[Object][6]** image options, see `sharp()` constructor. - `options` **[Object][6]** image options, see `sharp()` constructor.
@@ -84,7 +89,7 @@ Perform a bitwise boolean operation on all input image channels (bands) to produ
### Parameters ### Parameters
- `boolOp` **[String][2]** one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively. - `boolOp` **[string][2]** one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
### Examples ### Examples

View File

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

View File

@@ -20,7 +20,7 @@ and [https://www.cairographics.org/operators/][2]
### Parameters ### Parameters
- `images` **[Array][3]&lt;[Object][4]>** Ordered list of images to composite - `images` **[Array][3]&lt;[Object][4]>** Ordered list of images to composite
- `images[].input` **([Buffer][5] \| [String][6])?** Buffer containing image data, String containing the path to an image file, or Create object (see bellow) - `images[].input` **([Buffer][5] \| [String][6])?** Buffer containing image data, String containing the path to an image file, or Create object (see below)
- `images[].input.create` **[Object][4]?** describes a blank overlay to be created. - `images[].input.create` **[Object][4]?** describes a blank overlay to be created.
- `images[].input.create.width` **[Number][7]?** - `images[].input.create.width` **[Number][7]?**
- `images[].input.create.height` **[Number][7]?** - `images[].input.create.height` **[Number][7]?**
@@ -61,6 +61,10 @@ sharp('input.png')
Returns **Sharp** Returns **Sharp**
**Meta**
- **since**: 0.22.0
[1]: https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode [1]: https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode
[2]: https://www.cairographics.org/operators/ [2]: https://www.cairographics.org/operators/

View File

@@ -2,27 +2,43 @@
## Sharp ## Sharp
Constructor factory to create an instance of `sharp`, to which further methods are chained.
JPEG, PNG, WebP or TIFF format image data can be streamed out from this object.
When using Stream based output, derived attributes are available from the `info` event.
Non-critical problems encountered during processing are emitted as `warning` events.
Implements the [stream.Duplex][1] class.
### Parameters ### Parameters
- `input` **([Buffer][1] \| [String][2])?** if present, can be - `input` **([Buffer][2] \| [string][3])?** if present, can be
a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
a String containing the filesystem path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file. a String containing the filesystem path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present. 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` **[Object][4]?** if present, is an Object with optional attributes.
- `options.failOnError` **[Boolean][4]** by default halt processing and raise an error when loading invalid images. - `options.failOnError` **[boolean][5]** by default halt processing and raise an error when loading invalid images.
Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid. (optional, default `true`) 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.limitInputPixels` **([number][6] \| [boolean][5])** Do not process input images where the number of pixels
- `options.pages` **[Number][5]** number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages. (optional, default `1`) (width x height) exceeds this limit. Assumes image dimensions contained in the input metadata can be trusted.
- `options.page` **[Number][5]** page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based. (optional, default `0`) An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF). (optional, default `268402689`)
- `options.raw` **[Object][3]?** describes raw pixel input image data. See `raw()` for pixel ordering. - `options.sequentialRead` **[boolean][5]** Set this to `true` to use sequential rather than random access where possible.
- `options.raw.width` **[Number][5]?** This can reduce memory usage and might improve performance on some systems. (optional, default `false`)
- `options.raw.height` **[Number][5]?** - `options.density` **[number][6]** number representing the DPI for vector images in the range 1 to 100000. (optional, default `72`)
- `options.raw.channels` **[Number][5]?** 1-4 - `options.pages` **[number][6]** number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages. (optional, default `1`)
- `options.create` **[Object][3]?** describes a new image to be created. - `options.page` **[number][6]** page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based. (optional, default `0`)
- `options.create.width` **[Number][5]?** - `options.level` **[number][6]** level to extract from a multi-level input (OpenSlide), zero based. (optional, default `0`)
- `options.create.height` **[Number][5]?** - `options.animated` **[boolean][5]** Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`). (optional, default `false`)
- `options.create.channels` **[Number][5]?** 3-4 - `options.raw` **[Object][4]?** describes raw pixel input image data. See `raw()` for pixel ordering.
- `options.create.background` **([String][2] \| [Object][3])?** parsed by the [color][6] module to extract values for red, green, blue and alpha. - `options.raw.width` **[number][6]?**
- `options.raw.height` **[number][6]?**
- `options.raw.channels` **[number][6]?** 1-4
- `options.create` **[Object][4]?** describes a new image to be created.
- `options.create.width` **[number][6]?**
- `options.create.height` **[number][6]?**
- `options.create.channels` **[number][6]?** 3-4
- `options.create.background` **([string][3] \| [Object][4])?** parsed by the [color][7] module to extract values for red, green, blue and alpha.
### Examples ### Examples
@@ -63,59 +79,97 @@ sharp({
.then( ... ); .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 ```javascript
console.log(sharp.format); // Convert an animated GIF to an animated WebP
await sharp('in.gif', { animated: true }).toFile('out.webp');
``` ```
Returns **[Object][3]** - Throws **[Error][8]** Invalid parameters
### versions Returns **[Sharp][9]**
An Object containing the version numbers of libvips and its dependencies. ## clone
#### Examples Take a "snapshot" of the Sharp instance, returning a new instance.
Cloned instances inherit the input of their parent instance.
```javascript This allows multiple output Streams and therefore multiple processing pipelines to share a single input Stream.
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 ### Examples
```javascript ```javascript
sharp.queue.on('change', function(queueLength) { const pipeline = sharp().rotate();
console.log('Queue contains ' + queueLength + ' task(s)'); 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
``` ```
[1]: https://nodejs.org/api/buffer.html ```javascript
// Create a pipeline that will download an image, resize it and format it to different files
// Using Promises to know when the pipeline is complete
const fs = require("fs");
const got = require("got");
const sharpStream = sharp({
failOnError: false
});
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String const promises = [];
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object promises.push(
sharpStream
.clone()
.jpeg({ quality: 100 })
.toFile("originalFile.jpg")
);
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean promises.push(
sharpStream
.clone()
.resize({ width: 500 })
.jpeg({ quality: 80 })
.toFile("optimized-500.jpg")
);
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number promises.push(
sharpStream
.clone()
.resize({ width: 500 })
.webp({ quality: 80 })
.toFile("optimized-500.webp")
);
[6]: https://www.npmjs.org/package/color // https://github.com/sindresorhus/got#gotstreamurl-options
got.stream("https://www.example.com/some-file.jpg").pipe(sharpStream);
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error 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) {}
});
```
[8]: #sharp Returns **[Sharp][9]**
[1]: http://nodejs.org/api/stream.html#stream_class_stream_duplex
[2]: https://nodejs.org/api/buffer.html
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[7]: https://www.npmjs.org/package/color
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
[9]: #sharp

View File

@@ -1,24 +1,5 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. --> <!-- 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
Fast access to (uncached) image metadata without decoding any compressed image data. Fast access to (uncached) image metadata without decoding any compressed image data.
@@ -35,8 +16,11 @@ A `Promise` is returned when `callback` is not provided.
- `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 - `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 - `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 - `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.
- `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 - `pagePrimary`: Number of the primary page in a HEIF image
- `levels`: Details of each level in a multi-level image provided as an array of objects, requires libvips compiled with support for OpenSlide
- `hasProfile`: Boolean indicating the presence of an embedded ICC profile - `hasProfile`: Boolean indicating the presence of an embedded ICC profile
- `hasAlpha`: Boolean indicating the presence of an alpha transparency channel - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
- `orientation`: Number value of the EXIF Orientation header, if present - `orientation`: Number value of the EXIF Orientation header, if present
@@ -85,8 +69,10 @@ A `Promise` is returned when `callback` is not provided.
- `minY` (y-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) - `maxX` (x-coordinate of one of the pixel where the maximum lies)
- `maxY` (y-coordinate of one of the pixel where the maximum lies) - `maxY` (y-coordinate of one of the pixel where the maximum lies)
- `isOpaque`: Value to identify if the image is opaque or transparent, based on the presence and use of alpha channel - `isOpaque`: Is the image fully opaque? Will be `true` if the image has no alpha channel or if every pixel is fully opaque.
- `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental) - `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental)
- `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any (experimental)
- `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram (experimental)
### Parameters ### Parameters
@@ -103,39 +89,16 @@ image
}); });
``` ```
```javascript
const { entropy, sharpness, dominant } = await sharp(input).stats();
const { r, g, b } = dominant;
```
Returns **[Promise][5]&lt;[Object][6]>** Returns **[Promise][5]&lt;[Object][6]>**
## limitInputPixels [1]: https://libvips.github.io/libvips/API/current/VipsImage.html#VipsInterpretation
Do not process input images where the number of pixels (width x height) exceeds this limit. [2]: https://libvips.github.io/libvips/API/current/VipsImage.html#VipsBandFormat
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 [3]: https://www.npmjs.com/package/icc
@@ -144,9 +107,3 @@ Returns **Sharp**
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise [5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object [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

View File

@@ -21,9 +21,9 @@ for example `rotate(x).extract(y)` will produce a different result to `extract(y
### Parameters ### Parameters
- `angle` **[Number][1]** angle of rotation. (optional, default `auto`) - `angle` **[number][1]** angle of rotation. (optional, default `auto`)
- `options` **[Object][2]?** if present, is an Object with optional attributes. - `options` **[Object][2]?** if present, is an Object with optional attributes.
- `options.background` **([String][3] \| [Object][2])** parsed by the [color][4] module to extract values for red, green, blue and alpha. (optional, default `"#000000"`) - `options.background` **([string][3] \| [Object][2])** parsed by the [color][4] module to extract values for red, green, blue and alpha. (optional, default `"#000000"`)
### Examples ### Examples
@@ -74,9 +74,9 @@ Separate control over the level of sharpening in "flat" and "jagged" areas is av
### Parameters ### Parameters
- `sigma` **[Number][1]?** the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`. - `sigma` **[number][1]?** the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
- `flat` **[Number][1]** the level of sharpening to apply to "flat" areas. (optional, default `1.0`) - `flat` **[number][1]** the level of sharpening to apply to "flat" areas. (optional, default `1.0`)
- `jagged` **[Number][1]** the level of sharpening to apply to "jagged" areas. (optional, default `2.0`) - `jagged` **[number][1]** the level of sharpening to apply to "jagged" areas. (optional, default `2.0`)
- Throws **[Error][5]** Invalid parameters - Throws **[Error][5]** Invalid parameters
@@ -90,7 +90,7 @@ When used without parameters the default window is 3x3.
### Parameters ### Parameters
- `size` **[Number][1]** square mask size: size x size (optional, default `3`) - `size` **[number][1]** square mask size: size x size (optional, default `3`)
- Throws **[Error][5]** Invalid parameters - Throws **[Error][5]** Invalid parameters
@@ -105,7 +105,7 @@ When a `sigma` is provided, performs a slower, more accurate Gaussian blur.
### Parameters ### Parameters
- `sigma` **[Number][1]?** a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`. - `sigma` **[number][1]?** a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
- Throws **[Error][5]** Invalid parameters - Throws **[Error][5]** Invalid parameters
@@ -119,7 +119,7 @@ Merge alpha transparency channel, if any, with a background.
### Parameters ### Parameters
- `options` **[Object][2]?** - `options` **[Object][2]?**
- `options.background` **([String][3] \| [Object][2])** background colour, parsed by the [color][4] module, defaults to black. (optional, default `{r:0,g:0,b:0}`) - `options.background` **([string][3] \| [Object][2])** background colour, parsed by the [color][4] module, defaults to black. (optional, default `{r:0,g:0,b:0}`)
Returns **Sharp** Returns **Sharp**
@@ -135,8 +135,8 @@ Supply a second argument to use a different output gamma value, otherwise the fi
### Parameters ### Parameters
- `gamma` **[Number][1]** value between 1.0 and 3.0. (optional, default `2.2`) - `gamma` **[number][1]** value between 1.0 and 3.0. (optional, default `2.2`)
- `gammaOut` **[Number][1]?** value between 1.0 and 3.0. (optional, defaults to same as `gamma`) - `gammaOut` **[number][1]?** value between 1.0 and 3.0. (optional, defaults to same as `gamma`)
- Throws **[Error][5]** Invalid parameters - Throws **[Error][5]** Invalid parameters
@@ -180,11 +180,11 @@ Convolve the image with the specified kernel.
### Parameters ### Parameters
- `kernel` **[Object][2]** - `kernel` **[Object][2]**
- `kernel.width` **[Number][1]** width of the kernel in pixels. - `kernel.width` **[number][1]** width of the kernel in pixels.
- `kernel.height` **[Number][1]** width of the kernel in pixels. - `kernel.height` **[number][1]** width of the kernel in pixels.
- `kernel.kernel` **[Array][7]&lt;[Number][1]>** Array of length `width*height` containing the kernel values. - `kernel.kernel` **[Array][7]&lt;[number][1]>** Array of length `width*height` containing the kernel values.
- `kernel.scale` **[Number][1]** the scale of the kernel in pixels. (optional, default `sum`) - `kernel.scale` **[number][1]** the scale of the kernel in pixels. (optional, default `sum`)
- `kernel.offset` **[Number][1]** the offset of the kernel in pixels. (optional, default `0`) - `kernel.offset` **[number][1]** the offset of the kernel in pixels. (optional, default `0`)
### Examples ### Examples
@@ -212,7 +212,7 @@ Any pixel value greather than or equal to the threshold value will be set to 255
### Parameters ### Parameters
- `threshold` **[Number][1]** a value in the range 0-255 representing the level at which the threshold will be applied. (optional, default `128`) - `threshold` **[number][1]** a value in the range 0-255 representing the level at which the threshold will be applied. (optional, default `128`)
- `options` **[Object][2]?** - `options` **[Object][2]?**
- `options.greyscale` **[Boolean][6]** convert to single channel greyscale. (optional, default `true`) - `options.greyscale` **[Boolean][6]** convert to single channel greyscale. (optional, default `true`)
- `options.grayscale` **[Boolean][6]** alternative spelling for greyscale. (optional, default `true`) - `options.grayscale` **[Boolean][6]** alternative spelling for greyscale. (optional, default `true`)
@@ -231,13 +231,13 @@ the selected bitwise boolean `operation` between the corresponding pixels of the
### Parameters ### Parameters
- `operand` **([Buffer][8] \| [String][3])** Buffer containing image data or String containing the path to an image file. - `operand` **([Buffer][8] \| [string][3])** Buffer containing image data or string containing the path to an image file.
- `operator` **[String][3]** one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively. - `operator` **[string][3]** one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
- `options` **[Object][2]?** - `options` **[Object][2]?**
- `options.raw` **[Object][2]?** describes operand when using raw pixel data. - `options.raw` **[Object][2]?** describes operand when using raw pixel data.
- `options.raw.width` **[Number][1]?** - `options.raw.width` **[number][1]?**
- `options.raw.height` **[Number][1]?** - `options.raw.height` **[number][1]?**
- `options.raw.channels` **[Number][1]?** - `options.raw.channels` **[number][1]?**
- Throws **[Error][5]** Invalid parameters - Throws **[Error][5]** Invalid parameters
@@ -250,8 +250,8 @@ Apply the linear formula a \* input + b to the image (levels adjustment)
### Parameters ### Parameters
- `a` **[Number][1]** multiplier (optional, default `1.0`) - `a` **[number][1]** multiplier (optional, default `1.0`)
- `b` **[Number][1]** offset (optional, default `0.0`) - `b` **[number][1]** offset (optional, default `0.0`)
- Throws **[Error][5]** Invalid parameters - Throws **[Error][5]** Invalid parameters
@@ -264,8 +264,7 @@ Recomb the image with the specified matrix.
### Parameters ### Parameters
- `inputMatrix` - `inputMatrix` **[Array][7]&lt;[Array][7]&lt;[number][1]>>** 3x3 Recombination matrix
- `3x3` **[Array][7]&lt;[Array][7]&lt;[Number][1]>>** Recombination matrix
### Examples ### Examples
@@ -287,6 +286,10 @@ sharp(input)
Returns **Sharp** Returns **Sharp**
**Meta**
- **since**: 0.21.1
## modulate ## modulate
Transforms the image using brightness, saturation and hue rotation. Transforms the image using brightness, saturation and hue rotation.
@@ -294,9 +297,9 @@ Transforms the image using brightness, saturation and hue rotation.
### Parameters ### Parameters
- `options` **[Object][2]?** - `options` **[Object][2]?**
- `options.brightness` **[Number][1]?** Brightness multiplier - `options.brightness` **[number][1]?** Brightness multiplier
- `options.saturation` **[Number][1]?** Saturation multiplier - `options.saturation` **[number][1]?** Saturation multiplier
- `options.hue` **[Number][1]?** Degrees for hue rotation - `options.hue` **[number][1]?** Degrees for hue rotation
### Examples ### Examples
@@ -322,6 +325,10 @@ sharp(input)
Returns **Sharp** Returns **Sharp**
**Meta**
- **since**: 0.22.1
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number [1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object [2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object

View File

@@ -15,7 +15,7 @@ A `Promise` is returned when `callback` is not provided.
### Parameters ### Parameters
- `fileOut` **[String][2]** the path to write the image data to. - `fileOut` **[string][2]** the path to write the image data to.
- `callback` **[Function][3]?** called on completion with two arguments `(err, info)`. - `callback` **[Function][3]?** called on completion with two arguments `(err, info)`.
`info` contains the output image `format`, `size` (bytes), `width`, `height`, `info` contains the output image `format`, `size` (bytes), `width`, `height`,
`channels` and `premultiplied` (indicating if premultiplication was used). `channels` and `premultiplied` (indicating if premultiplication was used).
@@ -62,7 +62,7 @@ A `Promise` is returned when `callback` is not provided.
### Parameters ### Parameters
- `options` **[Object][6]?** - `options` **[Object][6]?**
- `options.resolveWithObject` **[Boolean][7]?** Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`. - `options.resolveWithObject` **[boolean][7]?** Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
- `callback` **[Function][3]?** - `callback` **[Function][3]?**
### Examples ### Examples
@@ -91,13 +91,17 @@ Returns **[Promise][5]&lt;[Buffer][8]>** when no callback is provided
## withMetadata ## withMetadata
Include all metadata (EXIF, XMP, IPTC) from the input image in the output image. Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
The default behaviour, when `withMetadata` is not used, is to strip all metadata and convert to the device-independent sRGB colour space. This will also convert to and add a web-friendly sRGB ICC profile unless a custom
This will also convert to and add a web-friendly sRGB ICC profile. output profile is provided.
The default behaviour, when `withMetadata` is not used, is to convert to the device-independent
sRGB colour space and strip all metadata, including the removal of any ICC profile.
### Parameters ### Parameters
- `options` **[Object][6]?** - `options` **[Object][6]?**
- `options.orientation` **[Number][9]?** value between 1 and 8, used to update the EXIF `Orientation` tag. - `options.orientation` **[number][9]?** value between 1 and 8, used to update the EXIF `Orientation` tag.
- `options.icc` **[string][2]?** filesystem path to output ICC profile, defaults to sRGB.
### Examples ### Examples
@@ -112,25 +116,49 @@ sharp('input.jpg')
Returns **Sharp** Returns **Sharp**
## toFormat
Force output to a given format.
### Parameters
- `format` **([string][2] \| [Object][6])** as a string or an Object with an 'id' attribute
- `options` **[Object][6]** output options
### Examples
```javascript
// Convert any input to PNG output
const data = await sharp(input)
.toFormat('png')
.toBuffer();
```
- Throws **[Error][4]** unsupported format or options
Returns **Sharp**
## jpeg ## jpeg
Use these JPEG options for output image. Use these JPEG options for output image.
Some of these options require the use of a globally-installed libvips compiled with support for mozjpeg.
### Parameters ### Parameters
- `options` **[Object][6]?** output options - `options` **[Object][6]?** output options
- `options.quality` **[Number][9]** quality, integer 1-100 (optional, default `80`) - `options.quality` **[number][9]** quality, integer 1-100 (optional, default `80`)
- `options.progressive` **[Boolean][7]** use progressive (interlace) scan (optional, default `false`) - `options.progressive` **[boolean][7]** use progressive (interlace) scan (optional, default `false`)
- `options.chromaSubsampling` **[String][2]** set to '4:4:4' to prevent chroma subsampling when quality &lt;= 90 (optional, default `'4:2:0'`) - `options.chromaSubsampling` **[string][2]** set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling (optional, default `'4:2:0'`)
- `options.trellisQuantisation` **[Boolean][7]** apply trellis quantisation, requires libvips compiled with support for mozjpeg (optional, default `false`) - `options.optimiseCoding` **[boolean][7]** optimise Huffman coding tables (optional, default `true`)
- `options.overshootDeringing` **[Boolean][7]** apply overshoot deringing, requires libvips compiled with support for mozjpeg (optional, default `false`) - `options.optimizeCoding` **[boolean][7]** alternative spelling of optimiseCoding (optional, default `true`)
- `options.optimiseScans` **[Boolean][7]** optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg (optional, default `false`) - `options.trellisQuantisation` **[boolean][7]** apply trellis quantisation, requires libvips compiled with support for mozjpeg (optional, default `false`)
- `options.optimizeScans` **[Boolean][7]** alternative spelling of optimiseScans (optional, default `false`) - `options.overshootDeringing` **[boolean][7]** apply overshoot deringing, requires libvips compiled with support for mozjpeg (optional, default `false`)
- `options.optimiseCoding` **[Boolean][7]** optimise Huffman coding tables (optional, default `true`) - `options.optimiseScans` **[boolean][7]** optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg (optional, default `false`)
- `options.optimizeCoding` **[Boolean][7]** alternative spelling of optimiseCoding (optional, default `true`) - `options.optimizeScans` **[boolean][7]** alternative spelling of optimiseScans, requires libvips compiled with support for mozjpeg (optional, default `false`)
- `options.quantisationTable` **[Number][9]** quantization table to use, integer 0-8, requires libvips compiled with support for mozjpeg (optional, default `0`) - `options.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.quantizationTable` **[number][9]** alternative spelling of quantisationTable, requires libvips compiled with support for mozjpeg (optional, default `0`)
- `options.force` **[Boolean][7]** force JPEG output, otherwise attempt to use input format (optional, default `true`) - `options.force` **[boolean][7]** force JPEG output, otherwise attempt to use input format (optional, default `true`)
### Examples ### Examples
@@ -155,18 +183,20 @@ Use these PNG options for output image.
PNG output is always full colour at 8 or 16 bits per pixel. PNG output is always full colour at 8 or 16 bits per pixel.
Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel. Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel.
Some of these options require the use of a globally-installed libvips compiled with support for libimagequant (GPL).
### Parameters ### Parameters
- `options` **[Object][6]?** - `options` **[Object][6]?**
- `options.progressive` **[Boolean][7]** use progressive (interlace) scan (optional, default `false`) - `options.progressive` **[boolean][7]** use progressive (interlace) scan (optional, default `false`)
- `options.compressionLevel` **[Number][9]** zlib compression level, 0-9 (optional, default `9`) - `options.compressionLevel` **[number][9]** zlib compression level, 0-9 (optional, default `9`)
- `options.adaptiveFiltering` **[Boolean][7]** use adaptive row filtering (optional, default `false`) - `options.adaptiveFiltering` **[boolean][7]** use adaptive row filtering (optional, default `false`)
- `options.palette` **[Boolean][7]** quantise to a palette-based image with alpha transparency support, requires libvips compiled with support for libimagequant (optional, default `false`) - `options.palette` **[boolean][7]** quantise to a palette-based image with alpha transparency support, 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.quality` **[number][9]** use the lowest number of colours needed to achieve given quality, sets `palette` to `true`, requires libvips compiled with support for libimagequant (optional, default `100`)
- `options.colours` **[Number][9]** maximum number of palette entries, requires libvips compiled with support for libimagequant (optional, default `256`) - `options.colours` **[number][9]** maximum number of palette entries, sets `palette` to `true`, requires libvips compiled with support for libimagequant (optional, default `256`)
- `options.colors` **[Number][9]** alternative spelling of `options.colours`, requires libvips compiled with support for libimagequant (optional, default `256`) - `options.colors` **[number][9]** alternative spelling of `options.colours`, sets `palette` to `true`, requires libvips compiled with support for libimagequant (optional, default `256`)
- `options.dither` **[Number][9]** level of Floyd-Steinberg error diffusion, requires libvips compiled with support for libimagequant (optional, default `1.0`) - `options.dither` **[number][9]** level of Floyd-Steinberg error diffusion, sets `palette` to `true`, 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`) - `options.force` **[boolean][7]** force PNG output, otherwise attempt to use input format (optional, default `true`)
### Examples ### Examples
@@ -188,13 +218,16 @@ Use these WebP options for output image.
### Parameters ### Parameters
- `options` **[Object][6]?** output options - `options` **[Object][6]?** output options
- `options.quality` **[Number][9]** quality, integer 1-100 (optional, default `80`) - `options.quality` **[number][9]** quality, integer 1-100 (optional, default `80`)
- `options.alphaQuality` **[Number][9]** quality of alpha layer, integer 0-100 (optional, default `100`) - `options.alphaQuality` **[number][9]** quality of alpha layer, integer 0-100 (optional, default `100`)
- `options.lossless` **[Boolean][7]** use lossless compression mode (optional, default `false`) - `options.lossless` **[boolean][7]** use lossless compression mode (optional, default `false`)
- `options.nearLossless` **[Boolean][7]** use near_lossless compression mode (optional, default `false`) - `options.nearLossless` **[boolean][7]** use near_lossless compression mode (optional, default `false`)
- `options.smartSubsample` **[Boolean][7]** use high quality chroma subsampling (optional, default `false`) - `options.smartSubsample` **[boolean][7]** use high quality chroma subsampling (optional, default `false`)
- `options.reductionEffort` **[Number][9]** level of CPU effort to reduce file size, integer 0-6 (optional, default `4`) - `options.reductionEffort` **[number][9]** level of CPU effort to reduce file size, integer 0-6 (optional, default `4`)
- `options.force` **[Boolean][7]** force WebP output, otherwise attempt to use input format (optional, default `true`) - `options.pageHeight` **[number][9]?** page height for animated output
- `options.loop` **[number][9]** number of animation iterations, use 0 for infinite animation (optional, default `0`)
- `options.delay` **[Array][10]&lt;[number][9]>?** list of delays between animation frames (in milliseconds)
- `options.force` **[boolean][7]** force WebP output, otherwise attempt to use input format (optional, default `true`)
### Examples ### Examples
@@ -205,6 +238,27 @@ const data = await sharp(input)
.toBuffer(); .toBuffer();
``` ```
- Throws **[Error][4]** Invalid options
Returns **Sharp**
## gif
Use these GIF options for output image.
Requires libvips compiled with support for ImageMagick or GraphicsMagick.
The prebuilt binaries do not include this - see
[installing a custom libvips][11].
### Parameters
- `options` **[Object][6]?** output options
- `options.pageHeight` **[number][9]?** page height for animated output
- `options.loop` **[number][9]** number of animation iterations, use 0 for infinite animation (optional, default `0`)
- `options.delay` **[Array][10]&lt;[number][9]>?** list of delays between animation frames (in milliseconds)
- `options.force` **[boolean][7]** force GIF output, otherwise attempt to use input format (optional, default `true`)
- Throws **[Error][4]** Invalid options - Throws **[Error][4]** Invalid options
Returns **Sharp** Returns **Sharp**
@@ -216,17 +270,17 @@ Use these TIFF options for output image.
### Parameters ### Parameters
- `options` **[Object][6]?** output options - `options` **[Object][6]?** output options
- `options.quality` **[Number][9]** quality, integer 1-100 (optional, default `80`) - `options.quality` **[number][9]** quality, integer 1-100 (optional, default `80`)
- `options.force` **[Boolean][7]** force TIFF output, otherwise attempt to use input format (optional, default `true`) - `options.force` **[boolean][7]** force TIFF output, otherwise attempt to use input format (optional, default `true`)
- `options.compression` **[Boolean][7]** compression options: lzw, deflate, jpeg, ccittfax4 (optional, default `'jpeg'`) - `options.compression` **[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.predictor` **[boolean][7]** compression predictor options: none, horizontal, float (optional, default `'horizontal'`)
- `options.pyramid` **[Boolean][7]** write an image pyramid (optional, default `false`) - `options.pyramid` **[boolean][7]** write an image pyramid (optional, default `false`)
- `options.tile` **[Boolean][7]** write a tiled tiff (optional, default `false`) - `options.tile` **[boolean][7]** write a tiled tiff (optional, default `false`)
- `options.tileWidth` **[Boolean][7]** horizontal tile size (optional, default `256`) - `options.tileWidth` **[boolean][7]** horizontal tile size (optional, default `256`)
- `options.tileHeight` **[Boolean][7]** vertical 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.xres` **[number][9]** horizontal resolution in pixels/mm (optional, default `1.0`)
- `options.yres` **[Number][9]** vertical resolution in pixels/mm (optional, default `1.0`) - `options.yres` **[number][9]** vertical resolution in pixels/mm (optional, default `1.0`)
- `options.squash` **[Boolean][7]** squash 8-bit images down to 1 bit (optional, default `false`) - `options.bitdepth` **[boolean][7]** reduce bitdepth to 1, 2 or 4 bit (optional, default `8`)
### Examples ### Examples
@@ -235,7 +289,7 @@ Use these TIFF options for output image.
sharp('input.svg') sharp('input.svg')
.tiff({ .tiff({
compression: 'lzw', compression: 'lzw',
squash: true bitdepth: 1
}) })
.toFile('1-bpp-output.tiff') .toFile('1-bpp-output.tiff')
.then(info => { ... }); .then(info => { ... });
@@ -259,18 +313,24 @@ Most versions of libheif support only the patent-encumbered HEVC compression for
### Parameters ### Parameters
- `options` **[Object][6]?** output options - `options` **[Object][6]?** output options
- `options.quality` **[Number][9]** quality, integer 1-100 (optional, default `80`) - `options.quality` **[number][9]** quality, integer 1-100 (optional, default `80`)
- `options.compression` **[Boolean][7]** compression format: hevc, avc, jpeg, av1 (optional, default `'hevc'`) - `options.compression` **[boolean][7]** compression format: hevc, avc, jpeg, av1 (optional, default `'hevc'`)
- `options.lossless` **[Boolean][7]** use lossless compression (optional, default `false`) - `options.lossless` **[boolean][7]** use lossless compression (optional, default `false`)
- Throws **[Error][4]** Invalid options - Throws **[Error][4]** Invalid options
Returns **Sharp** Returns **Sharp**
**Meta**
- **since**: 0.23.0
## raw ## raw
Force output to be raw, uncompressed uint8 pixel data. Force output to be raw, uncompressed, 8-bit unsigned integer (unit8) pixel data.
Pixel ordering is left-to-right, top-to-bottom, without padding.
Channel ordering will be RGB or RGBA for non-greyscale colourspaces.
### Examples ### Examples
@@ -281,28 +341,16 @@ const { data, info } = await sharp('input.jpg')
.toBuffer({ resolveWithObject: true }); .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 ```javascript
// Convert any input to PNG output // Extract alpha channel as raw pixel data from PNG input
const data = await sharp(input) const data = await sharp('input.png')
.toFormat('png') .ensureAlpha()
.extractChannel(3)
.toColourspace('b-w')
.raw()
.toBuffer(); .toBuffer();
``` ```
- Throws **[Error][4]** unsupported format or options
Returns **Sharp** Returns **Sharp**
## tile ## tile
@@ -316,14 +364,14 @@ Warning: multiple sharp instances concurrently producing tile output can expose
### Parameters ### Parameters
- `options` **[Object][6]?** - `options` **[Object][6]?**
- `options.size` **[Number][9]** tile size in pixels, a value between 1 and 8192. (optional, default `256`) - `options.size` **[number][9]** tile size in pixels, a value between 1 and 8192. (optional, default `256`)
- `options.overlap` **[Number][9]** tile overlap in pixels, a value between 0 and 8192. (optional, default `0`) - `options.overlap` **[number][9]** tile overlap in pixels, a value between 0 and 8192. (optional, default `0`)
- `options.angle` **[Number][9]** tile angle of rotation, must be a multiple of 90. (optional, default `0`) - `options.angle` **[number][9]** tile angle of rotation, must be a multiple of 90. (optional, default `0`)
- `options.background` **([String][2] \| [Object][6])** background colour, parsed by the [color][10] module, defaults to white without transparency. (optional, default `{r:255,g:255,b:255,alpha:1}`) - `options.background` **([string][2] \| [Object][6])** background colour, parsed by the [color][12] module, defaults to white without transparency. (optional, default `{r:255,g:255,b:255,alpha:1}`)
- `options.depth` **[String][2]?** how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout. - `options.depth` **[string][2]?** how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
- `options.skipBlanks` **[Number][9]** threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images (optional, default `-1`) - `options.skipBlanks` **[number][9]** threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images (optional, default `-1`)
- `options.container` **[String][2]** tile container, with value `fs` (filesystem) or `zip` (compressed file). (optional, default `'fs'`) - `options.container` **[string][2]** tile container, with value `fs` (filesystem) or `zip` (compressed file). (optional, default `'fs'`)
- `options.layout` **[String][2]** filesystem layout, possible values are `dz`, `zoomify` or `google`. (optional, default `'dz'`) - `options.layout` **[string][2]** filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`. (optional, default `'dz'`)
### Examples ### Examples
@@ -361,4 +409,8 @@ Returns **Sharp**
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number [9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[10]: https://www.npmjs.org/package/color [10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[11]: https://sharp.pixelplumbing.com/install#custom-libvips
[12]: https://www.npmjs.org/package/color

View File

@@ -6,19 +6,21 @@ Resize image to `width`, `height` or `width x height`.
When both a `width` and `height` are provided, the possible methods by which the image should **fit** these are: When both a `width` and `height` are provided, the possible methods by which the image should **fit** these are:
- `cover`: Crop to cover both provided dimensions (the default). - `cover`: (default) Preserving aspect ratio, ensure the image covers both provided dimensions by cropping/clipping to fit.
- `contain`: Embed within both provided dimensions. - `contain`: Preserving aspect ratio, contain within both provided dimensions using "letterboxing" where necessary.
- `fill`: Ignore the aspect ratio of the input and stretch to both provided dimensions. - `fill`: Ignore the aspect ratio of the input and stretch to both provided dimensions.
- `inside`: Preserving aspect ratio, resize the image to be as large as possible while ensuring its dimensions are less than or equal to both those specified. - `inside`: Preserving aspect ratio, resize the image to be as large as possible while ensuring its dimensions are less than or equal to both those specified.
- `outside`: Preserving aspect ratio, resize the image to be as small as possible while ensuring its dimensions are greater than or equal to both those specified. - `outside`: Preserving aspect ratio, resize the image to be as small as possible while ensuring its dimensions are greater than or equal to both those specified.
Some of these values are based on the [object-fit][1] CSS property.
Some of these values are based on the [object-fit][1] CSS property.
When using a `fit` of `cover` or `contain`, the default **position** is `centre`. Other options are: 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.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.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. - `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.
Some of these values are based on the [object-position][2] CSS property.
The experimental strategy-based approach resizes so one dimension is at its target length 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. then repeatedly ranks edge regions, discarding the edge with the lowest score based on the selected strategy.
@@ -36,8 +38,8 @@ Possible interpolation kernels are:
### Parameters ### Parameters
- `width` **[Number][8]?** pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height. - `width` **[number][8]?** pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height.
- `height` **[Number][8]?** pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width. - `height` **[number][8]?** pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
- `options` **[Object][9]?** - `options` **[Object][9]?**
- `options.width` **[String][10]?** alternative means of specifying `width`. If both are present this take priority. - `options.width` **[String][10]?** alternative means of specifying `width`. If both are present this take priority.
- `options.height` **[String][10]?** alternative means of specifying `height`. If both are present this take priority. - `options.height` **[String][10]?** alternative means of specifying `height`. If both are present this take priority.
@@ -114,6 +116,15 @@ sharp(input)
}); });
``` ```
```javascript
const scaleByHalf = await sharp(input)
.metadata()
.then(({ width }) => sharp(input)
.resize(Math.round(width * 0.5))
.toBuffer()
);
```
- Throws **[Error][13]** Invalid parameters - Throws **[Error][13]** Invalid parameters
Returns **Sharp** Returns **Sharp**
@@ -125,11 +136,11 @@ This operation will always occur after resizing and extraction, if any.
### Parameters ### Parameters
- `extend` **([Number][8] \| [Object][9])** single pixel count to add to all edges or an Object with per-edge counts - `extend` **([number][8] \| [Object][9])** single pixel count to add to all edges or an Object with per-edge counts
- `extend.top` **[Number][8]?** - `extend.top` **[number][8]?**
- `extend.left` **[Number][8]?** - `extend.left` **[number][8]?**
- `extend.bottom` **[Number][8]?** - `extend.bottom` **[number][8]?**
- `extend.right` **[Number][8]?** - `extend.right` **[number][8]?**
- `extend.background` **([String][10] \| [Object][9])** background colour, parsed by the [color][11] module, defaults to black without transparency. (optional, default `{r:0,g:0,b:0,alpha:1}`) - `extend.background` **([String][10] \| [Object][9])** background colour, parsed by the [color][11] module, defaults to black without transparency. (optional, default `{r:0,g:0,b:0,alpha:1}`)
### Examples ### Examples
@@ -155,7 +166,7 @@ Returns **Sharp**
## extract ## extract
Extract a region of the image. Extract/crop a region of the image.
- Use `extract` before `resize` for pre-resize extraction. - Use `extract` before `resize` for pre-resize extraction.
- Use `extract` after `resize` for post-resize extraction. - Use `extract` after `resize` for post-resize extraction.
@@ -164,10 +175,10 @@ Extract a region of the image.
### Parameters ### Parameters
- `options` **[Object][9]** describes the region to extract using integral pixel values - `options` **[Object][9]** describes the region to extract using integral pixel values
- `options.left` **[Number][8]** zero-indexed offset from left edge - `options.left` **[number][8]** zero-indexed offset from left edge
- `options.top` **[Number][8]** zero-indexed offset from top edge - `options.top` **[number][8]** zero-indexed offset from top edge
- `options.width` **[Number][8]** width of region to extract - `options.width` **[number][8]** width of region to extract
- `options.height` **[Number][8]** height of region to extract - `options.height` **[number][8]** height of region to extract
### Examples ### Examples
@@ -202,7 +213,7 @@ The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` pro
### Parameters ### Parameters
- `threshold` **[Number][8]** the allowed difference from the top-left pixel, a number greater than zero. (optional, default `10`) - `threshold` **[number][8]** the allowed difference from the top-left pixel, a number greater than zero. (optional, default `10`)
- Throws **[Error][13]** Invalid parameters - Throws **[Error][13]** Invalid parameters

View File

@@ -1,5 +1,27 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. --> <!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## format
An Object containing nested boolean values representing the available input and output formats/methods.
### Examples
```javascript
console.log(sharp.format);
```
Returns **[Object][1]**
## versions
An Object containing the version numbers of libvips and its dependencies.
### Examples
```javascript
console.log(sharp.versions);
```
## cache ## cache
Gets or, when options are provided, sets the limits of _libvips'_ operation cache. Gets or, when options are provided, sets the limits of _libvips'_ operation cache.
@@ -9,10 +31,10 @@ useful for determining how much working memory is required for a particular task
### Parameters ### Parameters
- `options` **([Object][1] \| [Boolean][2])** Object with the following attributes, or Boolean where true uses default cache settings and false removes all caching (optional, default `true`) - `options` **([Object][1] \| [boolean][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.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.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`) - `options.items` **[number][3]** is the maximum number of operations to cache (optional, default `100`)
### Examples ### Examples
@@ -42,7 +64,7 @@ This method always returns the current concurrency.
### Parameters ### Parameters
- `concurrency` **[Number][3]?** - `concurrency` **[number][3]?**
### Examples ### Examples
@@ -52,7 +74,22 @@ sharp.concurrency(2); // 2
sharp.concurrency(0); // 4 sharp.concurrency(0); // 4
``` ```
Returns **[Number][3]** concurrency Returns **[number][3]** concurrency
## queue
An EventEmitter that emits a `change` event when a task is either:
- queued, waiting for _libuv_ to provide a worker thread
- complete
### Examples
```javascript
sharp.queue.on('change', function(queueLength) {
console.log('Queue contains ' + queueLength + ' task(s)');
});
```
## counters ## counters
@@ -79,7 +116,7 @@ by taking advantage of the SIMD vector unit of the CPU, e.g. Intel SSE and ARM N
### Parameters ### Parameters
- `simd` **[Boolean][2]** (optional, default `true`) - `simd` **[boolean][2]** (optional, default `true`)
### Examples ### Examples
@@ -93,7 +130,7 @@ const simd = sharp.simd(false);
// prevent libvips from using liborc at runtime // prevent libvips from using liborc at runtime
``` ```
Returns **[Boolean][2]** Returns **[boolean][2]**
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object [1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object

View File

@@ -1,10 +1,180 @@
# Changelog # Changelog
### v0.23 - "*vision*" ## v0.26 - *zoom*
Requires libvips v8.10.0
### v0.26.1 - 20th September 2020
* Ensure correct pageHeight when verifying multi-page image dimensions.
[#2343](https://github.com/lovell/sharp/pull/2343)
[@derom](https://github.com/derom)
* Allow input density range up to 100000 DPI.
[#2348](https://github.com/lovell/sharp/pull/2348)
[@stefanprobst](https://github.com/stefanprobst)
* Ensure animation-related properties can be set for Stream-based input.
[#2369](https://github.com/lovell/sharp/pull/2369)
[@AcrylicShrimp](https://github.com/AcrylicShrimp)
* Ensure `stats` can be calculated for 1x1 input.
[#2372](https://github.com/lovell/sharp/issues/2372)
* Ensure animated GIF output is optimised.
[#2376](https://github.com/lovell/sharp/issues/2376)
### v0.26.0 - 25th August 2020
* Prebuilt libvips binaries are now statically-linked and Brotli-compressed, requiring Node.js 10.16.0+.
* TIFF output `squash` is replaced by `bitdepth` to reduce to 1, 2 or 4 bit.
* JPEG output `quality` >= 90 no longer automatically sets `chromaSubsampling` to `4:4:4`.
* Add most `dominant` colour to image `stats`.
[#640](https://github.com/lovell/sharp/issues/640)
* Add support for animated GIF (requires \*magick) and WebP output.
[#2012](https://github.com/lovell/sharp/pull/2012)
[@deftomat](https://github.com/deftomat)
* Add support for libvips ImageMagick v7 loaders.
[#2258](https://github.com/lovell/sharp/pull/2258)
[@vouillon](https://github.com/vouillon)
* Allow multi-page input via \*magick.
[#2259](https://github.com/lovell/sharp/pull/2259)
[@vouillon](https://github.com/vouillon)
* Add support to `withMetadata` for custom ICC profile.
[#2271](https://github.com/lovell/sharp/pull/2271)
[@roborourke](https://github.com/roborourke)
* Ensure prebuilt binaries for ARM default to v7 when using Electron.
[#2292](https://github.com/lovell/sharp/pull/2292)
[@diegodev3](https://github.com/diegodev3)
## v0.25 - *yield*
Requires libvips v8.9.1
### v0.25.4 - 12th June 2020
* Allow libvips binary location override where version is appended.
[#2217](https://github.com/lovell/sharp/pull/2217)
[@malice00](https://github.com/malice00)
* Enable PNG palette when setting quality, colours, colors or dither.
[#2226](https://github.com/lovell/sharp/pull/2226)
[@romaleev](https://github.com/romaleev)
* Add `level` constructor option to use a specific level of a multi-level image.
Expose `levels` metadata for multi-level images.
[#2222](https://github.com/lovell/sharp/issues/2222)
* Add support for named `alpha` channel to `extractChannel` operation.
[#2138](https://github.com/lovell/sharp/issues/2138)
* Add experimental `sharpness` calculation to `stats()` response.
[#2251](https://github.com/lovell/sharp/issues/2251)
* Emit `warning` event for non-critical processing problems.
[#2032](https://github.com/lovell/sharp/issues/2032)
### v0.25.3 - 17th May 2020
* Ensure libvips is initialised only once, improves worker thread safety.
[#2143](https://github.com/lovell/sharp/issues/2143)
* Ensure npm platform flag is respected when copying DLLs.
[#2188](https://github.com/lovell/sharp/pull/2188)
[@dimadeveatii](https://github.com/dimadeveatii)
* Allow SVG input with large inline images to be parsed.
[#2195](https://github.com/lovell/sharp/issues/2195)
### v0.25.2 - 20th March 2020
* Provide prebuilt binaries for Linux ARM64v8.
* Add IIIF layout support to tile-based output.
[#2098](https://github.com/lovell/sharp/pull/2098)
[@edsilv](https://github.com/edsilv)
* Ensure input options are consistently and correctly detected.
[#2118](https://github.com/lovell/sharp/issues/2118)
* Ensure N-API prebuilt binaries work on RHEL7 and its derivatives.
[#2119](https://github.com/lovell/sharp/issues/2119)
* Ensure AsyncWorker options are persisted.
[#2130](https://github.com/lovell/sharp/issues/2130)
### v0.25.1 - 7th March 2020
* Ensure prebuilt binaries are fetched based on N-API version.
[#2117](https://github.com/lovell/sharp/issues/2117)
### v0.25.0 - 7th March 2020
* Remove `limitInputPixels` and `sequentialRead` previously deprecated in v0.24.0.
* Migrate internals to N-API.
[#1282](https://github.com/lovell/sharp/issues/1282)
* Add support for 32-bit Windows.
[#2088](https://github.com/lovell/sharp/issues/2088)
* Ensure correct ordering of rotate-then-trim operations.
[#2087](https://github.com/lovell/sharp/issues/2087)
* Ensure composite accepts `limitInputPixels` and `sequentialRead` input options.
[#2099](https://github.com/lovell/sharp/issues/2099)
## v0.24 - "*wit*"
Requires libvips v8.9.0.
### v0.24.1 - 15<sup>th</sup> February 2020
* Prevent use of sequentialRead for EXIF-based rotate operation.
[#2042](https://github.com/lovell/sharp/issues/2042)
* Ensure RGBA LZW TIFF returns correct channel count.
[#2064](https://github.com/lovell/sharp/issues/2064)
### v0.24.0 - 16<sup>th</sup> January 2020
* Drop support for Node.js 8.
[#1910](https://github.com/lovell/sharp/issues/1910)
* Drop support for undefined input where options also provided.
[#1768](https://github.com/lovell/sharp/issues/1768)
* Move `limitInputPixels` and `sequentialRead` to input options, deprecating functions of the same name.
* Expose `delay` and `loop` metadata for animated images.
[#1905](https://github.com/lovell/sharp/issues/1905)
* Ensure correct colour output for 16-bit, 2-channel PNG input with ICC profile.
[#2013](https://github.com/lovell/sharp/issues/2013)
* Prevent use of sequentialRead for rotate operations.
[#2016](https://github.com/lovell/sharp/issues/2016)
* Correctly bind max width and height values when using withoutEnlargement.
[#2024](https://github.com/lovell/sharp/pull/2024)
[@BrychanOdlum](https://github.com/BrychanOdlum)
* Add support for input with 16-bit RGB profile.
[#2037](https://github.com/lovell/sharp/issues/2037)
## v0.23 - "*vision*"
Requires libvips v8.8.1. Requires libvips v8.8.1.
#### v0.23.4 - 5<sup>th</sup> December 2019 ### v0.23.4 - 5<sup>th</sup> December 2019
* Handle zero-length Buffer objects when using Node.js v13.2.0+. * Handle zero-length Buffer objects when using Node.js v13.2.0+.
@@ -14,7 +184,7 @@ Requires libvips v8.8.1.
* Improve thread safety by using copy-on-write when updating metadata. * Improve thread safety by using copy-on-write when updating metadata.
[#1986](https://github.com/lovell/sharp/issues/1986) [#1986](https://github.com/lovell/sharp/issues/1986)
#### v0.23.3 - 17<sup>th</sup> November 2019 ### v0.23.3 - 17<sup>th</sup> November 2019
* Ensure `trim` operation supports images contained in the alpha channel. * Ensure `trim` operation supports images contained in the alpha channel.
[#1597](https://github.com/lovell/sharp/issues/1597) [#1597](https://github.com/lovell/sharp/issues/1597)
@@ -30,7 +200,7 @@ Requires libvips v8.8.1.
* Ensure `modulate` and other colour-based operations can co-exist. * Ensure `modulate` and other colour-based operations can co-exist.
[#1958](https://github.com/lovell/sharp/issues/1958) [#1958](https://github.com/lovell/sharp/issues/1958)
#### v0.23.2 - 28<sup>th</sup> October 2019 ### v0.23.2 - 28<sup>th</sup> October 2019
* Add `background` option to tile output operation. * Add `background` option to tile output operation.
[#1924](https://github.com/lovell/sharp/pull/1924) [#1924](https://github.com/lovell/sharp/pull/1924)
@@ -40,7 +210,7 @@ Requires libvips v8.8.1.
[#1932](https://github.com/lovell/sharp/pull/1932) [#1932](https://github.com/lovell/sharp/pull/1932)
[@MayhemYDG](https://github.com/MayhemYDG) [@MayhemYDG](https://github.com/MayhemYDG)
#### v0.23.1 - 26<sup>th</sup> September 2019 ### v0.23.1 - 26<sup>th</sup> September 2019
* Ensure `sharp.format.vips` is present and correct (filesystem only). * Ensure `sharp.format.vips` is present and correct (filesystem only).
[#1813](https://github.com/lovell/sharp/issues/1813) [#1813](https://github.com/lovell/sharp/issues/1813)
@@ -62,7 +232,7 @@ Requires libvips v8.8.1.
* Ensure image is at least 3x3 pixels before attempting trim operation. * Ensure image is at least 3x3 pixels before attempting trim operation.
#### v0.23.0 - 29<sup>th</sup> July 2019 ### v0.23.0 - 29<sup>th</sup> July 2019
* Remove `overlayWith` previously deprecated in v0.22.0. * Remove `overlayWith` previously deprecated in v0.22.0.
@@ -92,11 +262,11 @@ Requires libvips v8.8.1.
[#1755](https://github.com/lovell/sharp/pull/1755) [#1755](https://github.com/lovell/sharp/pull/1755)
[@iovdin](https://github.com/iovdin) [@iovdin](https://github.com/iovdin)
### v0.22 - "*uptake*" ## v0.22 - "*uptake*"
Requires libvips v8.7.4. Requires libvips v8.7.4.
#### v0.22.1 - 25<sup>th</sup> April 2019 ### v0.22.1 - 25<sup>th</sup> April 2019
* Add `modulate` operation for brightness, saturation and hue. * Add `modulate` operation for brightness, saturation and hue.
[#1601](https://github.com/lovell/sharp/pull/1601) [#1601](https://github.com/lovell/sharp/pull/1601)
@@ -109,7 +279,7 @@ Requires libvips v8.7.4.
* Add support for Node 12. * Add support for Node 12.
[#1668](https://github.com/lovell/sharp/issues/1668) [#1668](https://github.com/lovell/sharp/issues/1668)
#### v0.22.0 - 18<sup>th</sup> March 2019 ### v0.22.0 - 18<sup>th</sup> March 2019
* Remove functions previously deprecated in v0.21.0: * Remove functions previously deprecated in v0.21.0:
`background`, `crop`, `embed`, `ignoreAspectRatio`, `max`, `min` and `withoutEnlargement`. `background`, `crop`, `embed`, `ignoreAspectRatio`, `max`, `min` and `withoutEnlargement`.
@@ -127,18 +297,18 @@ Requires libvips v8.7.4.
[#1595](https://github.com/lovell/sharp/pull/1595) [#1595](https://github.com/lovell/sharp/pull/1595)
[@ramiel](https://github.com/ramiel) [@ramiel](https://github.com/ramiel)
### v0.21 - "*teeth*" ## v0.21 - "*teeth*"
Requires libvips v8.7.0. Requires libvips v8.7.0.
#### v0.21.3 - 19<sup>th</sup> January 2019 ### v0.21.3 - 19<sup>th</sup> January 2019
* Input image decoding now fails fast, set `failOnError` to change this behaviour. * Input image decoding now fails fast, set `failOnError` to change this behaviour.
* Failed filesystem-based input now separates missing file and invalid format errors. * Failed filesystem-based input now separates missing file and invalid format errors.
[#1542](https://github.com/lovell/sharp/issues/1542) [#1542](https://github.com/lovell/sharp/issues/1542)
#### v0.21.2 - 13<sup>th</sup> January 2019 ### v0.21.2 - 13<sup>th</sup> January 2019
* Ensure all metadata is removed from PNG output unless `withMetadata` used. * Ensure all metadata is removed from PNG output unless `withMetadata` used.
@@ -163,7 +333,7 @@ Requires libvips v8.7.0.
* Ensure forced output format applied correctly when output chaining. * Ensure forced output format applied correctly when output chaining.
[#1528](https://github.com/lovell/sharp/issues/1528) [#1528](https://github.com/lovell/sharp/issues/1528)
#### v0.21.1 - 7<sup>th</sup> December 2018 ### v0.21.1 - 7<sup>th</sup> December 2018
* Install: support `sharp_dist_base_url` npm config, like existing `SHARP_DIST_BASE_URL`. * Install: support `sharp_dist_base_url` npm config, like existing `SHARP_DIST_BASE_URL`.
[#1422](https://github.com/lovell/sharp/pull/1422) [#1422](https://github.com/lovell/sharp/pull/1422)
@@ -192,7 +362,7 @@ Requires libvips v8.7.0.
[#1483](https://github.com/lovell/sharp/pull/1483) [#1483](https://github.com/lovell/sharp/pull/1483)
[@mbklein](https://github.com/mbklein) [@mbklein](https://github.com/mbklein)
#### v0.21.0 - 4<sup>th</sup> October 2018 ### v0.21.0 - 4<sup>th</sup> October 2018
* Deprecate the following resize-related functions: * Deprecate the following resize-related functions:
`crop`, `embed`, `ignoreAspectRatio`, `max`, `min` and `withoutEnlargement`. `crop`, `embed`, `ignoreAspectRatio`, `max`, `min` and `withoutEnlargement`.
@@ -229,11 +399,11 @@ Requires libvips v8.7.0.
[#1385](https://github.com/lovell/sharp/pull/1385) [#1385](https://github.com/lovell/sharp/pull/1385)
[@freezy](https://github.com/freezy) [@freezy](https://github.com/freezy)
### v0.20 - "*prebuild*" ## v0.20 - "*prebuild*"
Requires libvips v8.6.1. Requires libvips v8.6.1.
#### v0.20.8 - 5<sup>th</sup> September 2018 ### v0.20.8 - 5<sup>th</sup> September 2018
* Avoid race conditions when creating directories during installation. * Avoid race conditions when creating directories during installation.
[#1358](https://github.com/lovell/sharp/pull/1358) [#1358](https://github.com/lovell/sharp/pull/1358)
@@ -243,12 +413,12 @@ Requires libvips v8.6.1.
[#1362](https://github.com/lovell/sharp/pull/1362) [#1362](https://github.com/lovell/sharp/pull/1362)
[@aeirola](https://github.com/aeirola) [@aeirola](https://github.com/aeirola)
#### v0.20.7 - 21<sup>st</sup> August 2018 ### v0.20.7 - 21<sup>st</sup> August 2018
* Use copy+unlink if rename operation fails during installation. * Use copy+unlink if rename operation fails during installation.
[#1345](https://github.com/lovell/sharp/issues/1345) [#1345](https://github.com/lovell/sharp/issues/1345)
#### v0.20.6 - 20<sup>th</sup> August 2018 ### v0.20.6 - 20<sup>th</sup> August 2018
* Add removeAlpha operation to remove alpha channel, if any. * Add removeAlpha operation to remove alpha channel, if any.
[#1248](https://github.com/lovell/sharp/issues/1248) [#1248](https://github.com/lovell/sharp/issues/1248)
@@ -279,13 +449,13 @@ Requires libvips v8.6.1.
* Add experimental entropy field to stats response. * Add experimental entropy field to stats response.
#### v0.20.5 - 27<sup>th</sup> June 2018 ### v0.20.5 - 27<sup>th</sup> June 2018
* Expose libjpeg optimize_coding flag. * Expose libjpeg optimize_coding flag.
[#1265](https://github.com/lovell/sharp/pull/1265) [#1265](https://github.com/lovell/sharp/pull/1265)
[@tomlokhorst](https://github.com/tomlokhorst) [@tomlokhorst](https://github.com/tomlokhorst)
#### v0.20.4 - 20<sup>th</sup> June 2018 ### v0.20.4 - 20<sup>th</sup> June 2018
* Prevent possible rounding error when using shrink-on-load and 90/270 degree rotation. * Prevent possible rounding error when using shrink-on-load and 90/270 degree rotation.
[#1241](https://github.com/lovell/sharp/issues/1241) [#1241](https://github.com/lovell/sharp/issues/1241)
@@ -295,13 +465,13 @@ Requires libvips v8.6.1.
[#1257](https://github.com/lovell/sharp/issues/1257) [#1257](https://github.com/lovell/sharp/issues/1257)
[@jeremychone](https://github.com/jeremychone) [@jeremychone](https://github.com/jeremychone)
#### v0.20.3 - 29<sup>th</sup> May 2018 ### v0.20.3 - 29<sup>th</sup> May 2018
* Fix tint operation by ensuring LAB interpretation and allowing negative values. * Fix tint operation by ensuring LAB interpretation and allowing negative values.
[#1235](https://github.com/lovell/sharp/issues/1235) [#1235](https://github.com/lovell/sharp/issues/1235)
[@wezside](https://github.com/wezside) [@wezside](https://github.com/wezside)
#### v0.20.2 - 28<sup>th</sup> April 2018 ### v0.20.2 - 28<sup>th</sup> April 2018
* Add tint operation to set image chroma. * Add tint operation to set image chroma.
[#825](https://github.com/lovell/sharp/pull/825) [#825](https://github.com/lovell/sharp/pull/825)
@@ -319,7 +489,7 @@ Requires libvips v8.6.1.
[#1208](https://github.com/lovell/sharp/pull/1208) [#1208](https://github.com/lovell/sharp/pull/1208)
[@woolite64](https://github.com/woolite64) [@woolite64](https://github.com/woolite64)
#### v0.20.1 - 17<sup>th</sup> March 2018 ### v0.20.1 - 17<sup>th</sup> March 2018
* Improve installation experience when a globally-installed libvips below the minimum required version is found. * Improve installation experience when a globally-installed libvips below the minimum required version is found.
[#1148](https://github.com/lovell/sharp/issues/1148) [#1148](https://github.com/lovell/sharp/issues/1148)
@@ -332,16 +502,16 @@ Requires libvips v8.6.1.
[#1161](https://github.com/lovell/sharp/pull/1161) [#1161](https://github.com/lovell/sharp/pull/1161)
[@BiancoA](https://github.com/BiancoA) [@BiancoA](https://github.com/BiancoA)
#### v0.20.0 - 5<sup>th</sup> March 2018 ### v0.20.0 - 5<sup>th</sup> March 2018
* Add support for prebuilt sharp binaries on common platforms. * Add support for prebuilt sharp binaries on common platforms.
[#186](https://github.com/lovell/sharp/issues/186) [#186](https://github.com/lovell/sharp/issues/186)
### v0.19 - "*suit*" ## v0.19 - "*suit*"
Requires libvips v8.6.1. Requires libvips v8.6.1.
#### v0.19.1 - 24<sup>th</sup> February 2018 ### v0.19.1 - 24<sup>th</sup> February 2018
* Expose libvips' linear transform feature. * Expose libvips' linear transform feature.
[#1024](https://github.com/lovell/sharp/pull/1024) [#1024](https://github.com/lovell/sharp/pull/1024)
@@ -355,7 +525,7 @@ Requires libvips v8.6.1.
[#1134](https://github.com/lovell/sharp/issues/1134) [#1134](https://github.com/lovell/sharp/issues/1134)
[@pieh](https://github.com/pieh) [@pieh](https://github.com/pieh)
#### v0.19.0 - 11<sup>th</sup> January 2018 ### v0.19.0 - 11<sup>th</sup> January 2018
* Expose offset coordinates of strategy-based crop. * Expose offset coordinates of strategy-based crop.
[#868](https://github.com/lovell/sharp/issues/868) [#868](https://github.com/lovell/sharp/issues/868)
@@ -399,17 +569,17 @@ Requires libvips v8.6.1.
* TIFF output: switch default predictor from 'none' to 'horizontal' to match libvips' behaviour. * TIFF output: switch default predictor from 'none' to 'horizontal' to match libvips' behaviour.
### v0.18 - "*ridge*" ## v0.18 - "*ridge*"
Requires libvips v8.5.5. Requires libvips v8.5.5.
#### v0.18.4 - 18<sup>th</sup> September 2017 ### v0.18.4 - 18<sup>th</sup> September 2017
* Ensure input Buffer really is marked as Persistent, prevents mark-sweep GC. * Ensure input Buffer really is marked as Persistent, prevents mark-sweep GC.
[#950](https://github.com/lovell/sharp/issues/950) [#950](https://github.com/lovell/sharp/issues/950)
[@lfdoherty](https://github.com/lfdoherty) [@lfdoherty](https://github.com/lfdoherty)
#### v0.18.3 - 13<sup>th</sup> September 2017 ### v0.18.3 - 13<sup>th</sup> September 2017
* Skip shrink-on-load when trimming. * Skip shrink-on-load when trimming.
[#888](https://github.com/lovell/sharp/pull/888) [#888](https://github.com/lovell/sharp/pull/888)
@@ -419,7 +589,7 @@ Requires libvips v8.5.5.
[#945](https://github.com/lovell/sharp/pull/945) [#945](https://github.com/lovell/sharp/pull/945)
[@pbomb](https://github.com/pbomb) [@pbomb](https://github.com/pbomb)
#### v0.18.2 - 1<sup>st</sup> July 2017 ### v0.18.2 - 1<sup>st</sup> July 2017
* Expose libvips' xres and yres properties for TIFF output. * Expose libvips' xres and yres properties for TIFF output.
[#828](https://github.com/lovell/sharp/pull/828) [#828](https://github.com/lovell/sharp/pull/828)
@@ -436,13 +606,13 @@ Requires libvips v8.5.5.
[#857](https://github.com/lovell/sharp/pull/857) [#857](https://github.com/lovell/sharp/pull/857)
[@ekremkaraca](https://github.com/ekremkaraca) [@ekremkaraca](https://github.com/ekremkaraca)
#### v0.18.1 - 30<sup>th</sup> May 2017 ### v0.18.1 - 30<sup>th</sup> May 2017
* Remove regression from #781 that could cause incorrect shrink calculation. * Remove regression from #781 that could cause incorrect shrink calculation.
[#831](https://github.com/lovell/sharp/issues/831) [#831](https://github.com/lovell/sharp/issues/831)
[@suprMax](https://github.com/suprMax) [@suprMax](https://github.com/suprMax)
#### v0.18.0 - 30<sup>th</sup> May 2017 ### v0.18.0 - 30<sup>th</sup> May 2017
* Remove the previously-deprecated output format "option" functions: * Remove the previously-deprecated output format "option" functions:
quality, progressive, compressionLevel, withoutAdaptiveFiltering, quality, progressive, compressionLevel, withoutAdaptiveFiltering,
@@ -500,11 +670,11 @@ Requires libvips v8.5.5.
[#814](https://github.com/lovell/sharp/pull/814) [#814](https://github.com/lovell/sharp/pull/814)
[@jingsam](https://github.com/jingsam) [@jingsam](https://github.com/jingsam)
### v0.17 - "*quill*" ## v0.17 - "*quill*"
Requires libvips v8.4.2. Requires libvips v8.4.2.
#### v0.17.3 - 1<sup>st</sup> April 2017 ### v0.17.3 - 1<sup>st</sup> April 2017
* Allow toBuffer to optionally resolve a Promise with both info and data. * Allow toBuffer to optionally resolve a Promise with both info and data.
[#143](https://github.com/lovell/sharp/issues/143) [#143](https://github.com/lovell/sharp/issues/143)
@@ -522,7 +692,7 @@ Requires libvips v8.4.2.
[#738](https://github.com/lovell/sharp/pull/738) [#738](https://github.com/lovell/sharp/pull/738)
[@kristojorg](https://github.com/kristojorg) [@kristojorg](https://github.com/kristojorg)
#### v0.17.2 - 11<sup>th</sup> February 2017 ### v0.17.2 - 11<sup>th</sup> February 2017
* Ensure Readable side of Stream can start flowing after Writable side has finished. * Ensure Readable side of Stream can start flowing after Writable side has finished.
[#671](https://github.com/lovell/sharp/issues/671) [#671](https://github.com/lovell/sharp/issues/671)
@@ -532,7 +702,7 @@ Requires libvips v8.4.2.
[#685](https://github.com/lovell/sharp/pull/685) [#685](https://github.com/lovell/sharp/pull/685)
[@rnanwani](https://github.com/rnanwani) [@rnanwani](https://github.com/rnanwani)
#### v0.17.1 - 15<sup>th</sup> January 2017 ### v0.17.1 - 15<sup>th</sup> January 2017
* Improve error messages for invalid parameters. * Improve error messages for invalid parameters.
[@spikeon](https://github.com/spikeon) [@spikeon](https://github.com/spikeon)
@@ -545,7 +715,7 @@ Requires libvips v8.4.2.
[@wangzhiwei1888](https://github.com/wangzhiwei1888) [@wangzhiwei1888](https://github.com/wangzhiwei1888)
[#679](https://github.com/lovell/sharp/issues/679) [#679](https://github.com/lovell/sharp/issues/679)
#### v0.17.0 - 11<sup>th</sup> December 2016 ### v0.17.0 - 11<sup>th</sup> December 2016
* Drop support for versions of Node prior to v4. * Drop support for versions of Node prior to v4.
@@ -582,17 +752,17 @@ Requires libvips v8.4.2.
[#646](https://github.com/lovell/sharp/issues/646) [#646](https://github.com/lovell/sharp/issues/646)
[@DaGaMs](https://github.com/DaGaMs) [@DaGaMs](https://github.com/DaGaMs)
### v0.16 - "*pencil*" ## v0.16 - "*pencil*"
Requires libvips v8.3.3 Requires libvips v8.3.3
#### v0.16.2 - 22<sup>nd</sup> October 2016 ### v0.16.2 - 22<sup>nd</sup> October 2016
* Restrict readelf usage to Linux only when detecting global libvips version. * Restrict readelf usage to Linux only when detecting global libvips version.
[#602](https://github.com/lovell/sharp/issues/602) [#602](https://github.com/lovell/sharp/issues/602)
[@caoko](https://github.com/caoko) [@caoko](https://github.com/caoko)
#### v0.16.1 - 13<sup>th</sup> October 2016 ### v0.16.1 - 13<sup>th</sup> October 2016
* C++11 ABI version is now auto-detected, remove sharp-cxx11 installation flag. * C++11 ABI version is now auto-detected, remove sharp-cxx11 installation flag.
@@ -611,7 +781,7 @@ Requires libvips v8.3.3
[#566](https://github.com/lovell/sharp/issues/566) [#566](https://github.com/lovell/sharp/issues/566)
[@Nateowami](https://github.com/Nateowami) [@Nateowami](https://github.com/Nateowami)
#### v0.16.0 - 18<sup>th</sup> August 2016 ### v0.16.0 - 18<sup>th</sup> August 2016
* Add pre-compiled libvips for OS X, ARMv7 and ARMv8. * Add pre-compiled libvips for OS X, ARMv7 and ARMv8.
[#312](https://github.com/lovell/sharp/issues/312) [#312](https://github.com/lovell/sharp/issues/312)
@@ -651,11 +821,11 @@ Requires libvips v8.3.3
* Remove deprecated interpolateWith method - use resize(w, h, { interpolator: ... }) * Remove deprecated interpolateWith method - use resize(w, h, { interpolator: ... })
[#310](https://github.com/lovell/sharp/issues/310) [#310](https://github.com/lovell/sharp/issues/310)
### v0.15 - "*outfit*" ## v0.15 - "*outfit*"
Requires libvips v8.3.1 Requires libvips v8.3.1
#### v0.15.1 - 12<sup>th</sup> July 2016 ### v0.15.1 - 12<sup>th</sup> July 2016
* Concat Stream-based input in single operation for ~+3% perf and less GC. * Concat Stream-based input in single operation for ~+3% perf and less GC.
[#429](https://github.com/lovell/sharp/issues/429) [#429](https://github.com/lovell/sharp/issues/429)
@@ -721,7 +891,7 @@ Requires libvips v8.3.1
[#501](https://github.com/lovell/sharp/pull/501) [#501](https://github.com/lovell/sharp/pull/501)
[@mhirsch](https://github.com/mhirsch) [@mhirsch](https://github.com/mhirsch)
#### v0.15.0 - 21<sup>st</sup> May 2016 ### v0.15.0 - 21<sup>st</sup> May 2016
* Use libvips' new Lanczos 3 kernel as default for image reduction. * Use libvips' new Lanczos 3 kernel as default for image reduction.
Deprecate interpolateWith method, now provided as a resize option. Deprecate interpolateWith method, now provided as a resize option.
@@ -739,11 +909,11 @@ Requires libvips v8.3.1
[#413](https://github.com/lovell/sharp/issues/413) [#413](https://github.com/lovell/sharp/issues/413)
[@jardakotesovec](https://github.com/jardakotesovec) [@jardakotesovec](https://github.com/jardakotesovec)
### v0.14 - "*needle*" ## v0.14 - "*needle*"
Requires libvips v8.2.3 Requires libvips v8.2.3
#### v0.14.1 - 16<sup>th</sup> April 2016 ### v0.14.1 - 16<sup>th</sup> April 2016
* Allow removal of limitation on input pixel count via limitInputPixels. Use with care. * Allow removal of limitation on input pixel count via limitInputPixels. Use with care.
[#250](https://github.com/lovell/sharp/issues/250) [#250](https://github.com/lovell/sharp/issues/250)
@@ -767,7 +937,7 @@ Requires libvips v8.2.3
[#412](https://github.com/lovell/sharp/issues/412) [#412](https://github.com/lovell/sharp/issues/412)
[@nouh](https://github.com/nouh) [@nouh](https://github.com/nouh)
#### v0.14.0 - 2<sup>nd</sup> April 2016 ### v0.14.0 - 2<sup>nd</sup> April 2016
* Add ability to extend (pad) the edges of an image. * Add ability to extend (pad) the edges of an image.
[#128](https://github.com/lovell/sharp/issues/128) [#128](https://github.com/lovell/sharp/issues/128)
@@ -804,17 +974,17 @@ Requires libvips v8.2.3
* Remove deprecated style of calling extract API. Breaks calls using positional arguments. * Remove deprecated style of calling extract API. Breaks calls using positional arguments.
[#276](https://github.com/lovell/sharp/issues/276) [#276](https://github.com/lovell/sharp/issues/276)
### v0.13 - "*mind*" ## v0.13 - "*mind*"
Requires libvips v8.2.2 Requires libvips v8.2.2
#### v0.13.1 - 27<sup>th</sup> February 2016 ### v0.13.1 - 27<sup>th</sup> February 2016
* Fix embedding onto transparent backgrounds; regression introduced in v0.13.0. * Fix embedding onto transparent backgrounds; regression introduced in v0.13.0.
[#366](https://github.com/lovell/sharp/issues/366) [#366](https://github.com/lovell/sharp/issues/366)
[@diegocsandrim](https://github.com/diegocsandrim) [@diegocsandrim](https://github.com/diegocsandrim)
#### v0.13.0 - 15<sup>th</sup> February 2016 ### v0.13.0 - 15<sup>th</sup> February 2016
* Improve vector image support by allowing control of density/DPI. * Improve vector image support by allowing control of density/DPI.
Switch pre-built libs from Imagemagick to Graphicsmagick. Switch pre-built libs from Imagemagick to Graphicsmagick.
@@ -859,11 +1029,11 @@ Requires libvips v8.2.2
* Add support for gamma correction of images with an alpha channel. * Add support for gamma correction of images with an alpha channel.
### v0.12 - "*look*" ## v0.12 - "*look*"
Requires libvips v8.2.0 Requires libvips v8.2.0
#### v0.12.2 - 16<sup>th</sup> January 2016 ### v0.12.2 - 16<sup>th</sup> January 2016
* Upgrade libvips to v8.2.0 for improved vips_shrink. * Upgrade libvips to v8.2.0 for improved vips_shrink.
@@ -881,7 +1051,7 @@ Requires libvips v8.2.0
[#331](https://github.com/lovell/sharp/issues/331) [#331](https://github.com/lovell/sharp/issues/331)
[@dtoubelis](https://github.com/dtoubelis) [@dtoubelis](https://github.com/dtoubelis)
#### v0.12.1 - 12<sup>th</sup> December 2015 ### v0.12.1 - 12<sup>th</sup> December 2015
* Allow use of SIMD vector instructions (via liborc) to be toggled on/off. * Allow use of SIMD vector instructions (via liborc) to be toggled on/off.
[#172](https://github.com/lovell/sharp/issues/172) [#172](https://github.com/lovell/sharp/issues/172)
@@ -894,7 +1064,7 @@ Requires libvips v8.2.0
* Use the NPM-configured HTTPS proxy, if any, for binary downloads. * Use the NPM-configured HTTPS proxy, if any, for binary downloads.
#### v0.12.0 - 23<sup>rd</sup> November 2015 ### v0.12.0 - 23<sup>rd</sup> November 2015
* Bundle pre-compiled libvips and its dependencies for 64-bit Linux and Windows. * Bundle pre-compiled libvips and its dependencies for 64-bit Linux and Windows.
[#42](https://github.com/lovell/sharp/issues/42) [#42](https://github.com/lovell/sharp/issues/42)
@@ -930,9 +1100,9 @@ Requires libvips v8.2.0
[#309](https://github.com/lovell/sharp/pull/309) [#309](https://github.com/lovell/sharp/pull/309)
[@papandreou](https://github.com/papandreou) [@papandreou](https://github.com/papandreou)
### v0.11 - "*knife*" ## v0.11 - "*knife*"
#### v0.11.4 - 5<sup>th</sup> November 2015 ### v0.11.4 - 5<sup>th</sup> November 2015
* Add corners, e.g. `northeast`, to existing `gravity` option. * Add corners, e.g. `northeast`, to existing `gravity` option.
[#291](https://github.com/lovell/sharp/pull/291) [#291](https://github.com/lovell/sharp/pull/291)
@@ -946,13 +1116,13 @@ Requires libvips v8.2.0
[#287](https://github.com/lovell/sharp/pull/287) [#287](https://github.com/lovell/sharp/pull/287)
[@vlapo](https://github.com/vlapo) [@vlapo](https://github.com/vlapo)
#### v0.11.3 - 8<sup>th</sup> September 2015 ### v0.11.3 - 8<sup>th</sup> September 2015
* Intrepret blurSigma, sharpenFlat, and sharpenJagged as double precision. * Intrepret blurSigma, sharpenFlat, and sharpenJagged as double precision.
[#263](https://github.com/lovell/sharp/pull/263) [#263](https://github.com/lovell/sharp/pull/263)
[@chrisriley](https://github.com/chrisriley) [@chrisriley](https://github.com/chrisriley)
#### v0.11.2 - 28<sup>th</sup> August 2015 ### v0.11.2 - 28<sup>th</sup> August 2015
* Allow crop gravity to be provided as a String. * Allow crop gravity to be provided as a String.
[#255](https://github.com/lovell/sharp/pull/255) [#255](https://github.com/lovell/sharp/pull/255)
@@ -960,7 +1130,7 @@ Requires libvips v8.2.0
* Add support for io.js v3 and Node v4. * Add support for io.js v3 and Node v4.
[#246](https://github.com/lovell/sharp/issues/246) [#246](https://github.com/lovell/sharp/issues/246)
#### v0.11.1 - 12<sup>th</sup> August 2015 ### v0.11.1 - 12<sup>th</sup> August 2015
* Silence MSVC warning: "C4530: C++ exception handler used, but unwind semantics are not enabled". * Silence MSVC warning: "C4530: C++ exception handler used, but unwind semantics are not enabled".
[#244](https://github.com/lovell/sharp/pull/244) [#244](https://github.com/lovell/sharp/pull/244)
@@ -970,7 +1140,7 @@ Requires libvips v8.2.0
[#249](https://github.com/lovell/sharp/issues/249) [#249](https://github.com/lovell/sharp/issues/249)
[@compeak](https://github.com/compeak) [@compeak](https://github.com/compeak)
#### v0.11.0 - 15<sup>th</sup> July 2015 ### v0.11.0 - 15<sup>th</sup> July 2015
* Allow alpha transparency compositing via new `overlayWith` method. * Allow alpha transparency compositing via new `overlayWith` method.
[#97](https://github.com/lovell/sharp/issues/97) [#97](https://github.com/lovell/sharp/issues/97)
@@ -998,9 +1168,9 @@ Requires libvips v8.2.0
[#238](https://github.com/lovell/sharp/issues/238) [#238](https://github.com/lovell/sharp/issues/238)
[@richardadjogah](https://github.com/richardadjogah) [@richardadjogah](https://github.com/richardadjogah)
### v0.10 - "*judgment*" ## v0.10 - "*judgment*"
#### v0.10.1 - 1<sup>st</sup> June 2015 ### v0.10.1 - 1<sup>st</sup> June 2015
* Allow embed of image with alpha transparency onto non-transparent background. * Allow embed of image with alpha transparency onto non-transparent background.
[#204](https://github.com/lovell/sharp/issues/204) [#204](https://github.com/lovell/sharp/issues/204)
@@ -1010,7 +1180,7 @@ Requires libvips v8.2.0
[#228](https://github.com/lovell/sharp/issues/228) [#228](https://github.com/lovell/sharp/issues/228)
[@doggan](https://github.com/doggan) [@doggan](https://github.com/doggan)
#### v0.10.0 - 23<sup>rd</sup> April 2015 ### v0.10.0 - 23<sup>rd</sup> April 2015
* Add support for Windows (x86). * Add support for Windows (x86).
[#19](https://github.com/lovell/sharp/issues/19) [#19](https://github.com/lovell/sharp/issues/19)

View File

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

117
docs/firebase.json Normal file
View File

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

205
docs/humans.txt Normal file
View File

@@ -0,0 +1,205 @@
/* 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

216
docs/index.html Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,150 +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, 12 and 13
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)
* [Nicolas Stepien](https://github.com/MayhemYDG)
* [Paul Neave](https://github.com/neave)
* [Brendan Kennedy](https://github.com/rustyguts)
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,107 +10,116 @@ yarn add sharp
## Prerequisites ## Prerequisites
* Node.js v8.5.0+ * Node.js v10.16.0+
### Building from source ## Prebuilt binaries
Pre-compiled binaries for sharp are provided for use with Ready-compiled sharp and libvips binaries are provided for use with
Node versions 8, 10, 12 and 13 on Node.js v10.16.0+ on the most common platforms:
64-bit Windows, OS X and Linux platforms.
Sharp will be built from source at install time when: * macOS x64 (>= 10.13)
* Linux x64 (glibc >= 2.17, musl >= 1.1.24)
* Linux ARM64 (glibc >= 2.29)
* Windows
* a globally-installed libvips is detected, A ~7MB tarball containing libvips and its most commonly used dependencies
* pre-compiled binaries do not exist for the current platform and Node version, or is downloaded via HTTPS and stored within `node_modules/sharp/vendor` during `npm install`.
This provides support for the
JPEG, PNG, WebP, TIFF, GIF (input) and SVG (input) image formats.
The following platforms have prebuilt libvips but not sharp:
* Linux ARMv6
* Linux ARMv7 (glibc >= 2.28)
The following platforms require compilation of both libvips and sharp from source:
* Linux x86
* Linux x64 (glibc <= 2.16, includes RHEL/CentOS 6)
* Linux ARM64 (glibc <= 2.28, musl)
* Linux PowerPC
* FreeBSD
* OpenBSD
## Common problems
The architecture and platform of Node.js used for `npm install`
must be the same as the architecture and platform of Node.js used at runtime.
The `npm install --unsafe-perm` flag must be used when installing as `root` or a `sudo` user.
The `npm install --ignore-scripts=false` flag must be used when `npm` has been configured to ignore installation scripts.
Check the output of running `npm install --verbose sharp` for useful error messages.
## Custom libvips
To use a custom, globally-installed version of libvips instead of the provided binaries,
make sure it is at least the version listed under `config.libvips` in the `package.json` file
and that it can be located using `pkg-config --modversion vips-cpp`.
For help compiling libvips from source, please see
[https://libvips.github.io/libvips/install.html#building-libvips-from-a-source-tarball](https://libvips.github.io/libvips/install.html#building-libvips-from-a-source-tarball).
The use of a globally-installed libvips is unsupported on Windows.
## Building from source
This module will be compiled from source at `npm install` time when:
* a globally-installed libvips is detected (set the `SHARP_IGNORE_GLOBAL_LIBVIPS` environment variable to skip this),
* prebuilt binaries do not exist for the current platform and Node.js version, or
* when the `npm install --build-from-source` flag is used. * when the `npm install --build-from-source` flag is used.
Building from source requires: Building from source requires:
* C++11 compatible compiler such as gcc 4.8+, clang 3.0+ or MSVC 2013+ * C++11 compiler
* [node-gyp](https://github.com/nodejs/node-gyp#installation) and its dependencies (includes Python 2.7) * [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) 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.
libvips and its dependencies are fetched and stored within `node_modules/sharp/vendor` during `npm install`. To install the prebuilt libvips binaries from a custom URL,
This involves an automated HTTPS download of approximately 10MB. set the `sharp_libvips_binary_host` npm config option
or the `npm_config_sharp_libvips_binary_host` environment variable.
Most Linux-based (glibc, musl) operating systems running on x64 and ARMv6+ CPUs should "just work", e.g.: The version subpath and file name are appended to these.
* Debian 8+ For example, if `sharp_libvips_binary_host` is set to `https://hostname/path`
* Ubuntu 14.04+ and the libvips version is `1.2.3` then the resultant URL will be
* Red Hat Enterprise 7+ `https://hostname/path/v1.2.3/libvips-1.2.3-platform-arch.tar.br`.
* CentOS 7+
* Alpine 3.10+
* Fedora 21+
* openSUSE 13.2+
* Archlinux
* Raspbian Jessie
* Amazon Linux
* Solus
To use a globally-installed version of libvips instead of the provided binaries, See the Chinese mirror below for a further example.
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`.
If you are using non-standard paths (anything other than `/usr` or `/usr/local`), ## Chinese mirror
you might need to set `PKG_CONFIG_PATH` during `npm install`
and `LD_LIBRARY_PATH` at runtime.
This allows the use of newer versions of libvips with older versions of sharp. Alibaba provide a mirror site based in China containing binaries for both sharp and libvips.
For 32-bit Intel CPUs and older Linux-based operating systems such as To use this either set the following configuration:
those based on Red Hat Enterprise 6 (e.g. CentOS 6)
compiling libvips from source is recommended.
[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)
#### Alpine Linux
libvips is available in the
[community repository](https://pkgs.alpinelinux.org/packages?name=vips-dev):
```sh ```sh
apk add --upgrade --no-cache vips-dev build-base \ npm config set sharp_binary_host "https://npm.taobao.org/mirrors/sharp"
--repository https://alpine.global.ssl.fastly.net/alpine/v3.10/community/ npm config set sharp_libvips_binary_host "https://npm.taobao.org/mirrors/sharp-libvips"
npm install sharp
``` ```
The smaller stack size of musl libc means or set the following environment variables:
libvips may need to be used without a cache
via `sharp.cache(false)` to avoid a stack overflow.
### Mac OS ```sh
npm_config_sharp_binary_host="https://npm.taobao.org/mirrors/sharp" \
npm_config_sharp_libvips_binary_host="https://npm.taobao.org/mirrors/sharp-libvips" \
npm install sharp
```
[![OS X 10.12 Build Status](https://travis-ci.org/lovell/sharp.png?branch=master)](https://travis-ci.org/lovell/sharp) ## FreeBSD
libvips and its dependencies are fetched and stored within `node_modules/sharp/vendor` during `npm install`. The `vips` package must be installed before `npm install` is run.
This involves an automated HTTPS download of approximately 8MB.
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`.
### Windows x64
[![Windows x64 Build Status](https://ci.appveyor.com/api/projects/status/pgtul704nkhhg6sg)](https://ci.appveyor.com/project/lovell/sharp)
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
[![FreeBSD Build Status](https://api.cirrus-ci.com/github/lovell/sharp.svg)](https://cirrus-ci.com/github/lovell/sharp)
libvips must be installed before `npm install` is run.
This can be achieved via package or ports:
```sh ```sh
pkg install -y pkgconf vips pkg install -y pkgconf vips
@@ -120,175 +129,58 @@ pkg install -y pkgconf vips
cd /usr/ports/graphics/vips/ && make install clean 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 ## Heroku
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=193528
### Heroku Add the
[jemalloc buildpack](https://github.com/gaffneyc/heroku-buildpack-jemalloc)
to reduce the effects of memory fragmentation.
Set [NODE_MODULES_CACHE](https://devcenter.heroku.com/articles/nodejs-support#cache-behavior) Set
[NODE_MODULES_CACHE](https://devcenter.heroku.com/articles/nodejs-support#cache-behavior)
to `false` when using the `yarn` package manager. to `false` when using the `yarn` package manager.
To reduce the effects of memory fragmentation, add the ## AWS Lambda
[jemalloc buildpack](https://github.com/gaffneyc/heroku-buildpack-jemalloc).
### Docker Set the Lambda runtime to `nodejs12.x`.
[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 The binaries in the `node_modules` directory of the
[deployment package](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-create-deployment-pkg.html) [deployment package](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-package.html)
must be for the Linux x64 platform/architecture. must be for the Linux x64 platform.
On non-Linux machines such as OS X and Windows run the following: On machines other than Linux x64, such as macOS and Windows, run the following:
```sh ```sh
rm -rf node_modules/sharp rm -rf node_modules/sharp
npm install --arch=x64 --platform=linux --target=10.15.0 sharp npm install --arch=x64 --platform=linux sharp
``` ```
Alternatively a Docker container closely matching the Lambda runtime can be used: Alternatively a Docker container closely matching the Lambda runtime can be used:
```sh ```sh
rm -rf node_modules/sharp rm -rf node_modules/sharp
docker run -v "$PWD":/var/task lambci/lambda:build-nodejs10.x npm install sharp docker run -v "$PWD":/var/task lambci/lambda:build-nodejs12.x npm install sharp
``` ```
To get the best performance select the largest memory available. To get the best performance select the largest memory available.
A 1536 MB function provides ~12x more CPU time than a 128 MB function. A 1536 MB function provides ~12x more CPU time than a 128 MB function.
### NW.js ## Electron
Run the `nw-gyp` tool after installation. Electron provides versions of the V8 JavaScript engine
that are incompatible with Node.js.
To ensure the correct binaries are used, run the following:
```sh ```sh
cd node-modules/sharp npm install
nw-gyp rebuild --arch=x64 --target=[your nw version] npx electron-rebuild
node node_modules/sharp/install/dll-copy
``` ```
[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/) Further help can be found at
[https://electronjs.org/docs/tutorial/using-native-node-modules](https://electronjs.org/docs/tutorial/using-native-node-modules)
### Build tools ## Worker threads
* [gulp-responsive](https://www.npmjs.com/package/gulp-responsive) The main thread must call `require('sharp')`
* [grunt-sharp](https://www.npmjs.com/package/grunt-sharp) before worker threads are created
to ensure shared libraries remain loaded in memory
### Coding tools until after all threads are complete.
* [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
```
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
```
or set the `SHARP_DIST_BASE_URL` environment variable, e.g.
```sh
SHARP_DIST_BASE_URL="https://hostname/path/" npm install sharp
```
to use `https://hostname/path/libvips-x.y.z-platform.tar.gz`.
To install the prebuilt sharp binaries from a custom URL, please see
[https://github.com/prebuild/prebuild-install#custom-binaries](https://github.com/prebuild/prebuild-install#custom-binaries)
### Licences
This module is licensed under the terms of the
[Apache 2.0 Licence](https://github.com/lovell/sharp/blob/master/LICENSE).
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.
Use of libraries under the terms of the LGPLv3 is via the
"any later version" clause of the LGPLv2 or LGPLv2.1.
| 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) |

View File

@@ -1,44 +1,46 @@
# Performance # Performance
### Test environment A test to benchmark the performance of this module relative to alternatives.
* AWS EC2 eu-west-1 [c5.large](https://aws.amazon.com/ec2/instance-types/c5/) (2x Xeon Platinum 8124M CPU @ 3.00GHz) ## The contenders
* 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.16.0 - Image processing in pure JavaScript. Provides bicubic interpolation.
* [mapnik](https://www.npmjs.org/package/mapnik) v4.5.2 - Whilst primarily a map renderer, Mapnik contains bitmap image utilities.
* [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*". * [imagemagick](https://www.npmjs.com/package/imagemagick) v0.1.3 - Supports filesystem only and "*has been unmaintained for a long time*".
* [gm](https://www.npmjs.com/package/gm) v1.23.1 - Fully featured wrapper around GraphicsMagick's `gm` command line utility. * [gm](https://www.npmjs.com/package/gm) v1.23.1 - Fully featured wrapper around GraphicsMagick's `gm` command line utility.
* sharp v0.23.1 / libvips v8.8.1 - Caching within libvips disabled to ensure a fair comparison. * sharp v0.26.0 / libvips v8.10.0 - Caching within libvips disabled to ensure a fair comparison.
### The task ## The task
Decompress a 2725x2225 JPEG image, Decompress a 2725x2225 JPEG image,
resize to 720x588 using Lanczos 3 resampling (where available), resize to 720x588 using Lanczos 3 resampling (where available),
then compress to JPEG at a "quality" setting of 80. then compress to JPEG at a "quality" setting of 80.
### Results ## Test environment
* AWS EC2 eu-west-1 [c5d.large](https://aws.amazon.com/ec2/instance-types/c5/) (2x Xeon Platinum 8124M CPU @ 3.00GHz)
* Ubuntu 20.04 (ami-0f1d11c92a9467c07)
* Node.js v14.8.0
## Results
| Module | Input | Output | Ops/sec | Speed-up | | Module | Input | Output | Ops/sec | Speed-up |
| :----------------- | :----- | :----- | ------: | -------: | | :----------------- | :----- | :----- | ------: | -------: |
| jimp | buffer | buffer | 0.66 | 1.0 | | jimp | buffer | buffer | 0.75 | 1.0 |
| mapnik | buffer | buffer | 3.31 | 5.0 | | mapnik | buffer | buffer | 3.00 | 4.0 |
| gm | buffer | buffer | 3.79 | 5.7 | | gm | buffer | buffer | 4.12 | 5.5 |
| gm | file | file | 3.82 | 5.8 | | gm | file | file | 4.13 | 5.5 |
| imagemagick | file | file | 4.17 | 6.3 | | imagemagick | file | file | 4.30 | 5.7 |
| sharp | stream | stream | 25.81 | 39.1 | | sharp | stream | stream | 22.37 | 29.8 |
| sharp | file | file | 26.76 | 40.5 | | sharp | file | file | 23.40 | 31.2 |
| sharp | buffer | buffer | 28.06 | 42.5 | | sharp | buffer | buffer | 24.01 | 32.0 |
Greater libvips performance can be expected with caching enabled (default) Greater libvips performance can be expected with caching enabled (default)
and using 8+ core machines, especially those with larger L1/L2 CPU caches. and using 4+ core machines, especially those with larger L1/L2 CPU caches.
The I/O limits of the relevant (de)compression library will generally determine maximum throughput. The I/O limits of the relevant (de)compression library will generally determine maximum throughput.
### Benchmark test prerequisites ## Running the benchmark test
Requires _ImageMagick_, _GraphicsMagick_ and _Mapnik_: Requires _ImageMagick_, _GraphicsMagick_ and _Mapnik_:
@@ -56,8 +58,6 @@ sudo apt-get install imagemagick libmagick++-dev graphicsmagick libmapnik-dev
sudo yum install ImageMagick-devel ImageMagick-c++-devel GraphicsMagick mapnik-devel sudo yum install ImageMagick-devel ImageMagick-c++-devel GraphicsMagick mapnik-devel
``` ```
### Running the benchmark test
```sh ```sh
git clone https://github.com/lovell/sharp.git git clone https://github.com/lovell/sharp.git
cd sharp cd sharp

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,59 @@
'use strict';
const fs = require('fs');
const { extractDescription, extractKeywords } = require('./extract');
const searchIndex = [];
// Install
const contents = fs.readFileSync(`${__dirname}/../install.md`, 'utf8');
const matches = contents.matchAll(
/## (?<title>[A-Za-z ]+)\n\n(?<body>[^#]+)/gs
);
for (const match of matches) {
const { title, body } = match.groups;
const description = extractDescription(body);
searchIndex.push({
t: title,
d: description,
k: extractKeywords(`${title} ${description}`),
l: `/install#${title.toLowerCase().replace(/ /g, '-')}`
});
}
// API
[
'constructor',
'input',
'output',
'resize',
'composite',
'operation',
'channel',
'colour',
'utility'
].forEach((section) => {
const contents = fs.readFileSync(`${__dirname}/../api-${section}.md`, 'utf8');
const matches = contents.matchAll(
/\n## (?<title>[A-Za-z]+)\n\n(?<firstparagraph>.+?)\n\n/gs
);
for (const match of matches) {
const { title, firstparagraph } = match.groups;
const description = firstparagraph.startsWith('###')
? 'Constructor'
: extractDescription(firstparagraph);
searchIndex.push({
t: title,
d: description,
k: extractKeywords(`${title} ${description}`),
l: `/api-${section}#${title.toLowerCase()}`
});
}
});
fs.writeFileSync(
`${__dirname}/../search-index.json`,
JSON.stringify(searchIndex)
);

View File

@@ -0,0 +1,80 @@
'use strict';
const stopWords = [
'a',
'about',
'all',
'already',
'always',
'an',
'and',
'any',
'are',
'as',
'at',
'be',
'been',
'by',
'can',
'do',
'does',
'each',
'either',
'etc',
'for',
'from',
'get',
'gets',
'has',
'have',
'how',
'if',
'in',
'is',
'it',
'its',
'may',
'more',
'much',
'no',
'not',
'of',
'on',
'or',
'over',
'set',
'sets',
'should',
'that',
'the',
'their',
'there',
'therefore',
'these',
'this',
'to',
'use',
'using',
'when',
'which',
'will',
'with'
];
const extractDescription = (str) =>
str
.replace(/\(http[^)]+/g, '')
.replace(/\s+/g, ' ')
.replace(/[^A-Za-z0-9_/\-,. ]/g, '')
.replace(/\s+/g, ' ')
.substr(0, 140)
.trim();
const extractKeywords = (str) =>
str
.split(/[ -/]/)
.map((word) => word.toLowerCase().replace(/[^a-z]/g, ''))
.filter((word) => word.length > 2 && !stopWords.includes(word))
.join(' ');
module.exports = { extractDescription, extractKeywords };

View File

@@ -6,7 +6,10 @@ const path = require('path');
const libvips = require('../lib/libvips'); const libvips = require('../lib/libvips');
const npmLog = require('npmlog'); const npmLog = require('npmlog');
if (process.platform === 'win32') { const minimumLibvipsVersion = libvips.minimumLibvipsVersion;
const platform = process.env.npm_config_platform || process.platform;
if (platform === 'win32') {
const buildDir = path.join(__dirname, '..', 'build'); const buildDir = path.join(__dirname, '..', 'build');
const buildReleaseDir = path.join(buildDir, 'Release'); const buildReleaseDir = path.join(buildDir, 'Release');
npmLog.info('sharp', `Creating ${buildReleaseDir}`); npmLog.info('sharp', `Creating ${buildReleaseDir}`);
@@ -14,7 +17,7 @@ if (process.platform === 'win32') {
libvips.mkdirSync(buildDir); libvips.mkdirSync(buildDir);
libvips.mkdirSync(buildReleaseDir); libvips.mkdirSync(buildReleaseDir);
} catch (err) {} } catch (err) {}
const vendorLibDir = path.join(__dirname, '..', 'vendor', 'lib'); const vendorLibDir = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, 'lib');
npmLog.info('sharp', `Copying DLLs from ${vendorLibDir} to ${buildReleaseDir}`); npmLog.info('sharp', `Copying DLLs from ${vendorLibDir} to ${buildReleaseDir}`);
try { try {
fs fs

View File

@@ -3,19 +3,28 @@
const fs = require('fs'); const fs = require('fs');
const os = require('os'); const os = require('os');
const path = require('path'); const path = require('path');
const stream = require('stream');
const zlib = require('zlib');
const detectLibc = require('detect-libc'); const detectLibc = require('detect-libc');
const npmLog = require('npmlog'); const npmLog = require('npmlog');
const semver = require('semver'); const semver = require('semver');
const simpleGet = require('simple-get'); const simpleGet = require('simple-get');
const tar = require('tar'); const tarFs = require('tar-fs');
const agent = require('../lib/agent'); const agent = require('../lib/agent');
const libvips = require('../lib/libvips'); const libvips = require('../lib/libvips');
const platform = require('../lib/platform'); const platform = require('../lib/platform');
const minimumLibvipsVersion = libvips.minimumLibvipsVersion; const minimumGlibcVersionByArch = {
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}/`; arm: '2.28',
arm64: '2.29',
x64: '2.17'
};
const { minimumLibvipsVersion, minimumLibvipsVersionLabelled } = libvips;
const distHost = process.env.npm_config_sharp_libvips_binary_host || 'https://github.com/lovell/sharp-libvips/releases/download';
const distBaseUrl = process.env.npm_config_sharp_dist_base_url || process.env.SHARP_DIST_BASE_URL || `${distHost}/v${minimumLibvipsVersionLabelled}/`;
const fail = function (err) { const fail = function (err) {
npmLog.error('sharp', err.message); npmLog.error('sharp', err.message);
@@ -23,25 +32,28 @@ const fail = function (err) {
npmLog.info('sharp', 'Are you trying to install as a root or sudo user? Try again with the --unsafe-perm flag'); npmLog.info('sharp', 'Are you trying to install as a root or sudo user? Try again with the --unsafe-perm flag');
} }
npmLog.info('sharp', 'Attempting to build from source via node-gyp but this may fail due to the above error'); npmLog.info('sharp', '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'); npmLog.info('sharp', 'Please see https://sharp.pixelplumbing.com/install for required dependencies');
process.exit(1); process.exit(1);
}; };
const extractTarball = function (tarPath) { const extractTarball = function (tarPath) {
const vendorPath = path.join(__dirname, '..', 'vendor'); const vendorPath = path.join(__dirname, '..', 'vendor');
libvips.mkdirSync(vendorPath); libvips.mkdirSync(vendorPath);
tar const versionedVendorPath = path.join(vendorPath, minimumLibvipsVersion);
.extract({ libvips.mkdirSync(versionedVendorPath);
file: tarPath, stream.pipeline(
cwd: vendorPath, fs.createReadStream(tarPath),
strict: true new zlib.BrotliDecompress(),
}) tarFs.extract(versionedVendorPath),
.catch(function (err) { function (err) {
if (/unexpected end of file/.test(err.message)) { if (err) {
npmLog.error('sharp', `Please delete ${tarPath} as it is not a valid tarball`); if (/unexpected end of file/.test(err.message)) {
npmLog.error('sharp', `Please delete ${tarPath} as it is not a valid tarball`);
}
fail(err);
} }
fail(err); }
}); );
}; };
try { try {
@@ -57,20 +69,22 @@ try {
// Is this arch/platform supported? // Is this arch/platform supported?
const arch = process.env.npm_config_arch || process.arch; const arch = process.env.npm_config_arch || process.arch;
const platformAndArch = platform(); const platformAndArch = platform();
if (platformAndArch === 'win32-ia32') { if (arch === 'ia32' && !platformAndArch.startsWith('win32')) {
throw new Error('Windows x86 (32-bit) node.exe is not supported');
}
if (arch === 'ia32') {
throw new Error(`Intel Architecture 32-bit systems require manual installation of libvips >= ${minimumLibvipsVersion}`); throw new Error(`Intel Architecture 32-bit systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
} }
if (platformAndArch === 'freebsd-x64' || platformAndArch === 'openbsd-x64' || platformAndArch === 'sunos-x64') { if (platformAndArch === 'freebsd-x64' || platformAndArch === 'openbsd-x64' || platformAndArch === 'sunos-x64') {
throw new Error(`BSD/SunOS systems require manual installation of libvips >= ${minimumLibvipsVersion}`); throw new Error(`BSD/SunOS systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
} }
if (detectLibc.family === detectLibc.GLIBC && detectLibc.version && semver.lt(`${detectLibc.version}.0`, '2.17.0')) { if (detectLibc.family === detectLibc.GLIBC && detectLibc.version) {
throw new Error(`Use with glibc version ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`); if (semver.lt(`${detectLibc.version}.0`, `${minimumGlibcVersionByArch[arch]}.0`)) {
throw new Error(`Use with glibc ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`);
}
}
if (!semver.satisfies(process.versions.node, process.env.npm_package_engines_node)) {
throw new Error(`Expected Node.js version ${process.env.npm_package_engines_node} but found ${process.versions.node}`);
} }
// Download to per-process temporary file // Download to per-process temporary file
const tarFilename = ['libvips', minimumLibvipsVersion, platformAndArch].join('-') + '.tar.gz'; const tarFilename = ['libvips', minimumLibvipsVersion, platformAndArch].join('-') + '.tar.br';
const tarPathCache = path.join(libvips.cachePath(), tarFilename); const tarPathCache = path.join(libvips.cachePath(), tarFilename);
if (fs.existsSync(tarPathCache)) { if (fs.existsSync(tarPathCache)) {
npmLog.info('sharp', `Using cached ${tarPathCache}`); npmLog.info('sharp', `Using cached ${tarPathCache}`);
@@ -84,7 +98,7 @@ try {
if (err) { if (err) {
fail(err); fail(err);
} else if (response.statusCode === 404) { } else if (response.statusCode === 404) {
fail(new Error(`Prebuilt libvips binaries are not yet available for ${platformAndArch}`)); fail(new Error(`Prebuilt libvips ${minimumLibvipsVersion} binaries are not yet available for ${platformAndArch}`));
} else if (response.statusCode !== 200) { } else if (response.statusCode !== 200) {
fail(new Error(`Status ${response.statusCode} ${response.statusMessage}`)); fail(new Error(`Status ${response.statusCode} ${response.statusMessage}`));
} else { } else {

12
install/prebuild-ci.js Normal file
View File

@@ -0,0 +1,12 @@
'use strict';
const { spawnSync } = require('child_process');
const { prebuild_upload: hasToken, APPVEYOR_REPO_TAG_NAME, TRAVIS_TAG } = process.env;
if (hasToken && (APPVEYOR_REPO_TAG_NAME || TRAVIS_TAG)) {
spawnSync('node',
['./node_modules/prebuild/bin.js', '--runtime', 'napi', '--target', '3'],
{ shell: true, stdio: 'inherit' }
);
}

View File

@@ -32,6 +32,8 @@ 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 alpha channel, if missing. The added alpha channel will be fully opaque. This is a no-op if the image already has an alpha channel.
* *
* @since 0.21.2
*
* @example * @example
* sharp('rgb.jpg') * sharp('rgb.jpg')
* .ensureAlpha() * .ensureAlpha()
@@ -52,27 +54,25 @@ function ensureAlpha () {
* @example * @example
* sharp(input) * sharp(input)
* .extractChannel('green') * .extractChannel('green')
* .toFile('input_green.jpg', function(err, info) { * .toColourspace('b-w')
* .toFile('green.jpg', function(err, info) {
* // info.channels === 1 * // info.channels === 1
* // input_green.jpg contains the green channel of the input image * // green.jpg is a greyscale image containing the green channel of the input
* }); * });
* *
* @param {Number|String} channel - zero-indexed band number to extract, or `red`, `green` or `blue` as alternative to `0`, `1` or `2` respectively. * @param {number|string} channel - zero-indexed channel/band number to extract, or `red`, `green`, `blue` or `alpha`.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid channel * @throws {Error} Invalid channel
*/ */
function extractChannel (channel) { function extractChannel (channel) {
if (channel === 'red') { const channelMap = { red: 0, green: 1, blue: 2, alpha: 3 };
channel = 0; if (Object.keys(channelMap).includes(channel)) {
} else if (channel === 'green') { channel = channelMap[channel];
channel = 1;
} else if (channel === 'blue') {
channel = 2;
} }
if (is.integer(channel) && is.inRange(channel, 0, 4)) { if (is.integer(channel) && is.inRange(channel, 0, 4)) {
this.options.extractChannel = channel; this.options.extractChannel = channel;
} else { } else {
throw is.invalidParameterError('channel', 'integer or one of: red, green, blue', channel); throw is.invalidParameterError('channel', 'integer or one of: red, green, blue, alpha', channel);
} }
return this; return this;
} }
@@ -88,7 +88,7 @@ function extractChannel (channel) {
* 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: JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data.
* For raw pixel input, the `options` object should contain a `raw` attribute, which follows the format of the attribute of the same name in the `sharp()` constructor. * For raw pixel input, the `options` object should contain a `raw` attribute, which follows the format of the attribute of the same name in the `sharp()` constructor.
* *
* @param {Array<String|Buffer>|String|Buffer} images - one or more images (file paths, Buffers). * @param {Array<string|Buffer>|string|Buffer} images - one or more images (file paths, Buffers).
* @param {Object} options - image options, see `sharp()` constructor. * @param {Object} options - image options, see `sharp()` constructor.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
@@ -116,7 +116,7 @@ function joinChannel (images, options) {
* // then `O(1,1) = 0b11110111 & 0b10101010 & 0b00001111 = 0b00000010 = 2`. * // then `O(1,1) = 0b11110111 & 0b10101010 & 0b00001111 = 0b00000010 = 2`.
* }); * });
* *
* @param {String} boolOp - one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively. * @param {string} boolOp - one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */

View File

@@ -19,7 +19,7 @@ const colourspace = {
* Tint the image using the provided chroma while preserving the image luminance. * Tint the image using the provided chroma while preserving the image luminance.
* An alpha channel may be present and will be unchanged by the operation. * An alpha channel may be present and will be unchanged by the operation.
* *
* @param {String|Object} rgb - parsed by the [color](https://www.npmjs.org/package/color) module to extract chroma values. * @param {string|Object} rgb - parsed by the [color](https://www.npmjs.org/package/color) module to extract chroma values.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameter * @throws {Error} Invalid parameter
*/ */
@@ -57,7 +57,7 @@ function grayscale (grayscale) {
/** /**
* Set the output colourspace. * Set the output colourspace.
* By default output image will be web-friendly sRGB, with additional channels interpreted as alpha channels. * By default output image will be web-friendly sRGB, with additional channels interpreted as alpha channels.
* @param {String} [colourspace] - output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568) * @param {string} [colourspace] - output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568)
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -71,7 +71,7 @@ function toColourspace (colourspace) {
/** /**
* Alternative spelling of `toColourspace`. * Alternative spelling of `toColourspace`.
* @param {String} [colorspace] - output colorspace. * @param {string} [colorspace] - output colorspace.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -82,8 +82,8 @@ function toColorspace (colorspace) {
/** /**
* Update a colour attribute of the this.options Object. * Update a colour attribute of the this.options Object.
* @private * @private
* @param {String} key * @param {string} key
* @param {String|Object} value * @param {string|Object} value
* @throws {Error} Invalid value * @throws {Error} Invalid value
*/ */
function _setBackgroundColourOption (key, value) { function _setBackgroundColourOption (key, value) {

View File

@@ -53,6 +53,8 @@ const blend = {
* https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode * https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode
* and https://www.cairographics.org/operators/ * and https://www.cairographics.org/operators/
* *
* @since 0.22.0
*
* @example * @example
* sharp('input.png') * sharp('input.png')
* .rotate(180) * .rotate(180)
@@ -70,7 +72,7 @@ const blend = {
* }); * });
* *
* @param {Object[]} images - Ordered list of images to composite * @param {Object[]} images - Ordered list of images to composite
* @param {Buffer|String} [images[].input] - Buffer containing image data, String containing the path to an image file, or Create object (see bellow) * @param {Buffer|String} [images[].input] - Buffer containing image data, String containing the path to an image file, or Create object (see below)
* @param {Object} [images[].input.create] - describes a blank overlay to be created. * @param {Object} [images[].input.create] - describes a blank overlay to be created.
* @param {Number} [images[].input.create.width] * @param {Number} [images[].input.create.width]
* @param {Number} [images[].input.create.height] * @param {Number} [images[].input.create.height]
@@ -98,8 +100,7 @@ function composite (images) {
if (!is.object(image)) { if (!is.object(image)) {
throw is.invalidParameterError('image to composite', 'object', image); throw is.invalidParameterError('image to composite', 'object', image);
} }
const { raw, density } = image; const inputOptions = this._inputOptionsFromObject(image);
const inputOptions = (raw || density) ? { raw, density } : undefined;
const composite = { const composite = {
input: this._createInputDescriptor(image.input, inputOptions, { allowStream: false }), input: this._createInputDescriptor(image.input, inputOptions, { allowStream: false }),
blend: 'over', blend: 'over',

View File

@@ -2,15 +2,13 @@
const util = require('util'); const util = require('util');
const stream = require('stream'); const stream = require('stream');
const events = require('events');
const is = require('./is'); const is = require('./is');
require('./libvips').hasVendoredLibvips(); require('./libvips').hasVendoredLibvips();
let sharp;
/* istanbul ignore next */ /* istanbul ignore next */
try { try {
sharp = require('../build/Release/sharp.node'); require('../build/Release/sharp.node');
} catch (err) { } catch (err) {
// Bail early if bindings aren't available // Bail early if bindings aren't available
const help = ['', 'Something went wrong installing the "sharp" module', '', err.message, '']; const help = ['', 'Something went wrong installing the "sharp" module', '', err.message, ''];
@@ -18,30 +16,42 @@ try {
help.push('- Ensure the version of Node.js used at install time matches that used at runtime'); 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)) { } else if (/invalid ELF header/.test(err.message)) {
help.push(`- Ensure "${process.platform}" is used at install time as well as runtime`); help.push(`- Ensure "${process.platform}" is used at install time as well as runtime`);
} else if (/dylib/.test(err.message) && /Incompatible library version/.test(err.message)) {
help.push('- Run "brew update && brew upgrade vips"');
} else if (/Cannot find module/.test(err.message)) {
help.push('- Run "npm rebuild --verbose sharp" and look for errors');
} else { } else {
help.push('- Remove the "node_modules/sharp" directory, run "npm install" and look for errors'); help.push(
'- Remove the "node_modules/sharp" directory then run',
' "npm install --ignore-scripts=false --verbose" and look for errors'
);
} }
help.push( help.push(
'- Consult the installation documentation at https://sharp.pixelplumbing.com/en/stable/install/', '- Consult the installation documentation at https://sharp.pixelplumbing.com/install',
'- Search for this error at https://github.com/lovell/sharp/issues', '' '- Search for this error at https://github.com/lovell/sharp/issues', ''
); );
console.error(help.join('\n')); const error = help.join('\n');
process.exit(1); throw new Error(error);
} }
// Use NODE_DEBUG=sharp to enable libvips warnings // Use NODE_DEBUG=sharp to enable libvips warnings
const debuglog = util.debuglog('sharp'); const debuglog = util.debuglog('sharp');
/** /**
* @class Sharp
*
* Constructor factory to create an instance of `sharp`, to which further methods are chained. * Constructor factory to create an instance of `sharp`, to which further methods are chained.
* *
* JPEG, PNG, WebP or TIFF format image data can be streamed out from this object. * JPEG, PNG, WebP or TIFF format image data can be streamed out from this object.
* When using Stream based output, derived attributes are available from the `info` event. * When using Stream based output, derived attributes are available from the `info` event.
* *
* Non-critical problems encountered during processing are emitted as `warning` events.
*
* Implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class. * Implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.
* *
* @constructs Sharp
*
* @emits Sharp#info
* @emits Sharp#warning
*
* @example * @example
* sharp('input.jpg') * sharp('input.jpg')
* .resize(300, 200) * .resize(300, 200)
@@ -76,25 +86,36 @@ const debuglog = util.debuglog('sharp');
* .toBuffer() * .toBuffer()
* .then( ... ); * .then( ... );
* *
* @param {(Buffer|String)} [input] - if present, can be * @example
* // Convert an animated GIF to an animated WebP
* await sharp('in.gif', { animated: true }).toFile('out.webp');
*
* @param {(Buffer|string)} [input] - if present, can be
* a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or * a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
* a String containing the filesystem path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file. * a String containing the filesystem path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
* JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present. * JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
* @param {Object} [options] - if present, is an Object with optional attributes. * @param {Object} [options] - if present, is an Object with optional attributes.
* @param {Boolean} [options.failOnError=true] - by default halt processing and raise an error when loading invalid images. * @param {boolean} [options.failOnError=true] - by default halt processing and raise an error when loading invalid images.
* Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid. * Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid.
* @param {Number} [options.density=72] - number representing the DPI for vector images. * @param {number|boolean} [options.limitInputPixels=268402689] - Do not process input images where the number of pixels
* @param {Number} [options.pages=1] - number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages. * (width x height) exceeds this limit. Assumes image dimensions contained in the input metadata can be trusted.
* @param {Number} [options.page=0] - page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based. * An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF).
* @param {boolean} [options.sequentialRead=false] - Set this to `true` to use sequential rather than random access where possible.
* This can reduce memory usage and might improve performance on some systems.
* @param {number} [options.density=72] - number representing the DPI for vector images in the range 1 to 100000.
* @param {number} [options.pages=1] - number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages.
* @param {number} [options.page=0] - page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based.
* @param {number} [options.level=0] - level to extract from a multi-level input (OpenSlide), zero based.
* @param {boolean} [options.animated=false] - Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`).
* @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering. * @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering.
* @param {Number} [options.raw.width] * @param {number} [options.raw.width]
* @param {Number} [options.raw.height] * @param {number} [options.raw.height]
* @param {Number} [options.raw.channels] - 1-4 * @param {number} [options.raw.channels] - 1-4
* @param {Object} [options.create] - describes a new image to be created. * @param {Object} [options.create] - describes a new image to be created.
* @param {Number} [options.create.width] * @param {number} [options.create.width]
* @param {Number} [options.create.height] * @param {number} [options.create.height]
* @param {Number} [options.create.channels] - 3-4 * @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 {string|Object} [options.create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -107,9 +128,6 @@ const Sharp = function (input, options) {
} }
stream.Duplex.call(this); stream.Duplex.call(this);
this.options = { this.options = {
// input options
sequentialRead: false,
limitInputPixels: Math.pow(0x3FFF, 2),
// resize options // resize options
topOffsetPre: -1, topOffsetPre: -1,
leftOffsetPre: -1, leftOffsetPre: -1,
@@ -156,7 +174,7 @@ const Sharp = function (input, options) {
gamma: 0, gamma: 0,
gammaOut: 0, gammaOut: 0,
greyscale: false, greyscale: false,
normalise: 0, normalise: false,
brightness: 1, brightness: 1,
saturation: 1, saturation: 1,
hue: 0, hue: 0,
@@ -174,6 +192,7 @@ const Sharp = function (input, options) {
streamOut: false, streamOut: false,
withMetadata: false, withMetadata: false,
withMetadataOrientation: -1, withMetadataOrientation: -1,
withMetadataIcc: '',
resolveWithObject: false, resolveWithObject: false,
// output format // output format
jpegQuality: 80, jpegQuality: 80,
@@ -201,7 +220,7 @@ const Sharp = function (input, options) {
tiffCompression: 'jpeg', tiffCompression: 'jpeg',
tiffPredictor: 'horizontal', tiffPredictor: 'horizontal',
tiffPyramid: false, tiffPyramid: false,
tiffSquash: false, tiffBitdepth: 8,
tiffTile: false, tiffTile: false,
tiffTileHeight: 256, tiffTileHeight: 256,
tiffTileWidth: 256, tiffTileWidth: 256,
@@ -212,15 +231,23 @@ const Sharp = function (input, options) {
heifCompression: 'hevc', heifCompression: 'hevc',
tileSize: 256, tileSize: 256,
tileOverlap: 0, tileOverlap: 0,
tileContainer: 'fs',
tileLayout: 'dz',
tileFormat: 'last',
tileDepth: 'last',
tileAngle: 0,
tileSkipBlanks: -1, tileSkipBlanks: -1,
tileBackground: [255, 255, 255, 255], tileBackground: [255, 255, 255, 255],
linearA: 1, linearA: 1,
linearB: 0, linearB: 0,
// Function to notify of libvips warnings // Function to notify of libvips warnings
debuglog: debuglog, debuglog: warning => {
this.emit('warning', warning);
debuglog(warning);
},
// Function to notify of queue length changes // Function to notify of queue length changes
queueListener: function (queueLength) { queueListener: function (queueLength) {
queue.emit('change', queueLength); Sharp.queue.emit('change', queueLength);
} }
}; };
this.options.input = this._createInputDescriptor(input, options, { allowStream: true }); this.options.input = this._createInputDescriptor(input, options, { allowStream: true });
@@ -229,38 +256,84 @@ const Sharp = function (input, options) {
util.inherits(Sharp, stream.Duplex); util.inherits(Sharp, stream.Duplex);
/** /**
* An EventEmitter that emits a `change` event when a task is either: * Take a "snapshot" of the Sharp instance, returning a new instance.
* - queued, waiting for _libuv_ to provide a worker thread * Cloned instances inherit the input of their parent instance.
* - complete * This allows multiple output Streams and therefore multiple processing pipelines to share a single input Stream.
* @member *
* @example * @example
* sharp.queue.on('change', function(queueLength) { * const pipeline = sharp().rotate();
* console.log('Queue contains ' + queueLength + ' task(s)'); * pipeline.clone().resize(800, 600).pipe(firstWritableStream);
* pipeline.clone().extract({ left: 20, top: 20, width: 100, height: 100 }).pipe(secondWritableStream);
* readableStream.pipe(pipeline);
* // firstWritableStream receives auto-rotated, resized readableStream
* // secondWritableStream receives auto-rotated, extracted region of readableStream
*
* @example
* // Create a pipeline that will download an image, resize it and format it to different files
* // Using Promises to know when the pipeline is complete
* const fs = require("fs");
* const got = require("got");
* const sharpStream = sharp({
* failOnError: false
* }); * });
*
* const promises = [];
*
* promises.push(
* sharpStream
* .clone()
* .jpeg({ quality: 100 })
* .toFile("originalFile.jpg")
* );
*
* promises.push(
* sharpStream
* .clone()
* .resize({ width: 500 })
* .jpeg({ quality: 80 })
* .toFile("optimized-500.jpg")
* );
*
* promises.push(
* sharpStream
* .clone()
* .resize({ width: 500 })
* .webp({ quality: 80 })
* .toFile("optimized-500.webp")
* );
*
* // https://github.com/sindresorhus/got#gotstreamurl-options
* got.stream("https://www.example.com/some-file.jpg").pipe(sharpStream);
*
* Promise.all(promises)
* .then(res => { console.log("Done!", res); })
* .catch(err => {
* console.error("Error processing files, let's clean it up", err);
* try {
* fs.unlinkSync("originalFile.jpg");
* fs.unlinkSync("optimized-500.jpg");
* fs.unlinkSync("optimized-500.webp");
* } catch (e) {}
* });
*
* @returns {Sharp}
*/ */
const queue = new events.EventEmitter(); function clone () {
Sharp.queue = queue; // Clone existing options
const clone = this.constructor.call();
/** clone.options = Object.assign({}, this.options);
* An Object containing nested boolean values representing the available input and output formats/methods. // Pass 'finish' event to clone for Stream-based input
* @example if (this._isStreamInput()) {
* console.log(sharp.format); this.on('finish', () => {
* @returns {Object} // Clone inherits input data
*/ this._flattenBufferIn();
Sharp.format = sharp.format(); clone.options.bufferIn = this.options.bufferIn;
clone.emit('finish');
/** });
* An Object containing the version numbers of libvips and its dependencies. }
* @member return clone;
* @example }
* console.log(sharp.versions); Object.assign(Sharp.prototype, { clone });
*/
Sharp.versions = {
vips: sharp.libvipsVersion()
};
try {
Sharp.versions = require('../vendor/versions.json');
} catch (err) {}
/** /**
* Export constructor. * Export constructor.

View File

@@ -4,12 +4,27 @@ const color = require('color');
const is = require('./is'); const is = require('./is');
const sharp = require('../build/Release/sharp.node'); const sharp = require('../build/Release/sharp.node');
/**
* Extract input options, if any, from an object.
* @private
*/
function _inputOptionsFromObject (obj) {
const { raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages } = obj;
return [raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages].some(is.defined)
? { raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages }
: undefined;
}
/** /**
* Create Object containing input and input-related options. * Create Object containing input and input-related options.
* @private * @private
*/ */
function _createInputDescriptor (input, inputOptions, containerOptions) { function _createInputDescriptor (input, inputOptions, containerOptions) {
const inputDescriptor = { failOnError: true }; const inputDescriptor = {
failOnError: true,
limitInputPixels: Math.pow(0x3FFF, 2),
sequentialRead: false
};
if (is.string(input)) { if (is.string(input)) {
// filesystem // filesystem
inputDescriptor.file = input; inputDescriptor.file = input;
@@ -19,15 +34,17 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
} else if (is.plainObject(input) && !is.defined(inputOptions)) { } else if (is.plainObject(input) && !is.defined(inputOptions)) {
// Plain Object descriptor, e.g. create // Plain Object descriptor, e.g. create
inputOptions = input; inputOptions = input;
if (is.plainObject(inputOptions.raw) || is.bool(inputOptions.failOnError)) { if (_inputOptionsFromObject(inputOptions)) {
// Raw Stream // Stream with options
inputDescriptor.buffer = []; inputDescriptor.buffer = [];
} }
} else if (!is.defined(input) && is.object(containerOptions) && containerOptions.allowStream) { } else if (!is.defined(input) && !is.defined(inputOptions) && is.object(containerOptions) && containerOptions.allowStream) {
// Stream // Stream without options
inputDescriptor.buffer = []; inputDescriptor.buffer = [];
} else { } else {
throw new Error('Unsupported input ' + 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)) { if (is.object(inputOptions)) {
// Fail on error // Fail on error
@@ -40,10 +57,30 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
} }
// Density // Density
if (is.defined(inputOptions.density)) { if (is.defined(inputOptions.density)) {
if (is.inRange(inputOptions.density, 1, 2400)) { if (is.inRange(inputOptions.density, 1, 100000)) {
inputDescriptor.density = inputOptions.density; inputDescriptor.density = inputOptions.density;
} else { } else {
throw is.invalidParameterError('density', 'number between 1 and 2400', inputOptions.density); throw is.invalidParameterError('density', 'number between 1 and 100000', inputOptions.density);
}
}
// limitInputPixels
if (is.defined(inputOptions.limitInputPixels)) {
if (is.bool(inputOptions.limitInputPixels)) {
inputDescriptor.limitInputPixels = inputOptions.limitInputPixels
? Math.pow(0x3FFF, 2)
: 0;
} else if (is.integer(inputOptions.limitInputPixels) && inputOptions.limitInputPixels >= 0) {
inputDescriptor.limitInputPixels = inputOptions.limitInputPixels;
} else {
throw is.invalidParameterError('limitInputPixels', 'integer >= 0', inputOptions.limitInputPixels);
}
}
// sequentialRead
if (is.defined(inputOptions.sequentialRead)) {
if (is.bool(inputOptions.sequentialRead)) {
inputDescriptor.sequentialRead = inputOptions.sequentialRead;
} else {
throw is.invalidParameterError('sequentialRead', 'boolean', inputOptions.sequentialRead);
} }
} }
// Raw pixel input // Raw pixel input
@@ -62,6 +99,13 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
} }
} }
// Multi-page input (GIF, TIFF, PDF) // Multi-page input (GIF, TIFF, PDF)
if (is.defined(inputOptions.animated)) {
if (is.bool(inputOptions.animated)) {
inputDescriptor.pages = inputOptions.animated ? -1 : 1;
} else {
throw is.invalidParameterError('animated', 'boolean', inputOptions.animated);
}
}
if (is.defined(inputOptions.pages)) { if (is.defined(inputOptions.pages)) {
if (is.integer(inputOptions.pages) && is.inRange(inputOptions.pages, -1, 100000)) { if (is.integer(inputOptions.pages) && is.inRange(inputOptions.pages, -1, 100000)) {
inputDescriptor.pages = inputOptions.pages; inputDescriptor.pages = inputOptions.pages;
@@ -76,6 +120,14 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
throw is.invalidParameterError('page', 'integer between 0 and 100000', inputOptions.page); throw is.invalidParameterError('page', 'integer between 0 and 100000', inputOptions.page);
} }
} }
// Multi-level input (OpenSlide)
if (is.defined(inputOptions.level)) {
if (is.integer(inputOptions.level) && is.inRange(inputOptions.level, 0, 256)) {
inputDescriptor.level = inputOptions.level;
} else {
throw is.invalidParameterError('level', 'integer between 0 and 256', inputOptions.level);
}
}
// Create new image // Create new image
if (is.defined(inputOptions.create)) { if (is.defined(inputOptions.create)) {
if ( if (
@@ -110,7 +162,7 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
* Handle incoming Buffer chunk on Writable Stream. * Handle incoming Buffer chunk on Writable Stream.
* @private * @private
* @param {Buffer} chunk * @param {Buffer} chunk
* @param {String} encoding - unused * @param {string} encoding - unused
* @param {Function} callback * @param {Function} callback
*/ */
function _write (chunk, encoding, callback) { function _write (chunk, encoding, callback) {
@@ -146,43 +198,12 @@ function _flattenBufferIn () {
/** /**
* Are we expecting Stream-based input? * Are we expecting Stream-based input?
* @private * @private
* @returns {Boolean} * @returns {boolean}
*/ */
function _isStreamInput () { function _isStreamInput () {
return Array.isArray(this.options.input.buffer); return Array.isArray(this.options.input.buffer);
} }
/**
* Take a "snapshot" of the Sharp instance, returning a new instance.
* Cloned instances inherit the input of their parent instance.
* This allows multiple output Streams and therefore multiple processing pipelines to share a single input Stream.
*
* @example
* const pipeline = sharp().rotate();
* pipeline.clone().resize(800, 600).pipe(firstWritableStream);
* pipeline.clone().extract({ left: 20, top: 20, width: 100, height: 100 }).pipe(secondWritableStream);
* readableStream.pipe(pipeline);
* // firstWritableStream receives auto-rotated, resized readableStream
* // secondWritableStream receives auto-rotated, extracted region of readableStream
*
* @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. * Fast access to (uncached) image metadata without decoding any compressed image data.
* A `Promise` is returned when `callback` is not provided. * A `Promise` is returned when `callback` is not provided.
@@ -191,15 +212,18 @@ function clone () {
* - `size`: Total size of image in bytes, for Stream and Buffer input only * - `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) * - `width`: Number of pixels wide (EXIF orientation is not taken into consideration)
* - `height`: Number of pixels high (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) * - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://libvips.github.io/libvips/API/current/VipsImage.html#VipsInterpretation)
* - `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK * - `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://libvips.github.io/libvips/API/current/VipsImage.html#VipsBandFormat)
* - `density`: Number of pixels per inch (DPI), if present * - `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 * - `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 * - `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 * - `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.
* - `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 * - `pagePrimary`: Number of the primary page in a HEIF image
* - `levels`: Details of each level in a multi-level image provided as an array of objects, requires libvips compiled with support for OpenSlide
* - `hasProfile`: Boolean indicating the presence of an embedded ICC profile * - `hasProfile`: Boolean indicating the presence of an embedded ICC profile
* - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel * - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
* - `orientation`: Number value of the EXIF Orientation header, if present * - `orientation`: Number value of the EXIF Orientation header, if present
@@ -280,8 +304,10 @@ function metadata (callback) {
* - `minY` (y-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) * - `maxX` (x-coordinate of one of the pixel where the maximum lies)
* - `maxY` (y-coordinate of one of the pixel where the maximum lies) * - `maxY` (y-coordinate of one of the pixel where the maximum lies)
* - `isOpaque`: Value to identify if the image is opaque or transparent, based on the presence and use of alpha channel * - `isOpaque`: Is the image fully opaque? Will be `true` if the image has no alpha channel or if every pixel is fully opaque.
* - `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental) * - `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental)
* - `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any (experimental)
* - `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram (experimental)
* *
* @example * @example
* const image = sharp(inputJpg); * const image = sharp(inputJpg);
@@ -291,6 +317,10 @@ function metadata (callback) {
* // stats contains the channel-wise statistics array and the isOpaque value * // stats contains the channel-wise statistics array and the isOpaque value
* }); * });
* *
* @example
* const { entropy, sharpness, dominant } = await sharp(input).stats();
* const { r, g, b } = dominant;
*
* @param {Function} [callback] - called with the arguments `(err, stats)` * @param {Function} [callback] - called with the arguments `(err, stats)`
* @returns {Promise<Object>} * @returns {Promise<Object>}
*/ */
@@ -333,43 +363,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. * Decorate the Sharp prototype with input-related functions.
* @private * @private
@@ -377,15 +370,13 @@ function sequentialRead (sequentialRead) {
module.exports = function (Sharp) { module.exports = function (Sharp) {
Object.assign(Sharp.prototype, { Object.assign(Sharp.prototype, {
// Private // Private
_inputOptionsFromObject,
_createInputDescriptor, _createInputDescriptor,
_write, _write,
_flattenBufferIn, _flattenBufferIn,
_isStreamInput, _isStreamInput,
// Public // Public
clone,
metadata, metadata,
stats, stats
limitInputPixels,
sequentialRead
}); });
}; };

View File

@@ -21,7 +21,7 @@ const object = function (val) {
* @private * @private
*/ */
const plainObject = function (val) { const plainObject = function (val) {
return object(val) && Object.prototype.toString.call(val) === '[object Object]'; return Object.prototype.toString.call(val) === '[object Object]';
}; };
/** /**
@@ -45,7 +45,7 @@ const bool = function (val) {
* @private * @private
*/ */
const buffer = function (val) { const buffer = function (val) {
return object(val) && val instanceof Buffer; return val instanceof Buffer;
}; };
/** /**
@@ -69,7 +69,7 @@ const number = function (val) {
* @private * @private
*/ */
const integer = function (val) { const integer = function (val) {
return number(val) && val % 1 === 0; return Number.isInteger(val);
}; };
/** /**
@@ -85,14 +85,14 @@ const inRange = function (val, min, max) {
* @private * @private
*/ */
const inArray = function (val, list) { const inArray = function (val, list) {
return list.indexOf(val) !== -1; return list.includes(val);
}; };
/** /**
* Create an Error with a message relating to an invalid parameter. * Create an Error with a message relating to an invalid parameter.
* *
* @param {String} name - parameter name. * @param {string} name - parameter name.
* @param {String} expected - description of the type/value/range expected. * @param {string} expected - description of the type/value/range expected.
* @param {*} actual - the value received. * @param {*} actual - the value received.
* @returns {Error} Containing the formatted message. * @returns {Error} Containing the formatted message.
* @private * @private

View File

@@ -8,8 +8,9 @@ const semver = require('semver');
const platform = require('./platform'); const platform = require('./platform');
const env = process.env; const env = process.env;
const minimumLibvipsVersion = env.npm_package_config_libvips || /* istanbul ignore next */ const minimumLibvipsVersionLabelled = env.npm_package_config_libvips || /* istanbul ignore next */
require('../package.json').config.libvips; require('../package.json').config.libvips;
const minimumLibvipsVersion = semver.coerce(minimumLibvipsVersionLabelled).version;
const spawnSyncOptions = { const spawnSyncOptions = {
encoding: 'utf8', encoding: 'utf8',
@@ -47,24 +48,18 @@ const globalLibvipsVersion = function () {
const hasVendoredLibvips = function () { const hasVendoredLibvips = function () {
const currentPlatformId = platform(); const currentPlatformId = platform();
const vendorPath = path.join(__dirname, '..', 'vendor'); const vendorPath = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion);
let vendorVersionId;
let vendorPlatformId; let vendorPlatformId;
try { try {
vendorVersionId = require(path.join(vendorPath, 'versions.json')).vips;
vendorPlatformId = require(path.join(vendorPath, 'platform.json')); vendorPlatformId = require(path.join(vendorPath, 'platform.json'));
} catch (err) {} } catch (err) {}
/* istanbul ignore if */
if (vendorVersionId && vendorVersionId !== minimumLibvipsVersion) {
throw new Error(`Found vendored libvips v${vendorVersionId} but require v${minimumLibvipsVersion}. Please remove the 'node_modules/sharp/vendor' directory and run 'npm install'.`);
}
/* istanbul ignore else */ /* istanbul ignore else */
if (vendorPlatformId) { if (vendorPlatformId) {
/* istanbul ignore else */ /* istanbul ignore else */
if (currentPlatformId === vendorPlatformId) { if (currentPlatformId === vendorPlatformId) {
return true; return true;
} else { } else {
throw new Error(`'${vendorPlatformId}' binaries cannot be used on the '${currentPlatformId}' platform. Please remove the 'node_modules/sharp/vendor' directory and run 'npm install'.`); throw new Error(`'${vendorPlatformId}' binaries cannot be used on the '${currentPlatformId}' platform. Please remove the 'node_modules/sharp' directory and run 'npm install' on the '${currentPlatformId}' platform.`);
} }
} else { } else {
return false; return false;
@@ -93,11 +88,12 @@ const useGlobalLibvips = function () {
}; };
module.exports = { module.exports = {
minimumLibvipsVersion: minimumLibvipsVersion, minimumLibvipsVersion,
cachePath: cachePath, minimumLibvipsVersionLabelled,
globalLibvipsVersion: globalLibvipsVersion, cachePath,
hasVendoredLibvips: hasVendoredLibvips, globalLibvipsVersion,
pkgConfigPath: pkgConfigPath, hasVendoredLibvips,
useGlobalLibvips: useGlobalLibvips, pkgConfigPath,
mkdirSync: mkdirSync useGlobalLibvips,
mkdirSync
}; };

View File

@@ -32,9 +32,9 @@ const is = require('./is');
* }); * });
* readableStream.pipe(pipeline); * readableStream.pipe(pipeline);
* *
* @param {Number} [angle=auto] angle of rotation. * @param {number} [angle=auto] angle of rotation.
* @param {Object} [options] - if present, is an Object with optional attributes. * @param {Object} [options] - if present, is an Object with optional attributes.
* @param {String|Object} [options.background="#000000"] parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. * @param {string|Object} [options.background="#000000"] parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -88,9 +88,9 @@ function flop (flop) {
* When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space. * When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space.
* Separate control over the level of sharpening in "flat" and "jagged" areas is available. * Separate control over the level of sharpening in "flat" and "jagged" areas is available.
* *
* @param {Number} [sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`. * @param {number} [sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
* @param {Number} [flat=1.0] - the level of sharpening to apply to "flat" areas. * @param {number} [flat=1.0] - the level of sharpening to apply to "flat" areas.
* @param {Number} [jagged=2.0] - the level of sharpening to apply to "jagged" areas. * @param {number} [jagged=2.0] - the level of sharpening to apply to "jagged" areas.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -129,7 +129,7 @@ function sharpen (sigma, flat, jagged) {
/** /**
* Apply median filter. * Apply median filter.
* When used without parameters the default window is 3x3. * When used without parameters the default window is 3x3.
* @param {Number} [size=3] square mask size: size x size * @param {number} [size=3] square mask size: size x size
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -150,7 +150,7 @@ function median (size) {
* Blur the image. * Blur the image.
* When used without parameters, performs a fast, mild blur of the output image. * When used without parameters, performs a fast, mild blur of the output image.
* When a `sigma` is provided, performs a slower, more accurate Gaussian blur. * When a `sigma` is provided, performs a slower, more accurate Gaussian blur.
* @param {Number} [sigma] a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`. * @param {number} [sigma] a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -173,7 +173,7 @@ function blur (sigma) {
/** /**
* Merge alpha transparency channel, if any, with a background. * Merge alpha transparency channel, if any, with a background.
* @param {Object} [options] * @param {Object} [options]
* @param {String|Object} [options.background={r: 0, g: 0, b: 0}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black. * @param {string|Object} [options.background={r: 0, g: 0, b: 0}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black.
* @returns {Sharp} * @returns {Sharp}
*/ */
function flatten (options) { function flatten (options) {
@@ -193,8 +193,8 @@ function flatten (options) {
* *
* Supply a second argument to use a different output gamma value, otherwise the first value is used in both cases. * Supply a second argument to use a different output gamma value, otherwise the first value is used in both cases.
* *
* @param {Number} [gamma=2.2] value between 1.0 and 3.0. * @param {number} [gamma=2.2] value between 1.0 and 3.0.
* @param {Number} [gammaOut] value between 1.0 and 3.0. (optional, defaults to same as `gamma`) * @param {number} [gammaOut] value between 1.0 and 3.0. (optional, defaults to same as `gamma`)
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -264,11 +264,11 @@ function normalize (normalize) {
* }); * });
* *
* @param {Object} kernel * @param {Object} kernel
* @param {Number} kernel.width - width of the kernel in pixels. * @param {number} kernel.width - width of the kernel in pixels.
* @param {Number} kernel.height - width of the kernel in pixels. * @param {number} kernel.height - width of the kernel in pixels.
* @param {Array<Number>} kernel.kernel - Array of length `width*height` containing the kernel values. * @param {Array<number>} kernel.kernel - Array of length `width*height` containing the kernel values.
* @param {Number} [kernel.scale=sum] - the scale of the kernel in pixels. * @param {number} [kernel.scale=sum] - the scale of the kernel in pixels.
* @param {Number} [kernel.offset=0] - the offset of the kernel in pixels. * @param {number} [kernel.offset=0] - the offset of the kernel in pixels.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -300,7 +300,7 @@ function convolve (kernel) {
/** /**
* Any pixel value greather than or equal to the threshold value will be set to 255, otherwise it will be set to 0. * Any pixel value greather than or equal to the threshold value will be set to 255, otherwise it will be set to 0.
* @param {Number} [threshold=128] - a value in the range 0-255 representing the level at which the threshold will be applied. * @param {number} [threshold=128] - a value in the range 0-255 representing the level at which the threshold will be applied.
* @param {Object} [options] * @param {Object} [options]
* @param {Boolean} [options.greyscale=true] - convert to single channel greyscale. * @param {Boolean} [options.greyscale=true] - convert to single channel greyscale.
* @param {Boolean} [options.grayscale=true] - alternative spelling for greyscale. * @param {Boolean} [options.grayscale=true] - alternative spelling for greyscale.
@@ -331,13 +331,13 @@ function threshold (threshold, options) {
* This operation creates an output image where each pixel is the result of * This operation creates an output image where each pixel is the result of
* the selected bitwise boolean `operation` between the corresponding pixels of the input images. * the selected bitwise boolean `operation` between the corresponding pixels of the input images.
* *
* @param {Buffer|String} operand - Buffer containing image data or String containing the path to an image file. * @param {Buffer|string} operand - Buffer containing image data or string containing the path to an image file.
* @param {String} operator - one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively. * @param {string} operator - one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
* @param {Object} [options] * @param {Object} [options]
* @param {Object} [options.raw] - describes operand when using raw pixel data. * @param {Object} [options.raw] - describes operand when using raw pixel data.
* @param {Number} [options.raw.width] * @param {number} [options.raw.width]
* @param {Number} [options.raw.height] * @param {number} [options.raw.height]
* @param {Number} [options.raw.channels] * @param {number} [options.raw.channels]
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -353,8 +353,8 @@ function boolean (operand, operator, options) {
/** /**
* Apply the linear formula a * input + b to the image (levels adjustment) * Apply the linear formula a * input + b to the image (levels adjustment)
* @param {Number} [a=1.0] multiplier * @param {number} [a=1.0] multiplier
* @param {Number} [b=0.0] offset * @param {number} [b=0.0] offset
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -379,6 +379,8 @@ function linear (a, b) {
/** /**
* Recomb the image with the specified matrix. * Recomb the image with the specified matrix.
* *
* @since 0.21.1
*
* @example * @example
* sharp(input) * sharp(input)
* .recomb([ * .recomb([
@@ -392,7 +394,7 @@ function linear (a, b) {
* // With this example input, a sepia filter has been applied * // With this example input, a sepia filter has been applied
* }); * });
* *
* @param {Array<Array<Number>>} 3x3 Recombination matrix * @param {Array<Array<number>>} inputMatrix - 3x3 Recombination matrix
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -416,6 +418,8 @@ function recomb (inputMatrix) {
/** /**
* Transforms the image using brightness, saturation and hue rotation. * Transforms the image using brightness, saturation and hue rotation.
* *
* @since 0.22.1
*
* @example * @example
* sharp(input) * sharp(input)
* .modulate({ * .modulate({
@@ -436,9 +440,9 @@ function recomb (inputMatrix) {
* }); * });
* *
* @param {Object} [options] * @param {Object} [options]
* @param {Number} [options.brightness] Brightness multiplier * @param {number} [options.brightness] Brightness multiplier
* @param {Number} [options.saturation] Saturation multiplier * @param {number} [options.saturation] Saturation multiplier
* @param {Number} [options.hue] Degrees for hue rotation * @param {number} [options.hue] Degrees for hue rotation
* @returns {Sharp} * @returns {Sharp}
*/ */
function modulate (options) { function modulate (options) {

View File

@@ -3,6 +3,18 @@
const is = require('./is'); const is = require('./is');
const sharp = require('../build/Release/sharp.node'); const sharp = require('../build/Release/sharp.node');
const formats = new Map([
['heic', 'heif'],
['heif', 'heif'],
['jpeg', 'jpeg'],
['jpg', 'jpeg'],
['png', 'png'],
['raw', 'raw'],
['tiff', 'tiff'],
['webp', 'webp'],
['gif', 'gif']
]);
/** /**
* Write output image data to a file. * Write output image data to a file.
* *
@@ -25,7 +37,7 @@ const sharp = require('../build/Release/sharp.node');
* .then(info => { ... }) * .then(info => { ... })
* .catch(err => { ... }); * .catch(err => { ... });
* *
* @param {String} fileOut - the path to write the image data to. * @param {string} fileOut - the path to write the image data to.
* @param {Function} [callback] - called on completion with two arguments `(err, info)`. * @param {Function} [callback] - called on completion with two arguments `(err, info)`.
* `info` contains the output image `format`, `size` (bytes), `width`, `height`, * `info` contains the output image `format`, `size` (bytes), `width`, `height`,
* `channels` and `premultiplied` (indicating if premultiplication was used). * `channels` and `premultiplied` (indicating if premultiplication was used).
@@ -92,7 +104,7 @@ function toFile (fileOut, callback) {
* .catch(err => { ... }); * .catch(err => { ... });
* *
* @param {Object} [options] * @param {Object} [options]
* @param {Boolean} [options.resolveWithObject] Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`. * @param {boolean} [options.resolveWithObject] Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
* @param {Function} [callback] * @param {Function} [callback]
* @returns {Promise<Buffer>} - when no callback is provided * @returns {Promise<Buffer>} - when no callback is provided
*/ */
@@ -107,8 +119,11 @@ function toBuffer (options, callback) {
/** /**
* Include all metadata (EXIF, XMP, IPTC) from the input image in the output image. * Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
* The default behaviour, when `withMetadata` is not used, is to strip all metadata and convert to the device-independent sRGB colour space. * This will also convert to and add a web-friendly sRGB ICC profile unless a custom
* This will also convert to and add a web-friendly sRGB ICC profile. * output profile is provided.
*
* The default behaviour, when `withMetadata` is not used, is to convert to the device-independent
* sRGB colour space and strip all metadata, including the removal of any ICC profile.
* *
* @example * @example
* sharp('input.jpg') * sharp('input.jpg')
@@ -117,7 +132,8 @@ function toBuffer (options, callback) {
* .then(info => { ... }); * .then(info => { ... });
* *
* @param {Object} [options] * @param {Object} [options]
* @param {Number} [options.orientation] value between 1 and 8, used to update the EXIF `Orientation` tag. * @param {number} [options.orientation] value between 1 and 8, used to update the EXIF `Orientation` tag.
* @param {string} [options.icc] filesystem path to output ICC profile, defaults to sRGB.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -131,13 +147,44 @@ function withMetadata (options) {
throw is.invalidParameterError('orientation', 'integer between 1 and 8', options.orientation); throw is.invalidParameterError('orientation', 'integer between 1 and 8', options.orientation);
} }
} }
if (is.defined(options.icc)) {
if (is.string(options.icc)) {
this.options.withMetadataIcc = options.icc;
} else {
throw is.invalidParameterError('icc', 'string filesystem path to ICC profile', options.icc);
}
}
} }
return this; return this;
} }
/**
* Force output to a given format.
*
* @example
* // Convert any input to PNG output
* const data = await sharp(input)
* .toFormat('png')
* .toBuffer();
*
* @param {(string|Object)} format - as a string or an Object with an 'id' attribute
* @param {Object} options - output options
* @returns {Sharp}
* @throws {Error} unsupported format or options
*/
function toFormat (format, options) {
const actualFormat = formats.get(is.object(format) && is.string(format.id) ? format.id : format);
if (!actualFormat) {
throw is.invalidParameterError('format', `one of: ${[...formats.keys()].join(', ')}`, format);
}
return this[actualFormat](options);
}
/** /**
* Use these JPEG options for output image. * Use these JPEG options for output image.
* *
* Some of these options require the use of a globally-installed libvips compiled with support for mozjpeg.
*
* @example * @example
* // Convert any input to very high quality JPEG output * // Convert any input to very high quality JPEG output
* const data = await sharp(input) * const data = await sharp(input)
@@ -148,18 +195,18 @@ function withMetadata (options) {
* .toBuffer(); * .toBuffer();
* *
* @param {Object} [options] - output options * @param {Object} [options] - output options
* @param {Number} [options.quality=80] - quality, integer 1-100 * @param {number} [options.quality=80] - quality, integer 1-100
* @param {Boolean} [options.progressive=false] - use progressive (interlace) scan * @param {boolean} [options.progressive=false] - use progressive (interlace) scan
* @param {String} [options.chromaSubsampling='4:2:0'] - set to '4:4:4' to prevent chroma subsampling when quality <= 90 * @param {string} [options.chromaSubsampling='4:2:0'] - set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling
* @param {Boolean} [options.trellisQuantisation=false] - apply trellis quantisation, requires libvips compiled with support for mozjpeg * @param {boolean} [options.optimiseCoding=true] - optimise Huffman coding tables
* @param {Boolean} [options.overshootDeringing=false] - apply overshoot deringing, requires libvips compiled with support for mozjpeg * @param {boolean} [options.optimizeCoding=true] - alternative spelling of optimiseCoding
* @param {Boolean} [options.optimiseScans=false] - optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg * @param {boolean} [options.trellisQuantisation=false] - apply trellis quantisation, requires libvips compiled with support for mozjpeg
* @param {Boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans * @param {boolean} [options.overshootDeringing=false] - apply overshoot deringing, requires libvips compiled with support for mozjpeg
* @param {Boolean} [options.optimiseCoding=true] - optimise Huffman coding tables * @param {boolean} [options.optimiseScans=false] - optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg
* @param {Boolean} [options.optimizeCoding=true] - alternative spelling of optimiseCoding * @param {boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans, requires libvips compiled with support for mozjpeg
* @param {Number} [options.quantisationTable=0] - quantization table to use, integer 0-8, requires libvips compiled with support for mozjpeg * @param {number} [options.quantisationTable=0] - quantization table to use, integer 0-8, requires libvips compiled with support for mozjpeg
* @param {Number} [options.quantizationTable=0] - alternative spelling of quantisationTable * @param {number} [options.quantizationTable=0] - alternative spelling of quantisationTable, requires libvips compiled with support for mozjpeg
* @param {Boolean} [options.force=true] - force JPEG output, otherwise attempt to use input format * @param {boolean} [options.force=true] - force JPEG output, otherwise attempt to use input format
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
@@ -218,6 +265,8 @@ function jpeg (options) {
* PNG output is always full colour at 8 or 16 bits per pixel. * PNG output is always full colour at 8 or 16 bits per pixel.
* Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel. * Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel.
* *
* Some of these options require the use of a globally-installed libvips compiled with support for libimagequant (GPL).
*
* @example * @example
* // Convert any input to PNG output * // Convert any input to PNG output
* const data = await sharp(input) * const data = await sharp(input)
@@ -225,15 +274,15 @@ function jpeg (options) {
* .toBuffer(); * .toBuffer();
* *
* @param {Object} [options] * @param {Object} [options]
* @param {Boolean} [options.progressive=false] - use progressive (interlace) scan * @param {boolean} [options.progressive=false] - use progressive (interlace) scan
* @param {Number} [options.compressionLevel=9] - zlib compression level, 0-9 * @param {number} [options.compressionLevel=9] - zlib compression level, 0-9
* @param {Boolean} [options.adaptiveFiltering=false] - use adaptive row filtering * @param {boolean} [options.adaptiveFiltering=false] - use adaptive row filtering
* @param {Boolean} [options.palette=false] - quantise to a palette-based image with alpha transparency support, requires libvips compiled with support for libimagequant * @param {boolean} [options.palette=false] - quantise to a palette-based image with alpha transparency support, requires libvips compiled with support for libimagequant
* @param {Number} [options.quality=100] - use the lowest number of colours needed to achieve given quality, requires libvips compiled with support for libimagequant * @param {number} [options.quality=100] - use the lowest number of colours needed to achieve given quality, sets `palette` to `true`, requires libvips compiled with support for libimagequant
* @param {Number} [options.colours=256] - maximum number of palette entries, requires libvips compiled with support for libimagequant * @param {number} [options.colours=256] - maximum number of palette entries, sets `palette` to `true`, requires libvips compiled with support for libimagequant
* @param {Number} [options.colors=256] - alternative spelling of `options.colours`, requires libvips compiled with support for libimagequant * @param {number} [options.colors=256] - alternative spelling of `options.colours`, sets `palette` to `true`, requires libvips compiled with support for libimagequant
* @param {Number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, requires libvips compiled with support for libimagequant * @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, sets `palette` to `true`, requires libvips compiled with support for libimagequant
* @param {Boolean} [options.force=true] - force PNG output, otherwise attempt to use input format * @param {boolean} [options.force=true] - force PNG output, otherwise attempt to use input format
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
@@ -254,28 +303,30 @@ function png (options) {
} }
if (is.defined(options.palette)) { if (is.defined(options.palette)) {
this._setBooleanOption('pngPalette', options.palette); this._setBooleanOption('pngPalette', options.palette);
if (this.options.pngPalette) { } else if (is.defined(options.quality) || is.defined(options.colours || options.colors) || is.defined(options.dither)) {
if (is.defined(options.quality)) { this._setBooleanOption('pngPalette', true);
if (is.integer(options.quality) && is.inRange(options.quality, 0, 100)) { }
this.options.pngQuality = options.quality; if (this.options.pngPalette) {
} else { if (is.defined(options.quality)) {
throw is.invalidParameterError('quality', 'integer between 0 and 100', options.quality); if (is.integer(options.quality) && is.inRange(options.quality, 0, 100)) {
} this.options.pngQuality = options.quality;
} else {
throw is.invalidParameterError('quality', 'integer between 0 and 100', options.quality);
} }
const colours = options.colours || options.colors; }
if (is.defined(colours)) { const colours = options.colours || options.colors;
if (is.integer(colours) && is.inRange(colours, 2, 256)) { if (is.defined(colours)) {
this.options.pngColours = colours; if (is.integer(colours) && is.inRange(colours, 2, 256)) {
} else { this.options.pngColours = colours;
throw is.invalidParameterError('colours', 'integer between 2 and 256', colours); } else {
} throw is.invalidParameterError('colours', 'integer between 2 and 256', colours);
} }
if (is.defined(options.dither)) { }
if (is.number(options.dither) && is.inRange(options.dither, 0, 1)) { if (is.defined(options.dither)) {
this.options.pngDither = options.dither; if (is.number(options.dither) && is.inRange(options.dither, 0, 1)) {
} else { this.options.pngDither = options.dither;
throw is.invalidParameterError('dither', 'number between 0.0 and 1.0', options.dither); } else {
} throw is.invalidParameterError('dither', 'number between 0.0 and 1.0', options.dither);
} }
} }
} }
@@ -293,13 +344,16 @@ function png (options) {
* .toBuffer(); * .toBuffer();
* *
* @param {Object} [options] - output options * @param {Object} [options] - output options
* @param {Number} [options.quality=80] - quality, integer 1-100 * @param {number} [options.quality=80] - quality, integer 1-100
* @param {Number} [options.alphaQuality=100] - quality of alpha layer, integer 0-100 * @param {number} [options.alphaQuality=100] - quality of alpha layer, integer 0-100
* @param {Boolean} [options.lossless=false] - use lossless compression mode * @param {boolean} [options.lossless=false] - use lossless compression mode
* @param {Boolean} [options.nearLossless=false] - use near_lossless compression mode * @param {boolean} [options.nearLossless=false] - use near_lossless compression mode
* @param {Boolean} [options.smartSubsample=false] - use high quality chroma subsampling * @param {boolean} [options.smartSubsample=false] - use high quality chroma subsampling
* @param {Number} [options.reductionEffort=4] - level of CPU effort to reduce file size, integer 0-6 * @param {number} [options.reductionEffort=4] - level of CPU effort to reduce file size, integer 0-6
* @param {Boolean} [options.force=true] - force WebP output, otherwise attempt to use input format * @param {number} [options.pageHeight] - page height for animated output
* @param {number} [options.loop=0] - number of animation iterations, use 0 for infinite animation
* @param {number[]} [options.delay] - list of delays between animation frames (in milliseconds)
* @param {boolean} [options.force=true] - force WebP output, otherwise attempt to use input format
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
@@ -334,9 +388,73 @@ function webp (options) {
throw is.invalidParameterError('reductionEffort', 'integer between 0 and 6', options.reductionEffort); throw is.invalidParameterError('reductionEffort', 'integer between 0 and 6', options.reductionEffort);
} }
} }
trySetAnimationOptions(options, this.options);
return this._updateFormatOut('webp', options); return this._updateFormatOut('webp', options);
} }
/**
* Use these GIF options for output image.
*
* Requires libvips compiled with support for ImageMagick or GraphicsMagick.
* The prebuilt binaries do not include this - see
* {@link https://sharp.pixelplumbing.com/install#custom-libvips installing a custom libvips}.
*
* @param {Object} [options] - output options
* @param {number} [options.pageHeight] - page height for animated output
* @param {number} [options.loop=0] - number of animation iterations, use 0 for infinite animation
* @param {number[]} [options.delay] - list of delays between animation frames (in milliseconds)
* @param {boolean} [options.force=true] - force GIF output, otherwise attempt to use input format
* @returns {Sharp}
* @throws {Error} Invalid options
*/
/* istanbul ignore next */
function gif (options) {
if (!this.constructor.format.magick.output.buffer) {
throw new Error('The gif operation requires libvips to have been installed with support for ImageMagick');
}
trySetAnimationOptions(options, this.options);
return this._updateFormatOut('gif', options);
}
/**
* Set animation options if available.
* @private
*
* @param {Object} [source] - output options
* @param {number} [source.pageHeight] - page height for animated output
* @param {number} [source.loop=0] - number of animation iterations, use 0 for infinite animation
* @param {number[]} [source.delay] - list of delays between animation frames (in milliseconds)
* @param {Object} [target] - target object for valid options
* @throws {Error} Invalid options
*/
function trySetAnimationOptions (source, target) {
if (is.object(source) && is.defined(source.pageHeight)) {
if (is.integer(source.pageHeight) && source.pageHeight > 0) {
target.pageHeight = source.pageHeight;
} else {
throw is.invalidParameterError('pageHeight', 'integer larger than 0', source.pageHeight);
}
}
if (is.object(source) && is.defined(source.loop)) {
if (is.integer(source.loop) && is.inRange(source.loop, 0, 65535)) {
target.loop = source.loop;
} else {
throw is.invalidParameterError('loop', 'integer between 0 and 65535', source.loop);
}
}
if (is.object(source) && is.defined(source.delay)) {
if (
Array.isArray(source.delay) &&
source.delay.every(is.integer) &&
source.delay.every(v => is.inRange(v, 0, 65535))) {
target.delay = source.delay;
} else {
throw is.invalidParameterError('delay', 'array of integers between 0 and 65535', source.delay);
}
}
}
/** /**
* Use these TIFF options for output image. * Use these TIFF options for output image.
* *
@@ -345,23 +463,23 @@ function webp (options) {
* sharp('input.svg') * sharp('input.svg')
* .tiff({ * .tiff({
* compression: 'lzw', * compression: 'lzw',
* squash: true * bitdepth: 1
* }) * })
* .toFile('1-bpp-output.tiff') * .toFile('1-bpp-output.tiff')
* .then(info => { ... }); * .then(info => { ... });
* *
* @param {Object} [options] - output options * @param {Object} [options] - output options
* @param {Number} [options.quality=80] - quality, integer 1-100 * @param {number} [options.quality=80] - quality, integer 1-100
* @param {Boolean} [options.force=true] - force TIFF output, otherwise attempt to use input format * @param {boolean} [options.force=true] - force TIFF output, otherwise attempt to use input format
* @param {Boolean} [options.compression='jpeg'] - compression options: lzw, deflate, jpeg, ccittfax4 * @param {boolean} [options.compression='jpeg'] - compression options: lzw, deflate, jpeg, ccittfax4
* @param {Boolean} [options.predictor='horizontal'] - compression predictor options: none, horizontal, float * @param {boolean} [options.predictor='horizontal'] - compression predictor options: none, horizontal, float
* @param {Boolean} [options.pyramid=false] - write an image pyramid * @param {boolean} [options.pyramid=false] - write an image pyramid
* @param {Boolean} [options.tile=false] - write a tiled tiff * @param {boolean} [options.tile=false] - write a tiled tiff
* @param {Boolean} [options.tileWidth=256] - horizontal tile size * @param {boolean} [options.tileWidth=256] - horizontal tile size
* @param {Boolean} [options.tileHeight=256] - vertical tile size * @param {boolean} [options.tileHeight=256] - vertical tile size
* @param {Number} [options.xres=1.0] - horizontal resolution in pixels/mm * @param {number} [options.xres=1.0] - horizontal resolution in pixels/mm
* @param {Number} [options.yres=1.0] - vertical resolution in pixels/mm * @param {number} [options.yres=1.0] - vertical resolution in pixels/mm
* @param {Boolean} [options.squash=false] - squash 8-bit images down to 1 bit * @param {boolean} [options.bitdepth=8] - reduce bitdepth to 1, 2 or 4 bit
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
@@ -374,8 +492,12 @@ function tiff (options) {
throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality); throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
} }
} }
if (is.defined(options.squash)) { if (is.defined(options.bitdepth)) {
this._setBooleanOption('tiffSquash', options.squash); if (is.integer(options.bitdepth) && is.inArray(options.bitdepth, [1, 2, 4, 8])) {
this.options.tiffBitdepth = options.bitdepth;
} else {
throw is.invalidParameterError('bitdepth', '1, 2, 4 or 8', options.bitdepth);
}
} }
// tiling // tiling
if (is.defined(options.tile)) { if (is.defined(options.tile)) {
@@ -444,10 +566,12 @@ function tiff (options) {
* *
* Most versions of libheif support only the patent-encumbered HEVC compression format. * Most versions of libheif support only the patent-encumbered HEVC compression format.
* *
* @since 0.23.0
*
* @param {Object} [options] - output options * @param {Object} [options] - output options
* @param {Number} [options.quality=80] - quality, integer 1-100 * @param {number} [options.quality=80] - quality, integer 1-100
* @param {Boolean} [options.compression='hevc'] - compression format: hevc, avc, jpeg, av1 * @param {boolean} [options.compression='hevc'] - compression format: hevc, avc, jpeg, av1
* @param {Boolean} [options.lossless=false] - use lossless compression * @param {boolean} [options.lossless=false] - use lossless compression
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
@@ -482,7 +606,9 @@ function heif (options) {
} }
/** /**
* Force output to be raw, uncompressed uint8 pixel data. * Force output to be raw, uncompressed, 8-bit unsigned integer (unit8) pixel data.
* Pixel ordering is left-to-right, top-to-bottom, without padding.
* Channel ordering will be RGB or RGBA for non-greyscale colourspaces.
* *
* @example * @example
* // Extract raw RGB pixel data from JPEG input * // Extract raw RGB pixel data from JPEG input
@@ -490,45 +616,21 @@ function heif (options) {
* .raw() * .raw()
* .toBuffer({ resolveWithObject: true }); * .toBuffer({ resolveWithObject: true });
* *
* @example
* // Extract alpha channel as raw pixel data from PNG input
* const data = await sharp('input.png')
* .ensureAlpha()
* .extractChannel(3)
* .toColourspace('b-w')
* .raw()
* .toBuffer();
*
* @returns {Sharp} * @returns {Sharp}
*/ */
function raw () { function raw () {
return this._updateFormatOut('raw'); return this._updateFormatOut('raw');
} }
const formats = new Map([
['heic', 'heif'],
['heif', 'heif'],
['jpeg', 'jpeg'],
['jpg', 'jpeg'],
['png', 'png'],
['raw', 'raw'],
['tiff', 'tiff'],
['webp', 'webp']
]);
/**
* Force output to a given format.
*
* @example
* // Convert any input to PNG output
* const data = await sharp(input)
* .toFormat('png')
* .toBuffer();
*
* @param {(String|Object)} format - as a String or an Object with an 'id' attribute
* @param {Object} options - output options
* @returns {Sharp}
* @throws {Error} unsupported format or options
*/
function toFormat (format, options) {
const actualFormat = formats.get(is.object(format) && is.string(format.id) ? format.id : format);
if (!actualFormat) {
throw is.invalidParameterError('format', `one of: ${[...formats.keys()].join(', ')}`, format);
}
return this[actualFormat](options);
}
/** /**
* Use tile-based deep zoom (image pyramid) output. * Use tile-based deep zoom (image pyramid) output.
* Set the format and options for tile images via the `toFormat`, `jpeg`, `png` or `webp` functions. * Set the format and options for tile images via the `toFormat`, `jpeg`, `png` or `webp` functions.
@@ -548,14 +650,14 @@ function toFormat (format, options) {
* }); * });
* *
* @param {Object} [options] * @param {Object} [options]
* @param {Number} [options.size=256] tile size in pixels, a value between 1 and 8192. * @param {number} [options.size=256] tile size in pixels, a value between 1 and 8192.
* @param {Number} [options.overlap=0] tile overlap in pixels, a value between 0 and 8192. * @param {number} [options.overlap=0] tile overlap in pixels, a value between 0 and 8192.
* @param {Number} [options.angle=0] tile angle of rotation, must be a multiple of 90. * @param {number} [options.angle=0] tile angle of rotation, must be a multiple of 90.
* @param {String|Object} [options.background={r: 255, g: 255, b: 255, alpha: 1}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to white without transparency. * @param {string|Object} [options.background={r: 255, g: 255, b: 255, alpha: 1}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to white without transparency.
* @param {String} [options.depth] how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout. * @param {string} [options.depth] how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
* @param {Number} [options.skipBlanks=-1] threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images * @param {number} [options.skipBlanks=-1] threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images
* @param {String} [options.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file). * @param {string} [options.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file).
* @param {String} [options.layout='dz'] filesystem layout, possible values are `dz`, `zoomify` or `google`. * @param {string} [options.layout='dz'] filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -590,10 +692,10 @@ function tile (options) {
} }
// Layout // Layout
if (is.defined(options.layout)) { if (is.defined(options.layout)) {
if (is.string(options.layout) && is.inArray(options.layout, ['dz', 'google', 'zoomify'])) { if (is.string(options.layout) && is.inArray(options.layout, ['dz', 'google', 'iiif', 'zoomify'])) {
this.options.tileLayout = options.layout; this.options.tileLayout = options.layout;
} else { } else {
throw is.invalidParameterError('layout', 'one of: dz, google, zoomify', options.layout); throw is.invalidParameterError('layout', 'one of: dz, google, iiif, zoomify', options.layout);
} }
} }
// Angle of rotation, // Angle of rotation,
@@ -638,9 +740,9 @@ function tile (options) {
* Update the output format unless options.force is false, * Update the output format unless options.force is false,
* in which case revert to input format. * in which case revert to input format.
* @private * @private
* @param {String} formatOut * @param {string} formatOut
* @param {Object} [options] * @param {Object} [options]
* @param {Boolean} [options.force=true] - force output format, otherwise attempt to use input format * @param {boolean} [options.force=true] - force output format, otherwise attempt to use input format
* @returns {Sharp} * @returns {Sharp}
*/ */
function _updateFormatOut (formatOut, options) { function _updateFormatOut (formatOut, options) {
@@ -651,10 +753,10 @@ function _updateFormatOut (formatOut, options) {
} }
/** /**
* Update a Boolean attribute of the this.options Object. * Update a boolean attribute of the this.options Object.
* @private * @private
* @param {String} key * @param {string} key
* @param {Boolean} val * @param {boolean} val
* @throws {Error} Invalid key * @throws {Error} Invalid key
*/ */
function _setBooleanOption (key, val) { function _setBooleanOption (key, val) {
@@ -777,13 +879,14 @@ module.exports = function (Sharp) {
toFile, toFile,
toBuffer, toBuffer,
withMetadata, withMetadata,
toFormat,
jpeg, jpeg,
png, png,
webp, webp,
tiff, tiff,
heif, heif,
gif,
raw, raw,
toFormat,
tile, tile,
// Private // Private
_updateFormatOut, _updateFormatOut,

View File

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

View File

@@ -85,21 +85,30 @@ const mapFitToCanvas = {
outside: 'min' outside: 'min'
}; };
/**
* @private
*/
function isRotationExpected (options) {
return (options.angle % 360) !== 0 || options.useExifOrientation === true || options.rotationAngle !== 0;
}
/** /**
* Resize image to `width`, `height` or `width x height`. * Resize image to `width`, `height` or `width x height`.
* *
* When both a `width` and `height` are provided, the possible methods by which the image should **fit** these are: * When both a `width` and `height` are provided, the possible methods by which the image should **fit** these are:
* - `cover`: Crop to cover both provided dimensions (the default). * - `cover`: (default) Preserving aspect ratio, ensure the image covers both provided dimensions by cropping/clipping to fit.
* - `contain`: Embed within both provided dimensions. * - `contain`: Preserving aspect ratio, contain within both provided dimensions using "letterboxing" where necessary.
* - `fill`: Ignore the aspect ratio of the input and stretch to both provided dimensions. * - `fill`: Ignore the aspect ratio of the input and stretch to both provided dimensions.
* - `inside`: Preserving aspect ratio, resize the image to be as large as possible while ensuring its dimensions are less than or equal to both those specified. * - `inside`: Preserving aspect ratio, resize the image to be as large as possible while ensuring its dimensions are less than or equal to both those specified.
* - `outside`: Preserving aspect ratio, resize the image to be as small as possible while ensuring its dimensions are greater than or equal to both those specified. * - `outside`: Preserving aspect ratio, resize the image to be as small as possible while ensuring its dimensions are greater than or equal to both those specified.
*
* Some of these values are based on the [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) 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: * 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.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.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. * - `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. * 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 * The experimental strategy-based approach resizes so one dimension is at its target length
@@ -173,8 +182,16 @@ const mapFitToCanvas = {
* // and no larger than the input image * // 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. * @example
* @param {Number} [height] - pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width. * const scaleByHalf = await sharp(input)
* .metadata()
* .then(({ width }) => sharp(input)
* .resize(Math.round(width * 0.5))
* .toBuffer()
* );
*
* @param {number} [width] - pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height.
* @param {number} [height] - pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
* @param {Object} [options] * @param {Object} [options]
* @param {String} [options.width] - alternative means of specifying `width`. If both are present this take priority. * @param {String} [options.width] - alternative means of specifying `width`. If both are present this take priority.
* @param {String} [options.height] - alternative means of specifying `height`. If both are present this take priority. * @param {String} [options.height] - alternative means of specifying `height`. If both are present this take priority.
@@ -285,11 +302,11 @@ function resize (width, height, options) {
* }) * })
* ... * ...
* *
* @param {(Number|Object)} extend - single pixel count to add to all edges or an Object with per-edge counts * @param {(number|Object)} extend - single pixel count to add to all edges or an Object with per-edge counts
* @param {Number} [extend.top] * @param {number} [extend.top]
* @param {Number} [extend.left] * @param {number} [extend.left]
* @param {Number} [extend.bottom] * @param {number} [extend.bottom]
* @param {Number} [extend.right] * @param {number} [extend.right]
* @param {String|Object} [extend.background={r: 0, g: 0, b: 0, alpha: 1}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency. * @param {String|Object} [extend.background={r: 0, g: 0, b: 0, alpha: 1}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
@@ -319,7 +336,7 @@ function extend (extend) {
} }
/** /**
* Extract a region of the image. * Extract/crop a region of the image.
* *
* - Use `extract` before `resize` for pre-resize extraction. * - Use `extract` before `resize` for pre-resize extraction.
* - Use `extract` after `resize` for post-resize extraction. * - Use `extract` after `resize` for post-resize extraction.
@@ -341,10 +358,10 @@ function extend (extend) {
* }); * });
* *
* @param {Object} options - describes the region to extract using integral pixel values * @param {Object} options - describes the region to extract using integral pixel values
* @param {Number} options.left - zero-indexed offset from left edge * @param {number} options.left - zero-indexed offset from left edge
* @param {Number} options.top - zero-indexed offset from top edge * @param {number} options.top - zero-indexed offset from top edge
* @param {Number} options.width - width of region to extract * @param {number} options.width - width of region to extract
* @param {Number} options.height - height of region to extract * @param {number} options.height - height of region to extract
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -359,7 +376,7 @@ function extract (options) {
} }
}, this); }, this);
// Ensure existing rotation occurs before pre-resize extraction // Ensure existing rotation occurs before pre-resize extraction
if (suffix === 'Pre' && ((this.options.angle % 360) !== 0 || this.options.useExifOrientation === true || this.options.rotationAngle !== 0)) { if (suffix === 'Pre' && isRotationExpected(this.options)) {
this.options.rotateBeforePreExtract = true; this.options.rotateBeforePreExtract = true;
} }
return this; return this;
@@ -371,7 +388,7 @@ function extract (options) {
* *
* The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` properties. * 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. * @param {number} [threshold=10] the allowed difference from the top-left pixel, a number greater than zero.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -383,6 +400,9 @@ function trim (threshold) {
} else { } else {
throw is.invalidParameterError('threshold', 'number greater than zero', threshold); throw is.invalidParameterError('threshold', 'number greater than zero', threshold);
} }
if (this.options.trimThreshold && isRotationExpected(this.options)) {
this.options.rotateBeforePreExtract = true;
}
return this; return this;
} }

View File

@@ -1,8 +1,31 @@
'use strict'; 'use strict';
const events = require('events');
const is = require('./is'); const is = require('./is');
const sharp = require('../build/Release/sharp.node'); const sharp = require('../build/Release/sharp.node');
/**
* An Object containing nested boolean values representing the available input and output formats/methods.
* @member
* @example
* console.log(sharp.format);
* @returns {Object}
*/
const format = sharp.format();
/**
* An Object containing the version numbers of libvips and its dependencies.
* @member
* @example
* console.log(sharp.versions);
*/
let versions = {
vips: sharp.libvipsVersion()
};
try {
versions = require(`../vendor/${versions.vips}/versions.json`);
} catch (err) {}
/** /**
* Gets or, when options are provided, sets the limits of _libvips'_ operation cache. * 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. * Existing entries in the cache will be trimmed after any change in limits.
@@ -16,10 +39,10 @@ const sharp = require('../build/Release/sharp.node');
* sharp.cache( { files: 0 } ); * sharp.cache( { files: 0 } );
* sharp.cache(false); * sharp.cache(false);
* *
* @param {Object|Boolean} [options=true] - Object with the following attributes, or Boolean where true uses default cache settings and false removes all caching * @param {Object|boolean} [options=true] - Object with the following attributes, or boolean where true uses default cache settings and false removes all caching
* @param {Number} [options.memory=50] - is the maximum memory in MB to use for this cache * @param {number} [options.memory=50] - is the maximum memory in MB to use for this cache
* @param {Number} [options.files=20] - is the maximum number of files to hold open * @param {number} [options.files=20] - is the maximum number of files to hold open
* @param {Number} [options.items=100] - is the maximum number of operations to cache * @param {number} [options.items=100] - is the maximum number of operations to cache
* @returns {Object} * @returns {Object}
*/ */
function cache (options) { function cache (options) {
@@ -54,13 +77,25 @@ cache(true);
* sharp.concurrency(2); // 2 * sharp.concurrency(2); // 2
* sharp.concurrency(0); // 4 * sharp.concurrency(0); // 4
* *
* @param {Number} [concurrency] * @param {number} [concurrency]
* @returns {Number} concurrency * @returns {number} concurrency
*/ */
function concurrency (concurrency) { function concurrency (concurrency) {
return sharp.concurrency(is.integer(concurrency) ? concurrency : null); return sharp.concurrency(is.integer(concurrency) ? concurrency : null);
} }
/**
* 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. * 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. * - queue is the number of tasks this module has queued waiting for _libuv_ to provide a worker thread from its pool.
@@ -89,8 +124,8 @@ function counters () {
* const simd = sharp.simd(false); * const simd = sharp.simd(false);
* // prevent libvips from using liborc at runtime * // prevent libvips from using liborc at runtime
* *
* @param {Boolean} [simd=true] * @param {boolean} [simd=true]
* @returns {Boolean} * @returns {boolean}
*/ */
function simd (simd) { function simd (simd) {
return sharp.simd(is.bool(simd) ? simd : null); return sharp.simd(is.bool(simd) ? simd : null);
@@ -110,4 +145,7 @@ module.exports = function (Sharp) {
].forEach(function (f) { ].forEach(function (f) {
Sharp[f.name] = f; Sharp[f.name] = f;
}); });
Sharp.format = format;
Sharp.versions = versions;
Sharp.queue = queue;
}; };

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", "name": "sharp",
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images", "description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images",
"version": "0.23.4", "version": "0.26.1",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://github.com/lovell/sharp", "homepage": "https://github.com/lovell/sharp",
"contributors": [ "contributors": [
@@ -64,24 +64,30 @@
"Ilya Ovdin <iovdin@gmail.com>", "Ilya Ovdin <iovdin@gmail.com>",
"Andargor <andargor@yahoo.com>", "Andargor <andargor@yahoo.com>",
"Paul Neave <paul.neave@gmail.com>", "Paul Neave <paul.neave@gmail.com>",
"Brendan Kennedy <brenwken@gmail.com>" "Brendan Kennedy <brenwken@gmail.com>",
"Brychan Bennett-Odlum <git@brychan.io>",
"Edward Silverton <e.silverton@gmail.com>",
"Roman Malieiev <aromaleev@gmail.com>",
"Tomas Szabo <tomas.szabo@deftomat.com>",
"Robert O'Rourke <robert@o-rourke.org>"
], ],
"scripts": { "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-gyp rebuild && node install/dll-copy)",
"clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*", "clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*",
"test": "semistandard && cpplint && npm run test-unit && npm run test-licensing && prebuild-ci", "test": "semistandard && cpplint && npm run test-unit && npm run test-licensing && node install/prebuild-ci",
"test-unit": "nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js", "test-unit": "nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js",
"test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"", "test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
"test-coverage": "./test/coverage/report.sh", "test-coverage": "./test/coverage/report.sh",
"test-leak": "./test/leak/leak.sh", "test-leak": "./test/leak/leak.sh",
"docs": "for m in constructor input resize composite operation colour channel output utility; do documentation build --shallow --format=md --markdown-toc=false lib/$m.js >docs/api-$m.md; done" "docs-build": "documentation lint lib && for m in constructor input resize composite operation colour channel output utility; do documentation build --shallow --format=md --markdown-toc=false lib/$m.js >docs/api-$m.md; done && node docs/search-index/build",
"docs-serve": "cd docs && npx serve",
"docs-publish": "cd docs && npx firebase-tools deploy --project pixelplumbing --only hosting:pixelplumbing-sharp"
}, },
"main": "lib/index.js", "main": "lib/index.js",
"files": [ "files": [
"binding.gyp", "binding.gyp",
"docs/**",
"!docs/css/**",
"install/**", "install/**",
"!install/prebuild-ci.js",
"lib/**", "lib/**",
"src/**" "src/**"
], ],
@@ -108,40 +114,46 @@
"dependencies": { "dependencies": {
"color": "^3.1.2", "color": "^3.1.2",
"detect-libc": "^1.0.3", "detect-libc": "^1.0.3",
"nan": "^2.14.0", "node-addon-api": "^3.0.2",
"npmlog": "^4.1.2", "npmlog": "^4.1.2",
"prebuild-install": "^5.3.3", "prebuild-install": "^5.3.5",
"semver": "^6.3.0", "semver": "^7.3.2",
"simple-get": "^3.1.0", "simple-get": "^4.0.0",
"tar": "^5.0.5", "tar-fs": "^2.1.0",
"tunnel-agent": "^0.6.0" "tunnel-agent": "^0.6.0"
}, },
"devDependencies": { "devDependencies": {
"async": "^3.1.0", "async": "^3.2.0",
"cc": "^2.0.1", "cc": "^2.0.1",
"decompress-zip": "^0.3.2", "decompress-zip": "^0.3.2",
"documentation": "^12.1.4", "documentation": "^13.0.2",
"exif-reader": "^1.0.3", "exif-reader": "^1.0.3",
"icc": "^1.0.0", "icc": "^2.0.0",
"license-checker": "^25.0.1", "license-checker": "^25.0.1",
"mocha": "^6.2.2", "mocha": "^8.1.1",
"mock-fs": "^4.10.4", "mock-fs": "^4.13.0",
"nyc": "^14.1.1", "nyc": "^15.1.0",
"prebuild": "^9.1.1", "prebuild": "^10.0.1",
"prebuild-ci": "^3.1.0", "rimraf": "^3.0.2",
"rimraf": "^3.0.0", "semistandard": "^14.2.3"
"semistandard": "^14.2.0"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"config": { "config": {
"libvips": "8.8.1" "libvips": "8.10.0",
"runtime": "napi",
"target": 3
}, },
"engines": { "engines": {
"node": ">=8.5.0" "node": ">=10.16.0"
}, },
"funding": { "funding": {
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"binary": {
"napi_versions": [
3
]
},
"semistandard": { "semistandard": {
"env": [ "env": [
"mocha" "mocha"

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors. // Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -17,11 +17,10 @@
#include <string.h> #include <string.h>
#include <vector> #include <vector>
#include <queue> #include <queue>
#include <map>
#include <mutex> // NOLINT(build/c++11) #include <mutex> // NOLINT(build/c++11)
#include <node.h> #include <napi.h>
#include <node_buffer.h>
#include <nan.h>
#include <vips/vips8> #include <vips/vips8>
#include "common.h" #include "common.h"
@@ -30,62 +29,92 @@ using vips::VImage;
namespace sharp { namespace sharp {
// Convenience methods to access the attributes of a v8::Object // Convenience methods to access the attributes of a Napi::Object
bool HasAttr(v8::Local<v8::Object> obj, std::string attr) { bool HasAttr(Napi::Object obj, std::string attr) {
return Nan::Has(obj, Nan::New(attr).ToLocalChecked()).FromJust(); return obj.Has(attr);
} }
std::string AttrAsStr(v8::Local<v8::Object> obj, std::string attr) { std::string AttrAsStr(Napi::Object obj, std::string attr) {
return *Nan::Utf8String(Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked()); return obj.Get(attr).As<Napi::String>();
} }
std::vector<double> AttrAsRgba(v8::Local<v8::Object> obj, std::string attr) { uint32_t AttrAsUint32(Napi::Object obj, std::string attr) {
v8::Local<v8::Object> background = AttrAs<v8::Object>(obj, attr); return obj.Get(attr).As<Napi::Number>().Uint32Value();
std::vector<double> rgba(4); }
for (unsigned int i = 0; i < 4; i++) { int32_t AttrAsInt32(Napi::Object obj, std::string attr) {
rgba[i] = AttrTo<double>(background, i); return obj.Get(attr).As<Napi::Number>().Int32Value();
}
int32_t AttrAsInt32(Napi::Object obj, unsigned int const attr) {
return obj.Get(attr).As<Napi::Number>().Int32Value();
}
double AttrAsDouble(Napi::Object obj, std::string attr) {
return obj.Get(attr).As<Napi::Number>().DoubleValue();
}
double AttrAsDouble(Napi::Object obj, unsigned int const attr) {
return obj.Get(attr).As<Napi::Number>().DoubleValue();
}
bool AttrAsBool(Napi::Object obj, std::string attr) {
return obj.Get(attr).As<Napi::Boolean>().Value();
}
std::vector<double> AttrAsRgba(Napi::Object obj, std::string attr) {
Napi::Array background = obj.Get(attr).As<Napi::Array>();
std::vector<double> rgba(background.Length());
for (unsigned int i = 0; i < background.Length(); i++) {
rgba[i] = AttrAsDouble(background, i);
} }
return rgba; return rgba;
} }
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 // Create an InputDescriptor instance from a Napi::Object describing an input image
InputDescriptor* CreateInputDescriptor( InputDescriptor* CreateInputDescriptor(Napi::Object input) {
v8::Local<v8::Object> input, std::vector<v8::Local<v8::Object>> &buffersToPersist
) {
Nan::HandleScope();
InputDescriptor *descriptor = new InputDescriptor; InputDescriptor *descriptor = new InputDescriptor;
if (HasAttr(input, "file")) { if (HasAttr(input, "file")) {
descriptor->file = AttrAsStr(input, "file"); descriptor->file = AttrAsStr(input, "file");
} else if (HasAttr(input, "buffer")) { } else if (HasAttr(input, "buffer")) {
v8::Local<v8::Object> buffer = AttrAs<v8::Object>(input, "buffer"); Napi::Buffer<char> buffer = input.Get("buffer").As<Napi::Buffer<char>>();
descriptor->bufferLength = node::Buffer::Length(buffer); descriptor->bufferLength = buffer.Length();
descriptor->buffer = node::Buffer::Data(buffer); descriptor->buffer = buffer.Data();
descriptor->isBuffer = TRUE; descriptor->isBuffer = TRUE;
buffersToPersist.push_back(buffer);
} }
descriptor->failOnError = AttrTo<bool>(input, "failOnError"); descriptor->failOnError = AttrAsBool(input, "failOnError");
// Density for vector-based input // Density for vector-based input
if (HasAttr(input, "density")) { if (HasAttr(input, "density")) {
descriptor->density = AttrTo<double>(input, "density"); descriptor->density = AttrAsDouble(input, "density");
} }
// Raw pixel input // Raw pixel input
if (HasAttr(input, "rawChannels")) { if (HasAttr(input, "rawChannels")) {
descriptor->rawChannels = AttrTo<uint32_t>(input, "rawChannels"); descriptor->rawChannels = AttrAsUint32(input, "rawChannels");
descriptor->rawWidth = AttrTo<uint32_t>(input, "rawWidth"); descriptor->rawWidth = AttrAsUint32(input, "rawWidth");
descriptor->rawHeight = AttrTo<uint32_t>(input, "rawHeight"); descriptor->rawHeight = AttrAsUint32(input, "rawHeight");
} }
// Multi-page input (GIF, TIFF, PDF) // Multi-page input (GIF, TIFF, PDF)
if (HasAttr(input, "pages")) { if (HasAttr(input, "pages")) {
descriptor->pages = AttrTo<int32_t>(input, "pages"); descriptor->pages = AttrAsInt32(input, "pages");
} }
if (HasAttr(input, "page")) { 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");
} }
// Create new image // Create new image
if (HasAttr(input, "createChannels")) { if (HasAttr(input, "createChannels")) {
descriptor->createChannels = AttrTo<uint32_t>(input, "createChannels"); descriptor->createChannels = AttrAsUint32(input, "createChannels");
descriptor->createWidth = AttrTo<uint32_t>(input, "createWidth"); descriptor->createWidth = AttrAsUint32(input, "createWidth");
descriptor->createHeight = AttrTo<uint32_t>(input, "createHeight"); descriptor->createHeight = AttrAsUint32(input, "createHeight");
descriptor->createBackground = AttrAsRgba(input, "createBackground"); descriptor->createBackground = AttrAsRgba(input, "createBackground");
} }
// Limit input images to a given number of pixels, where pixels = width * height
descriptor->limitInputPixels = AttrAsUint32(input, "limitInputPixels");
// Allow switch from random to sequential access
descriptor->access = AttrAsBool(input, "sequentialRead") ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
return descriptor; return descriptor;
} }
@@ -108,6 +137,9 @@ namespace sharp {
bool IsWebp(std::string const &str) { bool IsWebp(std::string const &str) {
return EndsWith(str, ".webp") || EndsWith(str, ".WEBP"); return EndsWith(str, ".webp") || EndsWith(str, ".WEBP");
} }
bool IsGif(std::string const &str) {
return EndsWith(str, ".gif") || EndsWith(str, ".GIF");
}
bool IsTiff(std::string const &str) { bool IsTiff(std::string const &str) {
return EndsWith(str, ".tif") || EndsWith(str, ".tiff") || EndsWith(str, ".TIF") || EndsWith(str, ".TIFF"); return EndsWith(str, ".tif") || EndsWith(str, ".tiff") || EndsWith(str, ".TIF") || EndsWith(str, ".TIFF");
} }
@@ -156,32 +188,42 @@ namespace sharp {
return id; return id;
} }
std::map<std::string, ImageType> loaderToType = {
{ "jpegload", ImageType::JPEG },
{ "jpegload_buffer", ImageType::JPEG },
{ "pngload", ImageType::PNG },
{ "pngload_buffer", ImageType::PNG },
{ "webpload", ImageType::WEBP },
{ "webpload_buffer", ImageType::WEBP },
{ "tiffload", ImageType::TIFF },
{ "tiffload_buffer", ImageType::TIFF },
{ "gifload", ImageType::GIF },
{ "gifload_buffer", ImageType::GIF },
{ "svgload", ImageType::SVG },
{ "svgload_buffer", ImageType::SVG },
{ "heifload", ImageType::HEIF },
{ "heifload_buffer", ImageType::HEIF },
{ "pdfload", ImageType::PDF },
{ "pdfload_buffer", ImageType::PDF },
{ "magickload", ImageType::MAGICK },
{ "magickload_buffer", ImageType::MAGICK },
{ "openslideload", ImageType::OPENSLIDE },
{ "ppmload", ImageType::PPM },
{ "fitsload", ImageType::FITS },
{ "vipsload", ImageType::VIPS },
{ "rawload", ImageType::RAW }
};
/* /*
Determine image format of a buffer. Determine image format of a buffer.
*/ */
ImageType DetermineImageType(void *buffer, size_t const length) { ImageType DetermineImageType(void *buffer, size_t const length) {
ImageType imageType = ImageType::UNKNOWN; ImageType imageType = ImageType::UNKNOWN;
char const *load = vips_foreign_find_load_buffer(buffer, length); char const *load = vips_foreign_find_load_buffer(buffer, length);
if (load != NULL) { if (load != nullptr) {
std::string const loader = load; auto it = loaderToType.find(vips_nickname_find(g_type_from_name(load)));
if (EndsWith(loader, "JpegBuffer")) { if (it != loaderToType.end()) {
imageType = ImageType::JPEG; imageType = it->second;
} else if (EndsWith(loader, "PngBuffer")) {
imageType = ImageType::PNG;
} else if (EndsWith(loader, "WebpBuffer")) {
imageType = ImageType::WEBP;
} else if (EndsWith(loader, "TiffBuffer")) {
imageType = ImageType::TIFF;
} else if (EndsWith(loader, "GifBuffer")) {
imageType = ImageType::GIF;
} else if (EndsWith(loader, "SvgBuffer")) {
imageType = ImageType::SVG;
} else if (EndsWith(loader, "HeifBuffer")) {
imageType = ImageType::HEIF;
} else if (EndsWith(loader, "PdfBuffer")) {
imageType = ImageType::PDF;
} else if (EndsWith(loader, "MagickBuffer")) {
imageType = ImageType::MAGICK;
} }
} }
return imageType; return imageType;
@@ -194,36 +236,12 @@ namespace sharp {
ImageType imageType = ImageType::UNKNOWN; ImageType imageType = ImageType::UNKNOWN;
char const *load = vips_foreign_find_load(file); char const *load = vips_foreign_find_load(file);
if (load != nullptr) { if (load != nullptr) {
std::string const loader = load; auto it = loaderToType.find(vips_nickname_find(g_type_from_name(load)));
if (EndsWith(loader, "JpegFile")) { if (it != loaderToType.end()) {
imageType = ImageType::JPEG; imageType = it->second;
} 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;
} }
} else { } else {
if (EndsWith(vips::VError().what(), " not found\n")) { if (EndsWith(vips::VError().what(), " does not exist\n")) {
imageType = ImageType::MISSING; imageType = ImageType::MISSING;
} }
} }
@@ -235,6 +253,8 @@ namespace sharp {
*/ */
bool ImageTypeSupportsPage(ImageType imageType) { bool ImageTypeSupportsPage(ImageType imageType) {
return return
imageType == ImageType::WEBP ||
imageType == ImageType::MAGICK ||
imageType == ImageType::GIF || imageType == ImageType::GIF ||
imageType == ImageType::TIFF || imageType == ImageType::TIFF ||
imageType == ImageType::HEIF || imageType == ImageType::HEIF ||
@@ -244,7 +264,7 @@ namespace sharp {
/* /*
Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data) 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; VImage image;
ImageType imageType; ImageType imageType;
if (descriptor->isBuffer) { if (descriptor->isBuffer) {
@@ -264,8 +284,11 @@ namespace sharp {
if (imageType != ImageType::UNKNOWN) { if (imageType != ImageType::UNKNOWN) {
try { try {
vips::VOption *option = VImage::option() vips::VOption *option = VImage::option()
->set("access", accessMethod) ->set("access", descriptor->access)
->set("fail", descriptor->failOnError); ->set("fail", descriptor->failOnError);
if (imageType == ImageType::SVG) {
option->set("unlimited", TRUE);
}
if (imageType == ImageType::SVG || imageType == ImageType::PDF) { if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
option->set("dpi", descriptor->density); option->set("dpi", descriptor->density);
} }
@@ -276,6 +299,9 @@ namespace sharp {
option->set("n", descriptor->pages); option->set("n", descriptor->pages);
option->set("page", descriptor->page); option->set("page", descriptor->page);
} }
if (imageType == ImageType::OPENSLIDE) {
option->set("level", descriptor->level);
}
image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option); image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option);
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) { if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
image = SetDensity(image, descriptor->density); image = SetDensity(image, descriptor->density);
@@ -310,8 +336,11 @@ namespace sharp {
if (imageType != ImageType::UNKNOWN) { if (imageType != ImageType::UNKNOWN) {
try { try {
vips::VOption *option = VImage::option() vips::VOption *option = VImage::option()
->set("access", accessMethod) ->set("access", descriptor->access)
->set("fail", descriptor->failOnError); ->set("fail", descriptor->failOnError);
if (imageType == ImageType::SVG) {
option->set("unlimited", TRUE);
}
if (imageType == ImageType::SVG || imageType == ImageType::PDF) { if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
option->set("dpi", descriptor->density); option->set("dpi", descriptor->density);
} }
@@ -322,6 +351,9 @@ namespace sharp {
option->set("n", descriptor->pages); option->set("n", descriptor->pages);
option->set("page", descriptor->page); option->set("page", descriptor->page);
} }
if (imageType == ImageType::OPENSLIDE) {
option->set("level", descriptor->level);
}
image = VImage::new_from_file(descriptor->file.data(), option); image = VImage::new_from_file(descriptor->file.data(), option);
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) { if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
image = SetDensity(image, descriptor->density); image = SetDensity(image, descriptor->density);
@@ -334,6 +366,11 @@ 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()) > static_cast<uint64_t>(descriptor->limitInputPixels)) {
throw vips::VError("Input image exceeds pixel limit");
}
return std::make_tuple(image, imageType); return std::make_tuple(image, imageType);
} }
@@ -386,6 +423,38 @@ namespace sharp {
return copy; return copy;
} }
/*
Set animation properties if necessary.
Non-provided properties will be loaded from image.
*/
VImage SetAnimationProperties(VImage image, int pageHeight, std::vector<int> delay, int loop) {
bool hasDelay = delay.size() != 1 || delay.front() != -1;
if (pageHeight == 0 && image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT) {
pageHeight = image.get_int(VIPS_META_PAGE_HEIGHT);
}
if (!hasDelay && image.get_typeof("delay") == VIPS_TYPE_ARRAY_INT) {
delay = image.get_array_int("delay");
hasDelay = true;
}
if (loop == -1 && image.get_typeof("loop") == G_TYPE_INT) {
loop = image.get_int("loop");
}
if (pageHeight == 0) return image;
// It is necessary to create the copy as otherwise, pageHeight will be ignored!
VImage copy = image.copy();
copy.set(VIPS_META_PAGE_HEIGHT, pageHeight);
if (hasDelay) copy.set("delay", delay);
if (loop != -1) copy.set("loop", loop);
return copy;
}
/* /*
Does this image have a non-default density? Does this image have a non-default density?
*/ */
@@ -416,25 +485,30 @@ namespace sharp {
Check the proposed format supports the current dimensions. Check the proposed format supports the current dimensions.
*/ */
void AssertImageTypeDimensions(VImage image, ImageType const imageType) { void AssertImageTypeDimensions(VImage image, ImageType const imageType) {
const int height = image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT
? image.get_int(VIPS_META_PAGE_HEIGHT)
: image.height();
if (imageType == ImageType::JPEG) { if (imageType == ImageType::JPEG) {
if (image.width() > 65535 || image.height() > 65535) { if (image.width() > 65535 || height > 65535) {
throw vips::VError("Processed image is too large for the JPEG format"); throw vips::VError("Processed image is too large for the JPEG format");
} }
} else if (imageType == ImageType::WEBP) { } else if (imageType == ImageType::WEBP) {
if (image.width() > 16383 || image.height() > 16383) { if (image.width() > 16383 || height > 16383) {
throw vips::VError("Processed image is too large for the WebP format"); throw vips::VError("Processed image is too large for the WebP format");
} }
} else if (imageType == ImageType::GIF) {
if (image.width() > 65535 || height > 65535) {
throw vips::VError("Processed image is too large for the GIF format");
}
} }
} }
/* /*
Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows
*/ */
void FreeCallback(char* data, void* hint) { std::function<void(void*, char*)> FreeCallback = [](void*, char* data) {
if (data != nullptr) { g_free(data);
g_free(data); };
}
}
/* /*
Temporary buffer of warnings Temporary buffer of warnings
@@ -688,4 +762,25 @@ namespace sharp {
return std::make_tuple(image, alphaColour); return std::make_tuple(image, alphaColour);
} }
/*
Removes alpha channel, if any.
*/
VImage RemoveAlpha(VImage image) {
if (HasAlpha(image)) {
image = image.extract_band(0, VImage::option()->set("n", image.bands() - 1));
}
return image;
}
/*
Ensures alpha channel, if missing.
*/
VImage EnsureAlpha(VImage image) {
if (!HasAlpha(image)) {
std::vector<double> alpha;
alpha.push_back(sharp::MaximumImageAlpha(image.interpretation()));
image = image.bandjoin_const(alpha);
}
return image;
}
} // namespace sharp } // namespace sharp

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors. // Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -19,23 +19,22 @@
#include <tuple> #include <tuple>
#include <vector> #include <vector>
#include <node.h> #include <napi.h>
#include <nan.h>
#include <vips/vips8> #include <vips/vips8>
// Verify platform and compiler compatibility // Verify platform and compiler compatibility
#if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 8)) #if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 10))
#error libvips version 8.8.0+ is required - see sharp.pixelplumbing.com/page/install #error "libvips version 8.10.0+ is required - please see https://sharp.pixelplumbing.com/install"
#endif #endif
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6))) #if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
#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 #endif
#if (defined(__clang__) && defined(__has_feature)) #if (defined(__clang__) && defined(__has_feature))
#if (!__has_feature(cxx_range_for)) #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
#endif #endif
@@ -48,6 +47,8 @@ namespace sharp {
std::string file; std::string file;
char *buffer; char *buffer;
bool failOnError; bool failOnError;
int limitInputPixels;
VipsAccess access;
size_t bufferLength; size_t bufferLength;
bool isBuffer; bool isBuffer;
double density; double density;
@@ -56,6 +57,7 @@ namespace sharp {
int rawHeight; int rawHeight;
int pages; int pages;
int page; int page;
int level;
int createChannels; int createChannels;
int createWidth; int createWidth;
int createHeight; int createHeight;
@@ -64,6 +66,8 @@ namespace sharp {
InputDescriptor(): InputDescriptor():
buffer(nullptr), buffer(nullptr),
failOnError(TRUE), failOnError(TRUE),
limitInputPixels(0x3FFF * 0x3FFF),
access(VIPS_ACCESS_RANDOM),
bufferLength(0), bufferLength(0),
isBuffer(FALSE), isBuffer(FALSE),
density(72.0), density(72.0),
@@ -72,29 +76,27 @@ namespace sharp {
rawHeight(0), rawHeight(0),
pages(1), pages(1),
page(0), page(0),
level(0),
createChannels(0), createChannels(0),
createWidth(0), createWidth(0),
createHeight(0), createHeight(0),
createBackground{ 0.0, 0.0, 0.0, 255.0 } {} createBackground{ 0.0, 0.0, 0.0, 255.0 } {}
}; };
// Convenience methods to access the attributes of a v8::Object // Convenience methods to access the attributes of a Napi::Object
bool HasAttr(v8::Local<v8::Object> obj, std::string attr); bool HasAttr(Napi::Object obj, std::string attr);
std::string AttrAsStr(v8::Local<v8::Object> obj, std::string attr); std::string AttrAsStr(Napi::Object obj, std::string attr);
std::vector<double> AttrAsRgba(v8::Local<v8::Object> obj, std::string attr); uint32_t AttrAsUint32(Napi::Object obj, std::string attr);
template<typename T> v8::Local<T> AttrAs(v8::Local<v8::Object> obj, std::string attr) { int32_t AttrAsInt32(Napi::Object obj, std::string attr);
return Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked().As<T>(); int32_t AttrAsInt32(Napi::Object obj, unsigned int const attr);
} double AttrAsDouble(Napi::Object obj, std::string attr);
template<typename T> T AttrTo(v8::Local<v8::Object> obj, std::string attr) { double AttrAsDouble(Napi::Object obj, unsigned int const attr);
return Nan::To<T>(Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked()).FromJust(); bool AttrAsBool(Napi::Object obj, std::string attr);
} std::vector<double> AttrAsRgba(Napi::Object obj, std::string attr);
template<typename T> T AttrTo(v8::Local<v8::Object> obj, int attr) { std::vector<int32_t> AttrAsInt32Vector(Napi::Object obj, std::string attr);
return Nan::To<T>(Nan::Get(obj, attr).ToLocalChecked()).FromJust();
}
// Create an InputDescriptor instance from a v8::Object describing an input image // Create an InputDescriptor instance from a Napi::Object describing an input image
InputDescriptor* CreateInputDescriptor( InputDescriptor* CreateInputDescriptor(Napi::Object input);
v8::Local<v8::Object> input, std::vector<v8::Local<v8::Object>> &buffersToPersist); // NOLINT(runtime/references)
enum class ImageType { enum class ImageType {
JPEG, JPEG,
@@ -125,6 +127,7 @@ namespace sharp {
bool IsJpeg(std::string const &str); bool IsJpeg(std::string const &str);
bool IsPng(std::string const &str); bool IsPng(std::string const &str);
bool IsWebp(std::string const &str); bool IsWebp(std::string const &str);
bool IsGif(std::string const &str);
bool IsTiff(std::string const &str); bool IsTiff(std::string const &str);
bool IsHeic(std::string const &str); bool IsHeic(std::string const &str);
bool IsHeif(std::string const &str); bool IsHeif(std::string const &str);
@@ -156,7 +159,7 @@ namespace sharp {
/* /*
Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data) 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? Does this image have an embedded profile?
@@ -184,6 +187,12 @@ namespace sharp {
*/ */
VImage RemoveExifOrientation(VImage image); VImage RemoveExifOrientation(VImage image);
/*
Set animation properties if necessary.
Non-provided properties will be loaded from image.
*/
VImage SetAnimationProperties(VImage image, int pageHeight, std::vector<int> delay, int loop);
/* /*
Does this image have a non-default density? Does this image have a non-default density?
*/ */
@@ -207,7 +216,7 @@ namespace sharp {
/* /*
Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows 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 Called with warnings from the glib-registered "VIPS" domain
@@ -271,6 +280,16 @@ namespace sharp {
*/ */
std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour); std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour);
/*
Removes alpha channel, if any.
*/
VImage RemoveAlpha(VImage image);
/*
Ensures alpha channel, if missing.
*/
VImage EnsureAlpha(VImage image);
} // namespace sharp } // namespace sharp
#endif // SRC_COMMON_H_ #endif // SRC_COMMON_H_

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -61,7 +61,7 @@ VInterpolate::new_from_name( const char *name, VOption *options )
} }
VOption * VOption *
VOption::set( const char *name, VInterpolate value ) VOption::set( const char *name, const VInterpolate value )
{ {
Pair *pair = new Pair( name ); Pair *pair = new Pair( name );

View File

@@ -1,5 +1,5 @@
// bodies for vips operations // bodies for vips operations
// Wed Apr 24 15:50:21 CEST 2019 // Sun 5 Jul 22:36:37 BST 2020
// this file is generated automatically, do not edit! // this file is generated automatically, do not edit!
VImage VImage::CMC2LCh( VOption *options ) const VImage VImage::CMC2LCh( VOption *options ) const
@@ -491,6 +491,19 @@ VImage VImage::canny( VOption *options ) const
return( out ); 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 VImage::cast( VipsBandFormat format, VOption *options ) const
{ {
VImage out; VImage out;
@@ -741,6 +754,18 @@ VImage VImage::csvload( const char *filename, VOption *options )
return( out ); return( out );
} }
VImage VImage::csvload_source( VSource source, VOption *options )
{
VImage out;
call( "csvload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
void VImage::csvsave( const char *filename, VOption *options ) const void VImage::csvsave( const char *filename, VOption *options ) const
{ {
call( "csvsave", call( "csvsave",
@@ -749,6 +774,14 @@ void VImage::csvsave( const char *filename, VOption *options ) const
set( "filename", filename ) ); set( "filename", filename ) );
} }
void VImage::csvsave_target( VTarget target, VOption *options ) const
{
call( "csvsave_target",
(options ? options : VImage::option())->
set( "in", *this )->
set( "target", target ) );
}
VImage VImage::dE00( VImage right, VOption *options ) const VImage VImage::dE00( VImage right, VOption *options ) const
{ {
VImage out; VImage out;
@@ -1205,6 +1238,18 @@ VImage VImage::gifload_buffer( VipsBlob *buffer, VOption *options )
return( out ); return( out );
} }
VImage VImage::gifload_source( VSource source, VOption *options )
{
VImage out;
call( "gifload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
VImage VImage::globalbalance( VOption *options ) const VImage VImage::globalbalance( VOption *options ) const
{ {
VImage out; VImage out;
@@ -1284,6 +1329,18 @@ VImage VImage::heifload_buffer( VipsBlob *buffer, VOption *options )
return( out ); return( out );
} }
VImage VImage::heifload_source( VSource source, VOption *options )
{
VImage out;
call( "heifload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
void VImage::heifsave( const char *filename, VOption *options ) const void VImage::heifsave( const char *filename, VOption *options ) const
{ {
call( "heifsave", call( "heifsave",
@@ -1304,6 +1361,14 @@ VipsBlob *VImage::heifsave_buffer( VOption *options ) const
return( buffer ); return( buffer );
} }
void VImage::heifsave_target( VTarget target, VOption *options ) const
{
call( "heifsave_target",
(options ? options : VImage::option())->
set( "in", *this )->
set( "target", target ) );
}
VImage VImage::hist_cum( VOption *options ) const VImage VImage::hist_cum( VOption *options ) const
{ {
VImage out; VImage out;
@@ -1615,6 +1680,18 @@ VImage VImage::jpegload_buffer( VipsBlob *buffer, VOption *options )
return( out ); 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 void VImage::jpegsave( const char *filename, VOption *options ) const
{ {
call( "jpegsave", call( "jpegsave",
@@ -1642,6 +1719,14 @@ void VImage::jpegsave_mime( VOption *options ) const
set( "in", *this ) ); 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::labelregions( VOption *options ) const VImage VImage::labelregions( VOption *options ) const
{ {
VImage mask; VImage mask;
@@ -1995,6 +2080,18 @@ VImage VImage::matload( const char *filename, VOption *options )
return( out ); return( out );
} }
VImage VImage::matrixinvert( VOption *options ) const
{
VImage out;
call( "matrixinvert",
(options ? options : VImage::option())->
set( "in", *this )->
set( "out", &out ) );
return( out );
}
VImage VImage::matrixload( const char *filename, VOption *options ) VImage VImage::matrixload( const char *filename, VOption *options )
{ {
VImage out; VImage out;
@@ -2007,6 +2104,18 @@ VImage VImage::matrixload( const char *filename, VOption *options )
return( out ); return( out );
} }
VImage VImage::matrixload_source( VSource source, VOption *options )
{
VImage out;
call( "matrixload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
void VImage::matrixprint( VOption *options ) const void VImage::matrixprint( VOption *options ) const
{ {
call( "matrixprint", call( "matrixprint",
@@ -2022,6 +2131,14 @@ void VImage::matrixsave( const char *filename, VOption *options ) const
set( "filename", filename ) ); set( "filename", filename ) );
} }
void VImage::matrixsave_target( VTarget target, VOption *options ) const
{
call( "matrixsave_target",
(options ? options : VImage::option())->
set( "in", *this )->
set( "target", target ) );
}
double VImage::max( VOption *options ) const double VImage::max( VOption *options ) const
{ {
double out; double out;
@@ -2223,6 +2340,18 @@ VImage VImage::pdfload_buffer( VipsBlob *buffer, VOption *options )
return( out ); return( out );
} }
VImage VImage::pdfload_source( VSource source, VOption *options )
{
VImage out;
call( "pdfload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
int VImage::percent( double percent, VOption *options ) const int VImage::percent( double percent, VOption *options ) const
{ {
int threshold; int threshold;
@@ -2286,6 +2415,18 @@ VImage VImage::pngload_buffer( VipsBlob *buffer, VOption *options )
return( out ); 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 void VImage::pngsave( const char *filename, VOption *options ) const
{ {
call( "pngsave", call( "pngsave",
@@ -2306,6 +2447,14 @@ VipsBlob *VImage::pngsave_buffer( VOption *options ) const
return( buffer ); 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 VImage::ppmload( const char *filename, VOption *options )
{ {
VImage out; VImage out;
@@ -2318,6 +2467,18 @@ VImage VImage::ppmload( const char *filename, VOption *options )
return( out ); return( out );
} }
VImage VImage::ppmload_source( VSource source, VOption *options )
{
VImage out;
call( "ppmload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
void VImage::ppmsave( const char *filename, VOption *options ) const void VImage::ppmsave( const char *filename, VOption *options ) const
{ {
call( "ppmsave", call( "ppmsave",
@@ -2326,6 +2487,14 @@ void VImage::ppmsave( const char *filename, VOption *options ) const
set( "filename", filename ) ); set( "filename", filename ) );
} }
void VImage::ppmsave_target( VTarget target, VOption *options ) const
{
call( "ppmsave_target",
(options ? options : VImage::option())->
set( "in", *this )->
set( "target", target ) );
}
VImage VImage::premultiply( VOption *options ) const VImage VImage::premultiply( VOption *options ) const
{ {
VImage out; VImage out;
@@ -2413,6 +2582,30 @@ VImage VImage::radload( const char *filename, VOption *options )
return( out ); 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 void VImage::radsave( const char *filename, VOption *options ) const
{ {
call( "radsave", call( "radsave",
@@ -2433,6 +2626,14 @@ VipsBlob *VImage::radsave_buffer( VOption *options ) const
return( buffer ); 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 VImage::rank( int width, int height, int index, VOption *options ) const
{ {
VImage out; VImage out;
@@ -2977,6 +3178,30 @@ VImage VImage::svgload_buffer( VipsBlob *buffer, VOption *options )
return( out ); 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 ) void VImage::system( const char *cmd_format, VOption *options )
{ {
call( "system", call( "system",
@@ -3035,6 +3260,19 @@ VImage VImage::thumbnail_image( int width, VOption *options ) const
return( out ); 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 VImage::tiffload( const char *filename, VOption *options )
{ {
VImage out; VImage out;
@@ -3059,6 +3297,18 @@ VImage VImage::tiffload_buffer( VipsBlob *buffer, VOption *options )
return( out ); 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 void VImage::tiffsave( const char *filename, VOption *options ) const
{ {
call( "tiffsave", call( "tiffsave",
@@ -3170,6 +3420,18 @@ VImage VImage::webpload_buffer( VipsBlob *buffer, VOption *options )
return( out ); 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 void VImage::webpsave( const char *filename, VOption *options ) const
{ {
call( "webpsave", call( "webpsave",
@@ -3190,6 +3452,14 @@ VipsBlob *VImage::webpsave_buffer( VOption *options ) const
return( buffer ); return( buffer );
} }
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 VImage::worley( int width, int height, VOption *options )
{ {
VImage out; VImage out;

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors. // Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -15,28 +15,16 @@
#include <numeric> #include <numeric>
#include <vector> #include <vector>
#include <node.h> #include <napi.h>
#include <nan.h>
#include <vips/vips8> #include <vips/vips8>
#include "common.h" #include "common.h"
#include "metadata.h" #include "metadata.h"
class MetadataWorker : public Nan::AsyncWorker { class MetadataWorker : public Napi::AsyncWorker {
public: public:
MetadataWorker( MetadataWorker(Napi::Function callback, MetadataBaton *baton, Napi::Function debuglog) :
Nan::Callback *callback, MetadataBaton *baton, Nan::Callback *debuglog, Napi::AsyncWorker(callback), baton(baton), debuglog(Napi::Persistent(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() {} ~MetadataWorker() {}
void Execute() { void Execute() {
@@ -46,7 +34,7 @@ class MetadataWorker : public Nan::AsyncWorker {
vips::VImage image; vips::VImage image;
sharp::ImageType imageType = sharp::ImageType::UNKNOWN; sharp::ImageType imageType = sharp::ImageType::UNKNOWN;
try { try {
std::tie(image, imageType) = OpenInput(baton->input, VIPS_ACCESS_SEQUENTIAL); std::tie(image, imageType) = OpenInput(baton->input);
} catch (vips::VError const &err) { } catch (vips::VError const &err) {
(baton->err).append(err.what()); (baton->err).append(err.what());
} }
@@ -77,9 +65,24 @@ class MetadataWorker : public Nan::AsyncWorker {
if (image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT) { if (image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT) {
baton->pageHeight = image.get_int(VIPS_META_PAGE_HEIGHT); 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) { if (image.get_typeof("heif-primary") == G_TYPE_INT) {
baton->pagePrimary = image.get_int("heif-primary"); baton->pagePrimary = image.get_int("heif-primary");
} }
if (image.get_typeof("openslide.level-count") == VIPS_TYPE_REF_STRING) {
int const levels = std::stoi(image.get_string("openslide.level-count"));
for (int l = 0; l < levels; l++) {
std::string prefix = "openslide.level[" + std::to_string(l) + "].";
int const width = std::stoi(image.get_string((prefix + "width").data()));
int const height = std::stoi(image.get_string((prefix + "height").data()));
baton->levels.push_back(std::pair<int, int>(width, height));
}
}
baton->hasProfile = sharp::HasProfile(image); baton->hasProfile = sharp::HasProfile(image);
// Derived attributes // Derived attributes
baton->hasAlpha = sharp::HasAlpha(image); baton->hasAlpha = sharp::HasAlpha(image);
@@ -131,129 +134,126 @@ class MetadataWorker : public Nan::AsyncWorker {
vips_thread_shutdown(); vips_thread_shutdown();
} }
void HandleOKCallback() { void OnOK() {
using Nan::New; Napi::Env env = Env();
using Nan::Set; Napi::HandleScope scope(env);
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());
}
if (baton->tifftagPhotoshopLength > 0) {
Set(info,
New("tifftagPhotoshop").ToLocalChecked(),
Nan::NewBuffer(baton->tifftagPhotoshop, baton->tifftagPhotoshopLength, 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;
// Handle warnings // Handle warnings
std::string warning = sharp::VipsWarningPop(); std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) { while (!warning.empty()) {
v8::Local<v8::Value> message[1] = { New(warning).ToLocalChecked() }; debuglog.Call({ Napi::String::New(env, warning) });
debuglog->Call(1, message, async_resource);
warning = sharp::VipsWarningPop(); warning = sharp::VipsWarningPop();
} }
// Return to JavaScript if (baton->err.empty()) {
callback->Call(2, argv, async_resource); 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->levels.empty()) {
int i = 0;
Napi::Array levels = Napi::Array::New(env, static_cast<size_t>(baton->levels.size()));
for (std::pair<int, int> const &l : baton->levels) {
Napi::Object level = Napi::Object::New(env);
level.Set("width", l.first);
level.Set("height", l.second);
levels.Set(i++, level);
}
info.Set("levels", levels);
}
info.Set("hasProfile", baton->hasProfile);
info.Set("hasAlpha", baton->hasAlpha);
if (baton->orientation > 0) {
info.Set("orientation", baton->orientation);
}
if (baton->exifLength > 0) {
info.Set("exif", Napi::Buffer<char>::New(env, baton->exif, baton->exifLength, sharp::FreeCallback));
}
if (baton->iccLength > 0) {
info.Set("icc", Napi::Buffer<char>::New(env, baton->icc, baton->iccLength, sharp::FreeCallback));
}
if (baton->iptcLength > 0) {
info.Set("iptc", Napi::Buffer<char>::New(env, baton->iptc, baton->iptcLength, sharp::FreeCallback));
}
if (baton->xmpLength > 0) {
info.Set("xmp", Napi::Buffer<char>::New(env, baton->xmp, baton->xmpLength, sharp::FreeCallback));
}
if (baton->tifftagPhotoshopLength > 0) {
info.Set("tifftagPhotoshop",
Napi::Buffer<char>::New(env, baton->tifftagPhotoshop, baton->tifftagPhotoshopLength, sharp::FreeCallback));
}
Callback().MakeCallback(Receiver().Value(), { env.Null(), info });
} else {
Callback().MakeCallback(Receiver().Value(), { Napi::Error::New(env, baton->err).Value() });
}
delete baton->input;
delete baton;
} }
private: private:
MetadataBaton* baton; MetadataBaton* baton;
Nan::Callback *debuglog; Napi::FunctionReference debuglog;
std::vector<v8::Local<v8::Object>> buffersToPersist;
}; };
/* /*
metadata(options, callback) metadata(options, callback)
*/ */
NAN_METHOD(metadata) { Napi::Value metadata(const Napi::CallbackInfo& info) {
// Input Buffers must not undergo GC compaction during processing
std::vector<v8::Local<v8::Object>> buffersToPersist;
// V8 objects are converted to non-V8 types held in the baton struct // V8 objects are converted to non-V8 types held in the baton struct
MetadataBaton *baton = new MetadataBaton; MetadataBaton *baton = new MetadataBaton;
v8::Local<v8::Object> options = info[0].As<v8::Object>(); Napi::Object options = info[0].As<Napi::Object>();
// Input // 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 // 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 // Join queue for worker thread
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>()); Napi::Function callback = info[1].As<Napi::Function>();
Nan::AsyncQueueWorker(new MetadataWorker(callback, baton, debuglog, buffersToPersist)); MetadataWorker *worker = new MetadataWorker(callback, baton, debuglog);
worker->Receiver().Set("options", options);
worker->Queue();
// Increment queued task counter // Increment queued task counter
g_atomic_int_inc(&sharp::counterQueue); g_atomic_int_inc(&sharp::counterQueue);
return info.Env().Undefined();
} }

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors. // Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
#define SRC_METADATA_H_ #define SRC_METADATA_H_
#include <string> #include <string>
#include <nan.h> #include <napi.h>
#include "./common.h" #include "./common.h"
@@ -36,7 +36,10 @@ struct MetadataBaton {
int paletteBitDepth; int paletteBitDepth;
int pages; int pages;
int pageHeight; int pageHeight;
int loop;
std::vector<int> delay;
int pagePrimary; int pagePrimary;
std::vector<std::pair<int, int>> levels;
bool hasProfile; bool hasProfile;
bool hasAlpha; bool hasAlpha;
int orientation; int orientation;
@@ -62,6 +65,7 @@ struct MetadataBaton {
paletteBitDepth(0), paletteBitDepth(0),
pages(0), pages(0),
pageHeight(0), pageHeight(0),
loop(-1),
pagePrimary(-1), pagePrimary(-1),
hasProfile(false), hasProfile(false),
hasAlpha(false), hasAlpha(false),
@@ -78,6 +82,6 @@ struct MetadataBaton {
tifftagPhotoshopLength(0) {} tifftagPhotoshopLength(0) {}
}; };
NAN_METHOD(metadata); Napi::Value metadata(const Napi::CallbackInfo& info);
#endif // SRC_METADATA_H_ #endif // SRC_METADATA_H_

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors. // Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -27,29 +27,6 @@ using vips::VImage;
using vips::VError; using vips::VError;
namespace sharp { namespace sharp {
/*
Removes alpha channel, if any.
*/
VImage RemoveAlpha(VImage image) {
if (HasAlpha(image)) {
image = image.extract_band(0, VImage::option()->set("n", image.bands() - 1));
}
return image;
}
/*
Ensures alpha channel, if missing.
*/
VImage EnsureAlpha(VImage image) {
if (!HasAlpha(image)) {
std::vector<double> alpha;
alpha.push_back(sharp::MaximumImageAlpha(image.interpretation()));
image = image.bandjoin_const(alpha);
}
return image;
}
/* /*
* Tint an image using the specified chroma, preserving the original image luminance * Tint an image using the specified chroma, preserving the original image luminance
*/ */

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors. // Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -25,16 +25,6 @@ using vips::VImage;
namespace sharp { namespace sharp {
/*
Removes alpha channel, if any.
*/
VImage RemoveAlpha(VImage image);
/*
Ensures alpha channel, if missing.
*/
VImage EnsureAlpha(VImage image);
/* /*
* Tint an image using the specified chroma, preserving the original image luminance * Tint an image using the specified chroma, preserving the original image luminance
*/ */

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors. // Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -19,12 +19,12 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <nan.h> #include <napi.h>
#include <vips/vips8> #include <vips/vips8>
#include "./common.h" #include "./common.h"
NAN_METHOD(pipeline); Napi::Value pipeline(const Napi::CallbackInfo& info);
enum class Canvas { enum class Canvas {
CROP, CROP,
@@ -55,7 +55,6 @@ struct Composite {
struct PipelineBaton { struct PipelineBaton {
sharp::InputDescriptor *input; sharp::InputDescriptor *input;
int limitInputPixels;
std::string formatOut; std::string formatOut;
std::string fileOut; std::string fileOut;
void *bufferOut; void *bufferOut;
@@ -119,7 +118,6 @@ struct PipelineBaton {
int extendRight; int extendRight;
std::vector<double> extendBackground; std::vector<double> extendBackground;
bool withoutEnlargement; bool withoutEnlargement;
VipsAccess accessMethod;
int jpegQuality; int jpegQuality;
bool jpegProgressive; bool jpegProgressive;
std::string jpegChromaSubsampling; std::string jpegChromaSubsampling;
@@ -145,18 +143,19 @@ struct PipelineBaton {
VipsForeignTiffCompression tiffCompression; VipsForeignTiffCompression tiffCompression;
VipsForeignTiffPredictor tiffPredictor; VipsForeignTiffPredictor tiffPredictor;
bool tiffPyramid; bool tiffPyramid;
bool tiffSquash; int tiffBitdepth;
bool tiffTile; bool tiffTile;
int tiffTileHeight; int tiffTileHeight;
int tiffTileWidth; int tiffTileWidth;
double tiffXres; double tiffXres;
double tiffYres; double tiffYres;
int heifQuality; int heifQuality;
int heifCompression; // TODO(libvips 8.9.0): VipsForeignHeifCompression VipsForeignHeifCompression heifCompression;
bool heifLossless; bool heifLossless;
std::string err; std::string err;
bool withMetadata; bool withMetadata;
int withMetadataOrientation; int withMetadataOrientation;
std::string withMetadataIcc;
std::unique_ptr<double[]> convKernel; std::unique_ptr<double[]> convKernel;
int convKernelWidth; int convKernelWidth;
int convKernelHeight; int convKernelHeight;
@@ -169,6 +168,9 @@ struct PipelineBaton {
bool removeAlpha; bool removeAlpha;
bool ensureAlpha; bool ensureAlpha;
VipsInterpretation colourspace; VipsInterpretation colourspace;
int pageHeight;
std::vector<int> delay;
int loop;
int tileSize; int tileSize;
int tileOverlap; int tileOverlap;
VipsForeignDzContainer tileContainer; VipsForeignDzContainer tileContainer;
@@ -182,7 +184,6 @@ struct PipelineBaton {
PipelineBaton(): PipelineBaton():
input(nullptr), input(nullptr),
limitInputPixels(0),
bufferOutLength(0), bufferOutLength(0),
topOffsetPre(-1), topOffsetPre(-1),
topOffsetPost(-1), topOffsetPost(-1),
@@ -254,14 +255,14 @@ struct PipelineBaton {
tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG), tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG),
tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL), tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL),
tiffPyramid(false), tiffPyramid(false),
tiffSquash(false), tiffBitdepth(8),
tiffTile(false), tiffTile(false),
tiffTileHeight(256), tiffTileHeight(256),
tiffTileWidth(256), tiffTileWidth(256),
tiffXres(1.0), tiffXres(1.0),
tiffYres(1.0), tiffYres(1.0),
heifQuality(80), heifQuality(80),
heifCompression(1), // TODO(libvips 8.9.0): VIPS_FOREIGN_HEIF_COMPRESSION_HEVC heifCompression(VIPS_FOREIGN_HEIF_COMPRESSION_HEVC),
heifLossless(false), heifLossless(false),
withMetadata(false), withMetadata(false),
withMetadataOrientation(-1), withMetadataOrientation(-1),
@@ -276,6 +277,9 @@ struct PipelineBaton {
removeAlpha(false), removeAlpha(false),
ensureAlpha(false), ensureAlpha(false),
colourspace(VIPS_INTERPRETATION_LAST), colourspace(VIPS_INTERPRETATION_LAST),
pageHeight(0),
delay{-1},
loop(-1),
tileSize(256), tileSize(256),
tileOverlap(0), tileOverlap(0),
tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS), tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS),

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors. // Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include <node.h> #include <napi.h>
#include <nan.h>
#include <vips/vips8> #include <vips/vips8>
#include "common.h" #include "common.h"
@@ -22,33 +21,30 @@
#include "utilities.h" #include "utilities.h"
#include "stats.h" #include "stats.h"
NAN_MODULE_INIT(init) { static void* sharp_vips_init(void*) {
vips_init("sharp"); vips_init("sharp");
return nullptr;
}
Napi::Object init(Napi::Env env, Napi::Object exports) {
static GOnce sharp_vips_init_once = G_ONCE_INIT;
g_once(&sharp_vips_init_once, static_cast<GThreadFunc>(sharp_vips_init), nullptr);
g_log_set_handler("VIPS", static_cast<GLogLevelFlags>(G_LOG_LEVEL_WARNING), g_log_set_handler("VIPS", static_cast<GLogLevelFlags>(G_LOG_LEVEL_WARNING),
static_cast<GLogFunc>(sharp::VipsWarningCallback), nullptr); static_cast<GLogFunc>(sharp::VipsWarningCallback), nullptr);
// Methods available to JavaScript // Methods available to JavaScript
Nan::Set(target, Nan::New("metadata").ToLocalChecked(), exports.Set("metadata", Napi::Function::New(env, metadata));
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(metadata)).ToLocalChecked()); exports.Set("pipeline", Napi::Function::New(env, pipeline));
Nan::Set(target, Nan::New("pipeline").ToLocalChecked(), exports.Set("cache", Napi::Function::New(env, cache));
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(pipeline)).ToLocalChecked()); exports.Set("concurrency", Napi::Function::New(env, concurrency));
Nan::Set(target, Nan::New("cache").ToLocalChecked(), exports.Set("counters", Napi::Function::New(env, counters));
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(cache)).ToLocalChecked()); exports.Set("simd", Napi::Function::New(env, simd));
Nan::Set(target, Nan::New("concurrency").ToLocalChecked(), exports.Set("libvipsVersion", Napi::Function::New(env, libvipsVersion));
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(concurrency)).ToLocalChecked()); exports.Set("format", Napi::Function::New(env, format));
Nan::Set(target, Nan::New("counters").ToLocalChecked(), exports.Set("_maxColourDistance", Napi::Function::New(env, _maxColourDistance));
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(counters)).ToLocalChecked()); exports.Set("stats", Napi::Function::New(env, stats));
Nan::Set(target, Nan::New("simd").ToLocalChecked(), return exports;
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());
} }
NAN_MODULE_WORKER_ENABLED(sharp, init) NODE_API_MODULE(sharp, init)

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors. // Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -16,28 +16,16 @@
#include <vector> #include <vector>
#include <iostream> #include <iostream>
#include <node.h> #include <napi.h>
#include <nan.h>
#include <vips/vips8> #include <vips/vips8>
#include "common.h" #include "common.h"
#include "stats.h" #include "stats.h"
class StatsWorker : public Nan::AsyncWorker { class StatsWorker : public Napi::AsyncWorker {
public: public:
StatsWorker( StatsWorker(Napi::Function callback, StatsBaton *baton, Napi::Function debuglog) :
Nan::Callback *callback, StatsBaton *baton, Nan::Callback *debuglog, Napi::AsyncWorker(callback), baton(baton), debuglog(Napi::Persistent(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() {} ~StatsWorker() {}
const int STAT_MIN_INDEX = 0; const int STAT_MIN_INDEX = 0;
@@ -54,15 +42,11 @@ class StatsWorker : public Nan::AsyncWorker {
void Execute() { void Execute() {
// Decrement queued task counter // Decrement queued task counter
g_atomic_int_dec_and_test(&sharp::counterQueue); g_atomic_int_dec_and_test(&sharp::counterQueue);
using Nan::New;
using Nan::Set;
using sharp::MaximumImageAlpha;
vips::VImage image; vips::VImage image;
sharp::ImageType imageType = sharp::ImageType::UNKNOWN; sharp::ImageType imageType = sharp::ImageType::UNKNOWN;
try { try {
std::tie(image, imageType) = OpenInput(baton->input, baton->accessMethod); std::tie(image, imageType) = OpenInput(baton->input);
} catch (vips::VError const &err) { } catch (vips::VError const &err) {
(baton->err).append(err.what()); (baton->err).append(err.what());
} }
@@ -71,25 +55,51 @@ class StatsWorker : public Nan::AsyncWorker {
vips::VImage stats = image.stats(); vips::VImage stats = image.stats();
int const bands = image.bands(); int const bands = image.bands();
for (int b = 1; b <= bands; b++) { for (int b = 1; b <= bands; b++) {
ChannelStats cStats(static_cast<int>(stats.getpoint(STAT_MIN_INDEX, b).front()), ChannelStats cStats(
static_cast<int>(stats.getpoint(STAT_MAX_INDEX, b).front()), static_cast<int>(stats.getpoint(STAT_MIN_INDEX, b).front()),
stats.getpoint(STAT_SUM_INDEX, b).front(), stats.getpoint(STAT_SQ_SUM_INDEX, b).front(), static_cast<int>(stats.getpoint(STAT_MAX_INDEX, b).front()),
stats.getpoint(STAT_MEAN_INDEX, b).front(), stats.getpoint(STAT_STDEV_INDEX, b).front(), stats.getpoint(STAT_SUM_INDEX, b).front(),
static_cast<int>(stats.getpoint(STAT_MINX_INDEX, b).front()), stats.getpoint(STAT_SQ_SUM_INDEX, b).front(),
static_cast<int>(stats.getpoint(STAT_MINY_INDEX, b).front()), stats.getpoint(STAT_MEAN_INDEX, b).front(),
static_cast<int>(stats.getpoint(STAT_MAXX_INDEX, b).front()), stats.getpoint(STAT_STDEV_INDEX, b).front(),
static_cast<int>(stats.getpoint(STAT_MAXY_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); baton->channelStats.push_back(cStats);
} }
// Image is not opaque when alpha layer is present and contains a non-mamixa value // Image is not opaque when alpha layer is present and contains a non-mamixa value
if (sharp::HasAlpha(image)) { if (sharp::HasAlpha(image)) {
double const minAlpha = static_cast<double>(stats.getpoint(STAT_MIN_INDEX, bands).front()); 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; baton->isOpaque = false;
} }
} }
// Convert to greyscale
vips::VImage greyscale = image.colourspace(VIPS_INTERPRETATION_B_W)[0];
// Estimate entropy via histogram of greyscale value frequency // Estimate entropy via histogram of greyscale value frequency
baton->entropy = std::abs(image.colourspace(VIPS_INTERPRETATION_B_W)[0].hist_find().hist_entropy()); baton->entropy = std::abs(greyscale.hist_find().hist_entropy());
// Estimate sharpness via standard deviation of greyscale laplacian
if (image.width() > 1 || image.height() > 1) {
VImage laplacian = VImage::new_matrixv(3, 3,
0.0, 1.0, 0.0,
1.0, -4.0, 1.0,
0.0, 1.0, 0.0);
laplacian.set("scale", 9.0);
baton->sharpness = greyscale.conv(laplacian).deviate();
}
// Most dominant sRGB colour via 4096-bin 3D histogram
vips::VImage hist = sharp::RemoveAlpha(image)
.colourspace(VIPS_INTERPRETATION_sRGB)
.hist_find_ndim(VImage::option()->set("bins", 16));
std::complex<double> maxpos = hist.maxpos();
int const dx = static_cast<int>(std::real(maxpos));
int const dy = static_cast<int>(std::imag(maxpos));
std::vector<double> pel = hist(dx, dy);
int const dz = std::distance(pel.begin(), std::find(pel.begin(), pel.end(), hist.max()));
baton->dominantRed = dx * 16 + 8;
baton->dominantGreen = dy * 16 + 8;
baton->dominantBlue = dz * 16 + 8;
} catch (vips::VError const &err) { } catch (vips::VError const &err) {
(baton->err).append(err.what()); (baton->err).append(err.what());
} }
@@ -100,93 +110,84 @@ class StatsWorker : public Nan::AsyncWorker {
vips_thread_shutdown(); vips_thread_shutdown();
} }
void HandleOKCallback() { void OnOK() {
using Nan::New; Napi::Env env = Env();
using Nan::Set; Napi::HandleScope scope(env);
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;
// Handle warnings // Handle warnings
std::string warning = sharp::VipsWarningPop(); std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) { while (!warning.empty()) {
v8::Local<v8::Value> message[1] = { New(warning).ToLocalChecked() }; debuglog.Call({ Napi::String::New(env, warning) });
debuglog->Call(1, message, async_resource);
warning = sharp::VipsWarningPop(); warning = sharp::VipsWarningPop();
} }
// Return to JavaScript if (baton->err.empty()) {
callback->Call(2, argv, async_resource); // Stats Object
Napi::Object info = Napi::Object::New(env);
Napi::Array channels = Napi::Array::New(env);
std::vector<ChannelStats>::iterator it;
int i = 0;
for (it = baton->channelStats.begin(); it < baton->channelStats.end(); it++, i++) {
Napi::Object channelStat = Napi::Object::New(env);
channelStat.Set("min", it->min);
channelStat.Set("max", it->max);
channelStat.Set("sum", it->sum);
channelStat.Set("squaresSum", it->squaresSum);
channelStat.Set("mean", it->mean);
channelStat.Set("stdev", it->stdev);
channelStat.Set("minX", it->minX);
channelStat.Set("minY", it->minY);
channelStat.Set("maxX", it->maxX);
channelStat.Set("maxY", it->maxY);
channels.Set(i, channelStat);
}
info.Set("channels", channels);
info.Set("isOpaque", baton->isOpaque);
info.Set("entropy", baton->entropy);
info.Set("sharpness", baton->sharpness);
Napi::Object dominant = Napi::Object::New(env);
dominant.Set("r", baton->dominantRed);
dominant.Set("g", baton->dominantGreen);
dominant.Set("b", baton->dominantBlue);
info.Set("dominant", dominant);
Callback().MakeCallback(Receiver().Value(), { env.Null(), info });
} else {
Callback().MakeCallback(Receiver().Value(), { Napi::Error::New(env, baton->err).Value() });
}
delete baton->input;
delete baton;
} }
private: private:
StatsBaton* baton; StatsBaton* baton;
Nan::Callback *debuglog; Napi::FunctionReference debuglog;
std::vector<v8::Local<v8::Object>> buffersToPersist;
}; };
/* /*
stats(options, callback) stats(options, callback)
*/ */
NAN_METHOD(stats) { Napi::Value stats(const Napi::CallbackInfo& info) {
using sharp::AttrTo;
// Input Buffers must not undergo GC compaction during processing
std::vector<v8::Local<v8::Object>> buffersToPersist;
// V8 objects are converted to non-V8 types held in the baton struct // V8 objects are converted to non-V8 types held in the baton struct
StatsBaton *baton = new StatsBaton; StatsBaton *baton = new StatsBaton;
v8::Local<v8::Object> options = info[0].As<v8::Object>(); Napi::Object options = info[0].As<Napi::Object>();
// Input // Input
baton->input = sharp::CreateInputDescriptor(sharp::AttrAs<v8::Object>(options, "input"), buffersToPersist); baton->input = sharp::CreateInputDescriptor(options.Get("input").As<Napi::Object>());
baton->accessMethod = AttrTo<bool>(options, "sequentialRead") ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
// Function to notify of libvips warnings // 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 // Join queue for worker thread
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>()); Napi::Function callback = info[1].As<Napi::Function>();
Nan::AsyncQueueWorker(new StatsWorker(callback, baton, debuglog, buffersToPersist)); StatsWorker *worker = new StatsWorker(callback, baton, debuglog);
worker->Receiver().Set("options", options);
worker->Queue();
// Increment queued task counter // Increment queued task counter
g_atomic_int_inc(&sharp::counterQueue); g_atomic_int_inc(&sharp::counterQueue);
return info.Env().Undefined();
} }

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors. // Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
#define SRC_STATS_H_ #define SRC_STATS_H_
#include <string> #include <string>
#include <nan.h> #include <napi.h>
#include "./common.h" #include "./common.h"
@@ -33,12 +33,8 @@ struct ChannelStats {
int maxX; int maxX;
int maxY; int maxY;
ChannelStats(): ChannelStats(int minVal, int maxVal, double sumVal, double squaresSumVal,
min(0), max(0), sum(0), squaresSum(0), mean(0), stdev(0) double meanVal, double stdevVal, int minXVal, int minYVal, int maxXVal, int maxYVal):
, 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):
min(minVal), max(maxVal), sum(sumVal), squaresSum(squaresSumVal), min(minVal), max(maxVal), sum(sumVal), squaresSum(squaresSumVal),
mean(meanVal), stdev(stdevVal), minX(minXVal), minY(minYVal), maxX(maxXVal), maxY(maxYVal) {} mean(meanVal), stdev(stdevVal), minX(minXVal), minY(minYVal), maxX(maxXVal), maxY(maxYVal) {}
}; };
@@ -46,22 +42,29 @@ struct ChannelStats {
struct StatsBaton { struct StatsBaton {
// Input // Input
sharp::InputDescriptor *input; sharp::InputDescriptor *input;
VipsAccess accessMethod;
// Output // Output
std::vector<ChannelStats> channelStats; std::vector<ChannelStats> channelStats;
bool isOpaque; bool isOpaque;
double entropy; double entropy;
double sharpness;
int dominantRed;
int dominantGreen;
int dominantBlue;
std::string err; std::string err;
StatsBaton(): StatsBaton():
input(nullptr), input(nullptr),
isOpaque(true), isOpaque(true),
entropy(0.0) entropy(0.0),
sharpness(0.0),
dominantRed(0),
dominantGreen(0),
dominantBlue(0)
{} {}
}; };
NAN_METHOD(stats); Napi::Value stats(const Napi::CallbackInfo& info);
#endif // SRC_STATS_H_ #endif // SRC_STATS_H_

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors. // Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -15,8 +15,7 @@
#include <cmath> #include <cmath>
#include <string> #include <string>
#include <node.h> #include <napi.h>
#include <nan.h>
#include <vips/vips8> #include <vips/vips8>
#include <vips/vector.h> #include <vips/vector.h>
@@ -24,183 +23,145 @@
#include "operations.h" #include "operations.h"
#include "utilities.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 Get and set cache limits
*/ */
NAN_METHOD(cache) { Napi::Value cache(const Napi::CallbackInfo& info) {
HandleScope(); Napi::Env env = info.Env();
// Set memory limit // Set memory limit
if (info[0]->IsInt32()) { if (info[0].IsNumber()) {
vips_cache_set_max_mem(To<int32_t>(info[0]).FromJust() * 1048576); vips_cache_set_max_mem(info[0].As<Napi::Number>().Int32Value() * 1048576);
} }
// Set file limit // Set file limit
if (info[1]->IsInt32()) { if (info[1].IsNumber()) {
vips_cache_set_max_files(To<int32_t>(info[1]).FromJust()); vips_cache_set_max_files(info[1].As<Napi::Number>().Int32Value());
} }
// Set items limit // Set items limit
if (info[2]->IsInt32()) { if (info[2].IsNumber()) {
vips_cache_set_max(To<int32_t>(info[2]).FromJust()); vips_cache_set_max(info[2].As<Napi::Number>().Int32Value());
} }
// Get memory stats // Get memory stats
Local<Object> memory = New<Object>(); Napi::Object memory = Napi::Object::New(env);
Set(memory, New("current").ToLocalChecked(), memory.Set("current", round(vips_tracked_get_mem() / 1048576));
New<Integer>(static_cast<int>(round(vips_tracked_get_mem() / 1048576)))); memory.Set("high", round(vips_tracked_get_mem_highwater() / 1048576));
Set(memory, New("high").ToLocalChecked(), memory.Set("max", round(vips_cache_get_max_mem() / 1048576));
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))));
// Get file stats // Get file stats
Local<Object> files = New<Object>(); Napi::Object files = Napi::Object::New(env);
Set(files, New("current").ToLocalChecked(), New<Integer>(vips_tracked_get_files())); files.Set("current", vips_tracked_get_files());
Set(files, New("max").ToLocalChecked(), New<Integer>(vips_cache_get_max_files())); files.Set("max", vips_cache_get_max_files());
// Get item stats // Get item stats
Local<Object> items = New<Object>(); Napi::Object items = Napi::Object::New(env);
Set(items, New("current").ToLocalChecked(), New<Integer>(vips_cache_get_size())); items.Set("current", vips_cache_get_size());
Set(items, New("max").ToLocalChecked(), New<Integer>(vips_cache_get_max())); items.Set("max", vips_cache_get_max());
Local<Object> cache = New<Object>(); Napi::Object cache = Napi::Object::New(env);
Set(cache, New("memory").ToLocalChecked(), memory); cache.Set("memory", memory);
Set(cache, New("files").ToLocalChecked(), files); cache.Set("files", files);
Set(cache, New("items").ToLocalChecked(), items); cache.Set("items", items);
info.GetReturnValue().Set(cache); return cache;
} }
/* /*
Get and set size of thread pool Get and set size of thread pool
*/ */
NAN_METHOD(concurrency) { Napi::Value concurrency(const Napi::CallbackInfo& info) {
HandleScope();
// Set concurrency // Set concurrency
if (info[0]->IsInt32()) { if (info[0].IsNumber()) {
vips_concurrency_set(To<int32_t>(info[0]).FromJust()); vips_concurrency_set(info[0].As<Napi::Number>().Int32Value());
} }
// Get concurrency // 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) Get internal counters (queued tasks, processing tasks)
*/ */
NAN_METHOD(counters) { Napi::Value counters(const Napi::CallbackInfo& info) {
using sharp::counterProcess; Napi::Object counters = Napi::Object::New(info.Env());
using sharp::counterQueue; counters.Set("queue", sharp::counterQueue);
counters.Set("process", sharp::counterProcess);
HandleScope(); return counters;
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);
} }
/* /*
Get and set use of SIMD vector unit instructions Get and set use of SIMD vector unit instructions
*/ */
NAN_METHOD(simd) { Napi::Value simd(const Napi::CallbackInfo& info) {
HandleScope();
// Set state // Set state
if (info[0]->IsBoolean()) { if (info[0].IsBoolean()) {
vips_vector_set_enabled(To<bool>(info[0]).FromJust()); vips_vector_set_enabled(info[0].As<Napi::Boolean>().Value());
} }
// Get state // Get state
info.GetReturnValue().Set(New<Boolean>(vips_vector_isenabled())); return Napi::Boolean::New(info.Env(), vips_vector_isenabled());
} }
/* /*
Get libvips version Get libvips version
*/ */
NAN_METHOD(libvipsVersion) { Napi::Value libvipsVersion(const Napi::CallbackInfo& info) {
HandleScope();
char version[9]; char version[9];
g_snprintf(version, sizeof(version), "%d.%d.%d", vips_version(0), vips_version(1), vips_version(2)); g_snprintf(version, sizeof(version), "%d.%d.%d", vips_version(0), vips_version(1), vips_version(2));
info.GetReturnValue().Set(New(version).ToLocalChecked()); return Napi::String::New(info.Env(), version);
} }
/* /*
Get available input/output file/buffer/stream formats Get available input/output file/buffer/stream formats
*/ */
NAN_METHOD(format) { Napi::Value format(const Napi::CallbackInfo& info) {
HandleScope(); Napi::Env env = info.Env();
Napi::Object format = Napi::Object::New(env);
// Attribute names
Local<String> attrId = New("id").ToLocalChecked();
Local<String> attrInput = New("input").ToLocalChecked();
Local<String> attrOutput = New("output").ToLocalChecked();
Local<String> attrFile = New("file").ToLocalChecked();
Local<String> attrBuffer = New("buffer").ToLocalChecked();
Local<String> attrStream = New("stream").ToLocalChecked();
// Which load/save operations are available for each compressed format?
Local<Object> format = New<Object>();
for (std::string const f : { for (std::string const f : {
"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz", "jpeg", "png", "webp", "tiff", "magick", "openslide", "dz",
"ppm", "fits", "gif", "svg", "heif", "pdf", "vips" "ppm", "fits", "gif", "svg", "heif", "pdf", "vips"
}) { }) {
// Input // Input
Local<Boolean> hasInputFile = Napi::Boolean hasInputFile =
New<Boolean>(vips_type_find("VipsOperation", (f + "load").c_str())); Napi::Boolean::New(env, vips_type_find("VipsOperation", (f + "load").c_str()));
Local<Boolean> hasInputBuffer = Napi::Boolean hasInputBuffer =
New<Boolean>(vips_type_find("VipsOperation", (f + "load_buffer").c_str())); Napi::Boolean::New(env, vips_type_find("VipsOperation", (f + "load_buffer").c_str()));
Local<Object> input = New<Object>(); Napi::Object input = Napi::Object::New(env);
Set(input, attrFile, hasInputFile); input.Set("file", hasInputFile);
Set(input, attrBuffer, hasInputBuffer); input.Set("buffer", hasInputBuffer);
Set(input, attrStream, hasInputBuffer); input.Set("stream", hasInputBuffer);
// Output // Output
Local<Boolean> hasOutputFile = Napi::Boolean hasOutputFile =
New<Boolean>(vips_type_find("VipsOperation", (f + "save").c_str())); Napi::Boolean::New(env, vips_type_find("VipsOperation", (f + "save").c_str()));
Local<Boolean> hasOutputBuffer = Napi::Boolean hasOutputBuffer =
New<Boolean>(vips_type_find("VipsOperation", (f + "save_buffer").c_str())); Napi::Boolean::New(env, vips_type_find("VipsOperation", (f + "save_buffer").c_str()));
Local<Object> output = New<Object>(); Napi::Object output = Napi::Object::New(env);
Set(output, attrFile, hasOutputFile); output.Set("file", hasOutputFile);
Set(output, attrBuffer, hasOutputBuffer); output.Set("buffer", hasOutputBuffer);
Set(output, attrStream, hasOutputBuffer); output.Set("stream", hasOutputBuffer);
// Other attributes // Other attributes
Local<Object> container = New<Object>(); Napi::Object container = Napi::Object::New(env);
Local<String> formatId = New(f).ToLocalChecked(); container.Set("id", f);
Set(container, attrId, formatId); container.Set("input", input);
Set(container, attrInput, input); container.Set("output", output);
Set(container, attrOutput, output);
// Add to set of formats // Add to set of formats
Set(format, formatId, container); format.Set(f, container);
} }
// Raw, uncompressed data // Raw, uncompressed data
Local<Object> raw = New<Object>(); Napi::Boolean supported = Napi::Boolean::New(env, true);
Local<String> rawId = New("raw").ToLocalChecked(); Napi::Boolean unsupported = Napi::Boolean::New(env, false);
Set(raw, attrId, rawId); Napi::Object rawInput = Napi::Object::New(env);
Set(format, rawId, raw); rawInput.Set("file", unsupported);
Local<Boolean> supported = New<Boolean>(true); rawInput.Set("buffer", supported);
Local<Boolean> unsupported = New<Boolean>(false); rawInput.Set("stream", supported);
Local<Object> rawInput = New<Object>(); Napi::Object rawOutput = Napi::Object::New(env);
Set(rawInput, attrFile, unsupported); rawOutput.Set("file", unsupported);
Set(rawInput, attrBuffer, supported); rawOutput.Set("buffer", supported);
Set(rawInput, attrStream, supported); rawOutput.Set("stream", supported);
Set(raw, attrInput, rawInput); Napi::Object raw = Napi::Object::New(env);
Local<Object> rawOutput = New<Object>(); raw.Set("id", "raw");
Set(rawOutput, attrFile, unsupported); raw.Set("input", rawInput);
Set(rawOutput, attrBuffer, supported); raw.Set("output", rawOutput);
Set(rawOutput, attrStream, supported); format.Set("raw", raw);
Set(raw, attrOutput, rawOutput);
info.GetReturnValue().Set(format); return format;
} }
/* /*
@@ -208,65 +169,59 @@ NAN_METHOD(format) {
Calculates the maximum colour distance using the DE2000 algorithm Calculates the maximum colour distance using the DE2000 algorithm
between two images of the same dimensions and number of channels. between two images of the same dimensions and number of channels.
*/ */
NAN_METHOD(_maxColourDistance) { Napi::Value _maxColourDistance(const Napi::CallbackInfo& info) {
using vips::VImage; Napi::Env env = info.Env();
using vips::VError;
using sharp::DetermineImageType;
using sharp::ImageType;
using sharp::HasAlpha;
HandleScope();
// Open input files // Open input files
VImage image1; VImage image1;
ImageType imageType1 = DetermineImageType(*Utf8String(info[0])); sharp::ImageType imageType1 = sharp::DetermineImageType(info[0].As<Napi::String>().Utf8Value().data());
if (imageType1 != ImageType::UNKNOWN) { if (imageType1 != sharp::ImageType::UNKNOWN) {
try { try {
image1 = VImage::new_from_file(*Utf8String(info[0])); image1 = VImage::new_from_file(info[0].As<Napi::String>().Utf8Value().c_str());
} catch (...) { } catch (...) {
return ThrowError("Input file 1 has corrupt header"); throw Napi::Error::New(env, "Input file 1 has corrupt header");
} }
} else { } 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; VImage image2;
ImageType imageType2 = DetermineImageType(*Utf8String(info[1])); sharp::ImageType imageType2 = sharp::DetermineImageType(info[1].As<Napi::String>().Utf8Value().data());
if (imageType2 != ImageType::UNKNOWN) { if (imageType2 != sharp::ImageType::UNKNOWN) {
try { try {
image2 = VImage::new_from_file(*Utf8String(info[1])); image2 = VImage::new_from_file(info[1].As<Napi::String>().Utf8Value().c_str());
} catch (...) { } catch (...) {
return ThrowError("Input file 2 has corrupt header"); throw Napi::Error::New(env, "Input file 2 has corrupt header");
} }
} else { } 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 // Ensure same number of channels
if (image1.bands() != image2.bands()) { if (image1.bands() != image2.bands()) {
return ThrowError("mismatchedBands"); throw Napi::Error::New(env, "mismatchedBands");
} }
// Ensure same dimensions // Ensure same dimensions
if (image1.width() != image2.width() || image1.height() != image2.height()) { if (image1.width() != image2.width() || image1.height() != image2.height()) {
return ThrowError("mismatchedDimensions"); throw Napi::Error::New(env, "mismatchedDimensions");
} }
double maxColourDistance; double maxColourDistance;
try { try {
// Premultiply and remove alpha // Premultiply and remove alpha
if (HasAlpha(image1)) { if (sharp::HasAlpha(image1)) {
image1 = image1.premultiply().extract_band(1, VImage::option()->set("n", image1.bands() - 1)); 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)); image2 = image2.premultiply().extract_band(1, VImage::option()->set("n", image2.bands() - 1));
} }
// Calculate colour distance // Calculate colour distance
maxColourDistance = image1.dE00(image2).max(); maxColourDistance = image1.dE00(image2).max();
} catch (VError const &err) { } catch (vips::VError const &err) {
return ThrowError(err.what()); throw Napi::Error::New(env, err.what());
} }
// Clean up libvips' per-request data and threads // Clean up libvips' per-request data and threads
vips_error_clear(); vips_error_clear();
vips_thread_shutdown(); vips_thread_shutdown();
info.GetReturnValue().Set(New<Number>(maxColourDistance)); return Napi::Number::New(env, maxColourDistance);
} }

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors. // Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -15,14 +15,14 @@
#ifndef SRC_UTILITIES_H_ #ifndef SRC_UTILITIES_H_
#define SRC_UTILITIES_H_ #define SRC_UTILITIES_H_
#include <nan.h> #include <napi.h>
NAN_METHOD(cache); Napi::Value cache(const Napi::CallbackInfo& info);
NAN_METHOD(concurrency); Napi::Value concurrency(const Napi::CallbackInfo& info);
NAN_METHOD(counters); Napi::Value counters(const Napi::CallbackInfo& info);
NAN_METHOD(simd); Napi::Value simd(const Napi::CallbackInfo& info);
NAN_METHOD(libvipsVersion); Napi::Value libvipsVersion(const Napi::CallbackInfo& info);
NAN_METHOD(format); Napi::Value format(const Napi::CallbackInfo& info);
NAN_METHOD(_maxColourDistance); Napi::Value _maxColourDistance(const Napi::CallbackInfo& info);
#endif // SRC_UTILITIES_H_ #endif // SRC_UTILITIES_H_

View File

@@ -12,12 +12,12 @@
"benchmark": "^2.1.4", "benchmark": "^2.1.4",
"gm": "^1.23.1", "gm": "^1.23.1",
"imagemagick": "^0.1.3", "imagemagick": "^0.1.3",
"jimp": "^0.8.4", "jimp": "^0.16.0",
"mapnik": "^4.3.1", "mapnik": "^4.5.2",
"semver": "^6.3.0" "semver": "^7.1.1"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
"node": ">=8.5.0" "node": ">=10.16.0"
} }
} }

View File

@@ -460,8 +460,7 @@ async.series({
}).add('sharp-sequentialRead', { }).add('sharp-sequentialRead', {
defer: true, defer: true,
fn: function (deferred) { fn: function (deferred) {
sharp(inputJpgBuffer) sharp(inputJpgBuffer, { sequentialRead: true })
.sequentialRead()
.resize(width, height) .resize(width, height)
.toBuffer(function (err, buffer) { .toBuffer(function (err, buffer) {
if (err) { if (err) {
@@ -565,8 +564,9 @@ async.series({
}, },
// PNG // PNG
png: function (callback) { png: function (callback) {
const inputPngBuffer = fs.readFileSync(fixtures.inputPng); const inputPngBuffer = fs.readFileSync(fixtures.inputPngAlphaPremultiplicationLarge);
const pngSuite = new Benchmark.Suite('png'); const pngSuite = new Benchmark.Suite('png');
const minSamples = 64;
// jimp // jimp
pngSuite.add('jimp-buffer-buffer', { pngSuite.add('jimp-buffer-buffer', {
defer: true, defer: true,
@@ -577,6 +577,8 @@ async.series({
} else { } else {
image image
.resize(width, height) .resize(width, height)
.deflateLevel(6)
.filterType(0)
.getBuffer(jimp.MIME_PNG, function (err) { .getBuffer(jimp.MIME_PNG, function (err) {
if (err) { if (err) {
throw err; throw err;
@@ -590,12 +592,14 @@ async.series({
}).add('jimp-file-file', { }).add('jimp-file-file', {
defer: true, defer: true,
fn: function (deferred) { fn: function (deferred) {
jimp.read(fixtures.inputPng, function (err, image) { jimp.read(fixtures.inputPngAlphaPremultiplicationLarge, function (err, image) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
image image
.resize(width, height) .resize(width, height)
.deflateLevel(6)
.filterType(0)
.write(fixtures.outputPng, function (err) { .write(fixtures.outputPng, function (err) {
if (err) { if (err) {
throw err; throw err;
@@ -611,7 +615,7 @@ async.series({
pngSuite.add('mapnik-file-file', { pngSuite.add('mapnik-file-file', {
defer: true, defer: true,
fn: function (deferred) { fn: function (deferred) {
mapnik.Image.open(fixtures.inputPng, function (err, img) { mapnik.Image.open(fixtures.inputPngAlphaPremultiplicationLarge, function (err, img) {
if (err) throw err; if (err) throw err;
img.premultiply(function (err, img) { img.premultiply(function (err, img) {
if (err) throw err; if (err) throw err;
@@ -658,11 +662,15 @@ async.series({
defer: true, defer: true,
fn: function (deferred) { fn: function (deferred) {
imagemagick.resize({ imagemagick.resize({
srcPath: fixtures.inputPng, srcPath: fixtures.inputPngAlphaPremultiplicationLarge,
dstPath: fixtures.outputPng, dstPath: fixtures.outputPng,
width: width, width: width,
height: height, height: height,
filter: 'Lanczos' filter: 'Lanczos',
customArgs: [
'-define', 'PNG:compression-level=6',
'-define', 'PNG:compression-filter=0'
]
}, function (err) { }, function (err) {
if (err) { if (err) {
throw err; throw err;
@@ -676,9 +684,11 @@ async.series({
pngSuite.add('gm-file-file', { pngSuite.add('gm-file-file', {
defer: true, defer: true,
fn: function (deferred) { fn: function (deferred) {
gm(fixtures.inputPng) gm(fixtures.inputPngAlphaPremultiplicationLarge)
.filter('Lanczos') .filter('Lanczos')
.resize(width, height) .resize(width, height)
.define('PNG:compression-level=6')
.define('PNG:compression-filter=0')
.write(fixtures.outputPng, function (err) { .write(fixtures.outputPng, function (err) {
if (err) { if (err) {
throw err; throw err;
@@ -690,9 +700,11 @@ async.series({
}).add('gm-file-buffer', { }).add('gm-file-buffer', {
defer: true, defer: true,
fn: function (deferred) { fn: function (deferred) {
gm(fixtures.inputPng) gm(fixtures.inputPngAlphaPremultiplicationLarge)
.filter('Lanczos') .filter('Lanczos')
.resize(width, height) .resize(width, height)
.define('PNG:compression-level=6')
.define('PNG:compression-filter=0')
.toBuffer(function (err, buffer) { .toBuffer(function (err, buffer) {
if (err) { if (err) {
throw err; throw err;
@@ -706,9 +718,11 @@ async.series({
// sharp // sharp
pngSuite.add('sharp-buffer-file', { pngSuite.add('sharp-buffer-file', {
defer: true, defer: true,
minSamples,
fn: function (deferred) { fn: function (deferred) {
sharp(inputPngBuffer) sharp(inputPngBuffer)
.resize(width, height) .resize(width, height)
.png({ compressionLevel: 6 })
.toFile(fixtures.outputPng, function (err) { .toFile(fixtures.outputPng, function (err) {
if (err) { if (err) {
throw err; throw err;
@@ -719,9 +733,11 @@ async.series({
} }
}).add('sharp-buffer-buffer', { }).add('sharp-buffer-buffer', {
defer: true, defer: true,
minSamples,
fn: function (deferred) { fn: function (deferred) {
sharp(inputPngBuffer) sharp(inputPngBuffer)
.resize(width, height) .resize(width, height)
.png({ compressionLevel: 6 })
.toBuffer(function (err, buffer) { .toBuffer(function (err, buffer) {
if (err) { if (err) {
throw err; throw err;
@@ -733,9 +749,11 @@ async.series({
} }
}).add('sharp-file-file', { }).add('sharp-file-file', {
defer: true, defer: true,
minSamples,
fn: function (deferred) { fn: function (deferred) {
sharp(fixtures.inputPng) sharp(fixtures.inputPngAlphaPremultiplicationLarge)
.resize(width, height) .resize(width, height)
.png({ compressionLevel: 6 })
.toFile(fixtures.outputPng, function (err) { .toFile(fixtures.outputPng, function (err) {
if (err) { if (err) {
throw err; throw err;
@@ -746,9 +764,11 @@ async.series({
} }
}).add('sharp-file-buffer', { }).add('sharp-file-buffer', {
defer: true, defer: true,
minSamples,
fn: function (deferred) { fn: function (deferred) {
sharp(fixtures.inputPng) sharp(fixtures.inputPngAlphaPremultiplicationLarge)
.resize(width, height) .resize(width, height)
.png({ compressionLevel: 6 })
.toBuffer(function (err, buffer) { .toBuffer(function (err, buffer) {
if (err) { if (err) {
throw err; throw err;
@@ -760,10 +780,11 @@ async.series({
} }
}).add('sharp-progressive', { }).add('sharp-progressive', {
defer: true, defer: true,
minSamples,
fn: function (deferred) { fn: function (deferred) {
sharp(inputPngBuffer) sharp(inputPngBuffer)
.resize(width, height) .resize(width, height)
.png({ progressive: true }) .png({ compressionLevel: 6, progressive: true })
.toBuffer(function (err, buffer) { .toBuffer(function (err, buffer) {
if (err) { if (err) {
throw err; throw err;
@@ -775,10 +796,27 @@ async.series({
} }
}).add('sharp-adaptiveFiltering', { }).add('sharp-adaptiveFiltering', {
defer: true, defer: true,
minSamples,
fn: function (deferred) { fn: function (deferred) {
sharp(inputPngBuffer) sharp(inputPngBuffer)
.resize(width, height) .resize(width, height)
.png({ adaptiveFiltering: true }) .png({ adaptiveFiltering: true, compressionLevel: 6 })
.toBuffer(function (err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add('sharp-compressionLevel=9', {
defer: true,
minSamples,
fn: function (deferred) {
sharp(inputPngBuffer)
.resize(width, height)
.png({ compressionLevel: 9 })
.toBuffer(function (err, buffer) { .toBuffer(function (err, buffer) {
if (err) { if (err) {
throw err; throw err;

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

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

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

After

Width:  |  Height:  |  Size: 122 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 14 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

BIN
test/fixtures/hilutite.icm vendored Normal file

Binary file not shown.

View File

@@ -79,6 +79,7 @@ module.exports = {
inputPngWithGreyAlpha: getPath('grey-8bit-alpha.png'), inputPngWithGreyAlpha: getPath('grey-8bit-alpha.png'),
inputPngWithOneColor: getPath('2x2_fdcce6.png'), inputPngWithOneColor: getPath('2x2_fdcce6.png'),
inputPngWithTransparency16bit: getPath('tbgn2c16.png'), // http://www.schaik.com/pngsuite/tbgn2c16.png inputPngWithTransparency16bit: getPath('tbgn2c16.png'), // http://www.schaik.com/pngsuite/tbgn2c16.png
inputPng16BitGreyAlpha: getPath('16-bit-grey-alpha.png'), // CC-BY-NC-SA florc http://www.colourlovers.com/pattern/50713/pat
inputPngOverlayLayer0: getPath('alpha-layer-0-background.png'), inputPngOverlayLayer0: getPath('alpha-layer-0-background.png'),
inputPngOverlayLayer1: getPath('alpha-layer-1-fill.png'), inputPngOverlayLayer1: getPath('alpha-layer-1-fill.png'),
inputPngAlphaPremultiplicationSmall: getPath('alpha-premultiply-1024x768-paper.png'), inputPngAlphaPremultiplicationSmall: getPath('alpha-premultiply-1024x768-paper.png'),
@@ -92,6 +93,9 @@ module.exports = {
inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp
inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
inputWebPAnimated: getPath('rotating-squares.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
inputWebPAnimatedLoop3: getPath('animated-loop-3.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
inputWebPAnimatedBigHeight: getPath('big-height.webp'),
inputTiff: getPath('G31D.TIF'), // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm inputTiff: getPath('G31D.TIF'), // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm
inputTiffMultipage: getPath('G31D_MULTI.TIF'), // gm convert G31D.TIF -resize 50% G31D_2.TIF ; tiffcp G31D.TIF G31D_2.TIF G31D_MULTI.TIF inputTiffMultipage: getPath('G31D_MULTI.TIF'), // gm convert G31D.TIF -resize 50% G31D_2.TIF ; tiffcp G31D.TIF G31D_2.TIF G31D_MULTI.TIF
inputTiffCielab: getPath('cielab-dagams.tiff'), // https://github.com/lovell/sharp/issues/646 inputTiffCielab: getPath('cielab-dagams.tiff'), // https://github.com/lovell/sharp/issues/646
@@ -101,7 +105,9 @@ module.exports = {
inputGif: getPath('Crash_test.gif'), // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif inputGif: getPath('Crash_test.gif'), // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif
inputGifGreyPlusAlpha: getPath('grey-plus-alpha.gif'), // http://i.imgur.com/gZ5jlmE.gif inputGifGreyPlusAlpha: getPath('grey-plus-alpha.gif'), // http://i.imgur.com/gZ5jlmE.gif
inputGifAnimated: getPath('rotating-squares.gif'), // CC0 https://loading.io/spinner/blocks/-rotating-squares-preloader-gif inputGifAnimated: getPath('rotating-squares.gif'), // CC0 https://loading.io/spinner/blocks/-rotating-squares-preloader-gif
inputGifAnimatedLoop3: getPath('animated-loop-3.gif'), // CC-BY-SA-4.0 Petrus3743 https://commons.wikimedia.org/wiki/File:01-Goldener_Schnitt_Formel-Animation.gif
inputSvg: getPath('check.svg'), // http://dev.w3.org/SVG/tools/svgweb/samples/svg-files/check.svg inputSvg: getPath('check.svg'), // http://dev.w3.org/SVG/tools/svgweb/samples/svg-files/check.svg
inputSvgSmallViewBox: getPath('circle.svg'),
inputSvgWithEmbeddedImages: getPath('struct-image-04-t.svg'), // https://dev.w3.org/SVG/profiles/1.2T/test/svg/struct-image-04-t.svg inputSvgWithEmbeddedImages: getPath('struct-image-04-t.svg'), // https://dev.w3.org/SVG/profiles/1.2T/test/svg/struct-image-04-t.svg
inputJPGBig: getPath('flowers.jpeg'), inputJPGBig: getPath('flowers.jpeg'),

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@@ -5,7 +5,7 @@ if ! type valgrind >/dev/null; then
exit 1 exit 1
fi fi
curl -s -o ./test/leak/libvips.supp https://raw.githubusercontent.com/libvips/libvips/master/libvips.supp curl -s -o ./test/leak/libvips.supp https://raw.githubusercontent.com/libvips/libvips/master/suppressions/valgrind.supp
for test in ./test/unit/*.js; do for test in ./test/unit/*.js; do
G_SLICE=always-malloc G_DEBUG=gc-friendly valgrind \ G_SLICE=always-malloc G_DEBUG=gc-friendly valgrind \

View File

@@ -39,6 +39,16 @@
Memcheck:Cond Memcheck:Cond
obj:*/libjpeg.so* obj:*/libjpeg.so*
} }
{
value_jpeg_obj_static
Memcheck:Value8
obj:*/libvips.so*
}
{
cond_jpeg_obj_static
Memcheck:Cond
obj:*/libvips.so*
}
{ {
param_jpeg_jpeg_finish_compress param_jpeg_jpeg_finish_compress
Memcheck:Param Memcheck:Param
@@ -196,6 +206,18 @@
... ...
fun:FcInitLoadConfigAndFonts fun:FcInitLoadConfigAndFonts
} }
{
leak_fontconfig_doContent
Memcheck:Leak
match-leak-kinds: definite
fun:malloc
...
fun:doContent
fun:doProlog
fun:prologInitProcessor
fun:XML_ParseBuffer
obj:*/libfontconfig.so.*
}
# libvips # libvips
{ {
@@ -292,6 +314,16 @@
... ...
fun:vips__init fun:vips__init
} }
{
leak_rsvg_static_data
Memcheck:Leak
match-leak-kinds: definite
fun:malloc
...
fun:rsvg_rust_handle_new_from_stream_sync
...
fun:vips_object_build
}
# libuv warnings # libuv warnings
{ {
@@ -342,6 +374,12 @@
... ...
obj:/usr/bin/iojs obj:/usr/bin/iojs
} }
{
value_node_invoke_params
Memcheck:Value8
...
fun:_ZN2v88internal12_GLOBAL__N_16InvokeEPNS0_7IsolateERKNS1_12InvokeParamsE
}
{ {
leak_nodejs_ImmutableAsciiSource_CreateFromLiteral leak_nodejs_ImmutableAsciiSource_CreateFromLiteral
Memcheck:Leak Memcheck:Leak
@@ -400,6 +438,13 @@
... ...
fun:_ZN4node17CreateEnvironmentEPN2v87IsolateEP9uv_loop_sNS0_5LocalINS0_7ContextEEEiPKPKciSB_ fun:_ZN4node17CreateEnvironmentEPN2v87IsolateEP9uv_loop_sNS0_5LocalINS0_7ContextEEEiPKPKciSB_
} }
{
leak_nodejs_CreateEnvironment_IsolateData
Memcheck:Leak
match-leak-kinds: possible
...
fun:_ZN4node17CreateEnvironmentEPNS_11IsolateDataEN2v85LocalINS2_7ContextEEERKSt6vectorISsSaISsEESA_NS_16EnvironmentFlags5FlagsENS_8ThreadIdESt10unique_ptrINS_21InspectorParentHandleESt14default_deleteISF_EE
}
{ {
leak_nodejs_Environment_Start leak_nodejs_Environment_Start
Memcheck:Leak Memcheck:Leak
@@ -538,6 +583,13 @@
... ...
fun:_ZN4node9inspector5Agent5StartERKSsSt10shared_ptrINS_12DebugOptionsEEb fun:_ZN4node9inspector5Agent5StartERKSsSt10shared_ptrINS_12DebugOptionsEEb
} }
{
leak_nodejs_debug_host_port
Memcheck:Leak
match-leak-kinds: possible
...
fun:_ZN4node9inspector5Agent5StartERKSsRKNS_12DebugOptionsESt10shared_ptrINS_8HostPortEEb
}
{ {
leak_nodejs_start leak_nodejs_start
Memcheck:Leak Memcheck:Leak
@@ -553,11 +605,14 @@
fun:_ZN4node20BackgroundTaskRunnerC1Ei fun:_ZN4node20BackgroundTaskRunnerC1Ei
} }
{ {
leak_nan_FunctionCallbackInfo leak_napi_module_register
Memcheck:Leak Memcheck:Leak
match-leak-kinds: definite match-leak-kinds: definite
... ...
fun:_ZN3Nan3impL23FunctionCallbackWrapperERKN2v820FunctionCallbackInfoINS1_5ValueEEE fun:napi_module_register
fun:call_init.part.0
fun:call_init
fun:_dl_init
} }
{ {
leak_v8_FunctionCallbackInfo leak_v8_FunctionCallbackInfo

View File

@@ -5,7 +5,10 @@ const sharp = require('../../');
const usingCache = detectLibc.family !== detectLibc.MUSL; const usingCache = detectLibc.family !== detectLibc.MUSL;
const usingSimd = !process.env.G_DEBUG; const usingSimd = !process.env.G_DEBUG;
const concurrency = detectLibc.family === detectLibc.MUSL ? 1 : undefined; const concurrency =
detectLibc.family === detectLibc.MUSL || process.arch === 'arm'
? 1
: undefined;
beforeEach(function () { beforeEach(function () {
sharp.cache(usingCache); sharp.cache(usingCache);

View File

@@ -141,7 +141,7 @@ describe('composite', () => {
it('zero offset', done => { it('zero offset', done => {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(400) .resize(80)
.composite([{ .composite([{
input: fixtures.inputPngWithTransparency16bit, input: fixtures.inputPngWithTransparency16bit,
top: 0, top: 0,
@@ -157,7 +157,7 @@ describe('composite', () => {
it('offset and gravity', done => { it('offset and gravity', done => {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(400) .resize(80)
.composite([{ .composite([{
input: fixtures.inputPngWithTransparency16bit, input: fixtures.inputPngWithTransparency16bit,
left: 10, left: 10,
@@ -174,7 +174,7 @@ describe('composite', () => {
it('offset, gravity and tile', done => { it('offset, gravity and tile', done => {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(400) .resize(80)
.composite([{ .composite([{
input: fixtures.inputPngWithTransparency16bit, input: fixtures.inputPngWithTransparency16bit,
left: 10, left: 10,
@@ -192,7 +192,7 @@ describe('composite', () => {
it('offset and tile', done => { it('offset and tile', done => {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(400) .resize(80)
.composite([{ .composite([{
input: fixtures.inputPngWithTransparency16bit, input: fixtures.inputPngWithTransparency16bit,
left: 10, left: 10,

View File

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

View File

@@ -19,11 +19,17 @@ describe('failOnError', function () {
}); });
}); });
it('handles truncated PNG', function (done) { it('handles truncated PNG, emits warnings', function (done) {
let isWarningEmitted = false;
sharp(fixtures.inputPngTruncated, { failOnError: false }) sharp(fixtures.inputPngTruncated, { failOnError: false })
.on('warning', function (warning) {
assert.ok(warning.includes('not enough data') || warning.includes('end of stream'));
isWarningEmitted = true;
})
.resize(320, 240) .resize(320, 240)
.toBuffer(function (err, data, info) { .toBuffer(function (err, data, info) {
if (err) throw err; if (err) throw err;
assert.strictEqual(true, isWarningEmitted);
assert.strictEqual('png', info.format); assert.strictEqual('png', info.format);
assert.strictEqual(320, info.width); assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height); assert.strictEqual(240, info.height);
@@ -48,17 +54,17 @@ describe('failOnError', function () {
it('returns errors to callback for truncated JPEG', function (done) { it('returns errors to callback for truncated JPEG', function (done) {
sharp(fixtures.inputJpgTruncated).toBuffer(function (err, data, info) { sharp(fixtures.inputJpgTruncated).toBuffer(function (err, data, info) {
assert.ok(err.message.includes('VipsJpeg: Premature end of JPEG file'), err); assert.ok(err.message.includes('VipsJpeg: Premature end of JPEG file'), err);
assert.strictEqual(data, null); assert.strictEqual(data, undefined);
assert.strictEqual(info, null); assert.strictEqual(info, undefined);
done(); done();
}); });
}); });
it('returns errors to callback for truncated PNG', function (done) { it('returns errors to callback for truncated PNG', function (done) {
sharp(fixtures.inputPngTruncated).toBuffer(function (err, data, info) { sharp(fixtures.inputPngTruncated).toBuffer(function (err, data, info) {
assert.ok(err.message.includes('vipspng: libpng read error'), err); assert.ok(err.message.includes('read error'), err);
assert.strictEqual(data, null); assert.strictEqual(data, undefined);
assert.strictEqual(info, null); assert.strictEqual(info, undefined);
done(); done();
}); });
}); });

View File

@@ -42,7 +42,7 @@ describe('GIF input', () => {
.then(({ data, info }) => { .then(({ data, info }) => {
assert.strictEqual(true, data.length > 0); assert.strictEqual(true, data.length > 0);
assert.strictEqual(data.length, info.size); assert.strictEqual(data.length, info.size);
assert.strictEqual('png', info.format); assert.strictEqual(sharp.format.magick.input.buffer ? 'gif' : 'png', info.format);
assert.strictEqual(80, info.width); assert.strictEqual(80, info.width);
assert.strictEqual(80, info.height); assert.strictEqual(80, info.height);
assert.strictEqual(4, info.channels); assert.strictEqual(4, info.channels);
@@ -55,10 +55,79 @@ describe('GIF input', () => {
.then(({ data, info }) => { .then(({ data, info }) => {
assert.strictEqual(true, data.length > 0); assert.strictEqual(true, data.length > 0);
assert.strictEqual(data.length, info.size); assert.strictEqual(data.length, info.size);
assert.strictEqual('png', info.format); assert.strictEqual(sharp.format.magick.input.buffer ? 'gif' : 'png', info.format);
assert.strictEqual(80, info.width); assert.strictEqual(80, info.width);
assert.strictEqual(2400, info.height); assert.strictEqual(2400, info.height);
assert.strictEqual(4, info.channels); assert.strictEqual(4, info.channels);
}) })
); );
if (!sharp.format.magick.output.buffer) {
it('GIF output should fail due to missing ImageMagick', () => {
assert.throws(
() => {
sharp().gif();
},
/The gif operation requires libvips to have been installed with support for ImageMagick/
);
});
}
it('invalid pageHeight throws', () => {
assert.throws(() => {
sharp().gif({ pageHeight: 0 });
});
});
it('invalid loop throws', () => {
assert.throws(() => {
sharp().gif({ loop: -1 });
});
assert.throws(() => {
sharp().gif({ loop: 65536 });
});
});
it('invalid delay throws', () => {
assert.throws(() => {
sharp().gif({ delay: [-1] });
});
assert.throws(() => {
sharp().gif({ delay: [65536] });
});
});
it('should work with streams when only animated is set', function (done) {
if (sharp.format.magick.output.buffer) {
fs.createReadStream(fixtures.inputGifAnimated)
.pipe(sharp({ animated: true }))
.gif()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('gif', info.format);
fixtures.assertSimilar(fixtures.inputGifAnimated, data, done);
});
} else {
done();
}
});
it('should work with streams when only pages is set', function (done) {
if (sharp.format.magick.output.buffer) {
fs.createReadStream(fixtures.inputGifAnimated)
.pipe(sharp({ pages: -1 }))
.gif()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('gif', info.format);
fixtures.assertSimilar(fixtures.inputGifAnimated, data, done);
});
} else {
done();
}
});
}); });

View File

@@ -214,37 +214,39 @@ describe('Input/output', function () {
}); });
}); });
it('Sequential read, force JPEG', function (done) { it('Invalid sequential read option throws', () => {
sharp(fixtures.inputJpg) assert.throws(() => {
.sequentialRead() sharp({ sequentialRead: 'fail' });
.resize(320, 240) }, /Expected boolean for sequentialRead but received fail of type string/);
.toFormat(sharp.format.jpeg)
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual(data.length, info.size);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
done();
});
}); });
it('Not sequential read, force JPEG', function (done) { it('Sequential read, force JPEG', () =>
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg, { sequentialRead: true })
.sequentialRead(false) .resize(320, 240)
.toFormat(sharp.format.jpeg)
.toBuffer({ resolveWithObject: true })
.then(({ data, info }) => {
assert.strictEqual(data.length > 0, true);
assert.strictEqual(data.length, info.size);
assert.strictEqual(info.format, 'jpeg');
assert.strictEqual(info.width, 320);
assert.strictEqual(info.height, 240);
})
);
it('Not sequential read, force JPEG', () =>
sharp(fixtures.inputJpg, { sequentialRead: false })
.resize(320, 240) .resize(320, 240)
.toFormat('jpeg') .toFormat('jpeg')
.toBuffer(function (err, data, info) { .toBuffer({ resolveWithObject: true })
if (err) throw err; .then(({ data, info }) => {
assert.strictEqual(true, data.length > 0); assert.strictEqual(data.length > 0, true);
assert.strictEqual(data.length, info.size); assert.strictEqual(data.length, info.size);
assert.strictEqual('jpeg', info.format); assert.strictEqual(info.format, 'jpeg');
assert.strictEqual(320, info.width); assert.strictEqual(info.width, 320);
assert.strictEqual(240, info.height); assert.strictEqual(info.height, 240);
done(); })
}); );
});
it('Support output to jpg format', function (done) { it('Support output to jpg format', function (done) {
sharp(fixtures.inputPng) sharp(fixtures.inputPng)
@@ -300,6 +302,7 @@ describe('Input/output', function () {
}); });
it('Fail when input is empty Buffer', function (done) { it('Fail when input is empty Buffer', function (done) {
if (sharp.format.magick.input.buffer) return this.skip(); // can be removed with libvips 8.10.1+
sharp(Buffer.alloc(0)).toBuffer().then(function () { sharp(Buffer.alloc(0)).toBuffer().then(function () {
assert(false); assert(false);
done(); done();
@@ -467,7 +470,7 @@ describe('Input/output', function () {
.toFile(fixtures.outputZoinks, function (err, info) { .toFile(fixtures.outputZoinks, function (err, info) {
if (err) throw err; if (err) throw err;
assert.strictEqual(true, info.size > 0); assert.strictEqual(true, info.size > 0);
assert.strictEqual('png', info.format); assert.strictEqual(sharp.format.magick.input.buffer ? 'gif' : 'png', info.format);
assert.strictEqual(320, info.width); assert.strictEqual(320, info.width);
assert.strictEqual(80, info.height); assert.strictEqual(80, info.height);
rimraf(fixtures.outputZoinks, done); rimraf(fixtures.outputZoinks, done);
@@ -559,105 +562,125 @@ describe('Input/output', function () {
}); });
}); });
describe('Limit pixel count of input image', function () { describe('Limit pixel count of input image', () => {
it('Invalid fails - negative', function (done) { it('Invalid fails - negative', () => {
let isValid = false; assert.throws(() => {
try { sharp({ limitInputPixels: -1 });
sharp().limitInputPixels(-1);
isValid = true;
} catch (e) {}
assert(!isValid);
done();
});
it('Invalid fails - float', function (done) {
let isValid = false;
try {
sharp().limitInputPixels(12.3);
isValid = true;
} catch (e) {}
assert(!isValid);
done();
});
it('Invalid fails - string', function (done) {
let isValid = false;
try {
sharp().limitInputPixels('fail');
isValid = true;
} catch (e) {}
assert(!isValid);
done();
});
it('Same size as input works', function (done) {
sharp(fixtures.inputJpg).metadata(function (err, metadata) {
if (err) throw err;
sharp(fixtures.inputJpg)
.limitInputPixels(metadata.width * metadata.height)
.toBuffer(function (err) {
assert.strictEqual(true, !err);
done();
});
}); });
}); });
it('Disabling limit works', function (done) { it('Invalid fails - float', () => {
sharp(fixtures.inputJpgLarge) assert.throws(() => {
.limitInputPixels(false) sharp({ limitInputPixels: 12.3 });
});
});
it('Invalid fails - string', () => {
assert.throws(() => {
sharp({ limitInputPixels: 'fail' });
});
});
it('Same size as input works', () =>
sharp(fixtures.inputJpg)
.metadata()
.then(({ width, height }) =>
sharp(fixtures.inputJpg, { limitInputPixels: width * height }).toBuffer()
)
);
it('Disabling limit works', () =>
sharp(fixtures.inputJpgLarge, { limitInputPixels: false })
.resize(2) .resize(2)
.toBuffer(function (err) { .toBuffer()
assert.strictEqual(true, !err); );
done();
});
});
it('Enabling default limit works and fails with a large image', function (done) { it('Enabling default limit works and fails with a large image', () =>
sharp(fixtures.inputJpgLarge) sharp(fixtures.inputJpgLarge, { limitInputPixels: true })
.limitInputPixels(true) .toBuffer()
.toBuffer(function (err) { .then(() => {
assert.strictEqual(true, !!err); assert.fail('Expected to fail');
done(); })
}); .catch(err => {
}); assert.strictEqual(err.message, 'Input image exceeds pixel limit');
})
);
it('Smaller than input fails', function (done) { it('Smaller than input fails', () =>
sharp(fixtures.inputJpg).metadata(function (err, metadata) { sharp(fixtures.inputJpg)
if (err) throw err; .metadata()
sharp(fixtures.inputJpg) .then(({ width, height }) =>
.limitInputPixels((metadata.width * metadata.height) - 1) sharp(fixtures.inputJpg, { limitInputPixels: width * height - 1 })
.toBuffer(function (err) { .toBuffer()
assert.strictEqual(true, !!err); .then(() => {
done(); assert.fail('Expected to fail');
}); })
}); .catch(err => {
}); assert.strictEqual(err.message, 'Input image exceeds pixel limit');
})
)
);
}); });
describe('Input options', function () { describe('Input options', function () {
it('Option-less', function () {
sharp();
});
it('Ignore unknown attribute', function () {
sharp({ unknown: true });
});
it('undefined with options fails', function () {
assert.throws(function () {
sharp(undefined, {});
}, /Unsupported input 'undefined' of type undefined when also providing options of type object/);
});
it('null with options fails', function () {
assert.throws(function () {
sharp(null, {});
}, /Unsupported input 'null' of type object when also providing options of type object/);
});
it('Non-Object options fails', function () { it('Non-Object options fails', function () {
assert.throws(function () { assert.throws(function () {
sharp(null, 'zoinks'); sharp('test', 'zoinks');
}); }, /Invalid input options zoinks/);
}); });
it('Invalid density: string', function () { it('Invalid density: string', function () {
assert.throws(function () { assert.throws(function () {
sharp(null, { density: 'zoinks' }); sharp({ density: 'zoinks' });
}); }, /Expected number between 1 and 100000 for density but received zoinks of type string/);
}); });
it('Ignore unknown attribute', function () { it('Setting animated property updates pages property', function () {
sharp(null, { unknown: true }); assert.strictEqual(sharp({ animated: false }).options.input.pages, 1);
assert.strictEqual(sharp({ animated: true }).options.input.pages, -1);
});
it('Invalid animated property throws', function () {
assert.throws(function () {
sharp({ animated: -1 });
}, /Expected boolean for animated but received -1 of type number/);
}); });
it('Invalid page property throws', function () { it('Invalid page property throws', function () {
assert.throws(function () { assert.throws(function () {
sharp(null, { page: -1 }); sharp({ page: -1 });
}, /Expected integer between 0 and 100000 for page but received -1 of type number/); }, /Expected integer between 0 and 100000 for page but received -1 of type number/);
}); });
it('Invalid pages property throws', function () { it('Invalid pages property throws', function () {
assert.throws(function () { assert.throws(function () {
sharp(null, { pages: '1' }); sharp({ pages: '1' });
}, /Expected integer between -1 and 100000 for pages but received 1 of type string/); }, /Expected integer between -1 and 100000 for pages but received 1 of type string/);
}); });
it('Valid level property', function () {
sharp({ level: 1 });
});
it('Invalid level property (string) throws', function () {
assert.throws(function () {
sharp({ level: '1' });
}, /Expected integer between 0 and 256 for level but received 1 of type string/);
});
it('Invalid level property (negative) throws', function () {
assert.throws(function () {
sharp({ level: -1 });
}, /Expected integer between 0 and 256 for level but received -1 of type number/);
});
}); });
describe('create new image', function () { describe('create new image', function () {
@@ -749,7 +772,7 @@ describe('Input/output', function () {
assert.strictEqual(472, info.height); assert.strictEqual(472, info.height);
assert.strictEqual(3, info.channels); assert.strictEqual(3, info.channels);
}); });
const badPipeline = sharp(null, { raw: { width: 840, height: 500, channels: 3 } }) const badPipeline = sharp({ raw: { width: 840, height: 500, channels: 3 } })
.toFormat('jpeg') .toFormat('jpeg')
.toBuffer(function (err, data, info) { .toBuffer(function (err, data, info) {
assert.strictEqual(err.message.indexOf('memory area too small') > 0, true); assert.strictEqual(err.message.indexOf('memory area too small') > 0, true);
@@ -757,7 +780,7 @@ describe('Input/output', function () {
const inPipeline = sharp() const inPipeline = sharp()
.resize(840, 472) .resize(840, 472)
.raw(); .raw();
const goodPipeline = sharp(null, { raw: { width: 840, height: 472, channels: 3 } }) const goodPipeline = sharp({ raw: { width: 840, height: 472, channels: 3 } })
.toFormat('jpeg') .toFormat('jpeg')
.toBuffer(function (err, data, info) { .toBuffer(function (err, data, info) {
if (err) throw err; if (err) throw err;

View File

@@ -19,7 +19,7 @@ describe('Image metadata', function () {
assert.strictEqual('srgb', metadata.space); assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels); assert.strictEqual(3, metadata.channels);
assert.strictEqual('uchar', metadata.depth); assert.strictEqual('uchar', metadata.depth);
assert.strictEqual('undefined', typeof metadata.density); assert.strictEqual(true, ['undefined', 'number'].includes(typeof metadata.density));
assert.strictEqual('4:2:0', metadata.chromaSubsampling); assert.strictEqual('4:2:0', metadata.chromaSubsampling);
assert.strictEqual(false, metadata.isProgressive); assert.strictEqual(false, metadata.isProgressive);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
@@ -192,6 +192,54 @@ describe('Image metadata', function () {
}); });
}); });
it('Animated WebP', () =>
sharp(fixtures.inputWebPAnimated)
.metadata()
.then(({
format, width, height, space, channels, depth,
isProgressive, pages, pageHeight, loop, delay,
hasProfile, hasAlpha
}) => {
assert.strictEqual(format, 'webp');
assert.strictEqual(width, 80);
assert.strictEqual(height, 80);
assert.strictEqual(space, 'srgb');
assert.strictEqual(channels, 4);
assert.strictEqual(depth, 'uchar');
assert.strictEqual(isProgressive, false);
assert.strictEqual(pages, 9);
assert.strictEqual(pageHeight, 80);
assert.strictEqual(loop, 0);
assert.deepStrictEqual(delay, [120, 120, 90, 120, 120, 90, 120, 90, 30]);
assert.strictEqual(hasProfile, false);
assert.strictEqual(hasAlpha, true);
})
);
it('Animated WebP with limited looping', () =>
sharp(fixtures.inputWebPAnimatedLoop3)
.metadata()
.then(({
format, width, height, space, channels, depth,
isProgressive, pages, pageHeight, loop, delay,
hasProfile, hasAlpha
}) => {
assert.strictEqual(format, 'webp');
assert.strictEqual(width, 370);
assert.strictEqual(height, 285);
assert.strictEqual(space, 'srgb');
assert.strictEqual(channels, 4);
assert.strictEqual(depth, 'uchar');
assert.strictEqual(isProgressive, false);
assert.strictEqual(pages, 10);
assert.strictEqual(pageHeight, 285);
assert.strictEqual(loop, 3);
assert.deepStrictEqual(delay, [...Array(9).fill(3000), 15000]);
assert.strictEqual(hasProfile, false);
assert.strictEqual(hasAlpha, true);
})
);
it('GIF via giflib', function (done) { it('GIF via giflib', function (done) {
sharp(fixtures.inputGif).metadata(function (err, metadata) { sharp(fixtures.inputGif).metadata(function (err, metadata) {
if (err) throw err; if (err) throw err;
@@ -232,6 +280,55 @@ describe('Image metadata', function () {
done(); done();
}); });
}); });
it('Animated GIF', () =>
sharp(fixtures.inputGifAnimated)
.metadata()
.then(({
format, width, height, space, channels, depth,
isProgressive, pages, pageHeight, loop, delay,
hasProfile, hasAlpha
}) => {
assert.strictEqual(format, 'gif');
assert.strictEqual(width, 80);
assert.strictEqual(height, 80);
assert.strictEqual(space, 'srgb');
assert.strictEqual(channels, 4);
assert.strictEqual(depth, 'uchar');
assert.strictEqual(isProgressive, false);
assert.strictEqual(pages, 30);
assert.strictEqual(pageHeight, 80);
assert.strictEqual(loop, 0);
assert.deepStrictEqual(delay, Array(30).fill(30));
assert.strictEqual(hasProfile, false);
assert.strictEqual(hasAlpha, true);
})
);
it('Animated GIF with limited looping', () =>
sharp(fixtures.inputGifAnimatedLoop3)
.metadata()
.then(({
format, width, height, space, channels, depth,
isProgressive, pages, pageHeight, loop, delay,
hasProfile, hasAlpha
}) => {
assert.strictEqual(format, 'gif');
assert.strictEqual(width, 370);
assert.strictEqual(height, 285);
assert.strictEqual(space, 'srgb');
assert.strictEqual(channels, 4);
assert.strictEqual(depth, 'uchar');
assert.strictEqual(isProgressive, false);
assert.strictEqual(pages, 10);
assert.strictEqual(pageHeight, 285);
assert.strictEqual(loop, 3);
assert.deepStrictEqual(delay, [...Array(9).fill(3000), 15000]);
assert.strictEqual(hasProfile, false);
assert.strictEqual(hasAlpha, true);
})
);
it('vips', () => it('vips', () =>
sharp(fixtures.inputV) sharp(fixtures.inputV)
.metadata() .metadata()
@@ -262,7 +359,7 @@ describe('Image metadata', function () {
assert.strictEqual('srgb', metadata.space); assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels); assert.strictEqual(3, metadata.channels);
assert.strictEqual('uchar', metadata.depth); assert.strictEqual('uchar', metadata.depth);
assert.strictEqual('undefined', typeof metadata.density); assert.strictEqual(true, ['undefined', 'number'].includes(typeof metadata.density));
assert.strictEqual('4:2:0', metadata.chromaSubsampling); assert.strictEqual('4:2:0', metadata.chromaSubsampling);
assert.strictEqual(false, metadata.isProgressive); assert.strictEqual(false, metadata.isProgressive);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
@@ -294,7 +391,7 @@ describe('Image metadata', function () {
assert.strictEqual('srgb', metadata.space); assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels); assert.strictEqual(3, metadata.channels);
assert.strictEqual('uchar', metadata.depth); assert.strictEqual('uchar', metadata.depth);
assert.strictEqual('undefined', typeof metadata.density); assert.strictEqual(true, ['undefined', 'number'].includes(typeof metadata.density));
assert.strictEqual('4:2:0', metadata.chromaSubsampling); assert.strictEqual('4:2:0', metadata.chromaSubsampling);
assert.strictEqual(false, metadata.isProgressive); assert.strictEqual(false, metadata.isProgressive);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
@@ -332,7 +429,7 @@ describe('Image metadata', function () {
assert.strictEqual('srgb', metadata.space); assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels); assert.strictEqual(3, metadata.channels);
assert.strictEqual('uchar', metadata.depth); assert.strictEqual('uchar', metadata.depth);
assert.strictEqual('undefined', typeof metadata.density); assert.strictEqual(true, ['undefined', 'number'].includes(typeof metadata.density));
assert.strictEqual('4:2:0', metadata.chromaSubsampling); assert.strictEqual('4:2:0', metadata.chromaSubsampling);
assert.strictEqual(false, metadata.isProgressive); assert.strictEqual(false, metadata.isProgressive);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
@@ -356,7 +453,7 @@ describe('Image metadata', function () {
assert.strictEqual('srgb', metadata.space); assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels); assert.strictEqual(3, metadata.channels);
assert.strictEqual('uchar', metadata.depth); assert.strictEqual('uchar', metadata.depth);
assert.strictEqual('undefined', typeof metadata.density); assert.strictEqual(true, ['undefined', 'number'].includes(typeof metadata.density));
assert.strictEqual('4:2:0', metadata.chromaSubsampling); assert.strictEqual('4:2:0', metadata.chromaSubsampling);
assert.strictEqual(false, metadata.isProgressive); assert.strictEqual(false, metadata.isProgressive);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
@@ -404,6 +501,42 @@ describe('Image metadata', function () {
}); });
}); });
it('Apply CMYK output ICC profile', function (done) {
const output = fixtures.path('output.icc-cmyk.jpg');
sharp(fixtures.inputJpg)
.withMetadata({ icc: 'cmyk' })
.toFile(output, function (err, info) {
if (err) throw err;
sharp(output).metadata(function (err, metadata) {
if (err) throw err;
assert.strictEqual(true, metadata.hasProfile);
assert.strictEqual('cmyk', metadata.space);
assert.strictEqual(4, metadata.channels);
// ICC
assert.strictEqual('object', typeof metadata.icc);
assert.strictEqual(true, metadata.icc instanceof Buffer);
const profile = icc.parse(metadata.icc);
assert.strictEqual('object', typeof profile);
assert.strictEqual('CMYK', profile.colorSpace);
assert.strictEqual('Relative', profile.intent);
assert.strictEqual('Printer', profile.deviceClass);
});
fixtures.assertSimilar(output, fixtures.path('expected/icc-cmyk.jpg'), { threshold: 0 }, done);
});
});
it('Apply custom output ICC profile', function (done) {
const output = fixtures.path('output.hilutite.jpg');
sharp(fixtures.inputJpg)
.withMetadata({ icc: fixtures.path('hilutite.icm') })
.toFile(output, function (err, info) {
if (err) throw err;
fixtures.assertMaxColourDistance(output, fixtures.path('expected/hilutite.jpg'), 0);
fixtures.assertMaxColourDistance(output, fixtures.inputJpg, 16.5);
done();
});
});
it('Include metadata in output, enabled via empty object', () => it('Include metadata in output, enabled via empty object', () =>
sharp(fixtures.inputJpgWithExif) sharp(fixtures.inputJpgWithExif)
.withMetadata({}) .withMetadata({})
@@ -578,5 +711,10 @@ describe('Image metadata', function () {
sharp().withMetadata({ orientation: 9 }); sharp().withMetadata({ orientation: 9 });
}); });
}); });
it('Non string icc', function () {
assert.throws(function () {
sharp().withMetadata({ icc: true });
});
});
}); });
}); });

View File

@@ -43,7 +43,10 @@ describe('Platform-detection', function () {
it('Defaults to ARMv6 for 32-bit', function () { it('Defaults to ARMv6 for 32-bit', function () {
process.env.npm_config_arch = 'arm'; process.env.npm_config_arch = 'arm';
const armVersion = process.config.variables.arm_version;
delete process.config.variables.arm_version;
assert.strictEqual('armv6', platform().split('-')[1]); assert.strictEqual('armv6', platform().split('-')[1]);
process.config.variables.arm_version = armVersion;
delete process.env.npm_config_arch; delete process.env.npm_config_arch;
}); });
@@ -52,4 +55,12 @@ describe('Platform-detection', function () {
assert.strictEqual('arm64v8', platform().split('-')[1]); assert.strictEqual('arm64v8', platform().split('-')[1]);
delete process.env.npm_config_arch; delete process.env.npm_config_arch;
}); });
it('Can ensure version ARMv7 if electron version is present', function () {
process.env.npm_config_arch = 'arm';
process.versions.electron = 'test';
assert.strictEqual('armv7', platform().split('-')[1]);
delete process.env.npm_config_arch;
delete process.versions.electron;
});
}); });

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