Compare commits

...

75 Commits

Author SHA1 Message Date
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
Lovell Fuller
c210ac73cc Release v0.13.1 2016-02-27 16:17:58 +00:00
Lovell Fuller
962c91daf0 OpenSUSE package name for npm has changed 2016-02-25 22:19:08 +00:00
Lovell Fuller
df33c3024a Fix embedding onto transparent backgrounds #366
Fully automate embed tests to prevent regression
2016-02-25 18:36:00 +00:00
Lovell Fuller
62e04f7784 Merge pull request #361 from jardakotesovec/clone-clean-up
Remove left over, non-functional code from clone feature.
2016-02-17 11:38:34 +00:00
Jarda Kotesovec
32fcb771ca clone clean up 2016-02-17 10:25:00 +01:00
Lovell Fuller
a21760b374 Release v0.13.0 2016-02-15 19:12:23 +00:00
Lovell Fuller
cd05c7814a Merge pull request #359 from wjordan/alpine-packaging-test
Use libvips-dev apk for alpine-linux packaging test
2016-02-13 20:37:21 +00:00
Will Jordan
7b12f091e8 use alpine:edge image for packaging test
vips requires main/gettext-0.19.7r1 for aports commit 4ef70b432b05b720f7f144c2060550749d378205 to link correctly.
2016-02-13 13:48:46 -05:00
Will Jordan
e149e60c7a Use libvips-dev apk for alpine-linux packaging test 2016-02-13 16:07:56 +00:00
Lovell Fuller
bdac84059d Update perf results for forthcoming v0.13.0
Install runtime rather than dev pkgs on Alpine Linux
2016-02-12 19:33:21 +00:00
Lovell Fuller
2d05804fc3 Add cache recommendations for use with Alpine/musl #354
Prevent Windows EBUSY errors during tests
2016-02-11 20:33:33 +00:00
Lovell Fuller
2a56de69cc Add Alpine Linux packaging test #354
Requires libvips cache to be disabled for tests
Skip tiff/magick tests when format unavailable
2016-02-11 18:30:50 +00:00
Lovell Fuller
6ca2a4a9cd Ensure sharp.format lists support for raw input #220 2016-02-11 18:12:51 +00:00
Lovell Fuller
a9eb65c462 Most Linux systems no longer require the preinstall script 2016-02-09 20:18:00 +00:00
Lovell Fuller
afb30b3695 Ensure VipsArea is unreferenced after Buffer-based output
Prevents the leak of a ~1KB GMutex per output image
2016-02-09 19:28:17 +00:00
Lovell Fuller
09b019ed13 Expand glibc check to include 'gnu libc' identifier 2016-02-08 20:45:24 +00:00
Lovell Fuller
d46ac3a478 Check for glibc before downloading pre-compiled binaries #354 2016-02-08 20:06:01 +00:00
Lovell Fuller
677b2b9089 Selected output format > unknown file extension #344 2016-02-07 20:13:13 +00:00
Lovell Fuller
5c1067c63f Remove the no-longer-maintained gulp-sharp #353 2016-02-07 15:35:41 +00:00
Lovell Fuller
736c04a7a4 Only set density option when using magick loader
to reduce number of warnings from libvips #352
2016-02-04 19:16:54 +00:00
Lovell Fuller
0e29c55d13 Merge branch 'master' of https://github.com/lovell/sharp 2016-02-04 19:11:51 +00:00
Lovell Fuller
ca49e6079c Increase threshold for gamma=0 test, due to either
the version of libjpeg or a cumulative rounding error.
2016-02-04 19:10:58 +00:00
Lovell Fuller
320a7464c7 Merge pull request #351 from joelmukuthu/master
Fix: default crop gravity to sharp.gravity.center
2016-02-04 10:11:48 +00:00
Joel Mukuthu
da74cd078f Fix: default crop gravity to sharp.gravity.center
Closes https://github.com/lovell/sharp/issues/350
2016-02-04 10:38:58 +01:00
Lovell Fuller
322aa60891 Ensure 16-bit input images can be normalised 2016-02-03 19:33:34 +00:00
Lovell Fuller
e380576da2 Add support for raw, uncompressed pixel Buffer/Stream input 2016-02-03 19:21:37 +00:00
Lovell Fuller
cf7664a854 Improve SVG support by allowing control of density/DPI
Switch pre-built libs from Imagemagick to Graphicsmagick
2016-02-03 17:48:22 +00:00
Lovell Fuller
56508e8d79 Add support for libvips' PPM and FITS loaders #347 2016-02-03 17:48:22 +00:00
Lovell Fuller
2656c69d99 Upgrade to libvips v8.2.2 2016-02-03 17:48:22 +00:00
Lovell Fuller
57c1e3ae26 Slightly simplify marshalling of data from V8 Objects 2016-02-03 17:48:22 +00:00
Lovell Fuller
2675b2265b Ensure 16-bit input images embed onto alpha background
Support gamma correction of images with alpha channel
Favour shrink over affine when reducing by integral factor
2016-02-03 17:48:22 +00:00
Lovell Fuller
41e50770d1 Update benchmark test dependencies 2016-02-03 17:48:22 +00:00
Lovell Fuller
b3d6e94984 Optimisation for integral factors: favour shrink over affine 2016-02-03 17:48:22 +00:00
Lovell Fuller
5c9c17f1f6 Switch from libvips' C to C++ binding
Requires upgrade to libvips 8.2.1
2016-02-03 17:48:22 +00:00
Lovell Fuller
11329d5e09 Expose control of the number of open files in libvips' cache.
Breaks API of existing cache method.
Disable libvips cache for I/O tests.
2016-02-03 17:48:22 +00:00
Lovell Fuller
8843211e12 Upgrade libvips to v8.2.2 2016-01-31 11:54:19 +00:00
Lovell Fuller
20e75dc50b v0.12.2 2016-01-16 11:29:23 +00:00
Lovell Fuller
d2e5441d6e Dependency version bumps 2016-01-14 18:39:29 +00:00
Lovell Fuller
0ffa1e72d0 Attempt to remove temp file after install #331 2016-01-06 16:31:01 +00:00
Lovell Fuller
a0e034a9e9 Add support for pre-compiled libvips on ARM CPUs
Uses a HypriotOS-managed docker container for this
2016-01-06 15:50:36 +00:00
Lovell Fuller
3c7cbf8685 Update libvips to v8.2.0
Plus version bumps of other dependencies
2016-01-02 14:19:46 +00:00
Lovell Fuller
7541dfcab2 Document support for FreeBSD/gmake #326 2016-01-01 16:34:18 +00:00
Lovell Fuller
dc2b79ac9a Use array context for include/library paths in gyp config
Remove -I prefix from include paths
Should allow for compilation on FreeBSD #326
2016-01-01 14:04:52 +00:00
Lovell Fuller
6d62051877 Bump minimum version of libvips to 8.1.1 #324
Debian-based systems must compile from source
2015-12-23 20:54:37 +00:00
Lovell Fuller
61b86744d7 Ensure 16-bit input images work with embed option #325 2015-12-23 20:46:49 +00:00
96 changed files with 7082 additions and 2226 deletions

View File

@@ -4,7 +4,8 @@
"maxparams": 4,
"maxcomplexity": 13,
"globals": {
"before": true,
"beforeEach": true,
"afterEach": true,
"describe": true,
"it": true
}

View File

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

View File

@@ -4,6 +4,9 @@ node_js:
- "0.12"
- "4"
- "5"
os:
- linux
- osx
sudo: false
addons:
apt:
@@ -11,7 +14,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,8 +41,9 @@ Any change that modifies the existing public API should be added to the relevant
| Release | WIP branch |
| ------: | :--------- |
| v0.12.0 | look |
| v0.13.0 | mind |
| 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

@@ -13,7 +13,7 @@ Bicubic interpolation with Lanczos anti-alias filtering ensures quality is not s
As well as image resizing, operations such as
rotation, extraction, compositing and gamma correction are available.
64-bit Windows and recent Linux systems do not require
Most Windows (x64), Linux and ARMv6+ systems do not require
the installation of any external runtime dependencies.
Use with OS X is as simple as running `brew install homebrew/science/vips`
@@ -36,7 +36,7 @@ covers reporting bugs, requesting features and submitting code changes.
### 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

