Compare commits

..

335 Commits

Author SHA1 Message Date
Lovell Fuller
05d76eeadf Release v0.22.1 2019-04-25 12:14:45 +01:00
Lovell Fuller
28a6c53da0 Prevent issue templates being published to npm 2019-04-25 12:14:17 +01:00
Lovell Fuller
6fcd2153c5 Docs: add support for Node 12 #1668 2019-04-25 12:01:14 +01:00
Lovell Fuller
7ae0512b9b Docs: clarify some output features require custom libvips 2019-04-25 11:39:30 +01:00
Nicolas Stepien
0890b59c32 Add Node 12 to Windows CI (#1669) 2019-04-25 11:24:35 +01:00
Lovell Fuller
df3ce450d9 Bump all dependencies 2019-04-25 09:51:59 +01:00
Lovell Fuller
bb0257b318 Remove not-yet-available Node 12 from Windows CI 2019-04-25 09:51:03 +01:00
Lovell Fuller
9c3597670d Add Node 12 to CI 2019-04-25 08:42:13 +01:00
Lovell Fuller
aa9b328778 Remove use of deprecated V8 API, swap v8::Handle for v8::Local #1668 2019-04-23 20:16:31 +01:00
tyankatsu
159e8dace2 docs: Fix path to CONTRIBUTING.md (#1666) 2019-04-20 19:19:14 +01:00
Lovell Fuller
3be4d5bb45 Ensure limitInputPixels check uses 64-bit unsigned type 2019-04-20 17:51:19 +01:00
Lovell Fuller
af7caa7b25 Docs: modernise contributing guide 2019-04-02 22:33:24 +01:00
Lovell Fuller
b4ede75522 Add issue templates (#1639) 2019-04-02 22:32:06 +01:00
Lovell Fuller
9d98114074 Move contributing info to GitHub-specific subdirectory,
which then allows for future possible issue/PR templates etc.
2019-04-02 21:03:57 +01:00
Lovell Fuller
4ac51899c3 Docs: minimum supported version of Node.js is 6 2019-04-02 20:41:23 +01:00
Lovell Fuller
90a0382317 Tests: use a concurrency of 1 on musl-based Linux
Should reduce a bit of pressure on the stack
2019-04-02 20:40:36 +01:00
Lovell Fuller
687795c801 Enhancement to and changelog entry for #1638
Remove bindings dependency as this isn't really... required
2019-04-02 20:39:06 +01:00
Sidhartha Chatterjee
2e0fbbb942 Add warning if sharp bindings aren't built correctly (#1638) 2019-04-02 17:00:55 +01:00
Lovell Fuller
0a3512d066 Unpin prebuild as 8.2.x is no longer breaking 2019-04-01 20:51:45 +01:00
Lovell Fuller
6032171f91 Docs: clarify use of integral pixel values for extract 2019-04-01 20:06:46 +01:00
Lovell Fuller
fc178de309 Changelog entry for #1601 2019-03-25 08:32:20 +00:00
Jakub Michálek
3f4398457f Change docs for composite to reflect how create works (#1623) 2019-03-25 07:45:46 +00:00
Jakub Michálek
b494b2e872 Add brightness, saturation and hue modulation #609 (#1601) 2019-03-25 07:44:07 +00:00
Lovell Fuller
18afcf5f90 Release v0.22.0 2019-03-18 23:26:39 +00:00
Lovell Fuller
87a422942d Pin prebuild due to breaking change in 8.2.0 2019-03-18 23:10:33 +00:00
Lovell Fuller
ac515121e5 Release v0.22.0 2019-03-18 21:31:46 +00:00
Lovell Fuller
2bfea0ad76 Docs: refresh usage examples 2019-03-18 21:29:17 +00:00
Lovell Fuller
83cdb558f6 Allow Stream-based input of raw pixel data #1579 2019-03-18 20:15:18 +00:00
Lovell Fuller
9ee377963e Improve error message if libvips tarball is corrupt 2019-03-17 23:07:58 +00:00
Lovell Fuller
9cc06c887b Add support for pages option for multi-page input #1566 2019-03-17 16:37:27 +00:00
Lovell Fuller
7457b50373 Remove unused shared library 2019-03-15 15:58:25 +00:00
Lovell Fuller
6387fb79b1 Small improvements to input and install docs, bump deps 2019-03-15 15:48:55 +00:00
Lovell Fuller
54e5514b9a Bump dependencies to latest 2019-03-10 18:14:43 +00:00
Lovell Fuller
1e4597c284 Changelog entry for #1595 (plus add GIF) 2019-03-10 17:26:26 +00:00
Lovell Fuller
7cafd4386c Add composite op, supporting multiple images and blend modes #728 2019-03-09 22:46:23 +00:00
Lovell Fuller
e3549ba28c Remove functions previously deprecated in v0.21.0
background, crop, embed, ignoreAspectRatio, max, min, withoutEnlargement
2019-03-01 23:43:35 +00:00
Lovell Fuller
d1bbe62e52 Rename armv8 as arm64v8 to match Node's process.arch 2019-03-01 23:43:35 +00:00
Lovell Fuller
36af74a09b Upgrade to libvips v8.7.4 2019-03-01 23:43:35 +00:00
Fabrizio Ruggeri
5afe02be60 Allow page input option to be set for PDF (#1595) 2019-03-01 23:29:34 +00:00
Jack Cross
2262959673 Docs: add missing comma to extend example (#1588) 2019-02-27 11:03:02 +00:00
Lovell Fuller
ba3f914445 Document support for animated WebP in metadata pages 2019-01-27 21:01:49 +00:00
Lovell Fuller
770be35c44 Tests: add a couple of extra leak suppressions for Node 2019-01-27 20:48:17 +00:00
Lovell Fuller
cc9f2b90fd Docs: use absolute URL for logo 2019-01-19 15:15:22 +00:00
Lovell Fuller
4aff57b071 Release v0.21.3 2019-01-19 14:25:37 +00:00
Maxime BACONNAIS
1df8d82fe0 Docs: overlay parameter of overlayWith is optional (#1547) 2019-01-19 14:19:41 +00:00
Lovell Fuller
98e90784f4 Docs: overlay parameter of overlayWith is optional 2019-01-19 14:11:54 +00:00
Lovell Fuller
87ea54cc66 Bump devDependencies 2019-01-19 14:06:16 +00:00
Lovell Fuller
d5e98bc8ad Split file-based input errors into missing vs invalid #1542 2019-01-19 11:59:36 +00:00
Lovell Fuller
fa69ff773a Input image decoding fail fast by default 2019-01-18 19:25:55 +00:00
Lovell Fuller
a183bb1cac Add valgrind memory leak suppressions 2019-01-18 12:08:28 +00:00
Lovell Fuller
cf62372cab Install: log the fallback to build from source
https://github.com/lovell/sharp-libvips/issues/18
2019-01-14 19:33:01 +00:00
Lovell Fuller
56fa9c95a1 Release v0.21.2 2019-01-13 10:26:47 +00:00
Lovell Fuller
32a34a8841 Tests: separate IO suite into per-format unit files 2019-01-13 10:11:32 +00:00
Lovell Fuller
98797445de Expose PNG output options requiring libimagequant #1484 2019-01-13 09:06:05 +00:00
Lovell Fuller
bd377438b6 Ignore colour profiles in LAB images as they are already LAB 2019-01-12 18:13:43 +00:00
Lovell Fuller
9dd6510de6 Expose underlying error message for invalid input #1505 2019-01-12 16:10:25 +00:00
Lovell Fuller
93ad9d4a4a Ensure all metadata removed from PNG unless withMetadata used 2019-01-09 21:17:53 +00:00
Lovell Fuller
4c01a099ea Add ensureAlpha op, adds alpha channel if missing #1153 2019-01-05 21:12:33 +00:00
Lovell Fuller
8e70579e47 Docs: use HTTPS links where available 2019-01-04 21:25:21 +00:00
Lovell Fuller
ee8bfa3980 Add 2019 to list of years of copyright 2019-01-04 16:05:26 +00:00
Lovell Fuller
c5dfa49cae Docs: add sharp logo to readme 2019-01-04 15:56:22 +00:00
Lovell Fuller
0822404129 Docs: expand logo viewBox to prevent clipping 2019-01-04 15:54:33 +00:00
Lovell Fuller
144f39cd45 Docs: add sharp logo, CC BY-SA 4.0 2019-01-04 15:12:53 +00:00
Lovell Fuller
87f191fd05 Node 11 now supported by nodejs/nan 2019-01-03 14:01:18 +00:00
Lovell Fuller
37ed436202 Version bump of devDependencies 2019-01-03 13:26:56 +00:00
Lovell Fuller
88e490356d Test: remove stray console.log 2019-01-03 12:35:54 +00:00
Lovell Fuller
7c631c0787 Ensure shortest resized edge is >= 1px #1003 2019-01-03 12:01:55 +00:00
Lovell Fuller
f5d3721fe0 Doc refresh for #1205 2019-01-02 19:04:49 +00:00
Lovell Fuller
cc633589d9 Expose pages metadata for multi-page input images #1205 2019-01-01 22:10:27 +00:00
Lovell Fuller
cc1d4c1a6d Expose palette-bit-depth metadata, requires upcoming libvips v8.8.0 2019-01-01 21:02:00 +00:00
Lovell Fuller
30ca424942 Apply correct forced output when chaining #1528 2019-01-01 18:40:09 +00:00
Pascal Temel
813831acf0 Docs: Update deprecated overlayWith example (#1526) 2018-12-30 20:44:36 +00:00
Lovell Fuller
a54fe9f77c Prevent mutatation of jpeg options #1516 2018-12-21 19:54:33 +00:00
Amila Welihinda
8c6da5548a Docs: change repo badge to SVG (#1498) 2018-12-10 11:03:03 +01:00
Lovell Fuller
a2aa7d69e7 Add error handler to download stream lovell/sharp-libvips#14 2018-12-08 13:56:05 +00:00
Lovell Fuller
34d5252242 Release v0.21.1 2018-12-07 19:23:54 +00:00
Lovell Fuller
f31e4d2869 Changelog, credit and doc refresh for #1483 2018-12-06 21:58:14 +00:00
Michael B. Klein
c695c40abc Expose libvips pyramid/tile options for TIFF output (#1483) 2018-12-06 22:33:46 +01:00
Lovell Fuller
fd1ca1dbb2 Ensure the tests for #1477 pass on OS X 2018-12-04 23:58:02 +01:00
Lovell Fuller
f25dbd5f61 Ensure the tests for #1477 pass on OS X 2018-12-04 23:45:08 +01:00
Keith
541e7104fd Expose libvips recombination matrix operation #1477 2018-12-04 23:06:34 +01:00
Lovell Fuller
94945cf6ac Changelog and credit for #1475 2018-12-04 07:49:41 +00:00
Lovell Fuller
db76e655f8 Ensure licensing checker works on Windows 2018-11-29 10:15:13 +00:00
Lovell Fuller
d43c7b581d Add licensing checker for production dependencies 2018-11-29 09:53:00 +00:00
Julian Aubourg
383b933e26 Build prototype with Object.assign to allow minification (#1475) 2018-11-26 19:40:06 +01:00
Lovell Fuller
d26ccf6294 Docs: update Alpine repository URLs 2018-11-21 22:42:58 +00:00
Lovell Fuller
6f9699f605 Ensure correct channel info for raw, greyscale output #1425 2018-11-19 20:00:30 +00:00
Quinn Pan
1e9093d781 Docs: correct code example in extend operation 2018-11-19 20:35:16 +01:00
Lovell Fuller
9dc6492e52 Docs: correct code example in extend operation 2018-11-19 19:34:16 +00:00
Lovell Fuller
d22f7cae6a Silence cast-function-type warnings from GCC 8+ 2018-11-19 18:47:35 +00:00
Lovell Fuller
473afaab45 Install: detect missing libvips on OpenBSD and SunOS
See https://github.com/lovell/sharp-libvips/issues/12
2018-11-19 18:46:05 +00:00
Lovell Fuller
dcd68303a4 Docs: add installation details for Lambda without Docker 2018-11-16 10:28:34 +00:00
Lovell Fuller
03394556b5 Update semistandard linter to latest 2018-11-11 18:05:40 +00:00
Lovell Fuller
1c4f6f75f3 Add Node 11 to CI, experimental only, no prebuild
Hide deprecation warnings - see nodejs/nan#811
2018-11-11 17:55:35 +00:00
Lovell Fuller
f00928dedb Doc refresh for #1438 #1439 2018-11-11 17:40:19 +00:00
Daiz
a48f8fbb61 Allow separate parameters for gamma encoding and decoding (#1439) 2018-11-11 10:15:38 +01:00
Daiz
1fa388370e Add support for the "mitchell" kernel for image reductions (#1438) 2018-10-28 15:11:27 +00:00
Christoph Tavan
95ef6b3f71 Docs: update Alpine libvips installation instructions (#1429)
With version 8.7.0 of the vips-dev Alpine package the documented installation instructions no longer work and result in:

ERROR: unsatisfiable constraints:
  pc:fftw3 (missing):
    required by: vips-dev-8.7.0-r0[pc:fftw3] vips-dev-8.7.0-r0[pc:fftw3] vips-dev-8.7.0-r0[pc:fftw3]

The fix was proposed in https://bugs.alpinelinux.org/issues/9561#note-2
2018-10-24 14:31:32 +01:00
Lovell Fuller
de11d36d00 Minor version bumps 2018-10-22 19:38:06 +01:00
Lovell Fuller
d77c2adabe Changelog and docs for #1422 2018-10-22 19:17:42 +01:00
SethWen
c89c055ae0 Install: add support for npm_config_sharp_dist_base_url (#1422) 2018-10-22 18:54:47 +01:00
Lovell Fuller
dac8117f32 Docs: ensure options are included for flatten op 2018-10-08 19:56:30 +01:00
Waylon Walker
937b091bab Docs: clear _libvips cache on Windows (#1403) 2018-10-07 16:49:05 +01:00
Lovell Fuller
019e6a1bfe Release v0.21.0 2018-10-04 13:03:30 +01:00
Lovell Fuller
1565e58fcf Update benchmark results ahead of v0.21.0
Remove lwip and images as they lack Node 10 support
2018-10-04 12:41:01 +01:00
Lovell Fuller
c22e2a17ef Update benchmark dependencies 2018-10-04 11:01:09 +01:00
Lovell Fuller
fd2a10ccea Threshold trim tests for non-turbo libjpeg 2018-10-02 20:33:26 +01:00
Lovell Fuller
0725378257 Add trimOffsetLeft, trimOffsetTop to trim response #914 2018-10-02 20:16:00 +01:00
Lovell Fuller
c431909f35 Refresh resize docs to ensure options are present 2018-10-02 18:45:08 +01:00
Lovell Fuller
db4df6f0b2 Add size to metadata response (Stream/Buffer only) #695 2018-10-02 18:05:08 +01:00
Lovell Fuller
17f942c802 Add chromaSubsampling and isProgressive to metadata #1186 2018-10-02 17:11:25 +01:00
Lovell Fuller
60438ebfe5 Ensure precision of trim threshold, update docs #914 2018-10-02 12:45:37 +01:00
Lovell Fuller
21fbe546b8 Switch from custom trim op to vips_find_trim #914 2018-10-02 11:24:32 +01:00
Lovell Fuller
11900945eb Bump dependency versions to latest 2018-10-02 11:23:49 +01:00
Lovell Fuller
ea5270221b Add new leak suppression for nodejs/libuv 2018-10-02 11:23:15 +01:00
Lovell Fuller
a64844689e Deprecate background, add op-specific prop to resize/extend/flatten #1392 2018-10-01 20:58:55 +01:00
Lovell Fuller
6007e13a22 Improve/increase installation error handling 2018-10-01 11:06:12 +01:00
Lovell Fuller
c3274e480b Deprecate crop, embed, ignoreAspectRatio, max, min, withoutEnlargement.
These become options of the resize operation instead. #1135
2018-09-30 20:16:27 +01:00
Miguel Aragón
3c54eeda5b Use more universal English to improve global understanding 2018-09-28 16:04:56 +01:00
Lovell Fuller
6236e4b97d Changelog entry and credit for #1385 2018-09-27 21:01:41 +01:00
freezy
796738da65 Add support for arbitrary rotation angle via vips_rotate (#1385) 2018-09-27 18:00:36 +01:00
freezy
37d385fafa Move background extraction into separate method (#1383) 2018-09-24 10:00:00 +01:00
Lovell Fuller
db2af42ee7 File extend, extract and trim ops under 'resize' #1135
Should make them easier to find in the docs
2018-09-22 14:52:08 +01:00
Lovell Fuller
24b42ef192 Tests: Move all setup to named file 2018-09-22 13:54:20 +01:00
Lovell Fuller
2ce166ab0a Update links to libvips, now in its own GitHub org 2018-09-21 20:33:01 +01:00
Lovell Fuller
71755b69e4 Remove duplicate libvips version/platform check 2018-09-21 20:20:40 +01:00
Lovell Fuller
1106aac2d8 Tests: tweak colour thresholds for (non-turbo) libjpeg compat 2018-09-21 19:51:47 +01:00
Lovell Fuller
93aac660a3 Tests: avoid shrink-on-load for (non-turbo) libjpeg compat 2018-09-21 19:34:52 +01:00
Lovell Fuller
0ce8ad7130 Enable CI on OS X 2018-09-20 10:41:24 +01:00
Lovell Fuller
deacd553bf Enable SIMD convolution by default #1213 2018-09-19 21:42:40 +01:00
Lovell Fuller
c8ff7e11a9 Upgrade to libvips v8.7.0
Drop Node 4 support
Add experimental musl prebuild for Node 8 and 10
2018-09-19 21:38:09 +01:00
Lovell Fuller
4cff62258c Improve smartcrop saliency testing/reporting 2018-09-05 22:49:31 +01:00
Lovell Fuller
0144358afb Release v0.20.8 2018-09-05 08:44:01 +01:00
Lovell Fuller
136097efe7 Downgrade nyc for continued Node 4 support 2018-09-04 17:07:10 +01:00
Lovell Fuller
374c6959d7 Changelog and credit for #1358 #1362 2018-09-04 16:39:24 +01:00
Axel Eirola
7d48a5ccf4 Allow floating point density input (#1362)
Metadata output will still remain integer
2018-09-01 08:58:30 +01:00
ajhool
bf3254cb16 Install: avoid race conditions when creating directories (#1358) 2018-08-29 09:20:26 +01:00
Lovell Fuller
5bed3a7d52 Release v0.20.7 2018-08-21 11:50:14 +01:00
Lovell Fuller
ece111280b Use copy+unlink if rename fails during install #1345 2018-08-20 15:14:31 +01:00
Lovell Fuller
a15a9b956b Release v0.20.6 2018-08-20 11:40:10 +01:00
Lovell Fuller
42860c2f83 Changelog, credit and doc refresh for #1342 2018-08-19 10:43:25 +01:00
Alun Davies
b5b95e5ae1 Expose depth option for tile-based output (#1342) 2018-08-18 15:09:53 +01:00
Lovell Fuller
d705cffdd6 Ensure extractChannel works with 16-bit images #1330 2018-08-12 20:22:39 +01:00
Rodrigo Alviani
23a4bc103e Docs: correct quality option in overlayWith example (#1325) 2018-08-08 08:42:18 +01:00
Lovell Fuller
c14434f9e7 Add removeAlpha op, removes alpha channel if any #1248 2018-08-07 20:32:11 +01:00
Lovell Fuller
25bd2cea3e Add experimental entropy field to stats response 2018-08-06 15:41:27 +01:00
Lovell Fuller
532de4ecab Cache libvips binaries to reduce re-install time #1301 2018-08-05 10:31:41 +01:00
Lovell Fuller
bfdd27eeef Doc refresh and dependency bumps 2018-08-05 09:42:09 +01:00
Lovell Fuller
bd9f238ab4 Improve install time error messages for FreeBSD #1310 2018-08-04 22:27:32 +01:00
Lovell Fuller
75556bb57c Ensure vendor platform mismatch throws error #1303 2018-08-04 21:34:11 +01:00
thegareth
2de062a34a Docs: update the "make a transparent image" example (#1316)
Alpha for colour is between 0-1, not 0-255.
2018-08-02 09:42:25 +01:00
Lovell Fuller
4589b15dea Changelog and credit for #1285 #1290 2018-07-10 16:12:16 +01:00
Sylvain Dumont
8b75ce6786 Allow full WebP alphaQuality range of 0-100 (#1290) 2018-07-10 15:58:17 +01:00
Espen Hovlandsdal
7bbc5176a1 Expose mozjpeg quant_table flag (#1285) 2018-07-10 15:56:05 +01:00
Lovell Fuller
5cb35485f1 Release v0.20.5 2018-06-27 08:44:31 +01:00
Lovell Fuller
80189ed689 Add changelog and credit for #1265 2018-06-27 07:54:41 +01:00
Muhammad Faheem Akhtar
3d7e8ef432 Docs: update new Buffer() to Buffer.from() (#1273) 2018-06-26 08:19:27 +01:00
Lovell Fuller
1999c7103c Docs: add CSS to improve left-hand nav nesting 2018-06-25 19:58:50 +01:00
Thomas Vantuycom
9c20ae383e Remove top of file table of contents in documentation (#1270) 2018-06-24 21:45:51 +01:00
Tom Lokhorst
76c41eaf05 Expose libjpeg optimize_coding flag (#1265) 2018-06-21 18:12:10 +01:00
Lovell Fuller
873aa6700f Release v0.20.4 2018-06-20 08:26:28 +01:00
Lovell Fuller
0d9590a9a0 Documentation refresh 2018-06-20 08:14:03 +01:00
Lovell Fuller
94607b585a Ensure extractChannel sets bw colourspace interp #1257 2018-06-19 22:47:52 +01:00
Lovell Fuller
da0b0348a2 Prevent rounding err with shrink-on-load and 90/270 rot #1241 2018-06-19 21:19:34 +01:00
Lovell Fuller
09263455b5 Release v0.20.3 2018-05-29 08:38:42 +01:00
Lovell Fuller
ddc23493d4 Add warning about possible concurrent tile race #1151 2018-05-25 19:53:23 +01:00
Lovell Fuller
54a71fc142 Fix tint op by ensuring LAB and allowing negative values #1235
Add test cases for more tint colours and input interpretations
2018-05-23 20:51:47 +01:00
Lovell Fuller
b1a9bf10a2 Pre-built binaries provided for even-numbered major versions 2018-05-21 11:44:34 +01:00
Lovell Fuller
97cfbe1b63 Bump dependency versions 2018-05-19 15:19:23 +01:00
Lovell Fuller
0ee8c63551 Replace use of libvips API deprecated since v8.6.1
See jcupitt/libvips@b085908
2018-05-19 15:03:50 +01:00
Lovell Fuller
0ac5a9ad82 Promote nw.js details to main installation docs - see 6e51f2d 2018-05-19 14:53:22 +01:00
Michael
6e51f2d608 rebuild doc for NW.js (#1229)
refers to https://github.com/lovell/sharp/issues/1223
2018-05-15 07:28:09 +01:00
Lovell Fuller
f23a8dc9dc Release v0.20.2 2018-04-28 18:29:32 +01:00
Lovell Fuller
d09fe6178c Add support for Node 10, drop support for Node 9 2018-04-28 18:12:10 +01:00
Lovell Fuller
ae2cfcc4f3 Version bumps, hold simple-get at v2 for Node 4 support 2018-04-28 18:11:20 +01:00
Lovell Fuller
853cc65e32 Changelog entry for #1208 2018-04-28 14:06:45 +01:00
Nathan Graves
5d140d949f Add support for Group4 (CCITTFAX4) compression to TIFF output (#1208) 2018-04-26 19:49:08 +01:00
Lovell Fuller
6d2da2b3ba Switch unzip test dependency for Node 10 support 2018-04-25 20:37:10 +01:00
Lovell Fuller
165e337e44 Changelog entry and doc refresh for #1204 2018-04-25 10:19:33 +01:00
Nathan Graves
b154cd0418 Add support for page selection with multi-page TIFF input (#1204) 2018-04-24 22:57:27 +01:00
Lovell Fuller
8ef1532691 Dependency version bumps 2018-04-17 20:46:22 +01:00
Lovell Fuller
771e44f2a7 Add error highlighting lack of Windows x86 node.exe support 2018-04-17 20:37:19 +01:00
Lovell Fuller
8933f1128d Changelog entry and credit for #1165 2018-04-11 20:30:16 +01:00
Rik Heywood
dbac4b9a63 Add tint operation to set image chroma 2018-04-11 20:05:48 +01:00
Lovell Fuller
bdac5b5807 Add code usage examples for output-related functions 2018-04-06 19:52:00 +01:00
Lovell Fuller
b0961b5213 Bump dependencies 2018-04-06 19:49:17 +01:00
Thomas Parisot
0d7c3fc4d8 Ignore global libvips during install via SHARP_IGNORE_GLOBAL_LIBVIPS (#1165) 2018-03-22 18:48:30 +00:00
Lovell Fuller
8dac256096 Release v0.20.1 2018-03-17 14:04:35 +00:00
Lovell Fuller
8320da39c3 Changelog entry and doc refresh for #1161 2018-03-17 11:12:43 +00:00
Andrea Bianco
875937e3d8 Expose libvips' median filter operation (#1161) 2018-03-17 10:52:44 +00:00
Lovell Fuller
f880adbaac Bump dependencies ahead of v0.20.1 2018-03-16 20:46:08 +00:00
Lovell Fuller
48c5f86adb Improve install when global libvips below min version #1148 2018-03-13 20:37:42 +00:00
Lovell Fuller
f60f7dab12 Prevent error when cumulative rounding below target #1154 2018-03-13 19:42:10 +00:00
Lovell Fuller
8f690236ed Release v0.20.0 2018-03-04 21:54:28 +00:00
Lovell Fuller
430a4297aa Add support for prebuilt sharp binaries on common platforms 2018-03-04 14:33:44 +00:00
Oskar Thornblad
69e3ab02c4 Mention EXIF orientation in metadata's jsdocs (#1142) 2018-03-04 14:19:53 +00:00
Lovell Fuller
b87a20b881 Release v0.19.1 2018-02-24 10:02:12 +00:00
Lovell Fuller
0e6f2d16ab Bump dependencies 2018-02-23 19:27:34 +00:00
Lovell Fuller
498b061819 Upgrade nan dependency, enables async hooks 2018-02-23 19:18:31 +00:00
Lovell Fuller
5ab6f599fb Prevent crop when at or below target dimensions #1134 2018-02-23 10:31:11 +00:00
Lovell Fuller
8b80626035 Changelog, credit and doc refresh for #1121 2018-02-18 19:14:37 +00:00
Andrea Bianco
f86ae79fdb Expose angle option in tile feature (#1121) 2018-02-18 20:00:08 +01:00
Lovell Fuller
1a4e68096f Changelog and credit for #1024, bump dependencies 2018-02-04 13:30:47 +00:00
Marcel
d599d1f29e Expose linear transform feature of libvips (#1024) 2018-02-04 10:36:04 +00:00
Lovell Fuller
73edfb3d2c Update list of files that npm can ignore 2018-01-13 10:28:31 +00:00
Lovell Fuller
ebae68d579 Doc refresh for #1077 2018-01-13 10:27:43 +00:00
Tony Mobily
573836e2b8 Add mention of max() to preserve ratio in withoutEnlargement (#1077) 2018-01-13 09:59:59 +00:00
Lovell Fuller
da5deb8177 Release v0.19.0 2018-01-11 22:31:51 +00:00
Lovell Fuller
8fca89e876 Update documentation hostname 2018-01-10 23:35:50 +00:00
Lovell Fuller
d5295a2d0c Doc refresh for #1079 2018-01-10 22:24:46 +00:00
Oleg Aleynik
c4df115948 Expose IPTC and XMP metadata when available (#1079) 2018-01-10 22:12:11 +00:00
Lovell Fuller
8afcb16d8e Update performance benchmark results for v0.19.0 2018-01-07 14:30:25 +00:00
Lovell Fuller
3764d63244 Add valgrind suppressions for Node 8 2018-01-06 13:39:26 +00:00
Lovell Fuller
c82914df30 Doc refresh and devDependencies version bumps 2018-01-06 00:07:35 +00:00
Lovell Fuller
84ba921f5b Unpin tar dep as race condition is fixed - see 053e727 2018-01-05 10:51:24 +00:00
Lovell Fuller
8e74668e3c Update linter plus related spacing fixes 2018-01-05 10:18:08 +00:00
Lovell Fuller
707c05b5f5 Update libvips dependency minimum to v8.6.1 2018-01-05 09:57:33 +00:00
Lovell Fuller
9bd2cec199 Update benchmark test dependencies 2018-01-02 20:19:11 +00:00
Lovell Fuller
358b8fe8b6 Refresh Lambda install docs to recommend use of Docker 2017-12-22 19:31:03 +00:00
Lovell Fuller
7115ae5375 Prevent toFile ext taking precedence over toFormat #1037 2017-12-20 20:33:08 +00:00
Lovell Fuller
13997ca653 Run unit test files sequentially through valgrind 2017-12-18 19:57:26 +00:00
Lovell Fuller
9fa04a0b93 Attach event listener to clone only for Stream #995 2017-12-12 21:53:27 +00:00
Lovell Fuller
0894145284 Changelog and doc refresh for #1038 2017-12-12 19:42:07 +00:00
Kenric D'Souza
927b77700d Add gravity support to embed feature (#1038) 2017-12-12 19:29:16 +00:00
Lovell Fuller
1d7a0ea99e Changelog and doc refresh for #915 2017-12-09 13:14:23 +00:00
Rahul Nanwani
d6aee8e5ba Add pixel-derived image statistics via vips_stats (#915) 2017-12-09 11:17:48 +00:00
Lovell Fuller
dfaa39fa5d Enable OS X CI 2017-12-09 10:47:02 +00:00
Lovell Fuller
8fe3b59efe Update test fixtures for e9b7231ac0 2017-12-08 17:54:39 +00:00
Lovell Fuller
47237b1f15 Add expected min libvips version to error messages 2017-12-07 23:16:33 +00:00
Lovell Fuller
053e727bd5 Pin tar dependency as latest version has a race condition with Node 4 2017-12-01 19:00:28 +00:00
Lovell Fuller
2ce9e81d80 Wait for close event before decompressing tarball
Should reduce the chance of a race condition
2017-11-30 20:29:18 +00:00
Lovell Fuller
50848ee462 Add unit test coverage report for C++ 2017-11-30 20:16:30 +00:00
Lovell Fuller
ef06d560cd Windows CI: retry npm install to workaround race condition 2017-11-30 20:12:14 +00:00
Lovell Fuller
2abf9f96c7 Force TIFF predictor=none to match fixture in squash tests 2017-11-27 19:55:20 +00:00
Lovell Fuller
a91686b4cd Temporarily revert to previous npm for Node 9 support 2017-11-27 19:38:44 +00:00
Lovell Fuller
1be424cd47 Add missing file from commit fc233ed 2017-11-27 19:35:08 +00:00
Lovell Fuller
929ea10f76 Re-enable test for 1200 DPI SVG rasterisation to PNG 2017-11-27 19:31:31 +00:00
Lovell Fuller
11d1f39e3d Add Node 9 to CI test environments 2017-11-27 19:15:09 +00:00
Lovell Fuller
fc233ed4ff Update upstream libvips source to v8.6.0-beta1 2017-11-27 19:14:30 +00:00
Lovell Fuller
efd2e893cf Ensure expected fixtures match libvips v8.6.0 behaviour 2017-11-27 19:13:39 +00:00
Lovell Fuller
2a18b9a8f7 Remove centreSampling option, update some expected test fixtures
See https://github.com/jcupitt/libvips/issues/705
2017-11-26 13:49:56 +00:00
Lovell Fuller
6aa214181b Version bump dependencies 2017-11-25 11:25:27 +00:00
Lovell Fuller
7067beda99 Docs refresh for cropOffsetLeft/cropOffsetTop 2017-11-25 11:24:53 +00:00
Lovell Fuller
e0f0baf164 Silence a couple of MSVC type conversion warnings 2017-11-17 20:10:10 +00:00
Lovell Fuller
1fec132dee Add tilecache before smartcrop to avoid over-computation 2017-11-17 19:53:50 +00:00
Lovell Fuller
ba521fccb4 Replace caw with its tunnel-agent dependency 2017-11-07 19:38:10 +00:00
Lovell Fuller
965a97105e Default TIFF predictor to horizontal to match libvips' behaviour 2017-10-19 19:40:07 +01:00
Lovell Fuller
3c88c84998 Doc refresh, changelog and dead code removal for #977 2017-10-19 11:05:43 +01:00
Jarda Kotěšovec
d0f66c3734 Switch to libvips' resize, make fastShrinkOnLoad optional (#977) 2017-10-19 11:05:43 +01:00
Lovell Fuller
ebc2a741f6 Ensure accessMethod is applied during shrink-on-load 2017-10-19 11:05:43 +01:00
Lovell Fuller
d46512af1c Doc refresh and changelog entry for #976 2017-10-19 11:05:43 +01:00
Matthew McEachen
b4d72bd544 Add failOnError option to fail-fast on bad input image data (#976) 2017-10-19 11:05:43 +01:00
Lovell Fuller
382d476271 Temporarily allow CI failures on OS X 2017-10-19 11:05:43 +01:00
Lovell Fuller
80c240b54a Add compile time check for libvips 8.6.0+ 2017-10-19 11:05:43 +01:00
Lovell Fuller
21f99d88ab Docs: possible to install libvips via pkg on FreeBSD 2017-10-19 11:05:43 +01:00
Lovell Fuller
d5873a00d5 Docs refresh 2017-10-19 11:05:43 +01:00
Lovell Fuller
99076edc89 Default PNG output to adaptiveFiltering=false, compressionLevel=9
PNG is mostly used for logos and line art, so these defaults will
produce the smallest files for most output most of the time.
2017-10-19 11:05:43 +01:00
Lovell Fuller
7b6c80327e Verify platform of vendor binaries at install and run time 2017-10-19 11:05:43 +01:00
Lovell Fuller
57946ed672 Upgrade to libvips v8.6.0
Expose offset coordinates of strategy-based crop
Switch to Github releases for prebuilt libvips
Move packaging scripts to lovell/sharp-libvips repo
2017-10-19 11:05:43 +01:00
Aditya Bharti
aad16ac50d Improved jsdocs for withoutEnlargement and sequentialRead #972 (#992) 2017-10-15 09:28:08 +01:00
Brooks Becton
bbe897e607 Added Coding tools section and added types link (#975) 2017-10-04 13:02:11 +01:00
Mika Fischer
eca0e66e23 Remove use of dynamic require to facilitate bundling (#960) 2017-09-25 21:00:40 +01:00
Lovell Fuller
3511723914 Release v0.18.4 2017-09-18 18:26:12 +01:00
Lovell Fuller
6a1c7b7588 Ensure input Buffer really is marked as Persistent #950 2017-09-16 20:17:32 +01:00
Lovell Fuller
18fd6ef119 Release v0.18.3 2017-09-13 10:51:08 +01:00
Matt Parrish
0004f5d2ff Migrate from got to simple-get (#945)
The simple-get module provides support for basic auth and
is already used by the future prebuild dependency.
2017-09-12 07:38:17 +01:00
Mario Viens
5f29d1ba9c Clarifying cache documentation (#919) 2017-08-19 10:43:36 +01:00
Jasper Blues
791fd35c35 Update README.md (#913) 2017-08-16 12:09:02 +01:00
Kleis Auke Wolthuizen
e0d622d347 Skip shrink-on-load when trimming (#887) (#888) 2017-07-28 21:02:30 +01:00
Lovell Fuller
6b34e8a804 Docs: FreeBSD gcc v4/v5 needs _GLIBCXX_USE_C99 for C++11 #873 2017-07-25 14:54:44 +01:00
Lovell Fuller
eb8773fe3e Use detect-libc instead of ldd output parsing 2017-07-17 12:11:13 +01:00
Lovell Fuller
b40e3fa1f1 Docs: Alpine vips-dev package requires fftw-dev also 2017-07-15 14:47:25 +01:00
Lovell Fuller
d25d761b55 Update benchmark deps, allow node-images to fail 2017-07-14 13:18:13 +01:00
Lovell Fuller
d6051dd714 Release v0.18.2 2017-07-01 09:52:47 +01:00
Lovell Fuller
53ff061efa Document SHARP_DIST_BASE_URL #841 2017-07-01 09:44:02 +01:00
Lovell Fuller
72b0efd393 Remove possible switch case fall through
Luckily there were no side effects in this... case
2017-06-25 19:09:20 +01:00
Lovell Fuller
df97ef23d9 Document/changelog for Solus Linux support #857 2017-06-25 17:42:03 +01:00
Ekrem Karaca
f6373971bd Add support for Solus OS (#857) 2017-06-24 13:48:03 +01:00
Lovell Fuller
ec617f2489 Document minimum Node dependency of v4.5.0 #847 2017-06-20 22:05:05 +01:00
Lovell Fuller
502ae78579 Allow binary download URL override via SHARP_DIST_BASE_URL #841 2017-06-20 21:40:46 +01:00
Lovell Fuller
49297d6afb Ensure flip and flop operations work with auto-rotate #837 2017-06-19 23:42:26 +01:00
Lovell Fuller
29354badd8 Docs: add Alpine Linux install details 2017-06-19 08:13:49 +01:00
Lovell Fuller
3c4de796c8 Dependency bumps and next point release 2017-06-04 19:55:45 +01:00
Lovell Fuller
c7f4488e77 Docs and changelog entry for #828 2017-06-04 19:51:22 +01:00
Yves Bos
d8765f955d Allow xres and yres to be set for TIFF output (#828) 2017-06-03 10:52:09 +01:00
Lovell Fuller
9f20037dad Release v0.18.1 2017-05-30 21:02:35 +01:00
Lovell Fuller
2ebb090df2 Update Travis CI to Ubuntu 16.04
Rollback Appveyor CI from Node 8 to 7
2017-05-30 20:50:32 +01:00
Lovell Fuller
110fff3ab9 Replace Node 7 with Node 8 in CI environment 2017-05-30 20:32:15 +01:00
Lovell Fuller
f42a1ceab7 Recalculate residual after adjusting shrink #831 2017-05-30 20:22:15 +01:00
Lovell Fuller
9e39a7fa95 Correct shrink calc, regression introduced in e398b47 #831 2017-05-30 17:16:41 +01:00
Lovell Fuller
c879df3b31 Release v0.18.0 2017-05-30 08:09:59 +01:00
Lovell Fuller
361ed98353 Remove previously-deprecated output format 'option' functions 2017-05-23 21:57:05 +01:00
Lovell Fuller
d45f8ef2d3 Document the cache-free nature of metadata extraction #796 2017-05-23 21:24:29 +01:00
Lovell Fuller
d6a63d11d7 Docs refresh 2017-05-22 21:49:37 +01:00
jingsam
4c6804eadc Add toFormat 'jpg' alias for 'jpeg' (#814) 2017-05-22 12:59:43 +01:00
Nicolas Coden
99810c0311 Add support for any rotation angle (#791)
Allow to provide any positive or negative multiple of 90 to `.rotate(...)`.
Negative angles and angles above 360 are converted to valid 0/90/180/270
rotations (0 rotations are still ignored).

Changes:
- [Node] Add `useExifOrientation` internal variable to know if the Exif
  orientation must be used instead of the provided angle. This allows to save a
  negative angle in the `angle` option, because the `-1` special case is not
  needed.

- [Node] Change check for planed-rotation in extract, to prepare a
  rotation before extraction: check with both `angle` and `useExifOrientation`
  options.
  I think this check contains a bit too much logics on rotation options. Maybe
  we could move this condition to a dedicated function.

- [C++] Separate `CalculateRotationAndFlip` into two generic functions:
  - `CalculateExifRotationAndFlip`: Calculate the angle of rotation and
    need-to-flip for the given Exif orientation.
  - `CalculateAngleRotation`: Calculate the rotation for the given angle.

  One or the other function is used to calculate the rotation, depending on
  wether the Exif orientation tag or the provided angle must be used.

- Add unit tests for `-3690`, `-450`, `-90`, `90`, `450`, `3690` and `-3780`,
  `-540`, `0`, `180`, `540`, `3780` rotations
- Add `320x240` fixture image for tests.

Unrelated changes (squashed):
- Add ncoden to the list of contributors
2017-05-22 11:08:33 +01:00
gmaliar
d15fb1ab1b Docs: add link to TailorBrands-maintained libvips Dockerfiles (#813) 2017-05-21 20:37:57 +01:00
Lovell Fuller
0a6d8b37ad Ensure double to int cast introduced in 4d1a169 is static 2017-05-21 19:05:56 +01:00
Lovell Fuller
f78ffdb9ce Upgrade to libvips v8.5.5 2017-05-21 18:31:06 +01:00
Lovell Fuller
b7b6fdbdf5 Update perf test contenders, add node-images 2017-05-13 20:08:53 +01:00
Lovell Fuller
e398b471e1 Prevent aliasing by using dynamic values for shrink(-on-load) 2017-05-13 18:46:39 +01:00
Lovell Fuller
48f69f3d88 Upgrade libpng to v1.6.29 2017-05-13 18:20:54 +01:00
Lovell Fuller
95850d75f6 Include pixel format depth when reading metadata 2017-05-07 09:29:38 +01:00
Lovell Fuller
c41d755441 Ctor single arg: allow plain object, reject null/undefined
Thank you @kub1x
2017-05-06 19:03:14 +01:00
Lovell Fuller
39a21787b7 Remove 'require' test as bufferutil now ships prebuilt 2017-05-06 15:49:50 +01:00
Lovell Fuller
36078f9903 Switch to the libvips crop strategy implementations 2017-05-06 14:46:28 +01:00
Lovell Fuller
2f534dc01c Base maximum output dimensions on limitation of format 2017-05-04 23:20:37 +01:00
Lovell Fuller
c8e59f08ec Add support for Buffer and Stream-based TIFF output 2017-05-04 16:40:49 +01:00
Lovell Fuller
19dd6a997f Doc refresh, thank you @cspotcode 2017-05-01 09:34:10 +01:00
Lovell Fuller
4d1a1694cd Improve perf/accuracy of nearest neighbour integral upsample 2017-04-30 20:54:48 +01:00
Lovell Fuller
52bea15ad7 Upgrade libvips dependency to v8.5.4, plus other bumps 2017-04-26 23:04:08 +01:00
Lovell Fuller
6592361c5a Ensure ARM64 pre-built binaries use correct C++11 ABI 2017-04-26 21:41:03 +01:00
Lovell Fuller
f3f83494f5 Credit contributor YvesBos 2017-04-26 21:40:30 +01:00
Lovell Fuller
1169afbe90 Avoid (un)premultiplication for overlay image without alpha channel
Add 'premultiplied' boolean attribute to output info, helps test
2017-04-26 21:37:43 +01:00
Lovell Fuller
301bfbd271 Expose libvips warnings via NODE_DEBUG env var 2017-04-26 21:37:43 +01:00
Lovell Fuller
46aec7eabc Upgrade libvips dependency and packaging to v8.5.1 2017-04-26 21:37:43 +01:00
YvesBos
4cd3b66761 Add support for squashing TIFF output to 1-bit (#783) 2017-04-26 17:47:29 +01:00
Jakub Podlaha
567e3dd258 Add gentoo support to glibc detection (#760) 2017-04-06 15:17:30 +01:00
Lovell Fuller
fcf853712c Release v0.17.3 2017-04-01 10:20:44 +01:00
Lovell Fuller
088d36b47b Add support for TIFF float predictor 2017-04-01 10:08:47 +01:00
Lovell Fuller
27fb864ac4 Update dev deps, deconstify all the functions, API doc refresh 2017-03-31 21:42:23 +01:00
Lovell Fuller
4001c4a48a Add changelog and credit for #738 2017-03-31 21:17:19 +01:00
Sagiv Frankel
f64c18ef15 Docs: Add download info to Heroku section (#748) 2017-03-30 12:55:33 +01:00
Kristo Jorgenson
f8e72f443d Expose TIFF compression and predictor options (#738) 2017-03-29 12:12:04 +01:00
Lovell Fuller
5e015cc3ca Docs: use NODE_MODULES_CACHE=false for Heroku+yarn #722 2017-03-27 20:31:50 +01:00
Andreas Lind
9707f8c5d2 Add support for passing the crop strategy as a string (#735) 2017-03-16 14:27:09 +00:00
Lovell Fuller
6b1d698448 Add credit and changelog for #732 2017-03-16 07:37:05 +00:00
Alice Monday
72f69dda30 Add support for the "nearest" kernel for image reductions (#732) 2017-03-14 10:29:23 +00:00
Lovell Fuller
8b5d8a0577 Switch from seq to random access for normalise and 'smart' crop 2017-03-11 19:56:55 +00:00
Lovell Fuller
1aa053ce6f Create blank image (width, height, channels, background) #470 2017-03-11 11:46:01 +00:00
Lovell Fuller
701b1c4216 Document overlayWith image density parameter #729 2017-03-10 22:59:46 +00:00
Lovell Fuller
f1c4cef781 Version bump of dev dependencies 2017-03-04 22:28:01 +00:00
Lovell Fuller
6fe5b307b1 Allow toBuffer to resolve Promise with info+data #143 2017-03-04 22:15:31 +00:00
Lovell Fuller
679ce08998 Small doc updates 2017-03-04 18:37:23 +00:00
Lovell Fuller
eeb923eb5b Update docs domain name 2017-03-04 18:36:18 +00:00
244 changed files with 11456 additions and 5031 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
src/libvips/* linguist-vendored

View File

@@ -12,9 +12,12 @@ New bugs are assigned a `triage` label whilst under investigation.
## Submit a new feature request ## Submit a new feature request
If a [similar request](https://github.com/lovell/sharp/labels/enhancement) exists, it's probably fastest to add a comment to it about your requirement. If a [similar request](https://github.com/lovell/sharp/labels/enhancement) exists,
it's probably fastest to add a comment to it about your requirement.
Implementation is usually straightforward if _libvips_ [already supports](http://www.vips.ecs.soton.ac.uk/supported/current/doc/html/libvips/ch03.html) the feature you need. Implementation is usually straightforward if libvips
[already supports](https://libvips.github.io/libvips/API/current/func-list.html)
the feature you need.
## Submit a Pull Request to fix a bug ## Submit a Pull Request to fix a bug
@@ -41,19 +44,18 @@ Any change that modifies the existing public API should be added to the relevant
| Release | WIP branch | | Release | WIP branch |
| ------: | :--------- | | ------: | :--------- |
| v0.17.0 | quill | | v0.23.0 | vision |
| v0.18.0 | ridge | | v0.24.0 | wit |
| v0.19.0 | suit |
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
The API tries to be as fluent as possible. Image processing concepts follow the naming conventions from _libvips_ and, to a lesser extent, _ImageMagick_. The API tries to be as fluent as possible.
Image processing concepts follow the naming conventions from libvips and, to a lesser extent, ImageMagick.
Most methods have optional parameters and assume sensible defaults. Methods with mandatory parameters often have names like `doSomethingWith(X)`. Most methods have optional parameters and assume sensible defaults.
Please ensure backwards compatibility where possible.
Please ensure backwards compatibility where possible. Methods to modify previously default behaviour often have names like `withoutOptionY()` or `withExtraZ()`.
Feel free to create a [new issue](https://github.com/lovell/sharp/issues/new) to gather feedback on a potential API change. Feel free to create a [new issue](https://github.com/lovell/sharp/issues/new) to gather feedback on a potential API change.
@@ -61,7 +63,7 @@ Feel free to create a [new issue](https://github.com/lovell/sharp/issues/new) to
A method to be removed should be deprecated in the next major version then removed in the following major version. A method to be removed should be deprecated in the next major version then removed in the following major version.
By way of example, the [bilinearInterpolation method](https://github.com/lovell/sharp/blob/v0.6.0/index.js#L155) present in v0.5.0 was deprecated in v0.6.0 and removed in v0.7.0. By way of example, the `background()` method present in v0.20.0 was deprecated in v0.21.0 and removed in v0.22.0.
## Documentation ## Documentation
@@ -72,12 +74,7 @@ These can be converted to Markdown by running:
npm run docs npm run docs
``` ```
The `types.d.ts` TypeScript declaration can be generated by running: Please include documentation updates in any Pull Request that modifies the public API.
```sh
npm run types
```
Please include documentation and TypeScript declaration updates in any Pull Request that modifies the public API.
## Run the tests ## Run the tests
@@ -95,15 +92,6 @@ Requires [Valgrind](http://valgrind.org/).
npm run test-leak npm run test-leak
``` ```
### Packaging tests
Tests the installation on a number of Linux-based operating systems.
Requires docker.
```sh
npm run test-packaging
```
## Finally ## Finally
Please feel free to ask any questions via a Please feel free to ask any questions via a

View File

@@ -0,0 +1,18 @@
---
name: Feature request
about: Suggest an idea
title: ''
labels: enhancement
assignees: ''
---
What are you trying to achieve?
Have you searched for similar feature requests?
What would you expect the API to look like?
What alternatives have you considered?
Is there a sample image that helps explain?

14
.github/ISSUE_TEMPLATE/installation.md vendored Normal file
View File

@@ -0,0 +1,14 @@
---
name: Installation
about: For help if something went wrong installing sharp
title: ''
labels: ''
assignees: ''
---
What is the output of running `npm install --verbose sharp`?
What is the output of running `npx envinfo --binaries --languages --system --utilities`?
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?

18
.github/ISSUE_TEMPLATE/possible-bug.md vendored Normal file
View File

@@ -0,0 +1,18 @@
---
name: Possible bug
about: Please provide steps to reproduce
title: ''
labels: ''
assignees: ''
---
What is the output of running `npx envinfo --binaries --languages --system --utilities`?
What are the steps to reproduce?
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 sample image that helps explain the problem?

16
.github/ISSUE_TEMPLATE/question.md vendored Normal file
View File

@@ -0,0 +1,16 @@
---
name: Question
about: For help with an existing feature
title: ''
labels: question
assignees: ''
---
What are you trying to achieve?
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 sample image that helps explain the question?

7
.gitignore vendored
View File

@@ -1,6 +1,6 @@
build build
node_modules node_modules
coverage /coverage
test/bench/node_modules test/bench/node_modules
test/fixtures/output* test/fixtures/output*
test/leak/libvips.supp test/leak/libvips.supp
@@ -9,8 +9,7 @@ test/saliency/Image*
test/saliency/[Uu]serData* test/saliency/[Uu]serData*
!test/saliency/userData.js !test/saliency/userData.js
vendor vendor
packaging/libvips* .gitattributes
packaging/*.log
!packaging/build
.DS_Store .DS_Store
.nyc_output .nyc_output
package-lock.json

View File

@@ -2,13 +2,14 @@ build
node_modules node_modules
coverage coverage
.editorconfig .editorconfig
.gitattributes
.gitignore .gitignore
test test
.travis.yml .travis.yml
appveyor.yml appveyor.yml
circle.yml
mkdocs.yml mkdocs.yml
docs/css/
vendor vendor
packaging .prebuildrc
preinstall.sh
.nyc_output .nyc_output
.github/

4
.prebuildrc Normal file
View File

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

View File

@@ -1,21 +1,100 @@
language: node_js matrix:
node_js: include:
- "4" - name: "Linux (glibc) - Node 6"
- "6" os: linux
- "7" dist: trusty
os: sudo: false
- linux language: node_js
- osx node_js: "6"
sudo: false - name: "Linux (glibc) - Node 8"
addons: os: linux
apt: dist: trusty
sources: sudo: false
- ubuntu-toolchain-r-test language: node_js
packages: node_js: "8"
- g++-4.8 - name: "Linux (glibc) - Node 10"
osx_image: xcode8 os: linux
before_install: dist: trusty
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export CXX=g++-4.8; fi sudo: false
after_success: language: node_js
- npm install coveralls node_js: "10"
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js - name: "Linux (glibc) - Node 12"
os: linux
dist: trusty
sudo: false
language: node_js
node_js: "12"
after_success:
- npm install coveralls
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
- name: "Linux (glibc) - Node 11"
os: linux
dist: trusty
sudo: false
language: node_js
node_js: "11"
- 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 11"
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:11-alpine
- sudo docker exec sharp apk add build-base git python2 --update-cache
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
- name: "Linux (musl) - Node 12"
os: linux
dist: trusty
sudo: true
language: minimal
before_install:
- sudo docker run -dit --name sharp --env CI --env PREBUILD_TOKEN --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:12.0-alpine
- sudo docker exec sharp apk add build-base git python2 --update-cache
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
- name: "OS X - Node 6"
os: osx
osx_image: xcode9.2
language: node_js
node_js: "6"
- 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 11"
os: osx
osx_image: xcode9.2
language: node_js
node_js: "11"
- name: "OS X - Node 12"
os: osx
osx_image: xcode9.2
language: node_js
node_js: "12"

View File

@@ -1,5 +1,7 @@
# 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">
```sh ```sh
npm install sharp npm install sharp
``` ```
@@ -17,39 +19,64 @@ 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.
OS X, Windows (x64), Linux (x64, ARM) systems do not require Most modern 64-bit OS X, Windows and Linux systems running
the installation of any external runtime dependencies. Node versions 6, 8, 10, 11 and 12
do not require any additional install or runtime dependencies.
## Examples ## Examples
```javascript ```javascript
import sharp from 'sharp'; const sharp = require('sharp');
``` ```
### Callback
```javascript ```javascript
sharp(inputBuffer) sharp(inputBuffer)
.resize(320, 240) .resize(320, 240)
.toFile('output.webp', (err, info) => ... ); .toFile('output.webp', (err, info) => { ... });
``` ```
### Promise
```javascript ```javascript
sharp('input.jpg') sharp('input.jpg')
.rotate() .rotate()
.resize(200) .resize(200)
.toBuffer() .toBuffer()
.then( data => ... ) .then( data => { ... })
.catch( err => ... ); .catch( err => { ... });
``` ```
### Async/await
```javascript ```javascript
const roundedCorners = new Buffer( const semiTransparentRedPng = await sharp({
create: {
width: 48,
height: 48,
channels: 4,
background: { r: 255, g: 0, b: 0, alpha: 0.5 }
}
})
.png()
.toBuffer();
```
### Stream
```javascript
const roundedCorners = Buffer.from(
'<svg><rect x="0" y="0" width="200" height="200" rx="50" ry="50"/></svg>' '<svg><rect x="0" y="0" width="200" height="200" rx="50" ry="50"/></svg>'
); );
const roundedCornerResizer = const roundedCornerResizer =
sharp() sharp()
.resize(200, 200) .resize(200, 200)
.overlayWith(roundedCorners, { cutout: true }) .composite([{
input: roundedCorners,
blend: 'dest-in'
}])
.png(); .png();
readableStream readableStream
@@ -57,29 +84,29 @@ readableStream
.pipe(writableStream); .pipe(writableStream);
``` ```
[![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.png?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)
### Documentation ### Documentation
Visit [sharp.dimens.io](http://sharp.dimens.io/) for complete Visit [sharp.pixelplumbing.com](https://sharp.pixelplumbing.com/) for complete
[installation instructions](http://sharp.dimens.io/page/install), [installation instructions](https://sharp.pixelplumbing.com/page/install),
[API documentation](http://sharp.dimens.io/page/api), [API documentation](https://sharp.pixelplumbing.com/page/api),
[benchmark tests](http://sharp.dimens.io/page/performance) and [benchmark tests](https://sharp.pixelplumbing.com/page/performance) and
[changelog](http://sharp.dimens.io/page/changelog). [changelog](https://sharp.pixelplumbing.com/page/changelog).
### Contributing ### Contributing
A [guide for contributors](https://github.com/lovell/sharp/blob/master/CONTRIBUTING.md) A [guide for contributors](https://github.com/lovell/sharp/blob/master/.github/CONTRIBUTING.md)
covers reporting bugs, requesting features and submitting code changes. covers reporting bugs, requesting features and submitting code changes.
### Licence ### Licensing
Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors. Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0.html) [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,

View File

@@ -4,12 +4,14 @@ build: off
platform: x64 platform: x64
environment: environment:
matrix: matrix:
- nodejs_version: "4"
- nodejs_version: "6" - nodejs_version: "6"
- nodejs_version: "7" - nodejs_version: "8"
- nodejs_version: "10"
- nodejs_version: "11"
- nodejs_version: "12"
install: install:
- ps: Install-Product node $env:nodejs_version x64 - ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) x64
- npm install -g npm@latest - npm install -g npm@6
- npm install - npm install
test_script: test_script:
- npm test - npm test

View File

@@ -5,9 +5,6 @@
['OS == "win"', { ['OS == "win"', {
# Build libvips C++ binding for Windows due to MSVC std library ABI changes # Build libvips C++ binding for Windows due to MSVC std library ABI changes
'type': 'shared_library', 'type': 'shared_library',
'variables': {
'download_vips': '<!(node -e "require(\'./binding\').download_vips()")'
},
'defines': [ 'defines': [
'VIPS_CPLUSPLUS_EXPORTS', 'VIPS_CPLUSPLUS_EXPORTS',
'_ALLOW_KEYWORD_MACROS' '_ALLOW_KEYWORD_MACROS'
@@ -49,43 +46,22 @@
'dependencies': [ 'dependencies': [
'libvips-cpp' 'libvips-cpp'
], ],
# Nested variables "pattern" borrowed from http://src.chromium.org/viewvc/chrome/trunk/src/build/common.gypi
'variables': { 'variables': {
'variables': {
'variables': {
'conditions': [
['OS != "win"', {
# Build the PKG_CONFIG_PATH environment variable with all possible combinations
'pkg_config_path': '<!(which brew >/dev/null 2>&1 && eval $(brew --env) && echo $PKG_CONFIG_LIBDIR || true):$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig'
}, {
'pkg_config_path': ''
}]
],
},
'conditions': [
['OS != "win"', {
# Which version, if any, of libvips is available globally via pkg-config?
'global_vips_version': '<!(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --modversion vips-cpp 2>/dev/null || true)'
}, {
'global_vips_version': ''
}]
],
'pkg_config_path%': '<(pkg_config_path)'
},
'pkg_config_path%': '<(pkg_config_path)',
'runtime_link%': 'shared', 'runtime_link%': 'shared',
'conditions': [ 'conditions': [
['OS != "win"', { ['OS != "win"', {
# Does the globally available version of libvips, if any, meet the minimum version requirement? 'pkg_config_path': '<!(node -e "console.log(require(\'./lib/libvips\').pkgConfigPath())")',
'use_global_vips': '<!(GLOBAL_VIPS_VERSION="<(global_vips_version)" node -e "require(\'./binding\').use_global_vips()")' 'use_global_libvips': '<!(node -e "console.log(Boolean(require(\'./lib/libvips\').useGlobalLibvips()).toString())")'
}, { }, {
'use_global_vips': '' 'pkg_config_path': '',
'use_global_libvips': ''
}] }]
] ]
}, },
'sources': [ 'sources': [
'src/common.cc', 'src/common.cc',
'src/metadata.cc', 'src/metadata.cc',
'src/stats.cc',
'src/operations.cc', 'src/operations.cc',
'src/pipeline.cc', 'src/pipeline.cc',
'src/sharp.cc', 'src/sharp.cc',
@@ -95,7 +71,7 @@
'<!(node -e "require(\'nan\')")' '<!(node -e "require(\'nan\')")'
], ],
'conditions': [ 'conditions': [
['use_global_vips == "true"', { ['use_global_libvips == "true"', {
# Use pkg-config for include and lib # Use pkg-config for include and lib
'include_dirs': ['<!@(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --cflags-only-I vips-cpp vips glib-2.0 | sed s\/-I//g)'], 'include_dirs': ['<!@(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --cflags-only-I vips-cpp vips glib-2.0 | sed s\/-I//g)'],
'conditions': [ 'conditions': [
@@ -112,7 +88,7 @@
}] }]
] ]
}, { }, {
# Attempt to download pre-built libvips and install locally within node_modules # Use pre-built libvips stored locally within node_modules
'include_dirs': [ 'include_dirs': [
'vendor/include', 'vendor/include',
'vendor/include/glib-2.0', 'vendor/include/glib-2.0',
@@ -130,9 +106,6 @@
] ]
}], }],
['OS == "mac"', { ['OS == "mac"', {
'variables': {
'download_vips': '<!(node -e "require(\'./binding\').download_vips()")'
},
'libraries': [ 'libraries': [
'../vendor/lib/libvips-cpp.42.dylib', '../vendor/lib/libvips-cpp.42.dylib',
'../vendor/lib/libvips.42.dylib', '../vendor/lib/libvips.42.dylib',
@@ -143,9 +116,6 @@
] ]
}], }],
['OS == "linux"', { ['OS == "linux"', {
'variables': {
'download_vips': '<!(LDD_VERSION="<!(ldd --version 2>&1 || true)" node -e "require(\'./binding\').download_vips()")'
},
'defines': [ 'defines': [
'_GLIBCXX_USE_CXX11_ABI=0' '_GLIBCXX_USE_CXX11_ABI=0'
], ],
@@ -158,9 +128,11 @@
'../vendor/lib/libcairo.so', '../vendor/lib/libcairo.so',
'../vendor/lib/libcroco-0.6.so', '../vendor/lib/libcroco-0.6.so',
'../vendor/lib/libexif.so', '../vendor/lib/libexif.so',
'../vendor/lib/libexpat.so',
'../vendor/lib/libffi.so', '../vendor/lib/libffi.so',
'../vendor/lib/libfontconfig.so', '../vendor/lib/libfontconfig.so',
'../vendor/lib/libfreetype.so', '../vendor/lib/libfreetype.so',
'../vendor/lib/libfribidi.so',
'../vendor/lib/libgdk_pixbuf-2.0.so', '../vendor/lib/libgdk_pixbuf-2.0.so',
'../vendor/lib/libgif.so', '../vendor/lib/libgif.so',
'../vendor/lib/libgio-2.0.so', '../vendor/lib/libgio-2.0.so',
@@ -179,6 +151,8 @@
'../vendor/lib/librsvg-2.so', '../vendor/lib/librsvg-2.so',
'../vendor/lib/libtiff.so', '../vendor/lib/libtiff.so',
'../vendor/lib/libwebp.so', '../vendor/lib/libwebp.so',
'../vendor/lib/libwebpdemux.so',
'../vendor/lib/libwebpmux.so',
'../vendor/lib/libxml2.so', '../vendor/lib/libxml2.so',
'../vendor/lib/libz.so', '../vendor/lib/libz.so',
# Ensure runtime linking is relative to sharp.node # Ensure runtime linking is relative to sharp.node
@@ -208,71 +182,25 @@
}, },
'configurations': { 'configurations': {
'Release': { 'Release': {
'cflags_cc': [
'-Wno-cast-function-type',
'-Wno-deprecated-declarations'
],
'xcode_settings': {
'OTHER_CPLUSPLUSFLAGS': [
'-Wno-deprecated-declarations'
]
},
'msvs_settings': { 'msvs_settings': {
'VCCLCompilerTool': { 'VCCLCompilerTool': {
'ExceptionHandling': 1 'ExceptionHandling': 1
} }
}, },
'msvs_disabled_warnings': [ 'msvs_disabled_warnings': [
4275 4275,
4996
] ]
} }
}, },
}, {
'target_name': 'win_copy_dlls',
'type': 'none',
'dependencies': [
'sharp'
],
'conditions': [
['OS == "win"', {
# Windows lacks support for rpath
'copies': [{
'destination': 'build/Release',
'files': [
'vendor/lib/GNU.Gettext.dll',
'vendor/lib/libasprintf-0.dll',
'vendor/lib/libcairo-2.dll',
'vendor/lib/libcairo-gobject-2.dll',
'vendor/lib/libcairo-script-interpreter-2.dll',
'vendor/lib/libcharset-1.dll',
'vendor/lib/libcroco-0.6-3.dll',
'vendor/lib/libexif-12.dll',
'vendor/lib/libexpat-1.dll',
'vendor/lib/libffi-6.dll',
'vendor/lib/libfftw3-3.dll',
'vendor/lib/libfontconfig-1.dll',
'vendor/lib/libfreetype-6.dll',
'vendor/lib/libgcc_s_seh-1.dll',
'vendor/lib/libgdk_pixbuf-2.0-0.dll',
'vendor/lib/libgif-7.dll',
'vendor/lib/libgio-2.0-0.dll',
'vendor/lib/libglib-2.0-0.dll',
'vendor/lib/libgmodule-2.0-0.dll',
'vendor/lib/libgobject-2.0-0.dll',
'vendor/lib/libgsf-1-114.dll',
'vendor/lib/libgthread-2.0-0.dll',
'vendor/lib/libiconv-2.dll',
'vendor/lib/libintl-8.dll',
'vendor/lib/libjpeg-62.dll',
'vendor/lib/liblcms2-2.dll',
'vendor/lib/libpango-1.0-0.dll',
'vendor/lib/libpangocairo-1.0-0.dll',
'vendor/lib/libpangowin32-1.0-0.dll',
'vendor/lib/libpixman-1-0.dll',
'vendor/lib/libpng16-16.dll',
'vendor/lib/libquadmath-0.dll',
'vendor/lib/librsvg-2-2.dll',
'vendor/lib/libssp-0.dll',
'vendor/lib/libstdc++-6.dll',
'vendor/lib/libtiff-5.dll',
'vendor/lib/libvips-42.dll',
'vendor/lib/libwebp-6.dll',
'vendor/lib/libxml2-2.dll',
'vendor/lib/zlib1.dll'
]
}]
}]
]
}] }]
} }

View File

@@ -1,129 +0,0 @@
'use strict';
const fs = require('fs');
const os = require('os');
const path = require('path');
const zlib = require('zlib');
const caw = require('caw');
const got = require('got');
const semver = require('semver');
const tar = require('tar');
const distBaseUrl = 'https://dl.bintray.com/lovell/sharp/';
// Use NPM-provided environment variable where available, falling back to require-based method for Electron
const minimumLibvipsVersion = process.env.npm_package_config_libvips || require('./package.json').config.libvips;
const platform = process.env.npm_config_platform || process.platform;
const arch = process.env.npm_config_arch || process.arch;
// -- Helpers
// Does this file exist?
const isFile = function (file) {
try {
return fs.statSync(file).isFile();
} catch (err) {}
};
const unpack = function (tarPath, done) {
const extractor = tar.Extract({ path: path.join(__dirname, 'vendor') });
if (done) {
extractor.on('end', done);
}
extractor.on('error', error);
fs.createReadStream(tarPath)
.on('error', error)
.pipe(zlib.Unzip())
.pipe(extractor);
};
const platformId = function () {
const platformId = [platform];
if (arch === 'arm' || arch === 'armhf' || arch === 'arch64') {
const armVersion = (arch === 'arch64') ? '8' : process.env.npm_config_armv || process.config.variables.arm_version || '6';
platformId.push('armv' + armVersion);
} else {
platformId.push(arch);
}
return platformId.join('-');
};
// Error
const error = function (msg) {
if (msg instanceof Error) {
msg = msg.message;
}
process.stderr.write('ERROR: ' + msg + '\n');
process.exit(1);
};
// -- Binary downloaders
module.exports.download_vips = function () {
// Has vips been installed locally?
const vipsHeaderPath = path.join(__dirname, 'vendor', 'include', 'vips', 'vips.h');
if (!isFile(vipsHeaderPath)) {
// Ensure Intel 64-bit or ARM
if (arch === 'ia32') {
error('Intel Architecture 32-bit systems require manual installation - please see http://sharp.dimens.io/en/stable/install/');
}
// Ensure glibc >= 2.15
const lddVersion = process.env.LDD_VERSION;
if (lddVersion) {
if (/(glibc|gnu libc)/i.test(lddVersion)) {
const glibcVersion = lddVersion ? lddVersion.split(/\n/)[0].split(' ').slice(-1)[0].trim() : '';
if (glibcVersion && semver.lt(glibcVersion + '.0', '2.13.0')) {
error('glibc version ' + glibcVersion + ' requires manual installation - please see http://sharp.dimens.io/en/stable/install/');
}
} else {
error(lddVersion.split(/\n/)[0] + ' requires manual installation - please see http://sharp.dimens.io/en/stable/install/');
}
}
// Arch/platform-specific .tar.gz
const tarFilename = ['libvips', minimumLibvipsVersion, platformId()].join('-') + '.tar.gz';
const tarPathLocal = path.join(__dirname, 'packaging', tarFilename);
if (isFile(tarPathLocal)) {
unpack(tarPathLocal);
} else {
// Download to per-process temporary file
const tarPathTemp = path.join(os.tmpdir(), process.pid + '-' + tarFilename);
const tmpFile = fs.createWriteStream(tarPathTemp).on('finish', function () {
unpack(tarPathTemp, function () {
// Attempt to remove temporary file
try {
fs.unlinkSync(tarPathTemp);
} catch (err) {}
});
});
const gotOpt = {
agent: caw(null, {
protocol: 'https'
})
};
const url = distBaseUrl + tarFilename;
got.stream(url, gotOpt).on('response', function (response) {
if (response.statusCode !== 200) {
error(url + ' status code ' + response.statusCode);
}
}).on('error', function (err) {
error('Download of ' + url + ' failed: ' + err.message);
}).pipe(tmpFile);
}
}
};
module.exports.use_global_vips = function () {
const globalVipsVersion = process.env.GLOBAL_VIPS_VERSION;
if (globalVipsVersion) {
const useGlobalVips = semver.gte(
globalVipsVersion,
minimumLibvipsVersion
);
process.stdout.write(useGlobalVips ? 'true' : 'false');
} else {
process.stdout.write('false');
}
};

View File

@@ -1,8 +0,0 @@
machine:
node:
version: v4.6.1
services:
- docker
test:
override:
- ./packaging/test-linux-x64.sh

View File

@@ -1,20 +1,46 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. --> <!-- Generated by documentation.js. Update this documentation by updating the source code. -->
### Table of Contents ## removeAlpha
- [extractChannel](#extractchannel) Remove alpha channel, if any. This is a no-op if the image does not have an alpha channel.
- [joinChannel](#joinchannel)
- [bandbool](#bandbool) ### Examples
```javascript
sharp('rgba.png')
.removeAlpha()
.toFile('rgb.png', function(err, info) {
// rgb.png is a 3 channel image without an alpha channel
});
```
Returns **Sharp**
## ensureAlpha
Ensure alpha channel, if missing. The added alpha channel will be fully opaque. This is a no-op if the image already has an alpha channel.
### Examples
```javascript
sharp('rgb.jpg')
.ensureAlpha()
.toFile('rgba.png', function(err, info) {
// rgba.png is a 4 channel image with a fully opaque alpha channel
});
```
Returns **Sharp**
## extractChannel ## extractChannel
Extract a single channel from a multi-channel image. Extract a single channel from a multi-channel image.
**Parameters** ### Parameters
- `channel` **([Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) \| [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String))** 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 band number to extract, or `red`, `green` or `blue` as alternative to `0`, `1` or `2` respectively.
**Examples** ### Examples
```javascript ```javascript
sharp(input) sharp(input)
@@ -25,7 +51,7 @@ sharp(input)
}); });
``` ```
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid channel - Throws **[Error][3]** Invalid channel
Returns **Sharp** Returns **Sharp**
@@ -42,13 +68,13 @@ Channel ordering follows vips convention:
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.
**Parameters** ### Parameters
- `images` **([Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)&lt;([String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Buffer](https://nodejs.org/api/buffer.html))> | [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Buffer](https://nodejs.org/api/buffer.html))** 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** image options, see `sharp()` constructor. - `options` **[Object][6]** image options, see `sharp()` constructor.
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters - Throws **[Error][3]** Invalid parameters
Returns **Sharp** Returns **Sharp**
@@ -56,11 +82,11 @@ Returns **Sharp**
Perform a bitwise boolean operation on all input image channels (bands) to produce a single channel output image. Perform a bitwise boolean operation on all input image channels (bands) to produce a single channel output image.
**Parameters** ### Parameters
- `boolOp` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** 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
```javascript ```javascript
sharp('3-channel-rgb-input.png') sharp('3-channel-rgb-input.png')
@@ -72,6 +98,18 @@ sharp('3-channel-rgb-input.png')
}); });
``` ```
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters - Throws **[Error][3]** Invalid parameters
Returns **Sharp** Returns **Sharp**
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[5]: https://nodejs.org/api/buffer.html
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object

View File

@@ -1,28 +1,16 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. --> <!-- Generated by documentation.js. Update this documentation by updating the source code. -->
### Table of Contents ## tint
- [background](#background) Tint the image using the provided chroma while preserving the image luminance.
- [greyscale](#greyscale) An alpha channel may be present and will be unchanged by the operation.
- [grayscale](#grayscale)
- [toColourspace](#tocolourspace)
- [toColorspace](#tocolorspace)
## background ### Parameters
Set the background for the `embed`, `flatten` and `extend` operations. - `rgb` **([String][1] \| [Object][2])** parsed by the [color][3] module to extract chroma values.
The default background is `{r: 0, g: 0, b: 0, alpha: 1}`, black without transparency.
Delegates to the _color_ module, which can throw an Error
but is liberal in what it accepts, clipping values to sensible min/max.
The alpha value is a float between `0` (transparent) and `1` (opaque).
**Parameters**
- `rgba` **([String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object))** parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameter - Throws **[Error][4]** Invalid parameter
Returns **Sharp** Returns **Sharp**
@@ -35,9 +23,9 @@ This may be overridden by other sharp operations such as `toColourspace('b-w')`,
which will produce an output image containing one color channel. which will produce an output image containing one color channel.
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.
**Parameters** ### Parameters
- `greyscale` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `true`) - `greyscale` **[Boolean][5]** (optional, default `true`)
Returns **Sharp** Returns **Sharp**
@@ -45,9 +33,9 @@ Returns **Sharp**
Alternative spelling of `greyscale`. Alternative spelling of `greyscale`.
**Parameters** ### Parameters
- `grayscale` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `true`) - `grayscale` **[Boolean][5]** (optional, default `true`)
Returns **Sharp** Returns **Sharp**
@@ -56,12 +44,12 @@ Returns **Sharp**
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.
**Parameters** ### Parameters
- `colourspace` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568) - `colourspace` **[String][1]?** output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...][6]
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters - Throws **[Error][4]** Invalid parameters
Returns **Sharp** Returns **Sharp**
@@ -69,11 +57,23 @@ Returns **Sharp**
Alternative spelling of `toColourspace`. Alternative spelling of `toColourspace`.
**Parameters** ### Parameters
- `colorspace` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** output colorspace. - `colorspace` **[String][1]?** output colorspace.
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters - Throws **[Error][4]** Invalid parameters
Returns **Sharp** Returns **Sharp**
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[3]: https://www.npmjs.org/package/color
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[6]: https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568

View File

@@ -1,43 +1,53 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. --> <!-- Generated by documentation.js. Update this documentation by updating the source code. -->
### Table of Contents ## composite
- [overlayWith](#overlaywith) Composite image(s) over the processed (resized, extracted etc.) image.
## overlayWith The images to composite must be the same size or smaller than the processed image.
Overlay (composite) an image over the processed (resized, extracted etc.) image.
The overlay image must be the same size or smaller than the processed image.
If both `top` and `left` options are provided, they take precedence over `gravity`. If both `top` and `left` options are provided, they take precedence over `gravity`.
**Parameters** The `blend` option can be one of `clear`, `source`, `over`, `in`, `out`, `atop`,
`dest`, `dest-over`, `dest-in`, `dest-out`, `dest-atop`,
`xor`, `add`, `saturate`, `multiply`, `screen`, `overlay`, `darken`, `lighten`,
`colour-dodge`, `color-dodge`, `colour-burn`,`color-burn`,
`hard-light`, `soft-light`, `difference`, `exclusion`.
- `overlay` **([Buffer](https://nodejs.org/api/buffer.html) \| [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String))** Buffer containing image data or String containing the path to an image file. More information about blend modes can be found at
- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** [https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode][1]
- `options.gravity` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** gravity at which to place the overlay. (optional, default `'centre'`) and [https://www.cairographics.org/operators/][2]
- `options.top` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** the pixel offset from the top edge.
- `options.left` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** the pixel offset from the left edge.
- `options.tile` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** set to true to repeat the overlay image across the entire image with the given `gravity`. (optional, default `false`)
- `options.cutout` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** set to true to apply only the alpha channel of the overlay image to the input image, giving the appearance of one image being cut out of another. (optional, default `false`)
- `options.raw` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** describes overlay when using raw pixel data.
- `options.raw.width` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?**
- `options.raw.height` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?**
- `options.raw.channels` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?**
**Examples** ### Parameters
- `images` **[Array][3]&lt;[Object][4]>** Ordered list of images to composite
- `images[].input` **([Buffer][5] \| [String][6])?** Buffer containing image data, String containing the path to an image file, or Create object (see bellow)
- `images[].input.create` **[Object][4]?** describes a blank overlay to be created.
- `images[].input.create.width` **[Number][7]?**
- `images[].input.create.height` **[Number][7]?**
- `images[].input.create.channels` **[Number][7]?** 3-4
- `images[].input.create.background` **([String][6] \| [Object][4])?** parsed by the [color][8] module to extract values for red, green, blue and alpha.
- `images[].blend` **[String][6]** how to blend this image with the image below. (optional, default `'over'`)
- `images[].gravity` **[String][6]** gravity at which to place the overlay. (optional, default `'centre'`)
- `images[].top` **[Number][7]?** the pixel offset from the top edge.
- `images[].left` **[Number][7]?** the pixel offset from the left edge.
- `images[].tile` **[Boolean][9]** set to true to repeat the overlay image across the entire image with the given `gravity`. (optional, default `false`)
- `images[].density` **[Number][7]** number representing the DPI for vector overlay image. (optional, default `72`)
- `images[].raw` **[Object][4]?** describes overlay when using raw pixel data.
- `images[].raw.width` **[Number][7]?**
- `images[].raw.height` **[Number][7]?**
- `images[].raw.channels` **[Number][7]?**
### Examples
```javascript ```javascript
sharp('input.png') sharp('input.png')
.rotate(180) .rotate(180)
.resize(300) .resize(300)
.flatten() .flatten( { background: '#ff6600' } )
.background('#ff6600') .composite([{ input: 'overlay.png', gravity: 'southeast' }])
.overlayWith('overlay.png', { gravity: sharp.gravity.southeast } )
.sharpen() .sharpen()
.withMetadata() .withMetadata()
.quality(90) .webp( { quality: 90 } )
.webp()
.toBuffer() .toBuffer()
.then(function(outputBuffer) { .then(function(outputBuffer) {
// outputBuffer contains upside down, 300px wide, alpha channel flattened // outputBuffer contains upside down, 300px wide, alpha channel flattened
@@ -46,6 +56,26 @@ sharp('input.png')
}); });
``` ```
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters - Throws **[Error][10]** Invalid parameters
Returns **Sharp** Returns **Sharp**
[1]: https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode
[2]: https://www.cairographics.org/operators/
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[5]: https://nodejs.org/api/buffer.html
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[8]: https://www.npmjs.org/package/color
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error

View File

@@ -1,28 +1,30 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. --> <!-- Generated by documentation.js. Update this documentation by updating the source code. -->
### Table of Contents
- [Sharp](#sharp)
- [format](#format)
- [versions](#versions)
- [queue](#queue)
## Sharp ## Sharp
**Parameters** ### Parameters
- `input` **([Buffer](https://nodejs.org/api/buffer.html) \| [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String))?** if present, can be - `input` **([Buffer][1] \| [String][2])?** if present, can be
a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
a String containing the path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file. a String containing the path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when null or undefined. JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** if present, is an Object with optional attributes. - `options` **[Object][3]?** if present, is an Object with optional attributes.
- `options.density` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** integral number representing the DPI for vector images. (optional, default `72`) - `options.failOnError` **[Boolean][4]** by default halt processing and raise an error when loading invalid images.
- `options.raw` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** describes raw pixel image data. See `raw()` for pixel ordering. 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.raw.width` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** - `options.density` **[Number][5]** number representing the DPI for vector images. (optional, default `72`)
- `options.raw.height` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** - `options.pages` **[Number][5]** number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages. (optional, default `1`)
- `options.raw.channels` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** - `options.page` **[Number][5]** page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based. (optional, default `0`)
- `options.raw` **[Object][3]?** describes raw pixel input image data. See `raw()` for pixel ordering.
- `options.raw.width` **[Number][5]?**
- `options.raw.height` **[Number][5]?**
- `options.raw.channels` **[Number][5]?** 1-4
- `options.create` **[Object][3]?** describes a new image to be created.
- `options.create.width` **[Number][5]?**
- `options.create.height` **[Number][5]?**
- `options.create.channels` **[Number][5]?** 3-4
- `options.create.background` **([String][2] \| [Object][3])?** parsed by the [color][6] module to extract values for red, green, blue and alpha.
**Examples** ### Examples
```javascript ```javascript
sharp('input.jpg') sharp('input.jpg')
@@ -46,27 +48,42 @@ var transformer = sharp()
readableStream.pipe(transformer).pipe(writableStream); readableStream.pipe(transformer).pipe(writableStream);
``` ```
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters ```javascript
// Create a blank 300x200 PNG image of semi-transluent red pixels
sharp({
create: {
width: 300,
height: 200,
channels: 4,
background: { r: 255, g: 0, b: 0, alpha: 0.5 }
}
})
.png()
.toBuffer()
.then( ... );
```
Returns **[Sharp](#sharp)** - Throws **[Error][7]** Invalid parameters
Returns **[Sharp][8]**
### format ### format
An Object containing nested boolean values representing the available input and output formats/methods. An Object containing nested boolean values representing the available input and output formats/methods.
**Examples** #### Examples
```javascript ```javascript
console.log(sharp.format()); console.log(sharp.format);
``` ```
Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Returns **[Object][3]**
### versions ### versions
An Object containing the version numbers of libvips and its dependencies. An Object containing the version numbers of libvips and its dependencies.
**Examples** #### Examples
```javascript ```javascript
console.log(sharp.versions); console.log(sharp.versions);
@@ -79,10 +96,26 @@ An EventEmitter that emits a `change` event when a task is either:
- queued, waiting for _libuv_ to provide a worker thread - queued, waiting for _libuv_ to provide a worker thread
- complete - complete
**Examples** ### Examples
```javascript ```javascript
sharp.queue.on('change', function(queueLength) { sharp.queue.on('change', function(queueLength) {
console.log('Queue contains ' + queueLength + ' task(s)'); console.log('Queue contains ' + queueLength + ' task(s)');
}); });
``` ```
[1]: https://nodejs.org/api/buffer.html
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[6]: https://www.npmjs.org/package/color
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
[8]: #sharp

View File

@@ -1,19 +1,12 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. --> <!-- Generated by documentation.js. Update this documentation by updating the source code. -->
### Table of Contents
- [clone](#clone)
- [metadata](#metadata)
- [limitInputPixels](#limitinputpixels)
- [sequentialRead](#sequentialread)
## clone ## clone
Take a "snapshot" of the Sharp instance, returning a new instance. Take a "snapshot" of the Sharp instance, returning a new instance.
Cloned instances inherit the input of their parent 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. This allows multiple output Streams and therefore multiple processing pipelines to share a single input Stream.
**Examples** ### Examples
```javascript ```javascript
const pipeline = sharp().rotate(); const pipeline = sharp().rotate();
@@ -28,26 +21,34 @@ Returns **Sharp**
## metadata ## metadata
Fast access to image metadata without decoding any compressed image data. Fast access to (uncached) image metadata without decoding any compressed image data.
A Promises/A+ promise is returned when `callback` is not provided. A `Promise` is returned when `callback` is not provided.
- `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg` - `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
- `width`: Number of pixels wide - `size`: Total size of image in bytes, for Stream and Buffer input only
- `height`: Number of pixels high - `width`: Number of pixels wide (EXIF orientation is not taken into consideration)
- `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568) - `height`: Number of pixels high (EXIF orientation is not taken into consideration)
- `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...][1]
- `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK - `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
- `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...][2]
- `density`: Number of pixels per inch (DPI), if present - `density`: Number of pixels per inch (DPI), if present
- `chromaSubsampling`: String containing JPEG chroma subsampling, `4:2:0` or `4:4:4` for RGB, `4:2:0:4` or `4:4:4:4` for CMYK
- `isProgressive`: Boolean indicating whether the image is interlaced using a progressive scan
- `pages`: Number of pages/frames contained within the image, with support for TIFF, PDF, animated GIF and animated WebP
- `pageHeight`: Number of pixels high each page in this PDF image will be.
- `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
- `exif`: Buffer containing raw EXIF data, if present - `exif`: Buffer containing raw EXIF data, if present
- `icc`: Buffer containing raw [ICC](https://www.npmjs.com/package/icc) profile data, if present - `icc`: Buffer containing raw [ICC][3] profile data, if present
- `iptc`: Buffer containing raw IPTC data, if present
- `xmp`: Buffer containing raw XMP data, if present
**Parameters** ### Parameters
- `callback` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)?** called with the arguments `(err, metadata)` - `callback` **[Function][4]?** called with the arguments `(err, metadata)`
**Examples** ### Examples
```javascript ```javascript
const image = sharp(inputJpg); const image = sharp(inputJpg);
@@ -64,20 +65,56 @@ image
}); });
``` ```
Returns **([Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)&lt;[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)> | Sharp)** Returns **([Promise][5]&lt;[Object][6]> | Sharp)**
## stats
Access to pixel-derived image statistics for every channel in the image.
A `Promise` is returned when `callback` is not provided.
- `channels`: Array of channel statistics for each channel in the image. Each channel statistic contains
- `min` (minimum value in the channel)
- `max` (maximum value in the channel)
- `sum` (sum of all values in a channel)
- `squaresSum` (sum of squared values in a channel)
- `mean` (mean of the values in a channel)
- `stdev` (standard deviation for the values in a channel)
- `minX` (x-coordinate of one of the pixel where the minimum lies)
- `minY` (y-coordinate of one of the pixel where the minimum lies)
- `maxX` (x-coordinate of one of the pixel where the maximum lies)
- `maxY` (y-coordinate of one of the pixel where the maximum lies)
- `isOpaque`: Value to identify if the image is opaque or transparent, based on the presence and use of alpha channel
- `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental)
### Parameters
- `callback` **[Function][4]?** called with the arguments `(err, stats)`
### Examples
```javascript
const image = sharp(inputJpg);
image
.stats()
.then(function(stats) {
// stats contains the channel-wise statistics array and the isOpaque value
});
```
Returns **[Promise][5]&lt;[Object][6]>**
## limitInputPixels ## limitInputPixels
Do not process input images where the number of pixels (width _ height) exceeds this limit. 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. Assumes image dimensions contained in the input metadata can be trusted.
The default limit is 268402689 (0x3FFF _ 0x3FFF) pixels. The default limit is 268402689 (0x3FFF x 0x3FFF) pixels.
**Parameters** ### Parameters
- `limit` **([Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) \| [Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean))** an integral Number of pixels, zero or false to remove limit, true to use default limit. - `limit` **([Number][7] \| [Boolean][8])** an integral Number of pixels, zero or false to remove limit, true to use default limit.
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid limit - Throws **[Error][9]** Invalid limit
Returns **Sharp** Returns **Sharp**
@@ -86,8 +123,28 @@ Returns **Sharp**
An advanced setting that switches the libvips access method to `VIPS_ACCESS_SEQUENTIAL`. 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. This will reduce memory usage and can improve performance on some systems.
**Parameters** The default behaviour _before_ function call is `false`, meaning the libvips access method is not sequential.
- `sequentialRead` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `true`) ### Parameters
- `sequentialRead` **[Boolean][8]** (optional, default `true`)
Returns **Sharp** Returns **Sharp**
[1]: https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L636
[2]: https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L672
[3]: https://www.npmjs.com/package/icc
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error

View File

@@ -1,30 +1,17 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. --> <!-- Generated by documentation.js. Update this documentation by updating the source code. -->
### Table of Contents
- [rotate](#rotate)
- [extract](#extract)
- [flip](#flip)
- [flop](#flop)
- [sharpen](#sharpen)
- [blur](#blur)
- [extend](#extend)
- [flatten](#flatten)
- [trim](#trim)
- [gamma](#gamma)
- [negate](#negate)
- [normalise](#normalise)
- [normalize](#normalize)
- [convolve](#convolve)
- [threshold](#threshold)
- [boolean](#boolean)
## rotate ## rotate
Rotate the output image by either an explicit angle Rotate the output image by either an explicit angle
or auto-orient based on the EXIF `Orientation` tag. or auto-orient based on the EXIF `Orientation` tag.
Use this method without angle to determine the angle from EXIF data. If an angle is provided, it is converted to a valid positive degree rotation.
For example, `-450` will produce a 270deg rotation.
When rotating by an angle other than a multiple of 90,
the background colour can be provided with the `background` option.
If no angle is provided, it is determined from the EXIF data.
Mirroring is supported and may infer the use of a flip operation. Mirroring is supported and may infer the use of a flip operation.
The use of `rotate` implies the removal of the EXIF `Orientation` tag, if any. The use of `rotate` implies the removal of the EXIF `Orientation` tag, if any.
@@ -32,11 +19,13 @@ The use of `rotate` implies the removal of the EXIF `Orientation` tag, if any.
Method order is important when both rotating and extracting regions, Method order is important when both rotating and extracting regions,
for example `rotate(x).extract(y)` will produce a different result to `extract(y).rotate(x)`. for example `rotate(x).extract(y)` will produce a different result to `extract(y).rotate(x)`.
**Parameters** ### Parameters
- `angle` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** 0, 90, 180 or 270. (optional, default `auto`) - `angle` **[Number][1]** angle of rotation. (optional, default `auto`)
- `options` **[Object][2]?** if present, is an Object with optional attributes.
- `options.background` **([String][3] \| [Object][2])** parsed by the [color][4] module to extract values for red, green, blue and alpha. (optional, default `"#000000"`)
**Examples** ### Examples
```javascript ```javascript
const pipeline = sharp() const pipeline = sharp()
@@ -50,47 +39,7 @@ const pipeline = sharp()
readableStream.pipe(pipeline); readableStream.pipe(pipeline);
``` ```
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters - Throws **[Error][5]** Invalid parameters
Returns **Sharp**
## extract
Extract a region of the image.
- Use `extract` before `resize` for pre-resize extraction.
- Use `extract` after `resize` for post-resize extraction.
- Use `extract` before and after for both.
**Parameters**
- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)**
- `options.left` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** zero-indexed offset from left edge
- `options.top` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** zero-indexed offset from top edge
- `options.width` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** dimension of extracted image
- `options.height` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** dimension of extracted image
**Examples**
```javascript
sharp(input)
.extract({ left: left, top: top, width: width, height: height })
.toFile(output, function(err) {
// Extract a region of the input image, saving in the same format.
});
```
```javascript
sharp(input)
.extract({ left: leftOffsetPre, top: topOffsetPre, width: widthPre, height: heightPre })
.resize(width, height)
.extract({ left: leftOffsetPost, top: topOffsetPost, width: widthPost, height: heightPost })
.toFile(output, function(err) {
// Extract a region, resize, then extract from the resized image
});
```
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters
Returns **Sharp** Returns **Sharp**
@@ -99,9 +48,9 @@ Returns **Sharp**
Flip the image about the vertical Y axis. This always occurs after rotation, if any. Flip the image about the vertical Y axis. This always occurs after rotation, if any.
The use of `flip` implies the removal of the EXIF `Orientation` tag, if any. The use of `flip` implies the removal of the EXIF `Orientation` tag, if any.
**Parameters** ### Parameters
- `flip` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `true`) - `flip` **[Boolean][6]** (optional, default `true`)
Returns **Sharp** Returns **Sharp**
@@ -110,9 +59,9 @@ Returns **Sharp**
Flop the image about the horizontal X axis. This always occurs after rotation, if any. Flop the image about the horizontal X axis. This always occurs after rotation, if any.
The use of `flop` implies the removal of the EXIF `Orientation` tag, if any. The use of `flop` implies the removal of the EXIF `Orientation` tag, if any.
**Parameters** ### Parameters
- `flop` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `true`) - `flop` **[Boolean][6]** (optional, default `true`)
Returns **Sharp** Returns **Sharp**
@@ -123,14 +72,28 @@ When used without parameters, performs a fast, mild sharpen of the output image.
When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space. When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space.
Separate control over the level of sharpening in "flat" and "jagged" areas is available. Separate control over the level of sharpening in "flat" and "jagged" areas is available.
**Parameters** ### Parameters
- `sigma` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters - Throws **[Error][5]** Invalid parameters
Returns **Sharp**
## median
Apply median filter.
When used without parameters the default window is 3x3.
### Parameters
- `size` **[Number][1]** square mask size: size x size (optional, default `3`)
- Throws **[Error][5]** Invalid parameters
Returns **Sharp** Returns **Sharp**
@@ -140,64 +103,23 @@ 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.
**Parameters** ### Parameters
- `sigma` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters - Throws **[Error][5]** Invalid parameters
Returns **Sharp**
## extend
Extends/pads the edges of the image with the colour provided to the `background` method.
This operation will always occur after resizing and extraction, if any.
**Parameters**
- `extend` **([Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) \| [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object))** single pixel count to add to all edges or an Object with per-edge counts
- `extend.top` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?**
- `extend.left` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?**
- `extend.bottom` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?**
- `extend.right` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?**
**Examples**
```javascript
// Resize to 140 pixels wide, then add 10 transparent pixels
// to the top, left and right edges and 20 to the bottom edge
sharp(input)
.resize(140)
.background({r: 0, g: 0, b: 0, alpha: 0})
.extend({top: 10, bottom: 20, left: 10, right: 10})
...
```
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters
Returns **Sharp** Returns **Sharp**
## flatten ## flatten
Merge alpha transparency channel, if any, with `background`. Merge alpha transparency channel, if any, with a background.
**Parameters** ### Parameters
- `flatten` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `true`) - `options` **[Object][2]?**
- `options.background` **([String][3] \| [Object][2])** background colour, parsed by the [color][4] module, defaults to black. (optional, default `{r:0,g:0,b:0}`)
Returns **Sharp**
## trim
Trim "boring" pixels from all edges that contain values within a percentage similarity of the top-left pixel.
**Parameters**
- `tolerance` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** value between 1 and 99 representing the percentage similarity. (optional, default `10`)
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters
Returns **Sharp** Returns **Sharp**
@@ -209,12 +131,15 @@ This can improve the perceived brightness of a resized image in non-linear colou
JPEG and WebP input images will not take advantage of the shrink-on-load performance optimisation JPEG and WebP input images will not take advantage of the shrink-on-load performance optimisation
when applying a gamma correction. when applying a gamma correction.
**Parameters** Supply a second argument to use a different output gamma value, otherwise the first value is used in both cases.
- `gamma` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** value between 1.0 and 3.0. (optional, default `2.2`) ### Parameters
- `gamma` **[Number][1]** value between 1.0 and 3.0. (optional, default `2.2`)
- `gammaOut` **[Number][1]?** value between 1.0 and 3.0. (optional, defaults to same as `gamma`)
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters - Throws **[Error][5]** Invalid parameters
Returns **Sharp** Returns **Sharp**
@@ -222,9 +147,9 @@ Returns **Sharp**
Produce the "negative" of the image. Produce the "negative" of the image.
**Parameters** ### Parameters
- `negate` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `true`) - `negate` **[Boolean][6]** (optional, default `true`)
Returns **Sharp** Returns **Sharp**
@@ -232,9 +157,9 @@ Returns **Sharp**
Enhance output image contrast by stretching its luminance to cover the full dynamic range. Enhance output image contrast by stretching its luminance to cover the full dynamic range.
**Parameters** ### Parameters
- `normalise` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `true`) - `normalise` **[Boolean][6]** (optional, default `true`)
Returns **Sharp** Returns **Sharp**
@@ -242,9 +167,9 @@ Returns **Sharp**
Alternative spelling of normalise. Alternative spelling of normalise.
**Parameters** ### Parameters
- `normalize` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `true`) - `normalize` **[Boolean][6]** (optional, default `true`)
Returns **Sharp** Returns **Sharp**
@@ -252,16 +177,16 @@ Returns **Sharp**
Convolve the image with the specified kernel. Convolve the image with the specified kernel.
**Parameters** ### Parameters
- `kernel` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** - `kernel` **[Object][2]**
- `kernel.width` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** width of the kernel in pixels. - `kernel.width` **[Number][1]** width of the kernel in pixels.
- `kernel.height` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** width of the kernel in pixels. - `kernel.height` **[Number][1]** width of the kernel in pixels.
- `kernel.kernel` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)&lt;[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)>** 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** 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
```javascript ```javascript
sharp(input) sharp(input)
@@ -277,7 +202,7 @@ sharp(input)
}); });
``` ```
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters - Throws **[Error][5]** Invalid parameters
Returns **Sharp** Returns **Sharp**
@@ -285,15 +210,15 @@ Returns **Sharp**
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.
**Parameters** ### Parameters
- `threshold` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** - `options` **[Object][2]?**
- `options.greyscale` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** convert to single channel greyscale. (optional, default `true`) - `options.greyscale` **[Boolean][6]** convert to single channel greyscale. (optional, default `true`)
- `options.grayscale` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** alternative spelling for greyscale. (optional, default `true`) - `options.grayscale` **[Boolean][6]** alternative spelling for greyscale. (optional, default `true`)
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters - Throws **[Error][5]** Invalid parameters
Returns **Sharp** Returns **Sharp**
@@ -304,17 +229,111 @@ Perform a bitwise boolean operation with operand image.
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.
**Parameters** ### Parameters
- `operand` **([Buffer](https://nodejs.org/api/buffer.html) \| [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String))** 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** - `options` **[Object][2]?**
- `options.raw` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** describes operand when using raw pixel data. - `options.raw` **[Object][2]?** describes operand when using raw pixel data.
- `options.raw.width` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** - `options.raw.width` **[Number][1]?**
- `options.raw.height` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** - `options.raw.height` **[Number][1]?**
- `options.raw.channels` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** - `options.raw.channels` **[Number][1]?**
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters - Throws **[Error][5]** Invalid parameters
Returns **Sharp** Returns **Sharp**
## linear
Apply the linear formula a \* input + b to the image (levels adjustment)
### Parameters
- `a` **[Number][1]** multiplier (optional, default `1.0`)
- `b` **[Number][1]** offset (optional, default `0.0`)
- Throws **[Error][5]** Invalid parameters
Returns **Sharp**
## recomb
Recomb the image with the specified matrix.
### Parameters
- `inputMatrix`
- `3x3` **[Array][7]&lt;[Array][7]&lt;[Number][1]>>** Recombination matrix
### Examples
```javascript
sharp(input)
.recomb([
[0.3588, 0.7044, 0.1368],
[0.2990, 0.5870, 0.1140],
[0.2392, 0.4696, 0.0912],
])
.raw()
.toBuffer(function(err, data, info) {
// data contains the raw pixel data after applying the recomb
// With this example input, a sepia filter has been applied
});
```
- Throws **[Error][5]** Invalid parameters
Returns **Sharp**
## modulate
Transforms the image using brightness, saturation and hue rotation.
### Parameters
- `options` **[Object][2]?**
- `options.brightness` **[Number][1]?** Brightness multiplier
- `options.saturation` **[Number][1]?** Saturation multiplier
- `options.hue` **[Number][1]?** Degrees for hue rotation
### Examples
```javascript
sharp(input)
.modulate({
brightness: 2 // increase lightness by a factor of 2
});
sharp(input)
.modulate({
hue: 180 // hue-rotate by 180 degrees
});
// decreate brightness and saturation while also hue-rotating by 90 degrees
sharp(input)
.modulate({
brightness: 0.5,
saturation: 0.5,
hue: 90
});
```
Returns **Sharp**
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[4]: https://www.npmjs.org/package/color
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[8]: https://nodejs.org/api/buffer.html

View File

@@ -1,18 +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. -->
### Table of Contents
- [toFile](#tofile)
- [toBuffer](#tobuffer)
- [withMetadata](#withmetadata)
- [jpeg](#jpeg)
- [png](#png)
- [webp](#webp)
- [tiff](#tiff)
- [raw](#raw)
- [toFormat](#toformat)
- [tile](#tile)
## toFile ## toFile
Write output image data to a file. Write output image data to a file.
@@ -21,37 +8,78 @@ If an explicit output format is not selected, it will be inferred from the exten
with JPEG, PNG, WebP, TIFF, DZI, and libvips' V format supported. with JPEG, PNG, WebP, TIFF, DZI, and libvips' V format supported.
Note that raw pixel data is only supported for buffer output. Note that raw pixel data is only supported for buffer output.
A Promises/A+ promise is returned when `callback` is not provided. A `Promise` is returned when `callback` is not provided.
**Parameters** ### Parameters
- `fileOut` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** the path to write the image data to. - `fileOut` **[String][1]** the path to write the image data to.
- `callback` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)?** called on completion with two arguments `(err, info)`. - `callback` **[Function][2]?** called on completion with two arguments `(err, info)`.
`info` contains the output image `format`, `size` (bytes), `width`, `height` and `channels`. `info` contains the output image `format`, `size` (bytes), `width`, `height`,
`channels` and `premultiplied` (indicating if premultiplication was used).
When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
### Examples
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters ```javascript
sharp(input)
.toFile('output.png', (err, info) => { ... });
```
Returns **[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)&lt;[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)>** when no callback is provided ```javascript
sharp(input)
.toFile('output.png')
.then(info => { ... })
.catch(err => { ... });
```
- Throws **[Error][3]** Invalid parameters
Returns **[Promise][4]&lt;[Object][5]>** when no callback is provided
## toBuffer ## toBuffer
Write output to a Buffer. Write output to a Buffer.
JPEG, PNG, WebP, and RAW output are supported. JPEG, PNG, WebP, TIFF and RAW output are supported.
By default, the format will match the input image, except GIF and SVG input which become PNG output. By default, the format will match the input image, except GIF and SVG input which become PNG output.
`callback`, if present, gets three arguments `(err, buffer, info)` where: `callback`, if present, gets three arguments `(err, data, info)` where:
- `err` is an error message, if any. - `err` is an error, if any.
- `buffer` is the output image data. - `data` is the output image data.
- `info` contains the output image `format`, `size` (bytes), `width`, `height` and `channels`. - `info` contains the output image `format`, `size` (bytes), `width`, `height`,
A Promises/A+ promise is returned when `callback` is not provided. `channels` and `premultiplied` (indicating if premultiplication was used).
When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
**Parameters** A `Promise` is returned when `callback` is not provided.
- `callback` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)?** ### Parameters
Returns **[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)&lt;[Buffer](https://nodejs.org/api/buffer.html)>** when no callback is provided - `options` **[Object][5]?**
- `options.resolveWithObject` **[Boolean][6]?** Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
- `callback` **[Function][2]?**
### Examples
```javascript
sharp(input)
.toBuffer((err, data, info) => { ... });
```
```javascript
sharp(input)
.toBuffer()
.then(data => { ... })
.catch(err => { ... });
```
```javascript
sharp(input)
.toBuffer({ resolveWithObject: true })
.then(({ data, info }) => { ... })
.catch(err => { ... });
```
Returns **[Promise][4]&lt;[Buffer][7]>** when no callback is provided
## withMetadata ## withMetadata
@@ -59,13 +87,21 @@ 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. The default behaviour, when `withMetadata` is not used, is to strip all metadata and convert to the device-independent sRGB colour space.
This will also convert to and add a web-friendly sRGB ICC profile. This will also convert to and add a web-friendly sRGB ICC profile.
**Parameters** ### Parameters
- `withMetadata` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** - `withMetadata` **[Object][5]?**
- `withMetadata.orientation` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** value between 1 and 8, used to update the EXIF `Orientation` tag. - `withMetadata.orientation` **[Number][8]?** value between 1 and 8, used to update the EXIF `Orientation` tag.
### Examples
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters ```javascript
sharp('input.jpg')
.withMetadata()
.toFile('output-with-metadata.jpg')
.then(info => { ... });
```
- Throws **[Error][3]** Invalid parameters
Returns **Sharp** Returns **Sharp**
@@ -73,20 +109,35 @@ Returns **Sharp**
Use these JPEG options for output image. Use these JPEG options for output image.
**Parameters** ### Parameters
- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** output options - `options` **[Object][5]?** output options
- `options.quality` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** quality, integer 1-100 (optional, default `80`) - `options.quality` **[Number][8]** quality, integer 1-100 (optional, default `80`)
- `options.progressive` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** use progressive (interlace) scan (optional, default `false`) - `options.progressive` **[Boolean][6]** use progressive (interlace) scan (optional, default `false`)
- `options.chromaSubsampling` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** set to '4:4:4' to prevent chroma subsampling when quality &lt;= 90 (optional, default `'4:2:0'`) - `options.chromaSubsampling` **[String][1]** set to '4:4:4' to prevent chroma subsampling when quality &lt;= 90 (optional, default `'4:2:0'`)
- `options.trellisQuantisation` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** apply trellis quantisation, requires mozjpeg (optional, default `false`) - `options.trellisQuantisation` **[Boolean][6]** apply trellis quantisation, requires libvips compiled with support for mozjpeg (optional, default `false`)
- `options.overshootDeringing` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** apply overshoot deringing, requires mozjpeg (optional, default `false`) - `options.overshootDeringing` **[Boolean][6]** apply overshoot deringing, requires libvips compiled with support for mozjpeg (optional, default `false`)
- `options.optimiseScans` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** optimise progressive scans, forces progressive, requires mozjpeg (optional, default `false`) - `options.optimiseScans` **[Boolean][6]** optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg (optional, default `false`)
- `options.optimizeScans` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** alternative spelling of optimiseScans (optional, default `false`) - `options.optimizeScans` **[Boolean][6]** alternative spelling of optimiseScans (optional, default `false`)
- `options.force` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** force JPEG output, otherwise attempt to use input format (optional, default `true`) - `options.optimiseCoding` **[Boolean][6]** optimise Huffman coding tables (optional, default `true`)
- `options.optimizeCoding` **[Boolean][6]** alternative spelling of optimiseCoding (optional, default `true`)
- `options.quantisationTable` **[Number][8]** quantization table to use, integer 0-8, requires libvips compiled with support for mozjpeg (optional, default `0`)
- `options.quantizationTable` **[Number][8]** alternative spelling of quantisationTable (optional, default `0`)
- `options.force` **[Boolean][6]** force JPEG output, otherwise attempt to use input format (optional, default `true`)
### Examples
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid options ```javascript
// Convert any input to very high quality JPEG output
const data = await sharp(input)
.jpeg({
quality: 100,
chromaSubsampling: '4:4:4'
})
.toBuffer();
```
- Throws **[Error][3]** Invalid options
Returns **Sharp** Returns **Sharp**
@@ -94,16 +145,32 @@ Returns **Sharp**
Use these PNG options for output image. Use these PNG options for output image.
**Parameters** 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.
- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** ### Parameters
- `options.progressive` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** use progressive (interlace) scan (optional, default `false`)
- `options.compressionLevel` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** zlib compression level (optional, default `6`)
- `options.adaptiveFiltering` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** use adaptive row filtering (optional, default `true`)
- `options.force` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** force PNG output, otherwise attempt to use input format (optional, default `true`)
- `options` **[Object][5]?**
- `options.progressive` **[Boolean][6]** use progressive (interlace) scan (optional, default `false`)
- `options.compressionLevel` **[Number][8]** zlib compression level, 0-9 (optional, default `9`)
- `options.adaptiveFiltering` **[Boolean][6]** use adaptive row filtering (optional, default `false`)
- `options.palette` **[Boolean][6]** quantise to a palette-based image with alpha transparency support, requires libvips compiled with support for libimagequant (optional, default `false`)
- `options.quality` **[Number][8]** use the lowest number of colours needed to achieve given quality, requires libvips compiled with support for libimagequant (optional, default `100`)
- `options.colours` **[Number][8]** maximum number of palette entries, requires libvips compiled with support for libimagequant (optional, default `256`)
- `options.colors` **[Number][8]** alternative spelling of `options.colours`, requires libvips compiled with support for libimagequant (optional, default `256`)
- `options.dither` **[Number][8]** level of Floyd-Steinberg error diffusion, requires libvips compiled with support for libimagequant (optional, default `1.0`)
- `options.force` **[Boolean][6]** force PNG output, otherwise attempt to use input format (optional, default `true`)
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid options ### Examples
```javascript
// Convert any input to PNG output
const data = await sharp(input)
.png()
.toBuffer();
```
- Throws **[Error][3]** Invalid options
Returns **Sharp** Returns **Sharp**
@@ -111,17 +178,25 @@ Returns **Sharp**
Use these WebP options for output image. Use these WebP options for output image.
**Parameters** ### Parameters
- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** output options - `options` **[Object][5]?** output options
- `options.quality` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** quality, integer 1-100 (optional, default `80`) - `options.quality` **[Number][8]** quality, integer 1-100 (optional, default `80`)
- `options.alphaQuality` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** quality of alpha layer, integer 0-100 (optional, default `100`) - `options.alphaQuality` **[Number][8]** quality of alpha layer, integer 0-100 (optional, default `100`)
- `options.lossless` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** use lossless compression mode (optional, default `false`) - `options.lossless` **[Boolean][6]** use lossless compression mode (optional, default `false`)
- `options.nearLossless` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** use near_lossless compression mode (optional, default `false`) - `options.nearLossless` **[Boolean][6]** use near_lossless compression mode (optional, default `false`)
- `options.force` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** force WebP output, otherwise attempt to use input format (optional, default `true`) - `options.force` **[Boolean][6]** force WebP output, otherwise attempt to use input format (optional, default `true`)
### Examples
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid options ```javascript
// Convert any input to lossless WebP output
const data = await sharp(input)
.webp({ lossless: true })
.toBuffer();
```
- Throws **[Error][3]** Invalid options
Returns **Sharp** Returns **Sharp**
@@ -129,14 +204,35 @@ Returns **Sharp**
Use these TIFF options for output image. Use these TIFF options for output image.
**Parameters** ### Parameters
- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** output options - `options` **[Object][5]?** output options
- `options.quality` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** quality, integer 1-100 (optional, default `80`) - `options.quality` **[Number][8]** quality, integer 1-100 (optional, default `80`)
- `options.force` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** force TIFF output, otherwise attempt to use input format (optional, default `true`) - `options.force` **[Boolean][6]** force TIFF output, otherwise attempt to use input format (optional, default `true`)
- `options.compression` **[Boolean][6]** compression options: lzw, deflate, jpeg, ccittfax4 (optional, default `'jpeg'`)
- `options.predictor` **[Boolean][6]** compression predictor options: none, horizontal, float (optional, default `'horizontal'`)
- `options.pyramid` **[Boolean][6]** write an image pyramid (optional, default `false`)
- `options.tile` **[Boolean][6]** write a tiled tiff (optional, default `false`)
- `options.tileWidth` **[Boolean][6]** horizontal tile size (optional, default `256`)
- `options.tileHeight` **[Boolean][6]** vertical tile size (optional, default `256`)
- `options.xres` **[Number][8]** horizontal resolution in pixels/mm (optional, default `1.0`)
- `options.yres` **[Number][8]** vertical resolution in pixels/mm (optional, default `1.0`)
- `options.squash` **[Boolean][6]** squash 8-bit images down to 1 bit (optional, default `false`)
### Examples
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid options ```javascript
// Convert SVG input to LZW-compressed, 1 bit per pixel TIFF output
sharp('input.svg')
.tiff({
compression: 'lzw',
squash: true
})
.toFile('1-bpp-output.tiff')
.then(info => { ... });
```
- Throws **[Error][3]** Invalid options
Returns **Sharp** Returns **Sharp**
@@ -144,19 +240,36 @@ Returns **Sharp**
Force output to be raw, uncompressed uint8 pixel data. Force output to be raw, uncompressed uint8 pixel data.
### Examples
```javascript
// Extract raw RGB pixel data from JPEG input
const { data, info } = await sharp('input.jpg')
.raw()
.toBuffer({ resolveWithObject: true });
```
Returns **Sharp** Returns **Sharp**
## toFormat ## toFormat
Force output to a given format. Force output to a given format.
**Parameters** ### Parameters
- `format` **([String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object))** as a String or an Object with an 'id' attribute - `format` **([String][1] \| [Object][5])** as a String or an Object with an 'id' attribute
- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** output options - `options` **[Object][5]** output options
### Examples
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** unsupported format or options ```javascript
// Convert any input to PNG output
const data = await sharp(input)
.toFormat('png')
.toBuffer();
```
- Throws **[Error][3]** unsupported format or options
Returns **Sharp** Returns **Sharp**
@@ -166,15 +279,19 @@ 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.
Use a `.zip` or `.szi` file extension with `toFile` to write to a compressed archive file format. Use a `.zip` or `.szi` file extension with `toFile` to write to a compressed archive file format.
**Parameters** Warning: multiple sharp instances concurrently producing tile output can expose a possible race condition in some versions of libgsf.
- `tile` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** ### Parameters
- `tile.size` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** tile size in pixels, a value between 1 and 8192. (optional, default `256`)
- `tile.overlap` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** tile overlap in pixels, a value between 0 and 8192. (optional, default `0`)
- `tile.container` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** tile container, with value `fs` (filesystem) or `zip` (compressed file). (optional, default `'fs'`)
- `tile.layout` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** filesystem layout, possible values are `dz`, `zoomify` or `google`. (optional, default `'dz'`)
**Examples** - `tile` **[Object][5]?**
- `tile.size` **[Number][8]** tile size in pixels, a value between 1 and 8192. (optional, default `256`)
- `tile.overlap` **[Number][8]** tile overlap in pixels, a value between 0 and 8192. (optional, default `0`)
- `tile.angle` **[Number][8]** tile angle of rotation, must be a multiple of 90. (optional, default `0`)
- `tile.depth` **[String][1]?** how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
- `tile.container` **[String][1]** tile container, with value `fs` (filesystem) or `zip` (compressed file). (optional, default `'fs'`)
- `tile.layout` **[String][1]** filesystem layout, possible values are `dz`, `zoomify` or `google`. (optional, default `'dz'`)
### Examples
```javascript ```javascript
sharp('input.tiff') sharp('input.tiff')
@@ -188,6 +305,22 @@ sharp('input.tiff')
}); });
``` ```
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters - Throws **[Error][3]** Invalid parameters
Returns **Sharp** Returns **Sharp**
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[7]: https://nodejs.org/api/buffer.html
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number

View File

@@ -1,176 +1,234 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. --> <!-- Generated by documentation.js. Update this documentation by updating the source code. -->
### Table of Contents
- [resize](#resize)
- [crop](#crop)
- [embed](#embed)
- [max](#max)
- [min](#min)
- [ignoreAspectRatio](#ignoreaspectratio)
- [withoutEnlargement](#withoutenlargement)
## resize ## resize
Resize image to `width` x `height`. Resize image to `width`, `height` or `width x height`.
By default, the resized image is centre cropped to the exact size specified.
Possible reduction kernels are: When both a `width` and `height` are provided, the possible methods by which the image should **fit** these are:
- `cubic`: Use a [Catmull-Rom spline](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline). - `cover`: Crop to cover both provided dimensions (the default).
- `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`. - `contain`: Embed within both provided dimensions.
- `lanczos3`: Use a Lanczos kernel with `a=3` (the default). - `fill`: Ignore the aspect ratio of the input and stretch to both provided dimensions.
- `inside`: Preserving aspect ratio, resize the image to be as large as possible while ensuring its dimensions are less than or equal to both those specified.
- `outside`: Preserving aspect ratio, resize the image to be as small as possible while ensuring its dimensions are greater than or equal to both those specified.
Some of these values are based on the [object-fit][1] CSS property.
Possible enlargement interpolators are: When using a `fit` of `cover` or `contain`, the default **position** is `centre`. Other options are:
- `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation). - `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`.
- `bilinear`: Use [bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation), faster than bicubic but with less smooth results. - `sharp.gravity`: `north`, `northeast`, `east`, `southeast`, `south`, `southwest`, `west`, `northwest`, `center` or `centre`.
- `vertexSplitQuadraticBasisSpline`: Use the smoother [VSQBS interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48) to prevent "staircasing" when enlarging. - `sharp.strategy`: `cover` only, dynamically crop using either the `entropy` or `attention` strategy.
- `bicubic`: Use [bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) (the default). Some of these values are based on the [object-position][2] CSS property.
- `locallyBoundedBicubic`: Use [LBB interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100), which prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" but typically reduces performance by a factor of 2.
- `nohalo`: Use [Nohalo interpolation](http://eprints.soton.ac.uk/268086/), which prevents acutance but typically reduces performance by a factor of 3.
**Parameters**
- `width` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** pixels wide the resultant image should be, between 1 and 16383 (0x3FFF). Use `null` or `undefined` to auto-scale the width to match the height.
- `height` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** pixels high the resultant image should be, between 1 and 16383. Use `null` or `undefined` to auto-scale the height to match the width.
- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?**
- `options.kernel` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** the kernel to use for image reduction. (optional, default `'lanczos3'`)
- `options.interpolator` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** the interpolator to use for image enlargement. (optional, default `'bicubic'`)
- `options.centreSampling` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** use \*magick centre sampling convention instead of corner sampling. (optional, default `false`)
- `options.centerSampling` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** alternative spelling of centreSampling. (optional, default `false`)
**Examples**
```javascript
sharp(inputBuffer)
.resize(200, 300, {
kernel: sharp.kernel.lanczos2,
interpolator: sharp.interpolator.nohalo
})
.background('white')
.embed()
.toFile('output.tiff')
.then(function() {
// output.tiff is a 200 pixels wide and 300 pixels high image
// containing a lanczos2/nohalo scaled version, embedded on a white canvas,
// of the image data in inputBuffer
});
```
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters
Returns **Sharp**
## crop
Crop the resized image to the exact size specified, the default behaviour.
Possible attributes of the optional `sharp.gravity` are `north`, `northeast`, `east`, `southeast`, `south`,
`southwest`, `west`, `northwest`, `center` and `centre`.
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.
- `entropy`: focus on the region with the highest [Shannon entropy](https://en.wikipedia.org/wiki/Entropy_%28information_theory%29). - `entropy`: focus on the region with the highest [Shannon entropy][3].
- `attention`: focus on the region with the highest luminance frequency, colour saturation and presence of skin tones. - `attention`: focus on the region with the highest luminance frequency, colour saturation and presence of skin tones.
**Parameters** Possible interpolation kernels are:
- `crop` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** A member of `sharp.gravity` to crop to an edge/corner or `sharp.strategy` to crop dynamically. (optional, default `'centre'`) - `nearest`: Use [nearest neighbour interpolation][4].
- `cubic`: Use a [Catmull-Rom spline][5].
- `mitchell`: Use a [Mitchell-Netravali spline][6].
- `lanczos2`: Use a [Lanczos kernel][7] with `a=2`.
- `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
**Examples** ### Parameters
- `width` **[Number][8]?** pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height.
- `height` **[Number][8]?** pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
- `options` **[Object][9]?**
- `options.width` **[String][10]?** alternative means of specifying `width`. If both are present this take priority.
- `options.height` **[String][10]?** alternative means of specifying `height`. If both are present this take priority.
- `options.fit` **[String][10]** how the image should be resized to fit both provided dimensions, one of `cover`, `contain`, `fill`, `inside` or `outside`. (optional, default `'cover'`)
- `options.position` **[String][10]** position, gravity or strategy to use when `fit` is `cover` or `contain`. (optional, default `'centre'`)
- `options.background` **([String][10] \| [Object][9])** background colour when using a `fit` of `contain`, parsed by the [color][11] module, defaults to black without transparency. (optional, default `{r:0,g:0,b:0,alpha:1}`)
- `options.kernel` **[String][10]** the kernel to use for image reduction. (optional, default `'lanczos3'`)
- `options.withoutEnlargement` **[Boolean][12]** do not enlarge if the width _or_ height are already less than the specified dimensions, equivalent to GraphicsMagick's `>` geometry option. (optional, default `false`)
- `options.fastShrinkOnLoad` **[Boolean][12]** take greater advantage of the JPEG and WebP shrink-on-load feature, which can lead to a slight moiré pattern on some images. (optional, default `true`)
### Examples
```javascript
sharp(input)
.resize({ width: 100 })
.toBuffer()
.then(data => {
// 100 pixels wide, auto-scaled height
});
```
```javascript
sharp(input)
.resize({ height: 100 })
.toBuffer()
.then(data => {
// 100 pixels high, auto-scaled width
});
```
```javascript
sharp(input)
.resize(200, 300, {
kernel: sharp.kernel.nearest,
fit: 'contain',
position: 'right top',
background: { r: 255, g: 255, b: 255, alpha: 0.5 }
})
.toFile('output.png')
.then(() => {
// output.png is a 200 pixels wide and 300 pixels high image
// containing a nearest-neighbour scaled version
// contained within the north-east corner of a semi-transparent white canvas
});
```
```javascript ```javascript
const transformer = sharp() const transformer = sharp()
.resize(200, 200) .resize({
.crop(sharp.strategy.entropy) width: 200,
.on('error', function(err) { height: 200,
console.log(err); fit: sharp.fit.cover,
position: sharp.strategy.entropy
}); });
// Read image data from readableStream // Read image data from readableStream
// Write 200px square auto-cropped image data to writableStream // Write 200px square auto-cropped image data to writableStream
readableStream.pipe(transformer).pipe(writableStream); readableStream
.pipe(transformer)
.pipe(writableStream);
``` ```
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters
Returns **Sharp**
## embed
Preserving aspect ratio, resize the image to the maximum `width` or `height` specified
then embed on a background of the exact `width` and `height` specified.
If the background contains an alpha value then WebP and PNG format output images will
contain an alpha channel, even when the input image does not.
**Examples**
```javascript ```javascript
sharp('input.gif') sharp(input)
.resize(200, 300) .resize(200, 200, {
.background({r: 0, g: 0, b: 0, alpha: 0}) fit: sharp.fit.inside,
.embed() withoutEnlargement: true
.toFormat(sharp.format.webp) })
.toBuffer(function(err, outputBuffer) {
if (err) {
throw err;
}
// outputBuffer contains WebP image data of a 200 pixels wide and 300 pixels high
// containing a scaled version, embedded on a transparent canvas, of input.gif
});
```
Returns **Sharp**
## max
Preserving aspect ratio, resize the image to be as large as possible
while ensuring its dimensions are less than or equal to the `width` and `height` specified.
Both `width` and `height` must be provided via `resize` otherwise the behaviour will default to `crop`.
**Examples**
```javascript
sharp(inputBuffer)
.resize(200, 200)
.max()
.toFormat('jpeg') .toFormat('jpeg')
.toBuffer() .toBuffer()
.then(function(outputBuffer) { .then(function(outputBuffer) {
// outputBuffer contains JPEG image data no wider than 200 pixels and no higher // outputBuffer contains JPEG image data
// than 200 pixels regardless of the inputBuffer image dimensions // no wider and no higher than 200 pixels
// and no larger than the input image
}); });
``` ```
Returns **Sharp** - Throws **[Error][13]** Invalid parameters
## min
Preserving aspect ratio, resize the image to be as small as possible
while ensuring its dimensions are greater than or equal to the `width` and `height` specified.
Both `width` and `height` must be provided via `resize` otherwise the behaviour will default to `crop`.
Returns **Sharp** Returns **Sharp**
## ignoreAspectRatio ## extend
Ignoring the aspect ratio of the input, stretch the image to Extends/pads the edges of the image with the provided background colour.
the exact `width` and/or `height` provided via `resize`. This operation will always occur after resizing and extraction, if any.
### Parameters
- `extend` **([Number][8] \| [Object][9])** single pixel count to add to all edges or an Object with per-edge counts
- `extend.top` **[Number][8]?**
- `extend.left` **[Number][8]?**
- `extend.bottom` **[Number][8]?**
- `extend.right` **[Number][8]?**
- `extend.background` **([String][10] \| [Object][9])** background colour, parsed by the [color][11] module, defaults to black without transparency. (optional, default `{r:0,g:0,b:0,alpha:1}`)
### Examples
```javascript
// Resize to 140 pixels wide, then add 10 transparent pixels
// to the top, left and right edges and 20 to the bottom edge
sharp(input)
.resize(140)
.extend({
top: 10,
bottom: 20,
left: 10,
right: 10,
background: { r: 0, g: 0, b: 0, alpha: 0 }
})
...
```
- Throws **[Error][13]** Invalid parameters
Returns **Sharp** Returns **Sharp**
## withoutEnlargement ## extract
Do not enlarge the output image if the input image width _or_ height are already less than the required dimensions. Extract a region of the image.
This is equivalent to GraphicsMagick's `>` geometry option:
"_change the dimensions of the image only if its width or height exceeds the geometry specification_".
**Parameters** - Use `extract` before `resize` for pre-resize extraction.
- Use `extract` after `resize` for post-resize extraction.
- Use `extract` before and after for both.
- `withoutEnlargement` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `true`) ### Parameters
- `options` **[Object][9]** describes the region to extract using integral pixel values
- `options.left` **[Number][8]** zero-indexed offset from left edge
- `options.top` **[Number][8]** zero-indexed offset from top edge
- `options.width` **[Number][8]** width of region to extract
- `options.height` **[Number][8]** height of region to extract
### Examples
```javascript
sharp(input)
.extract({ left: left, top: top, width: width, height: height })
.toFile(output, function(err) {
// Extract a region of the input image, saving in the same format.
});
```
```javascript
sharp(input)
.extract({ left: leftOffsetPre, top: topOffsetPre, width: widthPre, height: heightPre })
.resize(width, height)
.extract({ left: leftOffsetPost, top: topOffsetPost, width: widthPost, height: heightPost })
.toFile(output, function(err) {
// Extract a region, resize, then extract from the resized image
});
```
- Throws **[Error][13]** Invalid parameters
Returns **Sharp** Returns **Sharp**
## trim
Trim "boring" pixels from all edges that contain values similar to the top-left pixel.
The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` properties.
### Parameters
- `threshold` **[Number][8]** the allowed difference from the top-left pixel, a number greater than zero. (optional, default `10`)
- Throws **[Error][13]** Invalid parameters
Returns **Sharp**
[1]: https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit
[2]: https://developer.mozilla.org/en-US/docs/Web/CSS/object-position
[3]: https://en.wikipedia.org/wiki/Entropy_%28information_theory%29
[4]: http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation
[5]: https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline
[6]: https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf
[7]: https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[11]: https://www.npmjs.org/package/color
[12]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[13]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error

View File

@@ -1,27 +1,20 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. --> <!-- Generated by documentation.js. Update this documentation by updating the source code. -->
### Table of Contents
- [cache](#cache)
- [concurrency](#concurrency)
- [counters](#counters)
- [simd](#simd)
## 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.
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.
This method always returns cache statistics, This method always returns cache statistics,
useful for determining how much working memory is required for a particular task. useful for determining how much working memory is required for a particular task.
**Parameters** ### Parameters
- `options` **([Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) \| [Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean))** Object with the following attributes, or Boolean where true uses default cache settings and false removes all caching. - `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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** 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
```javascript ```javascript
const stats = sharp.cache(); const stats = sharp.cache();
@@ -33,11 +26,11 @@ sharp.cache( { files: 0 } );
sharp.cache(false); sharp.cache(false);
``` ```
Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Returns **[Object][1]**
## concurrency ## concurrency
Gets, or when a concurrency is provided sets, Gets or, when a concurrency is provided, sets
the number of threads _libvips'_ should create to process each image. the number of threads _libvips'_ should create to process each image.
The default value is the number of CPU cores. The default value is the number of CPU cores.
A value of `0` will reset to this default. A value of `0` will reset to this default.
@@ -47,11 +40,11 @@ is limited by libuv's `UV_THREADPOOL_SIZE` environment variable.
This method always returns the current concurrency. This method always returns the current concurrency.
**Parameters** ### Parameters
- `concurrency` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** - `concurrency` **[Number][3]?**
**Examples** ### Examples
```javascript ```javascript
const threads = sharp.concurrency(); // 4 const threads = sharp.concurrency(); // 4
@@ -59,7 +52,7 @@ sharp.concurrency(2); // 2
sharp.concurrency(0); // 4 sharp.concurrency(0); // 4
``` ```
Returns **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** concurrency Returns **[Number][3]** concurrency
## counters ## counters
@@ -68,13 +61,13 @@ 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.
- process is the number of resize tasks currently being processed. - process is the number of resize tasks currently being processed.
**Examples** ### Examples
```javascript ```javascript
const counters = sharp.counters(); // { queue: 2, process: 4 } const counters = sharp.counters(); // { queue: 2, process: 4 }
``` ```
Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Returns **[Object][1]**
## simd ## simd
@@ -84,23 +77,26 @@ Requires libvips to have been compiled with liborc support.
Improves the performance of `resize`, `blur` and `sharpen` operations Improves the performance of `resize`, `blur` and `sharpen` operations
by taking advantage of the SIMD vector unit of the CPU, e.g. Intel SSE and ARM NEON. by taking advantage of the SIMD vector unit of the CPU, e.g. Intel SSE and ARM NEON.
This feature is currently off by default but future versions may reverse this. ### Parameters
Versions of liborc prior to 0.4.25 are known to segfault under heavy load.
**Parameters** - `simd` **[Boolean][2]** (optional, default `true`)
- `simd` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `false`) ### Examples
**Examples**
```javascript ```javascript
const simd = sharp.simd(); const simd = sharp.simd();
// simd is `true` if SIMD is currently enabled // simd is `true` if the runtime use of liborc is currently enabled
``` ```
```javascript ```javascript
const simd = sharp.simd(true); const simd = sharp.simd(false);
// attempts to enable the use of SIMD, returning true if available // prevent libvips from using liborc at runtime
``` ```
Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Returns **[Boolean][2]**
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number

View File

@@ -1,9 +1,435 @@
# Changelog # Changelog
### v0.22 - "*uptake*"
Requires libvips v8.7.4.
#### v0.22.1 - 25<sup>th</sup> April 2019
* Add `modulate` operation for brightness, saturation and hue.
[#1601](https://github.com/lovell/sharp/pull/1601)
[@Goues](https://github.com/Goues)
* Improve help messaging should `require("sharp")` fail.
[#1638](https://github.com/lovell/sharp/pull/1638)
[@sidharthachatterjee](https://github.com/sidharthachatterjee)
* Add support for Node 12.
[#1668](https://github.com/lovell/sharp/issues/1668)
#### v0.22.0 - 18<sup>th</sup> March 2019
* Remove functions previously deprecated in v0.21.0:
`background`, `crop`, `embed`, `ignoreAspectRatio`, `max`, `min` and `withoutEnlargement`.
* Add `composite` operation supporting multiple images and blend modes; deprecate `overlayWith`.
[#728](https://github.com/lovell/sharp/issues/728)
* Add support for `pages` input option for multi-page input.
[#1566](https://github.com/lovell/sharp/issues/1566)
* Allow Stream-based input of raw pixel data.
[#1579](https://github.com/lovell/sharp/issues/1579)
* Add support for `page` input option to GIF and PDF.
[#1595](https://github.com/lovell/sharp/pull/1595)
[@ramiel](https://github.com/ramiel)
### v0.21 - "*teeth*"
Requires libvips v8.7.0.
#### v0.21.3 - 19<sup>th</sup> January 2019
* Input image decoding now fails fast, set `failOnError` to change this behaviour.
* Failed filesystem-based input now separates missing file and invalid format errors.
[#1542](https://github.com/lovell/sharp/issues/1542)
#### v0.21.2 - 13<sup>th</sup> January 2019
* Ensure all metadata is removed from PNG output unless `withMetadata` used.
* Ensure shortest edge is at least one pixel after resizing.
[#1003](https://github.com/lovell/sharp/issues/1003)
* Add `ensureAlpha` operation to add an alpha channel, if missing.
[#1153](https://github.com/lovell/sharp/issues/1153)
* Expose `pages` and `pageHeight` metadata for multi-page input images.
[#1205](https://github.com/lovell/sharp/issues/1205)
* Expose PNG output options requiring libimagequant.
[#1484](https://github.com/lovell/sharp/issues/1484)
* Expose underlying error message for invalid input.
[#1505](https://github.com/lovell/sharp/issues/1505)
* Prevent mutatation of options passed to `jpeg`.
[#1516](https://github.com/lovell/sharp/issues/1516)
* Ensure forced output format applied correctly when output chaining.
[#1528](https://github.com/lovell/sharp/issues/1528)
#### v0.21.1 - 7<sup>th</sup> December 2018
* Install: support `sharp_dist_base_url` npm config, like existing `SHARP_DIST_BASE_URL`.
[#1422](https://github.com/lovell/sharp/pull/1422)
[@SethWen](https://github.com/SethWen)
* Ensure `channel` metadata is correct for raw, greyscale output.
[#1425](https://github.com/lovell/sharp/issues/1425)
* Add support for the "mitchell" kernel for image reductions.
[#1438](https://github.com/lovell/sharp/pull/1438)
[@Daiz](https://github.com/Daiz)
* Allow separate parameters for gamma encoding and decoding.
[#1439](https://github.com/lovell/sharp/pull/1439)
[@Daiz](https://github.com/Daiz)
* Build prototype with `Object.assign` to allow minification.
[#1475](https://github.com/lovell/sharp/pull/1475)
[@jaubourg](https://github.com/jaubourg)
* Expose libvips' recombination matrix operation.
[#1477](https://github.com/lovell/sharp/pull/1477)
[@fromkeith](https://github.com/fromkeith)
* Expose libvips' pyramid/tile options for TIFF output.
[#1483](https://github.com/lovell/sharp/pull/1483)
[@mbklein](https://github.com/mbklein)
#### v0.21.0 - 4<sup>th</sup> October 2018
* Deprecate the following resize-related functions:
`crop`, `embed`, `ignoreAspectRatio`, `max`, `min` and `withoutEnlargement`.
Access to these is now via options passed to the `resize` function.
For example:
`embed('north')` is now `resize(width, height, { fit: 'contain', position: 'north' })`,
`crop('attention')` is now `resize(width, height, { fit: 'cover', position: 'attention' })`,
`max().withoutEnlargement()` is now `resize(width, height, { fit: 'inside', withoutEnlargement: true })`.
[#1135](https://github.com/lovell/sharp/issues/1135)
* Deprecate the `background` function.
Per-operation `background` options added to `resize`, `extend` and `flatten` operations.
[#1392](https://github.com/lovell/sharp/issues/1392)
* Add `size` to `metadata` response (Stream and Buffer input only).
[#695](https://github.com/lovell/sharp/issues/695)
* Switch from custom trim operation to `vips_find_trim`.
[#914](https://github.com/lovell/sharp/issues/914)
* Add `chromaSubsampling` and `isProgressive` properties to `metadata` response.
[#1186](https://github.com/lovell/sharp/issues/1186)
* Drop Node 4 support.
[#1212](https://github.com/lovell/sharp/issues/1212)
* Enable SIMD convolution by default.
[#1213](https://github.com/lovell/sharp/issues/1213)
* Add experimental prebuilt binaries for musl-based Linux.
[#1379](https://github.com/lovell/sharp/issues/1379)
* Add support for arbitrary rotation angle via vips_rotate.
[#1385](https://github.com/lovell/sharp/pull/1385)
[@freezy](https://github.com/freezy)
### v0.20 - "*prebuild*"
Requires libvips v8.6.1.
#### v0.20.8 - 5<sup>th</sup> September 2018
* Avoid race conditions when creating directories during installation.
[#1358](https://github.com/lovell/sharp/pull/1358)
[@ajhool](https://github.com/ajhool)
* Accept floating point values for input density parameter.
[#1362](https://github.com/lovell/sharp/pull/1362)
[@aeirola](https://github.com/aeirola)
#### v0.20.7 - 21<sup>st</sup> August 2018
* Use copy+unlink if rename operation fails during installation.
[#1345](https://github.com/lovell/sharp/issues/1345)
#### v0.20.6 - 20<sup>th</sup> August 2018
* Add removeAlpha operation to remove alpha channel, if any.
[#1248](https://github.com/lovell/sharp/issues/1248)
* Expose mozjpeg quant_table flag.
[#1285](https://github.com/lovell/sharp/pull/1285)
[@rexxars](https://github.com/rexxars)
* Allow full WebP alphaQuality range of 0-100.
[#1290](https://github.com/lovell/sharp/pull/1290)
[@sylvaindumont](https://github.com/sylvaindumont)
* Cache libvips binaries to reduce re-install time.
[#1301](https://github.com/lovell/sharp/issues/1301)
* Ensure vendor platform mismatch throws error at install time.
[#1303](https://github.com/lovell/sharp/issues/1303)
* Improve install time error messages for FreeBSD users.
[#1310](https://github.com/lovell/sharp/issues/1310)
* Ensure extractChannel works with 16-bit images.
[#1330](https://github.com/lovell/sharp/issues/1330)
* Expose depth option for tile-based output.
[#1342](https://github.com/lovell/sharp/pull/1342)
[@alundavies](https://github.com/alundavies)
* Add experimental entropy field to stats response.
#### v0.20.5 - 27<sup>th</sup> June 2018
* Expose libjpeg optimize_coding flag.
[#1265](https://github.com/lovell/sharp/pull/1265)
[@tomlokhorst](https://github.com/tomlokhorst)
#### v0.20.4 - 20<sup>th</sup> June 2018
* Prevent possible rounding error when using shrink-on-load and 90/270 degree rotation.
[#1241](https://github.com/lovell/sharp/issues/1241)
[@anahit42](https://github.com/anahit42)
* Ensure extractChannel sets correct single-channel colour space interpretation.
[#1257](https://github.com/lovell/sharp/issues/1257)
[@jeremychone](https://github.com/jeremychone)
#### v0.20.3 - 29<sup>th</sup> May 2018
* Fix tint operation by ensuring LAB interpretation and allowing negative values.
[#1235](https://github.com/lovell/sharp/issues/1235)
[@wezside](https://github.com/wezside)
#### v0.20.2 - 28<sup>th</sup> April 2018
* Add tint operation to set image chroma.
[#825](https://github.com/lovell/sharp/pull/825)
[@rikh42](https://github.com/rikh42)
* Add environment variable to ignore globally-installed libvips.
[#1165](https://github.com/lovell/sharp/pull/1165)
[@oncletom](https://github.com/oncletom)
* Add support for page selection with multi-page input (GIF/TIFF).
[#1204](https://github.com/lovell/sharp/pull/1204)
[@woolite64](https://github.com/woolite64)
* Add support for Group4 (CCITTFAX4) compression with TIFF output.
[#1208](https://github.com/lovell/sharp/pull/1208)
[@woolite64](https://github.com/woolite64)
#### v0.20.1 - 17<sup>th</sup> March 2018
* Improve installation experience when a globally-installed libvips below the minimum required version is found.
[#1148](https://github.com/lovell/sharp/issues/1148)
* Prevent smartcrop error when cumulative rounding is below target size.
[#1154](https://github.com/lovell/sharp/issues/1154)
[@ralrom](https://github.com/ralrom)
* Expose libvips' median filter operation.
[#1161](https://github.com/lovell/sharp/pull/1161)
[@BiancoA](https://github.com/BiancoA)
#### v0.20.0 - 5<sup>th</sup> March 2018
* Add support for prebuilt sharp binaries on common platforms.
[#186](https://github.com/lovell/sharp/issues/186)
### v0.19 - "*suit*"
Requires libvips v8.6.1.
#### v0.19.1 - 24<sup>th</sup> February 2018
* Expose libvips' linear transform feature.
[#1024](https://github.com/lovell/sharp/pull/1024)
[@3epnm](https://github.com/3epnm)
* Expose angle option for tile-based output.
[#1121](https://github.com/lovell/sharp/pull/1121)
[@BiancoA](https://github.com/BiancoA)
* Prevent crop operation when image already at or below target dimensions.
[#1134](https://github.com/lovell/sharp/issues/1134)
[@pieh](https://github.com/pieh)
#### v0.19.0 - 11<sup>th</sup> January 2018
* Expose offset coordinates of strategy-based crop.
[#868](https://github.com/lovell/sharp/issues/868)
[@mirohristov-com](https://github.com/mirohristov-com)
* PNG output now defaults to adaptiveFiltering=false, compressionLevel=9
[#872](https://github.com/lovell/sharp/issues/872)
[@wmertens](https://github.com/wmertens)
* Add stats feature for pixel-derived image statistics.
[#915](https://github.com/lovell/sharp/pull/915)
[@rnanwani](https://github.com/rnanwani)
* Add failOnError option to fail-fast on bad input image data.
[#976](https://github.com/lovell/sharp/pull/976)
[@mceachen](https://github.com/mceachen)
* Resize: switch to libvips' implementation, make fastShrinkOnLoad optional, remove interpolator and centreSampling options.
[#977](https://github.com/lovell/sharp/pull/977)
[@jardakotesovec](https://github.com/jardakotesovec)
* Attach finish event listener to a clone only for Stream-based input.
[#995](https://github.com/lovell/sharp/issues/995)
[@whmountains](https://github.com/whmountains)
* Add tilecache before smartcrop to avoid over-computation of previous operations.
[#1028](https://github.com/lovell/sharp/issues/1028)
[@coffeebite](https://github.com/coffeebite)
* Prevent toFile extension taking precedence over requested format.
[#1037](https://github.com/lovell/sharp/issues/1037)
[@tomgallagher](https://github.com/tomgallagher)
* Add support for gravity option to existing embed feature.
[#1038](https://github.com/lovell/sharp/pull/1038)
[@AzureByte](https://github.com/AzureByte)
* Expose IPTC and XMP metadata when available.
[#1079](https://github.com/lovell/sharp/pull/1079)
[@oaleynik](https://github.com/oaleynik)
* TIFF output: switch default predictor from 'none' to 'horizontal' to match libvips' behaviour.
### v0.18 - "*ridge*"
Requires libvips v8.5.5.
#### v0.18.4 - 18<sup>th</sup> September 2017
* Ensure input Buffer really is marked as Persistent, prevents mark-sweep GC.
[#950](https://github.com/lovell/sharp/issues/950)
[@lfdoherty](https://github.com/lfdoherty)
#### v0.18.3 - 13<sup>th</sup> September 2017
* Skip shrink-on-load when trimming.
[#888](https://github.com/lovell/sharp/pull/888)
[@kleisauke](https://github.com/kleisauke)
* Migrate from got to simple-get for basic auth support.
[#945](https://github.com/lovell/sharp/pull/945)
[@pbomb](https://github.com/pbomb)
#### v0.18.2 - 1<sup>st</sup> July 2017
* Expose libvips' xres and yres properties for TIFF output.
[#828](https://github.com/lovell/sharp/pull/828)
[@YvesBos](https://github.com/YvesBos)
* Ensure flip and flop operations work with auto-rotate.
[#837](https://github.com/lovell/sharp/issues/837)
[@rexxars](https://github.com/rexxars)
* Allow binary download URL override via SHARP_DIST_BASE_URL env variable.
[#841](https://github.com/lovell/sharp/issues/841)
* Add support for Solus Linux.
[#857](https://github.com/lovell/sharp/pull/857)
[@ekremkaraca](https://github.com/ekremkaraca)
#### v0.18.1 - 30<sup>th</sup> May 2017
* Remove regression from #781 that could cause incorrect shrink calculation.
[#831](https://github.com/lovell/sharp/issues/831)
[@suprMax](https://github.com/suprMax)
#### v0.18.0 - 30<sup>th</sup> May 2017
* Remove the previously-deprecated output format "option" functions:
quality, progressive, compressionLevel, withoutAdaptiveFiltering,
withoutChromaSubsampling, trellisQuantisation, trellisQuantization,
overshootDeringing, optimiseScans and optimizeScans.
* Ensure maximum output dimensions are based on the format to be used.
[#176](https://github.com/lovell/sharp/issues/176)
[@stephanebachelier](https://github.com/stephanebachelier)
* Avoid costly (un)premultiply when using overlayWith without alpha channel.
[#573](https://github.com/lovell/sharp/issues/573)
[@strarsis](https://github.com/strarsis)
* Include pixel depth (e.g. "uchar") when reading metadata.
[#577](https://github.com/lovell/sharp/issues/577)
[@moedusa](https://github.com/moedusa)
* Add support for Buffer and Stream-based TIFF output.
[#587](https://github.com/lovell/sharp/issues/587)
[@strarsis](https://github.com/strarsis)
* Expose warnings from libvips via NODE_DEBUG=sharp environment variable.
[#607](https://github.com/lovell/sharp/issues/607)
[@puzrin](https://github.com/puzrin)
* Switch to the libvips implementation of "attention" and "entropy" crop strategies.
[#727](https://github.com/lovell/sharp/issues/727)
* Improve performance and accuracy of nearest neighbour integral upsampling.
[#752](https://github.com/lovell/sharp/issues/752)
[@MrIbby](https://github.com/MrIbby)
* Constructor single argument API: allow plain object, reject null/undefined.
[#768](https://github.com/lovell/sharp/issues/768)
[@kub1x](https://github.com/kub1x)
* Ensure ARM64 pre-built binaries use correct C++11 ABI version.
[#772](https://github.com/lovell/sharp/issues/772)
[@ajiratech2](https://github.com/ajiratech2)
* Prevent aliasing by using dynamic values for shrink(-on-load).
[#781](https://github.com/lovell/sharp/issues/781)
[@kleisauke](https://github.com/kleisauke)
* Expose libvips' "squash" parameter to enable 1-bit TIFF output.
[#783](https://github.com/lovell/sharp/pull/783)
[@YvesBos](https://github.com/YvesBos)
* Add support for rotation using any multiple of +/-90 degrees.
[#791](https://github.com/lovell/sharp/pull/791)
[@ncoden](https://github.com/ncoden)
* Add "jpg" alias to toFormat as shortened form of "jpeg".
[#814](https://github.com/lovell/sharp/pull/814)
[@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
* Allow toBuffer to optionally resolve a Promise with both info and data.
[#143](https://github.com/lovell/sharp/issues/143)
[@salzhrani](https://github.com/salzhrani)
* Create blank image of given width, height, channels and background.
[#470](https://github.com/lovell/sharp/issues/470)
[@pjarts](https://github.com/pjarts)
* Add support for the "nearest" kernel for image reductions.
[#732](https://github.com/lovell/sharp/pull/732)
[@alice0meta](https://github.com/alice0meta)
* Add support for TIFF compression and predictor options.
[#738](https://github.com/lovell/sharp/pull/738)
[@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.

5
docs/css/extra.css Normal file
View File

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

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="86 86 550 550">
<!-- Copyright 2019 Lovell Fuller. This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) License. -->
<path fill="none" stroke="#9c0" stroke-width="80" d="M258.411 285.777l200.176-26.8M244.113 466.413L451.44 438.66M451.441 438.66V238.484M451.441 88.363v171.572l178.725-23.917M270.323 255.602V477.22M272.71 634.17V462.591L93.984 486.515"/>
<path fill="none" stroke="#090" stroke-width="80" d="M451.441 610.246V438.66l178.725-23.91M269.688 112.59v171.58L90.964 308.093"/>
</svg>

After

Width:  |  Height:  |  Size: 592 B

View File

@@ -1,5 +1,7 @@
# sharp # 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 The typical use case for this high speed Node.js module
is to convert large images in common formats to is to convert large images in common formats to
smaller, web-friendly JPEG, PNG and WebP images of varying dimensions. smaller, web-friendly JPEG, PNG and WebP images of varying dimensions.
@@ -13,8 +15,9 @@ 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.
OS X, Windows (x64), Linux (x64, ARM) systems do not require Most modern 64-bit OS X, Windows and Linux systems running
the installation of any external runtime dependencies. Node versions 6, 8, 10, 11 and 12
do not require any additional install or runtime dependencies.
[![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.png?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master) [![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.png?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master)
@@ -22,7 +25,7 @@ the installation of any external runtime dependencies.
This module supports reading JPEG, PNG, WebP, TIFF, GIF and SVG images. This module supports reading JPEG, PNG, WebP, TIFF, GIF and SVG images.
Output images can be in JPEG, PNG and WebP formats as well as uncompressed raw pixel data. 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. Streams, Buffer objects and the filesystem can be used for input and output.
@@ -36,7 +39,7 @@ and [Leaflet](https://github.com/turban/Leaflet.Zoomify).
### Fast ### Fast
This module is powered by the blazingly fast This module is powered by the blazingly fast
[libvips](https://github.com/jcupitt/libvips) image processing library, [libvips](https://github.com/libvips/libvips) image processing library,
originally created in 1989 at Birkbeck College originally created in 1989 at Birkbeck College
and currently maintained by and currently maintained by
[John Cupitt](https://github.com/jcupitt). [John Cupitt](https://github.com/jcupitt).
@@ -46,7 +49,7 @@ are held in memory and processed at a time,
taking full advantage of multiple CPU cores and L1/L2/L3 cache. taking full advantage of multiple CPU cores and L1/L2/L3 cache.
Everything remains non-blocking thanks to _libuv_, Everything remains non-blocking thanks to _libuv_,
no child processes are spawned and Promises/A+ are supported. no child processes are spawned and Promises/async/await are supported.
### Optimal ### Optimal
@@ -55,13 +58,13 @@ without having to use separate command line tools like
[jpegoptim](https://github.com/tjko/jpegoptim) and [jpegoptim](https://github.com/tjko/jpegoptim) and
[jpegtran](http://jpegclub.org/jpegtran/). [jpegtran](http://jpegclub.org/jpegtran/).
PNG filtering can be disabled, PNG filtering is disabled by default,
which for diagrams and line art often produces the same result which for diagrams and line art often produces the same result
as [pngcrush](http://pmt.sourceforge.net/pngcrush/). as [pngcrush](https://pmt.sourceforge.io/pngcrush/).
### Contributing ### Contributing
A [guide for contributors](https://github.com/lovell/sharp/blob/master/CONTRIBUTING.md) A [guide for contributors](https://github.com/lovell/sharp/blob/master/.github/CONTRIBUTING.md)
covers reporting bugs, requesting features and submitting code changes. covers reporting bugs, requesting features and submitting code changes.
### Credits ### Credits
@@ -97,17 +100,42 @@ the help and code contributions of the following people:
* [Matthias Thoemmes](https://github.com/cmtt) * [Matthias Thoemmes](https://github.com/cmtt)
* [Patrick Paskaris](https://github.com/ppaskaris) * [Patrick Paskaris](https://github.com/ppaskaris)
* [Jérémy Lal](https://github.com/kapouer) * [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)
Thank you! Thank you!
### Licence ### Licensing
Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors. Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0.html) [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,

View File

@@ -8,56 +8,85 @@ npm install sharp
yarn add sharp yarn add sharp
``` ```
### Prerequisites ## Prerequisites
* Node.js v6+
### Building from source
Pre-compiled binaries for sharp are provided for use with
Node versions 6, 8, 10, 11 and 12 on
64-bit Windows, OS X and Linux platforms.
Sharp will be built from source at install time when:
* a globally-installed libvips is detected,
* pre-compiled binaries do not exist for the current platform and Node version, or
* when the `npm install --build-from-source` flag is used.
Building from source requires:
* Node v4+
* C++11 compatible compiler such as gcc 4.8+, clang 3.0+ or MSVC 2013+ * C++11 compatible compiler such as gcc 4.8+, clang 3.0+ or MSVC 2013+
* [node-gyp](https://github.com/TooTallNate/node-gyp#installation) and its dependencies * [node-gyp](https://github.com/nodejs/node-gyp#installation) and its dependencies (includes Python 2.7)
## libvips
### Linux ### Linux
[![Ubuntu 14.04 Build Status](https://travis-ci.org/lovell/sharp.png?branch=master)](https://travis-ci.org/lovell/sharp) [![Ubuntu 16.04 Build Status](https://travis-ci.org/lovell/sharp.png?branch=master)](https://travis-ci.org/lovell/sharp)
[![Linux Build Status](https://circleci.com/gh/lovell/sharp.svg?style=svg&circle-token=6cb6d1d287a51af83722b19ed8885377fbc85e5c)](https://circleci.com/gh/lovell/sharp)
libvips and its dependencies are fetched and stored within `node_modules/sharp/vendor` during `npm install`. libvips and its dependencies are fetched and stored within `node_modules/sharp/vendor` during `npm install`.
This involves an automated HTTPS download of approximately 6.5MB. This involves an automated HTTPS download of approximately 9MB.
Most recent Linux-based operating systems with glibc running on x64 and ARMv6+ CPUs should "just work", e.g.: Most Linux-based (glibc, musl) operating systems running on x64 and ARMv6+ CPUs should "just work", e.g.:
* Debian 7, 8 * Debian 7+
* Ubuntu 12.04, 14.04, 16.04 * Ubuntu 14.04+
* Centos 7 * Centos 7+
* Fedora 23, 24 * Alpine 3.8+ (Node 8+)
* openSUSE 13.2 * Fedora
* openSUSE 13.2+
* Archlinux * Archlinux
* Raspbian Jessie * Raspbian Jessie
* Amazon Linux 2016.03, 2016.09 * Amazon Linux
* Solus
To use a globally-installed version of libvips instead of the provided binaries, To use a globally-installed version of libvips instead of the provided binaries,
make sure it is at least the version listed under `config.libvips` in the `package.json` file 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`. and that it can be located using `pkg-config --modversion vips-cpp`.
If you are using non-stadard paths (anything other than `/usr` or `/usr/local`), If you are using non-standard paths (anything other than `/usr` or `/usr/local`),
you might need to set `PKG_CONFIG_PATH` during `npm install` you might need to set `PKG_CONFIG_PATH` during `npm install`
and `LD_LIBRARY_PATH` at runtime. and `LD_LIBRARY_PATH` at runtime.
This allows the use of newer versions of libvips with older versions of sharp. This allows the use of newer versions of libvips with older versions of sharp.
For 32-bit Intel CPUs and older Linux-based operating systems such as Centos 6, For 32-bit Intel CPUs and older Linux-based operating systems such as Centos 6,
it is recommended to install a system-wide installation of libvips from source: compiling libvips from source is recommended.
https://github.com/jcupitt/libvips#building-libvips-from-a-source-tarball [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)
For Linux-based operating systems such as Alpine that use musl libc, #### Alpine Linux
the smaller stack size means libvips' cache should be disabled
libvips is available in the
[testing repository](https://pkgs.alpinelinux.org/packages?name=vips-dev):
```sh
apk add vips-dev fftw-dev build-base --update-cache \
--repository https://alpine.global.ssl.fastly.net/alpine/edge/testing/ \
--repository https://alpine.global.ssl.fastly.net/alpine/edge/main
```
The smaller stack size of musl libc means
libvips may need to be used without a cache
via `sharp.cache(false)` to avoid a stack overflow. via `sharp.cache(false)` to avoid a stack overflow.
### Mac OS ### Mac OS
[![OS X 10.9.5 Build Status](https://travis-ci.org/lovell/sharp.png?branch=master)](https://travis-ci.org/lovell/sharp) [![OS X 10.12 Build Status](https://travis-ci.org/lovell/sharp.png?branch=master)](https://travis-ci.org/lovell/sharp)
libvips and its dependencies are fetched and stored within `node_modules/sharp/vendor` during `npm install`. libvips and its dependencies are fetched and stored within `node_modules/sharp/vendor` during `npm install`.
This involves an automated HTTPS download of approximately 6.3MB. 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 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 at least the version listed under `config.libvips` in the `package.json` file and
@@ -68,24 +97,33 @@ that it can be located using `pkg-config --modversion vips-cpp`.
[![Windows x64 Build Status](https://ci.appveyor.com/api/projects/status/pgtul704nkhhg6sg)](https://ci.appveyor.com/project/lovell/sharp) [![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`. libvips and its dependencies are fetched and stored within `node_modules\sharp\vendor` during `npm install`.
This involves an automated HTTPS download of approximately 9MB. This involves an automated HTTPS download of approximately 14MB.
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. Only 64-bit (x64) `node.exe` is supported.
### FreeBSD ### FreeBSD
libvips must be installed before `npm install` is run. libvips must be installed before `npm install` is run.
This can be achieved via [FreshPorts](https://www.freshports.org/graphics/vips/):
This can be achieved via package or ports:
```sh
pkg install -y pkgconf vips
```
```sh ```sh
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
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=193528
### Heroku ### Heroku
[Alessandro Tagliapietra](https://github.com/alex88) maintains an Set [NODE_MODULES_CACHE](https://devcenter.heroku.com/articles/nodejs-support#cache-behavior)
[Heroku buildpack for libvips](https://github.com/alex88/heroku-buildpack-vips) to `false` when using the `yarn` package manager.
and its dependencies.
### Docker ### Docker
@@ -103,38 +141,58 @@ docker pull marcbachmann/libvips
docker pull wjordan/libvips 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 ### AWS Lambda
In order to use sharp on AWS Lambda, you need to [create a deployment package](http://docs.aws.amazon.com/lambda/latest/dg/nodejs-create-deployment-pkg.html). Because sharp Set the Lambda runtime to Node.js 8.10.
downloads and links libraries for the current platform during `npm install` you have to
do this on a system similar to the [Lambda Execution Environment](http://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html). The easiest ways to do this, is to setup a
small t2.micro instance using the AMI ID listed in the previous link, ssh into it as ec2-user
and follow the instructions below.
Install dependencies: The binaries in the `node_modules` directory of the
[deployment package](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-create-deployment-pkg.html)
must be for the Linux x64 platform/architecture.
On non-Linux machines such as OS X and Windows run the following:
```sh ```sh
curl -s https://rpm.nodesource.com/setup_4.x | sudo bash - rm -rf node_modules/sharp
sudo yum install -y gcc-c++ nodejs npm install --arch=x64 --platform=linux --target=8.10.0 sharp
``` ```
Copy your code and package.json to the instance using `scp` and create a deployment package: Alternatively a Docker container closely matching the Lambda runtime can be used:
```sh ```sh
cd sharp-lambda-example rm -rf node_modules/sharp
npm install docker run -v "$PWD":/var/task lambci/lambda:build-nodejs8.10 npm install sharp
zip -ur9 ../sharp-lambda-example.zip index.js node_modules
``` ```
You can now download your deployment ZIP using `scp` and upload it to Lambda. Be sure to set your Lambda runtime to Node.js 4.3. To get the best performance select the largest memory available.
A 1536 MB function provides ~12x more CPU time than a 128 MB function.
**Performance Tip:** To get the best performance on Lambda choose the largest memory available because this also gives you the most cpu time (a 1536 MB function is 12x faster than a 128 MB function). ### NW.js
Run the `nw-gyp` tool after installation.
```sh
cd node-modules/sharp
nw-gyp rebuild --arch=x64 --target=[your nw version]
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/)
### Build tools ### Build tools
* [gulp-responsive](https://www.npmjs.com/package/gulp-responsive) * [gulp-responsive](https://www.npmjs.com/package/gulp-responsive)
* [grunt-sharp](https://www.npmjs.com/package/grunt-sharp) * [grunt-sharp](https://www.npmjs.com/package/grunt-sharp)
### Coding tools
* [Sharp TypeScript Types](https://www.npmjs.com/package/@types/sharp)
### CLI tools ### CLI tools
@@ -153,38 +211,42 @@ and [Valgrind](http://valgrind.org/) have been used to test
the most popular web-based formats, as well as libvips itself, the most popular web-based formats, as well as libvips itself,
you are advised to perform your own testing and sandboxing. you are advised to perform your own testing and sandboxing.
ImageMagick in particular has a relatively large attack surface, ### Pre-compiled libvips binaries
which can be partially mitigated with a
[policy.xml](http://www.imagemagick.org/script/resources.php)
configuration file to prevent the use of coders known to be vulnerable.
```xml This module will attempt to download a pre-compiled bundle of libvips
<policymap> and its dependencies on Linux and Windows machines under either of these
<policy domain="coder" rights="none" pattern="EPHEMERAL" /> conditions:
<policy domain="coder" rights="none" pattern="URL" />
<policy domain="coder" rights="none" pattern="HTTPS" /> 1. If a global installation of libvips that meets the
<policy domain="coder" rights="none" pattern="MVG" /> minimum version requirement cannot be found;
<policy domain="coder" rights="none" pattern="MSL" /> 1. If `SHARP_IGNORE_GLOBAL_LIBVIPS` environment variable is set.
<policy domain="coder" rights="none" pattern="TEXT" />
<policy domain="coder" rights="none" pattern="SHOW" /> ```sh
<policy domain="coder" rights="none" pattern="WIN" /> SHARP_IGNORE_GLOBAL_LIBVIPS=1 npm install sharp
<policy domain="coder" rights="none" pattern="PLT" />
</policymap>
``` ```
Set the `MAGICK_CONFIGURE_PATH` environment variable Should you need to manually download and inspect these files,
to the directory containing the `policy.xml` file. 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`.
### Licences ### Licences
If a global installation of libvips that meets the
minimum version requirement cannot be found,
this module will download a pre-compiled bundle of libvips
and its dependencies on Linux and Windows machines.
Should you need to manually download and inspect these files,
you can do so via https://dl.bintray.com/lovell/sharp/
This module is licensed under the terms of the This module is licensed under the terms of the
[Apache 2.0 Licence](https://github.com/lovell/sharp/blob/master/LICENSE). [Apache 2.0 Licence](https://github.com/lovell/sharp/blob/master/LICENSE).
@@ -198,8 +260,11 @@ Use of libraries under the terms of the LGPLv3 is via the
| Library | Used under the terms of | | Library | Used under the terms of |
|---------------|----------------------------------------------------------------------------------------------------------| |---------------|----------------------------------------------------------------------------------------------------------|
| cairo | Mozilla Public License 2.0 | | cairo | Mozilla Public License 2.0 |
| expat | MIT Licence |
| fontconfig | [fontconfig Licence](https://cgit.freedesktop.org/fontconfig/tree/COPYING) (BSD-like) | | 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) | | 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 | | giflib | MIT Licence |
| glib | LGPLv3 | | glib | LGPLv3 |
| harfbuzz | MIT Licence | | harfbuzz | MIT Licence |

View File

@@ -2,19 +2,18 @@
### Test environment ### Test environment
* AWS EC2 eu-central-1 [c4.xlarge](http://aws.amazon.com/ec2/instance-types/#c4) (4x E5-2666 v3 @ 2.90GHz) * AWS EC2 eu-west-1 [c5.large](https://aws.amazon.com/ec2/instance-types/c5/) (2x Xeon Platinum 8124M CPU @ 3.00GHz)
* Ubuntu 16.04.1 LTS (HVM, SSD, 20161115, ami-82cf0aed) * Ubuntu 18.04 (hvm-ssd/ubuntu-bionic-18.04-amd64-server-20180912 ami-00035f41c82244dab)
* Node.js v6.9.1 * Node.js v10.11.0
### The contenders ### The contenders
* [jimp](https://www.npmjs.com/package/jimp) v0.2.27 - Image processing in pure JavaScript. Bilinear interpolation only. * [jimp](https://www.npmjs.com/package/jimp) v0.5.3 - Image processing in pure JavaScript. Provides bicubic interpolation.
* [lwip](https://www.npmjs.com/package/lwip) v0.0.9 - Wrapper around CImg. Compiles outdated, insecure dependencies from source. * [mapnik](https://www.npmjs.org/package/mapnik) v4.0.1 - Whilst primarily a map renderer, Mapnik contains bitmap image utilities.
* [mapnik](https://www.npmjs.org/package/mapnik) v3.5.14 - Whilst primarily a map renderer, Mapnik contains bitmap image utilities.
* [imagemagick-native](https://www.npmjs.com/package/imagemagick-native) v1.9.3 - Wrapper around libmagick++, supports Buffers only. * [imagemagick-native](https://www.npmjs.com/package/imagemagick-native) v1.9.3 - Wrapper around libmagick++, supports Buffers only.
* [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.0 - 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.17.0 / libvips v8.4.2 - Caching within libvips disabled to ensure a fair comparison. * sharp v0.21.0 / libvips v8.7.0 - Caching within libvips disabled to ensure a fair comparison.
### The task ### The task
@@ -26,18 +25,14 @@ then compress to JPEG at a "quality" setting of 80.
| Module | Input | Output | Ops/sec | Speed-up | | Module | Input | Output | Ops/sec | Speed-up |
| :----------------- | :----- | :----- | ------: | -------: | | :----------------- | :----- | :----- | ------: | -------: |
| jimp (bilinear) | buffer | buffer | 1.06 | 1.0 | | jimp | buffer | buffer | 0.71 | 1.0 |
| lwip | buffer | buffer | 1.87 | 1.8 | | mapnik | buffer | buffer | 3.32 | 4.7 |
| mapnik | buffer | buffer | 2.91 | 2.7 | | gm | buffer | buffer | 3.97 | 5.6 |
| imagemagick-native | buffer | buffer | 4.03 | 3.8 | | imagemagick-native | buffer | buffer | 4.06 | 5.7 |
| imagemagick | file | file | 7.10 | 6.7 | | imagemagick | file | file | 4.24 | 6.0 |
| gm | buffer | buffer | 7.08 | 6.7 | | sharp | stream | stream | 25.30 | 35.6 |
| gm | file | file | 7.10 | 6.7 | | sharp | file | file | 26.17 | 36.9 |
| sharp | stream | stream | 27.61 | 26.0 | | sharp | buffer | buffer | 26.45 | 37.3 |
| sharp | file | file | 28.41 | 26.8 |
| sharp | buffer | file | 28.71 | 27.1 |
| sharp | file | buffer | 28.60 | 27.0 |
| sharp | buffer | buffer | 29.08 | 27.4 |
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 8+ core machines, especially those with larger L1/L2 CPU caches.
@@ -46,19 +41,20 @@ The I/O limits of the relevant (de)compression library will generally determine
### Benchmark test prerequisites ### Benchmark test prerequisites
Requires both _ImageMagick_ and _GraphicsMagick_: Requires _ImageMagick_, _GraphicsMagick_ and _Mapnik_:
```sh ```sh
brew install imagemagick brew install imagemagick
brew install graphicsmagick brew install graphicsmagick
brew install mapnik
``` ```
```sh ```sh
sudo apt-get install imagemagick libmagick++-dev graphicsmagick sudo apt-get install imagemagick libmagick++-dev graphicsmagick libmapnik-dev
``` ```
```sh ```sh
sudo yum install ImageMagick-devel ImageMagick-c++-devel GraphicsMagick sudo yum install ImageMagick-devel ImageMagick-c++-devel GraphicsMagick mapnik-devel
``` ```
### Running the benchmark test ### Running the benchmark test

35
install/dll-copy.js Normal file
View File

@@ -0,0 +1,35 @@
'use strict';
const fs = require('fs');
const path = require('path');
const copyFileSync = require('fs-copy-file-sync');
const libvips = require('../lib/libvips');
const npmLog = require('npmlog');
if (process.platform === 'win32') {
const buildDir = path.join(__dirname, '..', 'build');
const buildReleaseDir = path.join(buildDir, 'Release');
npmLog.info('sharp', `Creating ${buildReleaseDir}`);
try {
libvips.mkdirSync(buildDir);
libvips.mkdirSync(buildReleaseDir);
} catch (err) {}
const vendorLibDir = path.join(__dirname, '..', 'vendor', 'lib');
npmLog.info('sharp', `Copying DLLs from ${vendorLibDir} to ${buildReleaseDir}`);
try {
fs
.readdirSync(vendorLibDir)
.filter(function (filename) {
return /\.dll$/.test(filename);
})
.forEach(function (filename) {
copyFileSync(
path.join(vendorLibDir, filename),
path.join(buildReleaseDir, filename)
);
});
} catch (err) {
npmLog.error('sharp', err.message);
}
}

109
install/libvips.js Normal file
View File

@@ -0,0 +1,109 @@
'use strict';
const fs = require('fs');
const os = require('os');
const path = require('path');
const detectLibc = require('detect-libc');
const npmLog = require('npmlog');
const semver = require('semver');
const simpleGet = require('simple-get');
const tar = require('tar');
const copyFileSync = require('fs-copy-file-sync');
const agent = require('../lib/agent');
const libvips = require('../lib/libvips');
const platform = require('../lib/platform');
const minimumLibvipsVersion = libvips.minimumLibvipsVersion;
const distBaseUrl = process.env.npm_config_sharp_dist_base_url || process.env.SHARP_DIST_BASE_URL || `https://github.com/lovell/sharp-libvips/releases/download/v${minimumLibvipsVersion}/`;
const fail = function (err) {
npmLog.error('sharp', err.message);
npmLog.info('sharp', 'Attempting to build from source via node-gyp but this may fail due to the above error');
npmLog.info('sharp', 'Please see https://sharp.pixelplumbing.com/page/install for required dependencies');
process.exit(1);
};
const extractTarball = function (tarPath) {
const vendorPath = path.join(__dirname, '..', 'vendor');
libvips.mkdirSync(vendorPath);
tar
.extract({
file: tarPath,
cwd: vendorPath,
strict: true
})
.catch(function (err) {
if (/unexpected end of file/.test(err.message)) {
npmLog.error('sharp', `Please delete ${tarPath} as it is not a valid tarball`);
}
fail(err);
});
};
try {
const useGlobalLibvips = libvips.useGlobalLibvips();
if (useGlobalLibvips) {
const globalLibvipsVersion = libvips.globalLibvipsVersion();
npmLog.info('sharp', `Detected globally-installed libvips v${globalLibvipsVersion}`);
npmLog.info('sharp', 'Building from source via node-gyp');
process.exit(1);
} else if (libvips.hasVendoredLibvips()) {
npmLog.info('sharp', `Using existing vendored libvips v${minimumLibvipsVersion}`);
} else {
// Is this arch/platform supported?
const arch = process.env.npm_config_arch || process.arch;
const platformAndArch = platform();
if (platformAndArch === 'win32-ia32') {
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}`);
}
if (platformAndArch === 'freebsd-x64' || platformAndArch === 'openbsd-x64' || platformAndArch === 'sunos-x64') {
throw new Error(`BSD/SunOS systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
}
if (detectLibc.family === detectLibc.GLIBC && detectLibc.version && semver.lt(`${detectLibc.version}.0`, '2.13.0')) {
throw new Error(`Use with glibc version ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`);
}
// Download to per-process temporary file
const tarFilename = ['libvips', minimumLibvipsVersion, platformAndArch].join('-') + '.tar.gz';
const tarPathCache = path.join(libvips.cachePath(), tarFilename);
if (fs.existsSync(tarPathCache)) {
npmLog.info('sharp', `Using cached ${tarPathCache}`);
extractTarball(tarPathCache);
} else {
const tarPathTemp = path.join(os.tmpdir(), `${process.pid}-${tarFilename}`);
const tmpFile = fs.createWriteStream(tarPathTemp);
const url = distBaseUrl + tarFilename;
npmLog.info('sharp', `Downloading ${url}`);
simpleGet({ url: url, agent: agent() }, function (err, response) {
if (err) {
throw err;
}
if (response.statusCode !== 200) {
throw new Error(`Status ${response.statusCode}`);
}
response
.on('error', fail)
.pipe(tmpFile);
});
tmpFile
.on('error', fail)
.on('close', function () {
try {
// Attempt to rename
fs.renameSync(tarPathTemp, tarPathCache);
} catch (err) {
// Fall back to copy and unlink
copyFileSync(tarPathTemp, tarPathCache);
fs.unlinkSync(tarPathTemp);
}
extractTarball(tarPathCache);
});
}
}
} catch (err) {
fail(err);
}

37
lib/agent.js Normal file
View File

@@ -0,0 +1,37 @@
'use strict';
const url = require('url');
const tunnelAgent = require('tunnel-agent');
const is = require('./is');
const proxies = [
'HTTPS_PROXY',
'https_proxy',
'HTTP_PROXY',
'http_proxy',
'npm_config_https_proxy',
'npm_config_proxy'
];
function env (key) {
return process.env[key];
}
module.exports = function () {
try {
const proxy = url.parse(proxies.map(env).find(is.string));
const tunnel = proxy.protocol === 'https:'
? tunnelAgent.httpsOverHttps
: tunnelAgent.httpsOverHttp;
return tunnel({
proxy: {
port: Number(proxy.port),
host: proxy.hostname,
proxyAuth: proxy.auth
}
});
} catch (err) {
return null;
}
};

View File

@@ -12,6 +12,40 @@ const bool = {
eor: 'eor' eor: 'eor'
}; };
/**
* Remove alpha channel, if any. This is a no-op if the image does not have an alpha channel.
*
* @example
* sharp('rgba.png')
* .removeAlpha()
* .toFile('rgb.png', function(err, info) {
* // rgb.png is a 3 channel image without an alpha channel
* });
*
* @returns {Sharp}
*/
function removeAlpha () {
this.options.removeAlpha = true;
return this;
}
/**
* Ensure alpha channel, if missing. The added alpha channel will be fully opaque. This is a no-op if the image already has an alpha channel.
*
* @example
* sharp('rgb.jpg')
* .ensureAlpha()
* .toFile('rgba.png', function(err, info) {
* // rgba.png is a 4 channel image with a fully opaque alpha channel
* });
*
* @returns {Sharp}
*/
function ensureAlpha () {
this.options.ensureAlpha = true;
return this;
}
/** /**
* Extract a single channel from a multi-channel image. * Extract a single channel from a multi-channel image.
* *
@@ -27,7 +61,7 @@ const bool = {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid channel * @throws {Error} Invalid channel
*/ */
const extractChannel = function extractChannel (channel) { function extractChannel (channel) {
if (channel === 'red') { if (channel === 'red') {
channel = 0; channel = 0;
} else if (channel === 'green') { } else if (channel === 'green') {
@@ -41,7 +75,7 @@ const extractChannel = function extractChannel (channel) {
throw new Error('Cannot extract invalid channel ' + channel); throw new Error('Cannot extract invalid channel ' + channel);
} }
return this; return this;
}; }
/** /**
* Join one or more channels to the image. * Join one or more channels to the image.
@@ -59,7 +93,7 @@ const extractChannel = function extractChannel (channel) {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const joinChannel = function joinChannel (images, options) { function joinChannel (images, options) {
if (Array.isArray(images)) { if (Array.isArray(images)) {
images.forEach(function (image) { images.forEach(function (image) {
this.options.joinChannelIn.push(this._createInputDescriptor(image, options)); this.options.joinChannelIn.push(this._createInputDescriptor(image, options));
@@ -68,7 +102,7 @@ const joinChannel = function joinChannel (images, options) {
this.options.joinChannelIn.push(this._createInputDescriptor(images, options)); this.options.joinChannelIn.push(this._createInputDescriptor(images, options));
} }
return this; return this;
}; }
/** /**
* Perform a bitwise boolean operation on all input image channels (bands) to produce a single channel output image. * Perform a bitwise boolean operation on all input image channels (bands) to produce a single channel output image.
@@ -86,27 +120,27 @@ const joinChannel = function joinChannel (images, options) {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const bandbool = function bandbool (boolOp) { function bandbool (boolOp) {
if (is.string(boolOp) && is.inArray(boolOp, ['and', 'or', 'eor'])) { if (is.string(boolOp) && is.inArray(boolOp, ['and', 'or', 'eor'])) {
this.options.bandBoolOp = boolOp; this.options.bandBoolOp = boolOp;
} else { } else {
throw new Error('Invalid bandbool operation ' + boolOp); throw new Error('Invalid bandbool operation ' + boolOp);
} }
return this; return this;
}; }
/** /**
* Decorate the Sharp prototype with channel-related functions. * Decorate the Sharp prototype with channel-related functions.
* @private * @private
*/ */
module.exports = function (Sharp) { module.exports = function (Sharp) {
// Public instance functions Object.assign(Sharp.prototype, {
[ // Public instance functions
removeAlpha,
ensureAlpha,
extractChannel, extractChannel,
joinChannel, joinChannel,
bandbool bandbool
].forEach(function (f) {
Sharp.prototype[f.name] = f;
}); });
// Class attributes // Class attributes
Sharp.bool = bool; Sharp.bool = bool;

View File

@@ -16,27 +16,19 @@ const colourspace = {
}; };
/** /**
* Set the background for the `embed`, `flatten` and `extend` operations. * Tint the image using the provided chroma while preserving the image luminance.
* The default background is `{r: 0, g: 0, b: 0, alpha: 1}`, black without transparency. * An alpha channel may be present and will be unchanged by the operation.
* *
* Delegates to the _color_ module, which can throw an Error * @param {String|Object} rgb - parsed by the [color](https://www.npmjs.org/package/color) module to extract chroma values.
* but is liberal in what it accepts, clipping values to sensible min/max.
* The alpha value is a float between `0` (transparent) and `1` (opaque).
*
* @param {String|Object} rgba - 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 parameter * @throws {Error} Invalid parameter
*/ */
const background = function background (rgba) { function tint (rgb) {
const colour = color(rgba); const colour = color(rgb);
this.options.background = [ this.options.tintA = colour.a();
colour.red(), this.options.tintB = colour.b();
colour.green(),
colour.blue(),
Math.round(colour.alpha() * 255)
];
return this; return this;
}; }
/** /**
* Convert to 8-bit greyscale; 256 shades of grey. * Convert to 8-bit greyscale; 256 shades of grey.
@@ -48,34 +40,34 @@ const background = function background (rgba) {
* @param {Boolean} [greyscale=true] * @param {Boolean} [greyscale=true]
* @returns {Sharp} * @returns {Sharp}
*/ */
const greyscale = function greyscale (greyscale) { function greyscale (greyscale) {
this.options.greyscale = is.bool(greyscale) ? greyscale : true; this.options.greyscale = is.bool(greyscale) ? greyscale : true;
return this; return this;
}; }
/** /**
* Alternative spelling of `greyscale`. * Alternative spelling of `greyscale`.
* @param {Boolean} [grayscale=true] * @param {Boolean} [grayscale=true]
* @returns {Sharp} * @returns {Sharp}
*/ */
const grayscale = function grayscale (grayscale) { function grayscale (grayscale) {
return this.greyscale(grayscale); return this.greyscale(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/jcupitt/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
*/ */
const toColourspace = function toColourspace (colourspace) { function toColourspace (colourspace) {
if (!is.string(colourspace)) { if (!is.string(colourspace)) {
throw new Error('Invalid output colourspace ' + colourspace); throw new Error('Invalid output colourspace ' + colourspace);
} }
this.options.colourspace = colourspace; this.options.colourspace = colourspace;
return this; return this;
}; }
/** /**
* Alternative spelling of `toColourspace`. * Alternative spelling of `toColourspace`.
@@ -83,24 +75,43 @@ const toColourspace = function toColourspace (colourspace) {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const toColorspace = function toColorspace (colorspace) { function toColorspace (colorspace) {
return this.toColourspace(colorspace); return this.toColourspace(colorspace);
}; }
/**
* Update a colour attribute of the this.options Object.
* @private
* @param {String} key
* @param {String|Object} val
* @throws {Error} Invalid key
*/
function _setColourOption (key, val) {
if (is.object(val) || is.string(val)) {
const colour = color(val);
this.options[key] = [
colour.red(),
colour.green(),
colour.blue(),
Math.round(colour.alpha() * 255)
];
}
}
/** /**
* Decorate the Sharp prototype with colour-related functions. * Decorate the Sharp prototype with colour-related functions.
* @private * @private
*/ */
module.exports = function (Sharp) { module.exports = function (Sharp) {
// Public instance functions Object.assign(Sharp.prototype, {
[ // Public
background, tint,
greyscale, greyscale,
grayscale, grayscale,
toColourspace, toColourspace,
toColorspace toColorspace,
].forEach(function (f) { // Private
Sharp.prototype[f.name] = f; _setColourOption
}); });
// Class attributes // Class attributes
Sharp.colourspace = colourspace; Sharp.colourspace = colourspace;

View File

@@ -1,24 +1,69 @@
'use strict'; 'use strict';
const deprecate = require('util').deprecate;
const is = require('./is'); const is = require('./is');
/** /**
* Overlay (composite) an image over the processed (resized, extracted etc.) image. * Blend modes.
* @member
* @private
*/
const blend = {
clear: 'clear',
source: 'source',
over: 'over',
in: 'in',
out: 'out',
atop: 'atop',
dest: 'dest',
'dest-over': 'dest-over',
'dest-in': 'dest-in',
'dest-out': 'dest-out',
'dest-atop': 'dest-atop',
xor: 'xor',
add: 'add',
saturate: 'saturate',
multiply: 'multiply',
screen: 'screen',
overlay: 'overlay',
darken: 'darken',
lighten: 'lighten',
'colour-dodge': 'colour-dodge',
'color-dodge': 'colour-dodge',
'colour-burn': 'colour-burn',
'color-burn': 'colour-burn',
'hard-light': 'hard-light',
'soft-light': 'soft-light',
difference: 'difference',
exclusion: 'exclusion'
};
/**
* Composite image(s) over the processed (resized, extracted etc.) image.
* *
* The overlay image must be the same size or smaller than the processed image. * The images to composite must be the same size or smaller than the processed image.
* If both `top` and `left` options are provided, they take precedence over `gravity`. * If both `top` and `left` options are provided, they take precedence over `gravity`.
* *
* The `blend` option can be one of `clear`, `source`, `over`, `in`, `out`, `atop`,
* `dest`, `dest-over`, `dest-in`, `dest-out`, `dest-atop`,
* `xor`, `add`, `saturate`, `multiply`, `screen`, `overlay`, `darken`, `lighten`,
* `colour-dodge`, `color-dodge`, `colour-burn`,`color-burn`,
* `hard-light`, `soft-light`, `difference`, `exclusion`.
*
* More information about blend modes can be found at
* https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode
* and https://www.cairographics.org/operators/
*
* @example * @example
* sharp('input.png') * sharp('input.png')
* .rotate(180) * .rotate(180)
* .resize(300) * .resize(300)
* .flatten() * .flatten( { background: '#ff6600' } )
* .background('#ff6600') * .composite([{ input: 'overlay.png', gravity: 'southeast' }])
* .overlayWith('overlay.png', { gravity: sharp.gravity.southeast } )
* .sharpen() * .sharpen()
* .withMetadata() * .withMetadata()
* .quality(90) * .webp( { quality: 90 } )
* .webp()
* .toBuffer() * .toBuffer()
* .then(function(outputBuffer) { * .then(function(outputBuffer) {
* // outputBuffer contains upside down, 300px wide, alpha channel flattened * // outputBuffer contains upside down, 300px wide, alpha channel flattened
@@ -26,67 +71,104 @@ const is = require('./is');
* // sharpened, with metadata, 90% quality WebP image data. Phew! * // sharpened, with metadata, 90% quality WebP image data. Phew!
* }); * });
* *
* @param {(Buffer|String)} overlay - Buffer containing image data or String containing the path to an image file. * @param {Object[]} images - Ordered list of images to composite
* @param {Object} [options] * @param {Buffer|String} [images[].input] - Buffer containing image data, String containing the path to an image file, or Create object (see bellow)
* @param {String} [options.gravity='centre'] - gravity at which to place the overlay. * @param {Object} [images[].input.create] - describes a blank overlay to be created.
* @param {Number} [options.top] - the pixel offset from the top edge. * @param {Number} [images[].input.create.width]
* @param {Number} [options.left] - the pixel offset from the left edge. * @param {Number} [images[].input.create.height]
* @param {Boolean} [options.tile=false] - set to true to repeat the overlay image across the entire image with the given `gravity`. * @param {Number} [images[].input.create.channels] - 3-4
* @param {Boolean} [options.cutout=false] - set to true to apply only the alpha channel of the overlay image to the input image, giving the appearance of one image being cut out of another. * @param {String|Object} [images[].input.create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
* @param {Object} [options.raw] - describes overlay when using raw pixel data. * @param {String} [images[].blend='over'] - how to blend this image with the image below.
* @param {Number} [options.raw.width] * @param {String} [images[].gravity='centre'] - gravity at which to place the overlay.
* @param {Number} [options.raw.height] * @param {Number} [images[].top] - the pixel offset from the top edge.
* @param {Number} [options.raw.channels] * @param {Number} [images[].left] - the pixel offset from the left edge.
* @param {Boolean} [images[].tile=false] - set to true to repeat the overlay image across the entire image with the given `gravity`.
* @param {Number} [images[].density=72] - number representing the DPI for vector overlay image.
* @param {Object} [images[].raw] - describes overlay when using raw pixel data.
* @param {Number} [images[].raw.width]
* @param {Number} [images[].raw.height]
* @param {Number} [images[].raw.channels]
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const overlayWith = function overlayWith (overlay, options) { function composite (images) {
this.options.overlay = this._createInputDescriptor(overlay, options, { if (!Array.isArray(images)) {
allowStream: false throw is.invalidParameterError('images to composite', 'array', images);
});
if (is.object(options)) {
if (is.defined(options.tile)) {
if (is.bool(options.tile)) {
this.options.overlayTile = options.tile;
} else {
throw new Error('Invalid overlay tile ' + options.tile);
}
}
if (is.defined(options.cutout)) {
if (is.bool(options.cutout)) {
this.options.overlayCutout = options.cutout;
} else {
throw new Error('Invalid overlay cutout ' + options.cutout);
}
}
if (is.defined(options.left) || is.defined(options.top)) {
if (
is.integer(options.left) && is.inRange(options.left, 0, this.constructor.maximum.width) &&
is.integer(options.top) && is.inRange(options.top, 0, this.constructor.maximum.height)
) {
this.options.overlayXOffset = options.left;
this.options.overlayYOffset = options.top;
} else {
throw new Error('Invalid overlay left ' + options.left + ' and/or top ' + options.top);
}
}
if (is.defined(options.gravity)) {
if (is.integer(options.gravity) && is.inRange(options.gravity, 0, 8)) {
this.options.overlayGravity = options.gravity;
} else if (is.string(options.gravity) && is.integer(this.constructor.gravity[options.gravity])) {
this.options.overlayGravity = this.constructor.gravity[options.gravity];
} else {
throw new Error('Unsupported overlay gravity ' + options.gravity);
}
}
} }
this.options.composite = images.map(image => {
if (!is.object(image)) {
throw is.invalidParameterError('image to composite', 'object', image);
}
const { raw, density } = image;
const inputOptions = (raw || density) ? { raw, density } : undefined;
const composite = {
input: this._createInputDescriptor(image.input, inputOptions, { allowStream: false }),
blend: 'over',
tile: false,
left: -1,
top: -1,
gravity: 0
};
if (is.defined(image.blend)) {
if (is.string(blend[image.blend])) {
composite.blend = blend[image.blend];
} else {
throw is.invalidParameterError('blend', 'valid blend name', image.blend);
}
}
if (is.defined(image.tile)) {
if (is.bool(image.tile)) {
composite.tile = image.tile;
} else {
throw is.invalidParameterError('tile', 'boolean', image.tile);
}
}
if (is.defined(image.left)) {
if (is.integer(image.left) && image.left >= 0) {
composite.left = image.left;
} else {
throw is.invalidParameterError('left', 'positive integer', image.left);
}
}
if (is.defined(image.top)) {
if (is.integer(image.top) && image.top >= 0) {
composite.top = image.top;
} else {
throw is.invalidParameterError('top', 'positive integer', image.top);
}
}
if (composite.left !== composite.top && Math.min(composite.left, composite.top) === -1) {
throw new Error('Expected both left and top to be set');
}
if (is.defined(image.gravity)) {
if (is.integer(image.gravity) && is.inRange(image.gravity, 0, 8)) {
composite.gravity = image.gravity;
} else if (is.string(image.gravity) && is.integer(this.constructor.gravity[image.gravity])) {
composite.gravity = this.constructor.gravity[image.gravity];
} else {
throw is.invalidParameterError('gravity', 'valid gravity', image.gravity);
}
}
return composite;
});
return this; return this;
}; }
/**
* @deprecated
* @private
*/
function overlayWith (input, options) {
const blend = (is.object(options) && options.cutout) ? 'dest-in' : 'over';
return this.composite([Object.assign({ input, blend }, options)]);
}
/** /**
* Decorate the Sharp prototype with composite-related functions. * Decorate the Sharp prototype with composite-related functions.
* @private * @private
*/ */
module.exports = function (Sharp) { module.exports = function (Sharp) {
Sharp.prototype.overlayWith = overlayWith; Sharp.prototype.composite = composite;
Sharp.prototype.overlayWith = deprecate(overlayWith, 'overlayWith(input, options) is deprecated, use composite([{ input, ...options }]) instead');
Sharp.blend = blend;
}; };

View File

@@ -4,32 +4,40 @@ const path = require('path');
const util = require('util'); const util = require('util');
const stream = require('stream'); const stream = require('stream');
const events = require('events'); const events = require('events');
const semver = require('semver'); const is = require('./is');
const sharp = require('../build/Release/sharp.node');
// Versioning require('./libvips').hasVendoredLibvips();
let versions = {
vips: sharp.libvipsVersion() let sharp;
}; try {
(function () { sharp = require('../build/Release/sharp.node');
// Does libvips meet minimum requirement? } catch (err) {
const libvipsVersionMin = require('../package.json').config.libvips; // Bail early if bindings aren't available
/* istanbul ignore if */ const help = ['', 'Something went wrong installing the "sharp" module', '', err.message, ''];
if (semver.lt(versions.vips, libvipsVersionMin)) { if (/NODE_MODULE_VERSION/.test(err.message)) {
throw new Error('Found libvips ' + versions.vips + ' but require at least ' + libvipsVersionMin); help.push('- Ensure the version of Node.js used at install time matches that used at runtime');
} else if (/invalid ELF header/.test(err.message)) {
help.push(`- Ensure "${process.platform}" is used at install time as well as runtime`);
} else {
help.push('- Remove the "node_modules/sharp" directory, run "npm install" and look for errors');
} }
// Include versions of dependencies, if present help.push(
try { '- Consult the installation documentation at https://sharp.pixelplumbing.com/en/stable/install/',
versions = require('../vendor/lib/versions.json'); '- Search for this error at https://github.com/lovell/sharp/issues', ''
} catch (err) {} );
})(); console.error(help.join('\n'));
process.exit(1);
}
// Use NODE_DEBUG=sharp to enable libvips warnings
const debuglog = util.debuglog('sharp');
/** /**
* @class 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 or WebP 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.
* *
* 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.
@@ -54,20 +62,46 @@ let versions = {
* }); * });
* readableStream.pipe(transformer).pipe(writableStream); * readableStream.pipe(transformer).pipe(writableStream);
* *
* @example
* // Create a blank 300x200 PNG image of semi-transluent red pixels
* sharp({
* create: {
* width: 300,
* height: 200,
* channels: 4,
* background: { r: 255, g: 0, b: 0, alpha: 0.5 }
* }
* })
* .png()
* .toBuffer()
* .then( ... );
*
* @param {(Buffer|String)} [input] - if present, can be * @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 path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file. * a String containing the path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
* JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when null or undefined. * 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 {Number} [options.density=72] - integral number representing the DPI for vector images. * @param {Boolean} [options.failOnError=true] - by default halt processing and raise an error when loading invalid images.
* @param {Object} [options.raw] - describes raw pixel image data. See `raw()` for pixel ordering. * Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid.
* @param {Number} [options.density=72] - number representing the DPI for vector images.
* @param {Number} [options.pages=1] - number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages.
* @param {Number} [options.page=0] - page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based.
* @param {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] * @param {Number} [options.raw.channels] - 1-4
* @param {Object} [options.create] - describes a new image to be created.
* @param {Number} [options.create.width]
* @param {Number} [options.create.height]
* @param {Number} [options.create.channels] - 3-4
* @param {String|Object} [options.create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const Sharp = function (input, options) { const Sharp = function (input, options) {
if (arguments.length === 1 && !is.defined(input)) {
throw new Error('Invalid input');
}
if (!(this instanceof Sharp)) { if (!(this instanceof Sharp)) {
return new Sharp(input, options); return new Sharp(input, options);
} }
@@ -75,7 +109,7 @@ const Sharp = function (input, options) {
this.options = { this.options = {
// input options // input options
sequentialRead: false, sequentialRead: false,
limitInputPixels: maximum.pixels, limitInputPixels: Math.pow(0x3FFF, 2),
// ICC profiles // ICC profiles
iccProfilePath: path.join(__dirname, 'icc') + path.sep, iccProfilePath: path.join(__dirname, 'icc') + path.sep,
// resize options // resize options
@@ -90,8 +124,12 @@ const Sharp = function (input, options) {
width: -1, width: -1,
height: -1, height: -1,
canvas: 'crop', canvas: 'crop',
crop: 0, position: 0,
resizeBackground: [0, 0, 0, 255],
useExifOrientation: false,
angle: 0, angle: 0,
rotationAngle: 0,
rotationBackground: [0, 0, 0, 255],
rotateBeforePreExtract: false, rotateBeforePreExtract: false,
flip: false, flip: false,
flop: false, flop: false,
@@ -99,41 +137,46 @@ const Sharp = function (input, options) {
extendBottom: 0, extendBottom: 0,
extendLeft: 0, extendLeft: 0,
extendRight: 0, extendRight: 0,
extendBackground: [0, 0, 0, 255],
withoutEnlargement: false, withoutEnlargement: false,
kernel: 'lanczos3', kernel: 'lanczos3',
interpolator: 'bicubic', fastShrinkOnLoad: true,
centreSampling: false,
// operations // operations
background: [0, 0, 0, 255], tintA: 128,
tintB: 128,
flatten: false, flatten: false,
flattenBackground: [0, 0, 0],
negate: false, negate: false,
medianSize: 0,
blurSigma: 0, blurSigma: 0,
sharpenSigma: 0, sharpenSigma: 0,
sharpenFlat: 1, sharpenFlat: 1,
sharpenJagged: 2, sharpenJagged: 2,
threshold: 0, threshold: 0,
thresholdGrayscale: true, thresholdGrayscale: true,
trimTolerance: 0, trimThreshold: 0,
gamma: 0, gamma: 0,
gammaOut: 0,
greyscale: false, greyscale: false,
normalise: 0, normalise: 0,
brightness: 1,
saturation: 1,
hue: 0,
booleanBufferIn: null, booleanBufferIn: null,
booleanFileIn: '', booleanFileIn: '',
joinChannelIn: [], joinChannelIn: [],
extractChannel: -1, extractChannel: -1,
removeAlpha: false,
ensureAlpha: false,
colourspace: 'srgb', colourspace: 'srgb',
// overlay composite: [],
overlayGravity: 0,
overlayXOffset: -1,
overlayYOffset: -1,
overlayTile: false,
overlayCutout: false,
// output // output
fileOut: '', fileOut: '',
formatOut: 'input', formatOut: 'input',
streamOut: false, streamOut: false,
withMetadata: false, withMetadata: false,
withMetadataOrientation: -1, withMetadataOrientation: -1,
resolveWithObject: false,
// output format // output format
jpegQuality: 80, jpegQuality: 80,
jpegProgressive: false, jpegProgressive: false,
@@ -141,16 +184,35 @@ const Sharp = function (input, options) {
jpegTrellisQuantisation: false, jpegTrellisQuantisation: false,
jpegOvershootDeringing: false, jpegOvershootDeringing: false,
jpegOptimiseScans: false, jpegOptimiseScans: false,
jpegOptimiseCoding: true,
jpegQuantisationTable: 0,
pngProgressive: false, pngProgressive: false,
pngCompressionLevel: 6, pngCompressionLevel: 9,
pngAdaptiveFiltering: true, pngAdaptiveFiltering: false,
pngPalette: false,
pngQuality: 100,
pngColours: 256,
pngDither: 1,
webpQuality: 80, webpQuality: 80,
webpAlphaQuality: 100, webpAlphaQuality: 100,
webpLossless: false, webpLossless: false,
webpNearLossless: false, webpNearLossless: false,
tiffQuality: 80, tiffQuality: 80,
tiffCompression: 'jpeg',
tiffPredictor: 'horizontal',
tiffPyramid: false,
tiffSquash: false,
tiffTile: false,
tiffTileHeight: 256,
tiffTileWidth: 256,
tiffXres: 1.0,
tiffYres: 1.0,
tileSize: 256, tileSize: 256,
tileOverlap: 0, tileOverlap: 0,
linearA: 1,
linearB: 0,
// Function to notify of libvips warnings
debuglog: debuglog,
// Function to notify of queue length changes // Function to notify of queue length changes
queueListener: function (queueLength) { queueListener: function (queueLength) {
queue.emit('change', queueLength); queue.emit('change', queueLength);
@@ -161,18 +223,6 @@ const Sharp = function (input, options) {
}; };
util.inherits(Sharp, stream.Duplex); util.inherits(Sharp, stream.Duplex);
/**
* Pixel limits.
* @member
* @private
*/
const maximum = {
width: 0x3FFF,
height: 0x3FFF,
pixels: Math.pow(0x3FFF, 2)
};
Sharp.maximum = maximum;
/** /**
* An EventEmitter that emits a `change` event when a task is either: * An EventEmitter that emits a `change` event when a task is either:
* - queued, waiting for _libuv_ to provide a worker thread * - queued, waiting for _libuv_ to provide a worker thread
@@ -189,7 +239,7 @@ Sharp.queue = queue;
/** /**
* An Object containing nested boolean values representing the available input and output formats/methods. * An Object containing nested boolean values representing the available input and output formats/methods.
* @example * @example
* console.log(sharp.format()); * console.log(sharp.format);
* @returns {Object} * @returns {Object}
*/ */
Sharp.format = sharp.format(); Sharp.format = sharp.format();
@@ -200,7 +250,12 @@ Sharp.format = sharp.format();
* @example * @example
* console.log(sharp.versions); * console.log(sharp.versions);
*/ */
Sharp.versions = versions; Sharp.versions = {
vips: sharp.libvipsVersion()
};
try {
Sharp.versions = require('../vendor/versions.json');
} catch (err) {}
/** /**
* Export constructor. * Export constructor.

View File

@@ -1,17 +1,13 @@
'use strict'; 'use strict';
const Sharp = require('./constructor'); const Sharp = require('./constructor');
[ require('./input')(Sharp);
'input', require('./resize')(Sharp);
'resize', require('./composite')(Sharp);
'composite', require('./operation')(Sharp);
'operation', require('./colour')(Sharp);
'colour', require('./channel')(Sharp);
'channel', require('./output')(Sharp);
'output', require('./utility')(Sharp);
'utility'
].forEach(function (decorator) {
require('./' + decorator)(Sharp);
});
module.exports = Sharp; module.exports = Sharp;

View File

@@ -1,6 +1,6 @@
'use strict'; 'use strict';
const util = require('util'); 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');
@@ -8,14 +8,21 @@ const sharp = require('../build/Release/sharp.node');
* Create Object containing input and input-related options. * Create Object containing input and input-related options.
* @private * @private
*/ */
const _createInputDescriptor = function _createInputDescriptor (input, inputOptions, containerOptions) { function _createInputDescriptor (input, inputOptions, containerOptions) {
const inputDescriptor = {}; const inputDescriptor = { failOnError: true };
if (is.string(input)) { if (is.string(input)) {
// filesystem // filesystem
inputDescriptor.file = input; inputDescriptor.file = input;
} else if (is.buffer(input)) { } else if (is.buffer(input)) {
// Buffer // Buffer
inputDescriptor.buffer = input; inputDescriptor.buffer = input;
} else if (is.plainObject(input) && !is.defined(inputOptions)) {
// Plain Object descriptor, e.g. create
inputOptions = input;
if (is.plainObject(inputOptions.raw)) {
// Raw Stream
inputDescriptor.buffer = [];
}
} else if (!is.defined(input) && is.object(containerOptions) && containerOptions.allowStream) { } else if (!is.defined(input) && is.object(containerOptions) && containerOptions.allowStream) {
// Stream // Stream
inputDescriptor.buffer = []; inputDescriptor.buffer = [];
@@ -23,9 +30,17 @@ const _createInputDescriptor = function _createInputDescriptor (input, inputOpti
throw new Error('Unsupported input ' + typeof input); throw new Error('Unsupported input ' + typeof input);
} }
if (is.object(inputOptions)) { if (is.object(inputOptions)) {
// Fail on error
if (is.defined(inputOptions.failOnError)) {
if (is.bool(inputOptions.failOnError)) {
inputDescriptor.failOnError = inputOptions.failOnError;
} else {
throw new Error('Invalid failOnError (boolean) ' + inputOptions.failOnError);
}
}
// Density // Density
if (is.defined(inputOptions.density)) { if (is.defined(inputOptions.density)) {
if (is.integer(inputOptions.density) && is.inRange(inputOptions.density, 1, 2400)) { if (is.inRange(inputOptions.density, 1, 2400)) {
inputDescriptor.density = inputOptions.density; inputDescriptor.density = inputOptions.density;
} else { } else {
throw new Error('Invalid density (1 to 2400) ' + inputOptions.density); throw new Error('Invalid density (1 to 2400) ' + inputOptions.density);
@@ -35,8 +50,8 @@ const _createInputDescriptor = function _createInputDescriptor (input, inputOpti
if (is.defined(inputOptions.raw)) { if (is.defined(inputOptions.raw)) {
if ( if (
is.object(inputOptions.raw) && is.object(inputOptions.raw) &&
is.integer(inputOptions.raw.width) && is.inRange(inputOptions.raw.width, 1, this.constructor.maximum.width) && is.integer(inputOptions.raw.width) && inputOptions.raw.width > 0 &&
is.integer(inputOptions.raw.height) && is.inRange(inputOptions.raw.height, 1, this.constructor.maximum.height) && is.integer(inputOptions.raw.height) && inputOptions.raw.height > 0 &&
is.integer(inputOptions.raw.channels) && is.inRange(inputOptions.raw.channels, 1, 4) is.integer(inputOptions.raw.channels) && is.inRange(inputOptions.raw.channels, 1, 4)
) { ) {
inputDescriptor.rawWidth = inputOptions.raw.width; inputDescriptor.rawWidth = inputOptions.raw.width;
@@ -46,11 +61,46 @@ const _createInputDescriptor = function _createInputDescriptor (input, inputOpti
throw new Error('Expected width, height and channels for raw pixel input'); throw new Error('Expected width, height and channels for raw pixel input');
} }
} }
// Multi-page input (GIF, TIFF, PDF)
if (is.defined(inputOptions.pages)) {
if (is.integer(inputOptions.pages) && is.inRange(inputOptions.pages, -1, 100000)) {
inputDescriptor.pages = inputOptions.pages;
}
}
if (is.defined(inputOptions.page)) {
if (is.integer(inputOptions.page) && is.inRange(inputOptions.page, 0, 100000)) {
inputDescriptor.page = inputOptions.page;
}
}
// Create new image
if (is.defined(inputOptions.create)) {
if (
is.object(inputOptions.create) &&
is.integer(inputOptions.create.width) && inputOptions.create.width > 0 &&
is.integer(inputOptions.create.height) && inputOptions.create.height > 0 &&
is.integer(inputOptions.create.channels) && is.inRange(inputOptions.create.channels, 3, 4) &&
is.defined(inputOptions.create.background)
) {
inputDescriptor.createWidth = inputOptions.create.width;
inputDescriptor.createHeight = inputOptions.create.height;
inputDescriptor.createChannels = inputOptions.create.channels;
const background = color(inputOptions.create.background);
inputDescriptor.createBackground = [
background.red(),
background.green(),
background.blue(),
Math.round(background.alpha() * 255)
];
delete inputDescriptor.buffer;
} else {
throw new Error('Expected width, height, channels and background to create a new input image');
}
}
} else if (is.defined(inputOptions)) { } else if (is.defined(inputOptions)) {
throw new Error('Invalid input options ' + inputOptions); throw new Error('Invalid input options ' + inputOptions);
} }
return inputDescriptor; return inputDescriptor;
}; }
/** /**
* Handle incoming Buffer chunk on Writable Stream. * Handle incoming Buffer chunk on Writable Stream.
@@ -59,7 +109,7 @@ const _createInputDescriptor = function _createInputDescriptor (input, inputOpti
* @param {String} encoding - unused * @param {String} encoding - unused
* @param {Function} callback * @param {Function} callback
*/ */
const _write = function _write (chunk, encoding, callback) { function _write (chunk, encoding, callback) {
/* istanbul ignore else */ /* istanbul ignore else */
if (Array.isArray(this.options.input.buffer)) { if (Array.isArray(this.options.input.buffer)) {
/* istanbul ignore else */ /* istanbul ignore else */
@@ -78,26 +128,26 @@ const _write = function _write (chunk, encoding, callback) {
} else { } else {
callback(new Error('Unexpected data on Writable Stream')); callback(new Error('Unexpected data on Writable Stream'));
} }
}; }
/** /**
* Flattens the array of chunks accumulated in input.buffer. * Flattens the array of chunks accumulated in input.buffer.
* @private * @private
*/ */
const _flattenBufferIn = function _flattenBufferIn () { function _flattenBufferIn () {
if (this._isStreamInput()) { if (this._isStreamInput()) {
this.options.input.buffer = Buffer.concat(this.options.input.buffer); this.options.input.buffer = Buffer.concat(this.options.input.buffer);
} }
}; }
/** /**
* Are we expecting Stream-based input? * Are we expecting Stream-based input?
* @private * @private
* @returns {Boolean} * @returns {Boolean}
*/ */
const _isStreamInput = 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. * Take a "snapshot" of the Sharp instance, returning a new instance.
@@ -114,36 +164,46 @@ const _isStreamInput = function _isStreamInput () {
* *
* @returns {Sharp} * @returns {Sharp}
*/ */
const clone = function clone () { function clone () {
const that = this; const that = this;
// Clone existing options // Clone existing options
const clone = this.constructor.call(); const clone = this.constructor.call();
util._extend(clone.options, this.options); clone.options = Object.assign({}, this.options);
// Pass 'finish' event to clone for Stream-based input // Pass 'finish' event to clone for Stream-based input
this.on('finish', function () { if (this._isStreamInput()) {
// Clone inherits input data this.on('finish', function () {
that._flattenBufferIn(); // Clone inherits input data
clone.options.bufferIn = that.options.bufferIn; that._flattenBufferIn();
clone.emit('finish'); clone.options.bufferIn = that.options.bufferIn;
}); clone.emit('finish');
});
}
return clone; return clone;
}; }
/** /**
* Fast access to image metadata without decoding any compressed image data. * Fast access to (uncached) image metadata without decoding any compressed image data.
* A Promises/A+ promise is returned when `callback` is not provided. * A `Promise` is returned when `callback` is not provided.
* *
* - `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg` * - `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
* - `width`: Number of pixels wide * - `size`: Total size of image in bytes, for Stream and Buffer input only
* - `height`: Number of pixels high * - `width`: Number of pixels wide (EXIF orientation is not taken into consideration)
* - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568) * - `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)
* - `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)
* - `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
* - `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, PDF, animated GIF and animated WebP
* - `pageHeight`: Number of pixels high each page in this PDF image will be.
* - `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
* - `exif`: Buffer containing raw EXIF data, if present * - `exif`: Buffer containing raw EXIF data, if present
* - `icc`: Buffer containing raw [ICC](https://www.npmjs.com/package/icc) profile data, if present * - `icc`: Buffer containing raw [ICC](https://www.npmjs.com/package/icc) profile data, if present
* - `iptc`: Buffer containing raw IPTC data, if present
* - `xmp`: Buffer containing raw XMP data, if present
* *
* @example * @example
* const image = sharp(inputJpg); * const image = sharp(inputJpg);
@@ -162,7 +222,7 @@ const clone = function clone () {
* @param {Function} [callback] - called with the arguments `(err, metadata)` * @param {Function} [callback] - called with the arguments `(err, metadata)`
* @returns {Promise<Object>|Sharp} * @returns {Promise<Object>|Sharp}
*/ */
const metadata = function metadata (callback) { function metadata (callback) {
const that = this; const that = this;
if (is.fn(callback)) { if (is.fn(callback)) {
if (this._isStreamInput()) { if (this._isStreamInput()) {
@@ -200,48 +260,120 @@ const metadata = function metadata (callback) {
}); });
} }
} }
}; }
/** /**
* Do not process input images where the number of pixels (width * height) exceeds this limit. * Access to pixel-derived image statistics for every channel in the image.
* A `Promise` is returned when `callback` is not provided.
*
* - `channels`: Array of channel statistics for each channel in the image. Each channel statistic contains
* - `min` (minimum value in the channel)
* - `max` (maximum value in the channel)
* - `sum` (sum of all values in a channel)
* - `squaresSum` (sum of squared values in a channel)
* - `mean` (mean of the values in a channel)
* - `stdev` (standard deviation for the values in a channel)
* - `minX` (x-coordinate of one of the pixel where the minimum lies)
* - `minY` (y-coordinate of one of the pixel where the minimum lies)
* - `maxX` (x-coordinate of one of the pixel where the maximum lies)
* - `maxY` (y-coordinate of one of the pixel where the maximum lies)
* - `isOpaque`: Value to identify if the image is opaque or transparent, based on the presence and use of alpha channel
* - `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental)
*
* @example
* const image = sharp(inputJpg);
* image
* .stats()
* .then(function(stats) {
* // stats contains the channel-wise statistics array and the isOpaque value
* });
*
* @param {Function} [callback] - called with the arguments `(err, stats)`
* @returns {Promise<Object>}
*/
function stats (callback) {
const that = this;
if (is.fn(callback)) {
if (this._isStreamInput()) {
this.on('finish', function () {
that._flattenBufferIn();
sharp.stats(that.options, callback);
});
} else {
sharp.stats(this.options, callback);
}
return this;
} else {
if (this._isStreamInput()) {
return new Promise(function (resolve, reject) {
that.on('finish', function () {
that._flattenBufferIn();
sharp.stats(that.options, function (err, stats) {
if (err) {
reject(err);
} else {
resolve(stats);
}
});
});
});
} else {
return new Promise(function (resolve, reject) {
sharp.stats(that.options, function (err, stats) {
if (err) {
reject(err);
} else {
resolve(stats);
}
});
});
}
}
}
/**
* 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. * Assumes image dimensions contained in the input metadata can be trusted.
* The default limit is 268402689 (0x3FFF * 0x3FFF) pixels. * 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. * @param {(Number|Boolean)} limit - an integral Number of pixels, zero or false to remove limit, true to use default limit.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid limit * @throws {Error} Invalid limit
*/ */
const limitInputPixels = function limitInputPixels (limit) { function limitInputPixels (limit) {
// if we pass in false we represent the integer as 0 to disable // if we pass in false we represent the integer as 0 to disable
if (limit === false) { if (limit === false) {
limit = 0; limit = 0;
} else if (limit === true) { } else if (limit === true) {
limit = this.constructor.maximum.pixels; limit = Math.pow(0x3FFF, 2);
} }
if (is.integer(limit) && limit >= 0) { if (is.integer(limit) && limit >= 0) {
this.options.limitInputPixels = limit; this.options.limitInputPixels = limit;
} else { } else {
throw new Error('Invalid pixel limit (0 to ' + this.constructor.maximum.pixels + ') ' + limit); throw is.invalidParameterError('limitInputPixels', 'integer', limit);
} }
return this; return this;
}; }
/** /**
* An advanced setting that switches the libvips access method to `VIPS_ACCESS_SEQUENTIAL`. * 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. * 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] * @param {Boolean} [sequentialRead=true]
* @returns {Sharp} * @returns {Sharp}
*/ */
const sequentialRead = function sequentialRead (sequentialRead) { function sequentialRead (sequentialRead) {
this.options.sequentialRead = is.bool(sequentialRead) ? sequentialRead : true; this.options.sequentialRead = is.bool(sequentialRead) ? sequentialRead : true;
return this; return this;
}; }
/** /**
* Decorate the Sharp prototype with input-related functions. * Decorate the Sharp prototype with input-related functions.
* @private * @private
*/ */
module.exports = function (Sharp) { module.exports = function (Sharp) {
[ Object.assign(Sharp.prototype, {
// Private // Private
_createInputDescriptor, _createInputDescriptor,
_write, _write,
@@ -250,9 +382,8 @@ module.exports = function (Sharp) {
// Public // Public
clone, clone,
metadata, metadata,
stats,
limitInputPixels, limitInputPixels,
sequentialRead sequentialRead
].forEach(function (f) {
Sharp.prototype[f.name] = f;
}); });
}; };

View File

@@ -16,6 +16,14 @@ const object = function (val) {
return typeof val === 'object'; return typeof val === 'object';
}; };
/**
* Is this value a plain object?
* @private
*/
const plainObject = function (val) {
return object(val) && Object.prototype.toString.call(val) === '[object Object]';
};
/** /**
* Is this value a function? * Is this value a function?
* @private * @private
@@ -98,6 +106,7 @@ const invalidParameterError = function (name, expected, actual) {
module.exports = { module.exports = {
defined: defined, defined: defined,
object: object, object: object,
plainObject: plainObject,
fn: fn, fn: fn,
bool: bool, bool: bool,
buffer: buffer, buffer: buffer,

95
lib/libvips.js Normal file
View File

@@ -0,0 +1,95 @@
'use strict';
const fs = require('fs');
const os = require('os');
const path = require('path');
const spawnSync = require('child_process').spawnSync;
const semver = require('semver');
const platform = require('./platform');
const env = process.env;
const minimumLibvipsVersion = env.npm_package_config_libvips || require('../package.json').config.libvips;
const spawnSyncOptions = {
encoding: 'utf8',
shell: true
};
const mkdirSync = function (dirPath) {
try {
fs.mkdirSync(dirPath);
} catch (err) {
if (err.code !== 'EEXIST') {
throw err;
}
}
};
const cachePath = function () {
const npmCachePath = env.npm_config_cache || (env.APPDATA ? path.join(env.APPDATA, 'npm-cache') : path.join(os.homedir(), '.npm'));
mkdirSync(npmCachePath);
const libvipsCachePath = path.join(npmCachePath, '_libvips');
mkdirSync(libvipsCachePath);
return libvipsCachePath;
};
const globalLibvipsVersion = function () {
if (process.platform !== 'win32') {
const globalLibvipsVersion = spawnSync(`PKG_CONFIG_PATH="${pkgConfigPath()}" pkg-config --modversion vips-cpp`, spawnSyncOptions).stdout || '';
return globalLibvipsVersion.trim();
} else {
return '';
}
};
const hasVendoredLibvips = function () {
const currentPlatformId = platform();
const vendorPath = path.join(__dirname, '..', 'vendor');
let vendorVersionId;
let vendorPlatformId;
try {
vendorVersionId = require(path.join(vendorPath, 'versions.json')).vips;
vendorPlatformId = require(path.join(vendorPath, 'platform.json'));
} catch (err) {}
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'.`);
}
if (vendorPlatformId) {
if (currentPlatformId === vendorPlatformId) {
return true;
} else {
throw new Error(`'${vendorPlatformId}' binaries cannot be used on the '${currentPlatformId}' platform. Please remove the 'node_modules/sharp/vendor' directory and run 'npm install'.`);
}
}
return false;
};
const pkgConfigPath = function () {
if (process.platform !== 'win32') {
const brewPkgConfigPath = spawnSync('which brew >/dev/null 2>&1 && eval $(brew --env) && echo $PKG_CONFIG_LIBDIR', spawnSyncOptions).stdout || '';
return [brewPkgConfigPath.trim(), env.PKG_CONFIG_PATH, '/usr/local/lib/pkgconfig', '/usr/lib/pkgconfig']
.filter(function (p) { return !!p; })
.join(':');
} else {
return '';
}
};
const useGlobalLibvips = function () {
if (Boolean(env.SHARP_IGNORE_GLOBAL_LIBVIPS) === true) {
return false;
}
const globalVipsVersion = globalLibvipsVersion();
return !!globalVipsVersion && semver.gte(globalVipsVersion, minimumLibvipsVersion);
};
module.exports = {
minimumLibvipsVersion: minimumLibvipsVersion,
cachePath: cachePath,
globalLibvipsVersion: globalLibvipsVersion,
hasVendoredLibvips: hasVendoredLibvips,
pkgConfigPath: pkgConfigPath,
useGlobalLibvips: useGlobalLibvips,
mkdirSync: mkdirSync
};

View File

@@ -1,12 +1,19 @@
'use strict'; 'use strict';
const color = require('color');
const is = require('./is'); const is = require('./is');
/** /**
* Rotate the output image by either an explicit angle * Rotate the output image by either an explicit angle
* or auto-orient based on the EXIF `Orientation` tag. * or auto-orient based on the EXIF `Orientation` tag.
* *
* Use this method without angle to determine the angle from EXIF data. * If an angle is provided, it is converted to a valid positive degree rotation.
* For example, `-450` will produce a 270deg rotation.
*
* When rotating by an angle other than a multiple of 90,
* the background colour can be provided with the `background` option.
*
* If no angle is provided, it is determined from the EXIF data.
* Mirroring is supported and may infer the use of a flip operation. * Mirroring is supported and may infer the use of a flip operation.
* *
* The use of `rotate` implies the removal of the EXIF `Orientation` tag, if any. * The use of `rotate` implies the removal of the EXIF `Orientation` tag, if any.
@@ -25,67 +32,33 @@ const is = require('./is');
* }); * });
* readableStream.pipe(pipeline); * readableStream.pipe(pipeline);
* *
* @param {Number} [angle=auto] 0, 90, 180 or 270. * @param {Number} [angle=auto] angle of rotation.
* @param {Object} [options] - if present, is an Object with optional attributes.
* @param {String|Object} [options.background="#000000"] parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const rotate = function rotate (angle) { function rotate (angle, options) {
if (!is.defined(angle)) { if (!is.defined(angle)) {
this.options.angle = -1; this.options.useExifOrientation = true;
} else if (is.integer(angle) && is.inArray(angle, [0, 90, 180, 270])) { } else if (is.integer(angle) && !(angle % 90)) {
this.options.angle = angle; this.options.angle = angle;
} else { } else if (is.number(angle)) {
throw new Error('Unsupported angle (0, 90, 180, 270) ' + angle); this.options.rotationAngle = angle;
} if (is.object(options) && options.background) {
return this; const backgroundColour = color(options.background);
}; this.options.rotationBackground = [
backgroundColour.red(),
/** backgroundColour.green(),
* Extract a region of the image. backgroundColour.blue(),
* Math.round(backgroundColour.alpha() * 255)
* - Use `extract` before `resize` for pre-resize extraction. ];
* - Use `extract` after `resize` for post-resize extraction.
* - Use `extract` before and after for both.
*
* @example
* sharp(input)
* .extract({ left: left, top: top, width: width, height: height })
* .toFile(output, function(err) {
* // Extract a region of the input image, saving in the same format.
* });
* @example
* sharp(input)
* .extract({ left: leftOffsetPre, top: topOffsetPre, width: widthPre, height: heightPre })
* .resize(width, height)
* .extract({ left: leftOffsetPost, top: topOffsetPost, width: widthPost, height: heightPost })
* .toFile(output, function(err) {
* // Extract a region, resize, then extract from the resized image
* });
*
* @param {Object} options
* @param {Number} options.left - zero-indexed offset from left edge
* @param {Number} options.top - zero-indexed offset from top edge
* @param {Number} options.width - dimension of extracted image
* @param {Number} options.height - dimension of extracted image
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
const extract = function extract (options) {
const suffix = this.options.width === -1 && this.options.height === -1 ? 'Pre' : 'Post';
['left', 'top', 'width', 'height'].forEach(function (name) {
const value = options[name];
if (is.integer(value) && value >= 0) {
this.options[name + (name === 'left' || name === 'top' ? 'Offset' : '') + suffix] = value;
} else {
throw new Error('Non-integer value for ' + name + ' of ' + value);
} }
}, this); } else {
// Ensure existing rotation occurs before pre-resize extraction throw new Error('Unsupported angle: must be a number.');
if (suffix === 'Pre' && this.options.angle !== 0) {
this.options.rotateBeforePreExtract = true;
} }
return this; return this;
}; }
/** /**
* Flip the image about the vertical Y axis. This always occurs after rotation, if any. * Flip the image about the vertical Y axis. This always occurs after rotation, if any.
@@ -93,10 +66,10 @@ const extract = function extract (options) {
* @param {Boolean} [flip=true] * @param {Boolean} [flip=true]
* @returns {Sharp} * @returns {Sharp}
*/ */
const flip = function flip (flip) { function flip (flip) {
this.options.flip = is.bool(flip) ? flip : true; this.options.flip = is.bool(flip) ? flip : true;
return this; return this;
}; }
/** /**
* Flop the image about the horizontal X axis. This always occurs after rotation, if any. * Flop the image about the horizontal X axis. This always occurs after rotation, if any.
@@ -104,10 +77,10 @@ const flip = function flip (flip) {
* @param {Boolean} [flop=true] * @param {Boolean} [flop=true]
* @returns {Sharp} * @returns {Sharp}
*/ */
const flop = function flop (flop) { function flop (flop) {
this.options.flop = is.bool(flop) ? flop : true; this.options.flop = is.bool(flop) ? flop : true;
return this; return this;
}; }
/** /**
* Sharpen the image. * Sharpen the image.
@@ -121,7 +94,7 @@ const flop = function flop (flop) {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const sharpen = function sharpen (sigma, flat, jagged) { function sharpen (sigma, flat, jagged) {
if (!is.defined(sigma)) { if (!is.defined(sigma)) {
// No arguments: default to mild sharpen // No arguments: default to mild sharpen
this.options.sharpenSigma = -1; this.options.sharpenSigma = -1;
@@ -151,7 +124,27 @@ const sharpen = function sharpen (sigma, flat, jagged) {
throw new Error('Invalid sharpen sigma (0.01 - 10000) ' + sigma); throw new Error('Invalid sharpen sigma (0.01 - 10000) ' + sigma);
} }
return this; return this;
}; }
/**
* Apply median filter.
* When used without parameters the default window is 3x3.
* @param {Number} [size=3] square mask size: size x size
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
function median (size) {
if (!is.defined(size)) {
// No arguments: default to 3x3
this.options.medianSize = 3;
} else if (is.integer(size) && is.inRange(size, 1, 1000)) {
// Numeric argument: specific sigma
this.options.medianSize = size;
} else {
throw new Error('Invalid median size ' + size);
}
return this;
}
/** /**
* Blur the image. * Blur the image.
@@ -161,7 +154,7 @@ const sharpen = function sharpen (sigma, flat, jagged) {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const blur = function blur (sigma) { function blur (sigma) {
if (!is.defined(sigma)) { if (!is.defined(sigma)) {
// No arguments: default to mild blur // No arguments: default to mild blur
this.options.blurSigma = -1; this.options.blurSigma = -1;
@@ -175,78 +168,21 @@ const blur = function blur (sigma) {
throw new Error('Invalid blur sigma (0.3 - 1000.0) ' + sigma); throw new Error('Invalid blur sigma (0.3 - 1000.0) ' + sigma);
} }
return this; return this;
}; }
/** /**
* Extends/pads the edges of the image with the colour provided to the `background` method. * Merge alpha transparency channel, if any, with a background.
* This operation will always occur after resizing and extraction, if any. * @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.
* @example
* // Resize to 140 pixels wide, then add 10 transparent pixels
* // to the top, left and right edges and 20 to the bottom edge
* sharp(input)
* .resize(140)
* .background({r: 0, g: 0, b: 0, alpha: 0})
* .extend({top: 10, bottom: 20, left: 10, right: 10})
* ...
*
* @param {(Number|Object)} extend - single pixel count to add to all edges or an Object with per-edge counts
* @param {Number} [extend.top]
* @param {Number} [extend.left]
* @param {Number} [extend.bottom]
* @param {Number} [extend.right]
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
const extend = function extend (extend) {
if (is.integer(extend) && extend > 0) {
this.options.extendTop = extend;
this.options.extendBottom = extend;
this.options.extendLeft = extend;
this.options.extendRight = extend;
} else if (
is.object(extend) &&
is.integer(extend.top) && extend.top >= 0 &&
is.integer(extend.bottom) && extend.bottom >= 0 &&
is.integer(extend.left) && extend.left >= 0 &&
is.integer(extend.right) && extend.right >= 0
) {
this.options.extendTop = extend.top;
this.options.extendBottom = extend.bottom;
this.options.extendLeft = extend.left;
this.options.extendRight = extend.right;
} else {
throw new Error('Invalid edge extension ' + extend);
}
return this;
};
/**
* Merge alpha transparency channel, if any, with `background`.
* @param {Boolean} [flatten=true]
* @returns {Sharp} * @returns {Sharp}
*/ */
const flatten = function flatten (flatten) { function flatten (options) {
this.options.flatten = is.bool(flatten) ? flatten : true; this.options.flatten = is.bool(options) ? options : true;
return this; if (is.object(options)) {
}; this._setColourOption('flattenBackground', options.background);
/**
* Trim "boring" pixels from all edges that contain values within a percentage similarity of the top-left pixel.
* @param {Number} [tolerance=10] value between 1 and 99 representing the percentage similarity.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
const trim = function trim (tolerance) {
if (!is.defined(tolerance)) {
this.options.trimTolerance = 10;
} else if (is.integer(tolerance) && is.inRange(tolerance, 1, 99)) {
this.options.trimTolerance = tolerance;
} else {
throw new Error('Invalid trim tolerance (1 to 99) ' + tolerance);
} }
return this; return this;
}; }
/** /**
* Apply a gamma correction by reducing the encoding (darken) pre-resize at a factor of `1/gamma` * Apply a gamma correction by reducing the encoding (darken) pre-resize at a factor of `1/gamma`
@@ -254,11 +190,15 @@ const trim = function trim (tolerance) {
* This can improve the perceived brightness of a resized image in non-linear colour spaces. * This can improve the perceived brightness of a resized image in non-linear colour spaces.
* JPEG and WebP input images will not take advantage of the shrink-on-load performance optimisation * JPEG and WebP input images will not take advantage of the shrink-on-load performance optimisation
* when applying a gamma correction. * when applying a gamma correction.
*
* Supply a second argument to use a different output gamma value, otherwise the first value is used in both cases.
*
* @param {Number} [gamma=2.2] value between 1.0 and 3.0. * @param {Number} [gamma=2.2] value between 1.0 and 3.0.
* @param {Number} [gammaOut] value between 1.0 and 3.0. (optional, defaults to same as `gamma`)
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const gamma = function gamma (gamma) { function gamma (gamma, gammaOut) {
if (!is.defined(gamma)) { if (!is.defined(gamma)) {
// Default gamma correction of 2.2 (sRGB) // Default gamma correction of 2.2 (sRGB)
this.options.gamma = 2.2; this.options.gamma = 2.2;
@@ -267,37 +207,45 @@ const gamma = function gamma (gamma) {
} else { } else {
throw new Error('Invalid gamma correction (1.0 to 3.0) ' + gamma); throw new Error('Invalid gamma correction (1.0 to 3.0) ' + gamma);
} }
if (!is.defined(gammaOut)) {
// Default gamma correction for output is same as input
this.options.gammaOut = this.options.gamma;
} else if (is.number(gammaOut) && is.inRange(gammaOut, 1, 3)) {
this.options.gammaOut = gammaOut;
} else {
throw new Error('Invalid output gamma correction (1.0 to 3.0) ' + gammaOut);
}
return this; return this;
}; }
/** /**
* Produce the "negative" of the image. * Produce the "negative" of the image.
* @param {Boolean} [negate=true] * @param {Boolean} [negate=true]
* @returns {Sharp} * @returns {Sharp}
*/ */
const negate = function negate (negate) { function negate (negate) {
this.options.negate = is.bool(negate) ? negate : true; this.options.negate = is.bool(negate) ? negate : true;
return this; return this;
}; }
/** /**
* Enhance output image contrast by stretching its luminance to cover the full dynamic range. * Enhance output image contrast by stretching its luminance to cover the full dynamic range.
* @param {Boolean} [normalise=true] * @param {Boolean} [normalise=true]
* @returns {Sharp} * @returns {Sharp}
*/ */
const normalise = function normalise (normalise) { function normalise (normalise) {
this.options.normalise = is.bool(normalise) ? normalise : true; this.options.normalise = is.bool(normalise) ? normalise : true;
return this; return this;
}; }
/** /**
* Alternative spelling of normalise. * Alternative spelling of normalise.
* @param {Boolean} [normalize=true] * @param {Boolean} [normalize=true]
* @returns {Sharp} * @returns {Sharp}
*/ */
const normalize = function normalize (normalize) { function normalize (normalize) {
return this.normalise(normalize); return this.normalise(normalize);
}; }
/** /**
* Convolve the image with the specified kernel. * Convolve the image with the specified kernel.
@@ -324,12 +272,12 @@ const normalize = function normalize (normalize) {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const convolve = function convolve (kernel) { function convolve (kernel) {
if (!is.object(kernel) || !Array.isArray(kernel.kernel) || if (!is.object(kernel) || !Array.isArray(kernel.kernel) ||
!is.integer(kernel.width) || !is.integer(kernel.height) || !is.integer(kernel.width) || !is.integer(kernel.height) ||
!is.inRange(kernel.width, 3, 1001) || !is.inRange(kernel.height, 3, 1001) || !is.inRange(kernel.width, 3, 1001) || !is.inRange(kernel.height, 3, 1001) ||
kernel.height * kernel.width !== kernel.kernel.length kernel.height * kernel.width !== kernel.kernel.length
) { ) {
// must pass in a kernel // must pass in a kernel
throw new Error('Invalid convolution kernel'); throw new Error('Invalid convolution kernel');
} }
@@ -348,7 +296,7 @@ const convolve = function convolve (kernel) {
} }
this.options.convKernel = kernel; this.options.convKernel = kernel;
return this; return this;
}; }
/** /**
* Any pixel value greather than or equal to the threshold value will be set to 255, otherwise it will be set to 0. * Any pixel value greather than or equal to the threshold value will be set to 255, otherwise it will be set to 0.
@@ -359,7 +307,7 @@ const convolve = function convolve (kernel) {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const threshold = function threshold (threshold, options) { function threshold (threshold, options) {
if (!is.defined(threshold)) { if (!is.defined(threshold)) {
this.options.threshold = 128; this.options.threshold = 128;
} else if (is.bool(threshold)) { } else if (is.bool(threshold)) {
@@ -375,7 +323,7 @@ const threshold = function threshold (threshold, options) {
this.options.thresholdGrayscale = false; this.options.thresholdGrayscale = false;
} }
return this; return this;
}; }
/** /**
* Perform a bitwise boolean operation with operand image. * Perform a bitwise boolean operation with operand image.
@@ -393,7 +341,7 @@ const threshold = function threshold (threshold, options) {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const boolean = function boolean (operand, operator, options) { function boolean (operand, operator, options) {
this.options.boolean = this._createInputDescriptor(operand, options); this.options.boolean = this._createInputDescriptor(operand, options);
if (is.string(operator) && is.inArray(operator, ['and', 'or', 'eor'])) { if (is.string(operator) && is.inArray(operator, ['and', 'or', 'eor'])) {
this.options.booleanOp = operator; this.options.booleanOp = operator;
@@ -401,31 +349,150 @@ const boolean = function boolean (operand, operator, options) {
throw new Error('Invalid boolean operator ' + operator); throw new Error('Invalid boolean operator ' + operator);
} }
return this; return this;
}; }
/**
* Apply the linear formula a * input + b to the image (levels adjustment)
* @param {Number} [a=1.0] multiplier
* @param {Number} [b=0.0] offset
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
function linear (a, b) {
if (!is.defined(a)) {
this.options.linearA = 1.0;
} else if (is.number(a)) {
this.options.linearA = a;
} else {
throw new Error('Invalid linear transform multiplier ' + a);
}
if (!is.defined(b)) {
this.options.linearB = 0.0;
} else if (is.number(b)) {
this.options.linearB = b;
} else {
throw new Error('Invalid linear transform offset ' + b);
}
return this;
}
/**
* Recomb the image with the specified matrix.
*
* @example
* sharp(input)
* .recomb([
* [0.3588, 0.7044, 0.1368],
* [0.2990, 0.5870, 0.1140],
* [0.2392, 0.4696, 0.0912],
* ])
* .raw()
* .toBuffer(function(err, data, info) {
* // data contains the raw pixel data after applying the recomb
* // With this example input, a sepia filter has been applied
* });
*
* @param {Array<Array<Number>>} 3x3 Recombination matrix
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
function recomb (inputMatrix) {
if (!Array.isArray(inputMatrix) || inputMatrix.length !== 3 ||
inputMatrix[0].length !== 3 ||
inputMatrix[1].length !== 3 ||
inputMatrix[2].length !== 3
) {
// must pass in a kernel
throw new Error('Invalid Recomb Matrix');
}
this.options.recombMatrix = [
inputMatrix[0][0], inputMatrix[0][1], inputMatrix[0][2],
inputMatrix[1][0], inputMatrix[1][1], inputMatrix[1][2],
inputMatrix[2][0], inputMatrix[2][1], inputMatrix[2][2]
].map(Number);
return this;
}
/**
* Transforms the image using brightness, saturation and hue rotation.
*
* @example
* sharp(input)
* .modulate({
* brightness: 2 // increase lightness by a factor of 2
* });
*
* sharp(input)
* .modulate({
* hue: 180 // hue-rotate by 180 degrees
* });
*
* // decreate brightness and saturation while also hue-rotating by 90 degrees
* sharp(input)
* .modulate({
* brightness: 0.5,
* saturation: 0.5,
* hue: 90
* });
*
* @param {Object} [options]
* @param {Number} [options.brightness] Brightness multiplier
* @param {Number} [options.saturation] Saturation multiplier
* @param {Number} [options.hue] Degrees for hue rotation
* @returns {Sharp}
*/
function modulate (options) {
if (!is.plainObject(options)) {
throw is.invalidParameterError('options', 'plain object', options);
}
if ('brightness' in options) {
if (is.number(options.brightness) && options.brightness >= 0) {
this.options.brightness = options.brightness;
} else {
throw is.invalidParameterError('brightness', 'number above zero', options.brightness);
}
}
if ('saturation' in options) {
if (is.number(options.saturation) && options.saturation >= 0) {
this.options.saturation = options.saturation;
} else {
throw is.invalidParameterError('saturation', 'number above zero', options.saturation);
}
}
if ('hue' in options) {
if (is.integer(options.hue)) {
this.options.hue = options.hue % 360;
} else {
throw is.invalidParameterError('hue', 'number', options.hue);
}
}
return this;
}
/** /**
* Decorate the Sharp prototype with operation-related functions. * Decorate the Sharp prototype with operation-related functions.
* @private * @private
*/ */
module.exports = function (Sharp) { module.exports = function (Sharp) {
[ Object.assign(Sharp.prototype, {
rotate, rotate,
extract,
flip, flip,
flop, flop,
sharpen, sharpen,
median,
blur, blur,
extend,
flatten, flatten,
trim,
gamma, gamma,
negate, negate,
normalise, normalise,
normalize, normalize,
convolve, convolve,
threshold, threshold,
boolean boolean,
].forEach(function (f) { linear,
Sharp.prototype[f.name] = f; recomb,
modulate
}); });
}; };

View File

@@ -1,6 +1,5 @@
'use strict'; 'use strict';
const util = require('util');
const is = require('./is'); const is = require('./is');
const sharp = require('../build/Release/sharp.node'); const sharp = require('../build/Release/sharp.node');
@@ -11,17 +10,29 @@ const sharp = require('../build/Release/sharp.node');
* with JPEG, PNG, WebP, TIFF, DZI, and libvips' V format supported. * with JPEG, PNG, WebP, TIFF, DZI, and libvips' V format supported.
* Note that raw pixel data is only supported for buffer output. * Note that raw pixel data is only supported for buffer output.
* *
* A Promises/A+ promise is returned when `callback` is not provided. * A `Promise` is returned when `callback` is not provided.
*
* @example
* sharp(input)
* .toFile('output.png', (err, info) => { ... });
*
* @example
* sharp(input)
* .toFile('output.png')
* .then(info => { ... })
* .catch(err => { ... });
* *
* @param {String} fileOut - the path to write the image data to. * @param {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` and `channels`. * `info` contains the output image `format`, `size` (bytes), `width`, `height`,
* `channels` and `premultiplied` (indicating if premultiplication was used).
* When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
* @returns {Promise<Object>} - when no callback is provided * @returns {Promise<Object>} - when no callback is provided
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const toFile = function toFile (fileOut, callback) { function toFile (fileOut, callback) {
if (!fileOut || fileOut.length === 0) { if (!fileOut || fileOut.length === 0) {
const errOutputInvalid = new Error('Invalid output'); const errOutputInvalid = new Error('Missing output file path');
if (is.fn(callback)) { if (is.fn(callback)) {
callback(errOutputInvalid); callback(errOutputInvalid);
} else { } else {
@@ -41,36 +52,69 @@ const toFile = function toFile (fileOut, callback) {
} }
} }
return this; return this;
}; }
/** /**
* Write output to a Buffer. * Write output to a Buffer.
* JPEG, PNG, WebP, and RAW output are supported. * JPEG, PNG, WebP, TIFF and RAW output are supported.
* By default, the format will match the input image, except GIF and SVG input which become PNG output. * By default, the format will match the input image, except GIF and SVG input which become PNG output.
* *
* `callback`, if present, gets three arguments `(err, buffer, info)` where: * `callback`, if present, gets three arguments `(err, data, info)` where:
* - `err` is an error message, if any. * - `err` is an error, if any.
* - `buffer` is the output image data. * - `data` is the output image data.
* - `info` contains the output image `format`, `size` (bytes), `width`, `height` and `channels`. * - `info` contains the output image `format`, `size` (bytes), `width`, `height`,
* A Promises/A+ promise is returned when `callback` is not provided. * `channels` and `premultiplied` (indicating if premultiplication was used).
* When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
* *
* A `Promise` is returned when `callback` is not provided.
*
* @example
* sharp(input)
* .toBuffer((err, data, info) => { ... });
*
* @example
* sharp(input)
* .toBuffer()
* .then(data => { ... })
* .catch(err => { ... });
*
* @example
* sharp(input)
* .toBuffer({ resolveWithObject: true })
* .then(({ data, info }) => { ... })
* .catch(err => { ... });
*
* @param {Object} [options]
* @param {Boolean} [options.resolveWithObject] Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
* @param {Function} [callback] * @param {Function} [callback]
* @returns {Promise<Buffer>} - when no callback is provided * @returns {Promise<Buffer>} - when no callback is provided
*/ */
const toBuffer = function toBuffer (callback) { function toBuffer (options, callback) {
return this._pipeline(callback); if (is.object(options)) {
}; if (is.bool(options.resolveWithObject)) {
this.options.resolveWithObject = options.resolveWithObject;
}
}
return this._pipeline(is.fn(options) ? 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. * The default behaviour, when `withMetadata` is not used, is to strip all metadata and convert to the device-independent sRGB colour space.
* This will also convert to and add a web-friendly sRGB ICC profile. * This will also convert to and add a web-friendly sRGB ICC profile.
*
* @example
* sharp('input.jpg')
* .withMetadata()
* .toFile('output-with-metadata.jpg')
* .then(info => { ... });
*
* @param {Object} [withMetadata] * @param {Object} [withMetadata]
* @param {Number} [withMetadata.orientation] value between 1 and 8, used to update the EXIF `Orientation` tag. * @param {Number} [withMetadata.orientation] value between 1 and 8, used to update the EXIF `Orientation` tag.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const withMetadata = function withMetadata (withMetadata) { function withMetadata (withMetadata) {
this.options.withMetadata = is.bool(withMetadata) ? withMetadata : true; this.options.withMetadata = is.bool(withMetadata) ? withMetadata : true;
if (is.object(withMetadata)) { if (is.object(withMetadata)) {
if (is.defined(withMetadata.orientation)) { if (is.defined(withMetadata.orientation)) {
@@ -82,23 +126,37 @@ const withMetadata = function withMetadata (withMetadata) {
} }
} }
return this; return this;
}; }
/** /**
* Use these JPEG options for output image. * Use these JPEG options for output image.
*
* @example
* // Convert any input to very high quality JPEG output
* const data = await sharp(input)
* .jpeg({
* quality: 100,
* chromaSubsampling: '4:4:4'
* })
* .toBuffer();
*
* @param {Object} [options] - output options * @param {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 when quality <= 90
* @param {Boolean} [options.trellisQuantisation=false] - apply trellis quantisation, requires mozjpeg * @param {Boolean} [options.trellisQuantisation=false] - apply trellis quantisation, requires libvips compiled with support for mozjpeg
* @param {Boolean} [options.overshootDeringing=false] - apply overshoot deringing, requires mozjpeg * @param {Boolean} [options.overshootDeringing=false] - apply overshoot deringing, requires libvips compiled with support for mozjpeg
* @param {Boolean} [options.optimiseScans=false] - optimise progressive scans, forces progressive, requires mozjpeg * @param {Boolean} [options.optimiseScans=false] - optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg
* @param {Boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans * @param {Boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans
* @param {Boolean} [options.optimiseCoding=true] - optimise Huffman coding tables
* @param {Boolean} [options.optimizeCoding=true] - alternative spelling of optimiseCoding
* @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 {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
*/ */
const jpeg = function jpeg (options) { function jpeg (options) {
if (is.object(options)) { if (is.object(options)) {
if (is.defined(options.quality)) { if (is.defined(options.quality)) {
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) { if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
@@ -117,35 +175,62 @@ const jpeg = function jpeg (options) {
throw new Error('Invalid chromaSubsampling (4:2:0, 4:4:4) ' + options.chromaSubsampling); throw new Error('Invalid chromaSubsampling (4:2:0, 4:4:4) ' + options.chromaSubsampling);
} }
} }
options.trellisQuantisation = is.bool(options.trellisQuantization) ? options.trellisQuantization : options.trellisQuantisation; const trellisQuantisation = is.bool(options.trellisQuantization) ? options.trellisQuantization : options.trellisQuantisation;
if (is.defined(options.trellisQuantisation)) { if (is.defined(trellisQuantisation)) {
this._setBooleanOption('jpegTrellisQuantisation', options.trellisQuantisation); this._setBooleanOption('jpegTrellisQuantisation', trellisQuantisation);
} }
if (is.defined(options.overshootDeringing)) { if (is.defined(options.overshootDeringing)) {
this._setBooleanOption('jpegOvershootDeringing', options.overshootDeringing); this._setBooleanOption('jpegOvershootDeringing', options.overshootDeringing);
} }
options.optimiseScans = is.bool(options.optimizeScans) ? options.optimizeScans : options.optimiseScans; const optimiseScans = is.bool(options.optimizeScans) ? options.optimizeScans : options.optimiseScans;
if (is.defined(options.optimiseScans)) { if (is.defined(optimiseScans)) {
this._setBooleanOption('jpegOptimiseScans', options.optimiseScans); this._setBooleanOption('jpegOptimiseScans', optimiseScans);
if (options.optimiseScans) { if (optimiseScans) {
this.options.jpegProgressive = true; this.options.jpegProgressive = true;
} }
} }
const optimiseCoding = is.bool(options.optimizeCoding) ? options.optimizeCoding : options.optimiseCoding;
if (is.defined(optimiseCoding)) {
this._setBooleanOption('jpegOptimiseCoding', optimiseCoding);
}
const quantisationTable = is.number(options.quantizationTable) ? options.quantizationTable : options.quantisationTable;
if (is.defined(quantisationTable)) {
if (is.integer(quantisationTable) && is.inRange(quantisationTable, 0, 8)) {
this.options.jpegQuantisationTable = quantisationTable;
} else {
throw new Error('Invalid quantisation table (integer, 0-8) ' + quantisationTable);
}
}
} }
return this._updateFormatOut('jpeg', options); return this._updateFormatOut('jpeg', options);
}; }
/** /**
* Use these PNG options for output image. * Use these PNG options for output image.
*
* PNG output is always full colour at 8 or 16 bits per pixel.
* Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel.
*
* @example
* // Convert any input to PNG output
* const data = await sharp(input)
* .png()
* .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=6] - zlib compression level * @param {Number} [options.compressionLevel=9] - zlib compression level, 0-9
* @param {Boolean} [options.adaptiveFiltering=true] - 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 {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.colours=256] - maximum number of palette entries, 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.dither=1.0] - level of Floyd-Steinberg error diffusion, 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
*/ */
const png = function png (options) { function png (options) {
if (is.object(options)) { if (is.object(options)) {
if (is.defined(options.progressive)) { if (is.defined(options.progressive)) {
this._setBooleanOption('pngProgressive', options.progressive); this._setBooleanOption('pngProgressive', options.progressive);
@@ -160,12 +245,46 @@ const png = function png (options) {
if (is.defined(options.adaptiveFiltering)) { if (is.defined(options.adaptiveFiltering)) {
this._setBooleanOption('pngAdaptiveFiltering', options.adaptiveFiltering); this._setBooleanOption('pngAdaptiveFiltering', options.adaptiveFiltering);
} }
if (is.defined(options.palette)) {
this._setBooleanOption('pngPalette', options.palette);
if (this.options.pngPalette) {
if (is.defined(options.quality)) {
if (is.integer(options.quality) && is.inRange(options.quality, 0, 100)) {
this.options.pngQuality = options.quality;
} else {
throw is.invalidParameterError('quality', 'integer between 0 and 100', options.quality);
}
}
const colours = options.colours || options.colors;
if (is.defined(colours)) {
if (is.integer(colours) && is.inRange(colours, 2, 256)) {
this.options.pngColours = colours;
} else {
throw is.invalidParameterError('colours', 'integer between 2 and 256', colours);
}
}
if (is.defined(options.dither)) {
if (is.number(options.dither) && is.inRange(options.dither, 0, 1)) {
this.options.pngDither = options.dither;
} else {
throw is.invalidParameterError('dither', 'number between 0.0 and 1.0', options.dither);
}
}
}
}
} }
return this._updateFormatOut('png', options); return this._updateFormatOut('png', options);
}; }
/** /**
* Use these WebP options for output image. * Use these WebP options for output image.
*
* @example
* // Convert any input to lossless WebP output
* const data = await sharp(input)
* .webp({ lossless: true })
* .toBuffer();
*
* @param {Object} [options] - output options * @param {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
@@ -175,7 +294,7 @@ const png = function png (options) {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
const webp = function webp (options) { function webp (options) {
if (is.object(options) && is.defined(options.quality)) { if (is.object(options) && is.defined(options.quality)) {
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) { if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
this.options.webpQuality = options.quality; this.options.webpQuality = options.quality;
@@ -184,10 +303,10 @@ const webp = function webp (options) {
} }
} }
if (is.object(options) && is.defined(options.alphaQuality)) { if (is.object(options) && is.defined(options.alphaQuality)) {
if (is.integer(options.alphaQuality) && is.inRange(options.alphaQuality, 1, 100)) { if (is.integer(options.alphaQuality) && is.inRange(options.alphaQuality, 0, 100)) {
this.options.webpAlphaQuality = options.alphaQuality; this.options.webpAlphaQuality = options.alphaQuality;
} else { } else {
throw new Error('Invalid webp alpha quality (integer, 1-100) ' + options.alphaQuality); throw new Error('Invalid webp alpha quality (integer, 0-100) ' + options.alphaQuality);
} }
} }
if (is.object(options) && is.defined(options.lossless)) { if (is.object(options) && is.defined(options.lossless)) {
@@ -197,57 +316,166 @@ const webp = function webp (options) {
this._setBooleanOption('webpNearLossless', options.nearLossless); this._setBooleanOption('webpNearLossless', options.nearLossless);
} }
return this._updateFormatOut('webp', options); return this._updateFormatOut('webp', options);
}; }
/** /**
* Use these TIFF options for output image. * Use these TIFF options for output image.
*
* @example
* // Convert SVG input to LZW-compressed, 1 bit per pixel TIFF output
* sharp('input.svg')
* .tiff({
* compression: 'lzw',
* squash: true
* })
* .toFile('1-bpp-output.tiff')
* .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.predictor='horizontal'] - compression predictor options: none, horizontal, float
* @param {Boolean} [options.pyramid=false] - write an image pyramid
* @param {Boolean} [options.tile=false] - write a tiled tiff
* @param {Boolean} [options.tileWidth=256] - horizontal tile size
* @param {Boolean} [options.tileHeight=256] - vertical tile size
* @param {Number} [options.xres=1.0] - horizontal resolution in pixels/mm
* @param {Number} [options.yres=1.0] - vertical resolution in pixels/mm
* @param {Boolean} [options.squash=false] - squash 8-bit images down to 1 bit
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
const tiff = function tiff (options) { function tiff (options) {
if (is.object(options) && is.defined(options.quality)) { if (is.object(options)) {
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) { if (is.defined(options.quality)) {
this.options.tiffQuality = options.quality; if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
} else { this.options.tiffQuality = options.quality;
throw new Error('Invalid quality (integer, 1-100) ' + options.quality); } else {
throw new Error('Invalid quality (integer, 1-100) ' + options.quality);
}
}
if (is.defined(options.squash)) {
if (is.bool(options.squash)) {
this.options.tiffSquash = options.squash;
} else {
throw new Error('Invalid Value for squash ' + options.squash + ' Only Boolean Values allowed for options.squash.');
}
}
// tiling
if (is.defined(options.tile)) {
if (is.bool(options.tile)) {
this.options.tiffTile = options.tile;
} else {
throw new Error('Invalid Value for tile ' + options.tile + ' Only Boolean values allowed for options.tile');
}
}
if (is.defined(options.tileWidth)) {
if (is.number(options.tileWidth) && options.tileWidth > 0) {
this.options.tiffTileWidth = options.tileWidth;
} else {
throw new Error('Invalid Value for tileWidth ' + options.tileWidth + ' Only positive numeric values allowed for options.tileWidth');
}
}
if (is.defined(options.tileHeight)) {
if (is.number(options.tileHeight) && options.tileHeight > 0) {
this.options.tiffTileHeight = options.tileHeight;
} else {
throw new Error('Invalid Value for tileHeight ' + options.tileHeight + ' Only positive numeric values allowed for options.tileHeight');
}
}
// pyramid
if (is.defined(options.pyramid)) {
if (is.bool(options.pyramid)) {
this.options.tiffPyramid = options.pyramid;
} else {
throw new Error('Invalid Value for pyramid ' + options.pyramid + ' Only Boolean values allowed for options.pyramid');
}
}
// resolution
if (is.defined(options.xres)) {
if (is.number(options.xres)) {
this.options.tiffXres = options.xres;
} else {
throw new Error('Invalid Value for xres ' + options.xres + ' Only numeric values allowed for options.xres');
}
}
if (is.defined(options.yres)) {
if (is.number(options.yres)) {
this.options.tiffYres = options.yres;
} else {
throw new Error('Invalid Value for yres ' + options.yres + ' Only numeric values allowed for options.yres');
}
}
// compression
if (is.defined(options.compression)) {
if (is.string(options.compression) && is.inArray(options.compression, ['lzw', 'deflate', 'jpeg', 'ccittfax4', 'none'])) {
this.options.tiffCompression = options.compression;
} else {
const message = `Invalid compression option "${options.compression}". Should be one of: lzw, deflate, jpeg, ccittfax4, none`;
throw new Error(message);
}
}
// predictor
if (is.defined(options.predictor)) {
if (is.string(options.predictor) && is.inArray(options.predictor, ['none', 'horizontal', 'float'])) {
this.options.tiffPredictor = options.predictor;
} else {
const message = `Invalid predictor option "${options.predictor}". Should be one of: none, horizontal, float`;
throw new Error(message);
}
} }
} }
return this._updateFormatOut('tiff', options); return this._updateFormatOut('tiff', options);
}; }
/** /**
* Force output to be raw, uncompressed uint8 pixel data. * Force output to be raw, uncompressed uint8 pixel data.
*
* @example
* // Extract raw RGB pixel data from JPEG input
* const { data, info } = await sharp('input.jpg')
* .raw()
* .toBuffer({ resolveWithObject: true });
*
* @returns {Sharp} * @returns {Sharp}
*/ */
const raw = function raw () { function raw () {
return this._updateFormatOut('raw'); return this._updateFormatOut('raw');
}; }
/** /**
* Force output to a given format. * 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 {(String|Object)} format - as a String or an Object with an 'id' attribute
* @param {Object} options - output options * @param {Object} options - output options
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} unsupported format or options * @throws {Error} unsupported format or options
*/ */
const toFormat = function toFormat (format, options) { function toFormat (format, options) {
if (is.object(format) && is.string(format.id)) { if (is.object(format) && is.string(format.id)) {
format = format.id; format = format.id;
} }
if (format === 'jpg') format = 'jpeg';
if (!is.inArray(format, ['jpeg', 'png', 'webp', 'tiff', 'raw'])) { if (!is.inArray(format, ['jpeg', 'png', 'webp', 'tiff', 'raw'])) {
throw new Error('Unsupported output format ' + format); throw new Error('Unsupported output format ' + format);
} }
return this[format](options); return this[format](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.
* Use a `.zip` or `.szi` file extension with `toFile` to write to a compressed archive file format. * Use a `.zip` or `.szi` file extension with `toFile` to write to a compressed archive file format.
* *
* Warning: multiple sharp instances concurrently producing tile output can expose a possible race condition in some versions of libgsf.
*
* @example * @example
* sharp('input.tiff') * sharp('input.tiff')
* .png() * .png()
@@ -262,12 +490,14 @@ const toFormat = function toFormat (format, options) {
* @param {Object} [tile] * @param {Object} [tile]
* @param {Number} [tile.size=256] tile size in pixels, a value between 1 and 8192. * @param {Number} [tile.size=256] tile size in pixels, a value between 1 and 8192.
* @param {Number} [tile.overlap=0] tile overlap in pixels, a value between 0 and 8192. * @param {Number} [tile.overlap=0] tile overlap in pixels, a value between 0 and 8192.
* @param {Number} [tile.angle=0] tile angle of rotation, must be a multiple of 90.
* @param {String} [tile.depth] how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
* @param {String} [tile.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file). * @param {String} [tile.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file).
* @param {String} [tile.layout='dz'] filesystem layout, possible values are `dz`, `zoomify` or `google`. * @param {String} [tile.layout='dz'] filesystem layout, possible values are `dz`, `zoomify` or `google`.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const tile = function tile (tile) { function tile (tile) {
if (is.object(tile)) { if (is.object(tile)) {
// Size of square tiles, in pixels // Size of square tiles, in pixels
if (is.defined(tile.size)) { if (is.defined(tile.size)) {
@@ -304,6 +534,24 @@ const tile = function tile (tile) {
throw new Error('Invalid tile layout ' + tile.layout); throw new Error('Invalid tile layout ' + tile.layout);
} }
} }
// Angle of rotation,
if (is.defined(tile.angle)) {
if (is.integer(tile.angle) && !(tile.angle % 90)) {
this.options.tileAngle = tile.angle;
} else {
throw new Error('Unsupported angle: angle must be a positive/negative multiple of 90 ' + tile.angle);
}
}
// Depth of tiles
if (is.defined(tile.depth)) {
if (is.string(tile.depth) && is.inArray(tile.depth, ['onepixel', 'onetile', 'one'])) {
this.options.tileDepth = tile.depth;
} else {
throw new Error("Invalid tile depth '" + tile.depth + "', should be one of 'onepixel', 'onetile' or 'one'");
}
}
} }
// Format // Format
if (is.inArray(this.options.formatOut, ['jpeg', 'png', 'webp'])) { if (is.inArray(this.options.formatOut, ['jpeg', 'png', 'webp'])) {
@@ -311,8 +559,9 @@ const tile = function tile (tile) {
} else if (this.options.formatOut !== 'input') { } else if (this.options.formatOut !== 'input') {
throw new Error('Invalid tile format ' + this.options.formatOut); throw new Error('Invalid tile format ' + this.options.formatOut);
} }
return this._updateFormatOut('dz'); return this._updateFormatOut('dz');
}; }
/** /**
* Update the output format unless options.force is false, * Update the output format unless options.force is false,
@@ -323,10 +572,12 @@ const tile = function tile (tile) {
* @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}
*/ */
const _updateFormatOut = function _updateFormatOut (formatOut, options) { function _updateFormatOut (formatOut, options) {
this.options.formatOut = (is.object(options) && options.force === false) ? 'input' : formatOut; if (!(is.object(options) && options.force === false)) {
this.options.formatOut = formatOut;
}
return this; return this;
}; }
/** /**
* Update a Boolean attribute of the this.options Object. * Update a Boolean attribute of the this.options Object.
@@ -335,31 +586,31 @@ const _updateFormatOut = function _updateFormatOut (formatOut, options) {
* @param {Boolean} val * @param {Boolean} val
* @throws {Error} Invalid key * @throws {Error} Invalid key
*/ */
const _setBooleanOption = function _setBooleanOption (key, val) { function _setBooleanOption (key, val) {
if (is.bool(val)) { if (is.bool(val)) {
this.options[key] = val; this.options[key] = val;
} else { } else {
throw new Error('Invalid ' + key + ' (boolean) ' + val); throw new Error('Invalid ' + key + ' (boolean) ' + val);
} }
}; }
/** /**
* Called by a WriteableStream to notify us it is ready for data. * Called by a WriteableStream to notify us it is ready for data.
* @private * @private
*/ */
const _read = function _read () { function _read () {
if (!this.options.streamOut) { if (!this.options.streamOut) {
this.options.streamOut = true; this.options.streamOut = true;
this._pipeline(); this._pipeline();
} }
}; }
/** /**
* Invoke the C++ image processing pipeline * Invoke the C++ image processing pipeline
* Supports callback, stream and promise variants * Supports callback, stream and promise variants
* @private * @private
*/ */
const _pipeline = function _pipeline (callback) { function _pipeline (callback) {
const that = this; const that = this;
if (typeof callback === 'function') { if (typeof callback === 'function') {
// output=file/buffer // output=file/buffer
@@ -423,11 +674,15 @@ const _pipeline = function _pipeline (callback) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
that.on('finish', function () { that.on('finish', function () {
that._flattenBufferIn(); that._flattenBufferIn();
sharp.pipeline(that.options, function (err, data) { sharp.pipeline(that.options, function (err, data, info) {
if (err) { if (err) {
reject(err); reject(err);
} else { } else {
resolve(data); if (that.options.resolveWithObject) {
resolve({ data: data, info: info });
} else {
resolve(data);
}
} }
}); });
}); });
@@ -435,84 +690,28 @@ const _pipeline = function _pipeline (callback) {
} else { } else {
// output=promise, input=file/buffer // output=promise, input=file/buffer
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
sharp.pipeline(that.options, function (err, data) { sharp.pipeline(that.options, function (err, data, info) {
if (err) { if (err) {
reject(err); reject(err);
} else { } else {
resolve(data); if (that.options.resolveWithObject) {
resolve({ data: data, info: info });
} else {
resolve(data);
}
} }
}); });
}); });
} }
} }
}; }
// Deprecated output options
/* istanbul ignore next */
const quality = util.deprecate(function (quality) {
const formatOut = this.options.formatOut;
const options = { quality: quality };
this.jpeg(options).webp(options).tiff(options);
this.options.formatOut = formatOut;
return this;
}, 'quality: use jpeg({ quality: ... }), webp({ quality: ... }) and/or tiff({ quality: ... }) instead');
/* istanbul ignore next */
const progressive = util.deprecate(function (progressive) {
const formatOut = this.options.formatOut;
const options = { progressive: (progressive !== false) };
this.jpeg(options).png(options);
this.options.formatOut = formatOut;
return this;
}, 'progressive: use jpeg({ progressive: ... }) and/or png({ progressive: ... }) instead');
/* istanbul ignore next */
const compressionLevel = util.deprecate(function (compressionLevel) {
const formatOut = this.options.formatOut;
this.png({ compressionLevel: compressionLevel });
this.options.formatOut = formatOut;
return this;
}, 'compressionLevel: use png({ compressionLevel: ... }) instead');
/* istanbul ignore next */
const withoutAdaptiveFiltering = util.deprecate(function (withoutAdaptiveFiltering) {
const formatOut = this.options.formatOut;
this.png({ adaptiveFiltering: (withoutAdaptiveFiltering === false) });
this.options.formatOut = formatOut;
return this;
}, 'withoutAdaptiveFiltering: use png({ adaptiveFiltering: ... }) instead');
/* istanbul ignore next */
const withoutChromaSubsampling = util.deprecate(function (withoutChromaSubsampling) {
const formatOut = this.options.formatOut;
this.jpeg({ chromaSubsampling: (withoutChromaSubsampling === false) ? '4:2:0' : '4:4:4' });
this.options.formatOut = formatOut;
return this;
}, 'withoutChromaSubsampling: use jpeg({ chromaSubsampling: "4:4:4" }) instead');
/* istanbul ignore next */
const trellisQuantisation = util.deprecate(function (trellisQuantisation) {
const formatOut = this.options.formatOut;
this.jpeg({ trellisQuantisation: (trellisQuantisation !== false) });
this.options.formatOut = formatOut;
return this;
}, 'trellisQuantisation: use jpeg({ trellisQuantisation: ... }) instead');
/* istanbul ignore next */
const overshootDeringing = util.deprecate(function (overshootDeringing) {
const formatOut = this.options.formatOut;
this.jpeg({ overshootDeringing: (overshootDeringing !== false) });
this.options.formatOut = formatOut;
return this;
}, 'overshootDeringing: use jpeg({ overshootDeringing: ... }) instead');
/* istanbul ignore next */
const optimiseScans = util.deprecate(function (optimiseScans) {
const formatOut = this.options.formatOut;
this.jpeg({ optimiseScans: (optimiseScans !== false) });
this.options.formatOut = formatOut;
return this;
}, 'optimiseScans: use jpeg({ optimiseScans: ... }) instead');
/** /**
* Decorate the Sharp prototype with output-related functions. * Decorate the Sharp prototype with output-related functions.
* @private * @private
*/ */
module.exports = function (Sharp) { module.exports = function (Sharp) {
[ Object.assign(Sharp.prototype, {
// Public // Public
toFile, toFile,
toBuffer, toBuffer,
@@ -529,18 +728,5 @@ module.exports = function (Sharp) {
_setBooleanOption, _setBooleanOption,
_read, _read,
_pipeline _pipeline
].forEach(function (f) {
Sharp.prototype[f.name] = f;
}); });
// Deprecated
Sharp.prototype.quality = quality;
Sharp.prototype.progressive = progressive;
Sharp.prototype.compressionLevel = compressionLevel;
Sharp.prototype.withoutAdaptiveFiltering = withoutAdaptiveFiltering;
Sharp.prototype.withoutChromaSubsampling = withoutChromaSubsampling;
Sharp.prototype.trellisQuantisation = trellisQuantisation;
Sharp.prototype.trellisQuantization = trellisQuantisation;
Sharp.prototype.overshootDeringing = overshootDeringing;
Sharp.prototype.optimiseScans = optimiseScans;
Sharp.prototype.optimizeScans = optimiseScans;
}; };

23
lib/platform.js Normal file
View File

@@ -0,0 +1,23 @@
'use strict';
const detectLibc = require('detect-libc');
const env = process.env;
module.exports = function () {
const arch = env.npm_config_arch || process.arch;
const platform = env.npm_config_platform || process.platform;
const libc = (platform === 'linux' && detectLibc.isNonGlibcLinux) ? detectLibc.family : '';
const platformId = [`${platform}${libc}`];
if (arch === 'arm') {
platformId.push(`armv${env.npm_config_arm_version || process.config.variables.arm_version || '6'}`);
} else if (arch === 'arm64') {
platformId.push(`arm64v${env.npm_config_arm_version || '8'}`);
} else {
platformId.push(arch);
}
return platformId.join('-');
};

View File

@@ -3,7 +3,7 @@
const is = require('./is'); const is = require('./is');
/** /**
* Weighting to apply to image crop. * Weighting to apply when using contain/cover fit.
* @member * @member
* @private * @private
*/ */
@@ -21,7 +21,23 @@ const gravity = {
}; };
/** /**
* Strategies for automagic crop behaviour. * Position to apply when using contain/cover fit.
* @member
* @private
*/
const position = {
top: 1,
right: 2,
bottom: 3,
left: 4,
'right top': 5,
'right bottom': 6,
'left bottom': 7,
'left top': 8
};
/**
* Strategies for automagic cover behaviour.
* @member * @member
* @private * @private
*/ */
@@ -36,89 +52,195 @@ const strategy = {
* @private * @private
*/ */
const kernel = { const kernel = {
nearest: 'nearest',
cubic: 'cubic', cubic: 'cubic',
mitchell: 'mitchell',
lanczos2: 'lanczos2', lanczos2: 'lanczos2',
lanczos3: 'lanczos3' lanczos3: 'lanczos3'
}; };
/** /**
* Enlargement interpolators. * Methods by which an image can be resized to fit the provided dimensions.
* @member * @member
* @private * @private
*/ */
const interpolator = { const fit = {
nearest: 'nearest', contain: 'contain',
bilinear: 'bilinear', cover: 'cover',
bicubic: 'bicubic', fill: 'fill',
nohalo: 'nohalo', inside: 'inside',
lbb: 'lbb', outside: 'outside'
locallyBoundedBicubic: 'lbb',
vsqbs: 'vsqbs',
vertexSplitQuadraticBasisSpline: 'vsqbs'
}; };
/** /**
* Resize image to `width` x `height`. * Map external fit property to internal canvas property.
* By default, the resized image is centre cropped to the exact size specified. * @member
* @private
*/
const mapFitToCanvas = {
contain: 'embed',
cover: 'crop',
fill: 'ignore_aspect',
inside: 'max',
outside: 'min'
};
/**
* Resize image to `width`, `height` or `width x height`.
* *
* Possible reduction kernels 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).
* - `contain`: Embed within both provided dimensions.
* - `fill`: Ignore the aspect ratio of the input and stretch to both provided dimensions.
* - `inside`: Preserving aspect ratio, resize the image to be as large as possible while ensuring its dimensions are less than or equal to both those specified.
* - `outside`: Preserving aspect ratio, resize the image to be as small as possible while ensuring its dimensions are greater than or equal to both those specified.
* Some of these values are based on the [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) CSS property.
*
* When using a `fit` of `cover` or `contain`, the default **position** is `centre`. Other options are:
* - `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`.
* - `sharp.gravity`: `north`, `northeast`, `east`, `southeast`, `south`, `southwest`, `west`, `northwest`, `center` or `centre`.
* - `sharp.strategy`: `cover` only, dynamically crop using either the `entropy` or `attention` strategy.
* Some of these values are based on the [object-position](https://developer.mozilla.org/en-US/docs/Web/CSS/object-position) CSS property.
*
* The experimental strategy-based approach resizes so one dimension is at its target length
* then repeatedly ranks edge regions, discarding the edge with the lowest score based on the selected strategy.
* - `entropy`: focus on the region with the highest [Shannon entropy](https://en.wikipedia.org/wiki/Entropy_%28information_theory%29).
* - `attention`: focus on the region with the highest luminance frequency, colour saturation and presence of skin tones.
*
* Possible interpolation kernels are:
* - `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation).
* - `cubic`: Use a [Catmull-Rom spline](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline). * - `cubic`: Use a [Catmull-Rom spline](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline).
* - `mitchell`: Use a [Mitchell-Netravali spline](https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf).
* - `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`. * - `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`.
* - `lanczos3`: Use a Lanczos kernel with `a=3` (the default). * - `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
* *
* Possible enlargement interpolators are:
* - `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation).
* - `bilinear`: Use [bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation), faster than bicubic but with less smooth results.
* - `vertexSplitQuadraticBasisSpline`: Use the smoother [VSQBS interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48) to prevent "staircasing" when enlarging.
* - `bicubic`: Use [bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) (the default).
* - `locallyBoundedBicubic`: Use [LBB interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100), which prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" but typically reduces performance by a factor of 2.
* - `nohalo`: Use [Nohalo interpolation](http://eprints.soton.ac.uk/268086/), which prevents acutance but typically reduces performance by a factor of 3.
*
* @example * @example
* sharp(inputBuffer) * sharp(input)
* .resize(200, 300, { * .resize({ width: 100 })
* kernel: sharp.kernel.lanczos2, * .toBuffer()
* interpolator: sharp.interpolator.nohalo * .then(data => {
* }) * // 100 pixels wide, auto-scaled height
* .background('white')
* .embed()
* .toFile('output.tiff')
* .then(function() {
* // output.tiff is a 200 pixels wide and 300 pixels high image
* // containing a lanczos2/nohalo scaled version, embedded on a white canvas,
* // of the image data in inputBuffer
* }); * });
* *
* @param {Number} [width] - pixels wide the resultant image should be, between 1 and 16383 (0x3FFF). Use `null` or `undefined` to auto-scale the width to match the height. * @example
* @param {Number} [height] - pixels high the resultant image should be, between 1 and 16383. Use `null` or `undefined` to auto-scale the height to match the width. * sharp(input)
* .resize({ height: 100 })
* .toBuffer()
* .then(data => {
* // 100 pixels high, auto-scaled width
* });
*
* @example
* sharp(input)
* .resize(200, 300, {
* kernel: sharp.kernel.nearest,
* fit: 'contain',
* position: 'right top',
* background: { r: 255, g: 255, b: 255, alpha: 0.5 }
* })
* .toFile('output.png')
* .then(() => {
* // output.png is a 200 pixels wide and 300 pixels high image
* // containing a nearest-neighbour scaled version
* // contained within the north-east corner of a semi-transparent white canvas
* });
*
* @example
* const transformer = sharp()
* .resize({
* width: 200,
* height: 200,
* fit: sharp.fit.cover,
* position: sharp.strategy.entropy
* });
* // Read image data from readableStream
* // Write 200px square auto-cropped image data to writableStream
* readableStream
* .pipe(transformer)
* .pipe(writableStream);
*
* @example
* sharp(input)
* .resize(200, 200, {
* fit: sharp.fit.inside,
* withoutEnlargement: true
* })
* .toFormat('jpeg')
* .toBuffer()
* .then(function(outputBuffer) {
* // outputBuffer contains JPEG image data
* // no wider and no higher than 200 pixels
* // and no larger than the input image
* });
*
* @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.height] - alternative means of specifying `height`. If both are present this take priority.
* @param {String} [options.fit='cover'] - how the image should be resized to fit both provided dimensions, one of `cover`, `contain`, `fill`, `inside` or `outside`.
* @param {String} [options.position='centre'] - position, gravity or strategy to use when `fit` is `cover` or `contain`.
* @param {String|Object} [options.background={r: 0, g: 0, b: 0, alpha: 1}] - background colour when using a `fit` of `contain`, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency.
* @param {String} [options.kernel='lanczos3'] - the kernel to use for image reduction. * @param {String} [options.kernel='lanczos3'] - the kernel to use for image reduction.
* @param {String} [options.interpolator='bicubic'] - the interpolator to use for image enlargement. * @param {Boolean} [options.withoutEnlargement=false] - do not enlarge if the width *or* height are already less than the specified dimensions, equivalent to GraphicsMagick's `>` geometry option.
* @param {Boolean} [options.centreSampling=false] - use *magick centre sampling convention instead of corner sampling. * @param {Boolean} [options.fastShrinkOnLoad=true] - take greater advantage of the JPEG and WebP shrink-on-load feature, which can lead to a slight moiré pattern on some images.
* @param {Boolean} [options.centerSampling=false] - alternative spelling of centreSampling.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const resize = function resize (width, height, options) { function resize (width, height, options) {
if (is.defined(width)) { if (is.defined(width)) {
if (is.integer(width) && is.inRange(width, 1, this.constructor.maximum.width)) { if (is.object(width) && !is.defined(options)) {
options = width;
} else if (is.integer(width) && width > 0) {
this.options.width = width; this.options.width = width;
} else { } else {
throw is.invalidParameterError('width', `integer between 1 and ${this.constructor.maximum.width}`, width); throw is.invalidParameterError('width', 'positive integer', width);
} }
} else { } else {
this.options.width = -1; this.options.width = -1;
} }
if (is.defined(height)) { if (is.defined(height)) {
if (is.integer(height) && is.inRange(height, 1, this.constructor.maximum.height)) { if (is.integer(height) && height > 0) {
this.options.height = height; this.options.height = height;
} else { } else {
throw is.invalidParameterError('height', `integer between 1 and ${this.constructor.maximum.height}`, height); throw is.invalidParameterError('height', 'positive integer', height);
} }
} else { } else {
this.options.height = -1; this.options.height = -1;
} }
if (is.object(options)) { if (is.object(options)) {
// Width
if (is.integer(options.width) && options.width > 0) {
this.options.width = options.width;
}
// Height
if (is.integer(options.height) && options.height > 0) {
this.options.height = options.height;
}
// Fit
if (is.defined(options.fit)) {
const canvas = mapFitToCanvas[options.fit];
if (is.string(canvas)) {
this.options.canvas = canvas;
} else {
throw is.invalidParameterError('fit', 'valid fit', options.fit);
}
}
// Position
if (is.defined(options.position)) {
const pos = is.integer(options.position)
? options.position
: strategy[options.position] || position[options.position] || gravity[options.position];
if (is.integer(pos) && (is.inRange(pos, 0, 8) || is.inRange(pos, 16, 17))) {
this.options.position = pos;
} else {
throw is.invalidParameterError('position', 'valid position/gravity/strategy', options.position);
}
}
// Background
if (is.defined(options.background)) {
this._setColourOption('resizeBackground', options.background);
}
// Kernel // Kernel
if (is.defined(options.kernel)) { if (is.defined(options.kernel)) {
if (is.string(kernel[options.kernel])) { if (is.string(kernel[options.kernel])) {
@@ -127,175 +249,149 @@ const resize = function resize (width, height, options) {
throw is.invalidParameterError('kernel', 'valid kernel name', options.kernel); throw is.invalidParameterError('kernel', 'valid kernel name', options.kernel);
} }
} }
// Interpolator // Without enlargement
if (is.defined(options.interpolator)) { if (is.defined(options.withoutEnlargement)) {
if (is.string(interpolator[options.interpolator])) { this._setBooleanOption('withoutEnlargement', options.withoutEnlargement);
this.options.interpolator = interpolator[options.interpolator];
} else {
throw is.invalidParameterError('interpolator', 'valid interpolator name', options.interpolator);
}
} }
// Centre sampling // Shrink on load
options.centreSampling = is.bool(options.centerSampling) ? options.centerSampling : options.centreSampling; if (is.defined(options.fastShrinkOnLoad)) {
if (is.defined(options.centreSampling)) { this._setBooleanOption('fastShrinkOnLoad', options.fastShrinkOnLoad);
this._setBooleanOption('centreSampling', options.centreSampling);
} }
} }
return this; return this;
}; }
/** /**
* Crop the resized image to the exact size specified, the default behaviour. * Extends/pads the edges of the image with the provided background colour.
* * This operation will always occur after resizing and extraction, if any.
* Possible attributes of the optional `sharp.gravity` are `north`, `northeast`, `east`, `southeast`, `south`,
* `southwest`, `west`, `northwest`, `center` and `centre`.
*
* The experimental strategy-based approach resizes so one dimension is at its target length
* then repeatedly ranks edge regions, discarding the edge with the lowest score based on the selected strategy.
* - `entropy`: focus on the region with the highest [Shannon entropy](https://en.wikipedia.org/wiki/Entropy_%28information_theory%29).
* - `attention`: focus on the region with the highest luminance frequency, colour saturation and presence of skin tones.
* *
* @example * @example
* const transformer = sharp() * // Resize to 140 pixels wide, then add 10 transparent pixels
* .resize(200, 200) * // to the top, left and right edges and 20 to the bottom edge
* .crop(sharp.strategy.entropy) * sharp(input)
* .on('error', function(err) { * .resize(140)
* console.log(err); * .extend({
* }); * top: 10,
* // Read image data from readableStream * bottom: 20,
* // Write 200px square auto-cropped image data to writableStream * left: 10,
* readableStream.pipe(transformer).pipe(writableStream); * right: 10,
* background: { r: 0, g: 0, b: 0, alpha: 0 }
* })
* ...
* *
* @param {String} [crop='centre'] - A member of `sharp.gravity` to crop to an edge/corner or `sharp.strategy` to crop dynamically. * @param {(Number|Object)} extend - single pixel count to add to all edges or an Object with per-edge counts
* @param {Number} [extend.top]
* @param {Number} [extend.left]
* @param {Number} [extend.bottom]
* @param {Number} [extend.right]
* @param {String|Object} [extend.background={r: 0, g: 0, b: 0, alpha: 1}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
function extend (extend) {
if (is.integer(extend) && extend > 0) {
this.options.extendTop = extend;
this.options.extendBottom = extend;
this.options.extendLeft = extend;
this.options.extendRight = extend;
} else if (
is.object(extend) &&
is.integer(extend.top) && extend.top >= 0 &&
is.integer(extend.bottom) && extend.bottom >= 0 &&
is.integer(extend.left) && extend.left >= 0 &&
is.integer(extend.right) && extend.right >= 0
) {
this.options.extendTop = extend.top;
this.options.extendBottom = extend.bottom;
this.options.extendLeft = extend.left;
this.options.extendRight = extend.right;
this._setColourOption('extendBackground', extend.background);
} else {
throw new Error('Invalid edge extension ' + extend);
}
return this;
}
/**
* Extract a region of the image.
*
* - Use `extract` before `resize` for pre-resize extraction.
* - Use `extract` after `resize` for post-resize extraction.
* - Use `extract` before and after for both.
*
* @example
* sharp(input)
* .extract({ left: left, top: top, width: width, height: height })
* .toFile(output, function(err) {
* // Extract a region of the input image, saving in the same format.
* });
* @example
* sharp(input)
* .extract({ left: leftOffsetPre, top: topOffsetPre, width: widthPre, height: heightPre })
* .resize(width, height)
* .extract({ left: leftOffsetPost, top: topOffsetPost, width: widthPost, height: heightPost })
* .toFile(output, function(err) {
* // Extract a region, resize, then extract from the resized image
* });
*
* @param {Object} options - describes the region to extract using integral pixel values
* @param {Number} options.left - zero-indexed offset from left edge
* @param {Number} options.top - zero-indexed offset from top edge
* @param {Number} options.width - width of region to extract
* @param {Number} options.height - height of region to extract
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const crop = function crop (crop) { function extract (options) {
this.options.canvas = 'crop'; const suffix = this.options.width === -1 && this.options.height === -1 ? 'Pre' : 'Post';
if (!is.defined(crop)) { ['left', 'top', 'width', 'height'].forEach(function (name) {
// Default const value = options[name];
this.options.crop = gravity.center; if (is.integer(value) && value >= 0) {
} else if (is.integer(crop) && is.inRange(crop, 0, 8)) { this.options[name + (name === 'left' || name === 'top' ? 'Offset' : '') + suffix] = value;
// Gravity (numeric) } else {
this.options.crop = crop; throw new Error('Non-integer value for ' + name + ' of ' + value);
} else if (is.string(crop) && is.integer(gravity[crop])) { }
// Gravity (string) }, this);
this.options.crop = gravity[crop]; // Ensure existing rotation occurs before pre-resize extraction
} else if (is.integer(crop) && crop >= strategy.entropy) { if (suffix === 'Pre' && ((this.options.angle % 360) !== 0 || this.options.useExifOrientation === true)) {
// Strategy this.options.rotateBeforePreExtract = true;
this.options.crop = crop;
} else {
throw is.invalidParameterError('crop', 'valid crop id/name/strategy', crop);
} }
return this; return this;
}; }
/** /**
* Preserving aspect ratio, resize the image to the maximum `width` or `height` specified * Trim "boring" pixels from all edges that contain values similar to the top-left pixel.
* then embed on a background of the exact `width` and `height` specified. * 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.
* If the background contains an alpha value then WebP and PNG format output images will
* contain an alpha channel, even when the input image does not.
*
* @example
* sharp('input.gif')
* .resize(200, 300)
* .background({r: 0, g: 0, b: 0, alpha: 0})
* .embed()
* .toFormat(sharp.format.webp)
* .toBuffer(function(err, outputBuffer) {
* if (err) {
* throw err;
* }
* // outputBuffer contains WebP image data of a 200 pixels wide and 300 pixels high
* // containing a scaled version, embedded on a transparent canvas, of input.gif
* });
*
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters
*/ */
const embed = function embed () { function trim (threshold) {
this.options.canvas = 'embed'; if (!is.defined(threshold)) {
this.options.trimThreshold = 10;
} else if (is.number(threshold) && threshold > 0) {
this.options.trimThreshold = threshold;
} else {
throw is.invalidParameterError('threshold', 'number greater than zero', threshold);
}
return this; return this;
}; }
/**
* Preserving aspect ratio, resize the image to be as large as possible
* while ensuring its dimensions are less than or equal to the `width` and `height` specified.
*
* Both `width` and `height` must be provided via `resize` otherwise the behaviour will default to `crop`.
*
* @example
* sharp(inputBuffer)
* .resize(200, 200)
* .max()
* .toFormat('jpeg')
* .toBuffer()
* .then(function(outputBuffer) {
* // outputBuffer contains JPEG image data no wider than 200 pixels and no higher
* // than 200 pixels regardless of the inputBuffer image dimensions
* });
*
* @returns {Sharp}
*/
const max = function max () {
this.options.canvas = 'max';
return this;
};
/**
* Preserving aspect ratio, resize the image to be as small as possible
* while ensuring its dimensions are greater than or equal to the `width` and `height` specified.
*
* Both `width` and `height` must be provided via `resize` otherwise the behaviour will default to `crop`.
*
* @returns {Sharp}
*/
const min = function min () {
this.options.canvas = 'min';
return this;
};
/**
* Ignoring the aspect ratio of the input, stretch the image to
* the exact `width` and/or `height` provided via `resize`.
* @returns {Sharp}
*/
const ignoreAspectRatio = function ignoreAspectRatio () {
this.options.canvas = 'ignore_aspect';
return this;
};
/**
* Do not enlarge the output image if the input image width *or* height are already less than the required dimensions.
* This is equivalent to GraphicsMagick's `>` geometry option:
* "*change the dimensions of the image only if its width or height exceeds the geometry specification*".
* @param {Boolean} [withoutEnlargement=true]
* @returns {Sharp}
*/
const withoutEnlargement = function withoutEnlargement (withoutEnlargement) {
this.options.withoutEnlargement = is.bool(withoutEnlargement) ? withoutEnlargement : true;
return this;
};
/** /**
* Decorate the Sharp prototype with resize-related functions. * Decorate the Sharp prototype with resize-related functions.
* @private * @private
*/ */
module.exports = function (Sharp) { module.exports = function (Sharp) {
[ Object.assign(Sharp.prototype, {
resize, resize,
crop, extend,
embed, extract,
max, trim
min,
ignoreAspectRatio,
withoutEnlargement
].forEach(function (f) {
Sharp.prototype[f.name] = f;
}); });
// Class attributes // Class attributes
Sharp.gravity = gravity; Sharp.gravity = gravity;
Sharp.strategy = strategy; Sharp.strategy = strategy;
Sharp.kernel = kernel; Sharp.kernel = kernel;
Sharp.interpolator = interpolator; Sharp.fit = fit;
Sharp.position = position;
}; };

View File

@@ -4,7 +4,7 @@ const is = require('./is');
const sharp = require('../build/Release/sharp.node'); const sharp = require('../build/Release/sharp.node');
/** /**
* 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.
* This method always returns cache statistics, * This method always returns cache statistics,
* useful for determining how much working memory is required for a particular task. * useful for determining how much working memory is required for a particular task.
@@ -16,13 +16,13 @@ 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 - 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}
*/ */
const cache = function cache (options) { function cache (options) {
if (is.bool(options)) { if (is.bool(options)) {
if (options) { if (options) {
// Default cache settings of 50MB, 20 files, 100 items // Default cache settings of 50MB, 20 files, 100 items
@@ -35,11 +35,11 @@ const cache = function cache (options) {
} else { } else {
return sharp.cache(); return sharp.cache();
} }
}; }
cache(true); cache(true);
/** /**
* Gets, or when a concurrency is provided sets, * Gets or, when a concurrency is provided, sets
* the number of threads _libvips'_ should create to process each image. * the number of threads _libvips'_ should create to process each image.
* The default value is the number of CPU cores. * The default value is the number of CPU cores.
* A value of `0` will reset to this default. * A value of `0` will reset to this default.
@@ -57,9 +57,9 @@ cache(true);
* @param {Number} [concurrency] * @param {Number} [concurrency]
* @returns {Number} concurrency * @returns {Number} concurrency
*/ */
const concurrency = function concurrency (concurrency) { function concurrency (concurrency) {
return sharp.concurrency(is.integer(concurrency) ? concurrency : null); return sharp.concurrency(is.integer(concurrency) ? concurrency : null);
}; }
/** /**
* Provides access to internal task counters. * Provides access to internal task counters.
@@ -71,9 +71,9 @@ const concurrency = function concurrency (concurrency) {
* *
* @returns {Object} * @returns {Object}
*/ */
const counters = function counters () { function counters () {
return sharp.counters(); return sharp.counters();
}; }
/** /**
* Get and set use of SIMD vector unit instructions. * Get and set use of SIMD vector unit instructions.
@@ -82,23 +82,20 @@ const counters = function counters () {
* Improves the performance of `resize`, `blur` and `sharpen` operations * Improves the performance of `resize`, `blur` and `sharpen` operations
* by taking advantage of the SIMD vector unit of the CPU, e.g. Intel SSE and ARM NEON. * by taking advantage of the SIMD vector unit of the CPU, e.g. Intel SSE and ARM NEON.
* *
* This feature is currently off by default but future versions may reverse this.
* Versions of liborc prior to 0.4.25 are known to segfault under heavy load.
*
* @example * @example
* const simd = sharp.simd(); * const simd = sharp.simd();
* // simd is `true` if SIMD is currently enabled * // simd is `true` if the runtime use of liborc is currently enabled
* @example * @example
* const simd = sharp.simd(true); * const simd = sharp.simd(false);
* // attempts to enable the use of SIMD, returning true if available * // prevent libvips from using liborc at runtime
* *
* @param {Boolean} [simd=false] * @param {Boolean} [simd=true]
* @returns {Boolean} * @returns {Boolean}
*/ */
const simd = function simd (simd) { function simd (simd) {
return sharp.simd(is.bool(simd) ? simd : null); return sharp.simd(is.bool(simd) ? simd : null);
}; }
simd(false); simd(true);
/** /**
* Decorate the Sharp class with utility-related functions. * Decorate the Sharp class with utility-related functions.

View File

@@ -1,14 +1,15 @@
site_name: sharp site_name: sharp
site_url: http://sharp.dimens.io/ site_url: https://sharp.pixelplumbing.com/
repo_url: https://github.com/lovell/sharp 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 site_description: High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images
copyright: <a href="https://dimens.io/">dimens.io</a> copyright: <a href="https://pixelplumbing.com/">pixelplumbing.com</a>
google_analytics: ['UA-13034748-12', 'sharp.dimens.io'] google_analytics: ['UA-13034748-12', 'sharp.pixelplumbing.com']
theme: readthedocs theme: readthedocs
extra_css:
- css/extra.css
markdown_extensions: markdown_extensions:
- toc: - toc:
permalink: True permalink: True
dev_addr: 0.0.0.0:10101
pages: pages:
- Home: index.md - Home: index.md
- Installation: install.md - Installation: install.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.17.2", "version": "0.22.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": [
@@ -32,14 +32,44 @@
"Matthias Thoemmes <thoemmes@gmail.com>", "Matthias Thoemmes <thoemmes@gmail.com>",
"Patrick Paskaris <patrick@paskaris.gr>", "Patrick Paskaris <patrick@paskaris.gr>",
"Jérémy Lal <kapouer@melix.org>", "Jérémy Lal <kapouer@melix.org>",
"Rahul Nanwani <r.nanwani@gmail.com>" "Rahul Nanwani <r.nanwani@gmail.com>",
"Alice Monday <alice0meta@gmail.com>",
"Kristo Jorgenson <kristo.jorgenson@gmail.com>",
"YvesBos <yves_bos@outlook.com>",
"Guy Maliar <guy@tailorbrands.com>",
"Nicolas Coden <nicolas@ncoden.fr>",
"Matt Parrish <matt.r.parrish@gmail.com>",
"Marcel Bretschneider <marcel.bretschneider@gmail.com>",
"Matthew McEachen <matthew+github@mceachen.org>",
"Jarda Kotěšovec <jarda.kotesovec@gmail.com>",
"Kenric D'Souza <kenric.dsouza@gmail.com>",
"Oleh Aleinyk <oleg.aleynik@gmail.com>",
"Marcel Bretschneider <marcel.bretschneider@gmail.com>",
"Andrea Bianco <andrea.bianco@unibas.ch>",
"Rik Heywood <rik@rik.org>",
"Thomas Parisot <hi@oncletom.io>",
"Nathan Graves <nathanrgraves+github@gmail.com>",
"Tom Lokhorst <tom@lokhorst.eu>",
"Espen Hovlandsdal <espen@hovlandsdal.com>",
"Sylvain Dumont <sylvain.dumont35@gmail.com>",
"Alun Davies <alun.owain.davies@googlemail.com>",
"Aidan Hoolachan <ajhoolachan21@gmail.com>",
"Axel Eirola <axel.eirola@iki.fi>",
"Freezy <freezy@xbmc.org>",
"Daiz <taneli.vatanen@gmail.com>",
"Julian Aubourg <j@ubourg.net>",
"Keith Belovay <keith@picthrive.com>",
"Michael B. Klein <mbklein@gmail.com>"
], ],
"scripts": { "scripts": {
"clean": "rm -rf node_modules/ build/ vendor/ coverage/ test/fixtures/output.*", "install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
"test": "semistandard && cc && cross-env VIPS_WARNING=0 nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js", "clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*",
"test": "semistandard && cc && npm run test-unit && npm run test-licensing && prebuild-ci",
"test-unit": "nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js",
"test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
"test-coverage": "./test/coverage/report.sh",
"test-leak": "./test/leak/leak.sh", "test-leak": "./test/leak/leak.sh",
"test-packaging": "./packaging/test-linux-x64.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": "for m in constructor input resize composite operation colour channel output utility; do documentation build --shallow --format=md lib/$m.js >docs/api-$m.md; done"
}, },
"main": "lib/index.js", "main": "lib/index.js",
"repository": { "repository": {
@@ -58,37 +88,44 @@
"resize", "resize",
"thumbnail", "thumbnail",
"crop", "crop",
"embed",
"libvips", "libvips",
"vips" "vips"
], ],
"dependencies": { "dependencies": {
"caw": "^2.0.0", "color": "^3.1.1",
"color": "^1.0.3", "detect-libc": "^1.0.3",
"got": "^6.7.1", "fs-copy-file-sync": "^1.1.1",
"nan": "^2.5.1", "nan": "^2.13.2",
"semver": "^5.3.0", "npmlog": "^4.1.2",
"tar": "^2.2.1" "prebuild-install": "^5.3.0",
"semver": "^6.0.0",
"simple-get": "^3.0.3",
"tar": "^4.4.8",
"tunnel-agent": "^0.6.0"
}, },
"devDependencies": { "devDependencies": {
"async": "^2.1.4", "async": "^2.6.2",
"bufferutil": "^2.0.1", "cc": "^1.0.2",
"cc": "^1.0.0", "decompress-zip": "^0.3.2",
"cross-env": "^3.1.4", "documentation": "^10.0.0",
"documentation": "^4.0.0-beta.18",
"exif-reader": "^1.0.2", "exif-reader": "^1.0.2",
"icc": "^1.0.0", "icc": "^1.0.0",
"mocha": "^3.2.0", "license-checker": "^25.0.1",
"nyc": "^10.1.2", "mocha": "^6.1.4",
"rimraf": "^2.5.4", "mock-fs": "^4.9.0",
"semistandard": "^9.2.1", "nyc": "^14.0.0",
"unzip": "^0.1.11" "prebuild": "^8.2.1",
"prebuild-ci": "^3.0.0",
"rimraf": "^2.6.3",
"semistandard": "^13.0.1"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"config": { "config": {
"libvips": "8.4.2" "libvips": "8.7.4"
}, },
"engines": { "engines": {
"node": ">=4" "node": ">=6"
}, },
"semistandard": { "semistandard": {
"env": [ "env": [
@@ -98,8 +135,10 @@
"cc": { "cc": {
"linelength": "120", "linelength": "120",
"filter": [ "filter": [
"build/c++11",
"build/include", "build/include",
"runtime/indentation_namespace" "runtime/indentation_namespace",
"runtime/references"
] ]
} }
} }

View File

@@ -1,57 +0,0 @@
# Packaging scripts
libvips and its dependencies are provided as pre-compiled shared libraries
for the most common operating systems and CPU architectures.
During `npm install`, these binaries are fetched as tarballs from
[Bintray](https://dl.bintray.com/lovell/sharp/) via HTTPS
and stored locally within `node_modules/sharp`.
## Using a custom tarball
A custom tarball stored on the local filesystem can be used instead.
Place it in the following location, where `x.y.z` is the libvips version,
`platform` is the value of `process.platform` and
`arch` is the value of `process.arch` (plus the version number for ARM).
`node_modules/sharp/packaging/libvips-x.y.z-platform-arch.tar.gz`
For example, for libvips v8.3.3 on an ARMv6 Linux machine, use:
`node_modules/sharp/packaging/libvips-8.3.3-linux-armv6.tar.gz`
Remove any `sharp/lib` and `sharp/include` directories
before running `npm install` again.
## Creating a tarball
Most people will not need to do this; proceed with caution.
The `packaging` directory contains the top-level [build script](build.sh).
### Linux
One [build script](build/lin.sh) is used to (cross-)compile
the same shared libraries within multiple containers.
* [x64](linux-x64/Dockerfile)
* [ARMv6](linux-armv6/Dockerfile)
* [ARMv7-A](linux-armv7/Dockerfile)
* [ARMv8-A](linux-armv8/Dockerfile)
The QEMU user mode emulation binaries are required to build for
the ARMv6 platform as the Debian armhf cross-compiler erroneously
generates unsupported Thumb 2 instructions.
```sh
sudo apt-get install qemu-user-static
```
### Windows
The output of libvips' [build-win64](https://github.com/jcupitt/build-win64)
"web" target is [post-processed](build/win.sh) within a [container](win32-x64/Dockerfile).
### OS X
See [package-libvips-darwin](https://github.com/lovell/package-libvips-darwin).

View File

@@ -1,43 +0,0 @@
#!/bin/sh
set -e
if [ $# -lt 1 ]; then
echo
echo "Usage: $0 VERSION [PLATFORM]"
echo "Build shared libraries for libvips and its dependencies via containers"
echo
echo "Please specify the libvips VERSION, e.g. 8.3.3"
echo
echo "Optionally build for only one PLATFORM, defaults to building for all"
echo "Possible values for PLATFORM are: win32-x64, linux-x64, linux-armv6,"
echo "linux-armv7, linux-armv8"
echo
exit 1
fi
VERSION_VIPS="$1"
PLATFORM="${2:-all}"
# Is docker available?
if ! type docker >/dev/null; then
echo "Please install docker"
exit 1
fi
# Windows (x64)
if [ $PLATFORM = "all" ] || [ $PLATFORM = "win32-x64" ]; then
echo "Building win32-x64..."
docker build -t vips-dev-win32-x64 win32-x64
docker run --rm -e "VERSION_VIPS=${VERSION_VIPS}" -v $PWD:/packaging vips-dev-win32-x64 sh -c "/packaging/build/win.sh"
fi
# Linux (x64, ARMv6, ARMv7, ARMv8)
for flavour in linux-x64 linux-armv6 linux-armv7 linux-armv8; do
if [ $PLATFORM = "all" ] || [ $PLATFORM = $flavour ]; then
echo "Building $flavour..."
docker build -t vips-dev-$flavour $flavour
docker run --rm -e "VERSION_VIPS=${VERSION_VIPS}" -v $PWD:/packaging vips-dev-$flavour sh -c "/packaging/build/lin.sh"
fi
done
# Display checksums
sha256sum *.tar.gz

View File

@@ -1,239 +0,0 @@
#!/bin/sh
set -e
# Working directories
DEPS=/deps
TARGET=/target
mkdir ${DEPS}
mkdir ${TARGET}
# Common build paths and flags
export PKG_CONFIG_PATH="${PKG_CONFIG_PATH}:${TARGET}/lib/pkgconfig"
export PATH="${PATH}:${TARGET}/bin"
export CPPFLAGS="-I${TARGET}/include"
export LDFLAGS="-L${TARGET}/lib"
export CFLAGS="${FLAGS}"
export CXXFLAGS="${FLAGS}"
# Dependency version numbers
VERSION_ZLIB=1.2.10
VERSION_FFI=3.2.1
VERSION_GLIB=2.50.1
VERSION_XML2=2.9.4
VERSION_GSF=1.14.40
VERSION_EXIF=0.6.21
VERSION_LCMS2=2.8
VERSION_JPEG=1.5.1
VERSION_PNG16=1.6.28
VERSION_WEBP=0.5.1
VERSION_TIFF=4.0.6
VERSION_ORC=0.4.26
VERSION_GDKPIXBUF=2.36.0
VERSION_FREETYPE=2.7
VERSION_FONTCONFIG=2.12.1
VERSION_HARFBUZZ=1.3.2
VERSION_PIXMAN=0.34.0
VERSION_CAIRO=1.14.6
VERSION_PANGO=1.40.3
VERSION_CROCO=0.6.11
VERSION_SVG=2.40.16
VERSION_GIF=5.1.4
# Least out-of-sync Sourceforge mirror
SOURCEFORGE_MIRROR=netix
mkdir ${DEPS}/zlib
curl -Ls http://zlib.net/zlib-${VERSION_ZLIB}.tar.xz | tar xJC ${DEPS}/zlib --strip-components=1
cd ${DEPS}/zlib
./configure --prefix=${TARGET} --uname=linux
make install
rm ${TARGET}/lib/libz.a
mkdir ${DEPS}/ffi
curl -Ls ftp://sourceware.org/pub/libffi/libffi-${VERSION_FFI}.tar.gz | tar xzC ${DEPS}/ffi --strip-components=1
cd ${DEPS}/ffi
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-builddir
make install-strip
mkdir ${DEPS}/glib
curl -Ls https://download.gnome.org/sources/glib/2.50/glib-${VERSION_GLIB}.tar.xz | tar xJC ${DEPS}/glib --strip-components=1
cd ${DEPS}/glib
echo glib_cv_stack_grows=no >>glib.cache
echo glib_cv_uscore=no >>glib.cache
./configure --cache-file=glib.cache --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --with-pcre=internal --disable-libmount
make install-strip
mkdir ${DEPS}/xml2
curl -Ls http://xmlsoft.org/sources/libxml2-${VERSION_XML2}.tar.gz | tar xzC ${DEPS}/xml2 --strip-components=1
cd ${DEPS}/xml2
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--without-python --without-debug --without-docbook --without-ftp --without-html --without-legacy \
--without-pattern --without-push --without-regexps --without-schemas --without-schematron --with-zlib=${TARGET}
make install-strip
mkdir ${DEPS}/gsf
curl -Ls https://download.gnome.org/sources/libgsf/1.14/libgsf-${VERSION_GSF}.tar.xz | tar xJC ${DEPS}/gsf --strip-components=1
cd ${DEPS}/gsf
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking
make install-strip
mkdir ${DEPS}/exif
curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/libexif/libexif/${VERSION_EXIF}/libexif-${VERSION_EXIF}.tar.bz2 | tar xjC ${DEPS}/exif --strip-components=1
cd ${DEPS}/exif
autoreconf -fiv
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking
make install-strip
mkdir ${DEPS}/lcms2
curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/lcms/lcms/${VERSION_LCMS2}/lcms2-${VERSION_LCMS2}.tar.gz | tar xzC ${DEPS}/lcms2 --strip-components=1
cd ${DEPS}/lcms2
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking
make install-strip
mkdir ${DEPS}/jpeg
curl -Ls https://github.com/libjpeg-turbo/libjpeg-turbo/archive/${VERSION_JPEG}.tar.gz | tar xzC ${DEPS}/jpeg --strip-components=1
cd ${DEPS}/jpeg
autoreconf -fiv
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --with-jpeg8 --without-turbojpeg
make install-strip
mkdir ${DEPS}/png16
curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/libpng/libpng16/${VERSION_PNG16}/libpng-${VERSION_PNG16}.tar.xz | tar xJC ${DEPS}/png16 --strip-components=1
cd ${DEPS}/png16
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking
make install-strip
mkdir ${DEPS}/webp
curl -Ls http://downloads.webmproject.org/releases/webp/libwebp-${VERSION_WEBP}.tar.gz | tar xzC ${DEPS}/webp --strip-components=1
cd ${DEPS}/webp
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-neon
make install-strip
mkdir ${DEPS}/tiff
curl -Ls http://download.osgeo.org/libtiff/tiff-${VERSION_TIFF}.tar.gz | tar xzC ${DEPS}/tiff --strip-components=1
cd ${DEPS}/tiff
# Apply patches for various libtiff security vulnerabilities reported since v4.0.6
VERSION_TIFF_GIT_MASTER_SHA=$(curl -Ls https://api.github.com/repos/vadz/libtiff/git/refs/heads/master | jq -r '.object.sha' | head -c7)
curl -Ls https://github.com/vadz/libtiff/compare/Release-v4-0-6...master.patch | patch -p1 -t || true
if [ -n "${CHOST}" ]; then autoreconf -fiv; fi
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-mdi --disable-pixarlog --disable-cxx
make install-strip
mkdir ${DEPS}/orc
curl -Ls http://gstreamer.freedesktop.org/data/src/orc/orc-${VERSION_ORC}.tar.xz | tar xJC ${DEPS}/orc --strip-components=1
cd ${DEPS}/orc
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking
make install-strip
cd ${TARGET}/lib
rm -rf liborc-test-*
mkdir ${DEPS}/gdkpixbuf
curl -Ls https://download.gnome.org/sources/gdk-pixbuf/2.36/gdk-pixbuf-${VERSION_GDKPIXBUF}.tar.xz | tar xJC ${DEPS}/gdkpixbuf --strip-components=1
cd ${DEPS}/gdkpixbuf
LD_LIBRARY_PATH=${TARGET}/lib \
./configure --cache-file=gdkpixbuf.cache --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-introspection --disable-modules --disable-gio-sniffing --without-libtiff --without-gdiplus --with-included-loaders=png,jpeg
make install-strip
mkdir ${DEPS}/freetype
curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/freetype/freetype2/${VERSION_FREETYPE}/freetype-${VERSION_FREETYPE}.tar.gz | tar xzC ${DEPS}/freetype --strip-components=1
cd ${DEPS}/freetype
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static
make install
mkdir ${DEPS}/fontconfig
curl -Ls https://www.freedesktop.org/software/fontconfig/release/fontconfig-${VERSION_FONTCONFIG}.tar.bz2 | tar xjC ${DEPS}/fontconfig --strip-components=1
cd ${DEPS}/fontconfig
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --enable-libxml2 --sysconfdir=/etc
make install-strip
mkdir ${DEPS}/harfbuzz
curl -Ls https://www.freedesktop.org/software/harfbuzz/release/harfbuzz-${VERSION_HARFBUZZ}.tar.bz2 | tar xjC ${DEPS}/harfbuzz --strip-components=1
cd ${DEPS}/harfbuzz
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking
make install-strip
mkdir ${DEPS}/pixman
curl -Ls http://cairographics.org/releases/pixman-${VERSION_PIXMAN}.tar.gz | tar xzC ${DEPS}/pixman --strip-components=1
cd ${DEPS}/pixman
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-libpng --disable-arm-iwmmxt
make install-strip
mkdir ${DEPS}/cairo
curl -Ls http://cairographics.org/releases/cairo-${VERSION_CAIRO}.tar.xz | tar xJC ${DEPS}/cairo --strip-components=1
cd ${DEPS}/cairo
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--disable-xlib --disable-xcb --disable-quartz --disable-win32 --disable-egl --disable-glx --disable-wgl \
--disable-script --disable-ps --disable-gobject --disable-trace --disable-interpreter
make install-strip
mkdir ${DEPS}/pango
curl -Ls https://download.gnome.org/sources/pango/1.40/pango-${VERSION_PANGO}.tar.xz | tar xJC ${DEPS}/pango --strip-components=1
cd ${DEPS}/pango
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking
make install-strip
mkdir ${DEPS}/croco
curl -Ls https://download.gnome.org/sources/libcroco/0.6/libcroco-${VERSION_CROCO}.tar.xz | tar xJC ${DEPS}/croco --strip-components=1
cd ${DEPS}/croco
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking
make install-strip
mkdir ${DEPS}/svg
curl -Ls https://download.gnome.org/sources/librsvg/2.40/librsvg-${VERSION_SVG}.tar.xz | tar xJC ${DEPS}/svg --strip-components=1
cd ${DEPS}/svg
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-introspection --disable-tools --disable-pixbuf-loader
make install-strip
mkdir ${DEPS}/gif
curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/giflib/giflib-${VERSION_GIF}.tar.gz | tar xzC ${DEPS}/gif --strip-components=1
cd ${DEPS}/gif
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking
make install-strip
mkdir ${DEPS}/vips
curl -Ls http://www.vips.ecs.soton.ac.uk/supported/8.4/vips-${VERSION_VIPS}.tar.gz | tar xzC ${DEPS}/vips --strip-components=1
cd ${DEPS}/vips
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--disable-debug --disable-introspection --without-python --without-fftw \
--without-magick --without-pangoft2 --without-ppm --without-analyze --without-radiance \
--with-zip-includes=${TARGET}/include --with-zip-libraries=${TARGET}/lib \
--with-jpeg-includes=${TARGET}/include --with-jpeg-libraries=${TARGET}/lib
make install-strip
# Remove the old C++ bindings
cd ${TARGET}/include
rm -rf vips/vipsc++.h vips/vipscpp.h
cd ${TARGET}/lib
rm -rf pkgconfig .libs *.la libvipsCC*
# Create JSON file of version numbers
cd ${TARGET}
echo "{\n\
\"cairo\": \"${VERSION_CAIRO}\",\n\
\"croco\": \"${VERSION_CROCO}\",\n\
\"exif\": \"${VERSION_EXIF}\",\n\
\"ffi\": \"${VERSION_FFI}\",\n\
\"fontconfig\": \"${VERSION_FONTCONFIG}\",\n\
\"freetype\": \"${VERSION_FREETYPE}\",\n\
\"gdkpixbuf\": \"${VERSION_GDKPIXBUF}\",\n\
\"gif\": \"${VERSION_GIF}\",\n\
\"glib\": \"${VERSION_GLIB}\",\n\
\"gsf\": \"${VERSION_GSF}\",\n\
\"harfbuzz\": \"${VERSION_HARFBUZZ}\",\n\
\"jpeg\": \"${VERSION_JPEG}\",\n\
\"lcms\": \"${VERSION_LCMS2}\",\n\
\"orc\": \"${VERSION_ORC}\",\n\
\"pango\": \"${VERSION_PANGO}\",\n\
\"pixman\": \"${VERSION_PIXMAN}\",\n\
\"png\": \"${VERSION_PNG16}\",\n\
\"svg\": \"${VERSION_SVG}\",\n\
\"tiff\": \"${VERSION_TIFF}-${VERSION_TIFF_GIT_MASTER_SHA}\",\n\
\"vips\": \"${VERSION_VIPS}\",\n\
\"webp\": \"${VERSION_WEBP}\",\n\
\"xml\": \"${VERSION_XML2}\",\n\
\"zlib\": \"${VERSION_ZLIB}\"\n\
}" >lib/versions.json
# Create .tar.gz
tar czf /packaging/libvips-${VERSION_VIPS}-${PLATFORM}.tar.gz include lib
advdef --recompress --shrink-insane /packaging/libvips-${VERSION_VIPS}-${PLATFORM}.tar.gz

View File

@@ -1,22 +0,0 @@
#!/bin/sh
set -e
# Fetch and unzip
mkdir /vips
cd /vips
curl -L -O https://github.com/lovell/build-win64/releases/download/v${VERSION_VIPS}/vips-dev-w64-web-${VERSION_VIPS}.zip
unzip vips-dev-w64-web-${VERSION_VIPS}.zip
# Clean and zip
cd /vips/vips-dev-8.4
rm bin/libvipsCC-42.dll bin/libvips-cpp-42.dll bin/libgsf-win32-1-114.dll
cp bin/*.dll lib/
cp -r lib64/* lib/
# Temp patch for __declspec ordering
curl -L -o include/vips/VImage8.h https://raw.githubusercontent.com/lovell/libvips/e1aef0445bf123d2de000bc7f2ef97b9f788eea0/cplusplus/include/vips/VImage8.h
echo "Creating tarball"
tar czf /packaging/libvips-${VERSION_VIPS}-${PLATFORM}.tar.gz include lib/glib-2.0 lib/libvips.lib lib/libglib-2.0.lib lib/libgobject-2.0.lib lib/*.dll
echo "Shrinking tarball"
advdef --recompress --shrink-insane /packaging/libvips-${VERSION_VIPS}-${PLATFORM}.tar.gz

View File

@@ -1,15 +0,0 @@
FROM socialdefect/raspbian-jessie-core
MAINTAINER Lovell Fuller <npm@lovell.info>
# Create Rasbian-based container suitable for compiling Linux ARMv6 binaries
# Requires the QEMU user mode emulation binaries on the host machine
# Build dependencies
RUN \
apt-get update && \
apt-get install -y build-essential curl autoconf libtool nasm gtk-doc-tools texinfo advancecomp libglib2.0-dev jq
# Compiler settings
ENV \
PLATFORM=linux-armv6 \
FLAGS="-Os"

View File

@@ -1,20 +0,0 @@
FROM debian:jessie
MAINTAINER Lovell Fuller <npm@lovell.info>
# Create Debian-based container suitable for cross-compiling Linux ARMv7-A binaries
# Build dependencies
RUN \
apt-get update && \
apt-get install -y curl && \
echo "deb http://emdebian.org/tools/debian/ jessie main" | tee /etc/apt/sources.list.d/crosstools.list && \
curl http://emdebian.org/tools/debian/emdebian-toolchain-archive.key | apt-key add - && \
dpkg --add-architecture armhf && \
apt-get update && \
apt-get install -y crossbuild-essential-armhf autoconf libtool nasm gtk-doc-tools texinfo advancecomp libglib2.0-dev jq
# Compiler settings
ENV \
PLATFORM=linux-armv7 \
CHOST=arm-linux-gnueabihf \
FLAGS="-marm -march=armv7-a -mfpu=neon-vfpv4 -mfloat-abi=hard -Os"

View File

@@ -1,18 +0,0 @@
FROM debian:stretch
MAINTAINER Lovell Fuller <npm@lovell.info>
# Create Debian-based container suitable for cross-compiling Linux ARMv8-A binaries
# Build dependencies
RUN \
apt-get update && \
apt-get install -y curl && \
dpkg --add-architecture arm64 && \
apt-get update && \
apt-get install -y crossbuild-essential-arm64 autoconf libtool nasm gtk-doc-tools texinfo advancecomp libglib2.0-dev jq
# Compiler settings
ENV \
PLATFORM=linux-armv8 \
CHOST=aarch64-linux-gnu \
FLAGS="-march=armv8-a -Os"

View File

@@ -1,16 +0,0 @@
FROM debian:wheezy
MAINTAINER Lovell Fuller <npm@lovell.info>
# Create Debian-based container suitable for building Linux x64 binaries
# Build dependencies
RUN \
echo "deb http://ftp.debian.org/debian wheezy-backports main" | tee /etc/apt/sources.list.d/wheezy-backports.list && \
apt-get update && \
apt-get install -y build-essential autoconf libtool nasm gtk-doc-tools texinfo advancecomp && \
apt-get -t wheezy-backports install -y jq
# Compiler settings
ENV \
PLATFORM="linux-x64" \
FLAGS="-O3"

View File

@@ -1,28 +0,0 @@
#!/bin/sh
if [ $# -lt 1 ]; then
echo "Usage: $0 IP"
echo "Test sharp on ARM using Docker, where IP is"
echo "the address of a Raspberry Pi running HypriotOS"
exit 1
fi
IP="$1"
echo "Verifying connectivity to $IP"
if ! ping -c 1 $IP; then
echo "Could not connect to $IP"
exit 1
fi
if ! type sshpass >/dev/null; then
echo "Please install sshpass"
exit 1
fi
export SSHPASS=hypriot
echo "Copying sharp source to device"
sshpass -e scp -o PreferredAuthentications=password -r ../../sharp pirate@${IP}:/home/pirate/sharp
echo "Compile and test within container"
sshpass -e ssh -o PreferredAuthentications=password -t pirate@${IP} "docker run --rm -v \${PWD}/sharp:/s hypriot/rpi-node:6 sh -c 'cd /s && npm install --unsafe-perm && npm test'"

View File

@@ -1,56 +0,0 @@
#!/bin/sh
# Verify docker is available
if ! type docker >/dev/null; then
echo "Please install docker"
exit 1
fi
version_node=6.3.0
test="npm run clean; npm install --unsafe-perm; npm test"
# Debian 7, 8
# Ubuntu 14.04
for dist in wheezy jessie trusty; do
echo "Testing $dist..."
if docker run -i -t --rm -v $PWD:/v -e "NODE_ENV=development" nodesource/$dist:$version_node >packaging/$dist.log 2>&1 sh -c "cd /v; ./packaging/test/debian.sh; $test";
then echo "$dist OK"
else echo "$dist fail" && cat packaging/$dist.log
fi
done
# Centos 7
echo "Testing centos7..."
if docker run -i -t --rm -v $PWD:/v -e "NODE_ENV=development" nodesource/centos7:$version_node >packaging/$dist.log 2>&1 sh -c "cd /v; $test";
then echo "centos7 OK"
else echo "centos7 fail" && cat packaging/$dist.log
fi
# Fedora 22
echo "Testing fedora22..."
if docker run -i -t --rm -v $PWD:/v -e "NODE_ENV=development" nodesource/fedora22:$version_node >packaging/$dist.log 2>&1 sh -c "cd /v; $test";
then echo "fedora22 OK"
else echo "fedora22 fail" && cat packaging/$dist.log
fi
# openSUSE 13.2
echo "Testing opensuse..."
if docker run -i -t --rm -v $PWD:/v opensuse:13.2 >packaging/opensuse.log 2>&1 /bin/sh -c "cd /v; ./packaging/test/opensuse.sh; $test";
then echo "opensuse OK"
else echo "opensuse fail" && cat packaging/opensuse.log
fi
# Archlinux 2015.06.01
echo "Testing archlinux..."
if docker run -i -t --rm -v $PWD:/v pritunl/archlinux:latest >packaging/archlinux.log 2>&1 sh -c "cd /v; ./packaging/test/archlinux.sh; $test";
then echo "archlinux OK"
else echo "archlinux fail" && cat packaging/archlinux.log
fi
# Alpine
echo "Testing alpine..."
if docker run -i -t --rm -v $PWD:/v -e "SHARP_TEST_WITHOUT_CACHE=0" alpine:edge >packaging/alpine.log 2>&1 sh -c "cd /v; ./packaging/test/alpine.sh; $test";
then echo "alpine OK"
else echo "alpine fail" && cat packaging/alpine.log
fi

View File

@@ -1,7 +0,0 @@
#!/bin/sh
# Install build dependencies
apk add --update make gcc g++ python nodejs
# Install libvips from aports/testing
apk add --update --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing vips-dev

View File

@@ -1,5 +0,0 @@
#!/bin/sh
# Install Node.js on Archlinux
pacman -Sy --noconfirm gcc make python2 nodejs npm | cat
ln -s /usr/bin/python2 /usr/bin/python

View File

@@ -1,5 +0,0 @@
#!/bin/sh
# Install pkg-config on Debian/Ubuntu
apt-get update
apt-get install -y pkg-config

View File

@@ -1,7 +0,0 @@
#!/bin/sh
# Install Node.js on openSUSE 13.2
zypper addrepo http://download.opensuse.org/repositories/devel:languages:nodejs/openSUSE_13.2/devel:languages:nodejs.repo
zypper --gpg-auto-import-keys refresh
zypper --non-interactive install gcc-c++ make nodejs-devel npm
npm install -g npm

View File

@@ -1,8 +0,0 @@
FROM debian:jessie
MAINTAINER Lovell Fuller <npm@lovell.info>
# Create Debian-based container suitable for post-processing Windows x64 binaries
RUN apt-get update && apt-get install -y curl zip advancecomp
ENV PLATFORM=win32-x64

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors. // Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 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,6 +16,8 @@
#include <string> #include <string>
#include <string.h> #include <string.h>
#include <vector> #include <vector>
#include <queue>
#include <mutex>
#include <node.h> #include <node.h>
#include <node_buffer.h> #include <node_buffer.h>
@@ -29,30 +31,39 @@ 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 v8::Object
bool HasAttr(v8::Handle<v8::Object> obj, std::string attr) { bool HasAttr(v8::Local<v8::Object> obj, std::string attr) {
return Nan::Has(obj, Nan::New(attr).ToLocalChecked()).FromJust(); return Nan::Has(obj, Nan::New(attr).ToLocalChecked()).FromJust();
} }
std::string AttrAsStr(v8::Handle<v8::Object> obj, std::string attr) { std::string AttrAsStr(v8::Local<v8::Object> obj, std::string attr) {
return *Nan::Utf8String(Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked()); return *Nan::Utf8String(Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked());
} }
std::vector<double> AttrAsRgba(v8::Local<v8::Object> obj, std::string attr) {
v8::Local<v8::Object> background = AttrAs<v8::Object>(obj, attr);
std::vector<double> rgba(4);
for (unsigned int i = 0; i < 4; i++) {
rgba[i] = AttrTo<double>(background, i);
}
return rgba;
}
// Create an InputDescriptor instance from a v8::Object describing an input image // Create an InputDescriptor instance from a v8::Object describing an input image
InputDescriptor* CreateInputDescriptor( InputDescriptor* CreateInputDescriptor(
v8::Handle<v8::Object> input, std::vector<v8::Local<v8::Object>> buffersToPersist v8::Local<v8::Object> input, std::vector<v8::Local<v8::Object>> &buffersToPersist
) { ) {
Nan::HandleScope(); 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 { } else if (HasAttr(input, "buffer")) {
v8::Local<v8::Object> buffer = AttrAs<v8::Object>(input, "buffer"); v8::Local<v8::Object> buffer = AttrAs<v8::Object>(input, "buffer");
descriptor->bufferLength = node::Buffer::Length(buffer); descriptor->bufferLength = node::Buffer::Length(buffer);
descriptor->buffer = node::Buffer::Data(buffer); descriptor->buffer = node::Buffer::Data(buffer);
buffersToPersist.push_back(buffer); buffersToPersist.push_back(buffer);
} }
descriptor->failOnError = AttrTo<bool>(input, "failOnError");
// Density for vector-based input // Density for vector-based input
if (HasAttr(input, "density")) { if (HasAttr(input, "density")) {
descriptor->density = AttrTo<uint32_t>(input, "density"); descriptor->density = AttrTo<double>(input, "density");
} }
// Raw pixel input // Raw pixel input
if (HasAttr(input, "rawChannels")) { if (HasAttr(input, "rawChannels")) {
@@ -60,6 +71,20 @@ namespace sharp {
descriptor->rawWidth = AttrTo<uint32_t>(input, "rawWidth"); descriptor->rawWidth = AttrTo<uint32_t>(input, "rawWidth");
descriptor->rawHeight = AttrTo<uint32_t>(input, "rawHeight"); descriptor->rawHeight = AttrTo<uint32_t>(input, "rawHeight");
} }
// Multi-page input (GIF, TIFF, PDF)
if (HasAttr(input, "pages")) {
descriptor->pages = AttrTo<int32_t>(input, "pages");
}
if (HasAttr(input, "page")) {
descriptor->page = AttrTo<uint32_t>(input, "page");
}
// Create new image
if (HasAttr(input, "createChannels")) {
descriptor->createChannels = AttrTo<uint32_t>(input, "createChannels");
descriptor->createWidth = AttrTo<uint32_t>(input, "createWidth");
descriptor->createHeight = AttrTo<uint32_t>(input, "createHeight");
descriptor->createBackground = AttrAsRgba(input, "createBackground");
}
return descriptor; return descriptor;
} }
@@ -115,6 +140,7 @@ namespace sharp {
case ImageType::VIPS: id = "v"; break; case ImageType::VIPS: id = "v"; break;
case ImageType::RAW: id = "raw"; break; case ImageType::RAW: id = "raw"; break;
case ImageType::UNKNOWN: id = "unknown"; break; case ImageType::UNKNOWN: id = "unknown"; break;
case ImageType::MISSING: id = "missing"; break;
} }
return id; return id;
} }
@@ -181,10 +207,24 @@ namespace sharp {
} else if (EndsWith(loader, "Magick") || EndsWith(loader, "MagickFile")) { } else if (EndsWith(loader, "Magick") || EndsWith(loader, "MagickFile")) {
imageType = ImageType::MAGICK; imageType = ImageType::MAGICK;
} }
} else {
if (EndsWith(vips::VError().what(), " not found\n")) {
imageType = ImageType::MISSING;
}
} }
return imageType; return imageType;
} }
/*
Does this image type support multiple pages?
*/
bool ImageTypeSupportsPage(ImageType imageType) {
return
imageType == ImageType::GIF ||
imageType == ImageType::TIFF ||
imageType == ImageType::PDF;
}
/* /*
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)
*/ */
@@ -192,7 +232,6 @@ namespace sharp {
VImage image; VImage image;
ImageType imageType; ImageType imageType;
if (descriptor->buffer != nullptr) { if (descriptor->buffer != nullptr) {
// From buffer
if (descriptor->rawChannels > 0) { if (descriptor->rawChannels > 0) {
// Raw, uncompressed pixel data // Raw, uncompressed pixel data
image = VImage::new_from_memory(descriptor->buffer, descriptor->bufferLength, image = VImage::new_from_memory(descriptor->buffer, descriptor->bufferLength,
@@ -208,45 +247,75 @@ namespace sharp {
imageType = DetermineImageType(descriptor->buffer, descriptor->bufferLength); imageType = DetermineImageType(descriptor->buffer, descriptor->bufferLength);
if (imageType != ImageType::UNKNOWN) { if (imageType != ImageType::UNKNOWN) {
try { try {
vips::VOption *option = VImage::option()->set("access", accessMethod); vips::VOption *option = VImage::option()
->set("access", accessMethod)
->set("fail", descriptor->failOnError);
if (imageType == ImageType::SVG || imageType == ImageType::PDF) { if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
option->set("dpi", static_cast<double>(descriptor->density)); option->set("dpi", descriptor->density);
} }
if (imageType == ImageType::MAGICK) { if (imageType == ImageType::MAGICK) {
option->set("density", std::to_string(descriptor->density).data()); option->set("density", std::to_string(descriptor->density).data());
} }
if (ImageTypeSupportsPage(imageType)) {
option->set("n", descriptor->pages);
option->set("page", descriptor->page);
}
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) {
SetDensity(image, descriptor->density); SetDensity(image, descriptor->density);
} }
} catch (...) { } catch (vips::VError const &err) {
throw vips::VError("Input buffer has corrupt header"); throw vips::VError(std::string("Input buffer has corrupt header: ") + err.what());
} }
} else { } else {
throw vips::VError("Input buffer contains unsupported image format"); throw vips::VError("Input buffer contains unsupported image format");
} }
} }
} else { } else {
// From filesystem if (descriptor->createChannels > 0) {
imageType = DetermineImageType(descriptor->file.data()); // Create new image
if (imageType != ImageType::UNKNOWN) { std::vector<double> background = {
try { descriptor->createBackground[0],
vips::VOption *option = VImage::option()->set("access", accessMethod); descriptor->createBackground[1],
if (imageType == ImageType::SVG || imageType == ImageType::PDF) { descriptor->createBackground[2]
option->set("dpi", static_cast<double>(descriptor->density)); };
} if (descriptor->createChannels == 4) {
if (imageType == ImageType::MAGICK) { background.push_back(descriptor->createBackground[3]);
option->set("density", std::to_string(descriptor->density).data());
}
image = VImage::new_from_file(descriptor->file.data(), option);
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
SetDensity(image, descriptor->density);
}
} catch (...) {
throw vips::VError("Input file has corrupt header");
} }
image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight).new_from_image(background);
image.get_image()->Type = VIPS_INTERPRETATION_sRGB;
imageType = ImageType::RAW;
} else { } else {
throw vips::VError("Input file is missing or of an unsupported image format"); // From filesystem
imageType = DetermineImageType(descriptor->file.data());
if (imageType == ImageType::MISSING) {
throw vips::VError("Input file is missing");
}
if (imageType != ImageType::UNKNOWN) {
try {
vips::VOption *option = VImage::option()
->set("access", accessMethod)
->set("fail", descriptor->failOnError);
if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
option->set("dpi", descriptor->density);
}
if (imageType == ImageType::MAGICK) {
option->set("density", std::to_string(descriptor->density).data());
}
if (ImageTypeSupportsPage(imageType)) {
option->set("n", descriptor->pages);
option->set("page", descriptor->page);
}
image = VImage::new_from_file(descriptor->file.data(), option);
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
SetDensity(image, descriptor->density);
}
} catch (vips::VError const &err) {
throw vips::VError(std::string("Input file has corrupt header: ") + err.what());
}
} else {
throw vips::VError("Input file contains unsupported image format");
}
} }
} }
return std::make_tuple(image, imageType); return std::make_tuple(image, imageType);
@@ -314,13 +383,28 @@ namespace sharp {
/* /*
Set pixels/mm resolution based on a pixels/inch density. Set pixels/mm resolution based on a pixels/inch density.
*/ */
void SetDensity(VImage image, const int density) { void SetDensity(VImage image, const double density) {
const double pixelsPerMm = static_cast<double>(density) / 25.4; const double pixelsPerMm = density / 25.4;
image.set("Xres", pixelsPerMm); image.set("Xres", pixelsPerMm);
image.set("Yres", pixelsPerMm); image.set("Yres", pixelsPerMm);
image.set(VIPS_META_RESOLUTION_UNIT, "in"); image.set(VIPS_META_RESOLUTION_UNIT, "in");
} }
/*
Check the proposed format supports the current dimensions.
*/
void AssertImageTypeDimensions(VImage image, ImageType const imageType) {
if (imageType == ImageType::JPEG) {
if (image.width() > 65535 || image.height() > 65535) {
throw vips::VError("Processed image is too large for the JPEG format");
}
} else if (imageType == ImageType::WEBP) {
if (image.width() > 16383 || image.height() > 16383) {
throw vips::VError("Processed image is too large for the WebP 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
*/ */
@@ -330,9 +414,91 @@ namespace sharp {
} }
} }
/*
Temporary buffer of warnings
*/
std::queue<std::string> vipsWarnings;
std::mutex vipsWarningsMutex;
/*
Called with warnings from the glib-registered "VIPS" domain
*/
void VipsWarningCallback(char const* log_domain, GLogLevelFlags log_level, char const* message, void* ignore) {
std::lock_guard<std::mutex> lock(vipsWarningsMutex);
vipsWarnings.emplace(message);
}
/*
Pop the oldest warning message from the queue
*/
std::string VipsWarningPop() {
std::string warning;
std::lock_guard<std::mutex> lock(vipsWarningsMutex);
if (!vipsWarnings.empty()) {
warning = vipsWarnings.front();
vipsWarnings.pop();
}
return warning;
}
/* /*
Calculate the (left, top) coordinates of the output image Calculate the (left, top) coordinates of the output image
within the input image, applying the given gravity. within the input image, applying the given gravity during an embed.
@Azurebyte: We are basically swapping the inWidth and outWidth, inHeight and outHeight from the CalculateCrop function.
*/
std::tuple<int, int> CalculateEmbedPosition(int const inWidth, int const inHeight,
int const outWidth, int const outHeight, int const gravity) {
int left = 0;
int top = 0;
switch (gravity) {
case 1:
// North
left = (outWidth - inWidth) / 2;
break;
case 2:
// East
left = outWidth - inWidth;
top = (outHeight - inHeight) / 2;
break;
case 3:
// South
left = (outWidth - inWidth) / 2;
top = outHeight - inHeight;
break;
case 4:
// West
top = (outHeight - inHeight) / 2;
break;
case 5:
// Northeast
left = outWidth - inWidth;
break;
case 6:
// Southeast
left = outWidth - inWidth;
top = outHeight - inHeight;
break;
case 7:
// Southwest
top = outHeight - inHeight;
break;
case 8:
// Northwest
// Which is the default is 0,0 so we do not assign anything here.
break;
default:
// Centre
left = (outWidth - inWidth) / 2;
top = (outHeight - inHeight) / 2;
}
return std::make_tuple(left, top);
}
/*
Calculate the (left, top) coordinates of the output image
within the input image, applying the given gravity during a crop.
*/ */
std::tuple<int, int> CalculateCrop(int const inWidth, int const inHeight, std::tuple<int, int> CalculateCrop(int const inWidth, int const inHeight,
int const outWidth, int const outHeight, int const gravity) { int const outWidth, int const outHeight, int const gravity) {
@@ -366,9 +532,11 @@ namespace sharp {
// Southeast // Southeast
left = inWidth - outWidth; left = inWidth - outWidth;
top = inHeight - outHeight; top = inHeight - outHeight;
break;
case 7: case 7:
// Southwest // Southwest
top = inHeight - outHeight; top = inHeight - outHeight;
break;
case 8: case 8:
// Northwest // Northwest
break; break;
@@ -462,4 +630,40 @@ namespace sharp {
} }
} }
/*
Apply the alpha channel to a given colour
*/
std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour) {
// Scale up 8-bit values to match 16-bit input image
double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
// Create alphaColour colour
std::vector<double> alphaColour;
if (image.bands() > 2) {
alphaColour = {
multiplier * colour[0],
multiplier * colour[1],
multiplier * colour[2]
};
} else {
// Convert sRGB to greyscale
alphaColour = { multiplier * (
0.2126 * colour[0] +
0.7152 * colour[1] +
0.0722 * colour[2])
};
}
// Add alpha channel to alphaColour colour
if (colour[3] < 255.0 || HasAlpha(image)) {
alphaColour.push_back(colour[3] * multiplier);
}
// Ensure alphaColour colour uses correct colourspace
alphaColour = sharp::GetRgbaAsColourspace(alphaColour, image.interpretation());
// Add non-transparent alpha channel, if required
if (colour[3] < 255.0 && !HasAlpha(image)) {
image = image.bandjoin(
VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier));
}
return std::make_tuple(image, alphaColour);
}
} // namespace sharp } // namespace sharp

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors. // Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 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,17 +25,17 @@
// Verify platform and compiler compatibility // Verify platform and compiler compatibility
#if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 4)) #if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 7))
#error libvips version 8.4.x required - see sharp.dimens.io/page/install #error libvips version 8.7.0+ is required - see sharp.pixelplumbing.com/page/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.dimens.io/page/install#prerequisites #error GCC version 4.6+ is required for C++11 features - see sharp.pixelplumbing.com/page/install#prerequisites
#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.dimens.io/page/install#prerequisites #error clang version 3.0+ is required for C++11 features - see sharp.pixelplumbing.com/page/install#prerequisites
#endif #endif
#endif #endif
@@ -47,37 +47,52 @@ namespace sharp {
std::string name; std::string name;
std::string file; std::string file;
char *buffer; char *buffer;
bool failOnError;
size_t bufferLength; size_t bufferLength;
int density; double density;
int rawChannels; int rawChannels;
int rawWidth; int rawWidth;
int rawHeight; int rawHeight;
int pages;
int page;
int createChannels;
int createWidth;
int createHeight;
std::vector<double> createBackground;
InputDescriptor(): InputDescriptor():
buffer(nullptr), buffer(nullptr),
failOnError(TRUE),
bufferLength(0), bufferLength(0),
density(72), density(72.0),
rawChannels(0), rawChannels(0),
rawWidth(0), rawWidth(0),
rawHeight(0) {} rawHeight(0),
pages(1),
page(0),
createChannels(0),
createWidth(0),
createHeight(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 v8::Object
bool HasAttr(v8::Handle<v8::Object> obj, std::string attr); bool HasAttr(v8::Local<v8::Object> obj, std::string attr);
std::string AttrAsStr(v8::Handle<v8::Object> obj, std::string attr); std::string AttrAsStr(v8::Local<v8::Object> obj, std::string attr);
template<typename T> v8::Local<T> AttrAs(v8::Handle<v8::Object> obj, std::string attr) { std::vector<double> AttrAsRgba(v8::Local<v8::Object> obj, std::string attr);
template<typename T> v8::Local<T> AttrAs(v8::Local<v8::Object> obj, std::string attr) {
return Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked().As<T>(); return Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked().As<T>();
} }
template<typename T> T AttrTo(v8::Handle<v8::Object> obj, std::string attr) { template<typename T> T AttrTo(v8::Local<v8::Object> obj, std::string attr) {
return Nan::To<T>(Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked()).FromJust(); return Nan::To<T>(Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked()).FromJust();
} }
template<typename T> T AttrTo(v8::Handle<v8::Object> obj, int attr) { template<typename T> T AttrTo(v8::Local<v8::Object> obj, int attr) {
return Nan::To<T>(Nan::Get(obj, attr).ToLocalChecked()).FromJust(); 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 v8::Object describing an input image
InputDescriptor* CreateInputDescriptor( InputDescriptor* CreateInputDescriptor(
v8::Handle<v8::Object> input, std::vector<v8::Local<v8::Object>> buffersToPersist); v8::Local<v8::Object> input, std::vector<v8::Local<v8::Object>> &buffersToPersist);
enum class ImageType { enum class ImageType {
JPEG, JPEG,
@@ -93,7 +108,8 @@ namespace sharp {
FITS, FITS,
VIPS, VIPS,
RAW, RAW,
UNKNOWN UNKNOWN,
MISSING
}; };
// How many tasks are in the queue? // How many tasks are in the queue?
@@ -126,6 +142,11 @@ namespace sharp {
*/ */
ImageType DetermineImageType(char const *file); ImageType DetermineImageType(char const *file);
/*
Does this image type support multiple pages?
*/
bool ImageTypeSupportsPage(ImageType imageType);
/* /*
Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data) Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data)
*/ */
@@ -170,13 +191,35 @@ namespace sharp {
/* /*
Set pixels/mm resolution based on a pixels/inch density. Set pixels/mm resolution based on a pixels/inch density.
*/ */
void SetDensity(VImage image, const int density); void SetDensity(VImage image, const double density);
/*
Check the proposed format supports the current dimensions.
*/
void AssertImageTypeDimensions(VImage image, ImageType const imageType);
/* /*
Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows
*/ */
void FreeCallback(char* data, void* hint); void FreeCallback(char* data, void* hint);
/*
Called with warnings from the glib-registered "VIPS" domain
*/
void VipsWarningCallback(char const* log_domain, GLogLevelFlags log_level, char const* message, void* ignore);
/*
Pop the oldest warning message from the queue
*/
std::string VipsWarningPop();
/*
Calculate the (left, top) coordinates of the output image
within the input image, applying the given gravity during an embed.
*/
std::tuple<int, int> CalculateEmbedPosition(int const inWidth, int const inHeight,
int const outWidth, int const outHeight, int const gravity);
/* /*
Calculate the (left, top) coordinates of the output image Calculate the (left, top) coordinates of the output image
within the input image, applying the given gravity. within the input image, applying the given gravity.
@@ -217,6 +260,11 @@ namespace sharp {
*/ */
std::vector<double> GetRgbaAsColourspace(std::vector<double> const rgba, VipsInterpretation const interpretation); std::vector<double> GetRgbaAsColourspace(std::vector<double> const rgba, VipsInterpretation const interpretation);
/*
Apply the alpha channel to a given colour
*/
std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour);
} // namespace sharp } // namespace sharp
#endif // SRC_COMMON_H_ #endif // SRC_COMMON_H_

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors. // Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 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,9 +25,11 @@
class MetadataWorker : public Nan::AsyncWorker { class MetadataWorker : public Nan::AsyncWorker {
public: public:
MetadataWorker( MetadataWorker(
Nan::Callback *callback, MetadataBaton *baton, Nan::Callback *callback, MetadataBaton *baton, Nan::Callback *debuglog,
std::vector<v8::Local<v8::Object>> const buffersToPersist) std::vector<v8::Local<v8::Object>> const buffersToPersist) :
: Nan::AsyncWorker(callback), baton(baton), buffersToPersist(buffersToPersist) { Nan::AsyncWorker(callback, "sharp:MetadataWorker"),
baton(baton), debuglog(debuglog),
buffersToPersist(buffersToPersist) {
// Protect Buffer objects from GC, keyed on index // Protect Buffer objects from GC, keyed on index
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0, std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t { [this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
@@ -56,9 +58,25 @@ class MetadataWorker : public Nan::AsyncWorker {
baton->height = image.height(); baton->height = image.height();
baton->space = vips_enum_nick(VIPS_TYPE_INTERPRETATION, image.interpretation()); baton->space = vips_enum_nick(VIPS_TYPE_INTERPRETATION, image.interpretation());
baton->channels = image.bands(); baton->channels = image.bands();
baton->depth = vips_enum_nick(VIPS_TYPE_BAND_FORMAT, image.format());
if (sharp::HasDensity(image)) { if (sharp::HasDensity(image)) {
baton->density = sharp::GetDensity(image); baton->density = sharp::GetDensity(image);
} }
if (image.get_typeof("jpeg-chroma-subsample") == VIPS_TYPE_REF_STRING) {
baton->chromaSubsampling = image.get_string("jpeg-chroma-subsample");
}
if (image.get_typeof("interlaced") == G_TYPE_INT) {
baton->isProgressive = image.get_int("interlaced") == 1;
}
if (image.get_typeof("palette-bit-depth") == G_TYPE_INT) {
baton->paletteBitDepth = image.get_int("palette-bit-depth");
}
if (image.get_typeof(VIPS_META_N_PAGES) == G_TYPE_INT) {
baton->pages = image.get_int(VIPS_META_N_PAGES);
}
if (image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT) {
baton->pageHeight = image.get_int(VIPS_META_PAGE_HEIGHT);
}
baton->hasProfile = sharp::HasProfile(image); baton->hasProfile = sharp::HasProfile(image);
// Derived attributes // Derived attributes
baton->hasAlpha = sharp::HasAlpha(image); baton->hasAlpha = sharp::HasAlpha(image);
@@ -79,6 +97,22 @@ class MetadataWorker : public Nan::AsyncWorker {
memcpy(baton->icc, icc, iccLength); memcpy(baton->icc, icc, iccLength);
baton->iccLength = iccLength; baton->iccLength = iccLength;
} }
// IPTC
if (image.get_typeof(VIPS_META_IPTC_NAME) == VIPS_TYPE_BLOB) {
size_t iptcLength;
void const *iptc = image.get_blob(VIPS_META_IPTC_NAME, &iptcLength);
baton->iptc = static_cast<char *>(g_malloc(iptcLength));
memcpy(baton->iptc, iptc, iptcLength);
baton->iptcLength = iptcLength;
}
// XMP
if (image.get_typeof(VIPS_META_XMP_NAME) == VIPS_TYPE_BLOB) {
size_t xmpLength;
void const *xmp = image.get_blob(VIPS_META_XMP_NAME, &xmpLength);
baton->xmp = static_cast<char *>(g_malloc(xmpLength));
memcpy(baton->xmp, xmp, xmpLength);
baton->xmpLength = xmpLength;
}
} }
// Clean up // Clean up
@@ -98,13 +132,32 @@ class MetadataWorker : public Nan::AsyncWorker {
// Metadata Object // Metadata Object
v8::Local<v8::Object> info = New<v8::Object>(); v8::Local<v8::Object> info = New<v8::Object>();
Set(info, New("format").ToLocalChecked(), New<v8::String>(baton->format).ToLocalChecked()); 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("width").ToLocalChecked(), New<v8::Uint32>(baton->width));
Set(info, New("height").ToLocalChecked(), New<v8::Uint32>(baton->height)); Set(info, New("height").ToLocalChecked(), New<v8::Uint32>(baton->height));
Set(info, New("space").ToLocalChecked(), New<v8::String>(baton->space).ToLocalChecked()); Set(info, New("space").ToLocalChecked(), New<v8::String>(baton->space).ToLocalChecked());
Set(info, New("channels").ToLocalChecked(), New<v8::Uint32>(baton->channels)); 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) { if (baton->density > 0) {
Set(info, New("density").ToLocalChecked(), New<v8::Uint32>(baton->density)); 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));
}
Set(info, New("hasProfile").ToLocalChecked(), New<v8::Boolean>(baton->hasProfile)); Set(info, New("hasProfile").ToLocalChecked(), New<v8::Boolean>(baton->hasProfile));
Set(info, New("hasAlpha").ToLocalChecked(), New<v8::Boolean>(baton->hasAlpha)); Set(info, New("hasAlpha").ToLocalChecked(), New<v8::Boolean>(baton->hasAlpha));
if (baton->orientation > 0) { if (baton->orientation > 0) {
@@ -120,6 +173,16 @@ class MetadataWorker : public Nan::AsyncWorker {
New("icc").ToLocalChecked(), New("icc").ToLocalChecked(),
Nan::NewBuffer(baton->icc, baton->iccLength, sharp::FreeCallback, nullptr).ToLocalChecked()); Nan::NewBuffer(baton->icc, baton->iccLength, sharp::FreeCallback, nullptr).ToLocalChecked());
} }
if (baton->iptcLength > 0) {
Set(info,
New("iptc").ToLocalChecked(),
Nan::NewBuffer(baton->iptc, baton->iptcLength, sharp::FreeCallback, nullptr).ToLocalChecked());
}
if (baton->xmpLength > 0) {
Set(info,
New("xmp").ToLocalChecked(),
Nan::NewBuffer(baton->xmp, baton->xmpLength, sharp::FreeCallback, nullptr).ToLocalChecked());
}
argv[1] = info; argv[1] = info;
} }
@@ -132,12 +195,21 @@ class MetadataWorker : public Nan::AsyncWorker {
delete baton->input; delete baton->input;
delete baton; delete baton;
// Handle warnings
std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) {
v8::Local<v8::Value> message[1] = { New(warning).ToLocalChecked() };
debuglog->Call(1, message, async_resource);
warning = sharp::VipsWarningPop();
}
// Return to JavaScript // Return to JavaScript
callback->Call(2, argv); callback->Call(2, argv, async_resource);
} }
private: private:
MetadataBaton* baton; MetadataBaton* baton;
Nan::Callback *debuglog;
std::vector<v8::Local<v8::Object>> buffersToPersist; std::vector<v8::Local<v8::Object>> buffersToPersist;
}; };
@@ -155,9 +227,12 @@ NAN_METHOD(metadata) {
// Input // Input
baton->input = sharp::CreateInputDescriptor(sharp::AttrAs<v8::Object>(options, "input"), buffersToPersist); baton->input = sharp::CreateInputDescriptor(sharp::AttrAs<v8::Object>(options, "input"), buffersToPersist);
// Function to notify of libvips warnings
Nan::Callback *debuglog = new Nan::Callback(sharp::AttrAs<v8::Function>(options, "debuglog"));
// Join queue for worker thread // Join queue for worker thread
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>()); Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>());
Nan::AsyncQueueWorker(new MetadataWorker(callback, baton, buffersToPersist)); Nan::AsyncQueueWorker(new MetadataWorker(callback, baton, debuglog, buffersToPersist));
// Increment queued task counter // Increment queued task counter
g_atomic_int_inc(&sharp::counterQueue); g_atomic_int_inc(&sharp::counterQueue);

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors. // Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 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.
@@ -29,7 +29,13 @@ struct MetadataBaton {
int height; int height;
std::string space; std::string space;
int channels; int channels;
std::string depth;
int density; int density;
std::string chromaSubsampling;
bool isProgressive;
int paletteBitDepth;
int pages;
int pageHeight;
bool hasProfile; bool hasProfile;
bool hasAlpha; bool hasAlpha;
int orientation; int orientation;
@@ -37,6 +43,10 @@ struct MetadataBaton {
size_t exifLength; size_t exifLength;
char *icc; char *icc;
size_t iccLength; size_t iccLength;
char *iptc;
size_t iptcLength;
char *xmp;
size_t xmpLength;
std::string err; std::string err;
MetadataBaton(): MetadataBaton():
@@ -45,13 +55,21 @@ struct MetadataBaton {
height(0), height(0),
channels(0), channels(0),
density(0), density(0),
isProgressive(false),
paletteBitDepth(0),
pages(0),
pageHeight(0),
hasProfile(false), hasProfile(false),
hasAlpha(false), hasAlpha(false),
orientation(0), orientation(0),
exif(nullptr), exif(nullptr),
exifLength(0), exifLength(0),
icc(nullptr), icc(nullptr),
iccLength(0) {} iccLength(0),
iptc(nullptr),
iptcLength(0),
xmp(nullptr),
xmpLength(0) {}
}; };
NAN_METHOD(metadata); NAN_METHOD(metadata);

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors. // Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 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.
@@ -29,162 +29,52 @@ using vips::VError;
namespace sharp { namespace sharp {
/* /*
Alpha composite src over dst with given gravity. Removes alpha channel, if any.
Assumes alpha channels are already premultiplied and will be unpremultiplied after. */
*/ VImage RemoveAlpha(VImage image) {
VImage Composite(VImage src, VImage dst, const int gravity) { if (HasAlpha(image)) {
if (IsInputValidForComposition(src, dst)) { image = image.extract_band(0, VImage::option()->set("n", image.bands() - 1));
// Enlarge overlay src, if required
if (src.width() < dst.width() || src.height() < dst.height()) {
// Calculate the (left, top) coordinates of the output image within the input image, applying the given gravity.
int left;
int top;
std::tie(left, top) = CalculateCrop(dst.width(), dst.height(), src.width(), src.height(), gravity);
// Embed onto transparent background
std::vector<double> background { 0.0, 0.0, 0.0, 0.0 };
src = src.embed(left, top, dst.width(), dst.height(), VImage::option()
->set("extend", VIPS_EXTEND_BACKGROUND)
->set("background", background));
}
return CompositeImage(src, dst);
} }
// If the input was not valid for composition the return the input image itself return image;
return dst;
}
VImage Composite(VImage src, VImage dst, const int x, const int y) {
if (IsInputValidForComposition(src, dst)) {
// Enlarge overlay src, if required
if (src.width() < dst.width() || src.height() < dst.height()) {
// Calculate the (left, top) coordinates of the output image within the input image, applying the given gravity.
int left;
int top;
std::tie(left, top) = CalculateCrop(dst.width(), dst.height(), src.width(), src.height(), x, y);
// Embed onto transparent background
std::vector<double> background { 0.0, 0.0, 0.0, 0.0 };
src = src.embed(left, top, dst.width(), dst.height(), VImage::option()
->set("extend", VIPS_EXTEND_BACKGROUND)
->set("background", background));
}
return CompositeImage(src, dst);
}
// If the input was not valid for composition the return the input image itself
return dst;
}
bool IsInputValidForComposition(VImage src, VImage dst) {
using sharp::CalculateCrop;
using sharp::HasAlpha;
if (!HasAlpha(src)) {
throw VError("Overlay image must have an alpha channel");
}
if (!HasAlpha(dst)) {
throw VError("Image to be overlaid must have an alpha channel");
}
if (src.width() > dst.width() || src.height() > dst.height()) {
throw VError("Overlay image must have same dimensions or smaller");
}
return true;
}
VImage CompositeImage(VImage src, VImage dst) {
// Split src into non-alpha and alpha channels
VImage srcWithoutAlpha = src.extract_band(0, VImage::option()->set("n", src.bands() - 1));
VImage srcAlpha = src[src.bands() - 1] * (1.0 / 255.0);
// Split dst into non-alpha and alpha channels
VImage dstWithoutAlpha = dst.extract_band(0, VImage::option()->set("n", dst.bands() - 1));
VImage dstAlpha = dst[dst.bands() - 1] * (1.0 / 255.0);
//
// Compute normalized output alpha channel:
//
// References:
// - http://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
// - https://github.com/jcupitt/ruby-vips/issues/28#issuecomment-9014826
//
// out_a = src_a + dst_a * (1 - src_a)
// ^^^^^^^^^^^
// t0
VImage t0 = srcAlpha.linear(-1.0, 1.0);
VImage outAlphaNormalized = srcAlpha + dstAlpha * t0;
//
// Compute output RGB channels:
//
// Wikipedia:
// out_rgb = (src_rgb * src_a + dst_rgb * dst_a * (1 - src_a)) / out_a
// ^^^^^^^^^^^
// t0
//
// Omit division by `out_a` since `Compose` is supposed to output a
// premultiplied RGBA image as reversal of premultiplication is handled
// externally.
//
VImage outRGBPremultiplied = srcWithoutAlpha + dstWithoutAlpha * t0;
// Combine RGB and alpha channel into output image:
return outRGBPremultiplied.bandjoin(outAlphaNormalized * 255.0);
} }
/* /*
Cutout src over dst with given gravity. 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
*/ */
VImage Cutout(VImage mask, VImage dst, const int gravity) { VImage Tint(VImage image, double const a, double const b) {
using sharp::CalculateCrop; // Get original colourspace
using sharp::HasAlpha; VipsInterpretation typeBeforeTint = image.interpretation();
using sharp::MaximumImageAlpha; if (typeBeforeTint == VIPS_INTERPRETATION_RGB) {
typeBeforeTint = VIPS_INTERPRETATION_sRGB;
bool maskHasAlpha = HasAlpha(mask);
if (!maskHasAlpha && mask.bands() > 1) {
throw VError("Overlay image must have an alpha channel or one band");
} }
if (!HasAlpha(dst)) { // Extract luminance
throw VError("Image to be overlaid must have an alpha channel"); VImage luminance = image.colourspace(VIPS_INTERPRETATION_LAB)[0];
// Create the tinted version by combining the L from the original and the chroma from the tint
std::vector<double> chroma {a, b};
VImage tinted = luminance
.bandjoin(chroma)
.copy(VImage::option()->set("interpretation", VIPS_INTERPRETATION_LAB))
.colourspace(typeBeforeTint);
// Attach original alpha channel, if any
if (HasAlpha(image)) {
// Extract original alpha channel
VImage alpha = image[image.bands() - 1];
// Join alpha channel to normalised image
tinted = tinted.bandjoin(alpha);
} }
if (mask.width() > dst.width() || mask.height() > dst.height()) { return tinted;
throw VError("Overlay image must have same dimensions or smaller");
}
// Enlarge overlay mask, if required
if (mask.width() < dst.width() || mask.height() < dst.height()) {
// Calculate the (left, top) coordinates of the output image within the input image, applying the given gravity.
int left;
int top;
std::tie(left, top) = CalculateCrop(dst.width(), dst.height(), mask.width(), mask.height(), gravity);
// Embed onto transparent background
std::vector<double> background { 0.0, 0.0, 0.0, 0.0 };
mask = mask.embed(left, top, dst.width(), dst.height(), VImage::option()
->set("extend", VIPS_EXTEND_BACKGROUND)
->set("background", background));
}
// we use the mask alpha if it has alpha
if (maskHasAlpha) {
mask = mask.extract_band(mask.bands() - 1, VImage::option()->set("n", 1));;
}
// Split dst into an optional alpha
VImage dstAlpha = dst.extract_band(dst.bands() - 1, VImage::option()->set("n", 1));
// we use the dst non-alpha
dst = dst.extract_band(0, VImage::option()->set("n", dst.bands() - 1));
// the range of the mask and the image need to match .. one could be
// 16-bit, one 8-bit
double const dstMax = MaximumImageAlpha(dst.interpretation());
double const maskMax = MaximumImageAlpha(mask.interpretation());
// combine the new mask and the existing alpha ... there are
// many ways of doing this, mult is the simplest
mask = dstMax * ((mask / maskMax) * (dstAlpha / dstMax));
// append the mask to the image data ... the mask might be float now,
// we must cast the format down to match the image data
return dst.bandjoin(mask.cast(dst.format()));
} }
/* /*
@@ -231,10 +121,8 @@ namespace sharp {
VImage Gamma(VImage image, double const exponent) { VImage Gamma(VImage image, double const exponent) {
if (HasAlpha(image)) { if (HasAlpha(image)) {
// Separate alpha channel // Separate alpha channel
VImage imageWithoutAlpha = image.extract_band(0,
VImage::option()->set("n", image.bands() - 1));
VImage alpha = image[image.bands() - 1]; VImage alpha = image[image.bands() - 1];
return imageWithoutAlpha.gamma(VImage::option()->set("exponent", exponent)).bandjoin(alpha); return RemoveAlpha(image).gamma(VImage::option()->set("exponent", exponent)).bandjoin(alpha);
} else { } else {
return image.gamma(VImage::option()->set("exponent", exponent)); return image.gamma(VImage::option()->set("exponent", exponent));
} }
@@ -278,6 +166,40 @@ namespace sharp {
return image.conv(kernel); return image.conv(kernel);
} }
/*
* Recomb with a Matrix of the given bands/channel size.
* Eg. RGB will be a 3x3 matrix.
*/
VImage Recomb(VImage image, std::unique_ptr<double[]> const &matrix) {
double *m = matrix.get();
return image
.colourspace(VIPS_INTERPRETATION_sRGB)
.recomb(image.bands() == 3
? VImage::new_from_memory(
m, 9 * sizeof(double), 3, 3, 1, VIPS_FORMAT_DOUBLE
)
: VImage::new_matrixv(4, 4,
m[0], m[1], m[2], 0.0,
m[3], m[4], m[5], 0.0,
m[6], m[7], m[8], 0.0,
0.0, 0.0, 0.0, 1.0));
}
VImage Modulate(VImage image, double const brightness, double const saturation, int const hue) {
if (HasAlpha(image)) {
// Separate alpha channel
VImage alpha = image[image.bands() - 1];
return RemoveAlpha(image)
.colourspace(VIPS_INTERPRETATION_LCH)
.linear({brightness, saturation, 1}, {0, 0, static_cast<double>(hue)})
.bandjoin(alpha);
} else {
return image
.colourspace(VIPS_INTERPRETATION_LCH)
.linear({brightness, saturation, 1}, {0, 0, static_cast<double>(hue)});
}
}
/* /*
* Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen. * Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
*/ */
@@ -302,135 +224,6 @@ namespace sharp {
} }
} }
/*
Calculate the Shannon entropy
*/
double EntropyStrategy::operator()(VImage image) {
return image.hist_find().hist_entropy();
}
/*
Calculate the intensity of edges, skin tone and saturation
*/
double AttentionStrategy::operator()(VImage image) {
// Flatten RGBA onto a mid-grey background
if (image.bands() == 4 && HasAlpha(image)) {
double const midgrey = sharp::Is16Bit(image.interpretation()) ? 32768.0 : 128.0;
std::vector<double> background { midgrey, midgrey, midgrey };
image = image.flatten(VImage::option()->set("background", background));
}
// Convert to LAB colourspace
VImage lab = image.colourspace(VIPS_INTERPRETATION_LAB);
VImage l = lab[0];
VImage a = lab[1];
VImage b = lab[2];
// Edge detect luminosity with the Sobel operator
VImage sobel = vips::VImage::new_matrixv(3, 3,
-1.0, 0.0, 1.0,
-2.0, 0.0, 2.0,
-1.0, 0.0, 1.0);
VImage edges = l.conv(sobel).abs() + l.conv(sobel.rot90()).abs();
// Skin tone chroma thresholds trained with http://humanae.tumblr.com/
VImage skin = (a >= 3) & (a <= 22) & (b >= 4) & (b <= 31);
// Chroma >~50% saturation
VImage lch = lab.colourspace(VIPS_INTERPRETATION_LCH);
VImage c = lch[1];
VImage saturation = c > 60;
// Find maximum in combined saliency mask
VImage mask = edges + skin + saturation;
return mask.max();
}
/*
Calculate crop area based on image entropy
*/
std::tuple<int, int> Crop(
VImage image, int const outWidth, int const outHeight, std::function<double(VImage)> strategy
) {
int left = 0;
int top = 0;
int const inWidth = image.width();
int const inHeight = image.height();
if (inWidth > outWidth) {
// Reduce width by repeated removing slices from edge with lowest score
int width = inWidth;
double leftScore = 0.0;
double rightScore = 0.0;
// Max width of each slice
int const maxSliceWidth = static_cast<int>(ceil((inWidth - outWidth) / 8.0));
while (width > outWidth) {
// Width of current slice
int const slice = std::min(width - outWidth, maxSliceWidth);
if (leftScore == 0.0) {
// Update score of left slice
leftScore = strategy(image.extract_area(left, 0, slice, inHeight));
}
if (rightScore == 0.0) {
// Update score of right slice
rightScore = strategy(image.extract_area(width - slice - 1, 0, slice, inHeight));
}
// Keep slice with highest score
if (leftScore >= rightScore) {
// Discard right slice
rightScore = 0.0;
} else {
// Discard left slice
leftScore = 0.0;
left = left + slice;
}
width = width - slice;
}
}
if (inHeight > outHeight) {
// Reduce height by repeated removing slices from edge with lowest score
int height = inHeight;
double topScore = 0.0;
double bottomScore = 0.0;
// Max height of each slice
int const maxSliceHeight = static_cast<int>(ceil((inHeight - outHeight) / 8.0));
while (height > outHeight) {
// Height of current slice
int const slice = std::min(height - outHeight, maxSliceHeight);
if (topScore == 0.0) {
// Update score of top slice
topScore = strategy(image.extract_area(0, top, inWidth, slice));
}
if (bottomScore == 0.0) {
// Update score of bottom slice
bottomScore = strategy(image.extract_area(0, height - slice - 1, inWidth, slice));
}
// Keep slice with highest score
if (topScore >= bottomScore) {
// Discard bottom slice
bottomScore = 0.0;
} else {
// Discard top slice
topScore = 0.0;
top = top + slice;
}
height = height - slice;
}
}
return std::make_tuple(left, top);
}
/*
Insert a tile cache to prevent over-computation of any previous operations in the pipeline
*/
VImage TileCache(VImage image, double const factor) {
int tile_width;
int tile_height;
int scanline_count;
vips_get_tile_size(image.get_image(), &tile_width, &tile_height, &scanline_count);
double const need_lines = 1.2 * scanline_count / factor;
return image.tilecache(VImage::option()
->set("tile_width", image.width())
->set("tile_height", 10)
->set("max_tiles", static_cast<int>(round(1.0 + need_lines / 10.0)))
->set("access", VIPS_ACCESS_SEQUENTIAL)
->set("threaded", TRUE));
}
VImage Threshold(VImage image, double const threshold, bool const thresholdGrayscale) { VImage Threshold(VImage image, double const threshold, bool const thresholdGrayscale) {
if (!thresholdGrayscale) { if (!thresholdGrayscale) {
return image >= threshold; return image >= threshold;
@@ -453,56 +246,35 @@ namespace sharp {
return image.boolean(imageR, boolean); return image.boolean(imageR, boolean);
} }
VImage Trim(VImage image, int const tolerance) { /*
using sharp::MaximumImageAlpha; Trim an image
// An equivalent of ImageMagick's -trim in C++ ... automatically remove */
// "boring" image edges. VImage Trim(VImage image, double const threshold) {
// Top-left pixel provides the background colour
// We use .project to sum the rows and columns of a 0/255 mask image, the first VImage background = image.extract_area(0, 0, 1, 1);
// non-zero row or column is the object edge. We make the mask image with an if (HasAlpha(background)) {
// amount-different-from-background image plus a threshold. background = background.flatten();
}
// find the value of the pixel at (0, 0) ... we will search for all pixels int top, width, height;
// significantly different from this int const left = image.find_trim(&top, &width, &height, VImage::option()
std::vector<double> background = image(0, 0); ->set("background", background(0, 0))
->set("threshold", threshold));
double const max = MaximumImageAlpha(image.interpretation()); if (width == 0 || height == 0) {
// we need to smooth the image, subtract the background from every pixel, take
// the absolute value of the difference, then threshold
VImage mask = (image.median(3) - background).abs() > (max * tolerance / 100);
// sum mask rows and columns, then search for the first non-zero sum in each
// direction
VImage rows;
VImage columns = mask.project(&rows);
VImage profileLeftV;
VImage profileLeftH = columns.profile(&profileLeftV);
VImage profileRightV;
VImage profileRightH = columns.fliphor().profile(&profileRightV);
VImage profileTopV;
VImage profileTopH = rows.profile(&profileTopV);
VImage profileBottomV;
VImage profileBottomH = rows.flipver().profile(&profileBottomV);
int left = static_cast<int>(floor(profileLeftV.min()));
int right = columns.width() - static_cast<int>(floor(profileRightV.min()));
int top = static_cast<int>(floor(profileTopH.min()));
int bottom = rows.height() - static_cast<int>(floor(profileBottomH.min()));
int width = right - left;
int height = bottom - top;
if (width <= 0 || height <= 0) {
throw VError("Unexpected error while trimming. Try to lower the tolerance"); throw VError("Unexpected error while trimming. Try to lower the tolerance");
} }
// and now crop the original image
return image.extract_area(left, top, width, height); return image.extract_area(left, top, width, height);
} }
/*
* Calculate (a * in + b)
*/
VImage Linear(VImage image, double const a, double const b) {
if (HasAlpha(image)) {
// Separate alpha channel
VImage alpha = image[image.bands() - 1];
return RemoveAlpha(image).linear(a, b).bandjoin(alpha);
} else {
return image.linear(a, b);
}
}
} // namespace sharp } // namespace sharp

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors. // Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 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.
@@ -26,31 +26,19 @@ using vips::VImage;
namespace sharp { namespace sharp {
/* /*
Alpha composite src over dst with given gravity. Removes alpha channel, if any.
Assumes alpha channels are already premultiplied and will be unpremultiplied after. */
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
*/ */
VImage Composite(VImage src, VImage dst, const int gravity); VImage Tint(VImage image, double const a, double const b);
/*
Alpha composite src over dst with given x and y offsets.
Assumes alpha channels are already premultiplied and will be unpremultiplied after.
*/
VImage Composite(VImage src, VImage dst, const int x, const int y);
/*
Check if the src and dst Images for composition operation are valid
*/
bool IsInputValidForComposition(VImage src, VImage dst);
/*
Given a valid src and dst, returns the composite of the two images
*/
VImage CompositeImage(VImage src, VImage dst);
/*
Cutout src over dst with given gravity.
*/
VImage Cutout(VImage src, VImage dst, const int gravity);
/* /*
* Stretch luminance to cover full dynamic range. * Stretch luminance to cover full dynamic range.
@@ -78,27 +66,6 @@ namespace sharp {
*/ */
VImage Sharpen(VImage image, double const sigma, double const flat, double const jagged); VImage Sharpen(VImage image, double const sigma, double const flat, double const jagged);
/*
Crop strategy functors
*/
struct EntropyStrategy {
double operator()(VImage image);
};
struct AttentionStrategy {
double operator()(VImage image);
};
/*
Calculate crop area based on given strategy (Entropy, Attention)
*/
std::tuple<int, int> Crop(
VImage image, int const outWidth, int const outHeight, std::function<double(VImage)> strategy);
/*
Insert a tile cache to prevent over-computation of any previous operations in the pipeline
*/
VImage TileCache(VImage image, double const factor);
/* /*
Threshold an image Threshold an image
*/ */
@@ -117,7 +84,23 @@ namespace sharp {
/* /*
Trim an image Trim an image
*/ */
VImage Trim(VImage image, int const tolerance); VImage Trim(VImage image, double const threshold);
/*
* Linear adjustment (a * in + b)
*/
VImage Linear(VImage image, double const a, double const b);
/*
* Recomb with a Matrix of the given bands/channel size.
* Eg. RGB will be a 3x3 matrix.
*/
VImage Recomb(VImage image, std::unique_ptr<double[]> const &matrix);
/*
* Modulate brightness, saturation and hue
*/
VImage Modulate(VImage image, double const brightness, double const saturation, int const hue);
} // namespace sharp } // namespace sharp

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors. // Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 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.
@@ -34,6 +34,23 @@ enum class Canvas {
IGNORE_ASPECT IGNORE_ASPECT
}; };
struct Composite {
sharp::InputDescriptor *input;
VipsBlendMode mode;
int gravity;
int left;
int top;
bool tile;
Composite():
input(nullptr),
mode(VIPS_BLEND_MODE_OVER),
gravity(0),
left(-1),
top(-1),
tile(false) {}
};
struct PipelineBaton { struct PipelineBaton {
sharp::InputDescriptor *input; sharp::InputDescriptor *input;
std::string iccProfilePath; std::string iccProfilePath;
@@ -42,12 +59,7 @@ struct PipelineBaton {
std::string fileOut; std::string fileOut;
void *bufferOut; void *bufferOut;
size_t bufferOutLength; size_t bufferOutLength;
sharp::InputDescriptor *overlay; std::vector<Composite *> composite;
int overlayGravity;
int overlayXOffset;
int overlayYOffset;
bool overlayTile;
bool overlayCutout;
std::vector<sharp::InputDescriptor *> joinChannelIn; std::vector<sharp::InputDescriptor *> joinChannelIn;
int topOffsetPre; int topOffsetPre;
int leftOffsetPre; int leftOffsetPre;
@@ -61,26 +73,42 @@ struct PipelineBaton {
int height; int height;
int channels; int channels;
Canvas canvas; Canvas canvas;
int crop; int position;
int cropCalcLeft; std::vector<double> resizeBackground;
int cropCalcTop; bool hasCropOffset;
int cropOffsetLeft;
int cropOffsetTop;
bool premultiplied;
std::string kernel; std::string kernel;
std::string interpolator; bool fastShrinkOnLoad;
bool centreSampling; double tintA;
double background[4]; double tintB;
bool flatten; bool flatten;
std::vector<double> flattenBackground;
bool negate; bool negate;
double blurSigma; double blurSigma;
double brightness;
double saturation;
int hue;
int medianSize;
double sharpenSigma; double sharpenSigma;
double sharpenFlat; double sharpenFlat;
double sharpenJagged; double sharpenJagged;
int threshold; int threshold;
bool thresholdGrayscale; bool thresholdGrayscale;
int trimTolerance; double trimThreshold;
int trimOffsetLeft;
int trimOffsetTop;
double linearA;
double linearB;
double gamma; double gamma;
double gammaOut;
bool greyscale; bool greyscale;
bool normalise; bool normalise;
bool useExifOrientation;
int angle; int angle;
double rotationAngle;
std::vector<double> rotationBackground;
bool rotateBeforePreExtract; bool rotateBeforePreExtract;
bool flip; bool flip;
bool flop; bool flop;
@@ -88,22 +116,38 @@ struct PipelineBaton {
int extendBottom; int extendBottom;
int extendLeft; int extendLeft;
int extendRight; int extendRight;
std::vector<double> extendBackground;
bool withoutEnlargement; bool withoutEnlargement;
VipsAccess accessMethod; VipsAccess accessMethod;
int jpegQuality; int jpegQuality;
bool jpegProgressive; bool jpegProgressive;
std::string jpegChromaSubsampling; std::string jpegChromaSubsampling;
bool jpegTrellisQuantisation; bool jpegTrellisQuantisation;
int jpegQuantisationTable;
bool jpegOvershootDeringing; bool jpegOvershootDeringing;
bool jpegOptimiseScans; bool jpegOptimiseScans;
bool jpegOptimiseCoding;
bool pngProgressive; bool pngProgressive;
int pngCompressionLevel; int pngCompressionLevel;
bool pngAdaptiveFiltering; bool pngAdaptiveFiltering;
bool pngPalette;
int pngQuality;
int pngColours;
double pngDither;
int webpQuality; int webpQuality;
int webpAlphaQuality; int webpAlphaQuality;
bool webpNearLossless; bool webpNearLossless;
bool webpLossless; bool webpLossless;
int tiffQuality; int tiffQuality;
VipsForeignTiffCompression tiffCompression;
VipsForeignTiffPredictor tiffPredictor;
bool tiffPyramid;
bool tiffSquash;
bool tiffTile;
int tiffTileHeight;
int tiffTileWidth;
double tiffXres;
double tiffYres;
std::string err; std::string err;
bool withMetadata; bool withMetadata;
int withMetadataOrientation; int withMetadataOrientation;
@@ -116,62 +160,93 @@ struct PipelineBaton {
VipsOperationBoolean booleanOp; VipsOperationBoolean booleanOp;
VipsOperationBoolean bandBoolOp; VipsOperationBoolean bandBoolOp;
int extractChannel; int extractChannel;
bool removeAlpha;
bool ensureAlpha;
VipsInterpretation colourspace; VipsInterpretation colourspace;
int tileSize; int tileSize;
int tileOverlap; int tileOverlap;
VipsForeignDzContainer tileContainer; VipsForeignDzContainer tileContainer;
VipsForeignDzLayout tileLayout; VipsForeignDzLayout tileLayout;
std::string tileFormat; std::string tileFormat;
int tileAngle;
VipsForeignDzDepth tileDepth;
std::unique_ptr<double[]> recombMatrix;
PipelineBaton(): PipelineBaton():
input(nullptr), input(nullptr),
limitInputPixels(0), limitInputPixels(0),
bufferOutLength(0), bufferOutLength(0),
overlay(nullptr),
overlayGravity(0),
overlayXOffset(-1),
overlayYOffset(-1),
overlayTile(false),
overlayCutout(false),
topOffsetPre(-1), topOffsetPre(-1),
topOffsetPost(-1), topOffsetPost(-1),
channels(0), channels(0),
canvas(Canvas::CROP), canvas(Canvas::CROP),
crop(0), position(0),
cropCalcLeft(-1), resizeBackground{ 0.0, 0.0, 0.0, 255.0 },
cropCalcTop(-1), hasCropOffset(false),
centreSampling(false), cropOffsetLeft(0),
cropOffsetTop(0),
premultiplied(false),
tintA(128.0),
tintB(128.0),
flatten(false), flatten(false),
flattenBackground{ 0.0, 0.0, 0.0 },
negate(false), negate(false),
blurSigma(0.0), blurSigma(0.0),
brightness(1.0),
saturation(1.0),
hue(0.0),
medianSize(0),
sharpenSigma(0.0), sharpenSigma(0.0),
sharpenFlat(1.0), sharpenFlat(1.0),
sharpenJagged(2.0), sharpenJagged(2.0),
threshold(0), threshold(0),
thresholdGrayscale(true), thresholdGrayscale(true),
trimTolerance(0), trimThreshold(0.0),
trimOffsetLeft(0),
trimOffsetTop(0),
linearA(1.0),
linearB(0.0),
gamma(0.0), gamma(0.0),
greyscale(false), greyscale(false),
normalise(false), normalise(false),
useExifOrientation(false),
angle(0), angle(0),
rotationAngle(0.0),
rotationBackground{ 0.0, 0.0, 0.0, 255.0 },
flip(false), flip(false),
flop(false), flop(false),
extendTop(0), extendTop(0),
extendBottom(0), extendBottom(0),
extendLeft(0), extendLeft(0),
extendRight(0), extendRight(0),
extendBackground{ 0.0, 0.0, 0.0, 255.0 },
withoutEnlargement(false), withoutEnlargement(false),
jpegQuality(80), jpegQuality(80),
jpegProgressive(false), jpegProgressive(false),
jpegChromaSubsampling("4:2:0"), jpegChromaSubsampling("4:2:0"),
jpegTrellisQuantisation(false), jpegTrellisQuantisation(false),
jpegQuantisationTable(0),
jpegOvershootDeringing(false), jpegOvershootDeringing(false),
jpegOptimiseScans(false), jpegOptimiseScans(false),
jpegOptimiseCoding(true),
pngProgressive(false), pngProgressive(false),
pngCompressionLevel(6), pngCompressionLevel(9),
pngAdaptiveFiltering(true), pngAdaptiveFiltering(false),
pngPalette(false),
pngQuality(100),
pngColours(256),
pngDither(1.0),
webpQuality(80), webpQuality(80),
tiffQuality(80), tiffQuality(80),
tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG),
tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL),
tiffPyramid(false),
tiffSquash(false),
tiffTile(false),
tiffTileHeight(256),
tiffTileWidth(256),
tiffXres(1.0),
tiffYres(1.0),
withMetadata(false), withMetadata(false),
withMetadataOrientation(-1), withMetadataOrientation(-1),
convKernelWidth(0), convKernelWidth(0),
@@ -182,16 +257,15 @@ struct PipelineBaton {
booleanOp(VIPS_OPERATION_BOOLEAN_LAST), booleanOp(VIPS_OPERATION_BOOLEAN_LAST),
bandBoolOp(VIPS_OPERATION_BOOLEAN_LAST), bandBoolOp(VIPS_OPERATION_BOOLEAN_LAST),
extractChannel(-1), extractChannel(-1),
removeAlpha(false),
ensureAlpha(false),
colourspace(VIPS_INTERPRETATION_LAST), colourspace(VIPS_INTERPRETATION_LAST),
tileSize(256), tileSize(256),
tileOverlap(0), tileOverlap(0),
tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS), tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS),
tileLayout(VIPS_FOREIGN_DZ_LAYOUT_DZ) { tileLayout(VIPS_FOREIGN_DZ_LAYOUT_DZ),
background[0] = 0.0; tileAngle(0),
background[1] = 0.0; tileDepth(VIPS_FOREIGN_DZ_DEPTH_LAST) {}
background[2] = 0.0;
background[3] = 255.0;
}
}; };
#endif // SRC_PIPELINE_H_ #endif // SRC_PIPELINE_H_

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors. // Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 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.
@@ -20,10 +20,14 @@
#include "metadata.h" #include "metadata.h"
#include "pipeline.h" #include "pipeline.h"
#include "utilities.h" #include "utilities.h"
#include "stats.h"
NAN_MODULE_INIT(init) { NAN_MODULE_INIT(init) {
vips_init("sharp"); vips_init("sharp");
g_log_set_handler("VIPS", static_cast<GLogLevelFlags>(G_LOG_LEVEL_WARNING),
static_cast<GLogFunc>(sharp::VipsWarningCallback), nullptr);
// Methods available to JavaScript // Methods available to JavaScript
Nan::Set(target, Nan::New("metadata").ToLocalChecked(), Nan::Set(target, Nan::New("metadata").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(metadata)).ToLocalChecked()); Nan::GetFunction(Nan::New<v8::FunctionTemplate>(metadata)).ToLocalChecked());
@@ -43,6 +47,8 @@ NAN_MODULE_INIT(init) {
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(format)).ToLocalChecked()); Nan::GetFunction(Nan::New<v8::FunctionTemplate>(format)).ToLocalChecked());
Nan::Set(target, Nan::New("_maxColourDistance").ToLocalChecked(), Nan::Set(target, Nan::New("_maxColourDistance").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(_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());
} }
NODE_MODULE(sharp, init) NODE_MODULE(sharp, init)

192
src/stats.cc Normal file
View File

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

67
src/stats.h Normal file
View File

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

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors. // Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 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.
@@ -259,7 +259,7 @@ NAN_METHOD(_maxColourDistance) {
} }
// Calculate colour distance // Calculate colour distance
maxColourDistance = image1.dE00(image2).max(); maxColourDistance = image1.dE00(image2).max();
} catch (VError err) { } catch (VError const &err) {
return ThrowError(err.what()); return ThrowError(err.what());
} }

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors. // Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 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

@@ -5,21 +5,20 @@
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"description": "Benchmark and performance tests for sharp", "description": "Benchmark and performance tests for sharp",
"scripts": { "scripts": {
"test": "VIPS_WARNING=0 node perf && node random && node parallel" "test": "node perf && node random && node parallel"
}, },
"devDependencies": { "devDependencies": {
"async": "^2.1.4", "async": "^2.6.1",
"benchmark": "^2.1.2", "benchmark": "^2.1.4",
"gm": "^1.23.0", "gm": "^1.23.1",
"imagemagick": "^0.1.3", "imagemagick": "^0.1.3",
"imagemagick-native": "^1.9.3", "imagemagick-native": "^1.9.3",
"jimp": "^0.2.27", "jimp": "^0.5.3",
"lwip": "^0.0.9", "mapnik": "^4.0.1",
"mapnik": "^3.5.14", "semver": "^5.5.1"
"semver": "^5.3.0"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
"node": ">=4" "node": ">=6"
} }
} }

View File

@@ -31,6 +31,7 @@ async.mapSeries([1, 1, 2, 4, 8, 16, 32, 64], function (parallelism, next) {
function (err, ids) { function (err, ids) {
assert(!err); assert(!err);
assert(ids.length === parallelism); assert(ids.length === parallelism);
ids.sort();
const mean = ids.reduce(function (a, b) { const mean = ids.reduce(function (a, b) {
return a + b; return a + b;
}) / ids.length; }) / ids.length;

View File

@@ -7,23 +7,17 @@ const assert = require('assert');
const Benchmark = require('benchmark'); const Benchmark = require('benchmark');
// Contenders // Contenders
const sharp = require('../../');
const gm = require('gm'); const gm = require('gm');
const imagemagick = require('imagemagick'); const imagemagick = require('imagemagick');
const mapnik = require('mapnik'); const mapnik = require('mapnik');
const jimp = require('jimp'); const jimp = require('jimp');
const sharp = require('../../');
let imagemagickNative; let imagemagickNative;
try { try {
imagemagickNative = require('imagemagick-native'); imagemagickNative = require('imagemagick-native');
} catch (err) { } catch (err) {
console.log('Excluding imagemagick-native'); console.log('Excluding imagemagick-native');
} }
let lwip;
try {
lwip = require('lwip');
} catch (err) {
console.log('Excluding lwip');
}
const fixtures = require('../fixtures'); const fixtures = require('../fixtures');
@@ -32,8 +26,6 @@ const height = 588;
// Disable libvips cache to ensure tests are as fair as they can be // Disable libvips cache to ensure tests are as fair as they can be
sharp.cache(false); sharp.cache(false);
// Enable use of SIMD
sharp.simd(true);
async.series({ async.series({
'jpeg': function (callback) { 'jpeg': function (callback) {
@@ -48,7 +40,7 @@ async.series({
throw err; throw err;
} else { } else {
image image
.resize(width, height) .resize(width, height, jimp.RESIZE_BICUBIC)
.quality(80) .quality(80)
.getBuffer(jimp.MIME_JPEG, function (err) { .getBuffer(jimp.MIME_JPEG, function (err) {
if (err) { if (err) {
@@ -68,7 +60,7 @@ async.series({
throw err; throw err;
} else { } else {
image image
.resize(width, height) .resize(width, height, jimp.RESIZE_BICUBIC)
.quality(80) .quality(80)
.write(fixtures.outputJpg, function (err) { .write(fixtures.outputJpg, function (err) {
if (err) { if (err) {
@@ -81,51 +73,6 @@ async.series({
}); });
} }
}); });
// lwip
if (typeof lwip !== 'undefined') {
jpegSuite.add('lwip-file-file', {
defer: true,
fn: function (deferred) {
lwip.open(fixtures.inputJpg, function (err, image) {
if (err) {
throw err;
}
image.resize(width, height, 'lanczos', function (err, image) {
if (err) {
throw err;
}
image.writeFile(fixtures.outputJpg, {quality: 80}, function (err) {
if (err) {
throw err;
}
deferred.resolve();
});
});
});
}
}).add('lwip-buffer-buffer', {
defer: true,
fn: function (deferred) {
lwip.open(inputJpgBuffer, 'jpg', function (err, image) {
if (err) {
throw err;
}
image.resize(width, height, 'lanczos', function (err, image) {
if (err) {
throw err;
}
image.toBuffer('jpg', {quality: 80}, function (err, buffer) {
if (err) {
throw err;
}
assert.notStrictEqual(null, buffer);
deferred.resolve();
});
});
});
}
});
}
// mapnik // mapnik
jpegSuite.add('mapnik-file-file', { jpegSuite.add('mapnik-file-file', {
defer: true, defer: true,
@@ -145,7 +92,7 @@ async.series({
}).add('mapnik-buffer-buffer', { }).add('mapnik-buffer-buffer', {
defer: true, defer: true,
fn: function (deferred) { fn: function (deferred) {
mapnik.Image.fromBytes(inputJpgBuffer, function (err, img) { mapnik.Image.fromBytes(inputJpgBuffer, { max_size: 3000 }, function (err, img) {
if (err) throw err; if (err) throw err;
img img
.resize(width, height, { .resize(width, height, {
@@ -555,8 +502,10 @@ async.series({
defer: true, defer: true,
fn: function (deferred) { fn: function (deferred) {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height, {
.crop(sharp.strategy.entropy) fit: 'cover',
position: sharp.strategy.entropy
})
.toBuffer(function (err, buffer) { .toBuffer(function (err, buffer) {
if (err) { if (err) {
throw err; throw err;
@@ -570,8 +519,10 @@ async.series({
defer: true, defer: true,
fn: function (deferred) { fn: function (deferred) {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height, {
.crop(sharp.strategy.attention) fit: 'cover',
position: sharp.strategy.attention
})
.toBuffer(function (err, buffer) { .toBuffer(function (err, buffer) {
if (err) { if (err) {
throw err; throw err;
@@ -587,7 +538,7 @@ async.series({
callback(null, this.filter('fastest').map('name')); callback(null, this.filter('fastest').map('name'));
}).run(); }).run();
}, },
// Comparitive speed of kernels // Comparative speed of kernels
kernels: function (callback) { kernels: function (callback) {
const inputJpgBuffer = fs.readFileSync(fixtures.inputJpg); const inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);
(new Benchmark.Suite('kernels')).add('sharp-cubic', { (new Benchmark.Suite('kernels')).add('sharp-cubic', {
@@ -682,31 +633,6 @@ async.series({
}); });
} }
}); });
// lwip
if (typeof lwip !== 'undefined') {
pngSuite.add('lwip-buffer-buffer', {
defer: true,
fn: function (deferred) {
lwip.open(inputPngBuffer, 'png', function (err, image) {
if (err) {
throw err;
}
image.resize(width, height, 'lanczos', function (err, image) {
if (err) {
throw err;
}
image.toBuffer('png', function (err, buffer) {
if (err) {
throw err;
}
assert.notStrictEqual(null, buffer);
deferred.resolve();
});
});
});
}
});
}
// mapnik // mapnik
pngSuite.add('mapnik-file-file', { pngSuite.add('mapnik-file-file', {
defer: true, defer: true,
@@ -733,7 +659,7 @@ async.series({
}).add('mapnik-buffer-buffer', { }).add('mapnik-buffer-buffer', {
defer: true, defer: true,
fn: function (deferred) { fn: function (deferred) {
mapnik.Image.fromBytes(inputPngBuffer, function (err, img) { mapnik.Image.fromBytes(inputPngBuffer, { max_size: 3000 }, 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;
@@ -889,12 +815,12 @@ async.series({
} }
}); });
} }
}).add('sharp-withoutAdaptiveFiltering', { }).add('sharp-adaptiveFiltering', {
defer: true, defer: true,
fn: function (deferred) { fn: function (deferred) {
sharp(inputPngBuffer) sharp(inputPngBuffer)
.resize(width, height) .resize(width, height)
.png({ adaptiveFiltering: false }) .png({ adaptiveFiltering: true })
.toBuffer(function (err, buffer) { .toBuffer(function (err, buffer) {
if (err) { if (err) {
throw err; throw err;
@@ -957,7 +883,7 @@ async.series({
}).add('sharp-file-buffer', { }).add('sharp-file-buffer', {
defer: true, defer: true,
fn: function (deferred) { fn: function (deferred) {
sharp(fixtures.inputWebp) sharp(fixtures.inputWebP)
.resize(width, height) .resize(width, height)
.toBuffer(function (err, buffer) { .toBuffer(function (err, buffer) {
if (err) { if (err) {

View File

@@ -15,7 +15,7 @@ const min = 320;
const max = 960; const max = 960;
const randomDimension = function () { const randomDimension = function () {
return Math.ceil(Math.random() * (max - min) + min); return Math.ceil((Math.random() * (max - min)) + min);
}; };
new Benchmark.Suite('random').add('imagemagick', { new Benchmark.Suite('random').add('imagemagick', {

6
test/coverage/report.sh Executable file
View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 MiB

BIN
test/fixtures/320x240.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

BIN
test/fixtures/8bit_depth.tiff vendored Normal file

Binary file not shown.

BIN
test/fixtures/G31D_MULTI.TIF vendored Normal file

Binary file not shown.

BIN
test/fixtures/Landscape_9.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

BIN
test/fixtures/alpha-layer-2-ink.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
test/fixtures/centered_image.jpeg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
test/fixtures/embedgravitybird.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 KiB

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

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