Compare commits

..

47 Commits

Author SHA1 Message Date
Lovell Fuller
8a3b660bbc Release v0.15.0 2016-05-21 15:33:56 +01:00
Lovell Fuller
933989c87d Update benchmark results ahead of v0.15.0, ~20% improvement 2016-05-21 10:21:42 +01:00
Lovell Fuller
e3cbcb98c0 Add hints about compiling with _GLIBCXX_USE_CXX11_ABI #432
Increase deprecatedness of preinstall script
Additional valgrind suppressions for libwebp
2016-05-21 09:03:09 +01:00
Lovell Fuller
32a2787254 Thank you to all the new contributors 2016-05-18 20:40:31 +01:00
Lovell Fuller
fccfc27de0 Update ARM packaging to libvips v8.3.1 2016-05-18 19:58:30 +01:00
Lovell Fuller
cdb2894bd9 Use libvips' new lanczos3 kernel as default for image reduce
Deprecate interpolateWith method, now provided as an option
2016-05-18 19:57:22 +01:00
Lovell Fuller
051d022fc2 Upgrade to libvips v8.3.1
Remove packaging tests and therefore support for Centos 6
2016-05-08 22:15:08 +01:00
Lovell Fuller
7388d97502 Allow keyword macros for glib support on MSVC 2016-05-08 13:30:42 +01:00
Lovell Fuller
1bece3a792 Add 2 channel (grey+alpha) GIF test case #375 2016-05-07 20:04:17 +01:00
Lovell Fuller
1de0038516 Upgrade to libvips 8.3.x
Add support for libvips' new native loaders, including GIF and SVG
Pre-built binaries now include giflib and librsvg, exclude *magick
2016-05-07 20:04:17 +01:00
Lovell Fuller
b7a098fb28 Break existing sharpen API to accept sigma and improve precision 2016-05-07 20:04:17 +01:00
Lovell Fuller
ee21d2991c Use shrink-on-load for WebP input 2016-05-07 20:04:17 +01:00
Lovell Fuller
f8eab49962 Add Node v6 to CI builds 2016-05-07 19:50:15 +01:00
Lovell Fuller
c9b3847a69 Docs: basic security considerations for installation #424 2016-05-07 19:48:06 +01:00
Felix Bünemann
dce3840537 Update Lambda instructions for Node.js 4.3 (#419)
Amazon introduced Node.js 4.3 support for Lambda, which is now the
recommended runtime instead of the old Node.js 0.10. This commit revises
the Lambda docs to build Node.js 4.3 compatible binaries using the
latest stable Node.js 4.x packages from Nodesource.
2016-04-27 19:39:30 +01:00
Lovell Fuller
b6030c161b Update expected test fixtures for libvips 8.3 2016-04-23 20:07:55 +01:00
Lovell Fuller
c920180cb3 Remove (un)premultiply ops when not resizing/compositing #413 2016-04-23 19:50:00 +01:00
Lovell Fuller
531a0402f7 Changelog updates ahead of v0.14.1
Note effect of C++11 ABI changes on upgrades
2016-04-16 21:16:37 +01:00
Lovell Fuller
cb10f9a9c8 Support gyp-based (re)build without npm env vars #412 2016-04-16 20:35:28 +01:00
Lovell Fuller
c808139b02 Changelog additions and version bumps ahead of v0.14.1 2016-04-14 21:57:44 +01:00
Lovell Fuller
e0d58266be Allow use of embed with 1 and 2 channel images #411 2016-04-14 21:39:17 +01:00
Felix Bünemann
1b7c5816fc Speed up slow pixel limit tests (#404)
This replaces the single color 20000x14000 PNG with a 20000x14000
Grayscale JPG. On a Broadwell Core i7 @ 3.1 GHz this speeds up the
"Disabling limit works" test from ~6.2s to ~0.15s because it can use
jpeg resize-on-load optimizations.

This should fix occasional timeouts on Travis CI.
2016-04-08 22:21:28 +01:00
Felix Bünemann
b224874332 Add support for writing dz format to zip container (#402)
To enable this you can either use the `.zip` or `.szi` file extensions
or use `.tile({container: 'zip'})` with the `.dzi` extension.
2016-04-08 19:58:13 +01:00
Samy Al Zahrani
ef61da3051 Ensure dimensions of final output image are provided (#399)
Add a failing test for vips::VError exception
* fix buffer size mismatch
* Loosen error message assertion
* Update image
2016-04-08 08:58:51 +01:00
Samy Al Zahrani
f214269aa1 Enable RTTI for clang-based builds
This allows for a more verbose error message on mac os x

```

libc++abi.dylib: terminating with uncaught exception of type vips::VError

```

becomes

```

libc++abi.dylib: terminating with uncaught exception of type vips::VError: VipsImage: memory area too small --- should be 1191960 bytes, you passed 1189440

```
2016-04-06 15:23:17 +01:00
Lovell Fuller
6bc2ea8dc7 No longer publish deprecated preinstall script to npm 2016-04-04 12:49:48 +01:00
Lovell Fuller
71fb839e2b Speed up limitInputPixels test case
Update changelog
2016-04-04 12:48:53 +01:00
kentongray
8c9c070caf Ability to disable limitInputPixels #250
Update docs
Added a giant image for testing
Adding myself to contributors
Added tests to verify giant image can be opened
Extend test-win time limit (because of large images)
2016-04-04 08:35:11 +01:00
Lovell Fuller
b2d7d4c4a9 Release v0.14.0 2016-04-02 13:21:44 +01:00
Lovell Fuller
0ac7fbfc07 Changelog entry for #382 #385 2016-04-02 12:19:58 +01:00
John Tobin
ebfc897bcf Fix for orientation values 1-8 2016-04-02 11:59:26 +01:00
Lovell Fuller
c66495b66c Tighten C++ linting rules
Bump benchmark dependencies
Update leak test suppressions
Update future branch details
2016-03-31 20:30:40 +01:00
Lovell Fuller
24fb0c33c2 Add further test case for #387, which builds on 25b63a2 2016-03-30 19:26:19 +01:00
Lovell Fuller
25b63a2fb4 Ensure ratios are not swapped when rotating 90/270 and ignoring aspect 2016-03-28 22:40:37 +01:00
Lovell Fuller
e576165cf1 Use Travis CI's multi-OS feature 2016-03-25 19:12:25 +00:00
Lovell Fuller
fe2eccef39 Merge branch 'needle' 2016-03-22 09:52:25 +00:00
Lovell Fuller
0e0e746a0d Update preinstall to libvips v8.2.3 for Centos 6 2016-03-22 09:44:08 +00:00
Lovell Fuller
5b4f4b0672 Upgrade to libvips v8.2.3 ahead of sharp v0.14.0 2016-03-22 09:37:52 +00:00
Lovell Fuller
185fcfe635 Improve entropy-based crop docs based on feedback.
Fix includes to keep MSVC compiler happy.
Additional memory leak suppressions for latest V8.
2016-03-05 18:30:38 +00:00
Lovell Fuller
2034efcf55 Add experimental, entropy-based auto-crop
Remove deprecated extract API
2016-03-05 12:29:16 +00:00
Lovell Fuller
38ddb3b866 Add support for Zoomify and Google tile layouts
Breaks existing tile API
2016-03-03 20:39:38 +00:00
Lovell Fuller
f950294f70 Add ability to extend (pad) the edges of an image 2016-03-03 09:18:11 +00:00
Lovell Fuller
86815bc9c4 Emit post-processing 'info' event for Stream-based output 2016-03-01 20:08:05 +00:00
Lovell Fuller
bb37dc1ea6 Expose density metadata; set density of images from vector input 2016-03-01 19:33:54 +00:00
Lovell Fuller
d92ea31858 overlayWith improvements: diff sizes/formats, gravity, buffer input 2016-02-29 15:15:27 +00:00
Lovell Fuller
55f204c6f9 Merge pull request #368 from felixbuenemann/improve-linux-lambda-docs
[ci skip] Improved Linux docs, Lambda instructions
2016-02-28 09:15:02 +00:00
Felix Bünemann
e97909f776 [ci skip] Improved Linux docs, Lambda instructions
* Add a more detailed explanation of how sharp discovers libvips
* Add a section on packaging sharp for running on AWS Lambda
2016-02-27 20:25:32 +01:00
86 changed files with 2988 additions and 1534 deletions

View File

@@ -13,3 +13,4 @@ mkdocs.yml
lib lib
include include
packaging packaging
preinstall.sh

View File

@@ -4,6 +4,10 @@ node_js:
- "0.12" - "0.12"
- "4" - "4"
- "5" - "5"
- "6"
os:
- linux
- osx
sudo: false sudo: false
addons: addons:
apt: apt:
@@ -11,7 +15,9 @@ addons:
- ubuntu-toolchain-r-test - ubuntu-toolchain-r-test
packages: packages:
- g++-4.8 - g++-4.8
env: osx_image: xcode7.3
CXX=g++-4.8 before_install:
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export CXX=g++-4.8; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install homebrew/science/vips; fi
after_success: after_success:
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js

View File

@@ -41,9 +41,8 @@ Any change that modifies the existing public API should be added to the relevant
| Release | WIP branch | | Release | WIP branch |
| ------: | :--------- | | ------: | :--------- |
| v0.14.0 | needle |
| v0.15.0 | outfit |
| v0.16.0 | pencil | | v0.16.0 | pencil |
| v0.17.0 | quill |
Please squash your changes into a single commit using a command like `git rebase -i upstream/<wip-branch>`. Please squash your changes into a single commit using a command like `git rebase -i upstream/<wip-branch>`.

View File

@@ -1,14 +1,14 @@
# sharp # sharp
The typical use case for this high speed Node.js module The typical use case for this high speed Node.js module
is to convert large images of many 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.
Resizing an image is typically 4x faster than using the Resizing an image is typically 4x-5x faster than using the
quickest ImageMagick and GraphicsMagick settings. quickest ImageMagick and GraphicsMagick settings.
Colour spaces, embedded ICC profiles and alpha transparency channels are all handled correctly. Colour spaces, embedded ICC profiles and alpha transparency channels are all handled correctly.
Bicubic interpolation with Lanczos anti-alias filtering ensures quality is not sacrificed for speed. 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.

View File

@@ -8,6 +8,7 @@ environment:
- nodejs_version: "0.12" - nodejs_version: "0.12"
- nodejs_version: "4" - nodejs_version: "4"
- nodejs_version: "5" - nodejs_version: "5"
- nodejs_version: "6"
install: install:
- ps: Install-Product node $env:nodejs_version x64 - ps: Install-Product node $env:nodejs_version x64
- npm install - npm install

View File

@@ -9,7 +9,8 @@
'download_vips': '<!(node -e "require(\'./binding\').download_vips()")' 'download_vips': '<!(node -e "require(\'./binding\').download_vips()")'
}, },
'defines': [ 'defines': [
'VIPS_CPLUSPLUS_EXPORTS' 'VIPS_CPLUSPLUS_EXPORTS',
'_ALLOW_KEYWORD_MACROS'
], ],
'sources': [ 'sources': [
'src/libvips/cplusplus/VError.cpp', 'src/libvips/cplusplus/VError.cpp',
@@ -91,7 +92,8 @@
'src/utilities.cc' 'src/utilities.cc'
], ],
'defines': [ 'defines': [
'_GLIBCXX_USE_CXX11_ABI=0' '_GLIBCXX_USE_CXX11_ABI=0',
'_ALLOW_KEYWORD_MACROS'
], ],
'include_dirs': [ 'include_dirs': [
'<!(node -e "require(\'nan\')")' '<!(node -e "require(\'nan\')")'
@@ -132,23 +134,33 @@
'<(module_root_dir)/lib/libglib-2.0.so', '<(module_root_dir)/lib/libglib-2.0.so',
'<(module_root_dir)/lib/libgobject-2.0.so', '<(module_root_dir)/lib/libgobject-2.0.so',
# Dependencies of dependencies, included for openSUSE support # Dependencies of dependencies, included for openSUSE support
'<(module_root_dir)/lib/libGraphicsMagick.so', '<(module_root_dir)/lib/libcairo.so',
'<(module_root_dir)/lib/libGraphicsMagickWand.so', '<(module_root_dir)/lib/libcroco-0.6.so',
'<(module_root_dir)/lib/libexif.so', '<(module_root_dir)/lib/libexif.so',
'<(module_root_dir)/lib/libffi.so',
'<(module_root_dir)/lib/libfontconfig.so',
'<(module_root_dir)/lib/libfreetype.so',
'<(module_root_dir)/lib/libgdk_pixbuf-2.0.so',
'<(module_root_dir)/lib/libgif.so',
'<(module_root_dir)/lib/libgio-2.0.so', '<(module_root_dir)/lib/libgio-2.0.so',
'<(module_root_dir)/lib/libgmodule-2.0.so', '<(module_root_dir)/lib/libgmodule-2.0.so',
'<(module_root_dir)/lib/libgsf-1.so', '<(module_root_dir)/lib/libgsf-1.so',
'<(module_root_dir)/lib/libgthread-2.0.so',
'<(module_root_dir)/lib/libharfbuzz.so',
'<(module_root_dir)/lib/libjpeg.so', '<(module_root_dir)/lib/libjpeg.so',
'<(module_root_dir)/lib/liblcms2.so',
'<(module_root_dir)/lib/liborc-0.4.so',
'<(module_root_dir)/lib/libpango-1.0.so',
'<(module_root_dir)/lib/libpangocairo-1.0.so',
'<(module_root_dir)/lib/libpangoft2-1.0.so',
'<(module_root_dir)/lib/libpixman-1.so',
'<(module_root_dir)/lib/libpng.so', '<(module_root_dir)/lib/libpng.so',
'<(module_root_dir)/lib/libpng16.so',
'<(module_root_dir)/lib/librsvg-2.so',
'<(module_root_dir)/lib/libtiff.so', '<(module_root_dir)/lib/libtiff.so',
'<(module_root_dir)/lib/libwebp.so', '<(module_root_dir)/lib/libwebp.so',
'<(module_root_dir)/lib/libz.so',
'<(module_root_dir)/lib/libffi.so',
'<(module_root_dir)/lib/libgthread-2.0.so',
'<(module_root_dir)/lib/liblcms2.so',
'<(module_root_dir)/lib/libpng16.so',
'<(module_root_dir)/lib/libxml2.so', '<(module_root_dir)/lib/libxml2.so',
'<(module_root_dir)/lib/liborc-0.4.so', '<(module_root_dir)/lib/libz.so',
# Ensure runtime linking is relative to sharp.node # Ensure runtime linking is relative to sharp.node
'-Wl,-rpath=\'$${ORIGIN}/../../lib\'' '-Wl,-rpath=\'$${ORIGIN}/../../lib\''
] ]
@@ -167,6 +179,7 @@
'CLANG_CXX_LIBRARY': 'libc++', 'CLANG_CXX_LIBRARY': 'libc++',
'MACOSX_DEPLOYMENT_TARGET': '10.7', 'MACOSX_DEPLOYMENT_TARGET': '10.7',
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
'GCC_ENABLE_CPP_RTTI': 'YES',
'OTHER_CPLUSPLUSFLAGS': [ 'OTHER_CPLUSPLUSFLAGS': [
'-fexceptions', '-fexceptions',
'-Wall', '-Wall',
@@ -198,12 +211,11 @@
'destination': '<(module_root_dir)/build/Release', 'destination': '<(module_root_dir)/build/Release',
'files': [ 'files': [
'<(module_root_dir)/lib/GNU.Gettext.dll', '<(module_root_dir)/lib/GNU.Gettext.dll',
'<(module_root_dir)/lib/libMagickCore-6.Q16-2.dll',
'<(module_root_dir)/lib/libMagickWand-6.Q16-2.dll',
'<(module_root_dir)/lib/libasprintf-0.dll', '<(module_root_dir)/lib/libasprintf-0.dll',
'<(module_root_dir)/lib/libcairo-2.dll', '<(module_root_dir)/lib/libcairo-2.dll',
'<(module_root_dir)/lib/libcairo-gobject-2.dll', '<(module_root_dir)/lib/libcairo-gobject-2.dll',
'<(module_root_dir)/lib/libcairo-script-interpreter-2.dll', '<(module_root_dir)/lib/libcairo-script-interpreter-2.dll',
'<(module_root_dir)/lib/libcroco-0.6-3.dll',
'<(module_root_dir)/lib/libexif-12.dll', '<(module_root_dir)/lib/libexif-12.dll',
'<(module_root_dir)/lib/libexpat-1.dll', '<(module_root_dir)/lib/libexpat-1.dll',
'<(module_root_dir)/lib/libffi-6.dll', '<(module_root_dir)/lib/libffi-6.dll',
@@ -212,6 +224,7 @@
'<(module_root_dir)/lib/libfreetype-6.dll', '<(module_root_dir)/lib/libfreetype-6.dll',
'<(module_root_dir)/lib/libgcc_s_seh-1.dll', '<(module_root_dir)/lib/libgcc_s_seh-1.dll',
'<(module_root_dir)/lib/libgdk_pixbuf-2.0-0.dll', '<(module_root_dir)/lib/libgdk_pixbuf-2.0-0.dll',
'<(module_root_dir)/lib/libgif-4.dll',
'<(module_root_dir)/lib/libgio-2.0-0.dll', '<(module_root_dir)/lib/libgio-2.0-0.dll',
'<(module_root_dir)/lib/libglib-2.0-0.dll', '<(module_root_dir)/lib/libglib-2.0-0.dll',
'<(module_root_dir)/lib/libgmodule-2.0-0.dll', '<(module_root_dir)/lib/libgmodule-2.0-0.dll',
@@ -221,18 +234,18 @@
'<(module_root_dir)/lib/libintl-8.dll', '<(module_root_dir)/lib/libintl-8.dll',
'<(module_root_dir)/lib/libjpeg-62.dll', '<(module_root_dir)/lib/libjpeg-62.dll',
'<(module_root_dir)/lib/liblcms2-2.dll', '<(module_root_dir)/lib/liblcms2-2.dll',
'<(module_root_dir)/lib/libopenjpeg-1.dll',
'<(module_root_dir)/lib/libopenslide-0.dll',
'<(module_root_dir)/lib/libpango-1.0-0.dll', '<(module_root_dir)/lib/libpango-1.0-0.dll',
'<(module_root_dir)/lib/libpangocairo-1.0-0.dll', '<(module_root_dir)/lib/libpangocairo-1.0-0.dll',
'<(module_root_dir)/lib/libpangowin32-1.0-0.dll', '<(module_root_dir)/lib/libpangowin32-1.0-0.dll',
'<(module_root_dir)/lib/libpixman-1-0.dll', '<(module_root_dir)/lib/libpixman-1-0.dll',
'<(module_root_dir)/lib/libpng16-16.dll', '<(module_root_dir)/lib/libpng16-16.dll',
'<(module_root_dir)/lib/libquadmath-0.dll', '<(module_root_dir)/lib/libquadmath-0.dll',
'<(module_root_dir)/lib/libsqlite3-0.dll', '<(module_root_dir)/lib/librsvg-2-2.dll',
'<(module_root_dir)/lib/libssp-0.dll', '<(module_root_dir)/lib/libssp-0.dll',
'<(module_root_dir)/lib/libstdc++-6.dll',
'<(module_root_dir)/lib/libtiff-5.dll', '<(module_root_dir)/lib/libtiff-5.dll',
'<(module_root_dir)/lib/libvips-42.dll', '<(module_root_dir)/lib/libvips-42.dll',
'<(module_root_dir)/lib/libwebp-6.dll',
'<(module_root_dir)/lib/libxml2-2.dll', '<(module_root_dir)/lib/libxml2-2.dll',
'<(module_root_dir)/lib/zlib1.dll' '<(module_root_dir)/lib/zlib1.dll'
] ]

View File

@@ -12,6 +12,9 @@ var tmp = require('os').tmpdir();
var distBaseUrl = 'https://dl.bintray.com/lovell/sharp/'; var distBaseUrl = 'https://dl.bintray.com/lovell/sharp/';
// Use NPM-provided environment variable where available, falling back to require-based method for Electron
var minimumLibvipsVersion = process.env.npm_package_config_libvips || require('./package.json').config.libvips;
var vipsHeaderPath = path.join(__dirname, 'include', 'vips', 'vips.h'); var vipsHeaderPath = path.join(__dirname, 'include', 'vips', 'vips.h');
// -- Helpers // -- Helpers
@@ -75,7 +78,7 @@ module.exports.download_vips = function() {
} }
// Arch/platform-specific .tar.gz // Arch/platform-specific .tar.gz
var platform = (process.arch === 'arm') ? 'arm' : process.platform.substr(0, 3); var platform = (process.arch === 'arm') ? 'arm' : process.platform.substr(0, 3);
var tarFilename = ['libvips', process.env.npm_package_config_libvips, platform].join('-') + '.tar.gz'; var tarFilename = ['libvips', minimumLibvipsVersion, platform].join('-') + '.tar.gz';
var tarPath = path.join(__dirname, 'packaging', tarFilename); var tarPath = path.join(__dirname, 'packaging', tarFilename);
if (isFile(tarPath)) { if (isFile(tarPath)) {
unpack(tarPath); unpack(tarPath);
@@ -114,13 +117,13 @@ module.exports.use_global_vips = function() {
if (globalVipsVersion) { if (globalVipsVersion) {
useGlobalVips = semver.gte( useGlobalVips = semver.gte(
globalVipsVersion, globalVipsVersion,
process.env.npm_package_config_libvips minimumLibvipsVersion
); );
} }
if (process.platform === 'darwin' && !useGlobalVips) { if (process.platform === 'darwin' && !useGlobalVips) {
if (globalVipsVersion) { if (globalVipsVersion) {
error( error(
'Found libvips ' + globalVipsVersion + ' but require ' + process.env.npm_package_config_libvips + 'Found libvips ' + globalVipsVersion + ' but require ' + minimumLibvipsVersion +
'\nPlease upgrade libvips by running: brew update && brew upgrade' '\nPlease upgrade libvips by running: brew update && brew upgrade'
); );
} else { } else {

View File

@@ -13,7 +13,7 @@ Constructor to which further methods are chained.
`input`, if present, can be one of: `input`, if present, can be one of:
* Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or * Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
* String containing the path to an image file, with most major formats supported. * 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 JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data
can be streamed into the object when `input` is `null` or `undefined`. can be streamed into the object when `input` is `null` or `undefined`.
@@ -27,6 +27,7 @@ The object returned by the constructor implements the
[stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class. [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.
JPEG, PNG or WebP format image data can be streamed out from this object. JPEG, PNG or WebP format image data can be streamed out from this object.
When using Stream based output, derived attributes are available from the `info` event.
```javascript ```javascript
sharp('input.jpg') sharp('input.jpg')
@@ -37,17 +38,31 @@ sharp('input.jpg')
}); });
``` ```
```javascript
// Read image data from readableStream,
// resize to 300 pixels wide,
// emit an 'info' event with calculated dimensions
// and finally write image data to writableStream
var transformer = sharp()
.resize(300)
.on('info', function(info) {
console.log('Image height is ' + info.height);
});
readableStream.pipe(transformer).pipe(writableStream);
```
#### metadata([callback]) #### metadata([callback])
Fast access to image metadata without decoding any compressed image data. Fast access to image metadata without decoding any compressed image data.
`callback`, if present, gets the arguments `(err, metadata)` where `metadata` has the attributes: `callback`, if present, gets the arguments `(err, metadata)` where `metadata` has the attributes:
* `format`: Name of decoder to be used to decompress image data e.g. `jpeg`, `png`, `webp` (for file-based input additionally `tiff`, `magick`, `openslide`, `ppm`, `fits`) * `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
* `width`: Number of pixels wide * `width`: Number of pixels wide
* `height`: Number of pixels high * `height`: Number of pixels high
* `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `scrgb`, `cmyk`, `lab`, `xyz`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L522) * `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `scrgb`, `cmyk`, `lab`, `xyz`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L522)
* `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK * `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
* `density`: Number of pixels per inch (DPI), if present
* `hasProfile`: Boolean indicating the presence of an embedded ICC profile * `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
@@ -98,11 +113,12 @@ This will reduce memory usage and can improve performance on some systems.
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 * height) exceeds this limit.
`pixels` is the integral Number of pixels, with a value between 1 and the default 268402689 (0x3FFF * 0x3FFF). `pixels` is either an integral Number of pixels, with a value between 1 and the default 268402689 (0x3FFF * 0x3FFF) or
a boolean. `false` will disable checking while `true` will revert to the default limit.
### Resizing ### Resizing
#### resize([width], [height]) #### resize([width], [height], [options])
Scale output to `width` x `height`. By default, the resized image is cropped to the exact size specified. Scale output to `width` x `height`. By default, the resized image is cropped to the exact size specified.
@@ -110,23 +126,73 @@ Scale output to `width` x `height`. By default, the resized image is cropped to
`height` is the integral Number of pixels high the resultant image should be, between 1 and 16383. Use `null` or `undefined` to auto-scale the height to match the width. `height` is the integral Number of pixels high the resultant image should be, between 1 and 16383. Use `null` or `undefined` to auto-scale the height to match the width.
#### crop([gravity]) `options` is an optional Object. If present, it can contain one or more of:
* `options.kernel`, the kernel to use for image reduction, defaulting to `lanczos3`.
* `options.interpolator`, the interpolator to use for image enlargement, defaulting to `bicubic`.
Possible kernels are:
* `cubic`: Use a [Catmull-Rom spline](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline).
* `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`.
* `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
Possible interpolators are:
* `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation).
* `bilinear`: Use [bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation), faster than bicubic but with less smooth results.
* `vertexSplitQuadraticBasisSpline`: Use the smoother [VSQBS interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48) to prevent "staircasing" when enlarging.
* `bicubic`: Use [bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) (the default).
* `locallyBoundedBicubic`: Use [LBB interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100), which prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" but typically reduces performance by a factor of 2.
* `nohalo`: Use [Nohalo interpolation](http://eprints.soton.ac.uk/268086/), which prevents acutance but typically reduces performance by a factor of 3.
```javascript
sharp(inputBuffer)
.resize(200, 300, {
kernel: sharp.kernel.lanczos2,
interpolator: sharp.interpolator.nohalo
})
.background('white')
.embed()
.toFile('output.tiff')
.then(function() {
// output.tiff is a 200 pixels wide and 300 pixels high image
// containing a lanczos2/nohalo scaled version, embedded on a white canvas,
// of the image data in inputBuffer
});
```
#### crop([option])
Crop the resized image to the exact size specified, the default behaviour. Crop the resized image to the exact size specified, the default behaviour.
`gravity`, if present, is a String or an attribute of the `sharp.gravity` Object e.g. `sharp.gravity.north`. `option`, if present, is an attribute of:
Possible values are `north`, `northeast`, `east`, `southeast`, `south`, `southwest`, `west`, `northwest`, `center` and `centre`. * `sharp.gravity` e.g. `sharp.gravity.north`, to crop to an edge or corner, or
The default gravity is `center`/`centre`. * `sharp.strategy` e.g. `sharp.strategy.entropy`, to crop dynamically.
Possible attributes of `sharp.gravity` are
`north`, `northeast`, `east`, `southeast`, `south`,
`southwest`, `west`, `northwest`, `center` and `centre`.
Possible attributes of the experimental `sharp.strategy` are:
* `entropy`: resize so one dimension is at its target size
then repeatedly remove pixels from the edge with the lowest
[Shannon entropy](https://en.wikipedia.org/wiki/Entropy_%28information_theory%29)
until it too reaches the target size.
The default crop option is a `center`/`centre` gravity.
```javascript ```javascript
var transformer = sharp() var transformer = sharp()
.resize(300, 200) .resize(200, 200)
.crop(sharp.gravity.north) .crop(sharp.strategy.entropy)
.on('error', function(err) { .on('error', function(err) {
console.log(err); console.log(err);
}); });
// Read image data from readableStream, resize and write image data to writableStream // Read image data from readableStream
// Write 200px square auto-cropped image data to writableStream
readableStream.pipe(transformer).pipe(writableStream); readableStream.pipe(transformer).pipe(writableStream);
``` ```
@@ -202,37 +268,6 @@ if its width or height exceeds the geometry specification*".
Ignoring the aspect ratio of the input, stretch the image to the exact `width` and/or `height` provided via `resize`. Ignoring the aspect ratio of the input, stretch the image to the exact `width` and/or `height` provided via `resize`.
#### interpolateWith(interpolator)
Use the given interpolator for image resizing, where `interpolator` is an attribute of the `sharp.interpolator` Object e.g. `sharp.interpolator.bicubic`.
The default interpolator is `bicubic`, providing a general-purpose interpolator that is both fast and of good quality.
Possible interpolators, in order of performance, are:
* `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation), suitable for image enlargement only.
* `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.
[Compare the output of these interpolators](https://github.com/lovell/sharp/tree/master/test/interpolators)
```javascript
sharp(inputBuffer)
.resize(200, 300)
.interpolateWith(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 nohalo scaled version, embedded on a white canvas,
// of the image data in inputBuffer
});
```
### Operations ### Operations
#### extract({ left: left, top: top, width: width, height: height }) #### extract({ left: left, top: top, width: width, height: height })
@@ -265,7 +300,7 @@ sharp(input)
#### background(rgba) #### background(rgba)
Set the background for the `embed` and `flatten` operations. Set the background for the `embed`, `flatten` and `extend` operations.
`rgba` is parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. `rgba` is parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
@@ -277,6 +312,25 @@ The default background is `{r: 0, g: 0, b: 0, a: 1}`, black without transparency
Merge alpha transparency channel, if any, with `background`. Merge alpha transparency channel, if any, with `background`.
#### extend(extension)
Extends/pads the edges of the image with `background`, where `extension` is one of:
* a Number representing the pixel count to add to each edge, or
* an Object containing `top`, `left`, `bottom` and `right` attributes, each a Number of pixels to add to that edge.
This operation will always occur after resizing and extraction, if any.
```javascript
// Resize to 140 pixels wide, then add 10 transparent pixels
// to the top, left and right edges and 20 to the bottom edge
sharp(input)
.resize(140)
.background({r: 0, g: 0, b: 0, a: 0})
.extend({top: 10, bottom: 20, left: 10, right: 10})
...
```
#### negate() #### negate()
Produces the "negative" of the image. White => Black, Black => White, Blue => Yellow, etc. Produces the "negative" of the image. White => Black, Black => White, Blue => Yellow, etc.
@@ -325,15 +379,15 @@ When used without parameters, performs a fast, mild blur of the output image. Th
When a `sigma` is provided, performs a slower, more accurate Gaussian blur. This typically reduces performance by 25%. When a `sigma` is provided, performs a slower, more accurate Gaussian blur. This typically reduces performance by 25%.
* `sigma`, if present, is a Number between 0.3 and 1000 representing the approximate blur radius in pixels. * `sigma`, if present, is a Number between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
#### sharpen([radius], [flat], [jagged]) #### sharpen([sigma], [flat], [jagged])
When used without parameters, performs a fast, mild sharpen of the output image. This typically reduces performance by 10%. When used without parameters, performs a fast, mild sharpen of the output image. This typically reduces performance by 10%.
When a `radius` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space. Separate control over the level of sharpening in "flat" and "jagged" areas is available. This typically reduces performance by 50%. When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space. Separate control over the level of sharpening in "flat" and "jagged" areas is available. This typically reduces performance by 50%.
* `radius`, if present, is an integral Number representing the sharpen mask radius in pixels. * `sigma`, if present, is a Number representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
* `flat`, if present, is a Number representing the level of sharpening to apply to "flat" areas, defaulting to a value of 1.0. * `flat`, if present, is a Number representing the level of sharpening to apply to "flat" areas, defaulting to a value of 1.0.
* `jagged`, if present, is a Number representing the level of sharpening to apply to "jagged" areas, defaulting to a value of 2.0. * `jagged`, if present, is a Number representing the level of sharpening to apply to "jagged" areas, defaulting to a value of 2.0.
@@ -365,13 +419,18 @@ The output image will still be web-friendly sRGB and contain three (identical) c
Enhance output image contrast by stretching its luminance to cover the full dynamic range. This typically reduces performance by 30%. Enhance output image contrast by stretching its luminance to cover the full dynamic range. This typically reduces performance by 30%.
#### overlayWith(path) #### overlayWith(image, [options])
_Experimental_ Overlay (composite) a image containing an alpha channel over the processed (resized, extracted etc.) image.
Alpha composite image at `path` over the processed (resized, extracted) image. The dimensions of the two images must match. `image` is one of the following, and must be the same size or smaller than the processed image:
* `path` is a String containing the path to an image file with an alpha channel. * Buffer containing PNG, WebP, GIF or SVG image data, or
* String containing the path to an image file, with most major transparency formats supported.
`options`, if present, is an Object with the following optional attributes:
* `gravity` is a String or an attribute of the `sharp.gravity` Object e.g. `sharp.gravity.north` at which to place the overlay, defaulting to `center`/`centre`.
```javascript ```javascript
sharp('input.png') sharp('input.png')
@@ -379,7 +438,7 @@ sharp('input.png')
.resize(300) .resize(300)
.flatten() .flatten()
.background('#ff6600') .background('#ff6600')
.overlayWith('overlay.png') .overlayWith('overlay.png', { gravity: sharp.gravity.southeast } )
.sharpen() .sharpen()
.withMetadata() .withMetadata()
.quality(90) .quality(90)
@@ -387,8 +446,8 @@ sharp('input.png')
.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
// onto orange background, composited with overlay.png, sharpened, // onto orange background, composited with overlay.png with SE gravity,
// with metadata, 90% quality WebP image data. Phew! // sharpened, with metadata, 90% quality WebP image data. Phew!
}); });
``` ```
@@ -466,22 +525,32 @@ This will also convert to and add the latest web-friendly v2 sRGB ICC profile.
The optional `metadata` parameter, if present, is an Object with the attributes to update. The optional `metadata` parameter, if present, is an Object with the attributes to update.
New attributes cannot be inserted, only existing attributes updated. New attributes cannot be inserted, only existing attributes updated.
* `orientation` is an integral Number between 0 and 7, used to update the value of the EXIF `Orientation` tag. * `orientation` is an integral Number between 1 and 8, used to update the value of the EXIF `Orientation` tag.
This has no effect if the input image does not have an EXIF `Orientation` tag. This has no effect if the input image does not have an EXIF `Orientation` tag.
The default behaviour, when `withMetadata` is not used, is to strip all metadata and convert to the device-independent sRGB colour space. The default behaviour, when `withMetadata` is not used, is to strip all metadata and convert to the device-independent sRGB colour space.
#### tile([size], [overlap]) #### tile(options)
The size and overlap, in pixels, of square Deep Zoom image pyramid tiles. The size, overlap, container and directory layout to use when generating square Deep Zoom image pyramid tiles.
`options` is an Object with one or more of the following attributes:
* `size` is an integral Number between 1 and 8192. The default value is 256 pixels. * `size` is an integral Number between 1 and 8192. The default value is 256 pixels.
* `overlap` is an integral Number between 0 and 8192. The default value is 0 pixels. * `overlap` is an integral Number between 0 and 8192. The default value is 0 pixels.
* `container` is a String, with value `fs` or `zip`. The default value is `fs`.
* `layout` is a String, with value `dz`, `zoomify` or `google`. The default value is `dz`.
You can also use the file extension .zip or .szi to write to a ZIP container instead of the filesystem.
```javascript ```javascript
sharp('input.tiff').tile(256).toFile('output.dzi', function(err, info) { sharp('input.tiff')
// The output.dzi file is the XML format Deep Zoom definition .tile({
// The output_files directory contains 256x256 pixel tiles grouped by zoom level size: 512
})
.toFile('output.dzi', function(err, info) {
// output.dzi is the Deep Zoom XML definition
// output_files contains 512x512 tiles grouped by zoom level
}); });
``` ```
@@ -551,9 +620,6 @@ for example:
tiff: { id: 'tiff', tiff: { id: 'tiff',
input: { file: true, buffer: true, stream: true }, input: { file: true, buffer: true, stream: true },
output: { file: true, buffer: false, stream: false } }, output: { file: true, buffer: false, stream: false } },
magick: { id: 'magick',
input: { file: true, buffer: true, stream: true },
output: { file: false, buffer: false, stream: false } },
raw: { id: 'raw', raw: { id: 'raw',
input: { file: false, buffer: false, stream: false }, input: { file: false, buffer: false, stream: false },
output: { file: false, buffer: true, stream: true } } } output: { file: false, buffer: true, stream: true } } }
@@ -577,22 +643,7 @@ sharp.queue.on('change', function(queueLength) {
An Object containing the version numbers of libvips and, on Linux, its dependencies. An Object containing the version numbers of libvips and, on Linux, its dependencies.
```javascript ```javascript
> console.log(sharp.versions); console.log(sharp.versions);
{ zlib: '1.2.8',
ffi: '3.2.1',
glib: '2.46.2',
xml: '2.9.2',
gsf: '1.14.34',
exif: '0.6.21',
jpeg: '1.4.2',
png: '1.6.19',
lcms: '2.7',
webp: '0.4.4',
tiff: '4.0.6',
magick: '6.9.2-6',
orc: '0.4.24',
vips: '8.1.1' }
``` ```
### Utilities ### Utilities

View File

@@ -1,7 +1,93 @@
# Changelog # Changelog
### v0.15 - "*outfit*"
Requires libvips v8.3.1
#### v0.15.0 - 21<sup>st</sup> May 2016
* Use libvips' new Lanczos 3 kernel as default for image reduction.
Deprecate interpolateWith method, now provided as a resize option.
[#310](https://github.com/lovell/sharp/issues/310)
[@jcupitt](https://github.com/jcupitt)
* Take advantage of libvips v8.3 features.
Add support for libvips' new GIF and SVG loaders.
Pre-built binaries now include giflib and librsvg, exclude *magick.
Use shrink-on-load for WebP input.
Break existing sharpen API to accept sigma and improve precision.
[#369](https://github.com/lovell/sharp/issues/369)
* Remove unnecessary (un)premultiply operations when not resizing/compositing.
[#413](https://github.com/lovell/sharp/issues/413)
[@jardakotesovec](https://github.com/jardakotesovec)
### v0.14 - "*needle*"
Requires libvips v8.2.3
#### v0.14.1 - 16<sup>th</sup> April 2016
* Allow removal of limitation on input pixel count via limitInputPixels. Use with care.
[#250](https://github.com/lovell/sharp/issues/250)
[#316](https://github.com/lovell/sharp/pull/316)
[@anandthakker](https://github.com/anandthakker)
[@kentongray](https://github.com/kentongray)
* Use final output image for metadata passed to callback.
[#399](https://github.com/lovell/sharp/pull/399)
[@salzhrani](https://github.com/salzhrani)
* Add support for writing tiled images to a zip container.
[#402](https://github.com/lovell/sharp/pull/402)
[@felixbuenemann](https://github.com/felixbuenemann)
* Allow use of embed with 1 and 2 channel images.
[#411](https://github.com/lovell/sharp/issues/411)
[@janaz](https://github.com/janaz)
* Improve Electron compatibility by allowing node-gyp rebuilds without npm.
[#412](https://github.com/lovell/sharp/issues/412)
[@nouh](https://github.com/nouh)
#### v0.14.0 - 2<sup>nd</sup> April 2016
* Add ability to extend (pad) the edges of an image.
[#128](https://github.com/lovell/sharp/issues/128)
[@blowsie](https://github.com/blowsie)
* Add support for Zoomify and Google tile layouts. Breaks existing tile API.
[#223](https://github.com/lovell/sharp/issues/223)
[@bdunnette](https://github.com/bdunnette)
* Improvements to overlayWith: differing sizes/formats, gravity, buffer input.
[#239](https://github.com/lovell/sharp/issues/239)
[@chrisriley](https://github.com/chrisriley)
* Add entropy-based crop strategy to remove least interesting edges.
[#295](https://github.com/lovell/sharp/issues/295)
[@rightaway](https://github.com/rightaway)
* Expose density metadata; set density of images from vector input.
[#338](https://github.com/lovell/sharp/issues/338)
[@lookfirst](https://github.com/lookfirst)
* Emit post-processing 'info' event for Stream output.
[#367](https://github.com/lovell/sharp/issues/367)
[@salzhrani](https://github.com/salzhrani)
* Ensure output image EXIF Orientation values are within 1-8 range.
[#385](https://github.com/lovell/sharp/pull/385)
[@jtobinisaniceguy](https://github.com/jtobinisaniceguy)
* Ensure ratios are not swapped when rotating 90/270 and ignoring aspect.
[#387](https://github.com/lovell/sharp/issues/387)
[@kleisauke](https://github.com/kleisauke)
### v0.13 - "*mind*" ### v0.13 - "*mind*"
Requires libvips v8.2.2
#### v0.13.1 - 27<sup>th</sup> February 2016 #### v0.13.1 - 27<sup>th</sup> February 2016
* Fix embedding onto transparent backgrounds; regression introduced in v0.13.0. * Fix embedding onto transparent backgrounds; regression introduced in v0.13.0.
@@ -55,6 +141,8 @@
### v0.12 - "*look*" ### v0.12 - "*look*"
Requires libvips v8.2.0
#### v0.12.2 - 16<sup>th</sup> January 2016 #### v0.12.2 - 16<sup>th</sup> January 2016
* Upgrade libvips to v8.2.0 for improved vips_shrink. * Upgrade libvips to v8.2.0 for improved vips_shrink.

View File

@@ -1,14 +1,14 @@
# sharp # sharp
The typical use case for this high speed Node.js module The typical use case for this high speed Node.js module
is to convert large images of many 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.
Resizing an image is typically 4x faster than using the Resizing an image is typically 4x-5x faster than using the
quickest ImageMagick and GraphicsMagick settings. quickest ImageMagick and GraphicsMagick settings.
Colour spaces, embedded ICC profiles and alpha transparency channels are all handled correctly. Colour spaces, embedded ICC profiles and alpha transparency channels are all handled correctly.
Bicubic interpolation with Lanczos anti-alias filtering ensures quality is not sacrificed for speed. 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.
@@ -23,8 +23,7 @@ to install the libvips dependency.
### Formats ### Formats
This module supports reading JPEG, PNG, WebP, TIFF, OpenSlide, This module supports reading JPEG, PNG, WebP, TIFF, GIF and SVG images.
GIF and most other libmagick-supported formats.
Output images can be in JPEG, PNG and WebP formats as well as uncompressed raw pixel data. Output images can be in JPEG, PNG and WebP formats as well as uncompressed raw pixel data.
@@ -89,12 +88,16 @@ the help and code contributions of the following people:
* [Alaric Holloway](https://github.com/skedastik) * [Alaric Holloway](https://github.com/skedastik)
* [Bernhard K. Weisshuhn](https://github.com/bkw) * [Bernhard K. Weisshuhn](https://github.com/bkw)
* [David A. Carley](https://github.com/dacarley) * [David A. Carley](https://github.com/dacarley)
* [John Tobin](https://github.com/jtobinisaniceguy)
* [Kenton Gray](https://github.com/kentongray)
* [Felix Bünemann](https://github.com/felixbuenemann)
* [Samy Al Zahrani](https://github.com/salzhrani)
Thank you! Thank you!
### Licence ### Licence
Copyright 2013, 2014, 2015 Lovell Fuller and contributors. Copyright 2013, 2014, 2015, 2016 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

@@ -6,7 +6,7 @@ npm install sharp
### Prerequisites ### Prerequisites
* C++11 compatible compiler such as gcc 4.6+ (Node v4+ requires 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) * [node-gyp](https://github.com/TooTallNate/node-gyp#installation)
### Linux ### Linux
@@ -14,30 +14,39 @@ npm install sharp
[![Ubuntu 14.04 Build Status](https://travis-ci.org/lovell/sharp.png?branch=master)](https://travis-ci.org/lovell/sharp) [![Ubuntu 14.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) [![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` during `npm install`. libvips and its dependencies are fetched and stored within `node_modules/sharp/lib` during `npm install`.
This involves an automated HTTPS download of approximately 6MB. This involves an automated HTTPS download of approximately 6.7MB.
Most recent Linux-based operating systems with glibc running on x64 and ARMv6+ CPUs should "just work", e.g.: Most recent Linux-based operating systems with glibc running on x64 and ARMv6+ CPUs should "just work", e.g.:
* Debian 7, 8 * Debian 7, 8
* Ubuntu 12.04, 14.04, 14.10, 15.04, 15.10 * Ubuntu 12.04, 14.04, 15.10, 16.04
* Centos 7 * Centos 7
* Fedora 21, 22, 23 * Fedora 22, 23
* openSUSE 13.2 * openSUSE 13.2
* Archlinux 2015.06.01 * Archlinux 2015.06.01
* Raspbian Jessie * Raspbian Jessie
* Amazon Linux 2015.03, 2015.09
To use your own version of libvips instead of the provided binaries, make sure it is
at least the version listed under `config.libvips` in the `package.json` file,
that it can be located using `pkg-config --modversion vips-cpp`
and that it has been compiled with `_GLIBCXX_USE_CXX11_ABI=0`.
If you are using non-stadard paths (anything other than `/usr` or `/usr/local`),
you might need to set `PKG_CONFIG_PATH` during `npm install`
and `LD_LIBRARY_PATH` at runtime.
Preference will be given to an existing globally-installed (via `pkg-config`)
version of libvips that meets the minimum version requirement.
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 older Linux-based operating systems and 32-bit Intel CPUs, For 32-bit Intel CPUs and older Linux-based operating systems such as Centos 6,
a system-wide installation of the most suitable version of a system-wide installation of the most suitable version of
libvips and its dependencies can be achieved by running libvips and its dependencies can be achieved by running
the following command as a user with `sudo` access the following command as a user with `sudo` access
(requires `curl` and `pkg-config`): (requires `curl` and `pkg-config`):
```sh ```sh
# WARNING: This script is deprecated. You probably don't need to run it. Please read above.
curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash - curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash -
``` ```
@@ -45,9 +54,15 @@ For Linux-based operating systems such as Alpine that use musl libc,
the smaller stack size means libvips' cache should be disabled the smaller stack size means libvips' cache should be disabled
via `sharp.cache(false)` to avoid a stack overflow. via `sharp.cache(false)` to avoid a stack overflow.
Beware of Linux OS upgrades that introduce v5.1+ of the `g++` compiler due to
[changes](https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_dual_abi.html)
in the C++11 ABI.
This module assumes the previous behaviour, which can be enforced by setting the
`_GLIBCXX_USE_CXX11_ABI=0` environment variable at libvips' compile time.
### Mac OS ### Mac OS
[![OS X 10.9.5 Build Status](https://travis-ci.org/lovell/sharp-osx-ci.png?branch=master)](https://travis-ci.org/lovell/sharp-osx-ci) [![OS X 10.9.5 Build Status](https://travis-ci.org/lovell/sharp.png?branch=master)](https://travis-ci.org/lovell/sharp)
libvips must be installed before `npm install` is run. libvips must be installed before `npm install` is run.
This can be achieved via homebrew: This can be achieved via homebrew:
@@ -56,10 +71,10 @@ This can be achieved via homebrew:
brew install homebrew/science/vips brew install homebrew/science/vips
``` ```
For GIF input and WebP output suppport use: For WebP suppport use:
```sh ```sh
brew install homebrew/science/vips --with-imagemagick --with-webp brew install homebrew/science/vips --with-webp
``` ```
A missing or incorrectly configured _Xcode Command Line Tools_ installation A missing or incorrectly configured _Xcode Command Line Tools_ installation
@@ -80,7 +95,6 @@ libvips and its dependencies are fetched and stored within `node_modules\sharp`
This involves an automated HTTPS download of approximately 9MB. This involves an automated HTTPS download of approximately 9MB.
Only 64-bit (x64) `node.exe` is supported. Only 64-bit (x64) `node.exe` is supported.
The WebP format is currently unavailable on Windows.
### FreeBSD ### FreeBSD
@@ -113,7 +127,69 @@ docker pull marcbachmann/libvips
docker pull wjordan/libvips docker pull wjordan/libvips
``` ```
### AWS Lambda
In order to use sharp on AWS Lambda, you need to [create a deployment package](http://docs.aws.amazon.com/lambda/latest/dg/nodejs-create-deployment-pkg.html). Because sharp
downloads and links libraries for the current platform during `npm install` you have to
do this on a system similar to the [Lambda Execution Environment](http://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html). The easiest ways to do this, is to setup a
small t2.micro instance using the AMI ID listed in the previous link, ssh into it as ec2-user
and follow the instructions below.
Install dependencies:
```sh
curl -s https://rpm.nodesource.com/setup_4.x | sudo bash -
sudo yum install -y gcc-c++ nodejs
```
Copy your code and package.json to the instance using `scp` and create a deployment package:
```sh
cd sharp-lambda-example
npm install
zip -ur9 ../sharp-lambda-example.zip index.js node_modules
```
You can now download your deployment ZIP using `scp` and upload it to Lambda. Be sure to set your Lambda runtime to Node.js 4.3.
**Performance Tip:** To get the best performance on Lambda choose the largest memory available because this also gives you the most cpu time (a 1536 MB function is 12x faster than a 128 MB function).
### Build tools ### 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)
### Security
Many users of this module process untrusted, user-supplied images,
but there are aspects of security to consider when doing so.
It is possible to compile libvips with support for various third-party image loaders.
Each of these libraries has undergone differing levels of security testing.
Whilst tools such as [American Fuzzy Lop](http://lcamtuf.coredump.cx/afl/)
and [Valgrind](http://valgrind.org/) have been used to test
the most popular web-based formats, as well as libvips itself,
you are advised to perform your own testing and sandboxing.
ImageMagick in particular has a relatively large attack surface,
which can be partially mitigated with a
[policy.xml](http://www.imagemagick.org/script/resources.php)
configuration file to prevent the use of coders known to be vulnerable.
```xml
<policymap>
<policy domain="coder" rights="none" pattern="EPHEMERAL" />
<policy domain="coder" rights="none" pattern="URL" />
<policy domain="coder" rights="none" pattern="HTTPS" />
<policy domain="coder" rights="none" pattern="MVG" />
<policy domain="coder" rights="none" pattern="MSL" />
<policy domain="coder" rights="none" pattern="TEXT" />
<policy domain="coder" rights="none" pattern="SHOW" />
<policy domain="coder" rights="none" pattern="WIN" />
<policy domain="coder" rights="none" pattern="PLT" />
</policymap>
```
Set the `MAGICK_CONFIGURE_PATH` environment variable
to the directory containing the `policy.xml` file.

View File

@@ -3,41 +3,44 @@
### Test environment ### Test environment
* AWS EC2 [c4.xlarge](http://aws.amazon.com/ec2/instance-types/#c4) (4x E5-2666 v3 @ 2.90GHz) * AWS EC2 [c4.xlarge](http://aws.amazon.com/ec2/instance-types/#c4) (4x E5-2666 v3 @ 2.90GHz)
* Amazon Linux 2015.09.1 * Amazon Linux AMI 2016.03.1 (HVM), SSD Volume Type
* Node.js v5.5.0 * Node.js v6.2.0
### The contenders ### The contenders
* [jimp](https://www.npmjs.com/package/jimp) v0.2.20 - Image processing in pure JavaScript. Bilinear interpolation only. * [jimp](https://www.npmjs.com/package/jimp) v0.2.24 - Image processing in pure JavaScript. Bilinear interpolation only.
* [lwip](https://www.npmjs.com/package/lwip) v0.0.8 - Wrapper around CImg, compiles dependencies from source. * [lwip](https://www.npmjs.com/package/lwip) v0.0.9 - Wrapper around CImg, compiles dependencies from source.
* [imagemagick-native](https://www.npmjs.com/package/imagemagick-native) @47c7329 - Wrapper around libmagick++, supports Buffers only. * [imagemagick-native](https://www.npmjs.com/package/imagemagick-native) v1.9.2 - 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.21.0 - Fully featured wrapper around GraphicsMagick's `gm` command line utility. * [gm](https://www.npmjs.com/package/gm) v1.22.0 - Fully featured wrapper around GraphicsMagick's `gm` command line utility.
* sharp v0.13.0 / libvips v8.2.2 - Caching within libvips disabled to ensure a fair comparison. * sharp v0.15.0 / libvips v8.3.1 - Caching within libvips disabled to ensure a fair comparison.
### The task ### The task
Decompress a 2725x2225 JPEG image, resize to 720x480 using bicubic interpolation (where available), then compress to JPEG. Decompress a 2725x2225 JPEG image,
resize to 720x480 using Lanczos 3 resampling (where available),
then compress to JPEG.
### Results ### Results
| Module | Input | Output | Ops/sec | Speed-up | | Module | Input | Output | Ops/sec | Speed-up |
| :----------------- | :----- | :----- | ------: | -------: | | :----------------- | :----- | :----- | ------: | -------: |
| jimp (bilinear) | file | file | 1.04 | 1.0 | | jimp (bilinear) | file | file | 0.94 | 1.0 |
| jimp (bilinear) | buffer | buffer | 1.07 | 1.0 | | jimp (bilinear) | buffer | buffer | 0.98 | 1.0 |
| lwip | file | file | 1.13 | 1.1 | | lwip | file | file | 1.14 | 1.2 |
| lwip | buffer | buffer | 1.13 | 1.1 | | lwip | buffer | buffer | 1.14 | 1.2 |
| imagemagick-native | buffer | buffer | 1.65 | 1.6 | | imagemagick-native | buffer | buffer | 1.66 | 1.8 |
| imagemagick | file | file | 5.02 | 4.8 | | imagemagick | file | file | 5.08 | 5.4 |
| gm | buffer | buffer | 5.36 | 5.2 | | gm | buffer | buffer | 5.43 | 5.7 |
| gm | file | file | 5.39 | 5.2 | | gm | file | file | 5.46 | 5.8 |
| sharp | stream | stream | 22.00 | 21.2 | | sharp | stream | stream | 26.52 | 28.2 |
| sharp | file | file | 22.87 | 22.0 | | sharp | file | file | 28.16 | 30.0 |
| sharp | file | buffer | 23.03 | 22.1 | | sharp | file | buffer | 28.27 | 30.1 |
| sharp | buffer | file | 23.10 | 22.2 | | sharp | buffer | file | 28.42 | 30.2 |
| sharp | buffer | buffer | 23.21 | 22.3 | | sharp | buffer | buffer | 28.42 | 30.2 |
Greater performance can be expected with caching enabled (default) and using 8+ core machines. Greater libvips performance can be expected with caching enabled (default)
and using 8+ core machines, especially those with larger L1/L2 CPU caches.
The I/O limits of the relevant (de)compression library will generally determine maximum throughput. The I/O limits of the relevant (de)compression library will generally determine maximum throughput.

349
index.js
View File

@@ -46,7 +46,7 @@ var Sharp = function(input, options) {
streamIn: false, streamIn: false,
sequentialRead: false, sequentialRead: false,
limitInputPixels: maximum.pixels, limitInputPixels: maximum.pixels,
density: '72', density: 72,
rawWidth: 0, rawWidth: 0,
rawHeight: 0, rawHeight: 0,
rawChannels: 0, rawChannels: 0,
@@ -64,19 +64,24 @@ var Sharp = function(input, options) {
width: -1, width: -1,
height: -1, height: -1,
canvas: 'crop', canvas: 'crop',
gravity: 0, crop: 0,
angle: 0, angle: 0,
rotateBeforePreExtract: false, rotateBeforePreExtract: false,
flip: false, flip: false,
flop: false, flop: false,
extendTop: 0,
extendBottom: 0,
extendLeft: 0,
extendRight: 0,
withoutEnlargement: false, withoutEnlargement: false,
kernel: 'lanczos3',
interpolator: 'bicubic', interpolator: 'bicubic',
// operations // operations
background: [0, 0, 0, 255], background: [0, 0, 0, 255],
flatten: false, flatten: false,
negate: false, negate: false,
blurSigma: 0, blurSigma: 0,
sharpenRadius: 0, sharpenSigma: 0,
sharpenFlat: 1, sharpenFlat: 1,
sharpenJagged: 2, sharpenJagged: 2,
threshold: 0, threshold: 0,
@@ -84,7 +89,9 @@ var Sharp = function(input, options) {
greyscale: false, greyscale: false,
normalize: 0, normalize: 0,
// overlay // overlay
overlayPath: '', overlayFileIn: '',
overlayBufferIn: null,
overlayGravity: 0,
// output options // output options
formatOut: 'input', formatOut: 'input',
fileOut: '', fileOut: '',
@@ -106,13 +113,13 @@ var Sharp = function(input, options) {
module.exports.queue.emit('change', queueLength); module.exports.queue.emit('change', queueLength);
} }
}; };
if (typeof input === 'string') { if (isString(input)) {
// input=file // input=file
this.options.fileIn = input; this.options.fileIn = input;
} else if (typeof input === 'object' && input instanceof Buffer) { } else if (isBuffer(input)) {
// input=buffer // input=buffer
this.options.bufferIn = input; this.options.bufferIn = input;
} else if (typeof input === 'undefined' || input === null) { } else if (!isDefined(input)) {
// input=stream // input=stream
this.options.streamIn = true; this.options.streamIn = true;
} else { } else {
@@ -148,12 +155,27 @@ var isDefined = function(val) {
var isObject = function(val) { var isObject = function(val) {
return typeof val === 'object'; return typeof val === 'object';
}; };
var isBoolean = function(val) {
return typeof val === 'boolean';
};
var isBuffer = function(val) {
return typeof val === 'object' && val instanceof Buffer;
};
var isString = function(val) {
return typeof val === 'string' && val.length > 0;
};
var isNumber = function(val) {
return typeof val === 'number' && !Number.isNaN(val);
};
var isInteger = function(val) { var isInteger = function(val) {
return typeof val === 'number' && !Number.isNaN(val) && val % 1 === 0; return isNumber(val) && val % 1 === 0;
}; };
var inRange = function(val, min, max) { var inRange = function(val, min, max) {
return val >= min && val <= max; return val >= min && val <= max;
}; };
var contains = function(val, list) {
return list.indexOf(val) !== -1;
};
/* /*
Set input-related options Set input-related options
@@ -164,7 +186,7 @@ Sharp.prototype._inputOptions = function(options) {
// Density // Density
if (isDefined(options.density)) { if (isDefined(options.density)) {
if (isInteger(options.density) && inRange(options.density, 1, 2400)) { if (isInteger(options.density) && inRange(options.density, 1, 2400)) {
this.options.density = options.density.toString(); this.options.density = options.density;
} else { } else {
throw new Error('Invalid density (1 to 2400) ' + options.density); throw new Error('Invalid density (1 to 2400) ' + options.density);
} }
@@ -216,48 +238,53 @@ Sharp.prototype._write = function(chunk, encoding, callback) {
} }
}; };
// Crop this part of the resized image (Center/Centre, North, East, South, West) // Weighting to apply to image crop
module.exports.gravity = { module.exports.gravity = {
'center': 0, center: 0,
'centre': 0, centre: 0,
'north': 1, north: 1,
'east': 2, east: 2,
'south': 3, south: 3,
'west': 4, west: 4,
'northeast': 5, northeast: 5,
'southeast': 6, southeast: 6,
'southwest': 7, southwest: 7,
'northwest': 8 northwest: 8
}; };
Sharp.prototype.crop = function(gravity) { // Strategies for automagic behaviour
module.exports.strategy = {
entropy: 16
};
/*
What part of the image should be retained when cropping?
*/
Sharp.prototype.crop = function(crop) {
this.options.canvas = 'crop'; this.options.canvas = 'crop';
if (typeof gravity === 'undefined') { if (!isDefined(crop)) {
this.options.gravity = module.exports.gravity.center; // Default
} else if (typeof gravity === 'number' && !Number.isNaN(gravity) && gravity >= 0 && gravity <= 8) { this.options.crop = module.exports.gravity.center;
this.options.gravity = gravity; } else if (isInteger(crop) && inRange(crop, 0, 8)) {
} else if (typeof gravity === 'string' && typeof module.exports.gravity[gravity] === 'number') { // Gravity (numeric)
this.options.gravity = module.exports.gravity[gravity]; this.options.crop = crop;
} else if (isString(crop) && isInteger(module.exports.gravity[crop])) {
// Gravity (string)
this.options.crop = module.exports.gravity[crop];
} else if (isInteger(crop) && crop === module.exports.strategy.entropy) {
// Strategy
this.options.crop = crop;
} else { } else {
throw new Error('Unsupported crop gravity ' + gravity); throw new Error('Unsupported crop ' + crop);
} }
return this; return this;
}; };
Sharp.prototype.extract = function(options) { Sharp.prototype.extract = function(options) {
if (!options || typeof options !== 'object') {
// Legacy extract(top,left,width,height) syntax
options = {
left: arguments[1],
top: arguments[0],
width: arguments[2],
height: arguments[3]
};
}
var suffix = this.options.width === -1 && this.options.height === -1 ? 'Pre' : 'Post'; var suffix = this.options.width === -1 && this.options.height === -1 ? 'Pre' : 'Post';
['left', 'top', 'width', 'height'].forEach(function (name) { ['left', 'top', 'width', 'height'].forEach(function (name) {
var value = options[name]; var value = options[name];
if (typeof value === 'number' && !Number.isNaN(value) && value % 1 === 0 && value >= 0) { if (isInteger(value) && value >= 0) {
this.options[name + (name === 'left' || name === 'top' ? 'Offset' : '') + suffix] = value; this.options[name + (name === 'left' || name === 'top' ? 'Offset' : '') + suffix] = value;
} else { } else {
throw new Error('Non-integer value for ' + name + ' of ' + value); throw new Error('Non-integer value for ' + name + ' of ' + value);
@@ -316,14 +343,26 @@ Sharp.prototype.negate = function(negate) {
return this; return this;
}; };
Sharp.prototype.overlayWith = function(overlayPath) { /*
if (typeof overlayPath !== 'string') { Overlay with another image, using an optional gravity
throw new Error('The overlay path must be a string'); */
Sharp.prototype.overlayWith = function(overlay, options) {
if (isString(overlay)) {
this.options.overlayFileIn = overlay;
} else if (isBuffer(overlay)) {
this.options.overlayBufferIn = overlay;
} else {
throw new Error('Unsupported overlay ' + typeof overlay);
}
if (isObject(options)) {
if (isInteger(options.gravity) && inRange(options.gravity, 0, 8)) {
this.options.overlayGravity = options.gravity;
} else if (isString(options.gravity) && isInteger(module.exports.gravity[options.gravity])) {
this.options.overlayGravity = module.exports.gravity[options.gravity];
} else if (isDefined(options.gravity)) {
throw new Error('Unsupported overlay gravity ' + options.gravity);
} }
if (overlayPath === '') {
throw new Error('The overlay path cannot be empty');
} }
this.options.overlayPath = overlayPath;
return this; return this;
}; };
@@ -374,17 +413,17 @@ Sharp.prototype.withoutEnlargement = function(withoutEnlargement) {
Call with a sigma to use a slower, more accurate Gaussian blur. Call with a sigma to use a slower, more accurate Gaussian blur.
*/ */
Sharp.prototype.blur = function(sigma) { Sharp.prototype.blur = function(sigma) {
if (typeof sigma === 'undefined') { if (!isDefined(sigma)) {
// No arguments: default to mild blur // No arguments: default to mild blur
this.options.blurSigma = -1; this.options.blurSigma = -1;
} else if (typeof sigma === 'boolean') { } else if (isBoolean(sigma)) {
// Boolean argument: apply mild blur? // Boolean argument: apply mild blur?
this.options.blurSigma = sigma ? -1 : 0; this.options.blurSigma = sigma ? -1 : 0;
} else if (typeof sigma === 'number' && !Number.isNaN(sigma) && sigma >= 0.3 && sigma <= 1000) { } else if (isNumber(sigma) && inRange(sigma, 0.3, 1000)) {
// Numeric argument: specific sigma // Numeric argument: specific sigma
this.options.blurSigma = sigma; this.options.blurSigma = sigma;
} else { } else {
throw new Error('Invalid blur sigma (0.3 to 1000.0) ' + sigma); throw new Error('Invalid blur sigma (0.3 - 1000.0) ' + sigma);
} }
return this; return this;
}; };
@@ -393,38 +432,38 @@ Sharp.prototype.blur = function(sigma) {
Sharpen the output image. Sharpen the output image.
Call without a radius to use a fast, mild sharpen. Call without a radius to use a fast, mild sharpen.
Call with a radius to use a slow, accurate sharpen using the L of LAB colour space. Call with a radius to use a slow, accurate sharpen using the L of LAB colour space.
radius - size of mask in pixels, must be integer sigma - sigma of mask
flat - level of "flat" area sharpen, default 1 flat - level of "flat" area sharpen, default 1
jagged - level of "jagged" area sharpen, default 2 jagged - level of "jagged" area sharpen, default 2
*/ */
Sharp.prototype.sharpen = function(radius, flat, jagged) { Sharp.prototype.sharpen = function(sigma, flat, jagged) {
if (typeof radius === 'undefined') { if (!isDefined(sigma)) {
// No arguments: default to mild sharpen // No arguments: default to mild sharpen
this.options.sharpenRadius = -1; this.options.sharpenSigma = -1;
} else if (typeof radius === 'boolean') { } else if (isBoolean(sigma)) {
// Boolean argument: apply mild sharpen? // Boolean argument: apply mild sharpen?
this.options.sharpenRadius = radius ? -1 : 0; this.options.sharpenSigma = sigma ? -1 : 0;
} else if (typeof radius === 'number' && !Number.isNaN(radius) && (radius % 1 === 0) && radius >= 1) { } else if (isNumber(sigma) && inRange(sigma, 0.01, 10000)) {
// Numeric argument: specific radius // Numeric argument: specific sigma
this.options.sharpenRadius = radius; this.options.sharpenSigma = sigma;
// Control over flat areas // Control over flat areas
if (typeof flat !== 'undefined' && flat !== null) { if (isDefined(flat)) {
if (typeof flat === 'number' && !Number.isNaN(flat) && flat >= 0) { if (isNumber(flat) && inRange(flat, 0, 10000)) {
this.options.sharpenFlat = flat; this.options.sharpenFlat = flat;
} else { } else {
throw new Error('Invalid sharpen level for flat areas ' + flat + ' (expected >= 0)'); throw new Error('Invalid sharpen level for flat areas (0 - 10000) ' + flat);
} }
} }
// Control over jagged areas // Control over jagged areas
if (typeof jagged !== 'undefined' && jagged !== null) { if (isDefined(jagged)) {
if (typeof jagged === 'number' && !Number.isNaN(jagged) && jagged >= 0) { if (isNumber(jagged) && inRange(jagged, 0, 10000)) {
this.options.sharpenJagged = jagged; this.options.sharpenJagged = jagged;
} else { } else {
throw new Error('Invalid sharpen level for jagged areas ' + jagged + ' (expected >= 0)'); throw new Error('Invalid sharpen level for jagged areas (0 - 10000) ' + jagged);
} }
} }
} else { } else {
throw new Error('Invalid sharpen radius ' + radius + ' (expected integer >= 1)'); throw new Error('Invalid sharpen sigma (0.01 - 10000) ' + sigma);
} }
return this; return this;
}; };
@@ -442,33 +481,6 @@ Sharp.prototype.threshold = function(threshold) {
return this; return this;
}; };
/*
Set the interpolator to use for the affine transformation
*/
module.exports.interpolator = {
nearest: 'nearest',
bilinear: 'bilinear',
bicubic: 'bicubic',
nohalo: 'nohalo',
locallyBoundedBicubic: 'lbb',
vertexSplitQuadraticBasisSpline: 'vsqbs'
};
Sharp.prototype.interpolateWith = function(interpolator) {
var isValid = false;
for (var key in module.exports.interpolator) {
if (module.exports.interpolator[key] === interpolator) {
isValid = true;
break;
}
}
if (isValid) {
this.options.interpolator = interpolator;
} else {
throw new Error('Invalid interpolator ' + interpolator);
}
return this;
};
/* /*
Darken image pre-resize (1/gamma) and brighten post-resize (gamma). Darken image pre-resize (1/gamma) and brighten post-resize (gamma).
Improves brightness of resized image in non-linear colour spaces. Improves brightness of resized image in non-linear colour spaces.
@@ -592,12 +604,12 @@ Sharp.prototype.withMetadata = function(withMetadata) {
typeof withMetadata.orientation === 'number' && typeof withMetadata.orientation === 'number' &&
!Number.isNaN(withMetadata.orientation) && !Number.isNaN(withMetadata.orientation) &&
withMetadata.orientation % 1 === 0 && withMetadata.orientation % 1 === 0 &&
withMetadata.orientation >= 0 && withMetadata.orientation >= 1 &&
withMetadata.orientation <= 7 withMetadata.orientation <= 8
) { ) {
this.options.withMetadataOrientation = withMetadata.orientation; this.options.withMetadataOrientation = withMetadata.orientation;
} else { } else {
throw new Error('Invalid orientation (0 to 7) ' + withMetadata.orientation); throw new Error('Invalid orientation (1 to 8) ' + withMetadata.orientation);
} }
} }
} }
@@ -605,59 +617,158 @@ Sharp.prototype.withMetadata = function(withMetadata) {
}; };
/* /*
Tile size and overlap for Deep Zoom output Tile-based deep zoom output options: size, overlap, layout
*/ */
Sharp.prototype.tile = function(size, overlap) { Sharp.prototype.tile = function(tile) {
if (isObject(tile)) {
// Size of square tiles, in pixels // Size of square tiles, in pixels
if (typeof size !== 'undefined' && size !== null) { if (isDefined(tile.size)) {
if (!Number.isNaN(size) && size % 1 === 0 && size >= 1 && size <= 8192) { if (isInteger(tile.size) && inRange(tile.size, 1, 8192)) {
this.options.tileSize = size; this.options.tileSize = tile.size;
} else { } else {
throw new Error('Invalid tile size (1 to 8192) ' + size); throw new Error('Invalid tile size (1 to 8192) ' + tile.size);
} }
} }
// Overlap of tiles, in pixels // Overlap of tiles, in pixels
if (typeof overlap !== 'undefined' && overlap !== null) { if (isDefined(tile.overlap)) {
if (!Number.isNaN(overlap) && overlap % 1 === 0 && overlap >= 0 && overlap <= 8192) { if (isInteger(tile.overlap) && inRange(tile.overlap, 0, 8192)) {
if (overlap > this.options.tileSize) { if (tile.overlap > this.options.tileSize) {
throw new Error('Tile overlap ' + overlap + ' cannot be larger than tile size ' + this.options.tileSize); throw new Error('Tile overlap ' + tile.overlap + ' cannot be larger than tile size ' + this.options.tileSize);
} }
this.options.tileOverlap = overlap; this.options.tileOverlap = tile.overlap;
} else { } else {
throw new Error('Invalid tile overlap (0 to 8192) ' + overlap); throw new Error('Invalid tile overlap (0 to 8192) ' + tile.overlap);
}
}
// Container
if (isDefined(tile.container)) {
if (isString(tile.container) && contains(tile.container, ['fs', 'zip'])) {
this.options.tileContainer = tile.container;
} else {
throw new Error('Invalid tile container ' + tile.container);
}
}
// Layout
if (isDefined(tile.layout)) {
if (isString(tile.layout) && contains(tile.layout, ['dz', 'google', 'zoomify'])) {
this.options.tileLayout = tile.layout;
} else {
throw new Error('Invalid tile layout ' + tile.layout);
}
} }
} }
return this; return this;
}; };
Sharp.prototype.resize = function(width, height) { /*
if (!width) { Extend edges
this.options.width = -1; */
Sharp.prototype.extend = function(extend) {
if (isInteger(extend) && extend > 0) {
this.options.extendTop = extend;
this.options.extendBottom = extend;
this.options.extendLeft = extend;
this.options.extendRight = extend;
} else if (
isObject(extend) &&
isInteger(extend.top) && extend.top >= 0 &&
isInteger(extend.bottom) && extend.bottom >= 0 &&
isInteger(extend.left) && extend.left >= 0 &&
isInteger(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 { } else {
if (typeof width === 'number' && !Number.isNaN(width) && width % 1 === 0 && width > 0 && width <= maximum.width) { throw new Error('Invalid edge extension ' + extend);
}
return this;
};
// Kernels for reduction
module.exports.kernel = {
cubic: 'cubic',
lanczos2: 'lanczos2',
lanczos3: 'lanczos3'
};
// Interpolators for enlargement
module.exports.interpolator = {
nearest: 'nearest',
bilinear: 'bilinear',
bicubic: 'bicubic',
nohalo: 'nohalo',
lbb: 'lbb',
locallyBoundedBicubic: 'lbb',
vsqbs: 'vsqbs',
vertexSplitQuadraticBasisSpline: 'vsqbs'
};
/*
Resize image to width x height pixels
options.kernel is the kernel to use for reductions, default 'lanczos3'
options.interpolator is the interpolator to use for enlargements, default 'bicubic'
*/
Sharp.prototype.resize = function(width, height, options) {
if (isDefined(width)) {
if (isInteger(width) && inRange(width, 1, maximum.width)) {
this.options.width = width; this.options.width = width;
} else { } else {
throw new Error('Invalid width (1 to ' + maximum.width + ') ' + width); throw new Error('Invalid width (1 to ' + maximum.width + ') ' + width);
} }
}
if (!height) {
this.options.height = -1;
} else { } else {
if (typeof height === 'number' && !Number.isNaN(height) && height % 1 === 0 && height > 0 && height <= maximum.height) { this.options.width = -1;
}
if (isDefined(height)) {
if (isInteger(height) && inRange(height, 1, maximum.height)) {
this.options.height = height; this.options.height = height;
} else { } else {
throw new Error('Invalid height (1 to ' + maximum.height + ') ' + height); throw new Error('Invalid height (1 to ' + maximum.height + ') ' + height);
} }
} else {
this.options.height = -1;
}
if (isObject(options)) {
// Kernel
if (isDefined(options.kernel)) {
if (isString(module.exports.kernel[options.kernel])) {
this.options.kernel = module.exports.kernel[options.kernel];
} else {
throw new Error('Invalid kernel ' + options.kernel);
}
}
// Interpolator
if (isDefined(options.interpolator)) {
if (isString(module.exports.interpolator[options.interpolator])) {
this.options.interpolator = module.exports.interpolator[options.interpolator];
} else {
throw new Error('Invalid interpolator ' + options.interpolator);
}
}
} }
return this; return this;
}; };
Sharp.prototype.interpolateWith = util.deprecate(function(interpolator) {
return this.resize(
this.options.width > 0 ? this.options.width : null,
this.options.height > 0 ? this.options.height : null,
{ interpolator: interpolator }
);
}, 'interpolateWith: Please use resize(w, h, { interpolator: ... }) instead');
/* /*
Limit the total number of pixels for input images Limit the total number of pixels for input images
Assumes the image dimensions contained in the file header can be trusted Assumes the image dimensions contained in the file header can be trusted.
Alternatively can use boolean to disable or reset to default (maximum pixels)
*/ */
Sharp.prototype.limitInputPixels = function(limit) { Sharp.prototype.limitInputPixels = function(limit) {
if (typeof limit === 'number' && !Number.isNaN(limit) && limit % 1 === 0 && limit > 0) { //if we pass in false we represent the integer as 0 to disable
if(limit === false) {
limit = 0;
} else if(limit === true) {
limit = maximum.pixels;
}
if (typeof limit === 'number' && !Number.isNaN(limit) && limit % 1 === 0 && limit >= 0) {
this.options.limitInputPixels = limit; this.options.limitInputPixels = limit;
} else { } else {
throw new Error('Invalid pixel limit (1 to ' + maximum.pixels + ') ' + limit); throw new Error('Invalid pixel limit (1 to ' + maximum.pixels + ') ' + limit);
@@ -783,10 +894,11 @@ Sharp.prototype._pipeline = function(callback) {
if (this.options.streamIn) { if (this.options.streamIn) {
// output=stream, input=stream // output=stream, input=stream
this.on('finish', function() { this.on('finish', function() {
sharp.pipeline(that.options, function(err, data) { sharp.pipeline(that.options, function(err, data, info) {
if (err) { if (err) {
that.emit('error', err); that.emit('error', err);
} else { } else {
that.emit('info', info);
that.push(data); that.push(data);
} }
that.push(null); that.push(null);
@@ -794,10 +906,11 @@ Sharp.prototype._pipeline = function(callback) {
}); });
} else { } else {
// output=stream, input=file/buffer // output=stream, input=file/buffer
sharp.pipeline(this.options, function(err, data) { sharp.pipeline(this.options, function(err, data, info) {
if (err) { if (err) {
that.emit('error', err); that.emit('error', err);
} else { } else {
that.emit('info', info);
that.push(data); that.push(data);
} }
that.push(null); that.push(null);

View File

@@ -1,6 +1,6 @@
{ {
"name": "sharp", "name": "sharp",
"version": "0.13.1", "version": "0.15.0",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"contributors": [ "contributors": [
"Pierre Inglebert <pierre.inglebert@gmail.com>", "Pierre Inglebert <pierre.inglebert@gmail.com>",
@@ -18,13 +18,17 @@
"Alaric Holloway <alaric.holloway@gmail.com>", "Alaric Holloway <alaric.holloway@gmail.com>",
"Bernhard K. Weisshuhn <bkw@codingforce.com>", "Bernhard K. Weisshuhn <bkw@codingforce.com>",
"Chris Riley <criley@primedia.com>", "Chris Riley <criley@primedia.com>",
"David Carley <dacarley@gmail.com>" "David Carley <dacarley@gmail.com>",
"John Tobin <john@limelightmobileinc.com>",
"Kenton Gray <kentongray@gmail.com>",
"Felix Bünemann <Felix.Buenemann@gmail.com>",
"Samy Al Zahrani <samyalzahrany@gmail.com>"
], ],
"description": "High performance Node.js module to resize JPEG, PNG, WebP and TIFF images using the libvips library", "description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images",
"scripts": { "scripts": {
"clean": "rm -rf node_modules/ build/ include/ lib/ coverage/ test/fixtures/output.*", "clean": "rm -rf node_modules/ build/ include/ lib/ coverage/ test/fixtures/output.*",
"test": "VIPS_WARNING=0 node ./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha -- --slow=5000 --timeout=30000 ./test/unit/*.js", "test": "VIPS_WARNING=0 node ./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha -- --slow=5000 --timeout=30000 ./test/unit/*.js",
"test-win": "node ./node_modules/mocha/bin/mocha --slow=5000 --timeout=30000 ./test/unit/*.js", "test-win": "node ./node_modules/mocha/bin/mocha --slow=5000 --timeout=60000 ./test/unit/*.js",
"test-leak": "./test/leak/leak.sh", "test-leak": "./test/leak/leak.sh",
"test-packaging": "./packaging/test.sh", "test-packaging": "./packaging/test.sh",
"test-clean": "rm -rf coverage/ test/fixtures/output.* && npm install && npm test" "test-clean": "rm -rf coverage/ test/fixtures/output.* && npm install && npm test"
@@ -39,7 +43,10 @@
"png", "png",
"webp", "webp",
"tiff", "tiff",
"gif",
"svg",
"dzi", "dzi",
"image",
"resize", "resize",
"thumbnail", "thumbnail",
"crop", "crop",
@@ -47,28 +54,29 @@
"vips" "vips"
], ],
"dependencies": { "dependencies": {
"bluebird": "^3.3.3", "bluebird": "^3.3.5",
"color": "^0.11.1", "color": "^0.11.1",
"nan": "^2.2.0", "nan": "^2.2.1",
"semver": "^5.1.0", "semver": "^5.1.0",
"request": "^2.69.0", "request": "^2.71.0",
"tar": "^2.2.1" "tar": "^2.2.1"
}, },
"devDependencies": { "devDependencies": {
"async": "^1.5.2", "async": "^1.5.2",
"coveralls": "^2.11.8", "bufferutil": "^1.2.1",
"coveralls": "^2.11.9",
"exif-reader": "^1.0.0", "exif-reader": "^1.0.0",
"icc": "^0.0.2", "icc": "^0.0.2",
"istanbul": "^0.4.2", "istanbul": "^0.4.3",
"mocha": "^2.4.5", "mocha": "^2.4.5",
"mocha-jshint": "^2.3.1", "mocha-jshint": "^2.3.1",
"node-cpplint": "^0.4.0", "node-cpplint": "^0.4.0",
"rimraf": "^2.5.2", "rimraf": "^2.5.2",
"bufferutil": "^1.2.1" "unzip": "^0.1.11"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"config": { "config": {
"libvips": "8.2.2" "libvips": "8.3.1"
}, },
"engines": { "engines": {
"node": ">=0.10" "node": ">=0.10"

View File

@@ -19,18 +19,30 @@ export CXXFLAGS="-O3"
# Dependency version numbers # Dependency version numbers
VERSION_ZLIB=1.2.8 VERSION_ZLIB=1.2.8
VERSION_FFI=3.2.1 VERSION_FFI=3.2.1
VERSION_GLIB=2.47.5 VERSION_GLIB=2.48.0
VERSION_XML2=2.9.3 VERSION_XML2=2.9.3
VERSION_GSF=1.14.34 VERSION_GSF=1.14.36
VERSION_EXIF=0.6.21 VERSION_EXIF=0.6.21
VERSION_LCMS2=2.7 VERSION_LCMS2=2.7
VERSION_GM=1.3.23 VERSION_JPEG=1.4.90
VERSION_JPEG=1.4.2
VERSION_PNG16=1.6.21 VERSION_PNG16=1.6.21
VERSION_WEBP=0.5.0 VERSION_WEBP=0.5.0
VERSION_TIFF=4.0.6 VERSION_TIFF=4.0.6
VERSION_ORC=0.4.24 VERSION_ORC=0.4.25
VERSION_VIPS=8.2.2 VERSION_GDKPIXBUF=2.34.0
VERSION_FREETYPE=2.6.3
VERSION_FONTCONFIG=2.11.95
VERSION_HARFBUZZ=1.2.6
VERSION_PIXMAN=0.34.0
VERSION_CAIRO=1.14.6
VERSION_PANGO=1.40.1
VERSION_CROCO=0.6.11
VERSION_SVG=2.40.15
VERSION_GIF=5.1.4
VERSION_VIPS=8.3.1
# Least out-of-sync Sourceforge mirror
SOURCEFORGE_MIRROR=netix
mkdir ${DEPS}/zlib mkdir ${DEPS}/zlib
curl -Ls http://zlib.net/zlib-${VERSION_ZLIB}.tar.xz | tar xJC ${DEPS}/zlib --strip-components=1 curl -Ls http://zlib.net/zlib-${VERSION_ZLIB}.tar.xz | tar xJC ${DEPS}/zlib --strip-components=1
@@ -44,7 +56,7 @@ cd ${DEPS}/ffi
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-builddir && make install-strip ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-builddir && make install-strip
mkdir ${DEPS}/glib mkdir ${DEPS}/glib
curl -Ls https://download.gnome.org/sources/glib/2.47/glib-${VERSION_GLIB}.tar.xz | tar xJC ${DEPS}/glib --strip-components=1 curl -Ls https://download.gnome.org/sources/glib/2.48/glib-${VERSION_GLIB}.tar.xz | tar xJC ${DEPS}/glib --strip-components=1
cd ${DEPS}/glib cd ${DEPS}/glib
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --with-pcre=internal && make install-strip ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --with-pcre=internal && make install-strip
@@ -59,27 +71,22 @@ cd ${DEPS}/gsf
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
mkdir ${DEPS}/exif mkdir ${DEPS}/exif
curl -Ls http://heanet.dl.sourceforge.net/project/libexif/libexif/${VERSION_EXIF}/libexif-${VERSION_EXIF}.tar.bz2 | tar xjC ${DEPS}/exif --strip-components=1 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 cd ${DEPS}/exif
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
mkdir ${DEPS}/lcms2 mkdir ${DEPS}/lcms2
curl -Ls http://heanet.dl.sourceforge.net/project/lcms/lcms/${VERSION_LCMS2}/lcms2-${VERSION_LCMS2}.tar.gz | tar xzC ${DEPS}/lcms2 --strip-components=1 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 cd ${DEPS}/lcms2
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
mkdir ${DEPS}/gm
curl -Ls http://heanet.dl.sourceforge.net/project/graphicsmagick/graphicsmagick/${VERSION_GM}/GraphicsMagick-${VERSION_GM}.tar.xz | tar xJC ${DEPS}/gm --strip-components=1
cd ${DEPS}/gm
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --without-magick-plus-plus && make install-strip
mkdir ${DEPS}/jpeg mkdir ${DEPS}/jpeg
curl -Ls http://heanet.dl.sourceforge.net/project/libjpeg-turbo/${VERSION_JPEG}/libjpeg-turbo-${VERSION_JPEG}.tar.gz | tar xzC ${DEPS}/jpeg --strip-components=1 curl -Ls https://github.com/libjpeg-turbo/libjpeg-turbo/archive/${VERSION_JPEG}.tar.gz | tar xzC ${DEPS}/jpeg --strip-components=1
cd ${DEPS}/jpeg cd ${DEPS}/jpeg
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --with-jpeg8 --without-turbojpeg && make install-strip autoreconf -fiv && ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --with-jpeg8 --without-turbojpeg && make install-strip
mkdir ${DEPS}/png16 mkdir ${DEPS}/png16
curl -Ls http://heanet.dl.sourceforge.net/project/libpng/libpng16/${VERSION_PNG16}/libpng-${VERSION_PNG16}.tar.xz | tar xJC ${DEPS}/png16 --strip-components=1 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 cd ${DEPS}/png16
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
@@ -99,11 +106,69 @@ curl -Ls http://gstreamer.freedesktop.org/data/src/orc/orc-${VERSION_ORC}.tar.xz
cd ${DEPS}/orc cd ${DEPS}/orc
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
mkdir ${DEPS}/gdkpixbuf
curl -Ls https://download.gnome.org/sources/gdk-pixbuf/2.34/gdk-pixbuf-${VERSION_GDKPIXBUF}.tar.xz | tar xJC ${DEPS}/gdkpixbuf --strip-components=1
cd ${DEPS}/gdkpixbuf
LD_LIBRARY_PATH=${TARGET}/lib ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--disable-introspection --disable-modules --without-libpng --without-libjpeg --without-libtiff --without-gdiplus --with-included-loaders= \
&& make install-strip
mkdir ${DEPS}/freetype
curl -Ls http://download.savannah.gnu.org/releases/freetype/freetype-${VERSION_FREETYPE}.tar.gz | tar xzC ${DEPS}/freetype --strip-components=1
cd ${DEPS}/freetype
./configure --prefix=${TARGET} --enable-shared --disable-static && make install
mkdir ${DEPS}/fontconfig
curl -Ls https://www.freedesktop.org/software/fontconfig/release/fontconfig-${VERSION_FONTCONFIG}.tar.bz2 | tar xjC ${DEPS}/fontconfig --strip-components=1
cd ${DEPS}/fontconfig
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --enable-libxml2 && make install-strip
mkdir ${DEPS}/harfbuzz
curl -Ls https://www.freedesktop.org/software/harfbuzz/release/harfbuzz-${VERSION_HARFBUZZ}.tar.bz2 | tar xjC ${DEPS}/harfbuzz --strip-components=1
cd ${DEPS}/harfbuzz
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
mkdir ${DEPS}/pixman
curl -Ls http://cairographics.org/releases/pixman-${VERSION_PIXMAN}.tar.gz | tar xzC ${DEPS}/pixman --strip-components=1
cd ${DEPS}/pixman
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-libpng --disable-arm-iwmmxt && make install-strip
mkdir ${DEPS}/cairo
curl -Ls http://cairographics.org/releases/cairo-${VERSION_CAIRO}.tar.xz | tar xJC ${DEPS}/cairo --strip-components=1
cd ${DEPS}/cairo
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--disable-xlib --disable-xcb --disable-quartz --disable-win32 --disable-egl --disable-glx --disable-wgl \
--disable-script --disable-ps --disable-gobject --disable-trace --disable-interpreter \
&& make install-strip
mkdir ${DEPS}/pango
curl -Ls https://download.gnome.org/sources/pango/1.40/pango-${VERSION_PANGO}.tar.xz | tar xJC ${DEPS}/pango --strip-components=1
cd ${DEPS}/pango
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
mkdir ${DEPS}/croco
curl -Ls https://download.gnome.org/sources/libcroco/0.6/libcroco-${VERSION_CROCO}.tar.xz | tar xJC ${DEPS}/croco --strip-components=1
cd ${DEPS}/croco
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
mkdir ${DEPS}/svg
curl -Ls https://download.gnome.org/sources/librsvg/2.40/librsvg-${VERSION_SVG}.tar.xz | tar xJC ${DEPS}/svg --strip-components=1
cd ${DEPS}/svg
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--disable-introspection --disable-tools \
&& make install-strip
mkdir ${DEPS}/gif
curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/giflib/giflib-${VERSION_GIF}.tar.gz | tar xzC ${DEPS}/gif --strip-components=1
cd ${DEPS}/gif
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
mkdir ${DEPS}/vips mkdir ${DEPS}/vips
curl -Ls http://www.vips.ecs.soton.ac.uk/supported/8.2/vips-${VERSION_VIPS}.tar.gz | tar xzC ${DEPS}/vips --strip-components=1 curl -Ls http://www.vips.ecs.soton.ac.uk/supported/8.3/vips-${VERSION_VIPS}.tar.gz | tar xzC ${DEPS}/vips --strip-components=1
cd ${DEPS}/vips cd ${DEPS}/vips
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \ ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--disable-debug --disable-introspection --without-python --without-fftw --with-magickpackage=GraphicsMagick \ --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-zip-includes=${TARGET}/include --with-zip-libraries=${TARGET}/lib \
--with-jpeg-includes=${TARGET}/include --with-jpeg-libraries=${TARGET}/lib \ --with-jpeg-includes=${TARGET}/include --with-jpeg-libraries=${TARGET}/lib \
&& make install-strip && make install-strip
@@ -117,20 +182,29 @@ rm -rf pkgconfig .libs *.la libvipsCC*
# Create JSON file of version numbers # Create JSON file of version numbers
cd ${TARGET} cd ${TARGET}
echo "{\n\ echo "{\n\
\"cairo\": \"${VERSION_CAIRO}\",\n\
\"croco\": \"${VERSION_CROCO}\",\n\
\"exif\": \"${VERSION_EXIF}\",\n\ \"exif\": \"${VERSION_EXIF}\",\n\
\"ffi\": \"${VERSION_FFI}\",\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\ \"glib\": \"${VERSION_GLIB}\",\n\
\"gsf\": \"${VERSION_GSF}\",\n\ \"gsf\": \"${VERSION_GSF}\",\n\
\"harfbuzz\": \"${VERSION_HARFBUZZ}\",\n\
\"jpeg\": \"${VERSION_JPEG}\",\n\ \"jpeg\": \"${VERSION_JPEG}\",\n\
\"lcms\": \"${VERSION_LCMS2}\",\n\ \"lcms\": \"${VERSION_LCMS2}\",\n\
\"gm\": \"${VERSION_GM}\",\n\
\"orc\": \"${VERSION_ORC}\",\n\ \"orc\": \"${VERSION_ORC}\",\n\
\"pango\": \"${VERSION_PANGO}\",\n\
\"pixman\": \"${VERSION_PIXMAN}\",\n\
\"png\": \"${VERSION_PNG16}\",\n\ \"png\": \"${VERSION_PNG16}\",\n\
\"svg\": \"${VERSION_SVG}\",\n\
\"tiff\": \"${VERSION_TIFF}\",\n\ \"tiff\": \"${VERSION_TIFF}\",\n\
\"vips\": \"${VERSION_VIPS}\"\n\ \"vips\": \"${VERSION_VIPS}\",\n\
\"webp\": \"${VERSION_WEBP}\",\n\ \"webp\": \"${VERSION_WEBP}\",\n\
\"xml\": \"${VERSION_XML2}\",\n\ \"xml\": \"${VERSION_XML2}\",\n\
\"zlib\": \"${VERSION_ZLIB}\",\n\ \"zlib\": \"${VERSION_ZLIB}\"\n\
}" >lib/versions.json }" >lib/versions.json
# Create .tar.gz # Create .tar.gz

View File

@@ -1,5 +1,7 @@
#!/bin/sh #!/bin/sh
VERSION_VIPS=8.3.1
# Is docker available? # Is docker available?
if ! type docker >/dev/null; then if ! type docker >/dev/null; then
@@ -7,21 +9,21 @@ if ! type docker >/dev/null; then
exit 1 exit 1
fi fi
# TODO: docker v1.9.0 will allow build-time args - https://github.com/docker/docker/pull/15182 # TODO: docker v1.9.0 allows build-time args - https://github.com/docker/docker/pull/15182
# Windows # Windows
docker build -t vips-dev-win win docker build -t vips-dev-win win
WIN_CONTAINER_ID=$(docker run -d vips-dev-win) WIN_CONTAINER_ID=$(docker run -d vips-dev-win)
docker cp $WIN_CONTAINER_ID:/libvips-8.2.2-win.tar.gz . docker cp "${WIN_CONTAINER_ID}:/libvips-${VERSION_VIPS}-win.tar.gz" .
docker rm $WIN_CONTAINER_ID docker rm "${WIN_CONTAINER_ID}"
# Linux # Linux
docker build -t vips-dev-lin lin docker build -t vips-dev-lin lin
LIN_CONTAINER_ID=$(docker run -d vips-dev-lin) LIN_CONTAINER_ID=$(docker run -d vips-dev-lin)
docker cp $LIN_CONTAINER_ID:/libvips-8.2.2-lin.tar.gz . docker cp "${LIN_CONTAINER_ID}:/libvips-${VERSION_VIPS}-lin.tar.gz" .
docker rm $LIN_CONTAINER_ID docker rm "${LIN_CONTAINER_ID}"
# Checksums # Checksums

View File

@@ -10,28 +10,40 @@ ENV DEPS=/deps \
RUN mkdir ${DEPS} && mkdir ${TARGET} RUN mkdir ${DEPS} && mkdir ${TARGET}
# Common build paths and flags # Common build paths and flags
ENV PKG_CONFIG_PATH=${PKG_CONFIG_PATH}:${TARGET}/lib/pkgconfig \ ENV PKG_CONFIG_PATH="${PKG_CONFIG_PATH}:${TARGET}/lib/pkgconfig" \
PATH=${PATH}:${TARGET}/bin \ PATH="${PATH}:${TARGET}/bin" \
CPPFLAGS=-I${TARGET}/include \ CPPFLAGS="-I${TARGET}/include" \
LDFLAGS=-L${TARGET}/lib \ LDFLAGS="-L${TARGET}/lib" \
CFLAGS="-O3" \ CFLAGS="-O3" \
CXXFLAGS="-O3" CXXFLAGS="-O3"
# Dependency version numbers # Dependency version numbers
ENV VERSION_ZLIB=1.2.8 \ ENV VERSION_ZLIB=1.2.8 \
VERSION_FFI=3.2.1 \ VERSION_FFI=3.2.1 \
VERSION_GLIB=2.47.5 \ VERSION_GLIB=2.48.0 \
VERSION_XML2=2.9.3 \ VERSION_XML2=2.9.3 \
VERSION_GSF=1.14.34 \ VERSION_GSF=1.14.36 \
VERSION_EXIF=0.6.21 \ VERSION_EXIF=0.6.21 \
VERSION_LCMS2=2.7 \ VERSION_LCMS2=2.7 \
VERSION_GM=1.3.23 \ VERSION_JPEG=1.4.90 \
VERSION_JPEG=1.4.2 \
VERSION_PNG16=1.6.21 \ VERSION_PNG16=1.6.21 \
VERSION_WEBP=0.5.0 \ VERSION_WEBP=0.5.0 \
VERSION_TIFF=4.0.6 \ VERSION_TIFF=4.0.6 \
VERSION_ORC=0.4.24 \ VERSION_ORC=0.4.25 \
VERSION_VIPS=8.2.2 VERSION_GDKPIXBUF=2.34.0 \
VERSION_FREETYPE=2.6.3 \
VERSION_FONTCONFIG=2.11.95 \
VERSION_HARFBUZZ=1.2.6 \
VERSION_PIXMAN=0.34.0 \
VERSION_CAIRO=1.14.6 \
VERSION_PANGO=1.40.1 \
VERSION_CROCO=0.6.11 \
VERSION_SVG=2.40.15 \
VERSION_GIF=5.1.4 \
VERSION_VIPS=8.3.1
# Least out-of-sync Sourceforge mirror
ENV SOURCEFORGE_MIRROR=netix
RUN mkdir ${DEPS}/zlib RUN mkdir ${DEPS}/zlib
RUN curl -Ls http://zlib.net/zlib-${VERSION_ZLIB}.tar.xz | tar xJC ${DEPS}/zlib --strip-components=1 RUN curl -Ls http://zlib.net/zlib-${VERSION_ZLIB}.tar.xz | tar xJC ${DEPS}/zlib --strip-components=1
@@ -45,14 +57,18 @@ WORKDIR ${DEPS}/ffi
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-builddir && make install-strip RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-builddir && make install-strip
RUN mkdir ${DEPS}/glib RUN mkdir ${DEPS}/glib
RUN curl -Ls https://download.gnome.org/sources/glib/2.47/glib-${VERSION_GLIB}.tar.xz | tar xJC ${DEPS}/glib --strip-components=1 RUN curl -Ls https://download.gnome.org/sources/glib/2.48/glib-${VERSION_GLIB}.tar.xz | tar xJC ${DEPS}/glib --strip-components=1
WORKDIR ${DEPS}/glib WORKDIR ${DEPS}/glib
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --with-pcre=internal && make install-strip RUN CFLAGS="${CFLAGS} -Wl,--default-symver" CXXFLAGS="${CXXFLAGS} -Wl,--default-symver" \
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --with-pcre=internal && make install-strip
RUN mkdir ${DEPS}/xml2 RUN mkdir ${DEPS}/xml2
RUN curl -Ls http://xmlsoft.org/sources/libxml2-${VERSION_XML2}.tar.gz | tar xzC ${DEPS}/xml2 --strip-components=1 RUN curl -Ls http://xmlsoft.org/sources/libxml2-${VERSION_XML2}.tar.gz | tar xzC ${DEPS}/xml2 --strip-components=1
WORKDIR ${DEPS}/xml2 WORKDIR ${DEPS}/xml2
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --without-python --with-zlib=${TARGET} && make install-strip RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--without-python --without-debug --without-docbook --without-ftp --without-html --without-legacy \
--without-pattern --without-push --without-regexps --without-schemas --without-schematron --with-zlib=${TARGET} \
&& make install-strip
RUN mkdir ${DEPS}/gsf RUN mkdir ${DEPS}/gsf
RUN curl -Ls https://download.gnome.org/sources/libgsf/1.14/libgsf-${VERSION_GSF}.tar.xz | tar xJC ${DEPS}/gsf --strip-components=1 RUN curl -Ls https://download.gnome.org/sources/libgsf/1.14/libgsf-${VERSION_GSF}.tar.xz | tar xJC ${DEPS}/gsf --strip-components=1
@@ -60,27 +76,22 @@ WORKDIR ${DEPS}/gsf
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
RUN mkdir ${DEPS}/exif RUN mkdir ${DEPS}/exif
RUN curl -Ls http://heanet.dl.sourceforge.net/project/libexif/libexif/${VERSION_EXIF}/libexif-${VERSION_EXIF}.tar.bz2 | tar xjC ${DEPS}/exif --strip-components=1 RUN curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/libexif/libexif/${VERSION_EXIF}/libexif-${VERSION_EXIF}.tar.bz2 | tar xjC ${DEPS}/exif --strip-components=1
WORKDIR ${DEPS}/exif WORKDIR ${DEPS}/exif
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
RUN mkdir ${DEPS}/lcms2 RUN mkdir ${DEPS}/lcms2
RUN curl -Ls http://heanet.dl.sourceforge.net/project/lcms/lcms/${VERSION_LCMS2}/lcms2-${VERSION_LCMS2}.tar.gz | tar xzC ${DEPS}/lcms2 --strip-components=1 RUN curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/lcms/lcms/${VERSION_LCMS2}/lcms2-${VERSION_LCMS2}.tar.gz | tar xzC ${DEPS}/lcms2 --strip-components=1
WORKDIR ${DEPS}/lcms2 WORKDIR ${DEPS}/lcms2
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
RUN mkdir ${DEPS}/gm
RUN curl -Ls http://heanet.dl.sourceforge.net/project/graphicsmagick/graphicsmagick/${VERSION_GM}/GraphicsMagick-${VERSION_GM}.tar.xz | tar xJC ${DEPS}/gm --strip-components=1
WORKDIR ${DEPS}/gm
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --without-magick-plus-plus && make install-strip
RUN mkdir ${DEPS}/jpeg RUN mkdir ${DEPS}/jpeg
RUN curl -Ls http://heanet.dl.sourceforge.net/project/libjpeg-turbo/${VERSION_JPEG}/libjpeg-turbo-${VERSION_JPEG}.tar.gz | tar xzC ${DEPS}/jpeg --strip-components=1 RUN curl -Ls https://github.com/libjpeg-turbo/libjpeg-turbo/archive/${VERSION_JPEG}.tar.gz | tar xzC ${DEPS}/jpeg --strip-components=1
WORKDIR ${DEPS}/jpeg WORKDIR ${DEPS}/jpeg
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --with-jpeg8 --without-turbojpeg && make install-strip RUN autoreconf -fiv && ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --with-jpeg8 --without-turbojpeg && make install-strip
RUN mkdir ${DEPS}/png16 RUN mkdir ${DEPS}/png16
RUN curl -Ls http://heanet.dl.sourceforge.net/project/libpng/libpng16/${VERSION_PNG16}/libpng-${VERSION_PNG16}.tar.xz | tar xJC ${DEPS}/png16 --strip-components=1 RUN curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/libpng/libpng16/${VERSION_PNG16}/libpng-${VERSION_PNG16}.tar.xz | tar xJC ${DEPS}/png16 --strip-components=1
WORKDIR ${DEPS}/png16 WORKDIR ${DEPS}/png16
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
@@ -99,12 +110,74 @@ RUN mkdir ${DEPS}/orc
RUN curl -Ls http://gstreamer.freedesktop.org/data/src/orc/orc-${VERSION_ORC}.tar.xz | tar xJC ${DEPS}/orc --strip-components=1 RUN curl -Ls http://gstreamer.freedesktop.org/data/src/orc/orc-${VERSION_ORC}.tar.xz | tar xJC ${DEPS}/orc --strip-components=1
WORKDIR ${DEPS}/orc WORKDIR ${DEPS}/orc
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
RUN rm ${TARGET}/lib/liborc-test-*
RUN mkdir ${DEPS}/gdkpixbuf
RUN curl -Ls https://download.gnome.org/sources/gdk-pixbuf/2.34/gdk-pixbuf-${VERSION_GDKPIXBUF}.tar.xz | tar xJC ${DEPS}/gdkpixbuf --strip-components=1
WORKDIR ${DEPS}/gdkpixbuf
RUN LD_LIBRARY_PATH=${TARGET}/lib ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--disable-introspection --disable-modules --without-libpng --without-libjpeg --without-libtiff --without-gdiplus --with-included-loaders= \
&& make install-strip
RUN mkdir ${DEPS}/freetype
RUN curl -Ls http://download.savannah.gnu.org/releases/freetype/freetype-${VERSION_FREETYPE}.tar.gz | tar xzC ${DEPS}/freetype --strip-components=1
WORKDIR ${DEPS}/freetype
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static && make install
RUN mkdir ${DEPS}/fontconfig
RUN curl -Ls https://www.freedesktop.org/software/fontconfig/release/fontconfig-${VERSION_FONTCONFIG}.tar.bz2 | tar xjC ${DEPS}/fontconfig --strip-components=1
WORKDIR ${DEPS}/fontconfig
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --enable-libxml2 && make install-strip
RUN mkdir ${DEPS}/harfbuzz
RUN curl -Ls https://www.freedesktop.org/software/harfbuzz/release/harfbuzz-${VERSION_HARFBUZZ}.tar.bz2 | tar xjC ${DEPS}/harfbuzz --strip-components=1
WORKDIR ${DEPS}/harfbuzz
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
RUN mkdir ${DEPS}/pixman
RUN curl -Ls http://cairographics.org/releases/pixman-${VERSION_PIXMAN}.tar.gz | tar xzC ${DEPS}/pixman --strip-components=1
WORKDIR ${DEPS}/pixman
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-libpng && make install-strip
RUN mkdir ${DEPS}/cairo
RUN curl -Ls http://cairographics.org/releases/cairo-${VERSION_CAIRO}.tar.xz | tar xJC ${DEPS}/cairo --strip-components=1
WORKDIR ${DEPS}/cairo
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--disable-xlib --disable-xcb --disable-quartz --disable-win32 --disable-egl --disable-glx --disable-wgl \
--disable-script --disable-ps --disable-gobject --disable-trace --disable-interpreter \
&& make install-strip
RUN mkdir ${DEPS}/pango
RUN curl -Ls https://download.gnome.org/sources/pango/1.40/pango-${VERSION_PANGO}.tar.xz | tar xJC ${DEPS}/pango --strip-components=1
WORKDIR ${DEPS}/pango
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
RUN mkdir ${DEPS}/croco
RUN curl -Ls https://download.gnome.org/sources/libcroco/0.6/libcroco-${VERSION_CROCO}.tar.xz | tar xJC ${DEPS}/croco --strip-components=1
WORKDIR ${DEPS}/croco
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
RUN mkdir ${DEPS}/svg
RUN curl -Ls https://download.gnome.org/sources/librsvg/2.40/librsvg-${VERSION_SVG}.tar.xz | tar xJC ${DEPS}/svg --strip-components=1
WORKDIR ${DEPS}/svg
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--disable-introspection --disable-tools \
&& make install-strip
RUN mkdir ${DEPS}/gif
RUN curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/giflib/giflib-${VERSION_GIF}.tar.gz | tar xzC ${DEPS}/gif --strip-components=1
WORKDIR ${DEPS}/gif
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
RUN mkdir ${DEPS}/vips RUN mkdir ${DEPS}/vips
RUN curl -Ls http://www.vips.ecs.soton.ac.uk/supported/8.2/vips-${VERSION_VIPS}.tar.gz | tar xzC ${DEPS}/vips --strip-components=1 RUN curl -Ls http://www.vips.ecs.soton.ac.uk/supported/8.3/vips-${VERSION_VIPS}.tar.gz | tar xzC ${DEPS}/vips --strip-components=1
#RUN apt-get install -y swig gobject-introspection gettext glib2.0-dev
#RUN curl -Ls https://github.com/jcupitt/libvips/archive/master.tar.gz | tar xzC ${DEPS}/vips --strip-components=1
WORKDIR ${DEPS}/vips WORKDIR ${DEPS}/vips
#RUN ./bootstrap.sh
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \ RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--disable-debug --disable-introspection --without-python --without-fftw --with-magickpackage=GraphicsMagick \ --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-zip-includes=${TARGET}/include --with-zip-libraries=${TARGET}/lib \
--with-jpeg-includes=${TARGET}/include --with-jpeg-libraries=${TARGET}/lib \ --with-jpeg-includes=${TARGET}/include --with-jpeg-libraries=${TARGET}/lib \
&& make install-strip && make install-strip
@@ -118,20 +191,29 @@ RUN rm -rf pkgconfig .libs *.la libvipsCC*
# Create JSON file of version numbers # Create JSON file of version numbers
WORKDIR ${TARGET} WORKDIR ${TARGET}
RUN echo "{\n\ RUN echo "{\n\
\"cairo\": \"${VERSION_CAIRO}\",\n\
\"croco\": \"${VERSION_CROCO}\",\n\
\"exif\": \"${VERSION_EXIF}\",\n\ \"exif\": \"${VERSION_EXIF}\",\n\
\"ffi\": \"${VERSION_FFI}\",\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\ \"glib\": \"${VERSION_GLIB}\",\n\
\"gsf\": \"${VERSION_GSF}\",\n\ \"gsf\": \"${VERSION_GSF}\",\n\
\"harfbuzz\": \"${VERSION_HARFBUZZ}\",\n\
\"jpeg\": \"${VERSION_JPEG}\",\n\ \"jpeg\": \"${VERSION_JPEG}\",\n\
\"lcms\": \"${VERSION_LCMS2}\",\n\ \"lcms\": \"${VERSION_LCMS2}\",\n\
\"gm\": \"${VERSION_GM}\",\n\
\"orc\": \"${VERSION_ORC}\",\n\ \"orc\": \"${VERSION_ORC}\",\n\
\"pango\": \"${VERSION_PANGO}\",\n\
\"pixman\": \"${VERSION_PIXMAN}\",\n\
\"png\": \"${VERSION_PNG16}\",\n\ \"png\": \"${VERSION_PNG16}\",\n\
\"svg\": \"${VERSION_SVG}\",\n\
\"tiff\": \"${VERSION_TIFF}\",\n\ \"tiff\": \"${VERSION_TIFF}\",\n\
\"vips\": \"${VERSION_VIPS}\"\n\ \"vips\": \"${VERSION_VIPS}\",\n\
\"webp\": \"${VERSION_WEBP}\",\n\ \"webp\": \"${VERSION_WEBP}\",\n\
\"xml\": \"${VERSION_XML2}\",\n\ \"xml\": \"${VERSION_XML2}\",\n\
\"zlib\": \"${VERSION_ZLIB}\",\n\ \"zlib\": \"${VERSION_ZLIB}\"\n\
}" >lib/versions.json }" >lib/versions.json
# Create .tar.gz # Create .tar.gz

View File

@@ -6,34 +6,33 @@ if ! type docker >/dev/null; then
exit 1 exit 1
fi fi
version_node=4.4.2
test="npm run clean; npm install --unsafe-perm; npm test" test="npm run clean; npm install --unsafe-perm; npm test"
# Debian 7, 8 # Debian 7, 8
# Ubuntu 12.04, 14.04 # Ubuntu 14.04
for dist in wheezy jessie precise trusty; do for dist in wheezy jessie trusty; do
echo "Testing $dist..." echo "Testing $dist..."
if docker run -i -t --rm -v $PWD:/v -e "NODE_ENV=development" nodesource/$dist:0.12 >packaging/$dist.log 2>&1 sh -c "cd /v; ./packaging/test/debian.sh; $test"; 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" then echo "$dist OK"
else echo "$dist fail" && cat packaging/$dist.log else echo "$dist fail" && cat packaging/$dist.log
fi fi
done done
# Centos 6
echo "Testing centos6..."
if docker run -i -t --rm -v $PWD:/v -e "NODE_ENV=development" nodesource/centos6:0.12 >packaging/centos6.log 2>&1 sh -c "cd /v; source ./packaging/test/centos6.sh; ./preinstall.sh; $test";
then echo "centos6 OK"
else echo "centos6 fail" && cat packaging/centos6.log
fi
# Centos 7 # Centos 7
# Fedora 20, 21 echo "Testing centos7..."
for dist in centos7 fedora20 fedora21; do 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";
echo "Testing $dist..." then echo "$dist OK"
if docker run -i -t --rm -v $PWD:/v -e "NODE_ENV=development" nodesource/$dist:0.12 >packaging/$dist.log 2>&1 sh -c "cd /v; $test"; else echo "$dist fail" && cat packaging/$dist.log
fi
# Fedora 22
echo "Testing fedora22..."
if docker run -i -t --rm -v $PWD:/v -e "NODE_ENV=development" nodesource/fedora22:$version_node >packaging/$dist.log 2>&1 sh -c "cd /v; $test";
then echo "$dist OK" then echo "$dist OK"
else echo "$dist fail" && cat packaging/$dist.log else echo "$dist fail" && cat packaging/$dist.log
fi fi
done
# openSUSE 13.2 # openSUSE 13.2
echo "Testing opensuse..." echo "Testing opensuse..."

View File

@@ -1,8 +0,0 @@
#!/bin/sh
# Install C++11 compatible version of g++ on Centos 6
curl -o /etc/yum.repos.d/devtools-1.1.repo http://people.centos.org/tru/devtools-1.1/devtools-1.1.repo
yum install -y devtoolset-1.1
export CC=/opt/centos/devtoolset-1.1/root/usr/bin/gcc
export CPP=/opt/centos/devtoolset-1.1/root/usr/bin/cpp
export CXX=/opt/centos/devtoolset-1.1/root/usr/bin/c++

View File

@@ -1,18 +1,20 @@
FROM ubuntu:precise FROM debian:wheezy
MAINTAINER Lovell Fuller <npm@lovell.info> MAINTAINER Lovell Fuller <npm@lovell.info>
RUN apt-get update && apt-get install -y curl zip RUN apt-get update && apt-get install -y curl zip
ENV VERSION_VIPS=8.3.1
# Fetch and unzip # Fetch and unzip
RUN mkdir /vips RUN mkdir /vips
WORKDIR /vips WORKDIR /vips
RUN curl -O http://www.vips.ecs.soton.ac.uk/supported/8.2/win32/vips-dev-w64-8.2.2.zip RUN curl -L -O https://github.com/lovell/build-win64/releases/download/v${VERSION_VIPS}/vips-dev-w64-web-${VERSION_VIPS}.zip
RUN unzip vips-dev-w64-8.2.2.zip RUN unzip vips-dev-w64-web-${VERSION_VIPS}.zip
# Clean and zip # Clean and zip
WORKDIR /vips/vips-dev-8.2 WORKDIR /vips/vips-dev-8.3
RUN rm bin/libvipsCC-42.dll bin/libvips-cpp-42.dll bin/libgsf-win32-1-114.dll bin/libstdc++-6.dll RUN rm bin/libvipsCC-42.dll bin/libvips-cpp-42.dll bin/libgsf-win32-1-114.dll
RUN cp bin/*.dll lib/ RUN cp bin/*.dll lib/
RUN cp -r lib64/* lib/ RUN cp -r lib64/* lib/
RUN GZIP=-9 tar czf /libvips-8.2.2-win.tar.gz include lib/glib-2.0 lib/libvips.lib lib/libglib-2.0.lib lib/libgobject-2.0.lib lib/*.dll RUN GZIP=-9 tar czf /libvips-${VERSION_VIPS}-win.tar.gz include lib/glib-2.0 lib/libvips.lib lib/libglib-2.0.lib lib/libgobject-2.0.lib lib/*.dll

View File

@@ -1,28 +1,19 @@
#!/bin/sh #!/bin/sh
# This script is no longer required on most # Use of this script is deprecated
# 64-bit Linux systems when using sharp v0.12.0+
# See http://sharp.dimens.io/page/install#linux echo
echo "WARNING: This script is no longer required on most 64-bit Linux systems when using sharp v0.12.0+"
echo
echo "See http://sharp.dimens.io/page/install#linux"
echo
echo "If you really, really need this script, it will attempt"
echo "to globally install libvips if not already available."
echo
# If you really need this script, it will attempt to vips_version_minimum=8.3.1
# globally install libvips if not already available. vips_version_latest_major_minor=8.3
vips_version_latest_patch=1
# Supports:
# * Debian Linux
# * Debian 7, 8
# * Ubuntu 12.04, 14.04, 14.10, 15.04, 15.10
# * Mint 13, 17
# * Elementary 0.3
# * Red Hat Linux
# * RHEL/Centos/Scientific 6, 7
# * Fedora 21, 22, 23
# * Amazon Linux 2015.03, 2015.09
# * OpenSuse 13
vips_version_minimum=8.2.2
vips_version_latest_major_minor=8.2
vips_version_latest_patch=2
openslide_version_minimum=3.4.0 openslide_version_minimum=3.4.0
openslide_version_latest_major_minor=3.4 openslide_version_latest_major_minor=3.4
@@ -33,7 +24,7 @@ install_libvips_from_source() {
curl -O http://www.vips.ecs.soton.ac.uk/supported/$vips_version_latest_major_minor/vips-$vips_version_latest_major_minor.$vips_version_latest_patch.tar.gz curl -O http://www.vips.ecs.soton.ac.uk/supported/$vips_version_latest_major_minor/vips-$vips_version_latest_major_minor.$vips_version_latest_patch.tar.gz
tar zvxf vips-$vips_version_latest_major_minor.$vips_version_latest_patch.tar.gz tar zvxf vips-$vips_version_latest_major_minor.$vips_version_latest_patch.tar.gz
cd vips-$vips_version_latest_major_minor.$vips_version_latest_patch cd vips-$vips_version_latest_major_minor.$vips_version_latest_patch
./configure --disable-debug --disable-docs --disable-static --disable-introspection --disable-dependency-tracking --enable-cxx=yes --without-python --without-orc --without-fftw $1 CXXFLAGS="-D_GLIBCXX_USE_CXX11_ABI=0" ./configure --disable-debug --disable-docs --disable-static --disable-introspection --disable-dependency-tracking --enable-cxx=yes --without-python --without-orc --without-fftw $1
make make
make install make install
cd .. cd ..
@@ -139,7 +130,7 @@ if [ $enable_openslide -eq 1 ] && [ -z $vips_with_openslide ] && [ $openslide_ex
DISTRO=$(lsb_release -c -s) DISTRO=$(lsb_release -c -s)
echo "Detected Debian Linux '$DISTRO'" echo "Detected Debian Linux '$DISTRO'"
case "$DISTRO" in case "$DISTRO" in
jessie|vivid|wily) jessie|vivid|wily|xenial)
# Debian 8, Ubuntu 15 # Debian 8, Ubuntu 15
echo "Installing libopenslide via apt-get" echo "Installing libopenslide via apt-get"
apt-get install -y libopenslide-dev apt-get install -y libopenslide-dev

View File

@@ -8,16 +8,16 @@
// Verify platform and compiler compatibility // Verify platform and compiler compatibility
#if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 2)) #if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 2))
#error libvips version 8.2.0+ required - see http://sharp.dimens.io/page/install #error libvips version 8.2.0+ required - see sharp.dimens.io/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 http://sharp.dimens.io/page/install#prerequisites #error GCC version 4.6+ is required for C++11 features - see sharp.dimens.io/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 http://sharp.dimens.io/page/install#prerequisites #error clang version 3.0+ is required for C++11 features - see sharp.dimens.io/page/install#prerequisites
#endif #endif
#endif #endif
@@ -52,6 +52,9 @@ namespace sharp {
bool IsDz(std::string const &str) { bool IsDz(std::string const &str) {
return EndsWith(str, ".dzi") || EndsWith(str, ".DZI"); return EndsWith(str, ".dzi") || EndsWith(str, ".DZI");
} }
bool IsDzZip(std::string const &str) {
return EndsWith(str, ".zip") || EndsWith(str, ".ZIP") || EndsWith(str, ".szi") || EndsWith(str, ".SZI");
}
/* /*
Provide a string identifier for the given image type. Provide a string identifier for the given image type.
@@ -63,6 +66,9 @@ namespace sharp {
case ImageType::PNG: id = "png"; break; case ImageType::PNG: id = "png"; break;
case ImageType::WEBP: id = "webp"; break; case ImageType::WEBP: id = "webp"; break;
case ImageType::TIFF: id = "tiff"; break; case ImageType::TIFF: id = "tiff"; break;
case ImageType::GIF: id = "gif"; break;
case ImageType::SVG: id = "svg"; break;
case ImageType::PDF: id = "pdf"; break;
case ImageType::MAGICK: id = "magick"; break; case ImageType::MAGICK: id = "magick"; break;
case ImageType::OPENSLIDE: id = "openslide"; break; case ImageType::OPENSLIDE: id = "openslide"; break;
case ImageType::PPM: id = "ppm"; break; case ImageType::PPM: id = "ppm"; break;
@@ -89,6 +95,12 @@ namespace sharp {
imageType = ImageType::WEBP; imageType = ImageType::WEBP;
} else if (EndsWith(loader, "TiffBuffer")) { } else if (EndsWith(loader, "TiffBuffer")) {
imageType = ImageType::TIFF; imageType = ImageType::TIFF;
} else if (EndsWith(loader, "GifBuffer")) {
imageType = ImageType::GIF;
} else if (EndsWith(loader, "SvgBuffer")) {
imageType = ImageType::SVG;
} else if (EndsWith(loader, "PdfBuffer")) {
imageType = ImageType::PDF;
} else if (EndsWith(loader, "MagickBuffer")) { } else if (EndsWith(loader, "MagickBuffer")) {
imageType = ImageType::MAGICK; imageType = ImageType::MAGICK;
} }
@@ -114,6 +126,12 @@ namespace sharp {
imageType = ImageType::OPENSLIDE; imageType = ImageType::OPENSLIDE;
} else if (EndsWith(loader, "TiffFile")) { } else if (EndsWith(loader, "TiffFile")) {
imageType = ImageType::TIFF; imageType = ImageType::TIFF;
} else if (EndsWith(loader, "GifFile")) {
imageType = ImageType::GIF;
} else if (EndsWith(loader, "SvgFile")) {
imageType = ImageType::SVG;
} else if (EndsWith(loader, "PdfFile")) {
imageType = ImageType::PDF;
} else if (EndsWith(loader, "Ppm")) { } else if (EndsWith(loader, "Ppm")) {
imageType = ImageType::PPM; imageType = ImageType::PPM;
} else if (EndsWith(loader, "Fits")) { } else if (EndsWith(loader, "Fits")) {
@@ -176,6 +194,30 @@ namespace sharp {
SetExifOrientation(image, 0); SetExifOrientation(image, 0);
} }
/*
Does this image have a non-default density?
*/
bool HasDensity(VImage image) {
return image.xres() > 1.0;
}
/*
Get pixels/mm resolution as pixels/inch density.
*/
int GetDensity(VImage image) {
return static_cast<int>(round(image.xres() * 25.4));
}
/*
Set pixels/mm resolution based on a pixels/inch density.
*/
void SetDensity(VImage image, const int density) {
const double pixelsPerMm = static_cast<double>(density) / 25.4;
image.set("Xres", pixelsPerMm);
image.set("Yres", pixelsPerMm);
image.set(VIPS_META_RESOLUTION_UNIT, "in");
}
/* /*
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
*/ */
@@ -185,4 +227,54 @@ namespace sharp {
} }
} }
/*
Calculate the (left, top) coordinates of the output image
within the input image, applying the given gravity.
*/
std::tuple<int, int> CalculateCrop(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 = (inWidth - outWidth + 1) / 2;
break;
case 2:
// East
left = inWidth - outWidth;
top = (inHeight - outHeight + 1) / 2;
break;
case 3:
// South
left = (inWidth - outWidth + 1) / 2;
top = inHeight - outHeight;
break;
case 4:
// West
top = (inHeight - outHeight + 1) / 2;
break;
case 5:
// Northeast
left = inWidth - outWidth;
break;
case 6:
// Southeast
left = inWidth - outWidth;
top = inHeight - outHeight;
case 7:
// Southwest
top = inHeight - outHeight;
case 8:
// Northwest
break;
default:
// Centre
left = (inWidth - outWidth + 1) / 2;
top = (inHeight - outHeight + 1) / 2;
}
return std::make_tuple(left, top);
}
} // namespace sharp } // namespace sharp

View File

@@ -2,6 +2,8 @@
#define SRC_COMMON_H_ #define SRC_COMMON_H_
#include <string> #include <string>
#include <tuple>
#include <vips/vips8> #include <vips/vips8>
using vips::VImage; using vips::VImage;
@@ -9,16 +11,19 @@ using vips::VImage;
namespace sharp { namespace sharp {
enum class ImageType { enum class ImageType {
UNKNOWN,
JPEG, JPEG,
PNG, PNG,
WEBP, WEBP,
TIFF, TIFF,
GIF,
SVG,
PDF,
MAGICK, MAGICK,
OPENSLIDE, OPENSLIDE,
PPM, PPM,
FITS, FITS,
RAW RAW,
UNKNOWN
}; };
// How many tasks are in the queue? // How many tasks are in the queue?
@@ -33,6 +38,7 @@ namespace sharp {
bool IsWebp(std::string const &str); bool IsWebp(std::string const &str);
bool IsTiff(std::string const &str); bool IsTiff(std::string const &str);
bool IsDz(std::string const &str); bool IsDz(std::string const &str);
bool IsDzZip(std::string const &str);
/* /*
Provide a string identifier for the given image type. Provide a string identifier for the given image type.
@@ -75,11 +81,33 @@ namespace sharp {
*/ */
void RemoveExifOrientation(VImage image); void RemoveExifOrientation(VImage image);
/*
Does this image have a non-default density?
*/
bool HasDensity(VImage image);
/*
Get pixels/mm resolution as pixels/inch density.
*/
int GetDensity(VImage image);
/*
Set pixels/mm resolution based on a pixels/inch density.
*/
void SetDensity(VImage image, const int density);
/* /*
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);
/*
Calculate the (left, top) coordinates of the output image
within the input image, applying the given gravity.
*/
std::tuple<int, int> CalculateCrop(int const inWidth, int const inHeight,
int const outWidth, int const outHeight, int const gravity);
} // namespace sharp } // namespace sharp
#endif // SRC_COMMON_H_ #endif // SRC_COMMON_H_

View File

@@ -711,4 +711,499 @@ VImage::maxpos( VOption *options )
return( std::complex<double>( x, y ) ); return( std::complex<double>( x, y ) );
} }
// Operator overloads
VImage
VImage::operator[]( int index )
{
return( this->extract_band( index ) );
}
std::vector<double>
VImage::operator()( int x, int y )
{
return( this->getpoint( x, y ) );
}
VImage
operator+( VImage a, VImage b )
{
return( a.add( b ) );
}
VImage
operator+( double a, VImage b )
{
return( b.linear( 1.0, a ) );
}
VImage
operator+( VImage a, double b )
{
return( a.linear( 1.0, b ) );
}
VImage
operator+( std::vector<double> a, VImage b )
{
return( b.linear( 1.0, a ) );
}
VImage
operator+( VImage a, std::vector<double> b )
{
return( a.linear( 1.0, b ) );
}
VImage
operator-( VImage a, VImage b )
{
return( a.subtract( b ) );
}
VImage operator-( double a, VImage b )
{
return( b.linear( -1.0, a ) );
}
VImage
operator-( VImage a, double b )
{
return( a.linear( 1.0, -b ) );
}
VImage
operator-( std::vector<double> a, VImage b )
{
return( b.linear( -1.0, a ) );
}
VImage
operator-( VImage a, std::vector<double> b )
{
return( a.linear( 1.0, vips::negate( b ) ) );
}
VImage
operator-( VImage a )
{
return( a * -1 );
}
VImage
operator*( VImage a, VImage b )
{
return( a.multiply( b ) );
}
VImage
operator*( double a, VImage b )
{
return( b.linear( a, 0.0 ) );
}
VImage
operator*( VImage a, double b )
{
return( a.linear( b, 0.0 ) );
}
VImage
operator*( std::vector<double> a, VImage b )
{
return( b.linear( a, 0.0 ) );
}
VImage
operator*( VImage a, std::vector<double> b )
{
return( a.linear( b, 0.0 ) );
}
VImage
operator/( VImage a, VImage b )
{
return( a.divide( b ) );
}
VImage
operator/( double a, VImage b )
{
return( b.pow( -1.0 ).linear( a, 0.0 ) );
}
VImage
operator/( VImage a, double b )
{
return( a.linear( 1.0 / b, 0.0 ) );
}
VImage
operator/( std::vector<double> a, VImage b )
{
return( b.pow( -1.0 ).linear( a, 0.0 ) );
}
VImage
operator/( VImage a, std::vector<double> b )
{
return( a.linear( vips::invert( b ), 0.0 ) );
}
VImage
operator%( VImage a, VImage b )
{
return( a.remainder( b ) );
}
VImage
operator%( VImage a, double b )
{
return( a.remainder_const( to_vector( b ) ) );
}
VImage
operator%( VImage a, std::vector<double> b )
{
return( a.remainder_const( b ) );
}
VImage
operator<( VImage a, VImage b )
{
return( a.relational( b, VIPS_OPERATION_RELATIONAL_LESS ) );
}
VImage
operator<( double a, VImage b )
{
return( b.relational_const( to_vector( a ),
VIPS_OPERATION_RELATIONAL_MORE ) );
}
VImage
operator<( VImage a, double b )
{
return( a.relational_const( to_vector( b ),
VIPS_OPERATION_RELATIONAL_LESS ) );
}
VImage
operator<( std::vector<double> a, VImage b )
{
return( b.relational_const( a,
VIPS_OPERATION_RELATIONAL_MORE ) );
}
VImage
operator<( VImage a, std::vector<double> b )
{
return( a.relational_const( b,
VIPS_OPERATION_RELATIONAL_LESS ) );
}
VImage
operator<=( VImage a, VImage b )
{
return( a.relational( b, VIPS_OPERATION_RELATIONAL_LESSEQ ) );
}
VImage
operator<=( double a, VImage b )
{
return( b.relational_const( to_vector( a ),
VIPS_OPERATION_RELATIONAL_MOREEQ ) );
}
VImage
operator<=( VImage a, double b )
{
return( a.relational_const( to_vector( b ),
VIPS_OPERATION_RELATIONAL_LESSEQ ) );
}
VImage
operator<=( std::vector<double> a, VImage b )
{
return( b.relational_const( a,
VIPS_OPERATION_RELATIONAL_MOREEQ ) );
}
VImage
operator<=( VImage a, std::vector<double> b )
{
return( a.relational_const( b,
VIPS_OPERATION_RELATIONAL_LESSEQ ) );
}
VImage
operator>( VImage a, VImage b )
{
return( a.relational( b, VIPS_OPERATION_RELATIONAL_MORE ) );
}
VImage
operator>( double a, VImage b )
{
return( b.relational_const( to_vector( a ),
VIPS_OPERATION_RELATIONAL_LESS ) );
}
VImage
operator>( VImage a, double b )
{
return( a.relational_const( to_vector( b ),
VIPS_OPERATION_RELATIONAL_MORE ) );
}
VImage
operator>( std::vector<double> a, VImage b )
{
return( b.relational_const( a,
VIPS_OPERATION_RELATIONAL_LESS ) );
}
VImage
operator>( VImage a, std::vector<double> b )
{
return( a.relational_const( b,
VIPS_OPERATION_RELATIONAL_MORE ) );
}
VImage
operator>=( VImage a, VImage b )
{
return( a.relational( b, VIPS_OPERATION_RELATIONAL_MOREEQ ) );
}
VImage
operator>=( double a, VImage b )
{
return( b.relational_const( to_vector( a ),
VIPS_OPERATION_RELATIONAL_LESSEQ ) );
}
VImage
operator>=( VImage a, double b )
{
return( a.relational_const( to_vector( b ),
VIPS_OPERATION_RELATIONAL_MOREEQ ) );
}
VImage
operator>=( std::vector<double> a, VImage b )
{
return( b.relational_const( a,
VIPS_OPERATION_RELATIONAL_LESSEQ ) );
}
VImage
operator>=( VImage a, std::vector<double> b )
{
return( a.relational_const( b,
VIPS_OPERATION_RELATIONAL_MOREEQ ) );
}
VImage
operator==( VImage a, VImage b )
{
return( a.relational( b, VIPS_OPERATION_RELATIONAL_EQUAL ) );
}
VImage
operator==( double a, VImage b )
{
return( b.relational_const( to_vector( a ),
VIPS_OPERATION_RELATIONAL_EQUAL ) );
}
VImage
operator==( VImage a, double b )
{
return( a.relational_const( to_vector( b ),
VIPS_OPERATION_RELATIONAL_EQUAL ) );
}
VImage
operator==( std::vector<double> a, VImage b )
{
return( b.relational_const( a,
VIPS_OPERATION_RELATIONAL_EQUAL ) );
}
VImage
operator==( VImage a, std::vector<double> b )
{
return( a.relational_const( b,
VIPS_OPERATION_RELATIONAL_EQUAL ) );
}
VImage
operator!=( VImage a, VImage b )
{
return( a.relational( b, VIPS_OPERATION_RELATIONAL_NOTEQ ) );
}
VImage
operator!=( double a, VImage b )
{
return( b.relational_const( to_vector( a ),
VIPS_OPERATION_RELATIONAL_NOTEQ ) );
}
VImage
operator!=( VImage a, double b )
{
return( a.relational_const( to_vector( b ),
VIPS_OPERATION_RELATIONAL_NOTEQ ) );
}
VImage
operator!=( std::vector<double> a, VImage b )
{
return( b.relational_const( a,
VIPS_OPERATION_RELATIONAL_NOTEQ ) );
}
VImage
operator!=( VImage a, std::vector<double> b )
{
return( a.relational_const( b,
VIPS_OPERATION_RELATIONAL_NOTEQ ) );
}
VImage
operator&( VImage a, VImage b )
{
return( a.boolean( b, VIPS_OPERATION_BOOLEAN_AND ) );
}
VImage
operator&( double a, VImage b )
{
return( b.boolean_const( to_vector( a ),
VIPS_OPERATION_BOOLEAN_AND ) );
}
VImage
operator&( VImage a, double b )
{
return( a.boolean_const( to_vector( b ),
VIPS_OPERATION_BOOLEAN_AND ) );
}
VImage
operator&( std::vector<double> a, VImage b )
{
return( b.boolean_const( a, VIPS_OPERATION_BOOLEAN_AND ) );
}
VImage
operator&( VImage a, std::vector<double> b )
{
return( a.boolean_const( b, VIPS_OPERATION_BOOLEAN_AND ) );
}
VImage
operator|( VImage a, VImage b )
{
return( a.boolean( b, VIPS_OPERATION_BOOLEAN_OR ) );
}
VImage
operator|( double a, VImage b )
{
return( b.boolean_const( to_vector( a ),
VIPS_OPERATION_BOOLEAN_OR ) );
}
VImage
operator|( VImage a, double b )
{
return( a.boolean_const( to_vector( b ),
VIPS_OPERATION_BOOLEAN_OR ) );
}
VImage
operator|( std::vector<double> a, VImage b )
{
return( b.boolean_const( a, VIPS_OPERATION_BOOLEAN_OR ) );
}
VImage
operator|( VImage a, std::vector<double> b )
{
return( a.boolean_const( b, VIPS_OPERATION_BOOLEAN_OR ) );
}
VImage
operator^( VImage a, VImage b )
{
return( a.boolean( b, VIPS_OPERATION_BOOLEAN_EOR ) );
}
VImage
operator^( double a, VImage b )
{
return( b.boolean_const( to_vector( a ),
VIPS_OPERATION_BOOLEAN_EOR ) );
}
VImage
operator^( VImage a, double b )
{
return( a.boolean_const( to_vector( b ),
VIPS_OPERATION_BOOLEAN_EOR ) );
}
VImage
operator^( std::vector<double> a, VImage b )
{
return( b.boolean_const( a, VIPS_OPERATION_BOOLEAN_EOR ) );
}
VImage
operator^( VImage a, std::vector<double> b )
{
return( a.boolean_const( b, VIPS_OPERATION_BOOLEAN_EOR ) );
}
VImage
operator<<( VImage a, VImage b )
{
return( a.boolean( b, VIPS_OPERATION_BOOLEAN_LSHIFT ) );
}
VImage
operator<<( VImage a, double b )
{
return( a.boolean_const( to_vector( b ),
VIPS_OPERATION_BOOLEAN_LSHIFT ) );
}
VImage
operator<<( VImage a, std::vector<double> b )
{
return( a.boolean_const( b, VIPS_OPERATION_BOOLEAN_LSHIFT ) );
}
VImage
operator>>( VImage a, VImage b )
{
return( a.boolean( b, VIPS_OPERATION_BOOLEAN_RSHIFT ) );
}
VImage
operator>>( VImage a, double b )
{
return( a.boolean_const( to_vector( b ),
VIPS_OPERATION_BOOLEAN_RSHIFT ) );
}
VImage
operator>>( VImage a, std::vector<double> b )
{
return( a.boolean_const( b, VIPS_OPERATION_BOOLEAN_RSHIFT ) );
}
VIPS_NAMESPACE_END VIPS_NAMESPACE_END

View File

@@ -1,5 +1,5 @@
// bodies for vips operations // bodies for vips operations
// Sat Jan 9 15:05:58 GMT 2016 // Fri Feb 12 20:03:53 GMT 2016
// this file is generated automatically, do not edit! // this file is generated automatically, do not edit!
void VImage::system( char * cmd_format , VOption *options ) void VImage::system( char * cmd_format , VOption *options )
@@ -1408,6 +1408,78 @@ VImage VImage::vipsload( char * filename , VOption *options )
return( out ); return( out );
} }
VImage VImage::pdfload( char * filename , VOption *options )
{
VImage out;
call( "pdfload" ,
(options ? options : VImage::option()) ->
set( "filename", filename ) ->
set( "out", &out ) );
return( out );
}
VImage VImage::pdfload_buffer( VipsBlob * buffer , VOption *options )
{
VImage out;
call( "pdfload_buffer" ,
(options ? options : VImage::option()) ->
set( "buffer", buffer ) ->
set( "out", &out ) );
return( out );
}
VImage VImage::svgload( char * filename , VOption *options )
{
VImage out;
call( "svgload" ,
(options ? options : VImage::option()) ->
set( "filename", filename ) ->
set( "out", &out ) );
return( out );
}
VImage VImage::svgload_buffer( VipsBlob * buffer , VOption *options )
{
VImage out;
call( "svgload_buffer" ,
(options ? options : VImage::option()) ->
set( "buffer", buffer ) ->
set( "out", &out ) );
return( out );
}
VImage VImage::gifload( char * filename , VOption *options )
{
VImage out;
call( "gifload" ,
(options ? options : VImage::option()) ->
set( "filename", filename ) ->
set( "out", &out ) );
return( out );
}
VImage VImage::gifload_buffer( VipsBlob * buffer , VOption *options )
{
VImage out;
call( "gifload_buffer" ,
(options ? options : VImage::option()) ->
set( "buffer", buffer ) ->
set( "out", &out ) );
return( out );
}
VImage VImage::pngload( char * filename , VOption *options ) VImage VImage::pngload( char * filename , VOption *options )
{ {
VImage out; VImage out;
@@ -1783,11 +1855,37 @@ VImage VImage::shrinkv( int yshrink , VOption *options )
return( out ); return( out );
} }
VImage VImage::shrink2( double xshrink , double yshrink , VOption *options ) VImage VImage::reduceh( double xshrink , VOption *options )
{ {
VImage out; VImage out;
call( "shrink2" , call( "reduceh" ,
(options ? options : VImage::option()) ->
set( "in", *this ) ->
set( "out", &out ) ->
set( "xshrink", xshrink ) );
return( out );
}
VImage VImage::reducev( double yshrink , VOption *options )
{
VImage out;
call( "reducev" ,
(options ? options : VImage::option()) ->
set( "in", *this ) ->
set( "out", &out ) ->
set( "yshrink", yshrink ) );
return( out );
}
VImage VImage::reduce( double xshrink , double yshrink , VOption *options )
{
VImage out;
call( "reduce" ,
(options ? options : VImage::option()) -> (options ? options : VImage::option()) ->
set( "in", *this ) -> set( "in", *this ) ->
set( "out", &out ) -> set( "out", &out ) ->

View File

@@ -38,6 +38,8 @@ using sharp::DetermineImageType;
using sharp::HasProfile; using sharp::HasProfile;
using sharp::HasAlpha; using sharp::HasAlpha;
using sharp::ExifOrientation; using sharp::ExifOrientation;
using sharp::HasDensity;
using sharp::GetDensity;
using sharp::FreeCallback; using sharp::FreeCallback;
using sharp::counterQueue; using sharp::counterQueue;
@@ -52,6 +54,7 @@ struct MetadataBaton {
int height; int height;
std::string space; std::string space;
int channels; int channels;
int density;
bool hasProfile; bool hasProfile;
bool hasAlpha; bool hasAlpha;
int orientation; int orientation;
@@ -63,13 +66,13 @@ struct MetadataBaton {
MetadataBaton(): MetadataBaton():
bufferInLength(0), bufferInLength(0),
density(0),
orientation(0), orientation(0),
exifLength(0), exifLength(0),
iccLength(0) {} iccLength(0) {}
}; };
class MetadataWorker : public AsyncWorker { class MetadataWorker : public AsyncWorker {
public: public:
MetadataWorker(Callback *callback, MetadataBaton *baton, const Local<Object> &bufferIn) : MetadataWorker(Callback *callback, MetadataBaton *baton, const Local<Object> &bufferIn) :
AsyncWorker(callback), baton(baton) { AsyncWorker(callback), baton(baton) {
@@ -120,6 +123,9 @@ class MetadataWorker : public 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();
if (HasDensity(image)) {
baton->density = GetDensity(image);
}
baton->hasProfile = HasProfile(image); baton->hasProfile = HasProfile(image);
// Derived attributes // Derived attributes
baton->hasAlpha = HasAlpha(image); baton->hasAlpha = HasAlpha(image);
@@ -161,6 +167,9 @@ class MetadataWorker : public AsyncWorker {
Set(info, New("height").ToLocalChecked(), New<Number>(baton->height)); Set(info, New("height").ToLocalChecked(), New<Number>(baton->height));
Set(info, New("space").ToLocalChecked(), New<String>(baton->space).ToLocalChecked()); Set(info, New("space").ToLocalChecked(), New<String>(baton->space).ToLocalChecked());
Set(info, New("channels").ToLocalChecked(), New<Number>(baton->channels)); Set(info, New("channels").ToLocalChecked(), New<Number>(baton->channels));
if (baton->density > 0) {
Set(info, New("density").ToLocalChecked(), New<Number>(baton->density));
}
Set(info, New("hasProfile").ToLocalChecked(), New<Boolean>(baton->hasProfile)); Set(info, New("hasProfile").ToLocalChecked(), New<Boolean>(baton->hasProfile));
Set(info, New("hasAlpha").ToLocalChecked(), New<Boolean>(baton->hasAlpha)); Set(info, New("hasAlpha").ToLocalChecked(), New<Boolean>(baton->hasAlpha));
if (baton->orientation > 0) { if (baton->orientation > 0) {

View File

@@ -1,37 +1,54 @@
#include <algorithm>
#include <tuple>
#include <vips/vips8> #include <vips/vips8>
#include "common.h" #include "common.h"
#include "operations.h" #include "operations.h"
using vips::VImage; using vips::VImage;
using vips::VError;
namespace sharp { namespace sharp {
/* /*
Alpha composite src over dst Alpha composite src over dst with given gravity.
Assumes alpha channels are already premultiplied and will be unpremultiplied after Assumes alpha channels are already premultiplied and will be unpremultiplied after.
*/ */
VImage Composite(VImage src, VImage dst) { VImage Composite(VImage src, VImage dst, const int gravity) {
using sharp::CalculateCrop;
using sharp::HasAlpha; using sharp::HasAlpha;
// Split src into non-alpha and alpha 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");
}
// Enlarge overlay src, if required
if (src.width() < dst.width() || src.height() < dst.height()) {
// Calculate the (left, top) coordinates of the output image within the input image, applying the given gravity.
int left;
int top;
std::tie(left, top) = CalculateCrop(dst.width(), dst.height(), src.width(), src.height(), gravity);
// Embed onto transparent background
std::vector<double> background { 0.0, 0.0, 0.0, 0.0 };
src = src.embed(left, top, dst.width(), dst.height(), VImage::option()
->set("extend", VIPS_EXTEND_BACKGROUND)
->set("background", background)
);
}
// Split src into non-alpha and alpha channels
VImage srcWithoutAlpha = src.extract_band(0, VImage::option()->set("n", src.bands() - 1)); VImage srcWithoutAlpha = src.extract_band(0, VImage::option()->set("n", src.bands() - 1));
VImage srcAlpha = src[src.bands() - 1] * (1.0 / 255.0); VImage srcAlpha = src[src.bands() - 1] * (1.0 / 255.0);
// Split dst into non-alpha and alpha channels // Split dst into non-alpha and alpha channels
VImage dstWithoutAlpha; VImage dstWithoutAlpha = dst.extract_band(0, VImage::option()->set("n", dst.bands() - 1));
VImage dstAlpha; VImage dstAlpha = dst[dst.bands() - 1] * (1.0 / 255.0);
if (HasAlpha(dst)) {
// Non-alpha: extract all-but-last channel
dstWithoutAlpha = dst.extract_band(0, VImage::option()->set("n", dst.bands() - 1));
// Alpha: Extract last channel
dstAlpha = dst[dst.bands() - 1] * (1.0 / 255.0);
} else {
// Non-alpha: Copy reference
dstWithoutAlpha = dst;
// Alpha: Use blank, opaque (0xFF) image
dstAlpha = VImage::black(dst.width(), dst.height()).invert();
}
// //
// Compute normalized output alpha channel: // Compute normalized output alpha channel:
@@ -118,10 +135,10 @@ namespace sharp {
} }
/* /*
* Gaussian blur (use sigma <0 for fast blur) * Gaussian blur. Use sigma of -1.0 for fast blur.
*/ */
VImage Blur(VImage image, double const sigma) { VImage Blur(VImage image, double const sigma) {
if (sigma < 0.0) { if (sigma == -1.0) {
// Fast, mild blur - averages neighbouring pixels // Fast, mild blur - averages neighbouring pixels
VImage blur = VImage::new_matrixv(3, 3, VImage blur = VImage::new_matrixv(3, 3,
1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
@@ -136,10 +153,10 @@ namespace sharp {
} }
/* /*
* Sharpen flat and jagged areas. Use radius of -1 for fast sharpen. * Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
*/ */
VImage Sharpen(VImage image, int const radius, double const flat, double const jagged) { VImage Sharpen(VImage image, double const sigma, double const flat, double const jagged) {
if (radius == -1) { if (sigma == -1.0) {
// Fast, mild sharpen // Fast, mild sharpen
VImage sharpen = VImage::new_matrixv(3, 3, VImage sharpen = VImage::new_matrixv(3, 3,
-1.0, -1.0, -1.0, -1.0, -1.0, -1.0,
@@ -150,8 +167,105 @@ namespace sharp {
} else { } else {
// Slow, accurate sharpen in LAB colour space, with control over flat vs jagged areas // Slow, accurate sharpen in LAB colour space, with control over flat vs jagged areas
return image.sharpen( return image.sharpen(
VImage::option()->set("radius", radius)->set("m1", flat)->set("m2", jagged) VImage::option()->set("sigma", sigma)->set("m1", flat)->set("m2", jagged)
); );
} }
} }
/*
Calculate crop area based on image entropy
*/
std::tuple<int, int> EntropyCrop(VImage image, int const outWidth, int const outHeight) {
int left = 0;
int top = 0;
int const inWidth = image.width();
int const inHeight = image.height();
if (inWidth > outWidth) {
// Reduce width by repeated removing slices from edge with lowest entropy
int width = inWidth;
double leftEntropy = 0.0;
double rightEntropy = 0.0;
// Max width of each slice
int const maxSliceWidth = static_cast<int>(ceil((inWidth - outWidth) / 8.0));
while (width > outWidth) {
// Width of current slice
int const slice = std::min(width - outWidth, maxSliceWidth);
if (leftEntropy == 0.0) {
// Update entropy of left slice
leftEntropy = Entropy(image.extract_area(left, 0, slice, inHeight));
}
if (rightEntropy == 0.0) {
// Update entropy of right slice
rightEntropy = Entropy(image.extract_area(width - slice - 1, 0, slice, inHeight));
}
// Keep slice with highest entropy
if (leftEntropy >= rightEntropy) {
// Discard right slice
rightEntropy = 0.0;
} else {
// Discard left slice
leftEntropy = 0.0;
left = left + slice;
}
width = width - slice;
}
}
if (inHeight > outHeight) {
// Reduce height by repeated removing slices from edge with lowest entropy
int height = inHeight;
double topEntropy = 0.0;
double bottomEntropy = 0.0;
// Max height of each slice
int const maxSliceHeight = static_cast<int>(ceil((inHeight - outHeight) / 8.0));
while (height > outHeight) {
// Height of current slice
int const slice = std::min(height - outHeight, maxSliceHeight);
if (topEntropy == 0.0) {
// Update entropy of top slice
topEntropy = Entropy(image.extract_area(0, top, inWidth, slice));
}
if (bottomEntropy == 0.0) {
// Update entropy of bottom slice
bottomEntropy = Entropy(image.extract_area(0, height - slice - 1, inWidth, slice));
}
// Keep slice with highest entropy
if (topEntropy >= bottomEntropy) {
// Discard bottom slice
bottomEntropy = 0.0;
} else {
// Discard top slice
topEntropy = 0.0;
top = top + slice;
}
height = height - slice;
}
}
return std::make_tuple(left, top);
}
/*
Calculate the Shannon entropy for an image
*/
double Entropy(VImage image) {
return image.hist_find().hist_entropy();
}
/*
Insert a tile cache to prevent over-computation of any previous operations in the pipeline
*/
VImage TileCache(VImage image, double const factor) {
int tile_width;
int tile_height;
int scanline_count;
vips_get_tile_size(image.get_image(), &tile_width, &tile_height, &scanline_count);
double const need_lines = 1.2 * scanline_count / factor;
return image.tilecache(VImage::option()
->set("tile_width", image.width())
->set("tile_height", 10)
->set("max_tiles", static_cast<int>(round(1.0 + need_lines / 10.0)))
->set("access", VIPS_ACCESS_SEQUENTIAL)
->set("threaded", TRUE)
);
}
} // namespace sharp } // namespace sharp

View File

@@ -1,6 +1,7 @@
#ifndef SRC_OPERATIONS_H_ #ifndef SRC_OPERATIONS_H_
#define SRC_OPERATIONS_H_ #define SRC_OPERATIONS_H_
#include <tuple>
#include <vips/vips8> #include <vips/vips8>
using vips::VImage; using vips::VImage;
@@ -8,10 +9,10 @@ using vips::VImage;
namespace sharp { namespace sharp {
/* /*
Composite images `src` and `dst` with premultiplied alpha channel and output Alpha composite src over dst with given gravity.
image with premultiplied alpha. Assumes alpha channels are already premultiplied and will be unpremultiplied after.
*/ */
VImage Composite(VImage src, VImage dst); VImage Composite(VImage src, VImage dst, const int gravity);
/* /*
* Stretch luminance to cover full dynamic range. * Stretch luminance to cover full dynamic range.
@@ -24,14 +25,30 @@ namespace sharp {
VImage Gamma(VImage image, double const exponent); VImage Gamma(VImage image, double const exponent);
/* /*
* Gaussian blur. Use sigma of -1 for fast blur. * Gaussian blur. Use sigma of -1.0 for fast blur.
*/ */
VImage Blur(VImage image, double const sigma); VImage Blur(VImage image, double const sigma);
/* /*
* Sharpen flat and jagged areas. Use radius of -1 for fast sharpen. * Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
*/ */
VImage Sharpen(VImage image, int const radius, double const flat, double const jagged); VImage Sharpen(VImage image, double const sigma, double const flat, double const jagged);
/*
Calculate crop area based on image entropy
*/
std::tuple<int, int> EntropyCrop(VImage image, int const outWidth, int const outHeight);
/*
Calculate the Shannon entropy for an image
*/
double Entropy(VImage image);
/*
Insert a tile cache to prevent over-computation of any previous operations in the pipeline
*/
VImage TileCache(VImage image, double const factor);
} // namespace sharp } // namespace sharp
#endif // SRC_OPERATIONS_H_ #endif // SRC_OPERATIONS_H_

View File

@@ -1,10 +1,12 @@
#include <tuple>
#include <algorithm> #include <algorithm>
#include <utility>
#include <cmath> #include <cmath>
#include <tuple>
#include <utility>
#include <vips/vips8>
#include <node.h> #include <node.h>
#include <node_buffer.h> #include <node_buffer.h>
#include <vips/vips8>
#include "nan.h" #include "nan.h"
@@ -47,6 +49,8 @@ using sharp::Normalize;
using sharp::Gamma; using sharp::Gamma;
using sharp::Blur; using sharp::Blur;
using sharp::Sharpen; using sharp::Sharpen;
using sharp::EntropyCrop;
using sharp::TileCache;
using sharp::ImageType; using sharp::ImageType;
using sharp::ImageTypeId; using sharp::ImageTypeId;
@@ -56,139 +60,29 @@ using sharp::HasAlpha;
using sharp::ExifOrientation; using sharp::ExifOrientation;
using sharp::SetExifOrientation; using sharp::SetExifOrientation;
using sharp::RemoveExifOrientation; using sharp::RemoveExifOrientation;
using sharp::SetDensity;
using sharp::IsJpeg; using sharp::IsJpeg;
using sharp::IsPng; using sharp::IsPng;
using sharp::IsWebp; using sharp::IsWebp;
using sharp::IsTiff; using sharp::IsTiff;
using sharp::IsDz; using sharp::IsDz;
using sharp::IsDzZip;
using sharp::FreeCallback; using sharp::FreeCallback;
using sharp::CalculateCrop;
using sharp::counterProcess; using sharp::counterProcess;
using sharp::counterQueue; using sharp::counterQueue;
enum class Canvas {
CROP,
EMBED,
MAX,
MIN,
IGNORE_ASPECT
};
struct PipelineBaton {
std::string fileIn;
char *bufferIn;
size_t bufferInLength;
std::string iccProfilePath;
int limitInputPixels;
std::string density;
int rawWidth;
int rawHeight;
int rawChannels;
std::string formatOut;
std::string fileOut;
void *bufferOut;
size_t bufferOutLength;
int topOffsetPre;
int leftOffsetPre;
int widthPre;
int heightPre;
int topOffsetPost;
int leftOffsetPost;
int widthPost;
int heightPost;
int width;
int height;
int channels;
Canvas canvas;
int gravity;
std::string interpolator;
double background[4];
bool flatten;
bool negate;
double blurSigma;
int sharpenRadius;
double sharpenFlat;
double sharpenJagged;
int threshold;
std::string overlayPath;
double gamma;
bool greyscale;
bool normalize;
int angle;
bool rotateBeforePreExtract;
bool flip;
bool flop;
bool progressive;
bool withoutEnlargement;
VipsAccess accessMethod;
int quality;
int compressionLevel;
bool withoutAdaptiveFiltering;
bool withoutChromaSubsampling;
bool trellisQuantisation;
bool overshootDeringing;
bool optimiseScans;
std::string err;
bool withMetadata;
int withMetadataOrientation;
int tileSize;
int tileOverlap;
PipelineBaton():
bufferInLength(0),
limitInputPixels(0),
density(""),
rawWidth(0),
rawHeight(0),
rawChannels(0),
formatOut(""),
fileOut(""),
bufferOutLength(0),
topOffsetPre(-1),
topOffsetPost(-1),
channels(0),
canvas(Canvas::CROP),
gravity(0),
flatten(false),
negate(false),
blurSigma(0.0),
sharpenRadius(0),
sharpenFlat(1.0),
sharpenJagged(2.0),
threshold(0),
gamma(0.0),
greyscale(false),
normalize(false),
angle(0),
flip(false),
flop(false),
progressive(false),
withoutEnlargement(false),
quality(80),
compressionLevel(6),
withoutAdaptiveFiltering(false),
withoutChromaSubsampling(false),
trellisQuantisation(false),
overshootDeringing(false),
optimiseScans(false),
withMetadata(false),
withMetadataOrientation(-1),
tileSize(256),
tileOverlap(0) {
background[0] = 0.0;
background[1] = 0.0;
background[2] = 0.0;
background[3] = 255.0;
}
};
class PipelineWorker : public AsyncWorker { class PipelineWorker : public AsyncWorker {
public: public:
PipelineWorker(Callback *callback, PipelineBaton *baton, Callback *queueListener, const Local<Object> &bufferIn) : PipelineWorker(Callback *callback, PipelineBaton *baton, Callback *queueListener,
const Local<Object> &bufferIn, const Local<Object> &overlayBufferIn) :
AsyncWorker(callback), baton(baton), queueListener(queueListener) { AsyncWorker(callback), baton(baton), queueListener(queueListener) {
if (baton->bufferInLength > 0) { if (baton->bufferInLength > 0) {
SaveToPersistent("bufferIn", bufferIn); SaveToPersistent("bufferIn", bufferIn);
} }
if (baton->overlayBufferInLength > 0) {
SaveToPersistent("overlayBufferIn", overlayBufferIn);
}
} }
~PipelineWorker() {} ~PipelineWorker() {}
@@ -196,7 +90,6 @@ class PipelineWorker : public AsyncWorker {
libuv worker libuv worker
*/ */
void Execute() { void Execute() {
// Decrement queued task counter // Decrement queued task counter
g_atomic_int_dec_and_test(&counterQueue); g_atomic_int_dec_and_test(&counterQueue);
// Increment processing task counter // Increment processing task counter
@@ -212,6 +105,7 @@ class PipelineWorker : public AsyncWorker {
// From buffer // From buffer
if (baton->rawWidth > 0 && baton->rawHeight > 0 && baton->rawChannels > 0) { if (baton->rawWidth > 0 && baton->rawHeight > 0 && baton->rawChannels > 0) {
// Raw, uncompressed pixel data // Raw, uncompressed pixel data
try {
image = VImage::new_from_memory(baton->bufferIn, baton->bufferInLength, image = VImage::new_from_memory(baton->bufferIn, baton->bufferInLength,
baton->rawWidth, baton->rawHeight, baton->rawChannels, VIPS_FORMAT_UCHAR); baton->rawWidth, baton->rawHeight, baton->rawChannels, VIPS_FORMAT_UCHAR);
if (baton->rawChannels < 3) { if (baton->rawChannels < 3) {
@@ -220,16 +114,28 @@ class PipelineWorker : public AsyncWorker {
image.get_image()->Type = VIPS_INTERPRETATION_sRGB; image.get_image()->Type = VIPS_INTERPRETATION_sRGB;
} }
inputImageType = ImageType::RAW; inputImageType = ImageType::RAW;
} catch(VError const &err) {
(baton->err).append(err.what());
inputImageType = ImageType::UNKNOWN;
}
} else { } else {
// Compressed data // Compressed data
inputImageType = DetermineImageType(baton->bufferIn, baton->bufferInLength); inputImageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
if (inputImageType != ImageType::UNKNOWN) { if (inputImageType != ImageType::UNKNOWN) {
try { try {
VOption *option = VImage::option()->set("access", baton->accessMethod); VOption *option = VImage::option()->set("access", baton->accessMethod);
if (inputImageType == ImageType::SVG || inputImageType == ImageType::PDF) {
option->set("dpi", static_cast<double>(baton->density));
}
if (inputImageType == ImageType::MAGICK) { if (inputImageType == ImageType::MAGICK) {
option->set("density", baton->density.data()); option->set("density", std::to_string(baton->density).data());
} }
image = VImage::new_from_buffer(baton->bufferIn, baton->bufferInLength, nullptr, option); image = VImage::new_from_buffer(baton->bufferIn, baton->bufferInLength, nullptr, option);
if (inputImageType == ImageType::SVG ||
inputImageType == ImageType::PDF ||
inputImageType == ImageType::MAGICK) {
SetDensity(image, baton->density);
}
} catch (...) { } catch (...) {
(baton->err).append("Input buffer has corrupt header"); (baton->err).append("Input buffer has corrupt header");
inputImageType = ImageType::UNKNOWN; inputImageType = ImageType::UNKNOWN;
@@ -244,10 +150,18 @@ class PipelineWorker : public AsyncWorker {
if (inputImageType != ImageType::UNKNOWN) { if (inputImageType != ImageType::UNKNOWN) {
try { try {
VOption *option = VImage::option()->set("access", baton->accessMethod); VOption *option = VImage::option()->set("access", baton->accessMethod);
if (inputImageType == ImageType::SVG || inputImageType == ImageType::PDF) {
option->set("dpi", static_cast<double>(baton->density));
}
if (inputImageType == ImageType::MAGICK) { if (inputImageType == ImageType::MAGICK) {
option->set("density", baton->density.data()); option->set("density", std::to_string(baton->density).data());
} }
image = VImage::new_from_file(baton->fileIn.data(), option); image = VImage::new_from_file(baton->fileIn.data(), option);
if (inputImageType == ImageType::SVG ||
inputImageType == ImageType::PDF ||
inputImageType == ImageType::MAGICK) {
SetDensity(image, baton->density);
}
} catch (...) { } catch (...) {
(baton->err).append("Input file has corrupt header"); (baton->err).append("Input file has corrupt header");
inputImageType = ImageType::UNKNOWN; inputImageType = ImageType::UNKNOWN;
@@ -261,7 +175,8 @@ class PipelineWorker : public AsyncWorker {
} }
// Limit input images to a given number of pixels, where pixels = width * height // Limit input images to a given number of pixels, where pixels = width * height
if (image.width() * image.height() > baton->limitInputPixels) { // Ignore if 0
if (baton->limitInputPixels > 0 && image.width() * image.height() > baton->limitInputPixels) {
(baton->err).append("Input image exceeds pixel limit"); (baton->err).append("Input image exceeds pixel limit");
return Error(); return Error();
} }
@@ -295,22 +210,19 @@ class PipelineWorker : public AsyncWorker {
// Get pre-resize image width and height // Get pre-resize image width and height
int inputWidth = image.width(); int inputWidth = image.width();
int inputHeight = image.height(); int inputHeight = image.height();
if (!baton->rotateBeforePreExtract && (rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270)) { if (!baton->rotateBeforePreExtract &&
(rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270)) {
// Swap input output width and height when rotating by 90 or 270 degrees // Swap input output width and height when rotating by 90 or 270 degrees
std::swap(inputWidth, inputHeight); std::swap(inputWidth, inputHeight);
} }
// Get window size of interpolator, used for determining shrink vs affine
VInterpolate interpolator = VInterpolate::new_from_name(baton->interpolator.data());
int interpolatorWindowSize = vips_interpolate_get_window_size(interpolator.get_interpolate());
// Scaling calculations // Scaling calculations
double xfactor = 1.0; double xfactor = 1.0;
double yfactor = 1.0; double yfactor = 1.0;
if (baton->width > 0 && baton->height > 0) { if (baton->width > 0 && baton->height > 0) {
// Fixed width and height // Fixed width and height
xfactor = static_cast<double>(inputWidth) / static_cast<double>(baton->width); xfactor = static_cast<double>(inputWidth) / (static_cast<double>(baton->width) + 0.1);
yfactor = static_cast<double>(inputHeight) / static_cast<double>(baton->height); yfactor = static_cast<double>(inputHeight) / (static_cast<double>(baton->height) + 0.1);
switch (baton->canvas) { switch (baton->canvas) {
case Canvas::CROP: case Canvas::CROP:
xfactor = std::min(xfactor, yfactor); xfactor = std::min(xfactor, yfactor);
@@ -339,12 +251,15 @@ class PipelineWorker : public AsyncWorker {
} }
break; break;
case Canvas::IGNORE_ASPECT: case Canvas::IGNORE_ASPECT:
// xfactor, yfactor OK! if (!baton->rotateBeforePreExtract &&
(rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270)) {
std::swap(xfactor, yfactor);
}
break; break;
} }
} else if (baton->width > 0) { } else if (baton->width > 0) {
// Fixed width // Fixed width
xfactor = static_cast<double>(inputWidth) / static_cast<double>(baton->width); xfactor = static_cast<double>(inputWidth) / (static_cast<double>(baton->width) + 0.1);
if (baton->canvas == Canvas::IGNORE_ASPECT) { if (baton->canvas == Canvas::IGNORE_ASPECT) {
baton->height = inputHeight; baton->height = inputHeight;
} else { } else {
@@ -354,7 +269,7 @@ class PipelineWorker : public AsyncWorker {
} }
} else if (baton->height > 0) { } else if (baton->height > 0) {
// Fixed height // Fixed height
yfactor = static_cast<double>(inputHeight) / static_cast<double>(baton->height); yfactor = static_cast<double>(inputHeight) / (static_cast<double>(baton->height) + 0.1);
if (baton->canvas == Canvas::IGNORE_ASPECT) { if (baton->canvas == Canvas::IGNORE_ASPECT) {
baton->width = inputWidth; baton->width = inputWidth;
} else { } else {
@@ -369,23 +284,23 @@ class PipelineWorker : public AsyncWorker {
} }
// Calculate integral box shrink // Calculate integral box shrink
int xshrink = CalculateShrink(xfactor, interpolatorWindowSize); int xshrink = std::max(1, static_cast<int>(floor(xfactor)));
int yshrink = CalculateShrink(yfactor, interpolatorWindowSize); int yshrink = std::max(1, static_cast<int>(floor(yfactor)));
// Calculate residual float affine transformation // Calculate residual float affine transformation
double xresidual = CalculateResidual(xshrink, xfactor); double xresidual = static_cast<double>(xshrink) / xfactor;
double yresidual = CalculateResidual(yshrink, yfactor); double yresidual = static_cast<double>(yshrink) / yfactor;
// Do not enlarge the output if the input width *or* height // Do not enlarge the output if the input width *or* height
// are already less than the required dimensions // are already less than the required dimensions
if (baton->withoutEnlargement) { if (baton->withoutEnlargement) {
if (inputWidth < baton->width || inputHeight < baton->height) { if (inputWidth < baton->width || inputHeight < baton->height) {
xfactor = 1; xfactor = 1.0;
yfactor = 1; yfactor = 1.0;
xshrink = 1; xshrink = 1;
yshrink = 1; yshrink = 1;
xresidual = 0; xresidual = 1.0;
yresidual = 0; yresidual = 1.0;
baton->width = inputWidth; baton->width = inputWidth;
baton->height = inputHeight; baton->height = inputHeight;
} }
@@ -395,7 +310,8 @@ class PipelineWorker : public AsyncWorker {
// but not when applying gamma correction or pre-resize extract // but not when applying gamma correction or pre-resize extract
int shrink_on_load = 1; int shrink_on_load = 1;
if ( if (
xshrink == yshrink && inputImageType == ImageType::JPEG && xshrink >= 2 && xshrink == yshrink && xshrink >= 2 &&
(inputImageType == ImageType::JPEG || inputImageType == ImageType::WEBP) &&
baton->gamma == 0 && baton->topOffsetPre == -1 baton->gamma == 0 && baton->topOffsetPre == -1
) { ) {
if (xshrink >= 8) { if (xshrink >= 8) {
@@ -416,20 +332,30 @@ class PipelineWorker : public AsyncWorker {
// Recalculate integral shrink and double residual // Recalculate integral shrink and double residual
xfactor = std::max(xfactor, 1.0); xfactor = std::max(xfactor, 1.0);
yfactor = std::max(yfactor, 1.0); yfactor = std::max(yfactor, 1.0);
xshrink = CalculateShrink(xfactor, interpolatorWindowSize); xshrink = std::max(1, static_cast<int>(floor(xfactor)));
yshrink = CalculateShrink(yfactor, interpolatorWindowSize); yshrink = std::max(1, static_cast<int>(floor(yfactor)));
xresidual = CalculateResidual(xshrink, xfactor); xresidual = static_cast<double>(xshrink) / xfactor;
yresidual = CalculateResidual(yshrink, yfactor); yresidual = static_cast<double>(yshrink) / yfactor;
// Reload input using shrink-on-load // Reload input using shrink-on-load
VOption *option = VImage::option()->set("shrink", shrink_on_load);
if (baton->bufferInLength > 1) { if (baton->bufferInLength > 1) {
VipsBlob *blob = vips_blob_new(nullptr, baton->bufferIn, baton->bufferInLength); VipsBlob *blob = vips_blob_new(nullptr, baton->bufferIn, baton->bufferInLength);
image = VImage::jpegload_buffer(blob, VImage::option()->set("shrink", shrink_on_load)); if (inputImageType == ImageType::JPEG) {
// Reload JPEG buffer
image = VImage::jpegload_buffer(blob, option);
} else {
// Reload WebP buffer
image = VImage::webpload_buffer(blob, option);
}
vips_area_unref(reinterpret_cast<VipsArea*>(blob)); vips_area_unref(reinterpret_cast<VipsArea*>(blob));
} else { } else {
image = VImage::jpegload( if (inputImageType == ImageType::JPEG) {
const_cast<char*>((baton->fileIn).data()), // Reload JPEG file
VImage::option()->set("shrink", shrink_on_load) image = VImage::jpegload(const_cast<char*>((baton->fileIn).data()), option);
); } else {
// Reload WebP file
image = VImage::webpload(const_cast<char*>((baton->fileIn).data()), option);
}
} }
} }
@@ -489,11 +415,17 @@ class PipelineWorker : public AsyncWorker {
} }
if (xshrink > 1 || yshrink > 1) { if (xshrink > 1 || yshrink > 1) {
image = image.shrink(xshrink, yshrink); if (yshrink > 1) {
image = image.shrinkv(yshrink);
}
if (xshrink > 1) {
image = image.shrinkh(xshrink);
}
// Recalculate residual float based on dimensions of required vs shrunk images // Recalculate residual float based on dimensions of required vs shrunk images
int shrunkWidth = image.width(); int shrunkWidth = image.width();
int shrunkHeight = image.height(); int shrunkHeight = image.height();
if (rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270) { if (!baton->rotateBeforePreExtract &&
(rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270)) {
// Swap input output width and height when rotating by 90 or 270 degrees // Swap input output width and height when rotating by 90 or 270 degrees
std::swap(shrunkWidth, shrunkHeight); std::swap(shrunkWidth, shrunkHeight);
} }
@@ -502,17 +434,30 @@ class PipelineWorker : public AsyncWorker {
if (baton->canvas == Canvas::EMBED) { if (baton->canvas == Canvas::EMBED) {
xresidual = std::min(xresidual, yresidual); xresidual = std::min(xresidual, yresidual);
yresidual = xresidual; yresidual = xresidual;
} else if (baton->canvas != Canvas::IGNORE_ASPECT) { } else if (baton->canvas == Canvas::IGNORE_ASPECT) {
if (!baton->rotateBeforePreExtract &&
(rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270)) {
std::swap(xresidual, yresidual);
}
} else {
xresidual = std::max(xresidual, yresidual); xresidual = std::max(xresidual, yresidual);
yresidual = xresidual; yresidual = xresidual;
} }
} }
bool shouldAffineTransform = xresidual != 0.0 || yresidual != 0.0; // Ensure image has an alpha channel when there is an overlay
bool hasOverlay = baton->overlayBufferInLength > 0 || !baton->overlayFileIn.empty();
if (hasOverlay && !HasAlpha(image)) {
double multiplier = (image.interpretation() == VIPS_INTERPRETATION_RGB16) ? 256.0 : 1.0;
image = image.bandjoin(
VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier)
);
}
bool shouldAffineTransform = xresidual != 1.0 || yresidual != 1.0;
bool shouldBlur = baton->blurSigma != 0.0; bool shouldBlur = baton->blurSigma != 0.0;
bool shouldSharpen = baton->sharpenRadius != 0; bool shouldSharpen = baton->sharpenSigma != 0.0;
bool shouldThreshold = baton->threshold != 0; bool shouldThreshold = baton->threshold != 0;
bool hasOverlay = !baton->overlayPath.empty();
bool shouldPremultiplyAlpha = HasAlpha(image) && bool shouldPremultiplyAlpha = HasAlpha(image) &&
(shouldAffineTransform || shouldBlur || shouldSharpen || hasOverlay); (shouldAffineTransform || shouldBlur || shouldSharpen || hasOverlay);
@@ -523,32 +468,42 @@ class PipelineWorker : public AsyncWorker {
image = image.premultiply(VImage::option()->set("max_alpha", maxAlpha)); image = image.premultiply(VImage::option()->set("max_alpha", maxAlpha));
} }
// Use affine transformation with the remaining float part // Use affine increase or kernel reduce with the remaining float part
if (shouldAffineTransform) { if (shouldAffineTransform) {
// Use average of x and y residuals to compute sigma for Gaussian blur // Insert tile cache to prevent over-computation of previous operations
double residual = (xresidual + yresidual) / 2.0;
// Apply Gaussian blur before large affine reductions
if (residual < 1.0) {
// Calculate standard deviation
double sigma = ((1.0 / residual) - 0.4) / 3.0;
if (sigma >= 0.3) {
// Sequential input requires a small linecache before use of convolution
if (baton->accessMethod == VIPS_ACCESS_SEQUENTIAL) { if (baton->accessMethod == VIPS_ACCESS_SEQUENTIAL) {
image = image.linecache(VImage::option() image = TileCache(image, yresidual);
->set("access", VIPS_ACCESS_SEQUENTIAL) }
->set("tile_height", 1) // Perform kernel-based reduction
->set("threaded", TRUE) if (yresidual < 1.0 || xresidual < 1.0) {
VipsKernel kernel = static_cast<VipsKernel>(
vips_enum_from_nick(nullptr, VIPS_TYPE_KERNEL, baton->kernel.data())
); );
if (kernel != VIPS_KERNEL_CUBIC && kernel != VIPS_KERNEL_LANCZOS2 && kernel != VIPS_KERNEL_LANCZOS3) {
throw VError("Unknown kernel");
} }
// Apply Gaussian blur if (yresidual < 1.0) {
image = image.gaussblur(sigma); image = image.reducev(1.0 / yresidual, VImage::option()->set("kernel", kernel));
}
if (xresidual < 1.0) {
image = image.reduceh(1.0 / xresidual, VImage::option()->set("kernel", kernel));
} }
} }
// Perform affine transformation // Perform affine enlargement
image = image.affine({xresidual, 0.0, 0.0, yresidual}, VImage::option() if (yresidual > 1.0 || xresidual > 1.0) {
VInterpolate interpolator = VInterpolate::new_from_name(baton->interpolator.data());
if (yresidual > 1.0) {
image = image.affine({1.0, 0.0, 0.0, yresidual}, VImage::option()
->set("interpolate", interpolator) ->set("interpolate", interpolator)
); );
} }
if (xresidual > 1.0) {
image = image.affine({xresidual, 0.0, 0.0, 1.0}, VImage::option()
->set("interpolate", interpolator)
);
}
}
}
// Rotate // Rotate
if (!baton->rotateBeforePreExtract && rotation != VIPS_ANGLE_D0) { if (!baton->rotateBeforePreExtract && rotation != VIPS_ANGLE_D0) {
@@ -574,11 +529,21 @@ class PipelineWorker : public AsyncWorker {
// Scale up 8-bit values to match 16-bit input image // Scale up 8-bit values to match 16-bit input image
double multiplier = (image.interpretation() == VIPS_INTERPRETATION_RGB16) ? 256.0 : 1.0; double multiplier = (image.interpretation() == VIPS_INTERPRETATION_RGB16) ? 256.0 : 1.0;
// Create background colour // Create background colour
std::vector<double> background { std::vector<double> background;
baton->background[0] * multiplier, if (image.bands() > 2) {
baton->background[1] * multiplier, background = {
baton->background[2] * multiplier multiplier * baton->background[0],
multiplier * baton->background[1],
multiplier * baton->background[2]
}; };
} else {
// Convert sRGB to greyscale
background = { multiplier * (
0.2126 * baton->background[0] +
0.7152 * baton->background[1] +
0.0722 * baton->background[2]
)};
}
// Add alpha channel to background colour // Add alpha channel to background colour
if (baton->background[3] < 255.0 || HasAlpha(image)) { if (baton->background[3] < 255.0 || HasAlpha(image)) {
background.push_back(baton->background[3] * multiplier); background.push_back(baton->background[3] * multiplier);
@@ -600,9 +565,15 @@ class PipelineWorker : public AsyncWorker {
// Crop/max/min // Crop/max/min
int left; int left;
int top; int top;
if (baton->crop < 9) {
// Gravity-based crop
std::tie(left, top) = CalculateCrop( std::tie(left, top) = CalculateCrop(
image.width(), image.height(), baton->width, baton->height, baton->gravity image.width(), image.height(), baton->width, baton->height, baton->crop
); );
} else {
// Entropy-based crop
std::tie(left, top) = EntropyCrop(image, baton->width, baton->height);
}
int width = std::min(image.width(), baton->width); int width = std::min(image.width(), baton->width);
int height = std::min(image.height(), baton->height); int height = std::min(image.height(), baton->height);
image = image.extract_area(left, top, width, height); image = image.extract_area(left, top, width, height);
@@ -616,6 +587,27 @@ class PipelineWorker : public AsyncWorker {
); );
} }
// Extend edges
if (baton->extendTop > 0 || baton->extendBottom > 0 || baton->extendLeft > 0 || baton->extendRight > 0) {
// Scale up 8-bit values to match 16-bit input image
const double multiplier = (image.interpretation() == VIPS_INTERPRETATION_RGB16) ? 256.0 : 1.0;
// Create background colour
std::vector<double> background {
baton->background[0] * multiplier,
baton->background[1] * multiplier,
baton->background[2] * multiplier
};
// Add alpha channel to background colour
if (HasAlpha(image)) {
background.push_back(baton->background[3] * multiplier);
}
// Embed
baton->width = image.width() + baton->extendLeft + baton->extendRight;
baton->height = image.height() + baton->extendTop + baton->extendBottom;
image = image.embed(baton->extendLeft, baton->extendTop, baton->width, baton->height,
VImage::option()->set("extend", VIPS_EXTEND_BACKGROUND)->set("background", background));
}
// Threshold - must happen before blurring, due to the utility of blurring after thresholding // Threshold - must happen before blurring, due to the utility of blurring after thresholding
if (shouldThreshold) { if (shouldThreshold) {
image = image.colourspace(VIPS_INTERPRETATION_B_W) >= baton->threshold; image = image.colourspace(VIPS_INTERPRETATION_B_W) >= baton->threshold;
@@ -628,44 +620,47 @@ class PipelineWorker : public AsyncWorker {
// Sharpen // Sharpen
if (shouldSharpen) { if (shouldSharpen) {
image = Sharpen(image, baton->sharpenRadius, baton->sharpenFlat, baton->sharpenJagged); image = Sharpen(image, baton->sharpenSigma, baton->sharpenFlat, baton->sharpenJagged);
} }
// Composite with overlay, if present // Composite with overlay, if present
if (hasOverlay) { if (hasOverlay) {
VImage overlayImage; VImage overlayImage;
ImageType overlayImageType = DetermineImageType(baton->overlayPath.data()); ImageType overlayImageType = ImageType::UNKNOWN;
if (baton->overlayBufferInLength > 0) {
// Overlay with image from buffer
overlayImageType = DetermineImageType(baton->overlayBufferIn, baton->overlayBufferInLength);
if (overlayImageType != ImageType::UNKNOWN) { if (overlayImageType != ImageType::UNKNOWN) {
overlayImage = VImage::new_from_file( try {
baton->overlayPath.data(), overlayImage = VImage::new_from_buffer(baton->overlayBufferIn, baton->overlayBufferInLength,
VImage::option()->set("access", baton->accessMethod) nullptr, VImage::option()->set("access", baton->accessMethod));
); } catch (...) {
(baton->err).append("Overlay buffer has corrupt header");
overlayImageType = ImageType::UNKNOWN;
}
} else { } else {
(baton->err).append("Overlay image is of an unsupported image format"); (baton->err).append("Overlay buffer contains unsupported image format");
}
} else {
// Overlay with image from file
overlayImageType = DetermineImageType(baton->overlayFileIn.data());
if (overlayImageType != ImageType::UNKNOWN) {
try {
overlayImage = VImage::new_from_file(baton->overlayFileIn.data(),
VImage::option()->set("access", baton->accessMethod));
} catch (...) {
(baton->err).append("Overlay file has corrupt header");
overlayImageType = ImageType::UNKNOWN;
}
}
}
if (overlayImageType == ImageType::UNKNOWN) {
return Error(); return Error();
} }
if (image.format() != VIPS_FORMAT_UCHAR && image.format() != VIPS_FORMAT_FLOAT) { // Ensure overlay is premultiplied sRGB
(baton->err).append("Expected image band format to be uchar or float: ");
(baton->err).append(vips_enum_nick(VIPS_TYPE_BAND_FORMAT, image.format()));
return Error();
}
if (overlayImage.format() != VIPS_FORMAT_UCHAR && overlayImage.format() != VIPS_FORMAT_FLOAT) {
(baton->err).append("Expected overlay image band format to be uchar or float: ");
(baton->err).append(vips_enum_nick(VIPS_TYPE_BAND_FORMAT, overlayImage.format()));
return Error();
}
if (!HasAlpha(overlayImage)) {
(baton->err).append("Overlay image must have an alpha channel");
return Error();
}
if (overlayImage.width() != image.width() && overlayImage.height() != image.height()) {
(baton->err).append("Overlay image must have same dimensions as resized image");
return Error();
}
// Ensure overlay is sRGB and premutiplied
overlayImage = overlayImage.colourspace(VIPS_INTERPRETATION_sRGB).premultiply(); overlayImage = overlayImage.colourspace(VIPS_INTERPRETATION_sRGB).premultiply();
// Composite images with given gravity
image = Composite(overlayImage, image); image = Composite(overlayImage, image, baton->overlayGravity);
} }
// Reverse premultiplication after all transformations: // Reverse premultiplication after all transformations:
@@ -708,6 +703,10 @@ class PipelineWorker : public AsyncWorker {
SetExifOrientation(image, baton->withMetadataOrientation); SetExifOrientation(image, baton->withMetadataOrientation);
} }
// Number of channels used in output image
baton->channels = image.bands();
baton->width = image.width();
baton->height = image.height();
// Output // Output
if (baton->fileOut == "") { if (baton->fileOut == "") {
// Buffer output // Buffer output
@@ -728,13 +727,15 @@ class PipelineWorker : public AsyncWorker {
area->free_fn = nullptr; area->free_fn = nullptr;
vips_area_unref(area); vips_area_unref(area);
baton->formatOut = "jpeg"; baton->formatOut = "jpeg";
baton->channels = std::min(baton->channels, 3);
} else if (baton->formatOut == "png" || (baton->formatOut == "input" && inputImageType == ImageType::PNG)) { } else if (baton->formatOut == "png" || (baton->formatOut == "input" && inputImageType == ImageType::PNG)) {
// Write PNG to buffer // Write PNG to buffer
VipsArea *area = VIPS_AREA(image.pngsave_buffer(VImage::option() VipsArea *area = VIPS_AREA(image.pngsave_buffer(VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("compression", baton->compressionLevel) ->set("compression", baton->compressionLevel)
->set("interlace", baton->progressive) ->set("interlace", baton->progressive)
->set("filter", baton->withoutAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_NONE : VIPS_FOREIGN_PNG_FILTER_ALL) ->set("filter", baton->withoutAdaptiveFiltering ?
VIPS_FOREIGN_PNG_FILTER_NONE : VIPS_FOREIGN_PNG_FILTER_ALL)
)); ));
baton->bufferOut = static_cast<char*>(area->data); baton->bufferOut = static_cast<char*>(area->data);
baton->bufferOutLength = area->length; baton->bufferOutLength = area->length;
@@ -786,7 +787,8 @@ class PipelineWorker : public AsyncWorker {
bool isWebp = IsWebp(baton->fileOut); bool isWebp = IsWebp(baton->fileOut);
bool isTiff = IsTiff(baton->fileOut); bool isTiff = IsTiff(baton->fileOut);
bool isDz = IsDz(baton->fileOut); bool isDz = IsDz(baton->fileOut);
bool matchInput = baton->formatOut == "input" && !(isJpeg || isPng || isWebp || isTiff || isDz); bool isDzZip = IsDzZip(baton->fileOut);
bool matchInput = baton->formatOut == "input" && !(isJpeg || isPng || isWebp || isTiff || isDz || isDzZip);
if (baton->formatOut == "jpeg" || isJpeg || (matchInput && inputImageType == ImageType::JPEG)) { if (baton->formatOut == "jpeg" || isJpeg || (matchInput && inputImageType == ImageType::JPEG)) {
// Write JPEG to file // Write JPEG to file
image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option() image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
@@ -800,13 +802,15 @@ class PipelineWorker : public AsyncWorker {
->set("interlace", baton->progressive) ->set("interlace", baton->progressive)
); );
baton->formatOut = "jpeg"; baton->formatOut = "jpeg";
baton->channels = std::min(baton->channels, 3);
} else if (baton->formatOut == "png" || isPng || (matchInput && inputImageType == ImageType::PNG)) { } else if (baton->formatOut == "png" || isPng || (matchInput && inputImageType == ImageType::PNG)) {
// Write PNG to file // Write PNG to file
image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option() image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("compression", baton->compressionLevel) ->set("compression", baton->compressionLevel)
->set("interlace", baton->progressive) ->set("interlace", baton->progressive)
->set("filter", baton->withoutAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_NONE : VIPS_FOREIGN_PNG_FILTER_ALL) ->set("filter", baton->withoutAdaptiveFiltering ?
VIPS_FOREIGN_PNG_FILTER_NONE : VIPS_FOREIGN_PNG_FILTER_ALL)
); );
baton->formatOut = "png"; baton->formatOut = "png";
} else if (baton->formatOut == "webp" || isWebp || (matchInput && inputImageType == ImageType::WEBP)) { } else if (baton->formatOut == "webp" || isWebp || (matchInput && inputImageType == ImageType::WEBP)) {
@@ -824,12 +828,18 @@ class PipelineWorker : public AsyncWorker {
->set("compression", VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) ->set("compression", VIPS_FOREIGN_TIFF_COMPRESSION_JPEG)
); );
baton->formatOut = "tiff"; baton->formatOut = "tiff";
} else if (baton->formatOut == "dz" || IsDz(baton->fileOut)) { baton->channels = std::min(baton->channels, 3);
} else if (baton->formatOut == "dz" || isDz || isDzZip) {
if (isDzZip) {
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
}
// Write DZ to file // Write DZ to file
image.dzsave(const_cast<char*>(baton->fileOut.data()), VImage::option() image.dzsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("tile_size", baton->tileSize) ->set("tile_size", baton->tileSize)
->set("overlap", baton->tileOverlap) ->set("overlap", baton->tileOverlap)
->set("container", baton->tileContainer)
->set("layout", baton->tileLayout)
); );
baton->formatOut = "dz"; baton->formatOut = "dz";
} else { } else {
@@ -838,8 +848,6 @@ class PipelineWorker : public AsyncWorker {
return Error(); return Error();
} }
} }
// Number of channels used in output image
baton->channels = image.bands();
} catch (VError const &err) { } catch (VError const &err) {
(baton->err).append(err.what()); (baton->err).append(err.what());
} }
@@ -890,10 +898,13 @@ class PipelineWorker : public AsyncWorker {
} }
} }
// Dispose of Persistent wrapper around input Buffer so it can be garbage collected // Dispose of Persistent wrapper around input Buffers so they can be garbage collected
if (baton->bufferInLength > 0) { if (baton->bufferInLength > 0) {
GetFromPersistent("bufferIn"); GetFromPersistent("bufferIn");
} }
if (baton->overlayBufferInLength > 0) {
GetFromPersistent("overlayBufferIn");
}
delete baton; delete baton;
// Decrement processing task counter // Decrement processing task counter
@@ -944,70 +955,6 @@ class PipelineWorker : public AsyncWorker {
return std::make_tuple(rotate, flip, flop); return std::make_tuple(rotate, flip, flop);
} }
/*
Calculate the (left, top) coordinates of the output image
within the input image, applying the given gravity.
*/
std::tuple<int, int>
CalculateCrop(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 = (inWidth - outWidth + 1) / 2;
break;
case 2: // East
left = inWidth - outWidth;
top = (inHeight - outHeight + 1) / 2;
break;
case 3: // South
left = (inWidth - outWidth + 1) / 2;
top = inHeight - outHeight;
break;
case 4: // West
top = (inHeight - outHeight + 1) / 2;
break;
case 5: // Northeast
left = inWidth - outWidth;
break;
case 6: // Southeast
left = inWidth - outWidth;
top = inHeight - outHeight;
case 7: // Southwest
top = inHeight - outHeight;
case 8: // Northwest
break;
default: // Centre
left = (inWidth - outWidth + 1) / 2;
top = (inHeight - outHeight + 1) / 2;
}
return std::make_tuple(left, top);
}
/*
Calculate integral shrink given factor and interpolator window size
*/
int CalculateShrink(double factor, int interpolatorWindowSize) {
int shrink = 1;
if (factor >= 2.0 && trunc(factor) != factor && interpolatorWindowSize > 3) {
// Shrink less, affine more with interpolators that use at least 4x4 pixel window, e.g. bicubic
shrink = static_cast<int>(floor(factor * 3.0 / interpolatorWindowSize));
} else {
shrink = static_cast<int>(floor(factor));
}
if (shrink < 1) {
shrink = 1;
}
return shrink;
}
/*
Calculate residual given shrink and factor
*/
double CalculateResidual(int shrink, double factor) {
return static_cast<double>(shrink) / factor;
}
/* /*
Clear all thread-local data. Clear all thread-local data.
*/ */
@@ -1052,7 +999,7 @@ NAN_METHOD(pipeline) {
// Limit input images to a given number of pixels, where pixels = width * height // Limit input images to a given number of pixels, where pixels = width * height
baton->limitInputPixels = attrAs<int32_t>(options, "limitInputPixels"); baton->limitInputPixels = attrAs<int32_t>(options, "limitInputPixels");
// Density/DPI at which to load vector images via libmagick // Density/DPI at which to load vector images via libmagick
baton->density = attrAsStr(options, "density"); baton->density = attrAs<int32_t>(options, "density");
// Raw pixel input // Raw pixel input
baton->rawWidth = attrAs<int32_t>(options, "rawWidth"); baton->rawWidth = attrAs<int32_t>(options, "rawWidth");
baton->rawHeight = attrAs<int32_t>(options, "rawHeight"); baton->rawHeight = attrAs<int32_t>(options, "rawHeight");
@@ -1088,16 +1035,24 @@ NAN_METHOD(pipeline) {
baton->background[i] = To<int32_t>(Get(background, i).ToLocalChecked()).FromJust(); baton->background[i] = To<int32_t>(Get(background, i).ToLocalChecked()).FromJust();
} }
// Overlay options // Overlay options
baton->overlayPath = attrAsStr(options, "overlayPath"); baton->overlayFileIn = attrAsStr(options, "overlayFileIn");
Local<Object> overlayBufferIn;
if (node::Buffer::HasInstance(Get(options, New("overlayBufferIn").ToLocalChecked()).ToLocalChecked())) {
overlayBufferIn = Get(options, New("overlayBufferIn").ToLocalChecked()).ToLocalChecked().As<Object>();
baton->overlayBufferInLength = node::Buffer::Length(overlayBufferIn);
baton->overlayBufferIn = node::Buffer::Data(overlayBufferIn);
}
baton->overlayGravity = attrAs<int32_t>(options, "overlayGravity");
// Resize options // Resize options
baton->withoutEnlargement = attrAs<bool>(options, "withoutEnlargement"); baton->withoutEnlargement = attrAs<bool>(options, "withoutEnlargement");
baton->gravity = attrAs<int32_t>(options, "gravity"); baton->crop = attrAs<int32_t>(options, "crop");
baton->kernel = attrAsStr(options, "kernel");
baton->interpolator = attrAsStr(options, "interpolator"); baton->interpolator = attrAsStr(options, "interpolator");
// Operators // Operators
baton->flatten = attrAs<bool>(options, "flatten"); baton->flatten = attrAs<bool>(options, "flatten");
baton->negate = attrAs<bool>(options, "negate"); baton->negate = attrAs<bool>(options, "negate");
baton->blurSigma = attrAs<double>(options, "blurSigma"); baton->blurSigma = attrAs<double>(options, "blurSigma");
baton->sharpenRadius = attrAs<int32_t>(options, "sharpenRadius"); baton->sharpenSigma = attrAs<double>(options, "sharpenSigma");
baton->sharpenFlat = attrAs<double>(options, "sharpenFlat"); baton->sharpenFlat = attrAs<double>(options, "sharpenFlat");
baton->sharpenJagged = attrAs<double>(options, "sharpenJagged"); baton->sharpenJagged = attrAs<double>(options, "sharpenJagged");
baton->threshold = attrAs<int32_t>(options, "threshold"); baton->threshold = attrAs<int32_t>(options, "threshold");
@@ -1108,6 +1063,10 @@ NAN_METHOD(pipeline) {
baton->rotateBeforePreExtract = attrAs<bool>(options, "rotateBeforePreExtract"); baton->rotateBeforePreExtract = attrAs<bool>(options, "rotateBeforePreExtract");
baton->flip = attrAs<bool>(options, "flip"); baton->flip = attrAs<bool>(options, "flip");
baton->flop = attrAs<bool>(options, "flop"); baton->flop = attrAs<bool>(options, "flop");
baton->extendTop = attrAs<int32_t>(options, "extendTop");
baton->extendBottom = attrAs<int32_t>(options, "extendBottom");
baton->extendLeft = attrAs<int32_t>(options, "extendLeft");
baton->extendRight = attrAs<int32_t>(options, "extendRight");
// Output options // Output options
baton->progressive = attrAs<bool>(options, "progressive"); baton->progressive = attrAs<bool>(options, "progressive");
baton->quality = attrAs<int32_t>(options, "quality"); baton->quality = attrAs<int32_t>(options, "quality");
@@ -1122,8 +1081,24 @@ NAN_METHOD(pipeline) {
// Output // Output
baton->formatOut = attrAsStr(options, "formatOut"); baton->formatOut = attrAsStr(options, "formatOut");
baton->fileOut = attrAsStr(options, "fileOut"); baton->fileOut = attrAsStr(options, "fileOut");
// Tile output
baton->tileSize = attrAs<int32_t>(options, "tileSize"); baton->tileSize = attrAs<int32_t>(options, "tileSize");
baton->tileOverlap = attrAs<int32_t>(options, "tileOverlap"); baton->tileOverlap = attrAs<int32_t>(options, "tileOverlap");
std::string tileContainer = attrAsStr(options, "tileContainer");
if (tileContainer == "zip") {
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
} else {
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_FS;
}
std::string tileLayout = attrAsStr(options, "tileLayout");
if (tileLayout == "google") {
baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_GOOGLE;
} else if (tileLayout == "zoomify") {
baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_ZOOMIFY;
} else {
baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_DZ;
}
// Function to notify of queue length changes // Function to notify of queue length changes
Callback *queueListener = new Callback( Callback *queueListener = new Callback(
Get(options, New("queueListener").ToLocalChecked()).ToLocalChecked().As<Function>() Get(options, New("queueListener").ToLocalChecked()).ToLocalChecked().As<Function>()
@@ -1131,7 +1106,7 @@ NAN_METHOD(pipeline) {
// Join queue for worker thread // Join queue for worker thread
Callback *callback = new Callback(info[1].As<Function>()); Callback *callback = new Callback(info[1].As<Function>());
AsyncQueueWorker(new PipelineWorker(callback, baton, queueListener, bufferIn)); AsyncQueueWorker(new PipelineWorker(callback, baton, queueListener, bufferIn, overlayBufferIn));
// Increment queued task counter // Increment queued task counter
g_atomic_int_inc(&counterQueue); g_atomic_int_inc(&counterQueue);

View File

@@ -1,8 +1,144 @@
#ifndef SRC_PIPELINE_H_ #ifndef SRC_PIPELINE_H_
#define SRC_PIPELINE_H_ #define SRC_PIPELINE_H_
#include <vips/vips8>
#include "nan.h" #include "nan.h"
NAN_METHOD(pipeline); NAN_METHOD(pipeline);
enum class Canvas {
CROP,
EMBED,
MAX,
MIN,
IGNORE_ASPECT
};
struct PipelineBaton {
std::string fileIn;
char *bufferIn;
size_t bufferInLength;
std::string iccProfilePath;
int limitInputPixels;
int density;
int rawWidth;
int rawHeight;
int rawChannels;
std::string formatOut;
std::string fileOut;
void *bufferOut;
size_t bufferOutLength;
std::string overlayFileIn;
char *overlayBufferIn;
size_t overlayBufferInLength;
int overlayGravity;
int topOffsetPre;
int leftOffsetPre;
int widthPre;
int heightPre;
int topOffsetPost;
int leftOffsetPost;
int widthPost;
int heightPost;
int width;
int height;
int channels;
Canvas canvas;
int crop;
std::string kernel;
std::string interpolator;
double background[4];
bool flatten;
bool negate;
double blurSigma;
double sharpenSigma;
double sharpenFlat;
double sharpenJagged;
int threshold;
double gamma;
bool greyscale;
bool normalize;
int angle;
bool rotateBeforePreExtract;
bool flip;
bool flop;
int extendTop;
int extendBottom;
int extendLeft;
int extendRight;
bool progressive;
bool withoutEnlargement;
VipsAccess accessMethod;
int quality;
int compressionLevel;
bool withoutAdaptiveFiltering;
bool withoutChromaSubsampling;
bool trellisQuantisation;
bool overshootDeringing;
bool optimiseScans;
std::string err;
bool withMetadata;
int withMetadataOrientation;
int tileSize;
int tileOverlap;
VipsForeignDzContainer tileContainer;
VipsForeignDzLayout tileLayout;
PipelineBaton():
bufferInLength(0),
limitInputPixels(0),
density(72),
rawWidth(0),
rawHeight(0),
rawChannels(0),
formatOut(""),
fileOut(""),
bufferOutLength(0),
overlayBufferInLength(0),
overlayGravity(0),
topOffsetPre(-1),
topOffsetPost(-1),
channels(0),
canvas(Canvas::CROP),
crop(0),
flatten(false),
negate(false),
blurSigma(0.0),
sharpenSigma(0.0),
sharpenFlat(1.0),
sharpenJagged(2.0),
threshold(0),
gamma(0.0),
greyscale(false),
normalize(false),
angle(0),
flip(false),
flop(false),
extendTop(0),
extendBottom(0),
extendLeft(0),
extendRight(0),
progressive(false),
withoutEnlargement(false),
quality(80),
compressionLevel(6),
withoutAdaptiveFiltering(false),
withoutChromaSubsampling(false),
trellisQuantisation(false),
overshootDeringing(false),
optimiseScans(false),
withMetadata(false),
withMetadataOrientation(-1),
tileSize(256),
tileOverlap(0),
tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS),
tileLayout(VIPS_FOREIGN_DZ_LAYOUT_DZ) {
background[0] = 0.0;
background[1] = 0.0;
background[2] = 0.0;
background[3] = 255.0;
}
};
#endif // SRC_PIPELINE_H_ #endif // SRC_PIPELINE_H_

View File

@@ -138,7 +138,9 @@ NAN_METHOD(format) {
// Which load/save operations are available for each compressed format? // Which load/save operations are available for each compressed format?
Local<Object> format = New<Object>(); Local<Object> format = New<Object>();
for (std::string f : {"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz"}) { for (std::string f : {
"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz", "ppm", "fits", "gif", "svg", "pdf"
}) {
// Input // Input
Local<Boolean> hasInputFile = Local<Boolean> hasInputFile =
New<Boolean>(vips_type_find("VipsOperation", (f + "load").c_str())); New<Boolean>(vips_type_find("VipsOperation", (f + "load").c_str()));

View File

@@ -9,12 +9,12 @@
}, },
"devDependencies": { "devDependencies": {
"async": "^1.5.2", "async": "^1.5.2",
"benchmark": "^2.0.0", "benchmark": "^2.1.0",
"gm": "^1.21.0", "gm": "^1.22.0",
"imagemagick": "^0.1.3", "imagemagick": "^0.1.3",
"imagemagick-native": "elad/node-imagemagick-native", "imagemagick-native": "^1.9.2",
"jimp": "^0.2.20", "jimp": "^0.2.24",
"lwip": "^0.0.8", "lwip": "^0.0.9",
"semver": "^5.1.0" "semver": "^5.1.0"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",

View File

@@ -30,18 +30,15 @@ var fixtures = require('../fixtures');
var width = 720; var width = 720;
var height = 480; var height = 480;
var magickFilterBilinear = 'Triangle';
var magickFilterBicubic = 'Lanczos';
// 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 // Enable use of SIMD
sharp.simd(true); sharp.simd(true);
async.series({ async.series({
'jpeg-linear': function(callback) { 'jpeg': function(callback) {
var inputJpgBuffer = fs.readFileSync(fixtures.inputJpg); var inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);
var jpegSuite = new Benchmark.Suite('jpeg-linear'); var jpegSuite = new Benchmark.Suite('jpeg');
// jimp // jimp
jpegSuite.add('jimp-buffer-buffer', { jpegSuite.add('jimp-buffer-buffer', {
defer: true, defer: true,
@@ -93,7 +90,7 @@ async.series({
if (err) { if (err) {
throw err; throw err;
} }
image.resize(width, height, 'linear', function (err, image) { image.resize(width, height, 'lanczos', function (err, image) {
if (err) { if (err) {
throw err; throw err;
} }
@@ -113,7 +110,7 @@ async.series({
if (err) { if (err) {
throw err; throw err;
} }
image.resize(width, height, 'linear', function (err, image) { image.resize(width, height, 'lanczos', function (err, image) {
if (err) { if (err) {
throw err; throw err;
} }
@@ -140,7 +137,7 @@ async.series({
width: width, width: width,
height: height, height: height,
format: 'jpg', format: 'jpg',
filter: magickFilterBilinear filter: 'Lanczos'
}, function(err) { }, function(err) {
if (err) { if (err) {
throw err; throw err;
@@ -161,7 +158,7 @@ async.series({
width: width, width: width,
height: height, height: height,
format: 'JPEG', format: 'JPEG',
filter: magickFilterBilinear filter: 'Lanczos'
}, function (err, buffer) { }, function (err, buffer) {
if (err) { if (err) {
throw err; throw err;
@@ -179,7 +176,7 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
gm(inputJpgBuffer) gm(inputJpgBuffer)
.resize(width, height) .resize(width, height)
.filter(magickFilterBilinear) .filter('Lanczos')
.quality(80) .quality(80)
.write(fixtures.outputJpg, function (err) { .write(fixtures.outputJpg, function (err) {
if (err) { if (err) {
@@ -194,7 +191,7 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
gm(inputJpgBuffer) gm(inputJpgBuffer)
.resize(width, height) .resize(width, height)
.filter(magickFilterBilinear) .filter('Lanczos')
.quality(80) .quality(80)
.toBuffer(function (err, buffer) { .toBuffer(function (err, buffer) {
if (err) { if (err) {
@@ -210,7 +207,7 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
gm(fixtures.inputJpg) gm(fixtures.inputJpg)
.resize(width, height) .resize(width, height)
.filter(magickFilterBilinear) .filter('Lanczos')
.quality(80) .quality(80)
.write(fixtures.outputJpg, function (err) { .write(fixtures.outputJpg, function (err) {
if (err) { if (err) {
@@ -225,7 +222,7 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
gm(fixtures.inputJpg) gm(fixtures.inputJpg)
.resize(width, height) .resize(width, height)
.filter(magickFilterBilinear) .filter('Lanczos')
.quality(80) .quality(80)
.toBuffer(function (err, buffer) { .toBuffer(function (err, buffer) {
if (err) { if (err) {
@@ -243,7 +240,6 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.toFile(fixtures.outputJpg, function(err) { .toFile(fixtures.outputJpg, function(err) {
if (err) { if (err) {
throw err; throw err;
@@ -257,7 +253,6 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.toBuffer(function(err, buffer) { .toBuffer(function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
@@ -272,7 +267,6 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(width, height) .resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.toFile(fixtures.outputJpg, function(err) { .toFile(fixtures.outputJpg, function(err) {
if (err) { if (err) {
throw err; throw err;
@@ -290,8 +284,7 @@ async.series({
deferred.resolve(); deferred.resolve();
}); });
var pipeline = sharp() var pipeline = sharp()
.resize(width, height) .resize(width, height);
.interpolateWith(sharp.interpolator.bilinear);
readable.pipe(pipeline).pipe(writable); readable.pipe(pipeline).pipe(writable);
} }
}).add('sharp-file-buffer', { }).add('sharp-file-buffer', {
@@ -299,7 +292,6 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(width, height) .resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.toBuffer(function(err, buffer) { .toBuffer(function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
@@ -314,19 +306,27 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.toBuffer() .toBuffer()
.then(function(buffer) { .then(function(buffer) {
assert.notStrictEqual(null, buffer); assert.notStrictEqual(null, buffer);
deferred.resolve(); deferred.resolve();
}); });
} }
}).add('sharp-sharpen-mild', { }).on('cycle', function(event) {
console.log('jpeg ' + String(event.target));
}).on('complete', function() {
callback(null, this.filter('fastest').map('name'));
}).run();
},
// Effect of applying operations
operations: function(callback) {
var inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);
var operationsSuite = new Benchmark.Suite('operations');
operationsSuite.add('sharp-sharpen-mild', {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.sharpen() .sharpen()
.toBuffer(function(err, buffer) { .toBuffer(function(err, buffer) {
if (err) { if (err) {
@@ -342,7 +342,6 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.sharpen(3, 1, 3) .sharpen(3, 1, 3)
.toBuffer(function(err, buffer) { .toBuffer(function(err, buffer) {
if (err) { if (err) {
@@ -358,7 +357,6 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.blur() .blur()
.toBuffer(function(err, buffer) { .toBuffer(function(err, buffer) {
if (err) { if (err) {
@@ -374,7 +372,6 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.blur(3) .blur(3)
.toBuffer(function(err, buffer) { .toBuffer(function(err, buffer) {
if (err) { if (err) {
@@ -390,7 +387,6 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.gamma() .gamma()
.toBuffer(function(err, buffer) { .toBuffer(function(err, buffer) {
if (err) { if (err) {
@@ -406,7 +402,6 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.normalise() .normalise()
.toBuffer(function(err, buffer) { .toBuffer(function(err, buffer) {
if (err) { if (err) {
@@ -422,7 +417,6 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.greyscale() .greyscale()
.toBuffer(function(err, buffer) { .toBuffer(function(err, buffer) {
if (err) { if (err) {
@@ -438,7 +432,6 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.gamma() .gamma()
.greyscale() .greyscale()
.toBuffer(function(err, buffer) { .toBuffer(function(err, buffer) {
@@ -455,7 +448,6 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.progressive() .progressive()
.toBuffer(function(err, buffer) { .toBuffer(function(err, buffer) {
if (err) { if (err) {
@@ -471,7 +463,6 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.withoutChromaSubsampling() .withoutChromaSubsampling()
.toBuffer(function(err, buffer) { .toBuffer(function(err, buffer) {
if (err) { if (err) {
@@ -487,7 +478,6 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.rotate(90) .rotate(90)
.interpolateWith(sharp.interpolator.bilinear)
.resize(width, height) .resize(width, height)
.toBuffer(function(err, buffer) { .toBuffer(function(err, buffer) {
if (err) { if (err) {
@@ -503,8 +493,6 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
sharp.simd(false); sharp.simd(false);
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.rotate(90)
.interpolateWith(sharp.interpolator.bilinear)
.resize(width, height) .resize(width, height)
.toBuffer(function(err, buffer) { .toBuffer(function(err, buffer) {
sharp.simd(true); sharp.simd(true);
@@ -520,9 +508,8 @@ async.series({
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.sequentialRead() .sequentialRead()
.resize(width, height)
.toBuffer(function(err, buffer) { .toBuffer(function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
@@ -533,127 +520,19 @@ async.series({
}); });
} }
}).on('cycle', function(event) { }).on('cycle', function(event) {
console.log('jpeg-linear ' + String(event.target)); console.log('operations ' + String(event.target));
}).on('complete', function() { }).on('complete', function() {
callback(null, this.filter('fastest').map('name')); callback(null, this.filter('fastest').map('name'));
}).run(); }).run();
}, },
// Comparitive speed of kernels
'jpeg-cubic': function(callback) { kernels: function(callback) {
var inputJpgBuffer = fs.readFileSync(fixtures.inputJpg); var inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);
var jpegSuite = new Benchmark.Suite('jpeg-cubic'); (new Benchmark.Suite('kernels')).add('sharp-cubic', {
// lwip
if (typeof lwip !== 'undefined') {
jpegSuite.add('lwip-file-file', {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
lwip.open(fixtures.inputJpg, function (err, image) { sharp(inputJpgBuffer)
if (err) { .resize(width, height, { kernel: 'cubic' })
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();
});
});
});
}
});
}
// imagemagick
jpegSuite.add('imagemagick-file-file', {
defer: true,
fn: function(deferred) {
imagemagick.resize({
srcPath: fixtures.inputJpg,
dstPath: fixtures.outputJpg,
quality: 0.8,
width: width,
height: height,
format: 'jpg',
filter: magickFilterBicubic
}, function(err) {
if (err) {
throw err;
} else {
deferred.resolve();
}
});
}
});
// imagemagick-native
if (typeof imagemagickNative !== 'undefined') {
jpegSuite.add('imagemagick-native-buffer-buffer', {
defer: true,
fn: function(deferred) {
imagemagickNative.convert({
srcData: inputJpgBuffer,
quality: 80,
width: width,
height: height,
format: 'JPEG',
filter: magickFilterBicubic
}, function (err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
});
}
// gm
jpegSuite.add('gm-buffer-file', {
defer: true,
fn: function(deferred) {
gm(inputJpgBuffer)
.resize(width, height)
.filter(magickFilterBicubic)
.quality(80)
.write(fixtures.outputJpg, function (err) {
if (err) {
throw err;
} else {
deferred.resolve();
}
});
}
}).add('gm-buffer-buffer', {
defer: true,
fn: function(deferred) {
gm(inputJpgBuffer)
.resize(width, height)
.filter(magickFilterBicubic)
.quality(80)
.toBuffer(function(err, buffer) { .toBuffer(function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
@@ -663,28 +542,11 @@ async.series({
} }
}); });
} }
}).add('gm-file-file', { }).add('sharp-lanczos2', {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
gm(fixtures.inputJpg) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height, { kernel: 'lanczos2' })
.filter(magickFilterBicubic)
.quality(80)
.write(fixtures.outputJpg, function (err) {
if (err) {
throw err;
} else {
deferred.resolve();
}
});
}
}).add('gm-file-buffer', {
defer: true,
fn: function(deferred) {
gm(fixtures.inputJpg)
.resize(width, height)
.filter(magickFilterBicubic)
.quality(80)
.toBuffer(function(err, buffer) { .toBuffer(function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
@@ -694,182 +556,11 @@ async.series({
} }
}); });
} }
}); }).add('sharp-lanczos3', {
// sharp
jpegSuite.add('sharp-buffer-file', {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height, { kernel: 'lanczos3' })
.interpolateWith(sharp.interpolator.bicubic)
.toFile(fixtures.outputJpg, function(err) {
if (err) {
throw err;
} else {
deferred.resolve();
}
});
}
}).add('sharp-buffer-buffer', {
defer: true,
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.bicubic)
.toBuffer(function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add('sharp-file-file', {
defer: true,
fn: function(deferred) {
sharp(fixtures.inputJpg)
.resize(width, height)
.interpolateWith(sharp.interpolator.bicubic)
.toFile(fixtures.outputJpg, function(err) {
if (err) {
throw err;
} else {
deferred.resolve();
}
});
}
}).add('sharp-stream-stream', {
defer: true,
fn: function(deferred) {
var readable = fs.createReadStream(fixtures.inputJpg);
var writable = fs.createWriteStream(fixtures.outputJpg);
writable.on('finish', function() {
deferred.resolve();
});
var pipeline = sharp()
.resize(width, height)
.interpolateWith(sharp.interpolator.bicubic);
readable.pipe(pipeline).pipe(writable);
}
}).add('sharp-file-buffer', {
defer: true,
fn: function(deferred) {
sharp(fixtures.inputJpg)
.resize(width, height)
.interpolateWith(sharp.interpolator.bicubic)
.toBuffer(function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add('sharp-promise', {
defer: true,
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.bicubic)
.toBuffer()
.then(function(buffer) {
assert.notStrictEqual(null, buffer);
deferred.resolve();
});
}
}).on('cycle', function(event) {
console.log('jpeg-cubic ' + String(event.target));
}).on('complete', function() {
callback(null, this.filter('fastest').map('name'));
}).run();
},
// Comparitive speed of pixel interpolators
interpolators: function(callback) {
var inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);
(new Benchmark.Suite('interpolators')).add('sharp-nearest-neighbour', {
defer: true,
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.nearest)
.toBuffer(function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add('sharp-bilinear', {
defer: true,
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.toBuffer(function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add('sharp-vertexSplitQuadraticBasisSpline', {
defer: true,
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.vertexSplitQuadraticBasisSpline)
.toBuffer(function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add('sharp-bicubic', {
defer: true,
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.bicubic)
.toBuffer(function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add('sharp-locallyBoundedBicubic', {
defer: true,
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.locallyBoundedBicubic)
.toBuffer(function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add('sharp-nohalo', {
defer: true,
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.nohalo)
.toBuffer(function(err, buffer) { .toBuffer(function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
@@ -880,12 +571,12 @@ async.series({
}); });
} }
}).on('cycle', function(event) { }).on('cycle', function(event) {
console.log('interpolators ' + String(event.target)); console.log('kernels ' + String(event.target));
}).on('complete', function() { }).on('complete', function() {
callback(null, this.filter('fastest').map('name')); callback(null, this.filter('fastest').map('name'));
}).run(); }).run();
}, },
// PNG
png: function(callback) { png: function(callback) {
var inputPngBuffer = fs.readFileSync(fixtures.inputPng); var inputPngBuffer = fs.readFileSync(fixtures.inputPng);
var pngSuite = new Benchmark.Suite('png'); var pngSuite = new Benchmark.Suite('png');
@@ -938,7 +629,7 @@ async.series({
if (err) { if (err) {
throw err; throw err;
} }
image.resize(width, height, 'linear', function (err, image) { image.resize(width, height, 'lanczos', function (err, image) {
if (err) { if (err) {
throw err; throw err;
} }
@@ -963,7 +654,7 @@ async.series({
dstPath: fixtures.outputPng, dstPath: fixtures.outputPng,
width: width, width: width,
height: height, height: height,
filter: magickFilterBilinear filter: 'Lanczos'
}, function(err) { }, function(err) {
if (err) { if (err) {
throw err; throw err;
@@ -983,7 +674,7 @@ async.series({
width: width, width: width,
height: height, height: height,
format: 'PNG', format: 'PNG',
filter: magickFilterBilinear filter: 'Lanczos'
}); });
deferred.resolve(); deferred.resolve();
} }
@@ -995,7 +686,7 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
gm(fixtures.inputPng) gm(fixtures.inputPng)
.resize(width, height) .resize(width, height)
.filter(magickFilterBilinear) .filter('Lanczos')
.write(fixtures.outputPng, function (err) { .write(fixtures.outputPng, function (err) {
if (err) { if (err) {
throw err; throw err;
@@ -1009,7 +700,7 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
gm(fixtures.inputPng) gm(fixtures.inputPng)
.resize(width, height) .resize(width, height)
.filter(magickFilterBilinear) .filter('Lanczos')
.toBuffer(function (err, buffer) { .toBuffer(function (err, buffer) {
if (err) { if (err) {
throw err; throw err;
@@ -1026,7 +717,6 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
sharp(inputPngBuffer) sharp(inputPngBuffer)
.resize(width, height) .resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.toFile(fixtures.outputPng, function(err) { .toFile(fixtures.outputPng, function(err) {
if (err) { if (err) {
throw err; throw err;
@@ -1040,7 +730,6 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
sharp(inputPngBuffer) sharp(inputPngBuffer)
.resize(width, height) .resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.toBuffer(function(err, buffer) { .toBuffer(function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
@@ -1055,7 +744,6 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
sharp(fixtures.inputPng) sharp(fixtures.inputPng)
.resize(width, height) .resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.toFile(fixtures.outputPng, function(err) { .toFile(fixtures.outputPng, function(err) {
if (err) { if (err) {
throw err; throw err;
@@ -1069,7 +757,6 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
sharp(fixtures.inputPng) sharp(fixtures.inputPng)
.resize(width, height) .resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.toBuffer(function(err, buffer) { .toBuffer(function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
@@ -1084,7 +771,6 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
sharp(inputPngBuffer) sharp(inputPngBuffer)
.resize(width, height) .resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.progressive() .progressive()
.toBuffer(function(err, buffer) { .toBuffer(function(err, buffer) {
if (err) { if (err) {
@@ -1100,7 +786,6 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
sharp(inputPngBuffer) sharp(inputPngBuffer)
.resize(width, height) .resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.withoutAdaptiveFiltering() .withoutAdaptiveFiltering()
.toBuffer(function(err, buffer) { .toBuffer(function(err, buffer) {
if (err) { if (err) {
@@ -1118,7 +803,7 @@ async.series({
callback(null, this.filter('fastest').map('name')); callback(null, this.filter('fastest').map('name'));
}).run(); }).run();
}, },
// WebP
webp: function(callback) { webp: function(callback) {
var inputWebPBuffer = fs.readFileSync(fixtures.inputWebP); var inputWebPBuffer = fs.readFileSync(fixtures.inputWebP);
(new Benchmark.Suite('webp')).add('sharp-buffer-file', { (new Benchmark.Suite('webp')).add('sharp-buffer-file', {
@@ -1126,7 +811,6 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
sharp(inputWebPBuffer) sharp(inputWebPBuffer)
.resize(width, height) .resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.toFile(fixtures.outputWebP, function(err) { .toFile(fixtures.outputWebP, function(err) {
if (err) { if (err) {
throw err; throw err;
@@ -1140,7 +824,6 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
sharp(inputWebPBuffer) sharp(inputWebPBuffer)
.resize(width, height) .resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.toBuffer(function(err, buffer) { .toBuffer(function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
@@ -1155,7 +838,6 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
sharp(fixtures.inputWebP) sharp(fixtures.inputWebP)
.resize(width, height) .resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.toFile(fixtures.outputWebP, function(err) { .toFile(fixtures.outputWebP, function(err) {
if (err) { if (err) {
throw err; throw err;
@@ -1169,7 +851,6 @@ async.series({
fn: function(deferred) { fn: function(deferred) {
sharp(fixtures.inputWebp) sharp(fixtures.inputWebp)
.resize(width, height) .resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.toBuffer(function(err, buffer) { .toBuffer(function(err, buffer) {
if (err) { if (err) {
throw err; throw err;

View File

@@ -8,14 +8,12 @@ var Benchmark = require('benchmark');
var sharp = require('../../index'); var sharp = require('../../index');
var fixtures = require('../fixtures'); var fixtures = require('../fixtures');
sharp.cache(false);
sharp.simd(true); sharp.simd(true);
var min = 320; var min = 320;
var max = 960; var max = 960;
// Nearest equivalent to bilinear
var magickFilter = 'Triangle';
var randomDimension = function() { var randomDimension = function() {
return Math.ceil(Math.random() * (max - min) + min); return Math.ceil(Math.random() * (max - min) + min);
}; };
@@ -30,7 +28,7 @@ new Benchmark.Suite('random').add('imagemagick', {
width: randomDimension(), width: randomDimension(),
height: randomDimension(), height: randomDimension(),
format: 'jpg', format: 'jpg',
filter: magickFilter filter: 'Lanczos'
}, function(err) { }, function(err) {
if (err) { if (err) {
throw err; throw err;
@@ -44,7 +42,7 @@ new Benchmark.Suite('random').add('imagemagick', {
fn: function(deferred) { fn: function(deferred) {
gm(fixtures.inputJpg) gm(fixtures.inputJpg)
.resize(randomDimension(), randomDimension()) .resize(randomDimension(), randomDimension())
.filter(magickFilter) .filter('Lanczos')
.quality(80) .quality(80)
.toBuffer(function (err, buffer) { .toBuffer(function (err, buffer) {
if (err) { if (err) {
@@ -58,7 +56,9 @@ new Benchmark.Suite('random').add('imagemagick', {
}).add('sharp', { }).add('sharp', {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp(fixtures.inputJpg).resize(randomDimension(), randomDimension()).toBuffer(function(err, buffer) { sharp(fixtures.inputJpg)
.resize(randomDimension(), randomDimension())
.toBuffer(function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -72,5 +72,4 @@ new Benchmark.Suite('random').add('imagemagick', {
}).on('complete', function() { }).on('complete', function() {
var winner = this.filter('fastest').map('name'); var winner = this.filter('fastest').map('name');
assert.strictEqual('sharp', String(winner), 'sharp was slower than ' + winner); assert.strictEqual('sharp', String(winner), 'sharp was slower than ' + winner);
console.dir(sharp.cache());
}).run(); }).run();

BIN
test/fixtures/expected/crop-entropy.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

BIN
test/fixtures/expected/crop-entropy.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 755 B

After

Width:  |  Height:  |  Size: 988 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 827 B

After

Width:  |  Height:  |  Size: 999 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 703 B

BIN
test/fixtures/expected/extend-equal.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 840 B

After

Width:  |  Height:  |  Size: 839 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 621 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 574 B

After

Width:  |  Height:  |  Size: 716 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 938 B

After

Width:  |  Height:  |  Size: 823 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 15 KiB

BIN
test/fixtures/flowers.jpeg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

BIN
test/fixtures/giant-image.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
test/fixtures/grey-plus-alpha.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 B

View File

@@ -18,7 +18,6 @@ var fingerprint = function(image, callback) {
.normalise() .normalise()
.resize(9, 8) .resize(9, 8)
.ignoreAspectRatio() .ignoreAspectRatio()
.interpolateWith(sharp.interpolator.vertexSplitQuadraticBasisSpline)
.raw() .raw()
.toBuffer(function(err, data) { .toBuffer(function(err, data) {
if (err) { if (err) {
@@ -66,6 +65,7 @@ module.exports = {
inputJpgWithCmykNoProfile: getPath('Channel_digital_image_CMYK_color_no_profile.jpg'), inputJpgWithCmykNoProfile: getPath('Channel_digital_image_CMYK_color_no_profile.jpg'),
inputJpgWithCorruptHeader: getPath('corrupt-header.jpg'), inputJpgWithCorruptHeader: getPath('corrupt-header.jpg'),
inputJpgWithLowContrast: getPath('low-contrast.jpg'), // http://www.flickr.com/photos/grizdave/2569067123/ inputJpgWithLowContrast: getPath('low-contrast.jpg'), // http://www.flickr.com/photos/grizdave/2569067123/
inputJpgLarge: getPath('giant-image.jpg'),
inputPng: getPath('50020484-00001.png'), // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png inputPng: getPath('50020484-00001.png'), // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png
inputPngWithTransparency: getPath('blackbug.png'), // public domain inputPngWithTransparency: getPath('blackbug.png'), // public domain
@@ -84,11 +84,13 @@ module.exports = {
inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
inputTiff: getPath('G31D.TIF'), // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm inputTiff: getPath('G31D.TIF'), // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm
inputGif: getPath('Crash_test.gif'), // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif inputGif: getPath('Crash_test.gif'), // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif
inputGifGreyPlusAlpha: getPath('grey-plus-alpha.gif'), // http://i.imgur.com/gZ5jlmE.gif
inputSvg: getPath('check.svg'), // http://dev.w3.org/SVG/tools/svgweb/samples/svg-files/check.svg inputSvg: getPath('check.svg'), // http://dev.w3.org/SVG/tools/svgweb/samples/svg-files/check.svg
inputPsd: getPath('free-gearhead-pack.psd'), // https://dribbble.com/shots/1624241-Free-Gearhead-Vector-Pack
inputSvs: getPath('CMU-1-Small-Region.svs'), // http://openslide.cs.cmu.edu/download/openslide-testdata/Aperio/CMU-1-Small-Region.svs inputSvs: getPath('CMU-1-Small-Region.svs'), // http://openslide.cs.cmu.edu/download/openslide-testdata/Aperio/CMU-1-Small-Region.svs
inputJPGBig: getPath('flowers.jpeg'),
outputJpg: getPath('output.jpg'), outputJpg: getPath('output.jpg'),
outputPng: getPath('output.png'), outputPng: getPath('output.png'),
outputWebP: getPath('output.webp'), outputWebP: getPath('output.webp'),

View File

@@ -1,48 +0,0 @@
# Interpolators
[Photo](https://www.flickr.com/photos/aotaro/21978966091) by
[aotaro](https://www.flickr.com/photos/aotaro/) is licensed under
[CC BY 2.0](https://creativecommons.org/licenses/by/2.0/).
The following examples take the 4608x3072px original image
and resize to 480x320px using various interpolators.
To fetch the original 4608x3072px image and
generate the interpolator sample images:
```sh
curl -O https://farm6.staticflickr.com/5682/21978966091_b421afe866_o.jpg
node generate.js
```
## Nearest neighbour
![Nearest neighbour interpolation](nearest.jpg)
## Bilinear
![Bilinear interpolation](bilinear.jpg)
## Bicubic
![Bicubic interpolation](bicubic.jpg)
## Locally bounded bicubic
![Locally bounded bicubic interpolation](lbb.jpg)
## Vertex-split quadratic b-splines (VSQBS)
![Vertex-split quadratic b-splines interpolation](vsqbs.jpg)
## Nohalo
![Nohalo interpolation](nohalo.jpg)
## GraphicsMagick
![GraphicsMagick](gm.jpg)
```sh
gm convert 21978966091_b421afe866_o.jpg -resize 480x320^ -gravity center -extent 480x320 -quality 95 -strip -define jpeg:optimize-coding=true gm.jpg
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

View File

@@ -1,11 +0,0 @@
'use strict';
['nearest', 'bilinear', 'bicubic', 'vsqbs', 'lbb', 'nohalo'].forEach(function(interpolator) {
require('../../')('21978966091_b421afe866_o.jpg')
.resize(480, 320)
.interpolateWith(interpolator)
.quality(95)
.toFile(interpolator + '.jpg', function(err) {
if (err) throw err;
});
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

View File

@@ -83,6 +83,54 @@
... ...
fun:WebPEncodeRGB fun:WebPEncodeRGB
} }
{
value_libwebp_WebPEncode
Memcheck:Value8
...
fun:WebPEncode
}
{
cond_libwebp_WebPEncode
Memcheck:Cond
...
fun:WebPEncode
}
{
value_libwebp_WebPPictureImportRGBA
Memcheck:Value8
...
fun:WebPPictureImportRGBA
}
{
cond_libwebp_WebPPictureImportRGBA
Memcheck:Cond
...
fun:WebPPictureImportRGBA
}
{
value_libwebp_WebPPictureImportRGB
Memcheck:Value8
...
fun:WebPPictureImportRGB
}
{
cond_libwebp_WebPPictureImportRGB
Memcheck:Cond
...
fun:WebPPictureImportRGB
}
{
value_libwebp_WebPDecode
Memcheck:Value8
...
fun:WebPDecode
}
{
cond_libwebp_WebPDecode
Memcheck:Cond
...
fun:WebPDecode
}
# libvips # libvips
{ {
@@ -102,7 +150,21 @@
cond_libvips_col_sRGB2scRGB_8 cond_libvips_col_sRGB2scRGB_8
Memcheck:Value8 Memcheck:Value8
fun:vips_col_sRGB2scRGB_8 fun:vips_col_sRGB2scRGB_8
fun:vips_sRGB2scRGB_gen }
{
cond_libvips_vips_region_fill
Memcheck:Cond
...
fun:vips_region_fill
fun:vips_region_prepare
}
{
leak_libvips_init
Memcheck:Leak
match-leak-kinds: reachable
fun:malloc
...
fun:vips__init
} }
# libuv warnings # libuv warnings
@@ -199,6 +261,13 @@
... ...
fun:_ZN4node17CreateEnvironmentEPN2v87IsolateEP9uv_loop_sNS0_5LocalINS0_7ContextEEEiPKPKciSB_ fun:_ZN4node17CreateEnvironmentEPN2v87IsolateEP9uv_loop_sNS0_5LocalINS0_7ContextEEEiPKPKciSB_
} }
{
leak_nodejs_icu_getAvailableLocales
Memcheck:Leak
match-leak-kinds: possible
...
fun:_ZN6icu_568Collator19getAvailableLocalesERi
}
{ {
leak_nan_FunctionCallbackInfo leak_nan_FunctionCallbackInfo
Memcheck:Leak Memcheck:Leak
@@ -248,59 +317,31 @@
... ...
fun:_ZN2v88internal12_GLOBAL__N_117CreateICUCollatorEPNS0_7IsolateERKN6icu_556LocaleENS0_6HandleINS0_8JSObjectEEE fun:_ZN2v88internal12_GLOBAL__N_117CreateICUCollatorEPNS0_7IsolateERKN6icu_556LocaleENS0_6HandleINS0_8JSObjectEEE
} }
# vips__init warnings
{ {
leak_libvips_init leak_v8_CallInterfaceDescriptorData
Memcheck:Leak Memcheck:Leak
match-leak-kinds: reachable match-leak-kinds: possible
fun:malloc
... ...
fun:vips__init fun:_ZN2v88internal27CallInterfaceDescriptorData26InitializePlatformSpecificEiPNS0_8RegisterEPNS0_27PlatformInterfaceDescriptorE
} }
# *magick warnings
{ {
leak_magick_read leak_v8_InitializePlatformSpecific14
Memcheck:Leak Memcheck:Leak
match-leak-kinds: definite,indirect,possible match-leak-kinds: possible
... ...
fun:ReadImage fun:_ZN2v88internal14LoadDescriptor26InitializePlatformSpecificEPNS0_27CallInterfaceDescriptorDataE
...
fun:vips__magick_read
} }
{ {
leak_magick_read_header leak_v8_InitializePlatformSpecific15
Memcheck:Leak Memcheck:Leak
match-leak-kinds: definite,indirect,possible match-leak-kinds: possible
... ...
fun:ReadImage fun:_ZN2v88internal15StoreDescriptor26InitializePlatformSpecificEPNS0_27CallInterfaceDescriptorDataE
...
fun:vips__magick_read_header
} }
{ {
cond_magick_is_palette_image leak_v8_Malloced
Memcheck:Cond
fun:IsPaletteImage
...
fun:get_bands
}
{
value_magick_is_palette_image
Memcheck:Value8
fun:IsPaletteImage
...
fun:get_bands
}
# glib g_file_read_link
# https://github.com/GNOME/glib/commit/49a5d0f6f2aed99cd78f25655f137f4448e47d92
{
leak_g_file_read_link
Memcheck:Leak Memcheck:Leak
match-leak-kinds: definite,indirect,possible match-leak-kinds: possible
... ...
fun:g_file_read_link fun:_ZN2v88internal8Malloced3NewEm
...
fun:vips_gsf_path
} }

View File

@@ -45,15 +45,17 @@ describe('Alpha transparency', function() {
}); });
it('Flatten 16-bit PNG with transparency to orange', function(done) { it('Flatten 16-bit PNG with transparency to orange', function(done) {
var output = fixtures.path('output.flatten-rgb16-orange.jpg');
sharp(fixtures.inputPngWithTransparency16bit) sharp(fixtures.inputPngWithTransparency16bit)
.flatten() .flatten()
.background({r: 255, g: 102, b: 0}) .background({r: 255, g: 102, b: 0})
.toBuffer(function(err, data, info) { .toFile(output, function(err, info) {
if (err) throw err; if (err) throw err;
assert.strictEqual(true, info.size > 0); assert.strictEqual(true, info.size > 0);
assert.strictEqual(32, info.width); assert.strictEqual(32, info.width);
assert.strictEqual(32, info.height); assert.strictEqual(32, info.height);
fixtures.assertSimilar(fixtures.expected('flatten-rgb16-orange.jpg'), data, done); fixtures.assertMaxColourDistance(output, fixtures.expected('flatten-rgb16-orange.jpg'), 25);
done();
}); });
}); });

View File

@@ -57,7 +57,7 @@ describe('Clone', function() {
var rotator = sharp().rotate(90); var rotator = sharp().rotate(90);
// Cloned instances with differing dimensions // Cloned instances with differing dimensions
rotator.clone().resize(320, 240).pipe(writable1); rotator.clone().resize(320, 240).pipe(writable1);
rotator.clone().resize(100).pipe(writable2); rotator.clone().resize(100, 122).pipe(writable2);
// Go // Go
fs.createReadStream(fixtures.inputJpg).pipe(rotator); fs.createReadStream(fixtures.inputJpg).pipe(rotator);
}); });

View File

@@ -19,18 +19,15 @@ describe('cpplint', function() {
// Lint each source file // Lint each source file
cpplint({ cpplint({
files: [file], files: [file],
linelength: 140, linelength: 120,
filters: { filters: {
legal: { legal: {
copyright: false copyright: false
}, },
build: { build: {
include: false, include: false
include_order: false
}, },
whitespace: { whitespace: {
blank_line: false,
comments: false,
parens: false parens: false
} }
} }

View File

@@ -5,9 +5,9 @@ var assert = require('assert');
var sharp = require('../../index'); var sharp = require('../../index');
var fixtures = require('../fixtures'); var fixtures = require('../fixtures');
describe('Crop gravities', function() { describe('Crop', function() {
var testSettings = [ [
{ {
name: 'North', name: 'North',
width: 320, width: 320,
@@ -50,6 +50,13 @@ describe('Crop gravities', function() {
gravity: sharp.gravity.centre, gravity: sharp.gravity.centre,
fixture: 'gravity-centre.jpg' fixture: 'gravity-centre.jpg'
}, },
{
name: 'Default (centre)',
width: 80,
height: 320,
gravity: undefined,
fixture: 'gravity-centre.jpg'
},
{ {
name: 'Northeast', name: 'Northeast',
width: 320, width: 320,
@@ -106,10 +113,8 @@ describe('Crop gravities', function() {
gravity: sharp.gravity.northwest, gravity: sharp.gravity.northwest,
fixture: 'gravity-west.jpg' fixture: 'gravity-west.jpg'
} }
]; ].forEach(function(settings) {
it(settings.name + ' gravity', function(done) {
testSettings.forEach(function(settings) {
it(settings.name, function(done) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(settings.width, settings.height) .resize(settings.width, settings.height)
.crop(settings.gravity) .crop(settings.gravity)
@@ -122,7 +127,7 @@ describe('Crop gravities', function() {
}); });
}); });
it('allows specifying the gravity as a string', function(done) { it('Allows specifying the gravity as a string', function(done) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(80, 320) .resize(80, 320)
.crop('east') .crop('east')
@@ -134,36 +139,57 @@ describe('Crop gravities', function() {
}); });
}); });
it('Invalid number', function() { it('Invalid values fail', function() {
assert.throws(function() { assert.throws(function() {
sharp(fixtures.inputJpg).crop(9); sharp().crop(9);
});
assert.throws(function() {
sharp().crop(1.1);
});
assert.throws(function() {
sharp().crop(-1);
});
assert.throws(function() {
sharp().crop('zoinks');
}); });
}); });
it('Invalid string', function() { it('Uses default value when none specified', function() {
assert.throws(function() {
sharp(fixtures.inputJpg).crop('yadda');
});
});
it('does not throw if crop gravity is undefined', function() {
assert.doesNotThrow(function() { assert.doesNotThrow(function() {
sharp(fixtures.inputJpg).crop(); sharp().crop();
}); });
}); });
it('defaults crop gravity to sharp.gravity.center', function(done) { describe('Entropy-based strategy', function() {
var centerGravitySettings = testSettings.filter(function (settings) {
return settings.name === 'Center'; it('JPEG', function(done) {
})[0]; sharp(fixtures.inputJpgWithCmykProfile)
sharp(fixtures.inputJpg) .resize(80, 320)
.resize(centerGravitySettings.width, centerGravitySettings.height) .crop(sharp.strategy.entropy)
.crop()
.toBuffer(function(err, data, info) { .toBuffer(function(err, data, info) {
if (err) throw err; if (err) throw err;
assert.strictEqual(centerGravitySettings.width, info.width); assert.strictEqual('jpeg', info.format);
assert.strictEqual(centerGravitySettings.height, info.height); assert.strictEqual(3, info.channels);
fixtures.assertSimilar(fixtures.expected(centerGravitySettings.fixture), data, done); assert.strictEqual(80, info.width);
assert.strictEqual(320, info.height);
fixtures.assertSimilar(fixtures.expected('crop-entropy.jpg'), data, done);
}); });
}); });
it('PNG', function(done) {
sharp(fixtures.inputPngWithTransparency)
.resize(320, 80)
.crop(sharp.strategy.entropy)
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual('png', info.format);
assert.strictEqual(4, info.channels);
assert.strictEqual(320, info.width);
assert.strictEqual(80, info.height);
fixtures.assertSimilar(fixtures.expected('crop-entropy.png'), data, done);
});
});
});
}); });

View File

@@ -88,6 +88,22 @@ describe('Embed', function() {
}); });
}); });
it('PNG with 2 channels', function(done) {
sharp(fixtures.inputPngWithGreyAlpha)
.resize(32, 16)
.embed()
.background({r: 0, g: 0, b: 0, a: 0})
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('png', info.format);
assert.strictEqual(32, info.width);
assert.strictEqual(16, info.height);
assert.strictEqual(4, info.channels);
fixtures.assertSimilar(fixtures.expected('embed-2channel.png'), data, done);
});
});
it('Enlarge and embed', function(done) { it('Enlarge and embed', function(done) {
sharp(fixtures.inputPngWithOneColor) sharp(fixtures.inputPngWithOneColor)
.embed() .embed()

52
test/unit/extend.js Normal file
View File

@@ -0,0 +1,52 @@
'use strict';
var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
describe('Extend', function () {
it('extend all sides equally with RGB', function(done) {
sharp(fixtures.inputJpg)
.resize(120)
.background({r: 255, g: 0, b: 0})
.extend(10)
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(140, info.width);
assert.strictEqual(118, info.height);
fixtures.assertSimilar(fixtures.expected('extend-equal.jpg'), data, done);
});
});
it('extend sides unequally with RGBA', function(done) {
sharp(fixtures.inputPngWithTransparency16bit)
.resize(120)
.background({r: 0, g: 0, b: 0, a: 0})
.extend({top: 50, bottom: 0, left: 10, right: 35})
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(165, info.width);
assert.strictEqual(170, info.height);
fixtures.assertSimilar(fixtures.expected('extend-unequal.png'), data, done);
});
});
it('missing parameter fails', function() {
assert.throws(function() {
sharp().extend();
});
});
it('negative fails', function() {
assert.throws(function() {
sharp().extend(-1);
});
});
it('partial object fails', function() {
assert.throws(function() {
sharp().extend({top: 1});
});
});
});

View File

@@ -6,29 +6,6 @@ var sharp = require('../../index');
var fixtures = require('../fixtures'); var fixtures = require('../fixtures');
describe('Partial image extraction', function() { describe('Partial image extraction', function() {
describe('using the legacy extract(top,left,width,height) syntax', function () {
it('JPEG', function(done) {
sharp(fixtures.inputJpg)
.extract(2, 2, 20, 20)
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(20, info.width);
assert.strictEqual(20, info.height);
fixtures.assertSimilar(fixtures.expected('extract.jpg'), data, done);
});
});
it('PNG', function(done) {
sharp(fixtures.inputPng)
.extract(300, 200, 400, 200)
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(400, info.width);
assert.strictEqual(200, info.height);
fixtures.assertSimilar(fixtures.expected('extract.png'), data, done);
});
});
});
it('JPEG', function(done) { it('JPEG', function(done) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)

View File

@@ -38,7 +38,7 @@ describe('Gamma correction', function() {
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);
assert.strictEqual(129, info.width); assert.strictEqual(129, info.width);
assert.strictEqual(111, info.height); assert.strictEqual(111, info.height);
fixtures.assertSimilar(fixtures.expected('gamma-3.0.jpg'), data, done); fixtures.assertSimilar(fixtures.expected('gamma-3.0.jpg'), data, { threshold: 6 }, done);
}); });
}); });
@@ -49,7 +49,7 @@ describe('Gamma correction', function() {
.toBuffer(function(err, data, info) { .toBuffer(function(err, data, info) {
assert.strictEqual('png', info.format); assert.strictEqual('png', info.format);
assert.strictEqual(320, info.width); assert.strictEqual(320, info.width);
fixtures.assertSimilar(fixtures.expected('gamma-alpha.jpg'), data, done); fixtures.assertSimilar(fixtures.expected('gamma-alpha.jpg'), data, { threshold: 11 }, done);
}); });
}); });

View File

@@ -5,100 +5,67 @@ var assert = require('assert');
var sharp = require('../../index'); var sharp = require('../../index');
var fixtures = require('../fixtures'); var fixtures = require('../fixtures');
describe('Interpolation', function() { describe('Interpolators and kernels', function() {
it('nearest neighbour', function(done) { describe('Reducers', function() {
[
sharp.kernel.cubic,
sharp.kernel.lanczos2,
sharp.kernel.lanczos3
].forEach(function(kernel) {
it(kernel, function(done) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320, 240) .resize(320, null, { kernel: kernel })
.interpolateWith(sharp.interpolator.nearest)
.toBuffer(function(err, data, info) { .toBuffer(function(err, data, info) {
if (err) throw err; if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width); assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height); fixtures.assertSimilar(fixtures.inputJpg, data, done);
done(); });
});
}); });
}); });
it('bilinear', function(done) { describe('Enlargers', function() {
[
sharp.interpolator.nearest,
sharp.interpolator.bilinear,
sharp.interpolator.bicubic,
sharp.interpolator.nohalo,
sharp.interpolator.locallyBoundedBicubic,
sharp.interpolator.vertexSplitQuadraticBasisSpline
].forEach(function(interpolator) {
it(interpolator, function(done) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320, 240) .resize(320, null, { interpolator: interpolator })
.interpolateWith(sharp.interpolator.bilinear)
.toBuffer(function(err, data, info) { .toBuffer(function(err, data, info) {
if (err) throw err; if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width); assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height); fixtures.assertSimilar(fixtures.inputJpg, data, done);
done(); });
});
}); });
}); });
it('bicubic', function(done) { it('unknown kernel throws', function() {
sharp(fixtures.inputJpg) assert.throws(function() {
.resize(320, 240) sharp().resize(null, null, { kernel: 'unknown' });
.interpolateWith(sharp.interpolator.bicubic)
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
done();
}); });
}); });
it('nohalo', function(done) { it('unknown interpolator throws', function() {
sharp(fixtures.inputJpg) assert.throws(function() {
.resize(320, 240) sharp().resize(null, null, { interpolator: 'unknown' });
.interpolateWith(sharp.interpolator.nohalo)
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
done();
}); });
}); });
it('locally bounded bicubic (LBB)', function(done) { describe('deprecated interpolateWith method still works', function() {
sharp(fixtures.inputJpg) it('resize then interpolateWith', function() {
.resize(320, 240) sharp().resize(1, 1).interpolateWith('bicubic');
.interpolateWith(sharp.interpolator.locallyBoundedBicubic) });
.toBuffer(function(err, data, info) { it('interpolateWith then resize', function() {
if (err) throw err; sharp().interpolateWith('bicubic').resize(1, 1);
assert.strictEqual(true, data.length > 0);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
done();
}); });
}); });
it('vertex split quadratic basis spline (VSQBS)', function(done) {
sharp(fixtures.inputJpg)
.resize(320, 240)
.interpolateWith(sharp.interpolator.vertexSplitQuadraticBasisSpline)
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
done();
});
});
it('unknown interpolator throws', function(done) {
var isValid = false;
try {
sharp().interpolateWith('nonexistant');
isValid = true;
} catch (e) {}
assert(!isValid);
done();
});
}); });

View File

@@ -109,6 +109,26 @@ describe('Input/output', function() {
readable.pipe(pipeline).pipe(writable); readable.pipe(pipeline).pipe(writable);
}); });
it('Stream should emit info event', function(done) {
var readable = fs.createReadStream(fixtures.inputJpg);
var writable = fs.createWriteStream(fixtures.outputJpg);
var pipeline = sharp().resize(320, 240);
var infoEventEmitted = false;
pipeline.on('info', function(info) {
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
assert.strictEqual(3, info.channels);
infoEventEmitted = true;
});
writable.on('finish', function() {
assert.strictEqual(true, infoEventEmitted);
fs.unlinkSync(fixtures.outputJpg);
done();
});
readable.pipe(pipeline).pipe(writable);
});
it('Handle Stream to Stream error ', function(done) { it('Handle Stream to Stream error ', function(done) {
var pipeline = sharp().resize(320, 240); var pipeline = sharp().resize(320, 240);
var anErrorWasEmitted = false; var anErrorWasEmitted = false;
@@ -648,22 +668,25 @@ describe('Input/output', function() {
}); });
}); });
if (sharp.format.magick.input.file) { if (sharp.format.svg.input.file) {
it('Convert SVG to PNG at default 72DPI', function(done) { it('Convert SVG to PNG at default 72DPI', function(done) {
sharp(fixtures.inputSvg) sharp(fixtures.inputSvg)
.resize(1024) .resize(1024)
.extract({left: 290, top: 760, width: 40, height: 40}) .extract({left: 290, top: 760, width: 40, height: 40})
.toFormat('png') .toFormat('png')
.toBuffer(function(err, data, info) { .toBuffer(function(err, data, info) {
if (err) { if (err) throw err;
assert.strictEqual(0, err.message.indexOf('Input file is missing or of an unsupported image format'));
done();
} else {
assert.strictEqual('png', info.format); assert.strictEqual('png', info.format);
assert.strictEqual(40, info.width); assert.strictEqual(40, info.width);
assert.strictEqual(40, info.height); assert.strictEqual(40, info.height);
fixtures.assertSimilar(fixtures.expected('svg72.png'), data, done); fixtures.assertSimilar(fixtures.expected('svg72.png'), data, function(err) {
} if (err) throw err;
sharp(data).metadata(function(err, info) {
if (err) throw err;
assert.strictEqual(72, info.density);
done();
});
});
}); });
}); });
it('Convert SVG to PNG at 300DPI', function(done) { it('Convert SVG to PNG at 300DPI', function(done) {
@@ -672,33 +695,20 @@ describe('Input/output', function() {
.extract({left: 290, top: 760, width: 40, height: 40}) .extract({left: 290, top: 760, width: 40, height: 40})
.toFormat('png') .toFormat('png')
.toBuffer(function(err, data, info) { .toBuffer(function(err, data, info) {
if (err) { if (err) throw err;
assert.strictEqual(0, err.message.indexOf('Input file is missing or of an unsupported image format'));
done();
} else {
assert.strictEqual('png', info.format); assert.strictEqual('png', info.format);
assert.strictEqual(40, info.width); assert.strictEqual(40, info.width);
assert.strictEqual(40, info.height); assert.strictEqual(40, info.height);
fixtures.assertSimilar(fixtures.expected('svg1200.png'), data, done); fixtures.assertSimilar(fixtures.expected('svg1200.png'), data, function(err) {
}
});
});
}
if (sharp.format.magick.input.file) {
it('Convert PSD to PNG', function(done) {
sharp(fixtures.inputPsd)
.resize(320, 240)
.toFormat(sharp.format.png)
.toFile(fixtures.path('output.psd.png'), function(err, info) {
if (err) throw err; if (err) throw err;
assert.strictEqual(true, info.size > 0); sharp(data).metadata(function(err, info) {
assert.strictEqual('png', info.format); if (err) throw err;
assert.strictEqual(320, info.width); assert.strictEqual(1200, info.density);
assert.strictEqual(240, info.height);
done(); done();
}); });
}); });
});
});
} }
if (sharp.format.tiff.input.buffer) { if (sharp.format.tiff.input.buffer) {
@@ -719,7 +729,7 @@ describe('Input/output', function() {
}); });
} }
if (sharp.format.magick.input.buffer) { if (sharp.format.gif.input.buffer) {
it('Load GIF from Buffer', function(done) { it('Load GIF from Buffer', function(done) {
var inputGifBuffer = fs.readFileSync(fixtures.inputGif); var inputGifBuffer = fs.readFileSync(fixtures.inputGif);
sharp(inputGifBuffer) sharp(inputGifBuffer)
@@ -737,6 +747,24 @@ describe('Input/output', function() {
}); });
} }
if (sharp.format.gif.input.file) {
it('Load GIF grey+alpha from file', function(done) {
sharp(fixtures.inputGifGreyPlusAlpha)
.resize(8, 4)
.png()
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual(data.length, info.size);
assert.strictEqual('png', info.format);
assert.strictEqual(8, info.width);
assert.strictEqual(4, info.height);
assert.strictEqual(4, info.channels);
done();
});
});
}
if (sharp.format.openslide.input.file) { if (sharp.format.openslide.input.file) {
it('Load Aperio SVS file via Openslide', function(done) { it('Load Aperio SVS file via Openslide', function(done) {
sharp(fixtures.inputSvs) sharp(fixtures.inputSvs)
@@ -842,6 +870,25 @@ describe('Input/output', function() {
}); });
}); });
it('Disabling limit works', function(done) {
sharp(fixtures.inputJpgLarge)
.limitInputPixels(false)
.resize(2)
.toBuffer(function(err) {
assert.strictEqual(true, !err);
done();
});
});
it('Enabling default limit works and fails with a large image', function(done) {
sharp(fixtures.inputJpgLarge)
.limitInputPixels(true)
.toBuffer(function(err) {
assert.strictEqual(true, !!err);
done();
});
});
it('Smaller than input fails', function(done) { it('Smaller than input fails', function(done) {
sharp(fixtures.inputJpg).metadata(function(err, metadata) { sharp(fixtures.inputJpg).metadata(function(err, metadata) {
if (err) throw err; if (err) throw err;
@@ -978,4 +1025,32 @@ describe('Input/output', function() {
}); });
}); });
it('Info event data', function(done) {
var readable = fs.createReadStream(fixtures.inputJPGBig);
var inPipeline = sharp()
.resize(840, 472)
.raw()
.on('info', function(info) {
assert.strictEqual(840, info.width);
assert.strictEqual(472, info.height);
assert.strictEqual(3, info.channels);
});
var badPipeline = sharp(null, {raw: {width: 840, height: 500, channels: 3}})
.toFormat('jpeg')
.toBuffer(function(err, data, info) {
assert.strictEqual(err.message.indexOf('memory area too small') > 0, true);
readable = fs.createReadStream(fixtures.inputJPGBig);
var goodPipeline = sharp(null, {raw: {width: 840, height: 472, channels: 3}})
.toFormat('jpeg')
.toBuffer(function(err, data, info) {
if (err) throw err;
done();
});
inPipeline = sharp()
.resize(840, 472)
.raw();
readable.pipe(inPipeline).pipe(goodPipeline);
});
readable.pipe(inPipeline).pipe(badPipeline);
});
}); });

View File

@@ -18,6 +18,7 @@ describe('Image metadata', function() {
assert.strictEqual(2225, metadata.height); assert.strictEqual(2225, metadata.height);
assert.strictEqual('srgb', metadata.space); assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels); assert.strictEqual(3, metadata.channels);
assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation); assert.strictEqual('undefined', typeof metadata.orientation);
@@ -35,6 +36,7 @@ describe('Image metadata', function() {
assert.strictEqual(600, metadata.height); assert.strictEqual(600, metadata.height);
assert.strictEqual('srgb', metadata.space); assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels); assert.strictEqual(3, metadata.channels);
assert.strictEqual(72, metadata.density);
assert.strictEqual(true, metadata.hasProfile); assert.strictEqual(true, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual(8, metadata.orientation); assert.strictEqual(8, metadata.orientation);
@@ -64,6 +66,7 @@ describe('Image metadata', function() {
assert.strictEqual(3248, metadata.height); assert.strictEqual(3248, metadata.height);
assert.strictEqual('b-w', metadata.space); assert.strictEqual('b-w', metadata.space);
assert.strictEqual(1, metadata.channels); assert.strictEqual(1, metadata.channels);
assert.strictEqual(300, metadata.density);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation); assert.strictEqual('undefined', typeof metadata.orientation);
@@ -82,6 +85,7 @@ describe('Image metadata', function() {
assert.strictEqual(2074, metadata.height); assert.strictEqual(2074, metadata.height);
assert.strictEqual('b-w', metadata.space); assert.strictEqual('b-w', metadata.space);
assert.strictEqual(1, metadata.channels); assert.strictEqual(1, metadata.channels);
assert.strictEqual(300, metadata.density);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation); assert.strictEqual('undefined', typeof metadata.orientation);
@@ -99,6 +103,7 @@ describe('Image metadata', function() {
assert.strictEqual(1536, metadata.height); assert.strictEqual(1536, metadata.height);
assert.strictEqual('srgb', metadata.space); assert.strictEqual('srgb', metadata.space);
assert.strictEqual(4, metadata.channels); assert.strictEqual(4, metadata.channels);
assert.strictEqual(72, metadata.density);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(true, metadata.hasAlpha); assert.strictEqual(true, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation); assert.strictEqual('undefined', typeof metadata.orientation);
@@ -117,6 +122,7 @@ describe('Image metadata', function() {
assert.strictEqual(772, metadata.height); assert.strictEqual(772, metadata.height);
assert.strictEqual('srgb', metadata.space); assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels); assert.strictEqual(3, metadata.channels);
assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation); assert.strictEqual('undefined', typeof metadata.orientation);
@@ -127,16 +133,33 @@ describe('Image metadata', function() {
}); });
} }
if (sharp.format.magick.input.file) { if (sharp.format.gif.input.file) {
it('GIF via libmagick', function(done) { it('GIF via giflib', function(done) {
sharp(fixtures.inputGif).metadata(function(err, metadata) { sharp(fixtures.inputGif).metadata(function(err, metadata) {
if (err) throw err; if (err) throw err;
assert.strictEqual('magick', metadata.format); assert.strictEqual('gif', metadata.format);
assert.strictEqual(800, metadata.width); assert.strictEqual(800, metadata.width);
assert.strictEqual(533, metadata.height); assert.strictEqual(533, metadata.height);
assert.strictEqual(3, metadata.channels); assert.strictEqual(4, metadata.channels);
assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(true, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
assert.strictEqual('undefined', typeof metadata.exif);
assert.strictEqual('undefined', typeof metadata.icc);
done();
});
});
it('GIF grey+alpha via giflib', function(done) {
sharp(fixtures.inputGifGreyPlusAlpha).metadata(function(err, metadata) {
if (err) throw err;
assert.strictEqual('gif', metadata.format);
assert.strictEqual(2, metadata.width);
assert.strictEqual(1, metadata.height);
assert.strictEqual(4, metadata.channels);
assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(true, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation); assert.strictEqual('undefined', typeof metadata.orientation);
assert.strictEqual('undefined', typeof metadata.exif); assert.strictEqual('undefined', typeof metadata.exif);
assert.strictEqual('undefined', typeof metadata.icc); assert.strictEqual('undefined', typeof metadata.icc);
@@ -153,6 +176,7 @@ describe('Image metadata', function() {
assert.strictEqual(2220, metadata.width); assert.strictEqual(2220, metadata.width);
assert.strictEqual(2967, metadata.height); assert.strictEqual(2967, metadata.height);
assert.strictEqual(4, metadata.channels); assert.strictEqual(4, metadata.channels);
assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual('rgb', metadata.space); assert.strictEqual('rgb', metadata.space);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(true, metadata.hasAlpha); assert.strictEqual(true, metadata.hasAlpha);
@@ -171,6 +195,7 @@ describe('Image metadata', function() {
assert.strictEqual(2225, metadata.height); assert.strictEqual(2225, metadata.height);
assert.strictEqual('srgb', metadata.space); assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels); assert.strictEqual(3, metadata.channels);
assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation); assert.strictEqual('undefined', typeof metadata.orientation);
@@ -198,6 +223,7 @@ describe('Image metadata', function() {
assert.strictEqual(2225, metadata.height); assert.strictEqual(2225, metadata.height);
assert.strictEqual('srgb', metadata.space); assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels); assert.strictEqual(3, metadata.channels);
assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation); assert.strictEqual('undefined', typeof metadata.orientation);
@@ -219,6 +245,7 @@ describe('Image metadata', function() {
assert.strictEqual(2225, metadata.height); assert.strictEqual(2225, metadata.height);
assert.strictEqual('srgb', metadata.space); assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels); assert.strictEqual(3, metadata.channels);
assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation); assert.strictEqual('undefined', typeof metadata.orientation);
@@ -238,6 +265,7 @@ describe('Image metadata', function() {
assert.strictEqual(2225, metadata.height); assert.strictEqual(2225, metadata.height);
assert.strictEqual('srgb', metadata.space); assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels); assert.strictEqual(3, metadata.channels);
assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation); assert.strictEqual('undefined', typeof metadata.orientation);
@@ -325,9 +353,14 @@ describe('Image metadata', function() {
sharp().withMetadata({orientation: -1}); sharp().withMetadata({orientation: -1});
}); });
}); });
it('Zero orientation', function () {
assert.throws(function () {
sharp().withMetadata({ orientation: 0 });
});
});
it('Too large orientation', function() { it('Too large orientation', function() {
assert.throws(function() { assert.throws(function() {
sharp().withMetadata({orientation: 8}); sharp().withMetadata({orientation: 9});
}); });
}); });
}); });

View File

@@ -1,5 +1,6 @@
'use strict'; 'use strict';
var fs = require('fs');
var assert = require('assert'); var assert = require('assert');
var fixtures = require('../fixtures'); var fixtures = require('../fixtures');
var sharp = require('../../index'); var sharp = require('../../index');
@@ -17,7 +18,7 @@ var getPaths = function(baseName, extension) {
// Test // Test
describe('Overlays', function() { describe('Overlays', function() {
it('Overlay transparent PNG on solid background', function(done) { it('Overlay transparent PNG file on solid background', function(done) {
var paths = getPaths('alpha-layer-01'); var paths = getPaths('alpha-layer-01');
sharp(fixtures.inputPngOverlayLayer0) sharp(fixtures.inputPngOverlayLayer0)
@@ -29,6 +30,18 @@ describe('Overlays', function() {
}); });
}); });
it('Overlay transparent PNG Buffer on solid background', function(done) {
var paths = getPaths('alpha-layer-01');
sharp(fixtures.inputPngOverlayLayer0)
.overlayWith(fs.readFileSync(fixtures.inputPngOverlayLayer1))
.toFile(paths.actual, function (error) {
if (error) return done(error);
fixtures.assertMaxColourDistance(paths.actual, paths.expected);
done();
});
});
it('Overlay low-alpha transparent PNG on solid background', function(done) { it('Overlay low-alpha transparent PNG on solid background', function(done) {
var paths = getPaths('alpha-layer-01-low-alpha'); var paths = getPaths('alpha-layer-01-low-alpha');
@@ -141,18 +154,19 @@ describe('Overlays', function() {
}); });
} }
it('Fail when compositing images with different dimensions', function(done) { it('Fail when overlay does not contain alpha channel', function(done) {
sharp(fixtures.inputJpg) sharp(fixtures.inputPngOverlayLayer1)
.overlayWith(fixtures.inputPngWithGreyAlpha) .overlayWith(fixtures.inputJpg)
.toBuffer(function(error) { .toBuffer(function(error) {
assert.strictEqual(true, error instanceof Error); assert.strictEqual(true, error instanceof Error);
done(); done();
}); });
}); });
it('Fail when compositing non-PNG image', function(done) { it('Fail when overlay is larger', function(done) {
sharp(fixtures.inputPngOverlayLayer1) sharp(fixtures.inputJpg)
.overlayWith(fixtures.inputJpg) .resize(320)
.overlayWith(fixtures.inputPngOverlayLayer1)
.toBuffer(function(error) { .toBuffer(function(error) {
assert.strictEqual(true, error instanceof Error); assert.strictEqual(true, error instanceof Error);
done(); done();
@@ -170,4 +184,62 @@ describe('Overlays', function() {
sharp().overlayWith(1); sharp().overlayWith(1);
}); });
}); });
it('Fail with unsupported gravity', function() {
assert.throws(function() {
sharp()
.overlayWith(fixtures.inputPngOverlayLayer1, {
gravity: 9
});
});
});
it('Empty options', function() {
assert.doesNotThrow(function() {
sharp().overlayWith(fixtures.inputPngOverlayLayer1, {});
});
});
describe('Overlay with numeric gravity', function() {
Object.keys(sharp.gravity).forEach(function(gravity) {
it(gravity, function(done) {
var expected = fixtures.expected('overlay-gravity-' + gravity + '.jpg');
sharp(fixtures.inputJpg)
.resize(80)
.overlayWith(fixtures.inputPngWithTransparency16bit, {
gravity: gravity
})
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
assert.strictEqual(80, info.width);
assert.strictEqual(65, info.height);
assert.strictEqual(3, info.channels);
fixtures.assertSimilar(expected, data, done);
});
});
});
});
describe('Overlay with string-based gravity', function() {
Object.keys(sharp.gravity).forEach(function(gravity) {
it(gravity, function(done) {
var expected = fixtures.expected('overlay-gravity-' + gravity + '.jpg');
sharp(fixtures.inputJpg)
.resize(80)
.overlayWith(fixtures.inputPngWithTransparency16bit, {
gravity: sharp.gravity[gravity]
})
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
assert.strictEqual(80, info.width);
assert.strictEqual(65, info.height);
assert.strictEqual(3, info.channels);
fixtures.assertSimilar(expected, data, done);
});
});
});
});
}); });

View File

@@ -35,6 +35,40 @@ describe('Rotation', function() {
}); });
}); });
it('Rotate by 270 degrees, square output ignoring aspect ratio', function(done) {
sharp(fixtures.inputJpg)
.resize(240, 240)
.ignoreAspectRatio()
.rotate(270)
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(240, info.width);
assert.strictEqual(240, info.height);
sharp(data).metadata(function(err, metadata) {
assert.strictEqual(240, metadata.width);
assert.strictEqual(240, metadata.height);
done();
});
});
});
it('Rotate by 270 degrees, rectangular output ignoring aspect ratio', function(done) {
sharp(fixtures.inputJpg)
.resize(320, 240)
.ignoreAspectRatio()
.rotate(270)
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
sharp(data).metadata(function(err, metadata) {
assert.strictEqual(320, metadata.width);
assert.strictEqual(240, metadata.height);
done();
});
});
});
it('Input image has Orientation EXIF tag but do not rotate output', function(done) { it('Input image has Orientation EXIF tag but do not rotate output', function(done) {
sharp(fixtures.inputJpgWithExif) sharp(fixtures.inputJpgWithExif)
.resize(320) .resize(320)

View File

@@ -7,10 +7,10 @@ var fixtures = require('../fixtures');
describe('Sharpen', function() { describe('Sharpen', function() {
it('specific radius 10', function(done) { it('specific radius 10 (sigma 6)', function(done) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320, 240) .resize(320, 240)
.sharpen(10) .sharpen(6)
.toBuffer(function(err, data, info) { .toBuffer(function(err, data, info) {
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width); assert.strictEqual(320, info.width);
@@ -19,10 +19,10 @@ describe('Sharpen', function() {
}); });
}); });
it('specific radius 3 and levels 0.5, 2.5', function(done) { it('specific radius 3 (sigma 1.5) and levels 0.5, 2.5', function(done) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320, 240) .resize(320, 240)
.sharpen(3, 0.5, 2.5) .sharpen(1.5, 0.5, 2.5)
.toBuffer(function(err, data, info) { .toBuffer(function(err, data, info) {
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width); assert.strictEqual(320, info.width);
@@ -31,10 +31,10 @@ describe('Sharpen', function() {
}); });
}); });
it('specific radius 5 and levels 2, 4', function(done) { it('specific radius 5 (sigma 3.5) and levels 2, 4', function(done) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320, 240) .resize(320, 240)
.sharpen(5, 2, 4) .sharpen(3.5, 2, 4)
.toBuffer(function(err, data, info) { .toBuffer(function(err, data, info) {
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width); assert.strictEqual(320, info.width);
@@ -55,9 +55,9 @@ describe('Sharpen', function() {
}); });
}); });
it('invalid radius', function() { it('invalid sigma', function() {
assert.throws(function() { assert.throws(function() {
sharp(fixtures.inputJpg).sharpen(1.5); sharp(fixtures.inputJpg).sharpen(-1.5);
}); });
}); });

View File

@@ -6,6 +6,7 @@ var assert = require('assert');
var async = require('async'); var async = require('async');
var rimraf = require('rimraf'); var rimraf = require('rimraf');
var unzip = require('unzip');
var sharp = require('../../index'); var sharp = require('../../index');
var fixtures = require('../fixtures'); var fixtures = require('../fixtures');
@@ -47,137 +48,106 @@ var assertDeepZoomTiles = function(directory, expectedSize, expectedLevels, done
describe('Tile', function() { describe('Tile', function() {
describe('Invalid tile values', function() { it('Valid size values pass', function() {
it('size - NaN', function(done) { [1, 8192].forEach(function(size) {
var isValid = true; assert.doesNotThrow(function() {
try { sharp().tile({
sharp().tile('zoinks'); size: size
} catch (err) { });
isValid = false; });
} });
assert.strictEqual(false, isValid);
done();
}); });
it('size - float', function(done) { it('Invalid size values fail', function() {
var isValid = true; ['zoinks', 1.1, -1, 0, 8193].forEach(function(size) {
try { assert.throws(function() {
sharp().tile(1.1); sharp().tile({
} catch (err) { size: size
isValid = false; });
} });
assert.strictEqual(false, isValid); });
done();
}); });
it('size - negative', function(done) { it('Valid overlap values pass', function() {
var isValid = true; [0, 8192].forEach(function(overlap) {
try { assert.doesNotThrow(function() {
sharp().tile(-1); sharp().tile({
} catch (err) { size: 8192,
isValid = false; overlap: overlap
} });
assert.strictEqual(false, isValid); });
done(); });
}); });
it('size - zero', function(done) { it('Invalid overlap values fail', function() {
var isValid = true; ['zoinks', 1.1, -1, 8193].forEach(function(overlap) {
try { assert.throws(function() {
sharp().tile(0); sharp().tile({
} catch (err) { overlap: overlap
isValid = false; });
} });
assert.strictEqual(false, isValid); });
done();
}); });
it('size - too large', function(done) { it('Valid container values pass', function() {
var isValid = true; ['fs', 'zip'].forEach(function(container) {
try { assert.doesNotThrow(function() {
sharp().tile(8193); sharp().tile({
} catch (err) { container: container
isValid = false; });
} });
assert.strictEqual(false, isValid); });
done();
}); });
it('overlap - NaN', function(done) { it('Invalid container values fail', function() {
var isValid = true; ['zoinks', 1].forEach(function(container) {
try { assert.throws(function() {
sharp().tile(null, 'zoinks'); sharp().tile({
} catch (err) { container: container
isValid = false; });
} });
assert.strictEqual(false, isValid); });
done();
}); });
it('overlap - float', function(done) { it('Valid layout values pass', function() {
var isValid = true; ['dz', 'google', 'zoomify'].forEach(function(layout) {
try { assert.doesNotThrow(function() {
sharp().tile(null, 1.1); sharp().tile({
} catch (err) { layout: layout
isValid = false; });
} });
assert.strictEqual(false, isValid); });
done();
}); });
it('overlap - negative', function(done) { it('Invalid layout values fail', function() {
var isValid = true; ['zoinks', 1].forEach(function(layout) {
try { assert.throws(function() {
sharp().tile(null, -1); sharp().tile({
} catch (err) { layout: layout
isValid = false; });
} });
assert.strictEqual(false, isValid); });
done();
}); });
it('overlap - too large', function(done) { it('Prevent larger overlap than default size', function() {
var isValid = true; assert.throws(function() {
try { sharp().tile({overlap: 257});
sharp().tile(null, 8193); });
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
}); });
it('overlap - larger than default size', function(done) { it('Prevent larger overlap than provided size', function() {
var isValid = true; assert.throws(function() {
try { sharp().tile({size: 512, overlap: 513});
sharp().tile(null, 257);
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
}); });
it('overlap - larger than provided size', function(done) {
var isValid = true;
try {
sharp().tile(512, 513);
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
});
}); });
if (sharp.format.dz.output.file) { if (sharp.format.dz.output.file) {
describe('Deep Zoom output', function() {
it('Tile size - 256px default', function(done) { it('Deep Zoom layout', function(done) {
var directory = fixtures.path('output.256_files'); var directory = fixtures.path('output.dz_files');
rimraf(directory, function() { rimraf(directory, function() {
sharp(fixtures.inputJpg).toFile(fixtures.path('output.256.dzi'), function(err, info) { sharp(fixtures.inputJpg)
.toFile(fixtures.path('output.dz.dzi'), function(err, info) {
if (err) throw err; if (err) throw err;
assert.strictEqual('dz', info.format); assert.strictEqual('dz', info.format);
assertDeepZoomTiles(directory, 256, 13, done); assertDeepZoomTiles(directory, 256, 13, done);
@@ -185,10 +155,15 @@ describe('Tile', function() {
}); });
}); });
it('Tile size/overlap - 512/16px', function(done) { it('Deep Zoom layout with custom size+overlap', function(done) {
var directory = fixtures.path('output.512_files'); var directory = fixtures.path('output.dz.512_files');
rimraf(directory, function() { rimraf(directory, function() {
sharp(fixtures.inputJpg).tile(512, 16).toFile(fixtures.path('output.512.dzi'), function(err, info) { sharp(fixtures.inputJpg)
.tile({
size: 512,
overlap: 16
})
.toFile(fixtures.path('output.dz.512.dzi'), function(err, info) {
if (err) throw err; if (err) throw err;
assert.strictEqual('dz', info.format); assert.strictEqual('dz', info.format);
assertDeepZoomTiles(directory, 512 + 2 * 16, 13, done); assertDeepZoomTiles(directory, 512 + 2 * 16, 13, done);
@@ -196,7 +171,98 @@ describe('Tile', function() {
}); });
}); });
it('Zoomify layout', function(done) {
var directory = fixtures.path('output.zoomify');
rimraf(directory, function() {
sharp(fixtures.inputJpg)
.tile({
layout: 'zoomify'
})
.toFile(fixtures.path('output.zoomify.dzi'), function(err, info) {
if (err) throw err;
assert.strictEqual('dz', info.format);
fs.stat(path.join(directory, 'ImageProperties.xml'), function(err, stat) {
if (err) throw err;
assert.strictEqual(true, stat.isFile());
assert.strictEqual(true, stat.size > 0);
done();
}); });
});
});
});
it('Google layout', function(done) {
var directory = fixtures.path('output.google');
rimraf(directory, function() {
sharp(fixtures.inputJpg)
.tile({
layout: 'google'
})
.toFile(fixtures.path('output.google.dzi'), function(err, info) {
if (err) throw err;
assert.strictEqual('dz', info.format);
fs.stat(path.join(directory, '0', '0', '0.jpg'), function(err, stat) {
if (err) throw err;
assert.strictEqual(true, stat.isFile());
assert.strictEqual(true, stat.size > 0);
done();
});
});
});
});
it('Write to ZIP container using file extension', function(done) {
var container = fixtures.path('output.dz.container.zip');
var extractTo = fixtures.path('output.dz.container');
var directory = path.join(extractTo, 'output.dz.container_files');
rimraf(directory, function() {
sharp(fixtures.inputJpg)
.toFile(container, function(err, info) {
if (err) throw err;
assert.strictEqual('dz', info.format);
fs.stat(container, function(err, stat) {
if (err) throw err;
assert.strictEqual(true, stat.isFile());
assert.strictEqual(true, stat.size > 0);
fs.createReadStream(container)
.pipe(unzip.Extract({path: path.dirname(extractTo)}))
.on('error', function(err) { throw err; })
.on('close', function() {
assertDeepZoomTiles(directory, 256, 13, done);
});
});
});
});
});
it('Write to ZIP container using container tile option', function(done) {
var container = fixtures.path('output.dz.containeropt.zip');
var extractTo = fixtures.path('output.dz.containeropt');
var directory = path.join(extractTo, 'output.dz.containeropt_files');
rimraf(directory, function() {
sharp(fixtures.inputJpg)
.tile({
container: 'zip'
})
.toFile(fixtures.path('output.dz.containeropt.dzi'), function(err, info) {
// Vips overrides .dzi extension to .zip used by container var below
if (err) throw err;
assert.strictEqual('dz', info.format);
fs.stat(container, function(err, stat) {
if (err) throw err;
assert.strictEqual(true, stat.isFile());
assert.strictEqual(true, stat.size > 0);
fs.createReadStream(container)
.pipe(unzip.Extract({path: path.dirname(extractTo)}))
.on('error', function(err) { throw err; })
.on('close', function() {
assertDeepZoomTiles(directory, 256, 13, done);
});
});
});
});
});
} }
}); });