@@ -1,6 +1,53 @@
{
'targets': [{
'target_name': 'libvips-cpp',
'conditions': [
['OS == "win"', {
# Build libvips C++ binding for Windows due to MSVC std library ABI changes
'type': 'shared_library',
'variables': {
'download_vips': '<!(node -e "require(\'./binding\').download_vips()")'
},
'defines': [
'VIPS_CPLUSPLUS_EXPORTS'
],
'sources': [
'src/libvips/cplusplus/VError.cpp',
'src/libvips/cplusplus/VInterpolate.cpp',
'src/libvips/cplusplus/VImage.cpp'
],
'include_dirs': [
'<(module_root_dir)/include',
'<(module_root_dir)/include/glib-2.0',
'<(module_root_dir)/lib/glib-2.0/include'
],
'libraries': [
'<(module_root_dir)/lib/libvips.lib',
'<(module_root_dir)/lib/libglib-2.0.lib',
'<(module_root_dir)/lib/libgobject-2.0.lib'
],
'configurations': {
'Release': {
'msvs_settings': {
'VCCLCompilerTool': {
'ExceptionHandling': 1
}
},
'msvs_disabled_warnings': [
4275
]
}
}
}, {
# Ignore this target for non-Windows
'type': 'none'
}]
]
}, {
'target_name': 'sharp',
'dependencies': [
'libvips-cpp'
],
# Nested variables "pattern" borrowed from http://src.chromium.org/viewvc/chrome/trunk/src/build/common.gypi
'variables': {
'variables': {
@@ -17,7 +64,7 @@
'conditions': [
['OS != "win"', {
# Which version, if any, of libvips is available globally via pkg-config?
'global_vips_version': '<!(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --modversion vips 2>/dev/null || true)'
'global_vips_version': '<!(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --modversion vips-cpp 2>/dev/null || true)'
}, {
'global_vips_version': ''
}]
@@ -43,18 +90,21 @@
'src/sharp.cc',
'src/utilities.cc'
],
'defines': [
'_GLIBCXX_USE_CXX11_ABI=0'
],
'include_dirs': [
'<!(node -e "require(\'nan\')")'
],
'conditions': [
['use_global_vips == "true"', {
# Use pkg-config for include and lib
'include_dirs': ['<!(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --cflags vips glib-2.0)'],
'include_dirs': ['<!@(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --cflags-only-I vips-cpp vips glib-2.0 | sed s\/-I//g)'],
'conditions': [
['runtime_link == "static"', {
'libraries': ['<!(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --libs vips --static)']
'libraries': ['<!@(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --libs --static vips-cpp)']
}, {
'libraries': ['<!(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --libs vips)']
'libraries': ['<!@(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --libs vips-cpp)']
}]
]
}, {
@@ -66,9 +116,6 @@
],
'conditions': [
['OS == "win"', {
'variables': {
'download_vips': '<!(node -e "require(\'./binding\').download_vips()")'
},
'libraries': [
'<(module_root_dir)/lib/libvips.lib',
'<(module_root_dir)/lib/libglib-2.0.lib',
@@ -80,12 +127,13 @@
'download_vips': '<!(LDD_VERSION="<!(ldd --version 2>&1 || true)" node -e "require(\'./binding\').download_vips()")'
},
'libraries': [
'<(module_root_dir)/lib/libvips-cpp.so',
'<(module_root_dir)/lib/libvips.so',
'<(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/libMagickCore-6.Q16.so',
'<(module_root_dir)/lib/libMagickWand-6.Q16.so',
'<(module_root_dir)/lib/libGraphicsMagick.so',
'<(module_root_dir)/lib/libGraphicsMagickWand.so',
'<(module_root_dir)/lib/libexif.so',
'<(module_root_dir)/lib/libgio-2.0.so',
'<(module_root_dir)/lib/libgmodule-2.0.so',
@@ -118,6 +166,8 @@
'CLANG_CXX_LANGUAGE_STANDARD': 'c++11',
'CLANG_CXX_LIBRARY': 'libc++',
'MACOSX_DEPLOYMENT_TARGET': '10.7',
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
'GCC_ENABLE_CPP_RTTI': 'YES',
'OTHER_CPLUSPLUSFLAGS': [
'-fexceptions',
'-Wall',
@@ -130,7 +180,10 @@
'VCCLCompilerTool': {
'ExceptionHandling': 1
}
}
},
'msvs_disabled_warnings': [
4275
]
}
},
}, {
@@ -169,7 +222,7 @@
'<(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/libopenjp2.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',

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
@@ -25,7 +28,7 @@ var isFile = function(file) {
return exists;
};
var unpack = function(tarPath) {
var unpack = function(tarPath, done) {
var extractor = tar.Extract({
path: __dirname
});
@@ -34,6 +37,9 @@ var unpack = function(tarPath) {
if (!isFile(vipsHeaderPath)) {
error('Could not unpack ' + tarPath);
}
if (typeof done === 'function') {
done();
}
});
fs.createReadStream(tarPath).on('error', error)
.pipe(zlib.Unzip())
@@ -54,28 +60,38 @@ var error = function(msg) {
module.exports.download_vips = function() {
// Has vips been installed locally?
if (!isFile(vipsHeaderPath)) {
// Ensure 64-bit
if (process.arch !== 'x64') {
error('ARM and 32-bit systems require manual installation - please see http://sharp.dimens.io/en/stable/install/');
// Ensure Intel 64-bit or ARM
if (process.arch === 'ia32') {
error('Intel Architecture 32-bit systems require manual installation - please see http://sharp.dimens.io/en/stable/install/');
}
// Ensure libc >= 2.15
// Ensure glibc >= 2.15
var lddVersion = process.env.LDD_VERSION;
if (lddVersion) {
var libcVersion = lddVersion ? lddVersion.split(/\n/)[0].split(' ').slice(-1)[0].trim() : '';
if (libcVersion && semver.lt(libcVersion + '.0', '2.13.0')) {
error('libc version ' + libcVersion + ' requires manual installation - please see http://sharp.dimens.io/en/stable/install/');
if (/(glibc|gnu libc)/i.test(lddVersion)) {
var glibcVersion = lddVersion ? lddVersion.split(/\n/)[0].split(' ').slice(-1)[0].trim() : '';
if (glibcVersion && semver.lt(glibcVersion + '.0', '2.13.0')) {
error('glibc version ' + glibcVersion + ' requires manual installation - please see http://sharp.dimens.io/en/stable/install/');
}
} else {
error(lddVersion.split(/\n/)[0] + ' requires manual installation - please see http://sharp.dimens.io/en/stable/install/');
}
}
// Platform-specific .tar.gz
var tarFilename = ['libvips', process.env.npm_package_config_libvips, process.platform.substr(0, 3)].join('-') + '.tar.gz';
// Arch/platform-specific .tar.gz
var platform = (process.arch === 'arm') ? 'arm' : process.platform.substr(0, 3);
var tarFilename = ['libvips', minimumLibvipsVersion, platform].join('-') + '.tar.gz';
var tarPath = path.join(__dirname, 'packaging', tarFilename);
if (isFile(tarPath)) {
unpack(tarPath);
} else {
// Download
tarPath = path.join(tmp, tarFilename);
// Download to per-process temporary file
tarPath = path.join(tmp, process.pid + '-' + tarFilename);
var tmpFile = fs.createWriteStream(tarPath).on('finish', function() {
unpack(tarPath);
unpack(tarPath, function() {
// Attempt to remove temporary file
try {
fs.unlinkSync(tarPath);
} catch (err) {}
});
});
var options = {
url: distBaseUrl + tarFilename
@@ -101,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

@@ -6,22 +6,28 @@ var sharp = require('sharp');
### Input
#### sharp([input])
#### sharp([input], [options])
Constructor to which further methods are chained. `input`, if present, can be one of:
Constructor to which further methods are chained.
* Buffer containing JPEG, PNG, WebP, GIF* or TIFF image data, or
`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.
The object returned implements the
JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data
can be streamed into the object when `input` is `null` or `undefined`.
`options`, if present, is an Object with the following optional attributes:
* `density` an integral number representing the DPI for vector images, defaulting to 72.
* `raw` an Object containing `width`, `height` and `channels` when providing uncompressed data. See `raw()` for pixel ordering.
The object returned by the constructor implements the
[stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.
JPEG, PNG, WebP, GIF* or TIFF format image data
can be streamed into the object when `input` is not provided.
JPEG, PNG or WebP format image data can be streamed out from this object.
\* libvips 8.0.0+ is required for Buffer/Stream input of GIF and other `magick` formats.
When using Stream based output, derived attributes are available from the `info` event.
```javascript
sharp('input.jpg')
@@ -32,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` and `openslide`)
* `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`)
* `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
@@ -93,7 +113,8 @@ 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
@@ -105,23 +126,37 @@ 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])
#### 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);
```
@@ -260,7 +295,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.
@@ -272,6 +307,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.
@@ -360,13 +414,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')
@@ -374,7 +433,7 @@ sharp('input.png')
.resize(300)
.flatten()
.background('#ff6600')
.overlayWith('overlay.png')
.overlayWith('overlay.png', { gravity: sharp.gravity.southeast } )
.sharpen()
.withMetadata()
.quality(90)
@@ -382,8 +441,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!
});
```
@@ -391,12 +450,14 @@ sharp('input.png')
#### toFile(path, [callback])
`path` is a String containing the path to write the image data to. The format is inferred from the extension, with JPEG, PNG, WebP, TIFF and DZI supported.
`path` is a String containing the path to write the image data to.
If an explicit output format is not selected, it will be inferred from the extension, with JPEG, PNG, WebP, TIFF and DZI supported.
`callback`, if present, is called with two arguments `(err, info)` where:
* `err` contains an error message, if any.
* `info` contains the output image `format`, `size` (bytes), `width` and `height`.
* `info` contains the output image `format`, `size` (bytes), `width`, `height` and `channels`.
A Promises/A+ promise is returned when `callback` is not provided.
@@ -408,7 +469,7 @@ Write image data to a Buffer, the format of which will match the input image by
* `err` is an error message, if any.
* `buffer` is the output image data.
* `info` contains the output image `format`, `size` (bytes), `width` and `height`.
* `info` contains the output image `format`, `size` (bytes), `width`, `height` and `channels`.
A Promises/A+ promise is returned when `callback` is not provided.
@@ -459,23 +520,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()
@@ -590,19 +661,26 @@ An Object containing the version numbers of libvips and, on Linux, its dependenc
### Utilities
#### sharp.cache([memory], [items])
#### sharp.cache([options])
If `memory` or `items` are provided, set the limits of _libvips'_ operation cache.
If `options` is provided, sets the limits of _libvips'_ operation cache.
* `memory` is the maximum memory in MB to use for this cache, with a default value of 100
* `items` is the maximum number of operations to cache, with a default value of 500
* `options.memory` is the maximum memory in MB to use for this cache, with a default value of 50
* `options.files` is the maximum number of files to hold open, with a default value of 20
* `options.items` is the maximum number of operations to cache, with a default value of 100
`options` can also be a boolean, where `true` enables the default cache settings and `false` disables all caching.
This method always returns cache statistics, useful for determining how much working memory is required for a particular task.
```javascript
var stats = sharp.cache(); // { current: 75, high: 99, memory: 100, items: 500 }
sharp.cache(200); // { current: 75, high: 99, memory: 200, items: 500 }
sharp.cache(50, 200); // { current: 49, high: 99, memory: 50, items: 200}
var stats = sharp.cache();
```
```javascript
sharp.cache( { items: 200 } );
sharp.cache( { files: 0 } );
sharp.cache(false);
```
#### sharp.concurrency([threads])
@@ -635,7 +713,7 @@ var counters = sharp.counters(); // { queue: 2, process: 4 }
_Requires libvips to have been compiled with liborc support_
Improves the performance of `resize`, `blur` and `sharpen` operations
by taking advantage of the SIMD vector unit of the CPU.
by taking advantage of the SIMD vector unit of the CPU, e.g. Intel SSE and ARM NEON.
* `enable`, if present, is a boolean where `true` enables and `false` disables the use of SIMD.
@@ -650,7 +728,7 @@ have been known to crash under heavy load.
```javascript
var simd = sharp.simd();
// simd is `true` is SIMD is currently enabled
// simd is `true` if SIMD is currently enabled
```
```javascript

View File

@@ -1,7 +1,144 @@
# Changelog
### 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.
[#366](https://github.com/lovell/sharp/issues/366)
[@diegocsandrim](https://github.com/diegocsandrim)
#### v0.13.0 - 15<sup>th</sup> February 2016
* Improve vector image support by allowing control of density/DPI.
Switch pre-built libs from Imagemagick to Graphicsmagick.
[#110](https://github.com/lovell/sharp/issues/110)
[@bradisbell](https://github.com/bradisbell)
* Add support for raw, uncompressed pixel Buffer/Stream input.
[#220](https://github.com/lovell/sharp/issues/220)
[@mikemorris](https://github.com/mikemorris)
* Switch from libvips' C to C++ bindings, requires upgrade to v8.2.2.
[#299](https://github.com/lovell/sharp/issues/299)
* Control number of open files in libvips' cache; breaks existing `cache` behaviour.
[#315](https://github.com/lovell/sharp/issues/315)
[@impomezia](https://github.com/impomezia)
* Ensure 16-bit input images can be normalised and embedded onto transparent backgrounds.
[#339](https://github.com/lovell/sharp/issues/339)
[#340](https://github.com/lovell/sharp/issues/340)
[@janaz](https://github.com/janaz)
* Ensure selected format takes precedence over any unknown output filename extension.
[#344](https://github.com/lovell/sharp/issues/344)
[@ubaltaci](https://github.com/ubaltaci)
* Add support for libvips' PBM, PGM, PPM and FITS image format loaders.
[#347](https://github.com/lovell/sharp/issues/347)
[@oaleynik](https://github.com/oaleynik)
* Ensure default crop gravity is center/centre.
[#351](https://github.com/lovell/sharp/pull/351)
[@joelmukuthu](https://github.com/joelmukuthu)
* Improve support for musl libc systems e.g. Alpine Linux.
[#354](https://github.com/lovell/sharp/issues/354)
[#359](https://github.com/lovell/sharp/pull/359)
[@download13](https://github.com/download13)
[@wjordan](https://github.com/wjordan)
* Small optimisation when reducing by an integral factor to favour shrink over affine.
* Add support for gamma correction of images with an alpha channel.
### 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.
* Add pre-compiled libvips for ARMv6+ CPUs.
* Ensure 16-bit input images work with embed option.
[#325](https://github.com/lovell/sharp/issues/325)
[@janaz](https://github.com/janaz)
* Allow compilation with gmake to provide FreeBSD support.
[#326](https://github.com/lovell/sharp/issues/326)
[@c0decafe](https://github.com/c0decafe)
* Attempt to remove temporary file after installation.
[#331](https://github.com/lovell/sharp/issues/331)
[@dtoubelis](https://github.com/dtoubelis)
#### v0.12.1 - 12<sup>th</sup> December 2015
* Allow use of SIMD vector instructions (via liborc) to be toggled on/off.

View File

@@ -4,8 +4,8 @@ The typical use case for this high speed Node.js module
is to convert large images of many formats to
smaller, web-friendly JPEG, PNG and WebP images of varying dimensions.
Resizing an image is typically 4x faster than using
the quickest ImageMagick and GraphicsMagick settings.
Resizing an image is typically 4x 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.
@@ -13,7 +13,7 @@ Bicubic interpolation with Lanczos anti-alias filtering ensures quality is not s
As well as image resizing, operations such as
rotation, extraction, compositing and gamma correction are available.
64-bit Windows and recent Linux systems do not require
Most Windows (x64), Linux and ARMv6+ systems do not require
the installation of any external runtime dependencies.
Use with OS X is as simple as running `brew install homebrew/science/vips`
@@ -24,7 +24,7 @@ to install the libvips dependency.
### Formats
This module supports reading JPEG, PNG, WebP, TIFF, OpenSlide,
GIF and other libmagick-supported formats.
GIF and most other libmagick-supported formats.
Output images can be in JPEG, PNG and WebP formats as well as uncompressed raw pixel data.

View File

@@ -14,23 +14,31 @@ 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`.
libvips and its dependencies are fetched and stored within `node_modules/sharp/lib` during `npm install`.
This involves an automated HTTPS download of approximately 6MB.
Most recent 64-bit Linux-based operating systems should "just work", e.g.:
Most recent Linux-based operating systems with glibc running on x64 and ARMv6+ CPUs should "just work", e.g.:
* Debian 7, 8
* Ubuntu 12.04, 14.04, 14.10, 15.04, 15.10
* Centos 7
* Fedora 20, 21
* Fedora 21, 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`. 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.
You can print the detected vips version using: `pkg-config --modversion vips-cpp`
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 and 32-bit Linux-based operating systems,
For older Linux-based operating systems and 32-bit Intel CPUs,
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
@@ -40,9 +48,19 @@ the following command as a user with `sudo` access
curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash -
```
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:
@@ -77,6 +95,15 @@ 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
libvips must be installed before `npm install` is run.
This can be achieved via [FreshPorts](https://www.freshports.org/graphics/vips/):
```sh
cd /usr/ports/graphics/vips/ && make install clean
```
### Heroku
[Alessandro Tagliapietra](https://github.com/alex88) maintains an
@@ -99,8 +126,35 @@ 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 depencies:
```sh
sudo yum-config-manager --enable epel
sudo yum install -y nodejs gcc-c++
curl -s https://www.npmjs.com/install.sh | sudo sh
```
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.
**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)
* [gulp-sharp](https://www.npmjs.com/package/gulp-sharp)
* [grunt-sharp](https://www.npmjs.com/package/grunt-sharp)

View File

@@ -4,16 +4,16 @@
* 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.1.0
* Node.js v5.5.0
### The contenders
* [jimp](https://www.npmjs.com/package/jimp) v0.2.19 - Image processing in pure JavaScript. Bilinear interpolation only.
* [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) @5ab570e - Wrapper around libmagick++, supports Buffers only.
* [imagemagick-native](https://www.npmjs.com/package/imagemagick-native) @47c7329 - 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.12.0 / libvips v8.1.1 - Caching within libvips disabled to ensure a fair comparison.
* sharp v0.13.0 / libvips v8.2.2 - Caching within libvips disabled to ensure a fair comparison.
### The task
@@ -23,19 +23,19 @@ Decompress a 2725x2225 JPEG image, resize to 720x480 using bicubic interpolation
| Module | Input | Output | Ops/sec | Speed-up |
| :----------------- | :----- | :----- | ------: | -------: |
| jimp | file | file | 0.99 | 1.0 |
| jimp | buffer | buffer | 1.05 | 1.1 |
| 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.67 | 1.7 |
| imagemagick | file | file | 5.19 | 5.2 |
| gm | buffer | buffer | 5.56 | 5.6 |
| gm | file | file | 5.59 | 5.6 |
| sharp | stream | stream | 21.91 | 22.1 |
| sharp | file | file | 22.79 | 23.0 |
| sharp | file | buffer | 22.91 | 23.1 |
| sharp | buffer | file | 23.03 | 23.3 |
| sharp | buffer | buffer | 23.15 | 23.4 |
| 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 |
Greater performance can be expected with caching enabled (default) and using 8+ core machines.

333
index.js
View File

@@ -35,9 +35,9 @@ var maximum = {
};
// Constructor-factory
var Sharp = function(input) {
var Sharp = function(input, options) {
if (!(this instanceof Sharp)) {
return new Sharp(input);
return new Sharp(input, options);
}
stream.Duplex.call(this);
this.options = {
@@ -46,6 +46,10 @@ var Sharp = function(input) {
streamIn: false,
sequentialRead: false,
limitInputPixels: maximum.pixels,
density: 72,
rawWidth: 0,
rawHeight: 0,
rawChannels: 0,
// ICC profiles
iccProfilePath: path.join(__dirname, 'icc') + path.sep,
// resize options
@@ -60,11 +64,15 @@ var Sharp = function(input) {
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,
interpolator: 'bicubic',
// operations
@@ -80,9 +88,12 @@ var Sharp = function(input) {
greyscale: false,
normalize: 0,
// overlay
overlayPath: '',
overlayFileIn: '',
overlayBufferIn: null,
overlayGravity: 0,
// output options
output: '__input',
formatOut: 'input',
fileOut: '',
progressive: false,
quality: 80,
compressionLevel: 6,
@@ -101,18 +112,19 @@ var Sharp = function(input) {
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') {
} else if (!isDefined(input)) {
// input=stream
this.options.streamIn = true;
} else {
throw new Error('Unsupported input ' + typeof input);
}
this._inputOptions(options);
return this;
};
module.exports = Sharp;
@@ -133,6 +145,65 @@ module.exports.format = sharp.format();
*/
module.exports.versions = versions;
/*
Validation helpers
*/
var isDefined = function(val) {
return typeof val !== 'undefined' && val !== null;
};
var isObject = function(val) {
return typeof val === 'object';
};
var isBuffer = function(val) {
return typeof val === 'object' && val instanceof Buffer;
};
var isString = function(val) {
return typeof val === 'string' && val.length > 0;
};
var isInteger = function(val) {
return typeof val === 'number' && !Number.isNaN(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
density: DPI at which to load vector images via libmagick
*/
Sharp.prototype._inputOptions = function(options) {
if (isObject(options)) {
// Density
if (isDefined(options.density)) {
if (isInteger(options.density) && inRange(options.density, 1, 2400)) {
this.options.density = options.density;
} else {
throw new Error('Invalid density (1 to 2400) ' + options.density);
}
}
// Raw pixel input
if (isDefined(options.raw)) {
if (
isObject(options.raw) &&
isInteger(options.raw.width) && inRange(options.raw.width, 1, maximum.width) &&
isInteger(options.raw.height) && inRange(options.raw.height, 1, maximum.height) &&
isInteger(options.raw.channels) && inRange(options.raw.channels, 1, 4)
) {
this.options.rawWidth = options.raw.width;
this.options.rawHeight = options.raw.height;
this.options.rawChannels = options.raw.channels;
} else {
throw new Error('Expected width, height and channels for raw pixel input');
}
}
} else if (isDefined(options)) {
throw new Error('Invalid input options ' + options);
}
};
/*
Handle incoming chunk on Writable Stream
*/
@@ -160,46 +231,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 === '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);
@@ -258,14 +336,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;
};
@@ -534,12 +624,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);
}
}
}
@@ -547,27 +637,71 @@ 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);
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);
}
}
}
// 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);
}
this.options.tileOverlap = overlap;
} else {
throw new Error('Invalid tile overlap (0 to 8192) ' + overlap);
}
return this;
};
/*
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 {
throw new Error('Invalid edge extension ' + extend);
}
return this;
};
@@ -596,10 +730,17 @@ Sharp.prototype.resize = function(width, height) {
/*
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);
@@ -610,8 +751,8 @@ Sharp.prototype.limitInputPixels = function(limit) {
/*
Write output image data to a file
*/
Sharp.prototype.toFile = function(output, callback) {
if (!output || output.length === 0) {
Sharp.prototype.toFile = function(fileOut, callback) {
if (!fileOut || fileOut.length === 0) {
var errOutputInvalid = new Error('Invalid output');
if (typeof callback === 'function') {
callback(errOutputInvalid);
@@ -619,7 +760,7 @@ Sharp.prototype.toFile = function(output, callback) {
return BluebirdPromise.reject(errOutputInvalid);
}
} else {
if (this.options.fileIn === output) {
if (this.options.fileIn === fileOut) {
var errOutputIsInput = new Error('Cannot use same file for input and output');
if (typeof callback === 'function') {
callback(errOutputIsInput);
@@ -627,7 +768,7 @@ Sharp.prototype.toFile = function(output, callback) {
return BluebirdPromise.reject(errOutputIsInput);
}
} else {
this.options.output = output;
this.options.fileOut = fileOut;
return this._pipeline(callback);
}
}
@@ -645,7 +786,7 @@ Sharp.prototype.toBuffer = function(callback) {
Force JPEG output
*/
Sharp.prototype.jpeg = function() {
this.options.output = '__jpeg';
this.options.formatOut = 'jpeg';
return this;
};
@@ -653,7 +794,7 @@ Sharp.prototype.jpeg = function() {
Force PNG output
*/
Sharp.prototype.png = function() {
this.options.output = '__png';
this.options.formatOut = 'png';
return this;
};
@@ -661,7 +802,7 @@ Sharp.prototype.png = function() {
Force WebP output
*/
Sharp.prototype.webp = function() {
this.options.output = '__webp';
this.options.formatOut = 'webp';
return this;
};
@@ -669,7 +810,7 @@ Sharp.prototype.webp = function() {
Force raw, uint8 output
*/
Sharp.prototype.raw = function() {
this.options.output = '__raw';
this.options.formatOut = 'raw';
return this;
};
@@ -677,15 +818,17 @@ Sharp.prototype.raw = function() {
Force output to a given format
@param format is either the id as a String or an Object with an 'id' attribute
*/
Sharp.prototype.toFormat = function(format) {
var id = format;
if (typeof format === 'object') {
id = format.id;
Sharp.prototype.toFormat = function(formatOut) {
if (isObject(formatOut) && isDefined(formatOut.id)) {
formatOut = formatOut.id;
}
if (typeof id === 'string' && typeof module.exports.format[id] === 'object' && typeof this[id] === 'function') {
this[id]();
if (
isDefined(formatOut) &&
['jpeg', 'png', 'webp', 'raw', 'tiff', 'dz', 'input'].indexOf(formatOut) !== -1
) {
this.options.formatOut = formatOut;
} else {
throw new Error('Unsupported format ' + format);
throw new Error('Unsupported output format ' + formatOut);
}
return this;
};
@@ -723,10 +866,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);
@@ -734,10 +878,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);
@@ -824,7 +969,6 @@ Sharp.prototype.clone = function() {
// Clone existing options
var clone = new Sharp();
util._extend(clone.options, this.options);
clone.streamIn = false;
// Pass 'finish' event to clone for Stream-based input
this.on('finish', function() {
// Clone inherits input data
@@ -834,18 +978,25 @@ Sharp.prototype.clone = function() {
return clone;
};
/*
Get and set cache memory and item limits
/**
Get and set cache memory, file and item limits
*/
module.exports.cache = function(memory, items) {
if (typeof memory !== 'number' || Number.isNaN(memory)) {
memory = null;
module.exports.cache = function(options) {
if (typeof options === 'boolean') {
if (options) {
// Default cache settings of 50MB, 20 files, 100 items
return sharp.cache(50, 20, 100);
} else {
return sharp.cache(0, 0, 0);
}
} else if (typeof options === 'object') {
return sharp.cache(options.memory, options.files, options.items);
} else {
return sharp.cache();
}
if (typeof items !== 'number' || Number.isNaN(items)) {
items = null;
}
return sharp.cache(memory, items);
};
// Ensure default cache settings are set
module.exports.cache(true);
/*
Get and set size of thread pool

View File

@@ -1,6 +1,6 @@
{
"name": "sharp",
"version": "0.12.1",
"version": "0.14.1",
"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",
"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"
@@ -47,28 +51,29 @@
"vips"
],
"dependencies": {
"bluebird": "^3.0.5",
"color": "^0.10.1",
"nan": "^2.1.0",
"bluebird": "^3.3.5",
"color": "^0.11.1",
"nan": "^2.2.1",
"semver": "^5.1.0",
"request": "^2.67.0",
"request": "^2.71.0",
"tar": "^2.2.1"
},
"devDependencies": {
"async": "^1.5.0",
"coveralls": "^2.11.6",
"async": "^1.5.2",
"bufferutil": "^1.2.1",
"coveralls": "^2.11.9",
"exif-reader": "^1.0.0",
"icc": "^0.0.2",
"istanbul": "^0.4.0",
"mocha": "^2.3.4",
"mocha-jshint": "^2.2.5",
"istanbul": "^0.4.3",
"mocha": "^2.4.5",
"mocha-jshint": "^2.3.1",
"node-cpplint": "^0.4.0",
"rimraf": "^2.4.4",
"bufferutil": "^1.2.1"
"rimraf": "^2.5.2",
"unzip": "^0.1.11"
},
"license": "Apache-2.0",
"config": {
"libvips": "8.1.1"
"libvips": "8.2.3"
},
"engines": {
"node": ">=0.10"

34
packaging/arm-build.sh Executable file
View File

@@ -0,0 +1,34 @@
#!/bin/sh
if [ $# -lt 1 ]; then
echo "Usage: $0 IP"
echo "Build libvips for ARM using Docker, where IP is"
echo "the address of a Raspberry Pi running HypriotOS"
exit 1
fi
IP="$1"
echo "Verifying connectivity to $IP"
if ! ping -c 1 $IP; then
echo "Could not connect to $IP"
exit 1
fi
if ! type sshpass >/dev/null; then
echo "Please install sshpass"
exit 1
fi
export SSHPASS=hypriot
echo "Copying arm/Dockerfile and arm/build.sh to device"
sshpass -e scp -o PreferredAuthentications=password -r arm root@${IP}:/root
echo "Building Raspbian-based container"
sshpass -e ssh -o PreferredAuthentications=password -t root@${IP} "docker build -t vips-dev-arm arm"
echo "Running arm/build.sh within container"
sshpass -e ssh -o PreferredAuthentications=password -t root@${IP} "docker run -i -t --rm -v \${PWD}/arm:/arm vips-dev-arm sh -c 'cd /arm && ./build.sh' | tee arm/build.log"
echo "Copying resultant tar.gz file from device"
sshpass -e scp -o PreferredAuthentications=password root@${IP}:/root/arm/*.tar.gz .

28
packaging/arm-test.sh Executable file
View File

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

5
packaging/arm/Dockerfile Normal file
View File

@@ -0,0 +1,5 @@
FROM resin/rpi-raspbian:jessie
MAINTAINER Lovell Fuller <npm@lovell.info>
# Build dependencies
RUN apt-get update && apt-get install -y build-essential autoconf libtool nasm gtk-doc-tools texinfo curl

137
packaging/arm/build.sh Executable file
View File

@@ -0,0 +1,137 @@
#!/bin/sh
# To be run inside a Raspbian container
# Working directories
DEPS=/deps
TARGET=/target
mkdir ${DEPS}
mkdir ${TARGET}
# Common build paths and flags
export PKG_CONFIG_PATH="${PKG_CONFIG_PATH}:${TARGET}/lib/pkgconfig"
export PATH="${PATH}:${TARGET}/bin"
export CPPFLAGS="-I${TARGET}/include"
export LDFLAGS="-L${TARGET}/lib"
export CFLAGS="-O3"
export CXXFLAGS="-O3"
# Dependency version numbers
VERSION_ZLIB=1.2.8
VERSION_FFI=3.2.1
VERSION_GLIB=2.47.6
VERSION_XML2=2.9.3
VERSION_GSF=1.14.34
VERSION_EXIF=0.6.21
VERSION_LCMS2=2.7
VERSION_GM=1.3.23
VERSION_JPEG=1.4.2
VERSION_PNG16=1.6.21
VERSION_WEBP=0.5.0
VERSION_TIFF=4.0.6
VERSION_ORC=0.4.25
VERSION_VIPS=8.2.3
mkdir ${DEPS}/zlib
curl -Ls http://zlib.net/zlib-${VERSION_ZLIB}.tar.xz | tar xJC ${DEPS}/zlib --strip-components=1
cd ${DEPS}/zlib
./configure --prefix=${TARGET} && make install
rm ${TARGET}/lib/libz.a
mkdir ${DEPS}/ffi
curl -Ls ftp://sourceware.org/pub/libffi/libffi-${VERSION_FFI}.tar.gz | tar xzC ${DEPS}/ffi --strip-components=1
cd ${DEPS}/ffi
./configure --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
cd ${DEPS}/glib
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --with-pcre=internal && make install-strip
mkdir ${DEPS}/xml2
curl -Ls http://xmlsoft.org/sources/libxml2-${VERSION_XML2}.tar.gz | tar xzC ${DEPS}/xml2 --strip-components=1
cd ${DEPS}/xml2
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --without-python --with-zlib=${TARGET} && make install-strip
mkdir ${DEPS}/gsf
curl -Ls https://download.gnome.org/sources/libgsf/1.14/libgsf-${VERSION_GSF}.tar.xz | tar xJC ${DEPS}/gsf --strip-components=1
cd ${DEPS}/gsf
./configure --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
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
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
cd ${DEPS}/jpeg
./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
cd ${DEPS}/png16
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
mkdir ${DEPS}/webp
curl -Ls http://downloads.webmproject.org/releases/webp/libwebp-${VERSION_WEBP}.tar.gz | tar xzC ${DEPS}/webp --strip-components=1
cd ${DEPS}/webp
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
mkdir ${DEPS}/tiff
curl -Ls http://download.osgeo.org/libtiff/tiff-${VERSION_TIFF}.tar.gz | tar xzC ${DEPS}/tiff --strip-components=1
cd ${DEPS}/tiff
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
rm ${TARGET}/lib/libtiffxx*
mkdir ${DEPS}/orc
curl -Ls http://gstreamer.freedesktop.org/data/src/orc/orc-${VERSION_ORC}.tar.xz | tar xJC ${DEPS}/orc --strip-components=1
cd ${DEPS}/orc
./configure --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
cd ${DEPS}/vips
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--disable-debug --disable-introspection --without-python --without-fftw --with-magickpackage=GraphicsMagick \
--with-zip-includes=${TARGET}/include --with-zip-libraries=${TARGET}/lib \
--with-jpeg-includes=${TARGET}/include --with-jpeg-libraries=${TARGET}/lib \
&& make install-strip
# Remove the old C++ bindings
cd ${TARGET}/include
rm -rf vips/vipsc++.h vips/vipscpp.h
cd ${TARGET}/lib
rm -rf pkgconfig .libs *.la libvipsCC*
# Create JSON file of version numbers
cd ${TARGET}
echo "{\n\
\"exif\": \"${VERSION_EXIF}\",\n\
\"ffi\": \"${VERSION_FFI}\",\n\
\"glib\": \"${VERSION_GLIB}\",\n\
\"gsf\": \"${VERSION_GSF}\",\n\
\"jpeg\": \"${VERSION_JPEG}\",\n\
\"lcms\": \"${VERSION_LCMS2}\",\n\
\"gm\": \"${VERSION_GM}\",\n\
\"orc\": \"${VERSION_ORC}\",\n\
\"png\": \"${VERSION_PNG16}\",\n\
\"tiff\": \"${VERSION_TIFF}\",\n\
\"vips\": \"${VERSION_VIPS}\"\n\
\"webp\": \"${VERSION_WEBP}\",\n\
\"xml\": \"${VERSION_XML2}\",\n\
\"zlib\": \"${VERSION_ZLIB}\",\n\
}" >lib/versions.json
# Create .tar.gz
GZIP=-9 tar czf /arm/libvips-${VERSION_VIPS}-arm.tar.gz include lib

View File

@@ -1,5 +1,7 @@
#!/bin/sh
VERSION_VIPS=8.2.3
# Is docker available?
if ! type docker >/dev/null; then
@@ -13,15 +15,15 @@ fi
docker build -t vips-dev-win win
WIN_CONTAINER_ID=$(docker run -d vips-dev-win)
docker cp $WIN_CONTAINER_ID:/libvips-8.1.1-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.1.1-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

@@ -4,32 +4,34 @@ MAINTAINER Lovell Fuller <npm@lovell.info>
# Build dependencies
RUN apt-get update && apt-get install -y build-essential autoconf libtool nasm gtk-doc-tools texinfo
# Working directories
ENV DEPS /deps
ENV TARGET /target
# Create working directories
ENV DEPS=/deps \
TARGET=/target
RUN mkdir ${DEPS} && mkdir ${TARGET}
# Common build paths and flags
ENV PKG_CONFIG_PATH ${PKG_CONFIG_PATH}:${TARGET}/lib/pkgconfig
ENV PATH ${PATH}:${TARGET}/bin
ENV CPPFLAGS -I${TARGET}/include
ENV 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
ENV VERSION_FFI 3.2.1
ENV VERSION_GLIB 2.46.2
ENV VERSION_XML2 2.9.2
ENV VERSION_GSF 1.14.34
ENV VERSION_EXIF 0.6.21
ENV VERSION_JPEG 1.4.2
ENV VERSION_PNG16 1.6.19
ENV VERSION_LCMS2 2.7
ENV VERSION_WEBP 0.4.4
ENV VERSION_TIFF 4.0.6
ENV VERSION_MAGICK 6.9.2-6
ENV VERSION_ORC 0.4.24
ENV VERSION_VIPS 8.1.1
ENV VERSION_ZLIB=1.2.8 \
VERSION_FFI=3.2.1 \
VERSION_GLIB=2.47.6 \
VERSION_XML2=2.9.3 \
VERSION_GSF=1.14.34 \
VERSION_EXIF=0.6.21 \
VERSION_LCMS2=2.7 \
VERSION_GM=1.3.23 \
VERSION_JPEG=1.4.2 \
VERSION_PNG16=1.6.21 \
VERSION_WEBP=0.5.0 \
VERSION_TIFF=4.0.6 \
VERSION_ORC=0.4.25 \
VERSION_VIPS=8.2.3
RUN mkdir ${DEPS}/zlib
RUN curl -Ls http://zlib.net/zlib-${VERSION_ZLIB}.tar.xz | tar xJC ${DEPS}/zlib --strip-components=1
@@ -43,9 +45,9 @@ 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 http://ftp.gnome.org/pub/gnome/sources/glib/2.46/glib-${VERSION_GLIB}.tar.xz | tar xJC ${DEPS}/glib --strip-components=1
RUN curl -Ls https://download.gnome.org/sources/glib/2.47/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 && make install-strip
RUN ./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
@@ -53,83 +55,83 @@ WORKDIR ${DEPS}/xml2
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --without-python --with-zlib=${TARGET} && make install-strip
RUN mkdir ${DEPS}/gsf
RUN curl -Ls http://ftp.gnome.org/pub/GNOME/sources/libgsf/1.14/libgsf-${VERSION_GSF}.tar.xz | tar xJC ${DEPS}/gsf --strip-components=1
RUN curl -Ls https://download.gnome.org/sources/libgsf/1.14/libgsf-${VERSION_GSF}.tar.xz | tar xJC ${DEPS}/gsf --strip-components=1
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://kent.dl.sourceforge.net/project/libexif/libexif/${VERSION_EXIF}/libexif-${VERSION_EXIF}.tar.bz2 | tar xjC ${DEPS}/exif --strip-components=1
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
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
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://kent.dl.sourceforge.net/project/libjpeg-turbo/${VERSION_JPEG}/libjpeg-turbo-${VERSION_JPEG}.tar.gz | tar xzC ${DEPS}/jpeg --strip-components=1
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
WORKDIR ${DEPS}/jpeg
RUN ./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://kent.dl.sourceforge.net/project/libpng/libpng16/${VERSION_PNG16}/libpng-${VERSION_PNG16}.tar.xz | tar xJC ${DEPS}/png16 --strip-components=1
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
WORKDIR ${DEPS}/png16
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
RUN mkdir ${DEPS}/lcms2
RUN curl -Ls http://kent.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}/webp
RUN curl -Ls http://downloads.webmproject.org/releases/webp/libwebp-${VERSION_WEBP}.tar.gz | tar xzC ${DEPS}/webp --strip-components=1
WORKDIR ${DEPS}/webp
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
RUN mkdir ${DEPS}/tiff
RUN curl -Ls http://download.osgeo.org/libtiff/tiff-${VERSION_TIFF}.tar.gz /deps/tiff.tar.gz | tar xzC ${DEPS}/tiff --strip-components=1
RUN curl -Ls http://download.osgeo.org/libtiff/tiff-${VERSION_TIFF}.tar.gz | tar xzC ${DEPS}/tiff --strip-components=1
WORKDIR ${DEPS}/tiff
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
RUN rm ${TARGET}/lib/libtiffxx*
RUN mkdir ${DEPS}/magick
RUN curl -Ls http://www.imagemagick.org/download/releases/ImageMagick-${VERSION_MAGICK}.tar.xz | tar xJC ${DEPS}/magick --strip-components=1
WORKDIR ${DEPS}/magick
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --without-magick-plus-plus && make install-strip
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 mkdir ${DEPS}/vips
RUN curl -Ls http://www.vips.ecs.soton.ac.uk/supported/8.1/vips-${VERSION_VIPS}.tar.gz | tar xzC ${DEPS}/vips --strip-components=1
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
WORKDIR ${DEPS}/vips
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--disable-debug --disable-introspection --without-python --without-fftw \
--disable-debug --disable-introspection --without-python --without-fftw --with-magickpackage=GraphicsMagick \
--with-zip-includes=${TARGET}/include --with-zip-libraries=${TARGET}/lib \
--with-jpeg-includes=${TARGET}/include --with-jpeg-libraries=${TARGET}/lib \
&& make install-strip
# Remove the C++ bindings
# Remove the old C++ bindings
WORKDIR ${TARGET}/include
RUN rm -rf vips/vipsc++.h vips/vipscpp.h vips/V*.h
RUN rm -rf vips/vipsc++.h vips/vipscpp.h
WORKDIR ${TARGET}/lib
RUN rm -rf pkgconfig .libs *.la libvipsCC* libvips-cpp.*
RUN rm -rf pkgconfig .libs *.la libvipsCC*
# Create JSON file of version numbers
WORKDIR ${TARGET}
RUN echo "{\n\
\"zlib\": \"${VERSION_ZLIB}\",\n\
\"exif\": \"${VERSION_EXIF}\",\n\
\"ffi\": \"${VERSION_FFI}\",\n\
\"glib\": \"${VERSION_GLIB}\",\n\
\"xml\": \"${VERSION_XML2}\",\n\
\"gsf\": \"${VERSION_GSF}\",\n\
\"exif\": \"${VERSION_EXIF}\",\n\
\"jpeg\": \"${VERSION_JPEG}\",\n\
\"png\": \"${VERSION_PNG16}\",\n\
\"lcms\": \"${VERSION_LCMS2}\",\n\
\"webp\": \"${VERSION_WEBP}\",\n\
\"tiff\": \"${VERSION_TIFF}\",\n\
\"magick\": \"${VERSION_MAGICK}\",\n\
\"gm\": \"${VERSION_GM}\",\n\
\"orc\": \"${VERSION_ORC}\",\n\
\"png\": \"${VERSION_PNG16}\",\n\
\"tiff\": \"${VERSION_TIFF}\",\n\
\"vips\": \"${VERSION_VIPS}\"\n\
\"webp\": \"${VERSION_WEBP}\",\n\
\"xml\": \"${VERSION_XML2}\",\n\
\"zlib\": \"${VERSION_ZLIB}\",\n\
}" >lib/versions.json
# Create .tar.gz

View File

@@ -6,13 +6,13 @@ if ! type docker >/dev/null; then
exit 1
fi
test="npm run clean; NODE_ENV=development npm install --unsafe-perm; npm test"
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
echo "Testing $dist..."
if docker run -i -t --rm -v $PWD:/v 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:0.12 >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
@@ -20,7 +20,7 @@ done
# Centos 6
echo "Testing centos6..."
if docker run -i -t --rm -v $PWD:/v nodesource/centos6:0.12 >packaging/centos6.log 2>&1 sh -c "cd /v; source ./packaging/test/centos6.sh; ./preinstall.sh; $test";
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
@@ -29,7 +29,7 @@ fi
# Fedora 20, 21
for dist in centos7 fedora20 fedora21; do
echo "Testing $dist..."
if docker run -i -t --rm -v $PWD:/v nodesource/$dist:0.12 >packaging/$dist.log 2>&1 sh -c "cd /v; $test";
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
@@ -48,3 +48,10 @@ if docker run -i -t --rm -v $PWD:/v base/archlinux:2015.06.01 >packaging/archlin
then echo "archlinux OK"
else echo "archlinux fail" && cat packaging/archlinux.log
fi
# Alpine
echo "Testing alpine..."
if docker run -i -t --rm -v $PWD:/v -e "SHARP_TEST_WITHOUT_CACHE=0" alpine:edge >packaging/alpine.log 2>&1 sh -c "cd /v; ./packaging/test/alpine.sh; $test";
then echo "alpine OK"
else echo "alpine fail" && cat packaging/alpine.log
fi

7
packaging/test/alpine.sh Executable file
View File

@@ -0,0 +1,7 @@
#!/bin/sh
# Install build dependencies
apk add --update make gcc g++ python nodejs
# Install libvips with build headers and dependencies
apk add libvips-dev --update --repository https://s3.amazonaws.com/wjordan-apk --allow-untrusted

View File

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

View File

@@ -3,14 +3,18 @@ MAINTAINER Lovell Fuller <npm@lovell.info>
RUN apt-get update && apt-get install -y curl zip
ENV VERSION_VIPS=8.2.3
# Fetch and unzip
RUN mkdir /vips
WORKDIR /vips
RUN curl -O http://www.vips.ecs.soton.ac.uk/supported/8.1/win32/vips-dev-w64-8.1.1-3.zip
RUN unzip vips-dev-w64-8.1.1-3.zip
RUN curl -O http://www.vips.ecs.soton.ac.uk/supported/8.2/win32/vips-dev-w64-8.2.zip
RUN unzip vips-dev-w64-8.2.zip
# Clean and zip
WORKDIR /vips/vips-dev-8.1.1
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
RUN cp bin/*.dll lib/
RUN GZIP=-9 tar czf /libvips-8.1.1-win.tar.gz include lib/glib-2.0 lib/libvips.lib lib/libglib-2.0.lib lib/libgobject-2.0.lib lib/*.dll
RUN cp -r lib64/* lib/
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,7 +1,14 @@
#!/bin/sh
# Ensures libvips is installed and attempts to install it if not
# Currently supports:
# This script is no longer required on most
# 64-bit Linux systems when using sharp v0.12.0+
# See http://sharp.dimens.io/page/install#linux
# 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
@@ -9,13 +16,13 @@
# * Elementary 0.3
# * Red Hat Linux
# * RHEL/Centos/Scientific 6, 7
# * Fedora 21, 22
# * Fedora 21, 22, 23
# * Amazon Linux 2015.03, 2015.09
# * OpenSuse 13
vips_version_minimum=7.40.0
vips_version_latest_major_minor=8.1
vips_version_latest_patch=1
vips_version_minimum=8.2.3
vips_version_latest_major_minor=8.2
vips_version_latest_patch=3
openslide_version_minimum=3.4.0
openslide_version_latest_major_minor=3.4
@@ -119,6 +126,11 @@ if [ "$(id -u)" -ne "0" ]; then
exit 1
fi
# Deprecation warning
if [ "$(arch)" == "x86_64" ]; then
echo "This script is no longer required on most 64-bit Linux systems when using sharp v0.12.0+"
fi
# OS-specific installations of libopenslide follows
# Either openslide does not exist, or vips is installed without openslide support
if [ $enable_openslide -eq 1 ] && [ -z $vips_with_openslide ] && [ $openslide_exists -eq 0 ]; then
@@ -211,18 +223,8 @@ if [ -f /etc/debian_version ]; then
DISTRO=$(lsb_release -c -s)
echo "Detected Debian Linux '$DISTRO'"
case "$DISTRO" in
jessie|vivid|wily)
# Debian 8, Ubuntu 15
if [ $enable_openslide -eq 1 ]; then
echo "Recompiling vips with openslide support"
install_libvips_from_source
else
echo "Installing libvips via apt-get"
apt-get install -y libvips-dev libgsf-1-dev
fi
;;
trusty|utopic|qiana|rebecca|rafaela|freya)
# Ubuntu 14, Mint 17
jessie|trusty|utopic|vivid|wily|xenial|qiana|rebecca|rafaela|freya)
# Debian 8, Ubuntu 14.04+, Mint 17
echo "Installing libvips dependencies via apt-get"
apt-get install -y automake build-essential gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg-dev libpng12-dev libwebp-dev libtiff5-dev libexif-dev libgsf-1-dev liblcms2-dev libxml2-dev swig libmagickcore-dev curl
install_libvips_from_source
@@ -263,18 +265,12 @@ elif [ -f /etc/redhat-release ]; then
yum install -y --enablerepo=remi libwebp-devel
install_libvips_from_source "--prefix=/usr"
;;
"Fedora release 21 "*|"Fedora release 22 "*)
# Fedora 21, 22
if [ $enable_openslide -eq 1 ]; then
echo "Installing libvips dependencies via yum"
yum groupinstall -y "Development Tools"
yum install -y gcc-c++ gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel lcms-devel ImageMagick-devel gobject-introspection-devel libwebp-devel curl
echo "Compiling vips with openslide support"
install_libvips_from_source "--prefix=/usr"
else
echo "Installing libvips via yum"
yum install -y vips-devel
fi
"Fedora"*)
# Fedora 21, 22, 23
echo "Installing libvips dependencies via yum"
yum groupinstall -y "Development Tools"
yum install -y gcc-c++ gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel lcms-devel ImageMagick-devel gobject-introspection-devel libwebp-devel curl
install_libvips_from_source "--prefix=/usr"
;;
*)
# Unsupported RHEL-based OS

View File

@@ -1,28 +1,30 @@
#include <cstdlib>
#include <string>
#include <string.h>
#include <vips/vips.h>
#include <vips/vips8>
#include "common.h"
// Verify platform and compiler compatibility
#if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 1 && VIPS_PATCH_VERSION < 1))
#error libvips version 8.1.1+ required - see http://sharp.dimens.io/page/install
#if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 2))
#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
#define EXIF_IFD0_ORIENTATION "exif-ifd0-Orientation"
using vips::VImage;
namespace sharp {
// How many tasks are in the queue?
@@ -50,6 +52,29 @@ 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.
*/
std::string ImageTypeId(ImageType const imageType) {
std::string id;
switch (imageType) {
case ImageType::JPEG: id = "jpeg"; break;
case ImageType::PNG: id = "png"; break;
case ImageType::WEBP: id = "webp"; break;
case ImageType::TIFF: id = "tiff"; break;
case ImageType::MAGICK: id = "magick"; break;
case ImageType::OPENSLIDE: id = "openslide"; break;
case ImageType::PPM: id = "ppm"; break;
case ImageType::FITS: id = "fits"; break;
case ImageType::RAW: id = "raw"; break;
case ImageType::UNKNOWN: id = "unknown"; break;
}
return id;
}
/*
Determine image format of a buffer.
@@ -58,7 +83,7 @@ namespace sharp {
ImageType imageType = ImageType::UNKNOWN;
char const *load = vips_foreign_find_load_buffer(buffer, length);
if (load != NULL) {
std::string loader = load;
std::string const loader = load;
if (EndsWith(loader, "JpegBuffer")) {
imageType = ImageType::JPEG;
} else if (EndsWith(loader, "PngBuffer")) {
@@ -74,13 +99,6 @@ namespace sharp {
return imageType;
}
/*
Initialise and return a VipsImage from a buffer. Supports JPEG, PNG, WebP and TIFF.
*/
VipsImage* InitImage(void *buffer, size_t const length, VipsAccess const access) {
return vips_image_new_from_buffer(buffer, length, nullptr, "access", access, nullptr);
}
/*
Determine image format, reads the first few bytes of the file
*/
@@ -88,7 +106,7 @@ namespace sharp {
ImageType imageType = ImageType::UNKNOWN;
char const *load = vips_foreign_find_load(file);
if (load != nullptr) {
std::string loader = load;
std::string const loader = load;
if (EndsWith(loader, "JpegFile")) {
imageType = ImageType::JPEG;
} else if (EndsWith(loader, "Png")) {
@@ -99,6 +117,10 @@ namespace sharp {
imageType = ImageType::OPENSLIDE;
} else if (EndsWith(loader, "TiffFile")) {
imageType = ImageType::TIFF;
} else if (EndsWith(loader, "Ppm")) {
imageType = ImageType::PPM;
} else if (EndsWith(loader, "Fits")) {
imageType = ImageType::FITS;
} else if (EndsWith(loader, "Magick") || EndsWith(loader, "MagickFile")) {
imageType = ImageType::MAGICK;
}
@@ -106,43 +128,37 @@ namespace sharp {
return imageType;
}
/*
Initialise and return a VipsImage from a file.
*/
VipsImage* InitImage(char const *file, VipsAccess const access) {
return vips_image_new_from_file(file, "access", access, nullptr);
}
/*
Does this image have an embedded profile?
*/
bool HasProfile(VipsImage *image) {
return (vips_image_get_typeof(image, VIPS_META_ICC_NAME) > 0) ? TRUE : FALSE;
bool HasProfile(VImage image) {
return (image.get_typeof(VIPS_META_ICC_NAME) != 0) ? TRUE : FALSE;
}
/*
Does this image have an alpha channel?
Uses colour space interpretation with number of channels to guess this.
*/
bool HasAlpha(VipsImage *image) {
bool HasAlpha(VImage image) {
int const bands = image.bands();
VipsInterpretation const interpretation = image.interpretation();
return (
(image->Bands == 2 && image->Type == VIPS_INTERPRETATION_B_W) ||
(image->Bands == 4 && image->Type != VIPS_INTERPRETATION_CMYK) ||
(image->Bands == 5 && image->Type == VIPS_INTERPRETATION_CMYK)
(bands == 2 && interpretation == VIPS_INTERPRETATION_B_W) ||
(bands == 4 && interpretation != VIPS_INTERPRETATION_CMYK) ||
(bands == 5 && interpretation == VIPS_INTERPRETATION_CMYK)
);
}
/*
Get EXIF Orientation of image, if any.
*/
int ExifOrientation(VipsImage const *image) {
int ExifOrientation(VImage image) {
int orientation = 0;
const char *exif;
if (
vips_image_get_typeof(image, EXIF_IFD0_ORIENTATION) != 0 &&
!vips_image_get_string(image, EXIF_IFD0_ORIENTATION, &exif)
) {
orientation = atoi(&exif[0]);
if (image.get_typeof(EXIF_IFD0_ORIENTATION) != 0) {
char const *exif = image.get_string(EXIF_IFD0_ORIENTATION);
if (exif != nullptr) {
orientation = atoi(&exif[0]);
}
}
return orientation;
}
@@ -150,31 +166,41 @@ namespace sharp {
/*
Set EXIF Orientation of image.
*/
void SetExifOrientation(VipsImage *image, int const orientation) {
void SetExifOrientation(VImage image, int const orientation) {
char exif[3];
g_snprintf(exif, sizeof(exif), "%d", orientation);
vips_image_set_string(image, EXIF_IFD0_ORIENTATION, exif);
image.set(EXIF_IFD0_ORIENTATION, exif);
}
/*
Remove EXIF Orientation from image.
*/
void RemoveExifOrientation(VipsImage *image) {
void RemoveExifOrientation(VImage image) {
SetExifOrientation(image, 0);
}
/*
Returns the window size for the named interpolator. For example,
a window size of 3 means a 3x3 pixel grid is used for the calculation.
Does this image have a non-default density?
*/
int InterpolatorWindowSize(char const *name) {
VipsInterpolate *interpolator = vips_interpolate_new(name);
if (interpolator == nullptr) {
return -1;
}
int window_size = vips_interpolate_get_window_size(interpolator);
g_object_unref(interpolator);
return window_size;
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");
}
/*
@@ -186,4 +212,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,11 @@
#define SRC_COMMON_H_
#include <string>
#include <tuple>
#include <vips/vips8>
using vips::VImage;
namespace sharp {
@@ -12,7 +17,10 @@ namespace sharp {
WEBP,
TIFF,
MAGICK,
OPENSLIDE
OPENSLIDE,
PPM,
FITS,
RAW
};
// How many tasks are in the queue?
@@ -27,6 +35,12 @@ 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.
*/
std::string ImageTypeId(ImageType const imageType);
/*
Determine image format of a buffer.
@@ -38,53 +52,59 @@ namespace sharp {
*/
ImageType DetermineImageType(char const *file);
/*
Initialise and return a VipsImage from a buffer. Supports JPEG, PNG, WebP and TIFF.
*/
VipsImage* InitImage(void *buffer, size_t const length, VipsAccess const access);
/*
Initialise and return a VipsImage from a file.
*/
VipsImage* InitImage(char const *file, VipsAccess const access);
/*
Does this image have an embedded profile?
*/
bool HasProfile(VipsImage *image);
bool HasProfile(VImage image);
/*
Does this image have an alpha channel?
Uses colour space interpretation with number of channels to guess this.
*/
bool HasAlpha(VipsImage *image);
bool HasAlpha(VImage image);
/*
Get EXIF Orientation of image, if any.
*/
int ExifOrientation(VipsImage const *image);
int ExifOrientation(VImage image);
/*
Set EXIF Orientation of image.
*/
void SetExifOrientation(VipsImage *image, int const orientation);
void SetExifOrientation(VImage image, int const orientation);
/*
Remove EXIF Orientation from image.
*/
void RemoveExifOrientation(VipsImage *image);
void RemoveExifOrientation(VImage image);
/*
Returns the window size for the named interpolator. For example,
a window size of 3 means a 3x3 pixel grid is used for the calculation.
Does this image have a non-default density?
*/
int InterpolatorWindowSize(char const *name);
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

@@ -0,0 +1,52 @@
// Code for error type
/*
Copyright (C) 1991-2001 The National Gallery
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
*/
/*
These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/intl.h>
#include <iostream>
#include <vips/vips8>
VIPS_NAMESPACE_START
std::ostream &operator<<( std::ostream &file, const VError &err )
{
err.ostream_print( file );
return( file );
}
void VError::ostream_print( std::ostream &file ) const
{
file << _what;
}
VIPS_NAMESPACE_END

View File

@@ -0,0 +1,714 @@
/* Object part of VImage class
*
* 30/12/14
* - allow set enum value from string
*/
/*
Copyright (C) 1991-2001 The National Gallery
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
*/
/*
These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/intl.h>
#include <vips/vips8>
#include <vips/debug.h>
/*
#define VIPS_DEBUG
#define VIPS_DEBUG_VERBOSE
*/
VIPS_NAMESPACE_START
std::vector<double>
to_vectorv( int n, ... )
{
std::vector<double> vector( n );
va_list ap;
va_start( ap, n );
for( int i = 0; i < n; i++ )
vector[i] = va_arg( ap, double );
va_end( ap );
return( vector );
}
std::vector<double>
to_vector( double value )
{
return( to_vectorv( 1, value ) );
}
std::vector<double>
to_vector( int n, double array[] )
{
std::vector<double> vector( n );
for( int i = 0; i < n; i++ )
vector[i] = array[i];
return( vector );
}
std::vector<double>
negate( std::vector<double> vector )
{
std::vector<double> new_vector( vector.size() );
for( unsigned int i = 0; i < vector.size(); i++ )
new_vector[i] = vector[i] * -1;
return( new_vector );
}
std::vector<double>
invert( std::vector<double> vector )
{
std::vector<double> new_vector( vector.size() );
for( unsigned int i = 0; i < vector.size(); i++ )
new_vector[i] = 1.0 / vector[i];
return( new_vector );
}
VOption::~VOption()
{
std::list<Pair *>::iterator i;
for( i = options.begin(); i != options.end(); ++i )
delete *i;
}
// input bool
VOption *
VOption::set( const char *name, bool value )
{
Pair *pair = new Pair( name );
pair->input = true;
g_value_init( &pair->value, G_TYPE_BOOLEAN );
g_value_set_boolean( &pair->value, value );
options.push_back( pair );
return( this );
}
// input int ... this path is used for enums as well
VOption *
VOption::set( const char *name, int value )
{
Pair *pair = new Pair( name );
pair->input = true;
g_value_init( &pair->value, G_TYPE_INT );
g_value_set_int( &pair->value, value );
options.push_back( pair );
return( this );
}
// input double
VOption *
VOption::set( const char *name, double value )
{
Pair *pair = new Pair( name );
pair->input = true;
g_value_init( &pair->value, G_TYPE_DOUBLE );
g_value_set_double( &pair->value, value );
options.push_back( pair );
return( this );
}
VOption *
VOption::set( const char *name, const char *value )
{
Pair *pair = new Pair( name );
pair->input = true;
g_value_init( &pair->value, G_TYPE_STRING );
g_value_set_string( &pair->value, value );
options.push_back( pair );
return( this );
}
// input image
VOption *
VOption::set( const char *name, VImage value )
{
Pair *pair = new Pair( name );
pair->input = true;
g_value_init( &pair->value, VIPS_TYPE_IMAGE );
g_value_set_object( &pair->value, value.get_image() );
options.push_back( pair );
return( this );
}
// input double array
VOption *
VOption::set( const char *name, std::vector<double> value )
{
Pair *pair = new Pair( name );
double *array;
unsigned int i;
pair->input = true;
g_value_init( &pair->value, VIPS_TYPE_ARRAY_DOUBLE );
vips_value_set_array_double( &pair->value, NULL,
static_cast< int >( value.size() ) );
array = vips_value_get_array_double( &pair->value, NULL );
for( i = 0; i < value.size(); i++ )
array[i] = value[i];
options.push_back( pair );
return( this );
}
// input image array
VOption *
VOption::set( const char *name, std::vector<VImage> value )
{
Pair *pair = new Pair( name );
VipsImage **array;
unsigned int i;
pair->input = true;
g_value_init( &pair->value, VIPS_TYPE_ARRAY_IMAGE );
vips_value_set_array_image( &pair->value,
static_cast< int >( value.size() ) );
array = vips_value_get_array_image( &pair->value, NULL );
for( i = 0; i < value.size(); i++ ) {
VipsImage *vips_image = value[i].get_image();
array[i] = vips_image;
g_object_ref( vips_image );
}
options.push_back( pair );
return( this );
}
// input blob
VOption *
VOption::set( const char *name, VipsBlob *value )
{
Pair *pair = new Pair( name );
pair->input = true;
g_value_init( &pair->value, VIPS_TYPE_BLOB );
g_value_set_boxed( &pair->value, value );
options.push_back( pair );
return( this );
}
// output bool
VOption *
VOption::set( const char *name, bool *value )
{
Pair *pair = new Pair( name );
pair->input = false;
pair->vbool = value;
g_value_init( &pair->value, G_TYPE_BOOLEAN );
options.push_back( pair );
return( this );
}
// output int
VOption *
VOption::set( const char *name, int *value )
{
Pair *pair = new Pair( name );
pair->input = false;
pair->vint = value;
g_value_init( &pair->value, G_TYPE_INT );
options.push_back( pair );
return( this );
}
// output double
VOption *
VOption::set( const char *name, double *value )
{
Pair *pair = new Pair( name );
pair->input = false;
pair->vdouble = value;
g_value_init( &pair->value, G_TYPE_DOUBLE );
options.push_back( pair );
return( this );
}
// output image
VOption *
VOption::set( const char *name, VImage *value )
{
Pair *pair = new Pair( name );
pair->input = false;
pair->vimage = value;
g_value_init( &pair->value, VIPS_TYPE_IMAGE );
options.push_back( pair );
return( this );
}
// output doublearray
VOption *
VOption::set( const char *name, std::vector<double> *value )
{
Pair *pair = new Pair( name );
pair->input = false;
pair->vvector = value;
g_value_init( &pair->value, VIPS_TYPE_ARRAY_DOUBLE );
options.push_back( pair );
return( this );
}
// output blob
VOption *
VOption::set( const char *name, VipsBlob **value )
{
Pair *pair = new Pair( name );
pair->input = false;
pair->vblob = value;
g_value_init( &pair->value, VIPS_TYPE_BLOB );
options.push_back( pair );
return( this );
}
// just g_object_set_property(), except we allow set enum from string
static void
set_property( VipsObject *object, const char *name, const GValue *value )
{
VipsObjectClass *object_class = VIPS_OBJECT_GET_CLASS( object );
GType type = G_VALUE_TYPE( value );
GParamSpec *pspec;
VipsArgumentClass *argument_class;
VipsArgumentInstance *argument_instance;
if( vips_object_get_argument( object, name,
&pspec, &argument_class, &argument_instance ) ) {
vips_warn( NULL, "%s", vips_error_buffer() );
vips_error_clear();
return;
}
if( G_IS_PARAM_SPEC_ENUM( pspec ) &&
type == G_TYPE_STRING ) {
GType pspec_type = G_PARAM_SPEC_VALUE_TYPE( pspec );
int enum_value;
GValue value2 = { 0 };
if( (enum_value = vips_enum_from_nick( object_class->nickname,
pspec_type, g_value_get_string( value ) )) < 0 ) {
vips_warn( NULL, "%s", vips_error_buffer() );
vips_error_clear();
return;
}
g_value_init( &value2, pspec_type );
g_value_set_enum( &value2, enum_value );
g_object_set_property( G_OBJECT( object ), name, &value2 );
g_value_unset( &value2 );
}
else
g_object_set_property( G_OBJECT( object ), name, value );
}
// walk the options and set props on the operation
void
VOption::set_operation( VipsOperation *operation )
{
std::list<Pair *>::iterator i;
for( i = options.begin(); i != options.end(); ++i )
if( (*i)->input ) {
#ifdef VIPS_DEBUG_VERBOSE
printf( "set_operation: " );
vips_object_print_name( VIPS_OBJECT( operation ) );
char *str_value = g_strdup_value_contents( &(*i)->value );
printf( ".%s = %s\n", (*i)->name, str_value );
g_free( str_value );
#endif /*VIPS_DEBUG_VERBOSE*/
set_property( VIPS_OBJECT( operation ),
(*i)->name, &(*i)->value );
}
}
// walk the options and fetch any requested outputs
void
VOption::get_operation( VipsOperation *operation )
{
std::list<Pair *>::iterator i;
for( i = options.begin(); i != options.end(); ++i )
if( ! (*i)->input ) {
const char *name = (*i)->name;
g_object_get_property( G_OBJECT( operation ),
name, &(*i)->value );
#ifdef VIPS_DEBUG_VERBOSE
printf( "get_operation: " );
vips_object_print_name( VIPS_OBJECT( operation ) );
char *str_value = g_strdup_value_contents(
&(*i)->value );
printf( ".%s = %s\n", name, str_value );
g_free( str_value );
#endif /*VIPS_DEBUG_VERBOSE*/
GValue *value = &(*i)->value;
GType type = G_VALUE_TYPE( value );
if( type == VIPS_TYPE_IMAGE ) {
// rebox object
VipsImage *image = VIPS_IMAGE(
g_value_get_object( value ) );
*((*i)->vimage) = VImage( image );
}
else if( type == G_TYPE_INT )
*((*i)->vint) = g_value_get_int( value );
else if( type == G_TYPE_BOOLEAN )
*((*i)->vbool) = g_value_get_boolean( value );
else if( type == G_TYPE_DOUBLE )
*((*i)->vdouble) = g_value_get_double( value );
else if( type == VIPS_TYPE_ARRAY_DOUBLE ) {
int length;
double *array =
vips_value_get_array_double( value,
&length );
int j;
((*i)->vvector)->resize( length );
for( j = 0; j < length; j++ )
(*((*i)->vvector))[j] = array[j];
}
else if( type == VIPS_TYPE_BLOB ) {
// our caller gets a reference
*((*i)->vblob) =
(VipsBlob *) g_value_dup_boxed( value );
}
}
}
void
VImage::call_option_string( const char *operation_name,
const char *option_string, VOption *options )
{
VipsOperation *operation;
VIPS_DEBUG_MSG( "vips_call_by_name: starting for %s ...\n",
operation_name );
if( !(operation = vips_operation_new( operation_name )) ) {
if( options )
delete options;
throw( VError() );
}
/* Set str options before vargs options, so the user can't
* override things we set deliberately.
*/
if( option_string &&
vips_object_set_from_string( VIPS_OBJECT( operation ),
option_string ) ) {
vips_object_unref_outputs( VIPS_OBJECT( operation ) );
g_object_unref( operation );
delete options;
throw( VError() );
}
if( options )
options->set_operation( operation );
/* Build from cache.
*/
if( vips_cache_operation_buildp( &operation ) ) {
vips_object_unref_outputs( VIPS_OBJECT( operation ) );
delete options;
throw( VError() );
}
/* Walk args again, writing output.
*/
if( options )
options->get_operation( operation );
/* We're done with options!
*/
delete options;
/* The operation we have built should now have been reffed by
* one of its arguments or have finished its work. Either
* way, we can unref.
*/
g_object_unref( operation );
}
void
VImage::call( const char *operation_name, VOption *options )
{
call_option_string( operation_name, NULL, options );
}
VImage
VImage::new_from_file( const char *name, VOption *options )
{
char filename[VIPS_PATH_MAX];
char option_string[VIPS_PATH_MAX];
const char *operation_name;
VImage out;
vips__filename_split8( name, filename, option_string );
if( !(operation_name = vips_foreign_find_load( filename )) ) {
delete options;
throw VError();
}
call_option_string( operation_name, option_string,
(options ? options : VImage::option())->
set( "filename", filename )->
set( "out", &out ) );
return( out );
}
VImage
VImage::new_from_buffer( void *buf, size_t len, const char *option_string,
VOption *options )
{
const char *operation_name;
VipsBlob *blob;
VImage out;
if( !(operation_name = vips_foreign_find_load_buffer( buf, len )) ) {
delete options;
throw( VError() );
}
/* We don't take a copy of the data or free it.
*/
blob = vips_blob_new( NULL, buf, len );
options = (options ? options : VImage::option())->
set( "buffer", blob )->
set( "out", &out );
vips_area_unref( VIPS_AREA( blob ) );
call_option_string( operation_name, option_string, options );
return( out );
}
VImage
VImage::new_from_image( std::vector<double> pixel )
{
VImage onepx = VImage::black( 1, 1,
VImage::option()->set( "bands", bands() ) );
onepx = onepx.linear( to_vectorv( 1, 1.0 ), pixel ).cast( format() );
VImage big = onepx.embed( 0, 0, width(), height(),
VImage::option()->set( "extend", VIPS_EXTEND_COPY ) );
big = big.copy(
VImage::option()->
set( "interpretation", interpretation() )->
set( "xres", xres() )->
set( "yres", yres() )->
set( "xoffset", xres() )->
set( "yoffset", yres() ) );
return( big );
}
VImage
VImage::new_from_image( double pixel )
{
return( new_from_image( to_vectorv( 1, pixel ) ) );
}
VImage
VImage::new_matrix( int width, int height )
{
return( VImage( vips_image_new_matrix( width, height ) ) );
}
VImage
VImage::new_matrixv( int width, int height, ... )
{
VImage matrix = new_matrix( width, height );
VipsImage *vips_matrix = matrix.get_image();
va_list ap;
va_start( ap, height );
for( int y = 0; y < height; y++ )
for( int x = 0; x < width; x++ )
*VIPS_MATRIX( vips_matrix, x, y ) =
va_arg( ap, double );
va_end( ap );
return( matrix );
}
void
VImage::write_to_file( const char *name, VOption *options )
{
char filename[VIPS_PATH_MAX];
char option_string[VIPS_PATH_MAX];
const char *operation_name;
vips__filename_split8( name, filename, option_string );
if( !(operation_name = vips_foreign_find_save( filename )) ) {
delete options;
throw VError();
}
call_option_string( operation_name, option_string,
(options ? options : VImage::option())->
set( "in", *this )->
set( "filename", filename ) );
}
void
VImage::write_to_buffer( const char *suffix, void **buf, size_t *size,
VOption *options )
{
char filename[VIPS_PATH_MAX];
char option_string[VIPS_PATH_MAX];
const char *operation_name;
VipsBlob *blob;
vips__filename_split8( suffix, filename, option_string );
if( !(operation_name = vips_foreign_find_save_buffer( filename )) ) {
delete options;
throw VError();
}
call_option_string( operation_name, option_string,
(options ? options : VImage::option())->
set( "in", *this )->
set( "buffer", &blob ) );
if( blob ) {
if( buf ) {
*buf = VIPS_AREA( blob )->data;
VIPS_AREA( blob )->free_fn = NULL;
}
if( size )
*size = VIPS_AREA( blob )->length;
vips_area_unref( VIPS_AREA( blob ) );
}
}
#include "vips-operators.cpp"
std::vector<VImage>
VImage::bandsplit( VOption *options )
{
std::vector<VImage> b;
for( int i = 0; i < bands(); i++ )
b.push_back( extract_band( i ) );
return( b );
}
VImage
VImage::bandjoin( VImage other, VOption *options )
{
VImage v[2] = { *this, other };
std::vector<VImage> vec( v, v + VIPS_NUMBER( v ) );
return( bandjoin( vec, options ) );
}
std::complex<double>
VImage::minpos( VOption *options )
{
double x, y;
(void) min(
(options ? options : VImage::option()) ->
set( "x", &x ) ->
set( "y", &y ) );
return( std::complex<double>( x, y ) );
}
std::complex<double>
VImage::maxpos( VOption *options )
{
double x, y;
(void) max(
(options ? options : VImage::option()) ->
set( "x", &x ) ->
set( "y", &y ) );
return( std::complex<double>( x, y ) );
}
VIPS_NAMESPACE_END

View File

@@ -0,0 +1,76 @@
/* Object part of VInterpolate class
*/
/*
Copyright (C) 1991-2001 The National Gallery
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
*/
/*
These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/intl.h>
#include <vips/vips8>
#include <vips/debug.h>
/*
#define VIPS_DEBUG
#define VIPS_DEBUG_VERBOSE
*/
VIPS_NAMESPACE_START
VInterpolate
VInterpolate::new_from_name( const char *name, VOption *options )
{
VipsInterpolate *interp;
if( !(interp = vips_interpolate_new( name )) ) {
delete options;
throw VError();
}
delete options;
VInterpolate out( interp );
return( out );
}
VOption *
VOption::set( const char *name, VInterpolate value )
{
Pair *pair = new Pair( name );
pair->input = true;
g_value_init( &pair->value, VIPS_TYPE_INTERPOLATE );
g_value_set_object( &pair->value, value.get_interpolate() );
options.push_back( pair );
return( this );
}
VIPS_NAMESPACE_END

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
#include <node.h>
#include <vips/vips.h>
#include <vips/vips8>
#include "nan.h"
@@ -29,12 +29,17 @@ using Nan::NewBuffer;
using Nan::Null;
using Nan::Error;
using vips::VImage;
using vips::VError;
using sharp::ImageType;
using sharp::ImageTypeId;
using sharp::DetermineImageType;
using sharp::InitImage;
using sharp::HasProfile;
using sharp::HasAlpha;
using sharp::ExifOrientation;
using sharp::HasDensity;
using sharp::GetDensity;
using sharp::FreeCallback;
using sharp::counterQueue;
@@ -49,6 +54,7 @@ struct MetadataBaton {
int height;
std::string space;
int channels;
int density;
bool hasProfile;
bool hasAlpha;
int orientation;
@@ -60,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) {
@@ -81,13 +87,14 @@ class MetadataWorker : public AsyncWorker {
g_atomic_int_dec_and_test(&counterQueue);
ImageType imageType = ImageType::UNKNOWN;
VipsImage *image = nullptr;
VImage image;
if (baton->bufferInLength > 0) {
// From buffer
imageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
if (imageType != ImageType::UNKNOWN) {
image = InitImage(baton->bufferIn, baton->bufferInLength, VIPS_ACCESS_RANDOM);
if (image == nullptr) {
try {
image = VImage::new_from_buffer(baton->bufferIn, baton->bufferInLength, nullptr);
} catch (...) {
(baton->err).append("Input buffer has corrupt header");
imageType = ImageType::UNKNOWN;
}
@@ -98,57 +105,47 @@ class MetadataWorker : public AsyncWorker {
// From file
imageType = DetermineImageType(baton->fileIn.data());
if (imageType != ImageType::UNKNOWN) {
image = InitImage(baton->fileIn.data(), VIPS_ACCESS_RANDOM);
if (image == nullptr) {
try {
image = VImage::new_from_file(baton->fileIn.data());
} catch (...) {
(baton->err).append("Input file has corrupt header");
imageType = ImageType::UNKNOWN;
}
} else {
(baton->err).append("Input file is of an unsupported image format");
(baton->err).append("Input file is missing or of an unsupported image format");
}
}
if (image != nullptr && imageType != ImageType::UNKNOWN) {
if (imageType != ImageType::UNKNOWN) {
// Image type
switch (imageType) {
case ImageType::JPEG: baton->format = "jpeg"; break;
case ImageType::PNG: baton->format = "png"; break;
case ImageType::WEBP: baton->format = "webp"; break;
case ImageType::TIFF: baton->format = "tiff"; break;
case ImageType::MAGICK: baton->format = "magick"; break;
case ImageType::OPENSLIDE: baton->format = "openslide"; break;
case ImageType::UNKNOWN: break;
}
baton->format = ImageTypeId(imageType);
// VipsImage attributes
baton->width = image->Xsize;
baton->height = image->Ysize;
baton->space = vips_enum_nick(VIPS_TYPE_INTERPRETATION, image->Type);
baton->channels = image->Bands;
baton->width = image.width();
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);
baton->orientation = ExifOrientation(image);
// EXIF
if (vips_image_get_typeof(image, VIPS_META_EXIF_NAME) == VIPS_TYPE_BLOB) {
void* exif;
if (image.get_typeof(VIPS_META_EXIF_NAME) == VIPS_TYPE_BLOB) {
size_t exifLength;
if (!vips_image_get_blob(image, VIPS_META_EXIF_NAME, &exif, &exifLength)) {
baton->exifLength = exifLength;
baton->exif = static_cast<char*>(g_malloc(exifLength));
memcpy(baton->exif, exif, exifLength);
}
void const *exif = image.get_blob(VIPS_META_EXIF_NAME, &exifLength);
baton->exif = static_cast<char*>(g_malloc(exifLength));
memcpy(baton->exif, exif, exifLength);
baton->exifLength = exifLength;
}
// ICC profile
if (vips_image_get_typeof(image, VIPS_META_ICC_NAME) == VIPS_TYPE_BLOB) {
void* icc;
if (image.get_typeof(VIPS_META_ICC_NAME) == VIPS_TYPE_BLOB) {
size_t iccLength;
if (!vips_image_get_blob(image, VIPS_META_ICC_NAME, &icc, &iccLength)) {
baton->iccLength = iccLength;
baton->icc = static_cast<char*>(g_malloc(iccLength));
memcpy(baton->icc, icc, iccLength);
}
void const *icc = image.get_blob(VIPS_META_ICC_NAME, &iccLength);
baton->icc = static_cast<char*>(g_malloc(iccLength));
memcpy(baton->icc, icc, iccLength);
baton->iccLength = iccLength;
}
// Drop image reference
g_object_unref(image);
}
// Clean up
vips_error_clear();
@@ -170,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) {

0
src/metadata.h Executable file → Normal file
View File

View File

@@ -1,66 +1,54 @@
#include <vips/vips.h>
#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.
*/
int Composite(VipsObject *context, VipsImage *src, VipsImage *dst, VipsImage **out) {
VImage Composite(VImage src, VImage dst, const int gravity) {
using sharp::CalculateCrop;
using sharp::HasAlpha;
// Split src into non-alpha and alpha
VipsImage *srcWithoutAlpha;
if (vips_extract_band(src, &srcWithoutAlpha, 0, "n", src->Bands - 1, nullptr))
return -1;
vips_object_local(context, srcWithoutAlpha);
VipsImage *srcAlpha;
if (vips_extract_band(src, &srcAlpha, src->Bands - 1, "n", 1, nullptr))
return -1;
vips_object_local(context, srcAlpha);
// Split dst into non-alpha and alpha channels
VipsImage *dstWithoutAlpha;
VipsImage *dstAlpha;
if (HasAlpha(dst)) {
// Non-alpha: extract all-but-last channel
if (vips_extract_band(dst, &dstWithoutAlpha, 0, "n", dst->Bands - 1, nullptr)) {
return -1;
}
vips_object_local(context, dstWithoutAlpha);
// Alpha: Extract last channel
if (vips_extract_band(dst, &dstAlpha, dst->Bands - 1, "n", 1, nullptr)) {
return -1;
}
vips_object_local(context, dstAlpha);
} else {
// Non-alpha: Copy reference
dstWithoutAlpha = dst;
// Alpha: Use blank, opaque (0xFF) image
VipsImage *black;
if (vips_black(&black, dst->Xsize, dst->Ysize, nullptr)) {
return -1;
}
vips_object_local(context, black);
if (vips_invert(black, &dstAlpha, nullptr)) {
return -1;
}
vips_object_local(context, dstAlpha);
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");
}
// Compute normalized input alpha channels:
VipsImage *srcAlphaNormalized;
if (vips_linear1(srcAlpha, &srcAlphaNormalized, 1.0 / 255.0, 0.0, nullptr))
return -1;
vips_object_local(context, srcAlphaNormalized);
// 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)
);
}
VipsImage *dstAlphaNormalized;
if (vips_linear1(dstAlpha, &dstAlphaNormalized, 1.0 / 255.0, 0.0, nullptr))
return -1;
vips_object_local(context, dstAlphaNormalized);
// Split src into non-alpha and alpha channels
VImage srcWithoutAlpha = src.extract_band(0, VImage::option()->set("n", src.bands() - 1));
VImage srcAlpha = src[src.bands() - 1] * (1.0 / 255.0);
// Split dst into non-alpha and alpha channels
VImage dstWithoutAlpha = dst.extract_band(0, VImage::option()->set("n", dst.bands() - 1));
VImage dstAlpha = dst[dst.bands() - 1] * (1.0 / 255.0);
//
// Compute normalized output alpha channel:
@@ -72,22 +60,8 @@ namespace sharp {
// out_a = src_a + dst_a * (1 - src_a)
// ^^^^^^^^^^^
// t0
// ^^^^^^^^^^^^^^^^^^^
// t1
VipsImage *t0;
if (vips_linear1(srcAlphaNormalized, &t0, -1.0, 1.0, nullptr))
return -1;
vips_object_local(context, t0);
VipsImage *t1;
if (vips_multiply(dstAlphaNormalized, t0, &t1, nullptr))
return -1;
vips_object_local(context, t1);
VipsImage *outAlphaNormalized;
if (vips_add(srcAlphaNormalized, t1, &outAlphaNormalized, nullptr))
return -1;
vips_object_local(context, outAlphaNormalized);
VImage t0 = srcAlpha.linear(-1.0, 1.0);
VImage outAlphaNormalized = srcAlpha + dstAlpha * t0;
//
// Compute output RGB channels:
@@ -101,182 +75,179 @@ namespace sharp {
// premultiplied RGBA image as reversal of premultiplication is handled
// externally.
//
VipsImage *t2;
if (vips_multiply(dstWithoutAlpha, t0, &t2, nullptr))
return -1;
vips_object_local(context, t2);
VipsImage *outRGBPremultiplied;
if (vips_add(srcWithoutAlpha, t2, &outRGBPremultiplied, nullptr))
return -1;
vips_object_local(context, outRGBPremultiplied);
// Denormalize output alpha channel:
VipsImage *outAlpha;
if (vips_linear1(outAlphaNormalized, &outAlpha, 255.0, 0.0, nullptr))
return -1;
vips_object_local(context, outAlpha);
VImage outRGBPremultiplied = srcWithoutAlpha + dstWithoutAlpha * t0;
// Combine RGB and alpha channel into output image:
return vips_bandjoin2(outRGBPremultiplied, outAlpha, out, nullptr);
return outRGBPremultiplied.bandjoin(outAlphaNormalized * 255.0);
}
/*
* Stretch luminance to cover full dynamic range.
*/
int Normalize(VipsObject *context, VipsImage *image, VipsImage **out) {
VImage Normalize(VImage image) {
// Get original colourspace
VipsInterpretation typeBeforeNormalize = image->Type;
VipsInterpretation typeBeforeNormalize = image.interpretation();
if (typeBeforeNormalize == VIPS_INTERPRETATION_RGB) {
typeBeforeNormalize = VIPS_INTERPRETATION_sRGB;
}
// Convert to LAB colourspace
VipsImage *lab;
if (vips_colourspace(image, &lab, VIPS_INTERPRETATION_LAB, nullptr)) {
return -1;
}
vips_object_local(context, lab);
VImage lab = image.colourspace(VIPS_INTERPRETATION_LAB);
// Extract luminance
VipsImage *luminance;
if (vips_extract_band(lab, &luminance, 0, "n", 1, nullptr)) {
return -1;
}
vips_object_local(context, luminance);
// Extract chroma
VipsImage *chroma;
if (vips_extract_band(lab, &chroma, 1, "n", 2, nullptr)) {
return -1;
}
vips_object_local(context, chroma);
VImage luminance = lab[0];
// Find luminance range
VipsImage *stats;
if (vips_stats(luminance, &stats, nullptr)) {
return -1;
}
vips_object_local(context, stats);
double min = *VIPS_MATRIX(stats, 0, 0);
double max = *VIPS_MATRIX(stats, 1, 0);
VImage stats = luminance.stats();
double min = stats(0, 0)[0];
double max = stats(1, 0)[0];
if (min != max) {
// Extract chroma
VImage chroma = lab.extract_band(1, VImage::option()->set("n", 2));
// Calculate multiplication factor and addition
double f = 100.0 / (max - min);
double a = -(min * f);
// Scale luminance
VipsImage *luminance100;
if (vips_linear1(luminance, &luminance100, f, a, nullptr)) {
return -1;
}
vips_object_local(context, luminance100);
// Join scaled luminance to chroma
VipsImage *normalizedLab;
if (vips_bandjoin2(luminance100, chroma, &normalizedLab, nullptr)) {
return -1;
}
vips_object_local(context, normalizedLab);
// Convert to original colourspace
VipsImage *normalized;
if (vips_colourspace(normalizedLab, &normalized, typeBeforeNormalize, nullptr)) {
return -1;
}
vips_object_local(context, normalized);
// Scale luminance, join to chroma, convert back to original colourspace
VImage normalized = luminance.linear(f, a).bandjoin(chroma).colourspace(typeBeforeNormalize);
// Attach original alpha channel, if any
if (HasAlpha(image)) {
// Extract original alpha channel
VipsImage *alpha;
if (vips_extract_band(image, &alpha, image->Bands - 1, "n", 1, nullptr)) {
return -1;
}
vips_object_local(context, alpha);
VImage alpha = image[image.bands() - 1];
// Join alpha channel to normalised image
VipsImage *normalizedAlpha;
if (vips_bandjoin2(normalized, alpha, &normalizedAlpha, nullptr)) {
return -1;
}
vips_object_local(context, normalizedAlpha);
*out = normalizedAlpha;
return normalized.bandjoin(alpha);
} else {
*out = normalized;
return normalized;
}
} else {
// Cannot normalise zero-range image
*out = image;
}
return 0;
return image;
}
/*
* Gamma encoding/decoding
*/
VImage Gamma(VImage image, double const exponent) {
if (HasAlpha(image)) {
// Separate alpha channel
VImage imageWithoutAlpha = image.extract_band(0,
VImage::option()->set("n", image.bands() - 1));
VImage alpha = image[image.bands() - 1];
return imageWithoutAlpha.gamma(VImage::option()->set("exponent", exponent)).bandjoin(alpha);
} else {
return image.gamma(VImage::option()->set("exponent", exponent));
}
}
/*
* Gaussian blur (use sigma <0 for fast blur)
*/
int Blur(VipsObject *context, VipsImage *image, VipsImage **out, double sigma) {
VipsImage *blurred;
VImage Blur(VImage image, double const sigma) {
if (sigma < 0.0) {
// Fast, mild blur - averages neighbouring pixels
VipsImage *blur = vips_image_new_matrixv(3, 3,
VImage blur = VImage::new_matrixv(3, 3,
1.0, 1.0, 1.0,
1.0, 1.0, 1.0,
1.0, 1.0, 1.0);
vips_image_set_double(blur, "scale", 9);
vips_object_local(context, blur);
if (vips_conv(image, &blurred, blur, nullptr)) {
return -1;
}
blur.set("scale", 9.0);
return image.conv(blur);
} else {
// Slower, accurate Gaussian blur
// Create Gaussian function for standard deviation
VipsImage *gaussian;
if (vips_gaussmat(&gaussian, sigma, 0.2, "separable", TRUE, "integer", TRUE, nullptr)) {
return -1;
}
vips_object_local(context, gaussian);
// Apply Gaussian function
if (vips_convsep(image, &blurred, gaussian, "precision", VIPS_PRECISION_INTEGER, nullptr)) {
return -1;
}
return image.gaussblur(sigma);
}
vips_object_local(context, blurred);
*out = blurred;
return 0;
}
/*
* Sharpen flat and jagged areas. Use radius of -1 for fast sharpen.
*/
int Sharpen(VipsObject *context, VipsImage *image, VipsImage **out, int radius, double flat, double jagged) {
VipsImage *sharpened;
VImage Sharpen(VImage image, int const radius, double const flat, double const jagged) {
if (radius == -1) {
// Fast, mild sharpen
VipsImage *sharpen = vips_image_new_matrixv(3, 3,
VImage sharpen = VImage::new_matrixv(3, 3,
-1.0, -1.0, -1.0,
-1.0, 32.0, -1.0,
-1.0, -1.0, -1.0);
vips_image_set_double(sharpen, "scale", 24);
vips_object_local(context, sharpen);
if (vips_conv(image, &sharpened, sharpen, nullptr)) {
return -1;
}
sharpen.set("scale", 24.0);
return image.conv(sharpen);
} else {
// Slow, accurate sharpen in LAB colour space, with control over flat vs jagged areas
if (vips_sharpen(image, &sharpened, "radius", radius, "m1", flat, "m2", jagged, nullptr)) {
return -1;
return image.sharpen(
VImage::option()->set("radius", radius)->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;
}
}
vips_object_local(context, sharpened);
*out = sharpened;
return 0;
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);
}
int Threshold(VipsObject *context, VipsImage *image, VipsImage **out, int threshold) {
VipsImage *greyscale;
if (vips_colourspace(image, &greyscale, VIPS_INTERPRETATION_B_W, nullptr)) {
return -1;
}
vips_object_local(context, greyscale);
image = greyscale;
VipsImage *thresholded;
if (vips_moreeq_const1(image, &thresholded, threshold, nullptr)) {
return -1;
}
vips_object_local(context, thresholded);
*out = thresholded;
return 0;
/*
Calculate the Shannon entropy for an image
*/
double Entropy(VImage image) {
return image.hist_find().hist_entropy();
}
} // namespace sharp

35
src/operations.h Executable file → Normal file
View File

@@ -1,34 +1,49 @@
#ifndef SRC_OPERATIONS_H_
#define SRC_OPERATIONS_H_
#include <tuple>
#include <vips/vips8>
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.
*/
int Composite(VipsObject *context, VipsImage *src, VipsImage *dst, VipsImage **out);
VImage Composite(VImage src, VImage dst, const int gravity);
/*
* Stretch luminance to cover full dynamic range.
*/
int Normalize(VipsObject *context, VipsImage *image, VipsImage **out);
VImage Normalize(VImage image);
/*
* Gamma encoding/decoding
*/
VImage Gamma(VImage image, double const exponent);
/*
* Gaussian blur. Use sigma of -1 for fast blur.
*/
int Blur(VipsObject *context, VipsImage *image, VipsImage **out, double sigma);
VImage Blur(VImage image, double const sigma);
/*
* Sharpen flat and jagged areas. Use radius of -1 for fast sharpen.
*/
int Sharpen(VipsObject *context, VipsImage *image, VipsImage **out, int radius, double flat, double jagged);
VImage Sharpen(VImage image, int const radius, double const flat, double const jagged);
/*
* Perform thresholding on an image. If the image is not greyscale, will convert before thresholding.
* Pixels with a greyscale value greater-than-or-equal-to `threshold` will be pure white. All others will be pure black.
*/
int Threshold(VipsObject *context, VipsImage *image, VipsImage **out, int threshold);
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);
} // namespace sharp
#endif // SRC_OPERATIONS_H_

File diff suppressed because it is too large Load Diff

135
src/pipeline.h Executable file → Normal file
View File

@@ -1,8 +1,143 @@
#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 interpolator;
double background[4];
bool flatten;
bool negate;
double blurSigma;
int sharpenRadius;
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),
sharpenRadius(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

@@ -1,5 +1,5 @@
#include <node.h>
#include <vips/vips.h>
#include <vips/vips8>
#include "nan.h"
@@ -11,10 +11,6 @@
NAN_MODULE_INIT(init) {
vips_init("sharp");
// Set libvips operation cache limits
vips_cache_set_max_mem(100 * 1024 * 1024); // 100 MB
vips_cache_set_max(500); // 500 operations
// Methods available to JavaScript
Nan::Set(target, Nan::New("metadata").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(metadata)).ToLocalChecked());

View File

@@ -1,6 +1,6 @@
#include <cmath>
#include <node.h>
#include <vips/vips.h>
#include <vips/vips8>
#include <vips/vector.h>
#include "nan.h"
@@ -24,33 +24,49 @@ using Nan::To;
using Nan::Utf8String;
/*
Get and set cache memory and item limits
Get and set cache limits
*/
NAN_METHOD(cache) {
HandleScope();
// Set cache memory limit
// Set memory limit
if (info[0]->IsInt32()) {
vips_cache_set_max_mem(To<int32_t>(info[0]).FromJust() * 1048576);
}
// Set cache items limit
// Set file limit
if (info[1]->IsInt32()) {
vips_cache_set_max(To<int32_t>(info[1]).FromJust());
vips_cache_set_max_files(To<int32_t>(info[1]).FromJust());
}
// Set items limit
if (info[2]->IsInt32()) {
vips_cache_set_max(To<int32_t>(info[2]).FromJust());
}
// Get cache statistics
Local<Object> cache = New<Object>();
Set(cache, New("current").ToLocalChecked(),
// Get memory stats
Local<Object> memory = New<Object>();
Set(memory, New("current").ToLocalChecked(),
New<Integer>(static_cast<int>(round(vips_tracked_get_mem() / 1048576)))
);
Set(cache, New("high").ToLocalChecked(),
Set(memory, New("high").ToLocalChecked(),
New<Integer>(static_cast<int>(round(vips_tracked_get_mem_highwater() / 1048576)))
);
Set(cache, New("memory").ToLocalChecked(),
Set(memory, New("max").ToLocalChecked(),
New<Integer>(static_cast<int>(round(vips_cache_get_max_mem() / 1048576)))
);
Set(cache, New("items").ToLocalChecked(), New<Integer>(vips_cache_get_max()));
// Get file stats
Local<Object> files = New<Object>();
Set(files, New("current").ToLocalChecked(), New<Integer>(vips_tracked_get_files()));
Set(files, New("max").ToLocalChecked(), New<Integer>(vips_cache_get_max_files()));
// Get item stats
Local<Object> items = New<Object>();
Set(items, New("current").ToLocalChecked(), New<Integer>(vips_cache_get_size()));
Set(items, New("max").ToLocalChecked(), New<Integer>(vips_cache_get_max()));
Local<Object> cache = New<Object>();
Set(cache, New("memory").ToLocalChecked(), memory);
Set(cache, New("files").ToLocalChecked(), files);
Set(cache, New("items").ToLocalChecked(), items);
info.GetReturnValue().Set(cache);
}
@@ -156,19 +172,17 @@ NAN_METHOD(format) {
Local<String> rawId = New("raw").ToLocalChecked();
Set(raw, attrId, rawId);
Set(format, rawId, raw);
// No support for raw input yet, so always false
Local<Boolean> supported = New<Boolean>(true);
Local<Boolean> unsupported = New<Boolean>(false);
Local<Object> rawInput = New<Object>();
Set(rawInput, attrFile, unsupported);
Set(rawInput, attrBuffer, unsupported);
Set(rawInput, attrStream, unsupported);
Set(rawInput, attrBuffer, supported);
Set(rawInput, attrStream, supported);
Set(raw, attrInput, rawInput);
// Raw output via Buffer/Stream is available in libvips >= 7.42.0
Local<Boolean> hasOutputBufferRaw = New<Boolean>(vips_version(0) >= 8 || (vips_version(0) == 7 && vips_version(1) >= 42));
Local<Object> rawOutput = New<Object>();
Set(rawOutput, attrFile, unsupported);
Set(rawOutput, attrBuffer, hasOutputBufferRaw);
Set(rawOutput, attrStream, hasOutputBufferRaw);
Set(rawOutput, attrBuffer, supported);
Set(rawOutput, attrStream, supported);
Set(raw, attrOutput, rawOutput);
info.GetReturnValue().Set(format);
@@ -180,101 +194,64 @@ NAN_METHOD(format) {
between two images of the same dimensions and number of channels.
*/
NAN_METHOD(_maxColourDistance) {
using vips::VImage;
using vips::VError;
using sharp::DetermineImageType;
using sharp::ImageType;
using sharp::InitImage;
using sharp::HasAlpha;
HandleScope();
// Create "hook" VipsObject to hang image references from
VipsObject *hook = reinterpret_cast<VipsObject*>(vips_image_new());
// Open input files
VipsImage *image1 = nullptr;
VImage image1;
ImageType imageType1 = DetermineImageType(*Utf8String(info[0]));
if (imageType1 != ImageType::UNKNOWN) {
image1 = InitImage(*Utf8String(info[0]), VIPS_ACCESS_SEQUENTIAL);
if (image1 == nullptr) {
g_object_unref(hook);
try {
image1 = VImage::new_from_file(*Utf8String(info[0]));
} catch (...) {
return ThrowError("Input file 1 has corrupt header");
} else {
vips_object_local(hook, image1);
}
} else {
g_object_unref(hook);
return ThrowError("Input file 1 is of an unsupported image format");
}
VipsImage *image2 = nullptr;
VImage image2;
ImageType imageType2 = DetermineImageType(*Utf8String(info[1]));
if (imageType2 != ImageType::UNKNOWN) {
image2 = InitImage(*Utf8String(info[1]), VIPS_ACCESS_SEQUENTIAL);
if (image2 == nullptr) {
g_object_unref(hook);
try {
image2 = VImage::new_from_file(*Utf8String(info[1]));
} catch (...) {
return ThrowError("Input file 2 has corrupt header");
} else {
vips_object_local(hook, image2);
}
} else {
g_object_unref(hook);
return ThrowError("Input file 2 is of an unsupported image format");
}
// Ensure same number of channels
if (image1->Bands != image2->Bands) {
g_object_unref(hook);
if (image1.bands() != image2.bands()) {
return ThrowError("mismatchedBands");
}
// Ensure same dimensions
if (image1->Xsize != image2->Xsize || image1->Ysize != image2->Ysize) {
g_object_unref(hook);
if (image1.width() != image2.width() || image1.height() != image2.height()) {
return ThrowError("mismatchedDimensions");
}
// Premultiply and remove alpha
if (HasAlpha(image1)) {
VipsImage *imagePremultiplied1;
if (vips_premultiply(image1, &imagePremultiplied1, nullptr)) {
g_object_unref(hook);
return ThrowError(vips_error_buffer());
}
vips_object_local(hook, imagePremultiplied1);
VipsImage *imagePremultipliedNoAlpha1;
if (vips_extract_band(image1, &imagePremultipliedNoAlpha1, 1, "n", image1->Bands - 1, nullptr)) {
g_object_unref(hook);
return ThrowError(vips_error_buffer());
}
vips_object_local(hook, imagePremultipliedNoAlpha1);
image1 = imagePremultipliedNoAlpha1;
}
if (HasAlpha(image2)) {
VipsImage *imagePremultiplied2;
if (vips_premultiply(image2, &imagePremultiplied2, nullptr)) {
g_object_unref(hook);
return ThrowError(vips_error_buffer());
}
vips_object_local(hook, imagePremultiplied2);
VipsImage *imagePremultipliedNoAlpha2;
if (vips_extract_band(image2, &imagePremultipliedNoAlpha2, 1, "n", image2->Bands - 1, nullptr)) {
g_object_unref(hook);
return ThrowError(vips_error_buffer());
}
vips_object_local(hook, imagePremultipliedNoAlpha2);
image2 = imagePremultipliedNoAlpha2;
}
// Calculate colour distance
VipsImage *difference;
if (vips_dE00(image1, image2, &difference, nullptr)) {
g_object_unref(hook);
return ThrowError(vips_error_buffer());
}
vips_object_local(hook, difference);
// Extract maximum distance
double maxColourDistance;
if (vips_max(difference, &maxColourDistance, nullptr)) {
g_object_unref(hook);
return ThrowError(vips_error_buffer());
try {
// Premultiply and remove alpha
if (HasAlpha(image1)) {
image1 = image1.premultiply().extract_band(1, VImage::option()->set("n", image1.bands() - 1));
}
if (HasAlpha(image2)) {
image2 = image2.premultiply().extract_band(1, VImage::option()->set("n", image2.bands() - 1));
}
// Calculate colour distance
maxColourDistance = image1.dE00(image2).max();
} catch (VError err) {
return ThrowError(err.what());
}
g_object_unref(hook);
// Clean up libvips' per-request data and threads
vips_error_clear();
vips_thread_shutdown();
info.GetReturnValue().Set(New<Number>(maxColourDistance));
}

View File

@@ -5,14 +5,14 @@
"author": "Lovell Fuller <npm@lovell.info>",
"description": "Benchmark and performance tests for sharp",
"scripts": {
"test": "node perf && node random && node parallel"
"test": "VIPS_WARNING=0 node perf && node random && node parallel"
},
"devDependencies": {
"async": "^1.5.0",
"benchmark": "^1.0.0",
"async": "^1.5.2",
"benchmark": "^2.1.0",
"gm": "^1.21.0",
"imagemagick": "^0.1.3",
"imagemagick-native": "elad/node-imagemagick-native",
"imagemagick-native": "^1.9.2",
"jimp": "^0.2.20",
"lwip": "^0.0.8",
"semver": "^5.1.0"

View File

@@ -34,7 +34,7 @@ var magickFilterBilinear = 'Triangle';
var magickFilterBicubic = 'Lanczos';
// Disable libvips cache to ensure tests are as fair as they can be
sharp.cache(0);
sharp.cache(false);
// Enable use of SIMD
sharp.simd(true);
@@ -535,7 +535,7 @@ async.series({
}).on('cycle', function(event) {
console.log('jpeg-linear ' + String(event.target));
}).on('complete', function() {
callback(null, this.filter('fastest').pluck('name'));
callback(null, this.filter('fastest').map('name'));
}).run();
},
@@ -782,7 +782,7 @@ async.series({
}).on('cycle', function(event) {
console.log('jpeg-cubic ' + String(event.target));
}).on('complete', function() {
callback(null, this.filter('fastest').pluck('name'));
callback(null, this.filter('fastest').map('name'));
}).run();
},
@@ -882,7 +882,7 @@ async.series({
}).on('cycle', function(event) {
console.log('interpolators ' + String(event.target));
}).on('complete', function() {
callback(null, this.filter('fastest').pluck('name'));
callback(null, this.filter('fastest').map('name'));
}).run();
},
@@ -1115,7 +1115,7 @@ async.series({
pngSuite.on('cycle', function(event) {
console.log(' png ' + String(event.target));
}).on('complete', function() {
callback(null, this.filter('fastest').pluck('name'));
callback(null, this.filter('fastest').map('name'));
}).run();
},
@@ -1182,7 +1182,7 @@ async.series({
}).on('cycle', function(event) {
console.log('webp ' + String(event.target));
}).on('complete', function() {
callback(null, this.filter('fastest').pluck('name'));
callback(null, this.filter('fastest').map('name'));
}).run();
}
}, function(err, results) {

View File

@@ -70,7 +70,7 @@ new Benchmark.Suite('random').add('imagemagick', {
}).on('cycle', function(event) {
console.log(String(event.target));
}).on('complete', function() {
var winner = this.filter('fastest').pluck('name');
var winner = this.filter('fastest').map('name');
assert.strictEqual('sharp', String(winner), 'sharp was slower than ' + winner);
console.dir(sharp.cache());
}).run();

View File

@@ -1,17 +0,0 @@
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"
id="Wikimedia logo"
viewBox="-599 -599 1198 1198" width="1024" height="1024">
<defs>
<clipPath id="mask">
<path d="M 47.5,-87.5 v 425 h -95 v -425 l -552,-552 v 1250 h 1199 v -1250 z" />
</clipPath>
</defs>
<g clip-path="url(#mask)">
<circle id="green parts" fill="#396" r="336.5"/>
<circle id="blue arc" fill="none" stroke="#069" r="480.25" stroke-width="135.5" />
</g>
<circle fill="#900" cy="-379.5" r="184.5" id="red circle"/>
</svg>

Before

Width:  |  Height:  |  Size: 692 B

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

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<path d="M30,76q6-14,13-26q6-12,14-23q8-12,13-17q3-4,6-6q1-1,5-2q8-1,12-1q1,0,1,1q0,1-1,2q-13,11-27,33q-14,21-24,44q-4,9-5,11q-1,2-9,2q-5,0-6-1q-1-1-5-6q-5-8-12-15q-3-4-3-6q0-2,4-5q3-2,6-2q3,0,8,3q5,4,10,14z" fill="green" />
</svg>

After

Width:  |  Height:  |  Size: 301 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 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.

After

Width:  |  Height:  |  Size: 755 B

BIN
test/fixtures/expected/embed-16bit.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 827 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 703 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 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: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

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: 5.7 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 938 B

BIN
test/fixtures/flowers.jpeg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@@ -66,6 +66,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 +85,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
inputSvg: getPath('Wikimedia-logo.svg'), // http://commons.wikimedia.org/wiki/File:Wikimedia-logo.svg
inputSvg: getPath('check.svg'), // http://dev.w3.org/SVG/tools/svgweb/samples/svg-files/check.svg
inputPsd: getPath('free-gearhead-pack.psd'), // https://dribbble.com/shots/1624241-Free-Gearhead-Vector-Pack
inputSvs: getPath('CMU-1-Small-Region.svs'), // http://openslide.cs.cmu.edu/download/openslide-testdata/Aperio/CMU-1-Small-Region.svs
inputJPGBig: getPath('flowers.jpeg'),
outputJpg: getPath('output.jpg'),
outputPng: getPath('output.png'),
outputWebP: getPath('output.webp'),

View File

@@ -248,6 +248,34 @@
...
fun:_ZN2v88internal12_GLOBAL__N_117CreateICUCollatorEPNS0_7IsolateERKN6icu_556LocaleENS0_6HandleINS0_8JSObjectEEE
}
{
leak_v8_CallInterfaceDescriptorData
Memcheck:Leak
match-leak-kinds: possible
...
fun:_ZN2v88internal27CallInterfaceDescriptorData26InitializePlatformSpecificEiPNS0_8RegisterEPNS0_27PlatformInterfaceDescriptorE
}
{
leak_v8_InitializePlatformSpecific14
Memcheck:Leak
match-leak-kinds: possible
...
fun:_ZN2v88internal14LoadDescriptor26InitializePlatformSpecificEPNS0_27CallInterfaceDescriptorDataE
}
{
leak_v8_InitializePlatformSpecific15
Memcheck:Leak
match-leak-kinds: possible
...
fun:_ZN2v88internal15StoreDescriptor26InitializePlatformSpecificEPNS0_27CallInterfaceDescriptorDataE
}
{
leak_v8_Malloced
Memcheck:Leak
match-leak-kinds: possible
...
fun:_ZN2v88internal8Malloced3NewEm
}
# vips__init warnings
{
@@ -278,19 +306,15 @@
...
fun:vips__magick_read_header
}
{
cond_magick_is_palette_image
Memcheck:Cond
fun:IsPaletteImage
...
fun:get_bands
}
{
value_magick_is_palette_image
Memcheck:Value8
fun:IsPaletteImage
...
fun:get_bands
}
{
cond_magick_is_palette_image
Memcheck:Cond
fun:IsPaletteImage
}
# glib g_file_read_link

View File

@@ -4,8 +4,6 @@ var assert = require('assert');
var fixtures = require('../fixtures');
var sharp = require('../../index');
sharp.cache(0);
describe('Alpha transparency', function() {
it('Flatten to black', function(done) {
@@ -55,21 +53,18 @@ describe('Alpha transparency', function() {
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.assertSimilar(fixtures.expected('flatten-rgb16-orange.jpg'), data, { threshold: 6 }, done);
});
});
it('Do not flatten', function(done) {
sharp(fixtures.inputPngWithTransparency)
.flatten(false)
.toBuffer(function(err, data) {
.toBuffer(function(err, data, info) {
if (err) throw err;
sharp(data).metadata(function(err, metadata) {
if (err) throw err;
assert.strictEqual('png', metadata.format);
assert.strictEqual(4, metadata.channels);
done();
});
assert.strictEqual('png', info.format);
assert.strictEqual(4, info.channels);
done();
});
});
@@ -77,14 +72,11 @@ describe('Alpha transparency', function() {
sharp(fixtures.inputJpg)
.background('#ff0000')
.flatten()
.toBuffer(function(err, data) {
.toBuffer(function(err, data, info) {
if (err) throw err;
sharp(data).metadata(function(err, metadata) {
if (err) throw err;
assert.strictEqual('jpeg', metadata.format);
assert.strictEqual(3, metadata.channels);
done();
});
assert.strictEqual('jpeg', info.format);
assert.strictEqual(3, info.channels);
done();
});
});
@@ -94,7 +86,6 @@ describe('Alpha transparency', function() {
var expected = fixtures.expected(BASE_NAME);
sharp(fixtures.inputPngAlphaPremultiplicationSmall)
.resize(2048, 1536)
.interpolateWith('bicubic')
.toFile(actual, function(err) {
if (err) {
done(err);
@@ -111,7 +102,6 @@ describe('Alpha transparency', function() {
var expected = fixtures.expected(BASE_NAME);
sharp(fixtures.inputPngAlphaPremultiplicationLarge)
.resize(1024, 768)
.interpolateWith('bicubic')
.toFile(actual, function(err) {
if (err) {
done(err);

View File

@@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Blur', function() {
it('specific radius 1', function(done) {

9
test/unit/cache.js Normal file
View File

@@ -0,0 +1,9 @@
'use strict';
var sharp = require('../../index');
// Define SHARP_TEST_WITHOUT_CACHE environment variable to prevent use of libvips' cache
beforeEach(function() {
sharp.cache(process.env.SHARP_TEST_WITHOUT_CACHE ? false : true);
});

View File

@@ -6,9 +6,15 @@ var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Clone', function() {
beforeEach(function() {
sharp.cache(false);
});
afterEach(function() {
sharp.cache(true);
});
it('Read from Stream and write to multiple Streams', function(done) {
var finishEventsExpected = 2;
// Output stream 1
@@ -55,4 +61,5 @@ describe('Clone', function() {
// Go
fs.createReadStream(fixtures.inputJpg).pipe(rotator);
});
});

View File

@@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Colour space conversion', function() {
it('To greyscale', function(done) {
@@ -31,7 +29,7 @@ describe('Colour space conversion', function() {
.toFile(fixtures.path('output.greyscale-not.jpg'), done);
});
if (sharp.format.webp.output.buffer) {
if (sharp.format.tiff.input.file && sharp.format.webp.output.buffer) {
it('From 1-bit TIFF to sRGB WebP [slow]', function(done) {
sharp(fixtures.inputTiff)
.webp()

View File

@@ -11,24 +11,23 @@ describe('cpplint', function() {
// Ignore cpplint failures, possibly newline-related, on Windows
if (process.platform !== 'win32') {
// List C++ source files
fs.readdirSync(path.join(__dirname, '..', '..', 'src')).forEach(function (source) {
fs.readdirSync(path.join(__dirname, '..', '..', 'src')).filter(function(source) {
return source !== 'libvips';
}).forEach(function(source) {
var file = path.join('src', source);
it(file, function(done) {
// 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,11 +5,9 @@ var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Crop', function() {
describe('Crop gravities', function() {
var testSettings = [
[
{
name: 'North',
width: 320,
@@ -52,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,
@@ -108,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)
@@ -124,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')
@@ -136,15 +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('Uses default value when none specified', function() {
assert.doesNotThrow(function() {
sharp().crop();
});
});
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

@@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Embed', function() {
it('JPEG within PNG, no alpha channel', function(done) {
@@ -20,11 +18,8 @@ describe('Embed', function() {
assert.strictEqual('png', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
sharp(data).metadata(function(err, metadata) {
if (err) throw err;
assert.strictEqual(3, metadata.channels);
done();
});
assert.strictEqual(3, info.channels);
fixtures.assertSimilar(fixtures.expected('embed-3-into-3.png'), data, done);
});
});
@@ -41,11 +36,8 @@ describe('Embed', function() {
assert.strictEqual('webp', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
sharp(data).metadata(function(err, metadata) {
if (err) throw err;
assert.strictEqual(4, metadata.channels);
done();
});
assert.strictEqual(4, info.channels);
fixtures.assertSimilar(fixtures.expected('embed-3-into-4.webp'), data, done);
});
});
}
@@ -60,11 +52,55 @@ describe('Embed', function() {
assert.strictEqual('png', info.format);
assert.strictEqual(50, info.width);
assert.strictEqual(50, info.height);
sharp(data).metadata(function(err, metadata) {
if (err) throw err;
assert.strictEqual(4, metadata.channels);
done();
});
assert.strictEqual(4, info.channels);
fixtures.assertSimilar(fixtures.expected('embed-4-into-4.png'), data, done);
});
});
it('16-bit PNG with alpha channel', function(done) {
sharp(fixtures.inputPngWithTransparency16bit)
.resize(32, 16)
.embed()
.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-16bit.png'), data, done);
});
});
it('16-bit PNG with alpha channel onto RGBA', function(done) {
sharp(fixtures.inputPngWithTransparency16bit)
.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-16bit-rgba.png'), data, done);
});
});
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);
});
});
@@ -78,6 +114,7 @@ describe('Embed', function() {
assert.strictEqual('png', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
assert.strictEqual(3, info.channels);
fixtures.assertSimilar(fixtures.expected('embed-enlarge.png'), data, done);
});
});

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

@@ -5,32 +5,7 @@ var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
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)
@@ -67,17 +42,19 @@ describe('Partial image extraction', function() {
});
}
it('TIFF', function(done) {
sharp(fixtures.inputTiff)
.extract({ left: 34, top: 63, width: 341, height: 529 })
.jpeg()
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(341, info.width);
assert.strictEqual(529, info.height);
fixtures.assertSimilar(fixtures.expected('extract.tiff'), data, done);
});
});
if (sharp.format.tiff.output.file) {
it('TIFF', function(done) {
sharp(fixtures.inputTiff)
.extract({ left: 34, top: 63, width: 341, height: 529 })
.jpeg()
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(341, info.width);
assert.strictEqual(529, info.height);
fixtures.assertSimilar(fixtures.expected('extract.tiff'), data, done);
});
});
}
it('Before resize', function(done) {
sharp(fixtures.inputJpg)

View File

@@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Gamma correction', function() {
it('value of 0.0 (disabled)', function(done) {
@@ -16,7 +14,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-0.0.jpg'), data, done);
fixtures.assertSimilar(fixtures.expected('gamma-0.0.jpg'), data, { threshold: 9 }, done);
});
});
@@ -40,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);
});
});
@@ -51,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,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Interpolation', function() {
it('nearest neighbour', function(done) {

View File

@@ -6,10 +6,15 @@ var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Input/output', function() {
beforeEach(function() {
sharp.cache(false);
});
afterEach(function() {
sharp.cache(true);
});
it('Read from File and write to Stream', function(done) {
var writable = fs.createWriteStream(fixtures.outputJpg);
writable.on('finish', function() {
@@ -104,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;
@@ -387,76 +412,91 @@ describe('Input/output', function() {
});
});
describe('Output filename without extension uses input format', function() {
describe('Output filename with unknown extension', function() {
it('JPEG', function(done) {
sharp(fixtures.inputJpg).resize(320, 80).toFile(fixtures.outputZoinks, function(err, info) {
if (err) throw err;
assert.strictEqual(true, info.size > 0);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(80, info.height);
fs.unlinkSync(fixtures.outputZoinks);
done();
});
});
it('PNG', function(done) {
sharp(fixtures.inputPng).resize(320, 80).toFile(fixtures.outputZoinks, 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(80, info.height);
fs.unlinkSync(fixtures.outputZoinks);
done();
});
});
it('Transparent PNG', function(done) {
sharp(fixtures.inputPngWithTransparency).resize(320, 80).toFile(fixtures.outputZoinks, 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(80, info.height);
done();
});
});
if (sharp.format.webp.input.file) {
it('WebP', function(done) {
sharp(fixtures.inputWebP).resize(320, 80).toFile(fixtures.outputZoinks, function(err, info) {
it('Match JPEG input', function(done) {
sharp(fixtures.inputJpg)
.resize(320, 80)
.toFile(fixtures.outputZoinks, function(err, info) {
if (err) throw err;
assert.strictEqual(true, info.size > 0);
assert.strictEqual('webp', info.format);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(80, info.height);
fs.unlinkSync(fixtures.outputZoinks);
done();
});
});
it('Match PNG input', function(done) {
sharp(fixtures.inputPng)
.resize(320, 80)
.toFile(fixtures.outputZoinks, 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(80, info.height);
fs.unlinkSync(fixtures.outputZoinks);
done();
});
});
if (sharp.format.webp.input.file) {
it('Match WebP input', function(done) {
sharp(fixtures.inputWebP)
.resize(320, 80)
.toFile(fixtures.outputZoinks, function(err, info) {
if (err) throw err;
assert.strictEqual(true, info.size > 0);
assert.strictEqual('webp', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(80, info.height);
fs.unlinkSync(fixtures.outputZoinks);
done();
});
});
}
it('TIFF', function(done) {
sharp(fixtures.inputTiff).resize(320, 80).toFile(fixtures.outputZoinks, function(err, info) {
if (err) throw err;
assert.strictEqual(true, info.size > 0);
assert.strictEqual('tiff', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(80, info.height);
fs.unlinkSync(fixtures.outputZoinks);
done();
if (sharp.format.tiff.input.file) {
it('Match TIFF input', function(done) {
sharp(fixtures.inputTiff)
.resize(320, 80)
.toFile(fixtures.outputZoinks, function(err, info) {
if (err) throw err;
assert.strictEqual(true, info.size > 0);
assert.strictEqual('tiff', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(80, info.height);
fs.unlinkSync(fixtures.outputZoinks);
done();
});
});
}
it('Match GIF input, therefore fail', function(done) {
sharp(fixtures.inputGif)
.resize(320, 80)
.toFile(fixtures.outputZoinks, function(err) {
assert(!!err);
done();
});
});
it('Fail with GIF', function(done) {
sharp(fixtures.inputGif).resize(320, 80).toFile(fixtures.outputZoinks, function(err) {
assert(!!err);
done();
});
it('Force JPEG format for PNG input', function(done) {
sharp(fixtures.inputPng)
.resize(320, 80)
.jpeg()
.toFile(fixtures.outputZoinks, function(err, info) {
if (err) throw err;
assert.strictEqual(true, info.size > 0);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(80, info.height);
fs.unlinkSync(fixtures.outputZoinks);
done();
});
});
});
describe('PNG output', function() {
@@ -629,20 +669,51 @@ describe('Input/output', function() {
});
if (sharp.format.magick.input.file) {
it('Convert SVG, if supported, to PNG', function(done) {
it('Convert SVG to PNG at default 72DPI', function(done) {
sharp(fixtures.inputSvg)
.resize(100, 100)
.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 of an unsupported image format'));
assert.strictEqual(0, err.message.indexOf('Input file is missing or of an unsupported image format'));
done();
} else {
assert.strictEqual(true, info.size > 0);
assert.strictEqual('png', info.format);
assert.strictEqual(100, info.width);
assert.strictEqual(100, info.height);
fixtures.assertSimilar(fixtures.expected('svg.png'), data, done);
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) {
sharp(fixtures.inputSvg, { density: 1200 })
.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('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();
});
});
}
});
});
@@ -805,6 +876,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;
@@ -819,6 +909,109 @@ describe('Input/output', function() {
});
describe('Input options', function() {
it('Non-Object options fails', function() {
assert.throws(function() {
sharp(null, 'zoinks');
});
});
it('Invalid density: string', function() {
assert.throws(function() {
sharp(null, { density: 'zoinks' } );
});
});
it('Invalid density: float', function() {
assert.throws(function() {
sharp(null, { density: 0.5 } );
});
});
it('Ignore unknown attribute', function() {
sharp(null, { unknown: true } );
});
});
describe('Raw pixel input', function() {
it('Missing options', function() {
assert.throws(function() {
sharp(null, { raw: {} } );
});
});
it('Incomplete options', function() {
assert.throws(function() {
sharp(null, { raw: { width: 1, height: 1} } );
});
});
it('Invalid channels', function() {
assert.throws(function() {
sharp(null, { raw: { width: 1, height: 1, channels: 5} } );
});
});
it('Invalid height', function() {
assert.throws(function() {
sharp(null, { raw: { width: 1, height: 0, channels: 4} } );
});
});
it('Invalid width', function() {
assert.throws(function() {
sharp(null, { raw: { width: 'zoinks', height: 1, channels: 4} } );
});
});
it('RGB', function(done) {
// Convert to raw pixel data
sharp(fixtures.inputJpg)
.resize(256)
.raw()
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(256, info.width);
assert.strictEqual(209, info.height);
assert.strictEqual(3, info.channels);
// Convert back to JPEG
sharp(data, {
raw: {
width: info.width,
height: info.height,
channels: info.channels
}})
.jpeg()
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(256, info.width);
assert.strictEqual(209, info.height);
assert.strictEqual(3, info.channels);
fixtures.assertSimilar(fixtures.inputJpg, data, done);
});
});
});
it('RGBA', function(done) {
// Convert to raw pixel data
sharp(fixtures.inputPngOverlayLayer1)
.resize(256)
.raw()
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(256, info.width);
assert.strictEqual(192, info.height);
assert.strictEqual(4, info.channels);
// Convert back to PNG
sharp(data, {
raw: {
width: info.width,
height: info.height,
channels: info.channels
}})
.png()
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(256, info.width);
assert.strictEqual(192, info.height);
assert.strictEqual(4, info.channels);
fixtures.assertSimilar(fixtures.inputPngOverlayLayer1, data, done);
});
});
});
});
it('Queue length change events', function(done) {
var eventCounter = 0;
var queueListener = function(queueLength) {
@@ -838,4 +1031,32 @@ describe('Input/output', function() {
});
});
it('Info event data', function(done) {
var readable = fs.createReadStream(fixtures.inputJPGBig);
var inPipeline = sharp()
.resize(840)
.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: 473, 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)
.raw();
readable.pipe(inPipeline).pipe(goodPipeline);
});
readable.pipe(inPipeline).pipe(badPipeline);
});
});

View File

@@ -8,8 +8,6 @@ var icc = require('icc');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Image metadata', function() {
it('JPEG', function(done) {
@@ -20,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);
@@ -37,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);
@@ -57,22 +57,25 @@ describe('Image metadata', function() {
});
});
it('TIFF', function(done) {
sharp(fixtures.inputTiff).metadata(function(err, metadata) {
if (err) throw err;
assert.strictEqual('tiff', metadata.format);
assert.strictEqual(2464, metadata.width);
assert.strictEqual(3248, metadata.height);
assert.strictEqual('b-w', metadata.space);
assert.strictEqual(1, metadata.channels);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
assert.strictEqual('undefined', typeof metadata.exif);
assert.strictEqual('undefined', typeof metadata.icc);
done();
if (sharp.format.tiff.input.file) {
it('TIFF', function(done) {
sharp(fixtures.inputTiff).metadata(function(err, metadata) {
if (err) throw err;
assert.strictEqual('tiff', metadata.format);
assert.strictEqual(2464, metadata.width);
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);
assert.strictEqual('undefined', typeof metadata.exif);
assert.strictEqual('undefined', typeof metadata.icc);
done();
});
});
});
}
it('PNG', function(done) {
sharp(fixtures.inputPng).metadata(function(err, metadata) {
@@ -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,21 +133,24 @@ describe('Image metadata', function() {
});
}
it('GIF via libmagick', function(done) {
sharp(fixtures.inputGif).metadata(function(err, metadata) {
if (err) throw err;
assert.strictEqual('magick', metadata.format);
assert.strictEqual(800, metadata.width);
assert.strictEqual(533, metadata.height);
assert.strictEqual(3, metadata.channels);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
assert.strictEqual('undefined', typeof metadata.exif);
assert.strictEqual('undefined', typeof metadata.icc);
done();
if (sharp.format.magick.input.file) {
it('GIF via libmagick', function(done) {
sharp(fixtures.inputGif).metadata(function(err, metadata) {
if (err) throw err;
assert.strictEqual('magick', metadata.format);
assert.strictEqual(800, metadata.width);
assert.strictEqual(533, metadata.height);
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);
assert.strictEqual('undefined', typeof metadata.exif);
assert.strictEqual('undefined', typeof metadata.icc);
done();
});
});
});
}
if (sharp.format.openslide.input.file) {
it('Aperio SVS via openslide', function(done) {
@@ -151,6 +160,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);
@@ -169,6 +179,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);
@@ -196,6 +207,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);
@@ -217,6 +229,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);
@@ -236,6 +249,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);
@@ -323,9 +337,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

@@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Negate', function() {
it('negate (jpeg)', function(done) {
sharp(fixtures.inputJpg)

View File

@@ -5,109 +5,108 @@ var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
var assertNormalized = function(data) {
var min = 255;
var max = 0;
for (var i = 0; i < data.length; i++) {
min = Math.min(min, data[i]);
max = Math.max(max, data[i]);
}
assert.strictEqual(0, min);
assert.strictEqual(255, max);
};
describe('Normalization', function () {
it('uses the same prototype for both spellings', function () {
it('uses the same prototype for both spellings', function() {
assert.strictEqual(sharp.prototype.normalize, sharp.prototype.normalise);
});
// Normalize is currently unavailable on Windows
if (process.platform !== 'win32') {
it('spreads rgb image values between 0 and 255', function(done) {
sharp(fixtures.inputJpgWithLowContrast)
.normalize()
.raw()
.toBuffer(function (err, data, info) {
if (err) throw err;
assertNormalized(data);
done();
});
});
it('spreads rgb image values between 0 and 255', function(done) {
sharp(fixtures.inputJpgWithLowContrast)
.normalize()
.raw()
.toBuffer(function (err, data, info) {
if (err) throw err;
var min = 255, max = 0, i;
for (i = 0; i < data.length; i += 3) {
min = Math.min(min, data[i], data[i + 1], data[i + 2]);
max = Math.max(max, data[i], data[i + 1], data[i + 2]);
}
assert.strictEqual(0, min);
assert.strictEqual(255, max);
done();
});
});
it('spreads grayscaled image values between 0 and 255', function(done) {
sharp(fixtures.inputJpgWithLowContrast)
.gamma()
.greyscale()
.normalize(true)
.raw()
.toBuffer(function (err, data, info) {
if (err) throw err;
assertNormalized(data);
done();
});
});
it('spreads grayscaled image values between 0 and 255', function(done) {
sharp(fixtures.inputJpgWithLowContrast)
.gamma()
.greyscale()
.normalize(true)
.raw()
.toBuffer(function (err, data, info) {
if (err) throw err;
var min = 255, max = 0, i;
for (i = 0; i < data.length; i++) {
min = Math.min(min, data[i]);
max = Math.max(max, data[i]);
}
assert.strictEqual(0, min);
assert.strictEqual(255, max);
done();
});
});
it('stretches greyscale images with alpha channel', function(done) {
sharp(fixtures.inputPngWithGreyAlpha)
.normalize()
.raw()
.toBuffer(function (err, data, info) {
if (err) throw err;
assertNormalized(data);
done();
});
});
it('stretches greyscale images with alpha channel', function (done) {
sharp(fixtures.inputPngWithGreyAlpha)
.normalize()
.raw()
.toBuffer(function (err, data, info) {
var min = 255, max = 0, i;
for (i = 0; i < data.length; i++) {
min = Math.min(min, data[i]);
max = Math.max(max, data[i]);
}
assert.strictEqual(0, min);
assert.strictEqual(255, max);
done();
});
});
it('keeps an existing alpha channel', function (done) {
sharp(fixtures.inputPngWithTransparency)
.normalize()
.toBuffer(function (err, data) {
it('keeps an existing alpha channel', function(done) {
sharp(fixtures.inputPngWithTransparency)
.normalize()
.toBuffer(function (err, data) {
if (err) throw err;
sharp(data).metadata(function(err, metadata) {
if (err) return done(err);
sharp(data).metadata(function(err, metadata) {
if (err) return done(err);
assert.strictEqual(4, metadata.channels);
assert.strictEqual(true, metadata.hasAlpha);
assert.strictEqual('srgb', metadata.space);
done();
});
});
});
it('keeps the alpha channel of greyscale images intact', function (done) {
sharp(fixtures.inputPngWithGreyAlpha)
.normalize()
.toBuffer(function (err, data) {
if (err) return done(err);
sharp(data).metadata(function(err, metadata) {
if (err) return done(err);
assert.strictEqual(true, metadata.hasAlpha);
assert.strictEqual(4, metadata.channels);
assert.strictEqual('srgb', metadata.space);
done();
});
});
});
it('does not alter images with only one color', function (done) {
var output = fixtures.path('output.unmodified-png-with-one-color.png');
sharp(fixtures.inputPngWithOneColor)
.normalize()
.toFile(output, function(err, info) {
if (err) done(err);
fixtures.assertMaxColourDistance(output, fixtures.inputPngWithOneColor, 0);
assert.strictEqual(4, metadata.channels);
assert.strictEqual(true, metadata.hasAlpha);
assert.strictEqual('srgb', metadata.space);
done();
});
});
});
});
it('keeps the alpha channel of greyscale images intact', function(done) {
sharp(fixtures.inputPngWithGreyAlpha)
.normalize()
.toBuffer(function (err, data) {
if (err) throw err;
sharp(data).metadata(function(err, metadata) {
if (err) return done(err);
assert.strictEqual(true, metadata.hasAlpha);
assert.strictEqual(4, metadata.channels);
assert.strictEqual('srgb', metadata.space);
done();
});
});
});
it('does not alter images with only one color', function(done) {
var output = fixtures.path('output.unmodified-png-with-one-color.png');
sharp(fixtures.inputPngWithOneColor)
.normalize()
.toFile(output, function(err, info) {
if (err) done(err);
fixtures.assertMaxColourDistance(output, fixtures.inputPngWithOneColor, 0);
done();
});
});
it('works with 16-bit RGBA images', function(done) {
sharp(fixtures.inputPngWithTransparency16bit)
.normalize()
.raw()
.toBuffer(function (err, data, info) {
if (err) throw err;
assertNormalized(data);
done();
});
});
}
});

View File

@@ -1,11 +1,10 @@
'use strict';
var fs = require('fs');
var assert = require('assert');
var fixtures = require('../fixtures');
var sharp = require('../../index');
sharp.cache(0);
// Helpers
var getPaths = function(baseName, extension) {
if (typeof extension === 'undefined') {
@@ -19,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)
@@ -31,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');
@@ -143,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();
@@ -172,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

@@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Resize dimensions', function() {
it('Exact crop', function(done) {
@@ -130,27 +128,58 @@ describe('Resize dimensions', function() {
done();
});
it('TIFF embed known to cause rounding errors', function(done) {
sharp(fixtures.inputTiff).resize(240, 320).embed().jpeg().toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(240, info.width);
assert.strictEqual(320, info.height);
done();
if (sharp.format.tiff.input.file) {
it('TIFF embed known to cause rounding errors', function(done) {
sharp(fixtures.inputTiff)
.resize(240, 320)
.embed()
.jpeg()
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(240, info.width);
assert.strictEqual(320, info.height);
done();
});
});
});
it('TIFF known to cause rounding errors', function(done) {
sharp(fixtures.inputTiff).resize(240, 320).jpeg().toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(240, info.width);
assert.strictEqual(320, info.height);
done();
it('TIFF known to cause rounding errors', function(done) {
sharp(fixtures.inputTiff)
.resize(240, 320)
.jpeg()
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(240, info.width);
assert.strictEqual(320, info.height);
done();
});
});
});
it('Max width or height considering ratio (portrait)', function(done) {
sharp(fixtures.inputTiff).resize(320, 320).max().jpeg().toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(243, info.width);
assert.strictEqual(320, info.height);
done();
});
});
it('Min width or height considering ratio (portrait)', function(done) {
sharp(fixtures.inputTiff).resize(320, 320).min().jpeg().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(422, info.height);
done();
});
});
}
it('Max width or height considering ratio (landscape)', function(done) {
sharp(fixtures.inputJpg).resize(320, 320).max().toBuffer(function(err, data, info) {
@@ -163,17 +192,6 @@ describe('Resize dimensions', function() {
});
});
it('Max width or height considering ratio (portrait)', function(done) {
sharp(fixtures.inputTiff).resize(320, 320).max().jpeg().toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(243, info.width);
assert.strictEqual(320, info.height);
done();
});
});
it('Provide only one dimension with max, should default to crop', function(done) {
sharp(fixtures.inputJpg).resize(320).max().toBuffer(function(err, data, info) {
if (err) throw err;
@@ -196,17 +214,6 @@ describe('Resize dimensions', function() {
});
});
it('Min width or height considering ratio (portrait)', function(done) {
sharp(fixtures.inputTiff).resize(320, 320).min().jpeg().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(422, info.height);
done();
});
});
it('Provide only one dimension with min, should default to crop', function(done) {
sharp(fixtures.inputJpg).resize(320).min().toBuffer(function(err, data, info) {
if (err) throw err;

View File

@@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Rotation', function() {
['Landscape', 'Portrait'].forEach(function(orientation) {
@@ -37,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)
@@ -113,7 +145,7 @@ describe('Rotation', function() {
});
it('Attempt to auto-rotate image format without EXIF support', function(done) {
sharp(fixtures.inputGif)
sharp(fixtures.inputPng)
.rotate()
.resize(320)
.jpeg()
@@ -122,7 +154,7 @@ describe('Rotation', function() {
assert.strictEqual(true, data.length > 0);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(213, info.height);
assert.strictEqual(236, info.height);
done();
});
});

View File

@@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Sharpen', function() {
it('specific radius 10', function(done) {

View File

@@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Threshold', function() {
it('threshold 1 jpeg', function(done) {
sharp(fixtures.inputJpg)

View File

@@ -6,12 +6,11 @@ var assert = require('assert');
var async = require('async');
var rimraf = require('rimraf');
var unzip = require('unzip');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
// Verifies all tiles in a given dz output directory are <= size
var assertDeepZoomTiles = function(directory, expectedSize, expectedLevels, done) {
// Get levels
@@ -49,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);
});
});
});
});
});
}
});

View File

@@ -10,20 +10,48 @@ describe('Utilities', function() {
describe('Cache', function() {
it('Can be disabled', function() {
var cache = sharp.cache(0, 0);
assert.strictEqual(0, cache.memory);
assert.strictEqual(0, cache.items);
sharp.cache(false);
var cache = sharp.cache(false);
assert.strictEqual(cache.memory.current, 0);
assert.strictEqual(cache.memory.max, 0);
assert.strictEqual(typeof cache.memory.high, 'number');
assert.strictEqual(cache.files.current, 0);
assert.strictEqual(cache.files.max, 0);
assert.strictEqual(cache.items.current, 0);
assert.strictEqual(cache.items.max, 0);
});
it('Can be set to a maximum of 50MB and 500 items', function() {
var cache = sharp.cache(50, 500);
assert.strictEqual(50, cache.memory);
assert.strictEqual(500, cache.items);
it('Can be enabled with defaults', function() {
var cache = sharp.cache(true);
assert.strictEqual(cache.memory.max, 50);
assert.strictEqual(cache.files.max, 20);
assert.strictEqual(cache.items.max, 100);
});
it('Can be set to zero', function() {
var cache = sharp.cache({
memory: 0,
files: 0,
items: 0
});
assert.strictEqual(cache.memory.max, 0);
assert.strictEqual(cache.files.max, 0);
assert.strictEqual(cache.items.max, 0);
});
it('Can be set to a maximum of 10MB, 100 files and 1000 items', function() {
var cache = sharp.cache({
memory: 10,
files: 100,
items: 1000
});
assert.strictEqual(cache.memory.max, 10);
assert.strictEqual(cache.files.max, 100);
assert.strictEqual(cache.items.max, 1000);
});
it('Ignores invalid values', function() {
sharp.cache(50, 500);
sharp.cache(true);
var cache = sharp.cache('spoons');
assert.strictEqual(50, cache.memory);
assert.strictEqual(500, cache.items);
assert.strictEqual(cache.memory.max, 50);
assert.strictEqual(cache.files.max, 20);
assert.strictEqual(cache.items.max, 100);
});
});
@@ -85,6 +113,13 @@ describe('Utilities', function() {
});
});
});
it('Raw file=false, buffer=true, stream=true', function() {
['input', 'output'].forEach(function(direction) {
assert.strictEqual(false, sharp.format.raw[direction].file);
assert.strictEqual(true, sharp.format.raw[direction].buffer);
assert.strictEqual(true, sharp.format.raw[direction].stream);
});
});
});
describe('Versions', function() {