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
include
packaging
preinstall.sh

View File

@@ -4,6 +4,10 @@ node_js:
- "0.12"
- "4"
- "5"
- "6"
os:
- linux
- osx
sudo: false
addons:
apt:
@@ -11,7 +15,9 @@ addons:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
env:
CXX=g++-4.8
osx_image: xcode7.3
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:
- 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 |
| ------: | :--------- |
| v0.14.0 | needle |
| v0.15.0 | outfit |
| 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>`.

View File

@@ -1,14 +1,14 @@
# sharp
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.
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.
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
rotation, extraction, compositing and gamma correction are available.

View File

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

View File

@@ -9,7 +9,8 @@
'download_vips': '<!(node -e "require(\'./binding\').download_vips()")'
},
'defines': [
'VIPS_CPLUSPLUS_EXPORTS'
'VIPS_CPLUSPLUS_EXPORTS',
'_ALLOW_KEYWORD_MACROS'
],
'sources': [
'src/libvips/cplusplus/VError.cpp',
@@ -91,7 +92,8 @@
'src/utilities.cc'
],
'defines': [
'_GLIBCXX_USE_CXX11_ABI=0'
'_GLIBCXX_USE_CXX11_ABI=0',
'_ALLOW_KEYWORD_MACROS'
],
'include_dirs': [
'<!(node -e "require(\'nan\')")'
@@ -132,23 +134,33 @@
'<(module_root_dir)/lib/libglib-2.0.so',
'<(module_root_dir)/lib/libgobject-2.0.so',
# Dependencies of dependencies, included for openSUSE support
'<(module_root_dir)/lib/libGraphicsMagick.so',
'<(module_root_dir)/lib/libGraphicsMagickWand.so',
'<(module_root_dir)/lib/libcairo.so',
'<(module_root_dir)/lib/libcroco-0.6.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/libgmodule-2.0.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/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/libpng16.so',
'<(module_root_dir)/lib/librsvg-2.so',
'<(module_root_dir)/lib/libtiff.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/liborc-0.4.so',
'<(module_root_dir)/lib/libz.so',
# Ensure runtime linking is relative to sharp.node
'-Wl,-rpath=\'$${ORIGIN}/../../lib\''
]
@@ -167,6 +179,7 @@
'CLANG_CXX_LIBRARY': 'libc++',
'MACOSX_DEPLOYMENT_TARGET': '10.7',
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
'GCC_ENABLE_CPP_RTTI': 'YES',
'OTHER_CPLUSPLUSFLAGS': [
'-fexceptions',
'-Wall',
@@ -198,12 +211,11 @@
'destination': '<(module_root_dir)/build/Release',
'files': [
'<(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/libcairo-2.dll',
'<(module_root_dir)/lib/libcairo-gobject-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/libexpat-1.dll',
'<(module_root_dir)/lib/libffi-6.dll',
@@ -212,6 +224,7 @@
'<(module_root_dir)/lib/libfreetype-6.dll',
'<(module_root_dir)/lib/libgcc_s_seh-1.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/libglib-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/libjpeg-62.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/libpangocairo-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/libpng16-16.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/libstdc++-6.dll',
'<(module_root_dir)/lib/libtiff-5.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/zlib1.dll'
]

View File

@@ -12,6 +12,9 @@ var tmp = require('os').tmpdir();
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');
// -- Helpers
@@ -75,7 +78,7 @@ module.exports.download_vips = function() {
}
// Arch/platform-specific .tar.gz
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);
if (isFile(tarPath)) {
unpack(tarPath);
@@ -114,13 +117,13 @@ module.exports.use_global_vips = function() {
if (globalVipsVersion) {
useGlobalVips = semver.gte(
globalVipsVersion,
process.env.npm_package_config_libvips
minimumLibvipsVersion
);
}
if (process.platform === 'darwin' && !useGlobalVips) {
if (globalVipsVersion) {
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'
);
} else {

View File

@@ -13,7 +13,7 @@ Constructor to which further methods are chained.
`input`, if present, can be one of:
* 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
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.
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
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])
Fast access to image metadata without decoding any compressed image data.
`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
* `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)
* `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
* `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
* `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.
`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
#### 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.
@@ -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.
#### 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.
`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`.
The default gravity is `center`/`centre`.
* `sharp.gravity` e.g. `sharp.gravity.north`, to crop to an edge or corner, or
* `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
var transformer = sharp()
.resize(300, 200)
.crop(sharp.gravity.north)
.resize(200, 200)
.crop(sharp.strategy.entropy)
.on('error', function(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);
```
@@ -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`.
#### 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
#### extract({ left: left, top: top, width: width, height: height })
@@ -265,7 +300,7 @@ sharp(input)
#### 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.
@@ -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`.
#### 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()
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%.
* `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 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.
* `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%.
#### 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
sharp('input.png')
@@ -379,7 +438,7 @@ sharp('input.png')
.resize(300)
.flatten()
.background('#ff6600')
.overlayWith('overlay.png')
.overlayWith('overlay.png', { gravity: sharp.gravity.southeast } )
.sharpen()
.withMetadata()
.quality(90)
@@ -387,8 +446,8 @@ sharp('input.png')
.toBuffer()
.then(function(outputBuffer) {
// outputBuffer contains upside down, 300px wide, alpha channel flattened
// onto orange background, composited with overlay.png, sharpened,
// with metadata, 90% quality WebP image data. Phew!
// onto orange background, composited with overlay.png with SE gravity,
// sharpened, with metadata, 90% quality WebP image data. Phew!
});
```
@@ -466,23 +525,33 @@ 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.
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.
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.
* `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
sharp('input.tiff').tile(256).toFile('output.dzi', function(err, info) {
// The output.dzi file is the XML format Deep Zoom definition
// The output_files directory contains 256x256 pixel tiles grouped by zoom level
});
sharp('input.tiff')
.tile({
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
});
```
#### withoutChromaSubsampling()
@@ -551,9 +620,6 @@ for example:
tiff: { id: 'tiff',
input: { file: true, buffer: true, stream: true },
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',
input: { file: false, buffer: false, stream: false },
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.
```javascript
> 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' }
console.log(sharp.versions);
```
### Utilities

View File

@@ -1,7 +1,93 @@
# 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*"
Requires libvips v8.2.2
#### v0.13.1 - 27<sup>th</sup> February 2016
* Fix embedding onto transparent backgrounds; regression introduced in v0.13.0.
@@ -55,6 +141,8 @@
### v0.12 - "*look*"
Requires libvips v8.2.0
#### v0.12.2 - 16<sup>th</sup> January 2016
* Upgrade libvips to v8.2.0 for improved vips_shrink.

View File

@@ -1,14 +1,14 @@
# sharp
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.
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.
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
rotation, extraction, compositing and gamma correction are available.
@@ -23,8 +23,7 @@ to install the libvips dependency.
### Formats
This module supports reading JPEG, PNG, WebP, TIFF, OpenSlide,
GIF and most other libmagick-supported formats.
This module supports reading JPEG, PNG, WebP, TIFF, GIF and SVG images.
Output images can be in JPEG, PNG and WebP formats as well as uncompressed raw pixel data.
@@ -89,12 +88,16 @@ the help and code contributions of the following people:
* [Alaric Holloway](https://github.com/skedastik)
* [Bernhard K. Weisshuhn](https://github.com/bkw)
* [David A. Carley](https://github.com/dacarley)
* [John Tobin](https://github.com/jtobinisaniceguy)
* [Kenton Gray](https://github.com/kentongray)
* [Felix Bünemann](https://github.com/felixbuenemann)
* [Samy Al Zahrani](https://github.com/salzhrani)
Thank you!
### 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");
you may not use this file except in compliance with the License.

View File

@@ -6,7 +6,7 @@ npm install sharp
### 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)
### 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)
[![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`.
This involves an automated HTTPS download of approximately 6MB.
libvips and its dependencies are fetched and stored within `node_modules/sharp/lib` during `npm install`.
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.:
* 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
* Fedora 21, 22, 23
* Fedora 22, 23
* openSUSE 13.2
* Archlinux 2015.06.01
* 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.
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
libvips and its dependencies can be achieved by running
the following command as a user with `sudo` access
(requires `curl` and `pkg-config`):
```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 -
```
@@ -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
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
[![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.
This can be achieved via homebrew:
@@ -56,10 +71,10 @@ This can be achieved via homebrew:
brew install homebrew/science/vips
```
For GIF input and WebP output suppport use:
For WebP suppport use:
```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
@@ -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.
Only 64-bit (x64) `node.exe` is supported.
The WebP format is currently unavailable on Windows.
### FreeBSD
@@ -113,7 +127,69 @@ docker pull marcbachmann/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
* [gulp-responsive](https://www.npmjs.com/package/gulp-responsive)
* [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

@@ -2,42 +2,45 @@
### Test environment
* AWS EC2 [c4.xlarge](http://aws.amazon.com/ec2/instance-types/#c4) (4x E5-2666 v3 @2.90GHz)
* Amazon Linux 2015.09.1
* Node.js v5.5.0
* AWS EC2 [c4.xlarge](http://aws.amazon.com/ec2/instance-types/#c4) (4x E5-2666 v3 @ 2.90GHz)
* Amazon Linux AMI 2016.03.1 (HVM), SSD Volume Type
* Node.js v6.2.0
### The contenders
* [jimp](https://www.npmjs.com/package/jimp) v0.2.20 - 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.
* [imagemagick-native](https://www.npmjs.com/package/imagemagick-native) @47c7329 - Wrapper around libmagick++, supports Buffers 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.9 - Wrapper around CImg, compiles dependencies from source.
* [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*".
* [gm](https://www.npmjs.com/package/gm) v1.21.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.
* [gm](https://www.npmjs.com/package/gm) v1.22.0 - Fully featured wrapper around GraphicsMagick's `gm` command line utility.
* sharp v0.15.0 / libvips v8.3.1 - Caching within libvips disabled to ensure a fair comparison.
### 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
| Module | Input | Output | Ops/sec | Speed-up |
| :----------------- | :----- | :----- | ------: | -------: |
| jimp (bilinear) | file | file | 1.04 | 1.0 |
| jimp (bilinear) | buffer | buffer | 1.07 | 1.0 |
| lwip | file | file | 1.13 | 1.1 |
| lwip | buffer | buffer | 1.13 | 1.1 |
| imagemagick-native | buffer | buffer | 1.65 | 1.6 |
| imagemagick | file | file | 5.02 | 4.8 |
| gm | buffer | buffer | 5.36 | 5.2 |
| gm | file | file | 5.39 | 5.2 |
| sharp | stream | stream | 22.00 | 21.2 |
| sharp | file | file | 22.87 | 22.0 |
| sharp | file | buffer | 23.03 | 22.1 |
| sharp | buffer | file | 23.10 | 22.2 |
| sharp | buffer | buffer | 23.21 | 22.3 |
| jimp (bilinear) | file | file | 0.94 | 1.0 |
| jimp (bilinear) | buffer | buffer | 0.98 | 1.0 |
| lwip | file | file | 1.14 | 1.2 |
| lwip | buffer | buffer | 1.14 | 1.2 |
| imagemagick-native | buffer | buffer | 1.66 | 1.8 |
| imagemagick | file | file | 5.08 | 5.4 |
| gm | buffer | buffer | 5.43 | 5.7 |
| gm | file | file | 5.46 | 5.8 |
| sharp | stream | stream | 26.52 | 28.2 |
| sharp | file | file | 28.16 | 30.0 |
| sharp | file | buffer | 28.27 | 30.1 |
| sharp | buffer | file | 28.42 | 30.2 |
| 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.

361
index.js
View File

@@ -46,7 +46,7 @@ var Sharp = function(input, options) {
streamIn: false,
sequentialRead: false,
limitInputPixels: maximum.pixels,
density: '72',
density: 72,
rawWidth: 0,
rawHeight: 0,
rawChannels: 0,
@@ -64,19 +64,24 @@ var Sharp = function(input, options) {
width: -1,
height: -1,
canvas: 'crop',
gravity: 0,
crop: 0,
angle: 0,
rotateBeforePreExtract: false,
flip: false,
flop: false,
extendTop: 0,
extendBottom: 0,
extendLeft: 0,
extendRight: 0,
withoutEnlargement: false,
kernel: 'lanczos3',
interpolator: 'bicubic',
// operations
background: [0, 0, 0, 255],
flatten: false,
negate: false,
blurSigma: 0,
sharpenRadius: 0,
sharpenSigma: 0,
sharpenFlat: 1,
sharpenJagged: 2,
threshold: 0,
@@ -84,7 +89,9 @@ var Sharp = function(input, options) {
greyscale: false,
normalize: 0,
// overlay
overlayPath: '',
overlayFileIn: '',
overlayBufferIn: null,
overlayGravity: 0,
// output options
formatOut: 'input',
fileOut: '',
@@ -106,13 +113,13 @@ var Sharp = function(input, options) {
module.exports.queue.emit('change', queueLength);
}
};
if (typeof input === 'string') {
if (isString(input)) {
// input=file
this.options.fileIn = input;
} else if (typeof input === 'object' && input instanceof Buffer) {
} else if (isBuffer(input)) {
// input=buffer
this.options.bufferIn = input;
} else if (typeof input === 'undefined' || input === null) {
} else if (!isDefined(input)) {
// input=stream
this.options.streamIn = true;
} else {
@@ -148,12 +155,27 @@ var isDefined = function(val) {
var isObject = function(val) {
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) {
return typeof val === 'number' && !Number.isNaN(val) && val % 1 === 0;
return isNumber(val) && val % 1 === 0;
};
var inRange = function(val, min, max) {
return val >= min && val <= max;
};
var contains = function(val, list) {
return list.indexOf(val) !== -1;
};
/*
Set input-related options
@@ -164,7 +186,7 @@ Sharp.prototype._inputOptions = function(options) {
// Density
if (isDefined(options.density)) {
if (isInteger(options.density) && inRange(options.density, 1, 2400)) {
this.options.density = options.density.toString();
this.options.density = options.density;
} else {
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 = {
'center': 0,
'centre': 0,
'north': 1,
'east': 2,
'south': 3,
'west': 4,
'northeast': 5,
'southeast': 6,
'southwest': 7,
'northwest': 8
center: 0,
centre: 0,
north: 1,
east: 2,
south: 3,
west: 4,
northeast: 5,
southeast: 6,
southwest: 7,
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';
if (typeof gravity === 'undefined') {
this.options.gravity = module.exports.gravity.center;
} else if (typeof gravity === 'number' && !Number.isNaN(gravity) && gravity >= 0 && gravity <= 8) {
this.options.gravity = gravity;
} else if (typeof gravity === 'string' && typeof module.exports.gravity[gravity] === 'number') {
this.options.gravity = module.exports.gravity[gravity];
if (!isDefined(crop)) {
// Default
this.options.crop = module.exports.gravity.center;
} else if (isInteger(crop) && inRange(crop, 0, 8)) {
// Gravity (numeric)
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 {
throw new Error('Unsupported crop gravity ' + gravity);
throw new Error('Unsupported crop ' + crop);
}
return this;
};
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';
['left', 'top', 'width', 'height'].forEach(function (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;
} else {
throw new Error('Non-integer value for ' + name + ' of ' + value);
@@ -316,14 +343,26 @@ Sharp.prototype.negate = function(negate) {
return this;
};
Sharp.prototype.overlayWith = function(overlayPath) {
if (typeof overlayPath !== 'string') {
throw new Error('The overlay path must be a string');
/*
Overlay with another image, using an optional gravity
*/
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 (overlayPath === '') {
throw new Error('The overlay path cannot be empty');
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);
}
}
this.options.overlayPath = overlayPath;
return this;
};
@@ -374,17 +413,17 @@ Sharp.prototype.withoutEnlargement = function(withoutEnlargement) {
Call with a sigma to use a slower, more accurate Gaussian blur.
*/
Sharp.prototype.blur = function(sigma) {
if (typeof sigma === 'undefined') {
if (!isDefined(sigma)) {
// No arguments: default to mild blur
this.options.blurSigma = -1;
} else if (typeof sigma === 'boolean') {
} else if (isBoolean(sigma)) {
// Boolean argument: apply mild blur?
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
this.options.blurSigma = sigma;
} 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;
};
@@ -393,38 +432,38 @@ Sharp.prototype.blur = function(sigma) {
Sharpen the output image.
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.
radius - size of mask in pixels, must be integer
sigma - sigma of mask
flat - level of "flat" area sharpen, default 1
jagged - level of "jagged" area sharpen, default 2
*/
Sharp.prototype.sharpen = function(radius, flat, jagged) {
if (typeof radius === 'undefined') {
Sharp.prototype.sharpen = function(sigma, flat, jagged) {
if (!isDefined(sigma)) {
// No arguments: default to mild sharpen
this.options.sharpenRadius = -1;
} else if (typeof radius === 'boolean') {
this.options.sharpenSigma = -1;
} else if (isBoolean(sigma)) {
// Boolean argument: apply mild sharpen?
this.options.sharpenRadius = radius ? -1 : 0;
} else if (typeof radius === 'number' && !Number.isNaN(radius) && (radius % 1 === 0) && radius >= 1) {
// Numeric argument: specific radius
this.options.sharpenRadius = radius;
this.options.sharpenSigma = sigma ? -1 : 0;
} else if (isNumber(sigma) && inRange(sigma, 0.01, 10000)) {
// Numeric argument: specific sigma
this.options.sharpenSigma = sigma;
// Control over flat areas
if (typeof flat !== 'undefined' && flat !== null) {
if (typeof flat === 'number' && !Number.isNaN(flat) && flat >= 0) {
if (isDefined(flat)) {
if (isNumber(flat) && inRange(flat, 0, 10000)) {
this.options.sharpenFlat = flat;
} 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
if (typeof jagged !== 'undefined' && jagged !== null) {
if (typeof jagged === 'number' && !Number.isNaN(jagged) && jagged >= 0) {
if (isDefined(jagged)) {
if (isNumber(jagged) && inRange(jagged, 0, 10000)) {
this.options.sharpenJagged = jagged;
} 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 {
throw new Error('Invalid sharpen radius ' + radius + ' (expected integer >= 1)');
throw new Error('Invalid sharpen sigma (0.01 - 10000) ' + sigma);
}
return this;
};
@@ -442,33 +481,6 @@ Sharp.prototype.threshold = function(threshold) {
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).
Improves brightness of resized image in non-linear colour spaces.
@@ -592,12 +604,12 @@ Sharp.prototype.withMetadata = function(withMetadata) {
typeof withMetadata.orientation === 'number' &&
!Number.isNaN(withMetadata.orientation) &&
withMetadata.orientation % 1 === 0 &&
withMetadata.orientation >= 0 &&
withMetadata.orientation <= 7
withMetadata.orientation >= 1 &&
withMetadata.orientation <= 8
) {
this.options.withMetadataOrientation = withMetadata.orientation;
} 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) {
// Size of square tiles, in pixels
if (typeof size !== 'undefined' && size !== null) {
if (!Number.isNaN(size) && size % 1 === 0 && size >= 1 && size <= 8192) {
this.options.tileSize = size;
} else {
throw new Error('Invalid tile size (1 to 8192) ' + size);
}
}
// Overlap of tiles, in pixels
if (typeof overlap !== 'undefined' && overlap !== null) {
if (!Number.isNaN(overlap) && overlap % 1 === 0 && overlap >= 0 && overlap <= 8192) {
if (overlap > this.options.tileSize) {
throw new Error('Tile overlap ' + overlap + ' cannot be larger than tile size ' + this.options.tileSize);
Sharp.prototype.tile = function(tile) {
if (isObject(tile)) {
// Size of square tiles, in pixels
if (isDefined(tile.size)) {
if (isInteger(tile.size) && inRange(tile.size, 1, 8192)) {
this.options.tileSize = tile.size;
} else {
throw new Error('Invalid tile size (1 to 8192) ' + tile.size);
}
}
// Overlap of tiles, in pixels
if (isDefined(tile.overlap)) {
if (isInteger(tile.overlap) && inRange(tile.overlap, 0, 8192)) {
if (tile.overlap > this.options.tileSize) {
throw new Error('Tile overlap ' + tile.overlap + ' cannot be larger than tile size ' + this.options.tileSize);
}
this.options.tileOverlap = tile.overlap;
} else {
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);
}
this.options.tileOverlap = overlap;
} else {
throw new Error('Invalid tile overlap (0 to 8192) ' + overlap);
}
}
return this;
};
Sharp.prototype.resize = function(width, height) {
if (!width) {
this.options.width = -1;
/*
Extend edges
*/
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 {
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;
} else {
throw new Error('Invalid width (1 to ' + maximum.width + ') ' + width);
}
}
if (!height) {
this.options.height = -1;
} 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;
} else {
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;
};
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
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) {
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;
} else {
throw new Error('Invalid pixel limit (1 to ' + maximum.pixels + ') ' + limit);
@@ -783,10 +894,11 @@ Sharp.prototype._pipeline = function(callback) {
if (this.options.streamIn) {
// output=stream, input=stream
this.on('finish', function() {
sharp.pipeline(that.options, function(err, data) {
sharp.pipeline(that.options, function(err, data, info) {
if (err) {
that.emit('error', err);
} else {
that.emit('info', info);
that.push(data);
}
that.push(null);
@@ -794,10 +906,11 @@ Sharp.prototype._pipeline = function(callback) {
});
} else {
// output=stream, input=file/buffer
sharp.pipeline(this.options, function(err, data) {
sharp.pipeline(this.options, function(err, data, info) {
if (err) {
that.emit('error', err);
} else {
that.emit('info', info);
that.push(data);
}
that.push(null);

View File

@@ -1,6 +1,6 @@
{
"name": "sharp",
"version": "0.13.1",
"version": "0.15.0",
"author": "Lovell Fuller <npm@lovell.info>",
"contributors": [
"Pierre Inglebert <pierre.inglebert@gmail.com>",
@@ -18,13 +18,17 @@
"Alaric Holloway <alaric.holloway@gmail.com>",
"Bernhard K. Weisshuhn <bkw@codingforce.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": {
"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-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-packaging": "./packaging/test.sh",
"test-clean": "rm -rf coverage/ test/fixtures/output.* && npm install && npm test"
@@ -39,7 +43,10 @@
"png",
"webp",
"tiff",
"gif",
"svg",
"dzi",
"image",
"resize",
"thumbnail",
"crop",
@@ -47,28 +54,29 @@
"vips"
],
"dependencies": {
"bluebird": "^3.3.3",
"bluebird": "^3.3.5",
"color": "^0.11.1",
"nan": "^2.2.0",
"nan": "^2.2.1",
"semver": "^5.1.0",
"request": "^2.69.0",
"request": "^2.71.0",
"tar": "^2.2.1"
},
"devDependencies": {
"async": "^1.5.2",
"coveralls": "^2.11.8",
"bufferutil": "^1.2.1",
"coveralls": "^2.11.9",
"exif-reader": "^1.0.0",
"icc": "^0.0.2",
"istanbul": "^0.4.2",
"istanbul": "^0.4.3",
"mocha": "^2.4.5",
"mocha-jshint": "^2.3.1",
"node-cpplint": "^0.4.0",
"rimraf": "^2.5.2",
"bufferutil": "^1.2.1"
"unzip": "^0.1.11"
},
"license": "Apache-2.0",
"config": {
"libvips": "8.2.2"
"libvips": "8.3.1"
},
"engines": {
"node": ">=0.10"

View File

@@ -19,18 +19,30 @@ export CXXFLAGS="-O3"
# Dependency version numbers
VERSION_ZLIB=1.2.8
VERSION_FFI=3.2.1
VERSION_GLIB=2.47.5
VERSION_GLIB=2.48.0
VERSION_XML2=2.9.3
VERSION_GSF=1.14.34
VERSION_GSF=1.14.36
VERSION_EXIF=0.6.21
VERSION_LCMS2=2.7
VERSION_GM=1.3.23
VERSION_JPEG=1.4.2
VERSION_JPEG=1.4.90
VERSION_PNG16=1.6.21
VERSION_WEBP=0.5.0
VERSION_TIFF=4.0.6
VERSION_ORC=0.4.24
VERSION_VIPS=8.2.2
VERSION_ORC=0.4.25
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
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
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
./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
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
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
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
./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
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
./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
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
./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
./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
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
./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-jpeg-includes=${TARGET}/include --with-jpeg-libraries=${TARGET}/lib \
&& make install-strip
@@ -117,20 +182,29 @@ rm -rf pkgconfig .libs *.la libvipsCC*
# Create JSON file of version numbers
cd ${TARGET}
echo "{\n\
\"cairo\": \"${VERSION_CAIRO}\",\n\
\"croco\": \"${VERSION_CROCO}\",\n\
\"exif\": \"${VERSION_EXIF}\",\n\
\"ffi\": \"${VERSION_FFI}\",\n\
\"fontconfig\": \"${VERSION_FONTCONFIG}\",\n\
\"freetype\": \"${VERSION_FREETYPE}\",\n\
\"gdkpixbuf\": \"${VERSION_GDKPIXBUF}\",\n\
\"gif\": \"${VERSION_GIF}\",\n\
\"glib\": \"${VERSION_GLIB}\",\n\
\"gsf\": \"${VERSION_GSF}\",\n\
\"harfbuzz\": \"${VERSION_HARFBUZZ}\",\n\
\"jpeg\": \"${VERSION_JPEG}\",\n\
\"lcms\": \"${VERSION_LCMS2}\",\n\
\"gm\": \"${VERSION_GM}\",\n\
\"orc\": \"${VERSION_ORC}\",\n\
\"pango\": \"${VERSION_PANGO}\",\n\
\"pixman\": \"${VERSION_PIXMAN}\",\n\
\"png\": \"${VERSION_PNG16}\",\n\
\"svg\": \"${VERSION_SVG}\",\n\
\"tiff\": \"${VERSION_TIFF}\",\n\
\"vips\": \"${VERSION_VIPS}\"\n\
\"vips\": \"${VERSION_VIPS}\",\n\
\"webp\": \"${VERSION_WEBP}\",\n\
\"xml\": \"${VERSION_XML2}\",\n\
\"zlib\": \"${VERSION_ZLIB}\",\n\
\"zlib\": \"${VERSION_ZLIB}\"\n\
}" >lib/versions.json
# Create .tar.gz

View File

@@ -1,5 +1,7 @@
#!/bin/sh
VERSION_VIPS=8.3.1
# Is docker available?
if ! type docker >/dev/null; then
@@ -7,21 +9,21 @@ if ! type docker >/dev/null; then
exit 1
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
docker build -t vips-dev-win win
WIN_CONTAINER_ID=$(docker run -d vips-dev-win)
docker cp $WIN_CONTAINER_ID:/libvips-8.2.2-win.tar.gz .
docker rm $WIN_CONTAINER_ID
docker cp "${WIN_CONTAINER_ID}:/libvips-${VERSION_VIPS}-win.tar.gz" .
docker rm "${WIN_CONTAINER_ID}"
# Linux
docker build -t vips-dev-lin lin
LIN_CONTAINER_ID=$(docker run -d vips-dev-lin)
docker cp $LIN_CONTAINER_ID:/libvips-8.2.2-lin.tar.gz .
docker rm $LIN_CONTAINER_ID
docker cp "${LIN_CONTAINER_ID}:/libvips-${VERSION_VIPS}-lin.tar.gz" .
docker rm "${LIN_CONTAINER_ID}"
# Checksums

View File

@@ -10,28 +10,40 @@ ENV DEPS=/deps \
RUN mkdir ${DEPS} && mkdir ${TARGET}
# Common build paths and flags
ENV PKG_CONFIG_PATH=${PKG_CONFIG_PATH}:${TARGET}/lib/pkgconfig \
PATH=${PATH}:${TARGET}/bin \
CPPFLAGS=-I${TARGET}/include \
LDFLAGS=-L${TARGET}/lib \
ENV PKG_CONFIG_PATH="${PKG_CONFIG_PATH}:${TARGET}/lib/pkgconfig" \
PATH="${PATH}:${TARGET}/bin" \
CPPFLAGS="-I${TARGET}/include" \
LDFLAGS="-L${TARGET}/lib" \
CFLAGS="-O3" \
CXXFLAGS="-O3"
# Dependency version numbers
ENV VERSION_ZLIB=1.2.8 \
VERSION_FFI=3.2.1 \
VERSION_GLIB=2.47.5 \
VERSION_GLIB=2.48.0 \
VERSION_XML2=2.9.3 \
VERSION_GSF=1.14.34 \
VERSION_GSF=1.14.36 \
VERSION_EXIF=0.6.21 \
VERSION_LCMS2=2.7 \
VERSION_GM=1.3.23 \
VERSION_JPEG=1.4.2 \
VERSION_JPEG=1.4.90 \
VERSION_PNG16=1.6.21 \
VERSION_WEBP=0.5.0 \
VERSION_TIFF=4.0.6 \
VERSION_ORC=0.4.24 \
VERSION_VIPS=8.2.2
VERSION_ORC=0.4.25 \
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 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 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
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 curl -Ls http://xmlsoft.org/sources/libxml2-${VERSION_XML2}.tar.gz | tar xzC ${DEPS}/xml2 --strip-components=1
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 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 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
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
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
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 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
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 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
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
WORKDIR ${DEPS}/orc
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 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
#RUN ./bootstrap.sh
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-jpeg-includes=${TARGET}/include --with-jpeg-libraries=${TARGET}/lib \
&& make install-strip
@@ -118,20 +191,29 @@ RUN rm -rf pkgconfig .libs *.la libvipsCC*
# Create JSON file of version numbers
WORKDIR ${TARGET}
RUN echo "{\n\
\"cairo\": \"${VERSION_CAIRO}\",\n\
\"croco\": \"${VERSION_CROCO}\",\n\
\"exif\": \"${VERSION_EXIF}\",\n\
\"ffi\": \"${VERSION_FFI}\",\n\
\"fontconfig\": \"${VERSION_FONTCONFIG}\",\n\
\"freetype\": \"${VERSION_FREETYPE}\",\n\
\"gdkpixbuf\": \"${VERSION_GDKPIXBUF}\",\n\
\"gif\": \"${VERSION_GIF}\",\n\
\"glib\": \"${VERSION_GLIB}\",\n\
\"gsf\": \"${VERSION_GSF}\",\n\
\"harfbuzz\": \"${VERSION_HARFBUZZ}\",\n\
\"jpeg\": \"${VERSION_JPEG}\",\n\
\"lcms\": \"${VERSION_LCMS2}\",\n\
\"gm\": \"${VERSION_GM}\",\n\
\"orc\": \"${VERSION_ORC}\",\n\
\"pango\": \"${VERSION_PANGO}\",\n\
\"pixman\": \"${VERSION_PIXMAN}\",\n\
\"png\": \"${VERSION_PNG16}\",\n\
\"svg\": \"${VERSION_SVG}\",\n\
\"tiff\": \"${VERSION_TIFF}\",\n\
\"vips\": \"${VERSION_VIPS}\"\n\
\"vips\": \"${VERSION_VIPS}\",\n\
\"webp\": \"${VERSION_WEBP}\",\n\
\"xml\": \"${VERSION_XML2}\",\n\
\"zlib\": \"${VERSION_ZLIB}\",\n\
\"zlib\": \"${VERSION_ZLIB}\"\n\
}" >lib/versions.json
# Create .tar.gz

View File

@@ -6,34 +6,33 @@ if ! type docker >/dev/null; then
exit 1
fi
version_node=4.4.2
test="npm run clean; npm install --unsafe-perm; npm test"
# Debian 7, 8
# Ubuntu 12.04, 14.04
for dist in wheezy jessie precise trusty; do
# Ubuntu 14.04
for dist in wheezy jessie trusty; do
echo "Testing $dist..."
if docker run -i -t --rm -v $PWD:/v -e "NODE_ENV=development" nodesource/$dist: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"
else echo "$dist fail" && cat packaging/$dist.log
fi
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
# Fedora 20, 21
for dist in centos7 fedora20 fedora21; do
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; $test";
then echo "$dist OK"
else echo "$dist fail" && cat packaging/$dist.log
fi
done
echo "Testing centos7..."
if docker run -i -t --rm -v $PWD:/v -e "NODE_ENV=development" nodesource/centos7:$version_node >packaging/$dist.log 2>&1 sh -c "cd /v; $test";
then echo "$dist OK"
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"
else echo "$dist fail" && cat packaging/$dist.log
fi
# openSUSE 13.2
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>
RUN apt-get update && apt-get install -y curl zip
ENV VERSION_VIPS=8.3.1
# Fetch and unzip
RUN mkdir /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 unzip 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-web-${VERSION_VIPS}.zip
# Clean and zip
WORKDIR /vips/vips-dev-8.2
RUN rm bin/libvipsCC-42.dll bin/libvips-cpp-42.dll bin/libgsf-win32-1-114.dll bin/libstdc++-6.dll
WORKDIR /vips/vips-dev-8.3
RUN rm bin/libvipsCC-42.dll bin/libvips-cpp-42.dll bin/libgsf-win32-1-114.dll
RUN cp bin/*.dll 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
# This script is no longer required on most
# 64-bit Linux systems when using sharp v0.12.0+
# Use of this script is deprecated
# 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
# globally install libvips if not already available.
# 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
vips_version_minimum=8.3.1
vips_version_latest_major_minor=8.3
vips_version_latest_patch=1
openslide_version_minimum=3.4.0
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
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
./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 install
cd ..
@@ -139,7 +130,7 @@ if [ $enable_openslide -eq 1 ] && [ -z $vips_with_openslide ] && [ $openslide_ex
DISTRO=$(lsb_release -c -s)
echo "Detected Debian Linux '$DISTRO'"
case "$DISTRO" in
jessie|vivid|wily)
jessie|vivid|wily|xenial)
# Debian 8, Ubuntu 15
echo "Installing libopenslide via apt-get"
apt-get install -y libopenslide-dev

View File

@@ -8,16 +8,16 @@
// Verify platform and compiler compatibility
#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
#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
#if (defined(__clang__) && defined(__has_feature))
#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
@@ -52,6 +52,9 @@ namespace sharp {
bool IsDz(std::string const &str) {
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.
@@ -63,6 +66,9 @@ namespace sharp {
case ImageType::PNG: id = "png"; break;
case ImageType::WEBP: id = "webp"; 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::OPENSLIDE: id = "openslide"; break;
case ImageType::PPM: id = "ppm"; break;
@@ -89,6 +95,12 @@ namespace sharp {
imageType = ImageType::WEBP;
} else if (EndsWith(loader, "TiffBuffer")) {
imageType = ImageType::TIFF;
} else if (EndsWith(loader, "GifBuffer")) {
imageType = ImageType::GIF;
} else if (EndsWith(loader, "SvgBuffer")) {
imageType = ImageType::SVG;
} else if (EndsWith(loader, "PdfBuffer")) {
imageType = ImageType::PDF;
} else if (EndsWith(loader, "MagickBuffer")) {
imageType = ImageType::MAGICK;
}
@@ -114,6 +126,12 @@ namespace sharp {
imageType = ImageType::OPENSLIDE;
} else if (EndsWith(loader, "TiffFile")) {
imageType = ImageType::TIFF;
} else if (EndsWith(loader, "GifFile")) {
imageType = ImageType::GIF;
} else if (EndsWith(loader, "SvgFile")) {
imageType = ImageType::SVG;
} else if (EndsWith(loader, "PdfFile")) {
imageType = ImageType::PDF;
} else if (EndsWith(loader, "Ppm")) {
imageType = ImageType::PPM;
} else if (EndsWith(loader, "Fits")) {
@@ -176,6 +194,30 @@ namespace sharp {
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
*/
@@ -185,4 +227,54 @@ namespace sharp {
}
}
} // 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

View File

@@ -2,6 +2,8 @@
#define SRC_COMMON_H_
#include <string>
#include <tuple>
#include <vips/vips8>
using vips::VImage;
@@ -9,16 +11,19 @@ using vips::VImage;
namespace sharp {
enum class ImageType {
UNKNOWN,
JPEG,
PNG,
WEBP,
TIFF,
GIF,
SVG,
PDF,
MAGICK,
OPENSLIDE,
PPM,
FITS,
RAW
RAW,
UNKNOWN
};
// How many tasks are in the queue?
@@ -33,6 +38,7 @@ namespace sharp {
bool IsWebp(std::string const &str);
bool IsTiff(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.
@@ -75,11 +81,33 @@ namespace sharp {
*/
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
*/
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
#endif // SRC_COMMON_H_

View File

@@ -711,4 +711,499 @@ VImage::maxpos( VOption *options )
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

View File

@@ -1,5 +1,5 @@
// 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!
void VImage::system( char * cmd_format , VOption *options )
@@ -1408,6 +1408,78 @@ VImage VImage::vipsload( char * filename , VOption *options )
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 out;
@@ -1783,11 +1855,37 @@ VImage VImage::shrinkv( int yshrink , VOption *options )
return( out );
}
VImage VImage::shrink2( double xshrink , double yshrink , VOption *options )
VImage VImage::reduceh( double xshrink , VOption *options )
{
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()) ->
set( "in", *this ) ->
set( "out", &out ) ->

View File

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

View File

@@ -1,37 +1,54 @@
#include <algorithm>
#include <tuple>
#include <vips/vips8>
#include "common.h"
#include "operations.h"
using vips::VImage;
using vips::VError;
namespace sharp {
/*
Alpha composite src over dst
Assumes alpha channels are already premultiplied and will be unpremultiplied after
Alpha composite src over dst with given gravity.
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;
// 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 srcAlpha = src[src.bands() - 1] * (1.0 / 255.0);
// Split dst into non-alpha and alpha channels
VImage dstWithoutAlpha;
VImage dstAlpha;
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();
}
VImage dstWithoutAlpha = dst.extract_band(0, VImage::option()->set("n", dst.bands() - 1));
VImage dstAlpha = dst[dst.bands() - 1] * (1.0 / 255.0);
//
// Compute normalized output alpha channel:
@@ -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) {
if (sigma < 0.0) {
if (sigma == -1.0) {
// Fast, mild blur - averages neighbouring pixels
VImage blur = VImage::new_matrixv(3, 3,
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) {
if (radius == -1) {
VImage Sharpen(VImage image, double const sigma, double const flat, double const jagged) {
if (sigma == -1.0) {
// Fast, mild sharpen
VImage sharpen = VImage::new_matrixv(3, 3,
-1.0, -1.0, -1.0,
@@ -150,8 +167,105 @@ namespace sharp {
} else {
// Slow, accurate sharpen in LAB colour space, with control over flat vs jagged areas
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

View File

@@ -1,6 +1,7 @@
#ifndef SRC_OPERATIONS_H_
#define SRC_OPERATIONS_H_
#include <tuple>
#include <vips/vips8>
using vips::VImage;
@@ -8,10 +9,10 @@ using vips::VImage;
namespace sharp {
/*
Composite images `src` and `dst` with premultiplied alpha channel and output
image with premultiplied alpha.
Alpha composite src over dst with given gravity.
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.
@@ -24,14 +25,30 @@ namespace sharp {
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);
/*
* 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
#endif // SRC_OPERATIONS_H_

View File

@@ -1,10 +1,12 @@
#include <tuple>
#include <algorithm>
#include <utility>
#include <cmath>
#include <tuple>
#include <utility>
#include <vips/vips8>
#include <node.h>
#include <node_buffer.h>
#include <vips/vips8>
#include "nan.h"
@@ -47,6 +49,8 @@ using sharp::Normalize;
using sharp::Gamma;
using sharp::Blur;
using sharp::Sharpen;
using sharp::EntropyCrop;
using sharp::TileCache;
using sharp::ImageType;
using sharp::ImageTypeId;
@@ -56,139 +60,29 @@ using sharp::HasAlpha;
using sharp::ExifOrientation;
using sharp::SetExifOrientation;
using sharp::RemoveExifOrientation;
using sharp::SetDensity;
using sharp::IsJpeg;
using sharp::IsPng;
using sharp::IsWebp;
using sharp::IsTiff;
using sharp::IsDz;
using sharp::IsDzZip;
using sharp::FreeCallback;
using sharp::CalculateCrop;
using sharp::counterProcess;
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 {
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) {
if (baton->bufferInLength > 0) {
SaveToPersistent("bufferIn", bufferIn);
}
if (baton->overlayBufferInLength > 0) {
SaveToPersistent("overlayBufferIn", overlayBufferIn);
}
}
~PipelineWorker() {}
@@ -196,7 +90,6 @@ class PipelineWorker : public AsyncWorker {
libuv worker
*/
void Execute() {
// Decrement queued task counter
g_atomic_int_dec_and_test(&counterQueue);
// Increment processing task counter
@@ -212,24 +105,37 @@ class PipelineWorker : public AsyncWorker {
// From buffer
if (baton->rawWidth > 0 && baton->rawHeight > 0 && baton->rawChannels > 0) {
// Raw, uncompressed pixel data
image = VImage::new_from_memory(baton->bufferIn, baton->bufferInLength,
baton->rawWidth, baton->rawHeight, baton->rawChannels, VIPS_FORMAT_UCHAR);
if (baton->rawChannels < 3) {
image.get_image()->Type = VIPS_INTERPRETATION_B_W;
} else {
image.get_image()->Type = VIPS_INTERPRETATION_sRGB;
try {
image = VImage::new_from_memory(baton->bufferIn, baton->bufferInLength,
baton->rawWidth, baton->rawHeight, baton->rawChannels, VIPS_FORMAT_UCHAR);
if (baton->rawChannels < 3) {
image.get_image()->Type = VIPS_INTERPRETATION_B_W;
} else {
image.get_image()->Type = VIPS_INTERPRETATION_sRGB;
}
inputImageType = ImageType::RAW;
} catch(VError const &err) {
(baton->err).append(err.what());
inputImageType = ImageType::UNKNOWN;
}
inputImageType = ImageType::RAW;
} else {
// Compressed data
inputImageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
if (inputImageType != ImageType::UNKNOWN) {
try {
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) {
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);
if (inputImageType == ImageType::SVG ||
inputImageType == ImageType::PDF ||
inputImageType == ImageType::MAGICK) {
SetDensity(image, baton->density);
}
} catch (...) {
(baton->err).append("Input buffer has corrupt header");
inputImageType = ImageType::UNKNOWN;
@@ -244,10 +150,18 @@ class PipelineWorker : public AsyncWorker {
if (inputImageType != ImageType::UNKNOWN) {
try {
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) {
option->set("density", baton->density.data());
option->set("density", std::to_string(baton->density).data());
}
image = VImage::new_from_file(baton->fileIn.data(), option);
if (inputImageType == ImageType::SVG ||
inputImageType == ImageType::PDF ||
inputImageType == ImageType::MAGICK) {
SetDensity(image, baton->density);
}
} catch (...) {
(baton->err).append("Input file has corrupt header");
inputImageType = ImageType::UNKNOWN;
@@ -261,7 +175,8 @@ class PipelineWorker : public AsyncWorker {
}
// 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");
return Error();
}
@@ -295,22 +210,19 @@ class PipelineWorker : public AsyncWorker {
// Get pre-resize image width and height
int inputWidth = image.width();
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
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
double xfactor = 1.0;
double yfactor = 1.0;
if (baton->width > 0 && baton->height > 0) {
// Fixed width and height
xfactor = static_cast<double>(inputWidth) / static_cast<double>(baton->width);
yfactor = static_cast<double>(inputHeight) / static_cast<double>(baton->height);
xfactor = static_cast<double>(inputWidth) / (static_cast<double>(baton->width) + 0.1);
yfactor = static_cast<double>(inputHeight) / (static_cast<double>(baton->height) + 0.1);
switch (baton->canvas) {
case Canvas::CROP:
xfactor = std::min(xfactor, yfactor);
@@ -339,12 +251,15 @@ class PipelineWorker : public AsyncWorker {
}
break;
case Canvas::IGNORE_ASPECT:
// xfactor, yfactor OK!
if (!baton->rotateBeforePreExtract &&
(rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270)) {
std::swap(xfactor, yfactor);
}
break;
}
} else if (baton->width > 0) {
// 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) {
baton->height = inputHeight;
} else {
@@ -354,7 +269,7 @@ class PipelineWorker : public AsyncWorker {
}
} else if (baton->height > 0) {
// 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) {
baton->width = inputWidth;
} else {
@@ -369,23 +284,23 @@ class PipelineWorker : public AsyncWorker {
}
// Calculate integral box shrink
int xshrink = CalculateShrink(xfactor, interpolatorWindowSize);
int yshrink = CalculateShrink(yfactor, interpolatorWindowSize);
int xshrink = std::max(1, static_cast<int>(floor(xfactor)));
int yshrink = std::max(1, static_cast<int>(floor(yfactor)));
// Calculate residual float affine transformation
double xresidual = CalculateResidual(xshrink, xfactor);
double yresidual = CalculateResidual(yshrink, yfactor);
double xresidual = static_cast<double>(xshrink) / xfactor;
double yresidual = static_cast<double>(yshrink) / yfactor;
// Do not enlarge the output if the input width *or* height
// are already less than the required dimensions
if (baton->withoutEnlargement) {
if (inputWidth < baton->width || inputHeight < baton->height) {
xfactor = 1;
yfactor = 1;
xfactor = 1.0;
yfactor = 1.0;
xshrink = 1;
yshrink = 1;
xresidual = 0;
yresidual = 0;
xresidual = 1.0;
yresidual = 1.0;
baton->width = inputWidth;
baton->height = inputHeight;
}
@@ -395,7 +310,8 @@ class PipelineWorker : public AsyncWorker {
// but not when applying gamma correction or pre-resize extract
int shrink_on_load = 1;
if (
xshrink == yshrink && inputImageType == ImageType::JPEG && xshrink >= 2 &&
xshrink == yshrink && xshrink >= 2 &&
(inputImageType == ImageType::JPEG || inputImageType == ImageType::WEBP) &&
baton->gamma == 0 && baton->topOffsetPre == -1
) {
if (xshrink >= 8) {
@@ -416,20 +332,30 @@ class PipelineWorker : public AsyncWorker {
// Recalculate integral shrink and double residual
xfactor = std::max(xfactor, 1.0);
yfactor = std::max(yfactor, 1.0);
xshrink = CalculateShrink(xfactor, interpolatorWindowSize);
yshrink = CalculateShrink(yfactor, interpolatorWindowSize);
xresidual = CalculateResidual(xshrink, xfactor);
yresidual = CalculateResidual(yshrink, yfactor);
xshrink = std::max(1, static_cast<int>(floor(xfactor)));
yshrink = std::max(1, static_cast<int>(floor(yfactor)));
xresidual = static_cast<double>(xshrink) / xfactor;
yresidual = static_cast<double>(yshrink) / yfactor;
// Reload input using shrink-on-load
VOption *option = VImage::option()->set("shrink", shrink_on_load);
if (baton->bufferInLength > 1) {
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));
} else {
image = VImage::jpegload(
const_cast<char*>((baton->fileIn).data()),
VImage::option()->set("shrink", shrink_on_load)
);
if (inputImageType == ImageType::JPEG) {
// Reload JPEG file
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) {
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
int shrunkWidth = image.width();
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
std::swap(shrunkWidth, shrunkHeight);
}
@@ -502,17 +434,30 @@ class PipelineWorker : public AsyncWorker {
if (baton->canvas == Canvas::EMBED) {
xresidual = std::min(xresidual, yresidual);
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);
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 shouldSharpen = baton->sharpenRadius != 0;
bool shouldSharpen = baton->sharpenSigma != 0.0;
bool shouldThreshold = baton->threshold != 0;
bool hasOverlay = !baton->overlayPath.empty();
bool shouldPremultiplyAlpha = HasAlpha(image) &&
(shouldAffineTransform || shouldBlur || shouldSharpen || hasOverlay);
@@ -523,31 +468,41 @@ class PipelineWorker : public AsyncWorker {
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) {
// Use average of x and y residuals to compute sigma for Gaussian blur
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) {
image = image.linecache(VImage::option()
->set("access", VIPS_ACCESS_SEQUENTIAL)
->set("tile_height", 1)
->set("threaded", TRUE)
);
}
// Apply Gaussian blur
image = image.gaussblur(sigma);
// Insert tile cache to prevent over-computation of previous operations
if (baton->accessMethod == VIPS_ACCESS_SEQUENTIAL) {
image = TileCache(image, yresidual);
}
// Perform kernel-based reduction
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");
}
if (yresidual < 1.0) {
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 enlargement
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)
);
}
if (xresidual > 1.0) {
image = image.affine({xresidual, 0.0, 0.0, 1.0}, VImage::option()
->set("interpolate", interpolator)
);
}
}
// Perform affine transformation
image = image.affine({xresidual, 0.0, 0.0, yresidual}, VImage::option()
->set("interpolate", interpolator)
);
}
// Rotate
@@ -574,11 +529,21 @@ class PipelineWorker : public AsyncWorker {
// Scale up 8-bit values to match 16-bit input image
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
};
std::vector<double> background;
if (image.bands() > 2) {
background = {
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
if (baton->background[3] < 255.0 || HasAlpha(image)) {
background.push_back(baton->background[3] * multiplier);
@@ -600,9 +565,15 @@ class PipelineWorker : public AsyncWorker {
// Crop/max/min
int left;
int top;
std::tie(left, top) = CalculateCrop(
image.width(), image.height(), baton->width, baton->height, baton->gravity
);
if (baton->crop < 9) {
// Gravity-based crop
std::tie(left, top) = CalculateCrop(
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 height = std::min(image.height(), baton->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
if (shouldThreshold) {
image = image.colourspace(VIPS_INTERPRETATION_B_W) >= baton->threshold;
@@ -628,44 +620,47 @@ class PipelineWorker : public AsyncWorker {
// Sharpen
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
if (hasOverlay) {
VImage overlayImage;
ImageType overlayImageType = DetermineImageType(baton->overlayPath.data());
if (overlayImageType != ImageType::UNKNOWN) {
overlayImage = VImage::new_from_file(
baton->overlayPath.data(),
VImage::option()->set("access", baton->accessMethod)
);
ImageType overlayImageType = ImageType::UNKNOWN;
if (baton->overlayBufferInLength > 0) {
// Overlay with image from buffer
overlayImageType = DetermineImageType(baton->overlayBufferIn, baton->overlayBufferInLength);
if (overlayImageType != ImageType::UNKNOWN) {
try {
overlayImage = VImage::new_from_buffer(baton->overlayBufferIn, baton->overlayBufferInLength,
nullptr, VImage::option()->set("access", baton->accessMethod));
} catch (...) {
(baton->err).append("Overlay buffer has corrupt header");
overlayImageType = ImageType::UNKNOWN;
}
} else {
(baton->err).append("Overlay buffer contains unsupported image format");
}
} else {
(baton->err).append("Overlay image is of an unsupported image format");
// 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();
}
if (image.format() != VIPS_FORMAT_UCHAR && image.format() != VIPS_FORMAT_FLOAT) {
(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
// Ensure overlay is premultiplied sRGB
overlayImage = overlayImage.colourspace(VIPS_INTERPRETATION_sRGB).premultiply();
image = Composite(overlayImage, image);
// Composite images with given gravity
image = Composite(overlayImage, image, baton->overlayGravity);
}
// Reverse premultiplication after all transformations:
@@ -708,6 +703,10 @@ class PipelineWorker : public AsyncWorker {
SetExifOrientation(image, baton->withMetadataOrientation);
}
// Number of channels used in output image
baton->channels = image.bands();
baton->width = image.width();
baton->height = image.height();
// Output
if (baton->fileOut == "") {
// Buffer output
@@ -728,13 +727,15 @@ class PipelineWorker : public AsyncWorker {
area->free_fn = nullptr;
vips_area_unref(area);
baton->formatOut = "jpeg";
baton->channels = std::min(baton->channels, 3);
} else if (baton->formatOut == "png" || (baton->formatOut == "input" && inputImageType == ImageType::PNG)) {
// Write PNG to buffer
VipsArea *area = VIPS_AREA(image.pngsave_buffer(VImage::option()
->set("strip", !baton->withMetadata)
->set("compression", baton->compressionLevel)
->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->bufferOutLength = area->length;
@@ -786,7 +787,8 @@ class PipelineWorker : public AsyncWorker {
bool isWebp = IsWebp(baton->fileOut);
bool isTiff = IsTiff(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)) {
// Write JPEG to file
image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
@@ -800,13 +802,15 @@ class PipelineWorker : public AsyncWorker {
->set("interlace", baton->progressive)
);
baton->formatOut = "jpeg";
baton->channels = std::min(baton->channels, 3);
} else if (baton->formatOut == "png" || isPng || (matchInput && inputImageType == ImageType::PNG)) {
// Write PNG to file
image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata)
->set("compression", baton->compressionLevel)
->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";
} 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)
);
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
image.dzsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata)
->set("tile_size", baton->tileSize)
->set("overlap", baton->tileOverlap)
->set("container", baton->tileContainer)
->set("layout", baton->tileLayout)
);
baton->formatOut = "dz";
} else {
@@ -838,8 +848,6 @@ class PipelineWorker : public AsyncWorker {
return Error();
}
}
// Number of channels used in output image
baton->channels = image.bands();
} catch (VError const &err) {
(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) {
GetFromPersistent("bufferIn");
}
if (baton->overlayBufferInLength > 0) {
GetFromPersistent("overlayBufferIn");
}
delete baton;
// Decrement processing task counter
@@ -927,10 +938,10 @@ class PipelineWorker : public AsyncWorker {
case 6: rotate = VIPS_ANGLE_D90; break;
case 3: rotate = VIPS_ANGLE_D180; break;
case 8: rotate = VIPS_ANGLE_D270; break;
case 2: flop = TRUE; break; // flop 1
case 7: flip = TRUE; rotate = VIPS_ANGLE_D90; break; // flip 6
case 4: flop = TRUE; rotate = VIPS_ANGLE_D180; break; // flop 3
case 5: flip = TRUE; rotate = VIPS_ANGLE_D270; break; // flip 8
case 2: flop = TRUE; break; // flop 1
case 7: flip = TRUE; rotate = VIPS_ANGLE_D90; break; // flip 6
case 4: flop = TRUE; rotate = VIPS_ANGLE_D180; break; // flop 3
case 5: flip = TRUE; rotate = VIPS_ANGLE_D270; break; // flip 8
}
} else {
if (angle == 90) {
@@ -944,70 +955,6 @@ class PipelineWorker : public AsyncWorker {
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.
*/
@@ -1052,7 +999,7 @@ NAN_METHOD(pipeline) {
// Limit input images to a given number of pixels, where pixels = width * height
baton->limitInputPixels = attrAs<int32_t>(options, "limitInputPixels");
// 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
baton->rawWidth = attrAs<int32_t>(options, "rawWidth");
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();
}
// 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
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");
// Operators
baton->flatten = attrAs<bool>(options, "flatten");
baton->negate = attrAs<bool>(options, "negate");
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->sharpenJagged = attrAs<double>(options, "sharpenJagged");
baton->threshold = attrAs<int32_t>(options, "threshold");
@@ -1108,6 +1063,10 @@ NAN_METHOD(pipeline) {
baton->rotateBeforePreExtract = attrAs<bool>(options, "rotateBeforePreExtract");
baton->flip = attrAs<bool>(options, "flip");
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
baton->progressive = attrAs<bool>(options, "progressive");
baton->quality = attrAs<int32_t>(options, "quality");
@@ -1122,8 +1081,24 @@ NAN_METHOD(pipeline) {
// Output
baton->formatOut = attrAsStr(options, "formatOut");
baton->fileOut = attrAsStr(options, "fileOut");
// Tile output
baton->tileSize = attrAs<int32_t>(options, "tileSize");
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
Callback *queueListener = new Callback(
Get(options, New("queueListener").ToLocalChecked()).ToLocalChecked().As<Function>()
@@ -1131,7 +1106,7 @@ NAN_METHOD(pipeline) {
// Join queue for worker thread
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
g_atomic_int_inc(&counterQueue);

View File

@@ -1,8 +1,144 @@
#ifndef SRC_PIPELINE_H_
#define SRC_PIPELINE_H_
#include <vips/vips8>
#include "nan.h"
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_

View File

@@ -138,7 +138,9 @@ NAN_METHOD(format) {
// Which load/save operations are available for each compressed format?
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
Local<Boolean> hasInputFile =
New<Boolean>(vips_type_find("VipsOperation", (f + "load").c_str()));

View File

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

View File

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

View File

@@ -8,14 +8,12 @@ var Benchmark = require('benchmark');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(false);
sharp.simd(true);
var min = 320;
var max = 960;
// Nearest equivalent to bilinear
var magickFilter = 'Triangle';
var randomDimension = function() {
return Math.ceil(Math.random() * (max - min) + min);
};
@@ -30,7 +28,7 @@ new Benchmark.Suite('random').add('imagemagick', {
width: randomDimension(),
height: randomDimension(),
format: 'jpg',
filter: magickFilter
filter: 'Lanczos'
}, function(err) {
if (err) {
throw err;
@@ -44,7 +42,7 @@ new Benchmark.Suite('random').add('imagemagick', {
fn: function(deferred) {
gm(fixtures.inputJpg)
.resize(randomDimension(), randomDimension())
.filter(magickFilter)
.filter('Lanczos')
.quality(80)
.toBuffer(function (err, buffer) {
if (err) {
@@ -58,19 +56,20 @@ new Benchmark.Suite('random').add('imagemagick', {
}).add('sharp', {
defer: true,
fn: function(deferred) {
sharp(fixtures.inputJpg).resize(randomDimension(), randomDimension()).toBuffer(function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
sharp(fixtures.inputJpg)
.resize(randomDimension(), randomDimension())
.toBuffer(function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).on('cycle', function(event) {
console.log(String(event.target));
}).on('complete', function() {
var winner = this.filter('fastest').map('name');
assert.strictEqual('sharp', String(winner), 'sharp was slower than ' + winner);
console.dir(sharp.cache());
}).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()
.resize(9, 8)
.ignoreAspectRatio()
.interpolateWith(sharp.interpolator.vertexSplitQuadraticBasisSpline)
.raw()
.toBuffer(function(err, data) {
if (err) {
@@ -66,6 +65,7 @@ module.exports = {
inputJpgWithCmykNoProfile: getPath('Channel_digital_image_CMYK_color_no_profile.jpg'),
inputJpgWithCorruptHeader: getPath('corrupt-header.jpg'),
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
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
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
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
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
inputJPGBig: getPath('flowers.jpeg'),
outputJpg: getPath('output.jpg'),
outputPng: getPath('output.png'),
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
}
{
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
{
@@ -102,7 +150,21 @@
cond_libvips_col_sRGB2scRGB_8
Memcheck:Value8
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
@@ -199,6 +261,13 @@
...
fun:_ZN4node17CreateEnvironmentEPN2v87IsolateEP9uv_loop_sNS0_5LocalINS0_7ContextEEEiPKPKciSB_
}
{
leak_nodejs_icu_getAvailableLocales
Memcheck:Leak
match-leak-kinds: possible
...
fun:_ZN6icu_568Collator19getAvailableLocalesERi
}
{
leak_nan_FunctionCallbackInfo
Memcheck:Leak
@@ -248,59 +317,31 @@
...
fun:_ZN2v88internal12_GLOBAL__N_117CreateICUCollatorEPNS0_7IsolateERKN6icu_556LocaleENS0_6HandleINS0_8JSObjectEEE
}
# vips__init warnings
{
leak_libvips_init
leak_v8_CallInterfaceDescriptorData
Memcheck:Leak
match-leak-kinds: reachable
fun:malloc
match-leak-kinds: possible
...
fun:vips__init
fun:_ZN2v88internal27CallInterfaceDescriptorData26InitializePlatformSpecificEiPNS0_8RegisterEPNS0_27PlatformInterfaceDescriptorE
}
# *magick warnings
{
leak_magick_read
leak_v8_InitializePlatformSpecific14
Memcheck:Leak
match-leak-kinds: definite,indirect,possible
match-leak-kinds: possible
...
fun:ReadImage
...
fun:vips__magick_read
fun:_ZN2v88internal14LoadDescriptor26InitializePlatformSpecificEPNS0_27CallInterfaceDescriptorDataE
}
{
leak_magick_read_header
leak_v8_InitializePlatformSpecific15
Memcheck:Leak
match-leak-kinds: definite,indirect,possible
match-leak-kinds: possible
...
fun:ReadImage
...
fun:vips__magick_read_header
fun:_ZN2v88internal15StoreDescriptor26InitializePlatformSpecificEPNS0_27CallInterfaceDescriptorDataE
}
{
cond_magick_is_palette_image
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
leak_v8_Malloced
Memcheck:Leak
match-leak-kinds: definite,indirect,possible
match-leak-kinds: possible
...
fun:g_file_read_link
...
fun:vips_gsf_path
fun:_ZN2v88internal8Malloced3NewEm
}

View File

@@ -45,15 +45,17 @@ describe('Alpha transparency', function() {
});
it('Flatten 16-bit PNG with transparency to orange', function(done) {
var output = fixtures.path('output.flatten-rgb16-orange.jpg');
sharp(fixtures.inputPngWithTransparency16bit)
.flatten()
.background({r: 255, g: 102, b: 0})
.toBuffer(function(err, data, info) {
.toFile(output, function(err, info) {
if (err) throw err;
assert.strictEqual(true, info.size > 0);
assert.strictEqual(32, info.width);
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);
// Cloned instances with differing dimensions
rotator.clone().resize(320, 240).pipe(writable1);
rotator.clone().resize(100).pipe(writable2);
rotator.clone().resize(100, 122).pipe(writable2);
// Go
fs.createReadStream(fixtures.inputJpg).pipe(rotator);
});

View File

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

View File

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

View File

@@ -38,7 +38,7 @@ describe('Gamma correction', function() {
assert.strictEqual('jpeg', info.format);
assert.strictEqual(129, info.width);
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) {
assert.strictEqual('png', info.format);
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 fixtures = require('../fixtures');
describe('Interpolation', function() {
describe('Interpolators and kernels', function() {
it('nearest neighbour', function(done) {
sharp(fixtures.inputJpg)
.resize(320, 240)
.interpolateWith(sharp.interpolator.nearest)
.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();
describe('Reducers', function() {
[
sharp.kernel.cubic,
sharp.kernel.lanczos2,
sharp.kernel.lanczos3
].forEach(function(kernel) {
it(kernel, function(done) {
sharp(fixtures.inputJpg)
.resize(320, null, { kernel: kernel })
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
fixtures.assertSimilar(fixtures.inputJpg, data, done);
});
});
});
});
it('bilinear', function(done) {
sharp(fixtures.inputJpg)
.resize(320, 240)
.interpolateWith(sharp.interpolator.bilinear)
.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();
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)
.resize(320, null, { interpolator: interpolator })
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
fixtures.assertSimilar(fixtures.inputJpg, data, done);
});
});
});
});
it('bicubic', function(done) {
sharp(fixtures.inputJpg)
.resize(320, 240)
.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('unknown kernel throws', function() {
assert.throws(function() {
sharp().resize(null, null, { kernel: 'unknown' });
});
});
it('nohalo', function(done) {
sharp(fixtures.inputJpg)
.resize(320, 240)
.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('unknown interpolator throws', function() {
assert.throws(function() {
sharp().resize(null, null, { interpolator: 'unknown' });
});
});
it('locally bounded bicubic (LBB)', function(done) {
sharp(fixtures.inputJpg)
.resize(320, 240)
.interpolateWith(sharp.interpolator.locallyBoundedBicubic)
.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();
});
describe('deprecated interpolateWith method still works', function() {
it('resize then interpolateWith', function() {
sharp().resize(1, 1).interpolateWith('bicubic');
});
it('interpolateWith then resize', function() {
sharp().interpolateWith('bicubic').resize(1, 1);
});
});
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);
});
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) {
var pipeline = sharp().resize(320, 240);
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) {
sharp(fixtures.inputSvg)
.resize(1024)
.extract({left: 290, top: 760, width: 40, height: 40})
.toFormat('png')
.toBuffer(function(err, data, info) {
if (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(40, info.width);
assert.strictEqual(40, info.height);
fixtures.assertSimilar(fixtures.expected('svg72.png'), data, done);
}
if (err) throw err;
assert.strictEqual('png', info.format);
assert.strictEqual(40, info.width);
assert.strictEqual(40, info.height);
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) {
@@ -672,31 +695,18 @@ describe('Input/output', function() {
.extract({left: 290, top: 760, width: 40, height: 40})
.toFormat('png')
.toBuffer(function(err, data, info) {
if (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(40, info.width);
assert.strictEqual(40, info.height);
fixtures.assertSimilar(fixtures.expected('svg1200.png'), data, done);
}
});
});
}
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;
assert.strictEqual(true, info.size > 0);
assert.strictEqual('png', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
done();
assert.strictEqual(40, info.width);
assert.strictEqual(40, info.height);
fixtures.assertSimilar(fixtures.expected('svg1200.png'), data, function(err) {
if (err) throw err;
sharp(data).metadata(function(err, info) {
if (err) throw err;
assert.strictEqual(1200, info.density);
done();
});
});
});
});
}
@@ -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) {
var inputGifBuffer = fs.readFileSync(fixtures.inputGif);
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) {
it('Load Aperio SVS file via Openslide', function(done) {
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) {
sharp(fixtures.inputJpg).metadata(function(err, metadata) {
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('srgb', metadata.space);
assert.strictEqual(3, metadata.channels);
assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
@@ -35,6 +36,7 @@ describe('Image metadata', function() {
assert.strictEqual(600, metadata.height);
assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels);
assert.strictEqual(72, metadata.density);
assert.strictEqual(true, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual(8, metadata.orientation);
@@ -64,6 +66,7 @@ describe('Image metadata', function() {
assert.strictEqual(3248, metadata.height);
assert.strictEqual('b-w', metadata.space);
assert.strictEqual(1, metadata.channels);
assert.strictEqual(300, metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
@@ -82,6 +85,7 @@ describe('Image metadata', function() {
assert.strictEqual(2074, metadata.height);
assert.strictEqual('b-w', metadata.space);
assert.strictEqual(1, metadata.channels);
assert.strictEqual(300, metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
@@ -99,6 +103,7 @@ describe('Image metadata', function() {
assert.strictEqual(1536, metadata.height);
assert.strictEqual('srgb', metadata.space);
assert.strictEqual(4, metadata.channels);
assert.strictEqual(72, metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(true, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
@@ -117,6 +122,7 @@ describe('Image metadata', function() {
assert.strictEqual(772, metadata.height);
assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels);
assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
@@ -127,16 +133,33 @@ describe('Image metadata', function() {
});
}
if (sharp.format.magick.input.file) {
it('GIF via libmagick', function(done) {
if (sharp.format.gif.input.file) {
it('GIF via giflib', function(done) {
sharp(fixtures.inputGif).metadata(function(err, metadata) {
if (err) throw err;
assert.strictEqual('magick', metadata.format);
assert.strictEqual('gif', metadata.format);
assert.strictEqual(800, metadata.width);
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.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.exif);
assert.strictEqual('undefined', typeof metadata.icc);
@@ -153,6 +176,7 @@ describe('Image metadata', function() {
assert.strictEqual(2220, metadata.width);
assert.strictEqual(2967, metadata.height);
assert.strictEqual(4, metadata.channels);
assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual('rgb', metadata.space);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(true, metadata.hasAlpha);
@@ -171,6 +195,7 @@ describe('Image metadata', function() {
assert.strictEqual(2225, metadata.height);
assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels);
assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
@@ -198,6 +223,7 @@ describe('Image metadata', function() {
assert.strictEqual(2225, metadata.height);
assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels);
assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
@@ -219,6 +245,7 @@ describe('Image metadata', function() {
assert.strictEqual(2225, metadata.height);
assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels);
assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
@@ -238,6 +265,7 @@ describe('Image metadata', function() {
assert.strictEqual(2225, metadata.height);
assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels);
assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
@@ -325,9 +353,14 @@ describe('Image metadata', function() {
sharp().withMetadata({orientation: -1});
});
});
it('Zero orientation', function () {
assert.throws(function () {
sharp().withMetadata({ orientation: 0 });
});
});
it('Too large orientation', function() {
assert.throws(function() {
sharp().withMetadata({orientation: 8});
sharp().withMetadata({orientation: 9});
});
});
});

View File

@@ -1,5 +1,6 @@
'use strict';
var fs = require('fs');
var assert = require('assert');
var fixtures = require('../fixtures');
var sharp = require('../../index');
@@ -17,7 +18,7 @@ var getPaths = function(baseName, extension) {
// Test
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');
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) {
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) {
sharp(fixtures.inputJpg)
.overlayWith(fixtures.inputPngWithGreyAlpha)
it('Fail when overlay does not contain alpha channel', function(done) {
sharp(fixtures.inputPngOverlayLayer1)
.overlayWith(fixtures.inputJpg)
.toBuffer(function(error) {
assert.strictEqual(true, error instanceof Error);
done();
});
});
it('Fail when compositing non-PNG image', function(done) {
sharp(fixtures.inputPngOverlayLayer1)
.overlayWith(fixtures.inputJpg)
it('Fail when overlay is larger', function(done) {
sharp(fixtures.inputJpg)
.resize(320)
.overlayWith(fixtures.inputPngOverlayLayer1)
.toBuffer(function(error) {
assert.strictEqual(true, error instanceof Error);
done();
@@ -170,4 +184,62 @@ describe('Overlays', function() {
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) {
sharp(fixtures.inputJpgWithExif)
.resize(320)

View File

@@ -7,10 +7,10 @@ var fixtures = require('../fixtures');
describe('Sharpen', function() {
it('specific radius 10', function(done) {
it('specific radius 10 (sigma 6)', function(done) {
sharp(fixtures.inputJpg)
.resize(320, 240)
.sharpen(10)
.sharpen(6)
.toBuffer(function(err, data, info) {
assert.strictEqual('jpeg', info.format);
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)
.resize(320, 240)
.sharpen(3, 0.5, 2.5)
.sharpen(1.5, 0.5, 2.5)
.toBuffer(function(err, data, info) {
assert.strictEqual('jpeg', info.format);
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)
.resize(320, 240)
.sharpen(5, 2, 4)
.sharpen(3.5, 2, 4)
.toBuffer(function(err, data, info) {
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
@@ -55,9 +55,9 @@ describe('Sharpen', function() {
});
});
it('invalid radius', function() {
it('invalid sigma', 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 rimraf = require('rimraf');
var unzip = require('unzip');
var sharp = require('../../index');
var fixtures = require('../fixtures');
@@ -47,156 +48,221 @@ var assertDeepZoomTiles = function(directory, expectedSize, expectedLevels, done
describe('Tile', function() {
describe('Invalid tile values', function() {
it('size - NaN', function(done) {
var isValid = true;
try {
sharp().tile('zoinks');
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
it('Valid size values pass', function() {
[1, 8192].forEach(function(size) {
assert.doesNotThrow(function() {
sharp().tile({
size: size
});
});
});
});
it('size - float', function(done) {
var isValid = true;
try {
sharp().tile(1.1);
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
it('Invalid size values fail', function() {
['zoinks', 1.1, -1, 0, 8193].forEach(function(size) {
assert.throws(function() {
sharp().tile({
size: size
});
});
});
});
it('size - negative', function(done) {
var isValid = true;
try {
sharp().tile(-1);
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
it('Valid overlap values pass', function() {
[0, 8192].forEach(function(overlap) {
assert.doesNotThrow(function() {
sharp().tile({
size: 8192,
overlap: overlap
});
});
});
});
it('size - zero', function(done) {
var isValid = true;
try {
sharp().tile(0);
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
it('Invalid overlap values fail', function() {
['zoinks', 1.1, -1, 8193].forEach(function(overlap) {
assert.throws(function() {
sharp().tile({
overlap: overlap
});
});
});
});
it('size - too large', function(done) {
var isValid = true;
try {
sharp().tile(8193);
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
it('Valid container values pass', function() {
['fs', 'zip'].forEach(function(container) {
assert.doesNotThrow(function() {
sharp().tile({
container: container
});
});
});
});
it('overlap - NaN', function(done) {
var isValid = true;
try {
sharp().tile(null, 'zoinks');
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
it('Invalid container values fail', function() {
['zoinks', 1].forEach(function(container) {
assert.throws(function() {
sharp().tile({
container: container
});
});
});
});
it('overlap - float', function(done) {
var isValid = true;
try {
sharp().tile(null, 1.1);
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
it('Valid layout values pass', function() {
['dz', 'google', 'zoomify'].forEach(function(layout) {
assert.doesNotThrow(function() {
sharp().tile({
layout: layout
});
});
});
});
it('overlap - negative', function(done) {
var isValid = true;
try {
sharp().tile(null, -1);
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
it('Invalid layout values fail', function() {
['zoinks', 1].forEach(function(layout) {
assert.throws(function() {
sharp().tile({
layout: layout
});
});
});
});
it('overlap - too large', function(done) {
var isValid = true;
try {
sharp().tile(null, 8193);
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
it('Prevent larger overlap than default size', function() {
assert.throws(function() {
sharp().tile({overlap: 257});
});
});
it('overlap - larger than default size', function(done) {
var isValid = true;
try {
sharp().tile(null, 257);
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
it('Prevent larger overlap than provided size', function() {
assert.throws(function() {
sharp().tile({size: 512, overlap: 513});
});
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) {
describe('Deep Zoom output', function() {
it('Tile size - 256px default', function(done) {
var directory = fixtures.path('output.256_files');
rimraf(directory, function() {
sharp(fixtures.inputJpg).toFile(fixtures.path('output.256.dzi'), function(err, info) {
it('Deep Zoom layout', function(done) {
var directory = fixtures.path('output.dz_files');
rimraf(directory, function() {
sharp(fixtures.inputJpg)
.toFile(fixtures.path('output.dz.dzi'), function(err, info) {
if (err) throw err;
assert.strictEqual('dz', info.format);
assertDeepZoomTiles(directory, 256, 13, done);
});
});
});
});
it('Tile size/overlap - 512/16px', function(done) {
var directory = fixtures.path('output.512_files');
rimraf(directory, function() {
sharp(fixtures.inputJpg).tile(512, 16).toFile(fixtures.path('output.512.dzi'), function(err, info) {
it('Deep Zoom layout with custom size+overlap', function(done) {
var directory = fixtures.path('output.dz.512_files');
rimraf(directory, function() {
sharp(fixtures.inputJpg)
.tile({
size: 512,
overlap: 16
})
.toFile(fixtures.path('output.dz.512.dzi'), function(err, info) {
if (err) throw err;
assert.strictEqual('dz', info.format);
assertDeepZoomTiles(directory, 512 + 2 * 16, 13, done);
});
});
});
});
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);
});
});
});
});
});
}
});