Compare commits

..

40 Commits

Author SHA1 Message Date
Lovell Fuller
9982182926 Update perf test results ahead of v0.12.0 2015-11-23 13:09:06 +00:00
Lovell Fuller
607d157b76 Benchmark test updates ahead of v0.12.0 2015-11-23 10:53:52 +00:00
Lovell Fuller
e21277ceba Version bump of libpng Windows dependency 2015-11-22 21:11:29 +00:00
Lovell Fuller
8012733a52 Expose libvips+deps versions attribute
Add versions.json for Linux packaging

Bump vips-dev Windows version for latest libpng
2015-11-22 20:58:38 +00:00
Lovell Fuller
01a1377972 Include cmath header for clang #301 2015-11-22 09:39:42 +00:00
Lovell Fuller
37e4b9b5ba Update changelog ahead of v0.12.0
Highlight features in readme+docs

Add link to Docker-based Linux CI build status
2015-11-22 09:17:51 +00:00
Lovell Fuller
8a3098604c Remove experimental code that could prevent anti-alias filter 2015-11-21 23:37:44 +00:00
Lovell Fuller
5febce7a59 Remove executable bit from test/* file permissions 2015-11-21 23:05:48 +00:00
Lovell Fuller
3dbedf1fb6 Match g_malloc/g_free for make benefit glorious nation of Windows
Remove C++ standard lib from Windows packaging
2015-11-21 22:51:32 +00:00
Lovell Fuller
0ae619dfc5 Remove stray win32 library that was causing segfaults
Explicit int cast to prevent 'loss of precision' warnings

Remove unnecessary semver checking from I/O tests
2015-11-21 22:18:39 +00:00
Lovell Fuller
05dd191e17 Ensure 16-bit+alpha input images work with vips_premultiply #301
Improves SVG support as *magick serves these as 16-bit

Add automated tests for SVG and 16-bit+alpha PNG inputs
2015-11-21 20:21:34 +00:00
Lovell Fuller
c9ecc7a517 Document Debian 7 support
Add liborc to fix openSUSE support

Switch recommended *magick to ImageMagick for OS X

Increase test timeouts (for Circle CI)
2015-11-19 22:47:34 +00:00
Lovell Fuller
434a433a09 Make output of packaging tests easier to understand
Slight simplification of Linux packaging script
2015-11-19 21:36:16 +00:00
Lovell Fuller
3de54d897c Merge pull request #309 from papandreou/feature/extractWithOptionsObject
Add an options object to the extract operation, deprecate existing behaviour.
2015-11-18 11:48:11 +00:00
Andreas Lind
530c2a9fcf Stop using the legacy .extract(top,left,width,height) in the documentation. 2015-11-18 12:06:10 +01:00
Andreas Lind
60b8b92630 Add support for .extract({left:...,top:...,width:...,height:...}). 2015-11-18 12:06:10 +01:00
Lovell Fuller
5842da22d8 Merge pull request #306 from dacarley/negate
Add negate operation to invert all pixel values.
2015-11-17 20:45:09 +00:00
Lovell Fuller
9850e3dae0 Merge pull request #303 from dacarley/threshold
Add threshold operation.
Converts to greyscale then splits into black/white based on given value.
2015-11-17 20:34:40 +00:00
David Carley
3af62446fc Implements greyscale thresholding 2015-11-17 12:15:34 -06:00
Lovell Fuller
1f71dade67 Merge pull request #307 from papandreou/wheezy
Linux Dockerfile: Use Debian Wheezy as the base image
2015-11-17 17:29:35 +00:00
Lovell Fuller
8be664b66f Merge pull request #305 from papandreou/fix/imagemagickUrl
Dockerfile: Update ImageMagick tarball url (they took -5 down and up -6)
2015-11-17 17:24:30 +00:00
Andreas Lind
c0be4f1307 Dial back the required libc version to 2.13 2015-11-17 17:37:06 +01:00
Andreas Lind
d9c754f5c1 Linux Dockerfile: Use a Debian Wheezy image instead of Ubuntu Precise. 2015-11-17 17:37:06 +01:00
David Carley
33a175eafb Implements negation. 2015-11-17 10:18:59 -06:00
Andreas Lind
7c990b3ab3 Dockerfile: Update ImageMagick tarball url (they took -5 down and up put -6). 2015-11-17 17:16:19 +01:00
Lovell Fuller
5dfeaa9fd1 Ensure gaussian blur is applied before lbb interpolator #289 2015-11-16 08:26:35 +00:00
Lovell Fuller
84fd1caa46 Switch default interpolator to bicubic #289
Only use gaussian blur for non-linear interpolators

Improves performance of bilinear by ~15%

Add liborc to the packaged build to improve bicubic perf

Add examples of the various interpolation methods

Add bilinear vs bicubic to perf tests
2015-11-15 22:04:31 +00:00
Lovell Fuller
2678d761ba Use FreeCallback to support mixed Windows runtime libs #152 2015-11-14 13:58:05 +00:00
Lovell Fuller
ede2ee9ce3 Use Persistent wrapper to prevent GC of input Buffer #152
Avoids memcpy of input and output Buffer objects

Improves Buffer and Stream performance by ~3%
2015-11-14 11:24:15 +00:00
Lovell Fuller
20f468991f Start to use libvips 8.1.0+ features #152
Use native (un)premultiply

Support normalise on Windows
2015-11-12 22:14:53 +00:00
Lovell Fuller
58d9e0fef7 Pre-extract rotate should not swap width/height #296 2015-11-11 22:34:30 +00:00
Lovell Fuller
d7278f022b Ensure latest npm on openSUSE test 2015-11-11 22:25:17 +00:00
Lovell Fuller
7383596f8c Allow tty-less docker-inside-docker usage 2015-11-11 21:37:04 +00:00
Lovell Fuller
f6831ab46b Increase packaging test logging 2015-11-10 23:30:08 +00:00
Lovell Fuller
7cf0f95ed1 Fix Circle CI config
CI providers should really provide config linters :)
2015-11-10 21:53:26 +00:00
Lovell Fuller
bf6b894480 Improve dependency-less documentation
Start to comment ever-growing GYP config

Add Circle CI config to run packaging tests
2015-11-10 21:49:45 +00:00
Lovell Fuller
ee8fcb6109 Update docs to reflect ease-of-installation 2015-11-10 00:04:06 +00:00
Lovell Fuller
05cec013fe Ensure support for more Linux flavours
Add docker-based packaging tests
2015-11-09 08:37:30 +00:00
Lovell Fuller
f4cbbd7b79 Ensure Windows CI uses x64 2015-11-07 20:22:17 +00:00
Lovell Fuller
2129adfcc3 Initial commit of local libvips binding/packaging
Copy Windows DLLs into release dir as no rpath equivalent
Use local libvips on Windows CI
2015-11-07 19:58:26 +00:00
105 changed files with 2381 additions and 1041 deletions

12
.editorconfig Normal file
View File

@@ -0,0 +1,12 @@
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

2
.gitignore vendored
View File

@@ -6,4 +6,6 @@ test/fixtures/output*
test/leak/libvips.supp
lib
include
packaging/libvips*
packaging/*.log
.DS_Store

View File

@@ -7,6 +7,8 @@ coverage
test
.travis.yml
appveyor.yml
circle.yml
mkdocs.yml
lib
include
packaging

View File

@@ -2,18 +2,16 @@ language: node_js
node_js:
- "0.10"
- "0.12"
- "iojs-v2"
- "iojs-v3"
- "4"
sudo: 9000
- "5"
sudo: false
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
before_install:
- sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 90
- curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash -
env:
CXX=g++-4.8
after_success:
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js

View File

@@ -6,16 +6,10 @@ Hello, thank you for your interest in helping!
Please create a [new issue](https://github.com/lovell/sharp/issues/new) containing the steps to reproduce the problem.
If you're having installation problems, please include the output of running `npm install --verbose sharp`.
New bugs are assigned a `triage` label whilst under investigation.
If you're having problems with `npm install sharp`, please include the output of the following commands, perhaps as a [gist](https://gist.github.com/):
```sh
vips -v
pkg-config --print-provides vips
npm install --verbose sharp
```
## Submit a new feature request
If a [similar request](https://github.com/lovell/sharp/labels/enhancement) exists, it's probably fastest to add a comment to it about your requirement.
@@ -47,7 +41,6 @@ Any change that modifies the existing public API should be added to the relevant
| Release | WIP branch |
| ------: | :--------- |
| v0.11.0 | knife |
| v0.12.0 | look |
| v0.13.0 | mind |
@@ -85,6 +78,15 @@ Requires [Valgrind](http://valgrind.org/).
npm run test-leak
```
### Packaging tests
Tests the installation on a number of Linux-based operating systems.
Requires docker.
```sh
npm run test-packaging
```
## Finally
Please feel free to ask any questions via a [new issue](https://github.com/lovell/sharp/issues/new) or contact me by [e-mail](https://github.com/lovell/sharp/blob/master/package.json#L4).

View File

@@ -4,13 +4,30 @@ 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.
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.
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
the installation of any external runtime dependencies.
Use with OS X is as simple as running `brew install homebrew/science/vips`
to install the libvips dependency.
[![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.png?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master)
### Documentation
Visit [sharp.dimens.io](http://sharp.dimens.io/) for
complete installation instructions, API documentation,
benchmark tests and a changelog.
Visit [sharp.dimens.io](http://sharp.dimens.io/) for complete
[installation instructions](http://sharp.dimens.io/page/install),
[API documentation](http://sharp.dimens.io/page/api),
[benchmark tests](http://sharp.dimens.io/page/performance) and
[changelog](http://sharp.dimens.io/page/changelog).
### Contributing

View File

@@ -3,26 +3,13 @@ version: "{build}"
build: off
platform: x64
environment:
VIPS_VERSION_MAJOR_MINOR: 8.1
VIPS_VERSION_PATCH: 1
VIPS_WARNING: 0
matrix:
- nodejs_version: "0.12"
nodejs_exec: "node"
- nodejs_version: "2"
nodejs_exec: "iojs"
- nodejs_version: "3"
nodejs_exec: "iojs"
- nodejs_version: "4"
nodejs_exec: "node"
- nodejs_version: "5"
install:
- ps: $env:VIPS_VERSION = "$env:VIPS_VERSION_MAJOR_MINOR.$env:VIPS_VERSION_PATCH"
- ps: Write-Output "Fetching http://www.vips.ecs.soton.ac.uk/supported/$env:VIPS_VERSION_MAJOR_MINOR/win32/vips-dev-$env:VIPS_VERSION.zip"
- ps: Start-FileDownload http://www.vips.ecs.soton.ac.uk/supported/$env:VIPS_VERSION_MAJOR_MINOR/win32/vips-dev-$env:VIPS_VERSION.zip -FileName c:\vips-dev-$env:VIPS_VERSION.zip
- ps: Invoke-Expression "& 7z -y x c:\vips-dev-$env:VIPS_VERSION.zip -oc:\ | FIND /V `"ing `""
- ps: $env:VIPS_HOME = "c:\vips-dev-$env:VIPS_VERSION"
- ps: $env:PATH = "$env:VIPS_HOME\bin;$env:PATH"
- ps: Install-Product node $env:nodejs_version x86
- npm install --arch=ia32
- ps: Install-Product node $env:nodejs_version x64
- npm install
test_script:
- npm run-script test-win32-%nodejs_exec%
- npm run-script test-win

205
binding.gyp Executable file → Normal file
View File

@@ -1,8 +1,39 @@
{
'targets': [{
'target_name': 'sharp',
# Nested variables "pattern" borrowed from http://src.chromium.org/viewvc/chrome/trunk/src/build/common.gypi
'variables': {
'runtime_link%':'shared',
'variables': {
'variables': {
'conditions': [
['OS != "win"', {
# Build the PKG_CONFIG_PATH environment variable with all possible combinations
'pkg_config_path': '<!(which brew >/dev/null 2>&1 && eval $(brew --env) && echo $PKG_CONFIG_LIBDIR || true):$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig'
}, {
'pkg_config_path': ''
}]
],
},
'conditions': [
['OS != "win"', {
# Which version, if any, of libvips is available globally via pkg-config?
'global_vips_version': '<!(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --modversion vips 2>/dev/null || true)'
}, {
'global_vips_version': ''
}]
],
'pkg_config_path%': '<(pkg_config_path)'
},
'pkg_config_path%': '<(pkg_config_path)',
'runtime_link%': 'shared',
'conditions': [
['OS != "win"', {
# Does the globally available version of libvips, if any, meet the minimum version requirement?
'use_global_vips': '<!(GLOBAL_VIPS_VERSION="<(global_vips_version)" node -e "require(\'./binding\').use_global_vips()")'
}, {
'use_global_vips': ''
}]
]
},
'sources': [
'src/common.cc',
@@ -12,55 +43,70 @@
'src/sharp.cc',
'src/utilities.cc'
],
'include_dirs': [
'<!(node -e "require(\'nan\')")'
],
'conditions': [
['OS=="win"', {
'library_dirs': [
'$(VIPS_HOME)/lib'
],
'libraries': [
'libvips.dll.a',
'glib-2.0.lib',
'gobject-2.0.lib',
'gthread-2.0.lib',
'gmodule-2.0.lib',
'liblcms2.dll.a',
'libxml2.lib',
'intl.lib',
'libjpeg.dll.a',
'libexif.dll.a',
'libpng.lib',
'libtiff.dll.a',
'libMagickWand-6.Q16.dll.a',
'libMagickCore-6.Q16.dll.a',
'pango-1.0.lib',
'pangoft2-1.0.lib',
'libgsf-1.dll.a',
'libopenslide.dll.a',
'libfftw3.dll.a'
],
'include_dirs': [
'$(VIPS_HOME)/include',
'$(VIPS_HOME)/include/glib-2.0',
'$(VIPS_HOME)/lib/glib-2.0/include',
'<!(node -e "require(\'nan\')")'
]
}, {
['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)'],
'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 vips)']
}]
]
}, {
# Attempt to download pre-built libvips and install locally within node_modules
'include_dirs': [
'<(module_root_dir)/include',
'<(module_root_dir)/include/glib-2.0',
'<(module_root_dir)/lib/glib-2.0/include'
],
'conditions': [
['OS == "win"', {
'variables': {
'PKG_CONFIG_PATH': '<!(which brew >/dev/null 2>&1 && eval $(brew --env) && echo $PKG_CONFIG_LIBDIR || true):$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig'
'download_vips': '<!(node -e "require(\'./binding\').download_vips()")'
},
'libraries': [
'<!(PKG_CONFIG_PATH="<(PKG_CONFIG_PATH)" pkg-config --libs vips)'
],
'include_dirs': [
'<!(PKG_CONFIG_PATH="<(PKG_CONFIG_PATH)" pkg-config --cflags vips glib-2.0)',
'<!(node -e "require(\'nan\')")'
],
'conditions': [
['runtime_link == "static"', {
'libraries': ['<!(PKG_CONFIG_PATH="<(PKG_CONFIG_PATH)" pkg-config --libs vips --static)']
}]
'<(module_root_dir)/lib/libvips.lib',
'<(module_root_dir)/lib/libglib-2.0.lib',
'<(module_root_dir)/lib/libgobject-2.0.lib'
]
}]
}],
['OS == "linux"', {
'variables': {
'download_vips': '<!(LDD_VERSION="<!(ldd --version 2>&1 || true)" node -e "require(\'./binding\').download_vips()")'
},
'libraries': [
'<(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/libexif.so',
'<(module_root_dir)/lib/libgio-2.0.so',
'<(module_root_dir)/lib/libgmodule-2.0.so',
'<(module_root_dir)/lib/libgsf-1.so',
'<(module_root_dir)/lib/libjpeg.so',
'<(module_root_dir)/lib/libpng.so',
'<(module_root_dir)/lib/libtiff.so',
'<(module_root_dir)/lib/libwebp.so',
'<(module_root_dir)/lib/libz.so',
'<(module_root_dir)/lib/libffi.so',
'<(module_root_dir)/lib/libgthread-2.0.so',
'<(module_root_dir)/lib/liblcms2.so',
'<(module_root_dir)/lib/libpng16.so',
'<(module_root_dir)/lib/libxml2.so',
'<(module_root_dir)/lib/liborc-0.4.so',
# Ensure runtime linking is relative to sharp.node
'-Wl,-rpath=\'$${ORIGIN}/../../lib\''
]
}]
]
}]
],
'cflags_cc': [
'-std=c++0x',
@@ -69,28 +115,77 @@
'-O3'
],
'xcode_settings': {
'CLANG_CXX_LANGUAGE_STANDARD': 'c++11',
'CLANG_CXX_LIBRARY': 'libc++',
'MACOSX_DEPLOYMENT_TARGET': '10.7',
'OTHER_CPLUSPLUSFLAGS': [
'-std=c++11',
'-stdlib=libc++',
'-fexceptions',
'-Wall',
'-O3'
],
'MACOSX_DEPLOYMENT_TARGET': '10.7'
},
'msvs_settings': {
'VCCLCompilerTool': {
'ExceptionHandling': 1 # /EHsc
}
]
},
'configurations': {
'Release': {
'msvs_settings': {
'VCCLCompilerTool': {
'ExceptionHandling': 1,
'ExceptionHandling': 1
}
}
}
},
}, {
'target_name': 'win_copy_dlls',
'type': 'none',
'dependencies': [
'sharp'
],
'conditions': [
['OS == "win"', {
# Windows lacks support for rpath
'copies': [{
'destination': '<(module_root_dir)/build/Release',
'files': [
'<(module_root_dir)/lib/GNU.Gettext.dll',
'<(module_root_dir)/lib/libMagickCore-6.Q16-2.dll',
'<(module_root_dir)/lib/libMagickWand-6.Q16-2.dll',
'<(module_root_dir)/lib/libasprintf-0.dll',
'<(module_root_dir)/lib/libcairo-2.dll',
'<(module_root_dir)/lib/libcairo-gobject-2.dll',
'<(module_root_dir)/lib/libcairo-script-interpreter-2.dll',
'<(module_root_dir)/lib/libexif-12.dll',
'<(module_root_dir)/lib/libexpat-1.dll',
'<(module_root_dir)/lib/libffi-6.dll',
'<(module_root_dir)/lib/libfftw3-3.dll',
'<(module_root_dir)/lib/libfontconfig-1.dll',
'<(module_root_dir)/lib/libfreetype-6.dll',
'<(module_root_dir)/lib/libgcc_s_seh-1.dll',
'<(module_root_dir)/lib/libgdk_pixbuf-2.0-0.dll',
'<(module_root_dir)/lib/libgio-2.0-0.dll',
'<(module_root_dir)/lib/libglib-2.0-0.dll',
'<(module_root_dir)/lib/libgmodule-2.0-0.dll',
'<(module_root_dir)/lib/libgobject-2.0-0.dll',
'<(module_root_dir)/lib/libgsf-1-114.dll',
'<(module_root_dir)/lib/libgthread-2.0-0.dll',
'<(module_root_dir)/lib/libintl-8.dll',
'<(module_root_dir)/lib/libjpeg-62.dll',
'<(module_root_dir)/lib/liblcms2-2.dll',
'<(module_root_dir)/lib/libopenjpeg-1.dll',
'<(module_root_dir)/lib/libopenslide-0.dll',
'<(module_root_dir)/lib/libpango-1.0-0.dll',
'<(module_root_dir)/lib/libpangocairo-1.0-0.dll',
'<(module_root_dir)/lib/libpangowin32-1.0-0.dll',
'<(module_root_dir)/lib/libpixman-1-0.dll',
'<(module_root_dir)/lib/libpng16-16.dll',
'<(module_root_dir)/lib/libquadmath-0.dll',
'<(module_root_dir)/lib/libsqlite3-0.dll',
'<(module_root_dir)/lib/libssp-0.dll',
'<(module_root_dir)/lib/libtiff-5.dll',
'<(module_root_dir)/lib/libvips-42.dll',
'<(module_root_dir)/lib/libxml2-2.dll',
'<(module_root_dir)/lib/zlib1.dll'
]
}]
}]
]
}]
}

111
binding.js Normal file
View File

@@ -0,0 +1,111 @@
'use strict';
var fs = require('fs');
var path = require('path');
var zlib = require('zlib');
var semver = require('semver');
var request = require('request');
var tar = require('tar');
var tmp = require('os').tmpdir();
var distBaseUrl = 'https://dl.bintray.com/lovell/sharp/';
var vipsHeaderPath = path.join(__dirname, 'include', 'vips', 'vips.h');
// -- Helpers
// Does this file exist?
var isFile = function(file) {
var exists = false;
try {
exists = fs.statSync(file).isFile();
} catch (err) {}
return exists;
};
var unpack = function(tarPath) {
var extractor = tar.Extract({
path: __dirname
});
extractor.on('error', error);
extractor.on('end', function() {
if (!isFile(vipsHeaderPath)) {
error('Could not unpack ' + tarPath);
}
});
fs.createReadStream(tarPath).on('error', error)
.pipe(zlib.Unzip())
.pipe(extractor);
};
// Error
var error = function(msg) {
if (msg instanceof Error) {
msg = msg.message;
}
process.stderr.write('ERROR: ' + msg + '\n');
process.exit(1);
};
// -- Binary downloaders
module.exports.download_vips = function() {
// Has vips been installed locally?
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 libc >= 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/');
}
}
// Platform-specific .tar.gz
var tarFilename = ['libvips', process.env.npm_package_config_libvips, process.platform.substr(0, 3)].join('-') + '.tar.gz';
var tarPath = path.join(__dirname, 'packaging', tarFilename);
if (isFile(tarPath)) {
unpack(tarPath);
} else {
// Download
tarPath = path.join(tmp, tarFilename);
var tmpFile = fs.createWriteStream(tarPath).on('finish', function() {
unpack(tarPath);
});
request(distBaseUrl + tarFilename).on('response', function(response) {
if (response.statusCode !== 200) {
error(distBaseUrl + tarFilename + ' status code ' + response.statusCode);
}
}).on('error', function(err) {
error('Download from ' + distBaseUrl + tarFilename + ' failed: ' + err.message);
}).pipe(tmpFile);
}
}
};
module.exports.use_global_vips = function() {
var useGlobalVips = false;
var globalVipsVersion = process.env.GLOBAL_VIPS_VERSION;
if (globalVipsVersion) {
useGlobalVips = semver.gte(
globalVipsVersion,
process.env.npm_package_config_libvips
);
}
if (process.platform === 'darwin' && !useGlobalVips) {
if (globalVipsVersion) {
error(
'Found libvips ' + globalVipsVersion + ' but require ' + process.env.npm_package_config_libvips +
'\nPlease upgrade libvips by running: brew update && brew upgrade'
);
} else {
error('Please install libvips by running: brew install homebrew/science/vips --with-webp --with-graphicsmagick');
}
}
process.stdout.write(useGlobalVips ? 'true' : 'false');
};

6
circle.yml Normal file
View File

@@ -0,0 +1,6 @@
machine:
services:
- docker
test:
override:
- ./packaging/test.sh

View File

@@ -78,7 +78,7 @@ to share a single input Stream.
```javascript
var pipeline = sharp().rotate();
pipeline.clone().resize(800, 600).pipe(firstWritableStream);
pipeline.clone().extract(20, 20, 100, 100).pipe(secondWritableStream);
pipeline.clone().extract({ left: 20, top: 20, width: 100, height: 100 }).pipe(secondWritableStream);
readableStream.pipe(pipeline);
// firstWritableStream receives auto-rotated, resized readableStream
// secondWritableStream receives auto-rotated, extracted region of readableStream
@@ -201,14 +201,18 @@ Ignoring the aspect ratio of the input, stretch the image to the exact `width` a
Use the given interpolator for image resizing, where `interpolator` is an attribute of the `sharp.interpolator` Object e.g. `sharp.interpolator.bicubic`.
The default interpolator is `bicubic`, providing a general-purpose interpolator that is both fast and of good quality.
Possible interpolators, in order of performance, are:
* `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation), suitable for image enlargement only.
* `bilinear`: Use [bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation), the default and fastest image reduction interpolation.
* `bicubic`: Use [bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation), which typically reduces performance by 5%.
* `vertexSplitQuadraticBasisSpline`: Use [VSQBS interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48), which prevents "staircasing" and typically reduces performance by 5%.
* `locallyBoundedBicubic`: Use [LBB interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100), which prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" and typically reduces performance by a factor of 2.
* `nohalo`: Use [Nohalo interpolation](http://eprints.soton.ac.uk/268086/), which prevents acutance and typically reduces performance by a factor of 3.
* `bilinear`: Use [bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation), faster than bicubic but with less smooth results.
* `vertexSplitQuadraticBasisSpline`: Use the smoother [VSQBS interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48) to prevent "staircasing" when enlarging.
* `bicubic`: Use [bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) (the default).
* `locallyBoundedBicubic`: Use [LBB interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100), which prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" but typically reduces performance by a factor of 2.
* `nohalo`: Use [Nohalo interpolation](http://eprints.soton.ac.uk/268086/), which prevents acutance but typically reduces performance by a factor of 3.
[Compare the output of these interpolators](https://github.com/lovell/sharp/tree/master/test/interpolators)
```javascript
sharp(inputBuffer)
@@ -226,11 +230,11 @@ sharp(inputBuffer)
### Operations
#### extract(top, left, width, height)
#### extract({ left: left, top: top, width: width, height: height })
Extract a region of the image. Can be used with or without a `resize` operation.
`top` and `left` are the offset, in pixels, from the top-left corner.
`left` and `top` are the offset, in pixels, from the top-left corner.
`width` and `height` are the dimensions of the extracted image.
@@ -238,7 +242,7 @@ Use `extract` before `resize` for pre-resize extraction. Use `extract` after `re
```javascript
sharp(input)
.extract(top, left, width, height)
.extract({ left: left, top: top, width: width, height: height })
.toFile(output, function(err) {
// Extract a region of the input image, saving in the same format.
});
@@ -246,9 +250,9 @@ sharp(input)
```javascript
sharp(input)
.extract(topOffsetPre, leftOffsetPre, widthPre, heightPre)
.extract({ left: leftOffsetPre, top: topOffsetPre, width: widthPre, height: heightPre })
.resize(width, height)
.extract(topOffsetPost, leftOffsetPost, widthPost, heightPost)
.extract({ left: leftOffsetPost, top: topOffsetPost, width: widthPost, height: heightPost })
.toFile(output, function(err) {
// Extract a region, resize, then extract from the resized image
});
@@ -268,6 +272,10 @@ The default background is `{r: 0, g: 0, b: 0, a: 1}`, black without transparency
Merge alpha transparency channel, if any, with `background`.
#### negate()
Produces the "negative" of the image. White => Black, Black => White, Blue => Yellow, etc.
#### rotate([angle])
Rotate the output image by either an explicit angle or auto-orient based on the EXIF `Orientation` tag.
@@ -324,6 +332,12 @@ When a `radius` is provided, performs a slower, more accurate sharpen of the L c
* `flat`, if present, is a Number representing the level of sharpening to apply to "flat" areas, defaulting to a value of 1.0.
* `jagged`, if present, is a Number representing the level of sharpening to apply to "jagged" areas, defaulting to a value of 2.0.
#### threshold([threshold])
Converts all pixels in the image to greyscale white or black. Any pixel greather-than-or-equal-to the threshold (0..255) will be white. All others will be black.
* `threshold`, if present, is a Number, representing the level above which pixels will be forced to white.
#### gamma([gamma])
Apply a gamma correction by reducing the encoding (darken) pre-resize at a factor of `1/gamma` then increasing the encoding (brighten) post-resize at a factor of `gamma`.
@@ -555,6 +569,29 @@ sharp.queue.on('change', function(queueLength) {
});
```
#### versions
An Object containing the version numbers of libvips and, on Linux, its dependencies.
```javascript
> console.log(sharp.versions);
{ zlib: '1.2.8',
ffi: '3.2.1',
glib: '2.46.2',
xml: '2.9.2',
gsf: '1.14.34',
exif: '0.6.21',
jpeg: '1.4.2',
png: '1.6.19',
lcms: '2.7',
webp: '0.4.4',
tiff: '4.0.6',
magick: '6.9.2-6',
orc: '0.4.24',
vips: '8.1.1' }
```
### Utilities
#### sharp.cache([memory], [items])

View File

@@ -1,5 +1,43 @@
# Changelog
### v0.12 - "*look*"
#### v0.12.0 - TBD
* Bundle pre-compiled libvips and its dependencies for 64-bit Linux and Windows.
[#42](https://github.com/lovell/sharp/issues/42)
* Take advantage of libvips v8.1.0+ features.
[#152](https://github.com/lovell/sharp/issues/152)
* Add support for 64-bit Windows. Drop support for 32-bit Windows.
[#224](https://github.com/lovell/sharp/issues/224)
[@sabrehagen](https://github.com/sabrehagen)
* Switch default interpolator to bicubic.
[#289](https://github.com/lovell/sharp/issues/289)
[@mahnunchik](https://github.com/mahnunchik)
* Pre-extract rotatation should not swap width/height.
[#296](https://github.com/lovell/sharp/issues/296)
[@asilvas](https://github.com/asilvas)
* Ensure 16-bit+alpha input images are (un)premultiplied correctly.
[#301](https://github.com/lovell/sharp/issues/301)
[@izaakschroeder](https://github.com/izaakschroeder)
* Add `threshold` operation.
[#303](https://github.com/lovell/sharp/pull/303)
[@dacarley](https://github.com/dacarley)
* Add `negate` operation.
[#306](https://github.com/lovell/sharp/pull/306)
[@dacarley](https://github.com/dacarley)
* Support `options` Object with existing `extract` operation.
[#309](https://github.com/lovell/sharp/pull/309)
[@papandreou](https://github.com/papandreou)
### v0.11 - "*knife*"
#### v0.11.4 - 5<sup>th</sup> November 2015

View File

@@ -7,6 +7,18 @@ 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.
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.
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
the installation of any external runtime dependencies.
Use with OS X is as simple as running `brew install homebrew/science/vips`
to install the libvips dependency.
[![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.png?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master)
### Formats
@@ -25,14 +37,6 @@ suitable for use with "slippy map" tile viewers like
[OpenSeadragon](https://github.com/openseadragon/openseadragon)
and [Leaflet](https://github.com/turban/Leaflet.Zoomify).
### Features
As well as image resizing, operations such as
rotation, extraction, compositing and gamma correction are available.
Colour spaces, embedded ICC profiles and alpha transparency channels
are all handled correctly.
### Fast
This module is powered by the blazingly fast
@@ -84,6 +88,7 @@ the help and code contributions of the following people:
* [Victor Mateevitsi](https://github.com/mvictoras)
* [Alaric Holloway](https://github.com/skedastik)
* [Bernhard K. Weisshuhn](https://github.com/bkw)
* [David A. Carley](https://github.com/dacarley)
Thank you!

View File

@@ -6,75 +6,55 @@ npm install sharp
### Prerequisites
* Node.js v0.10+ or io.js
* [libvips](https://github.com/jcupitt/libvips) v7.40.0+ (8.1.1+ recommended)
* C++11 compatible compiler such as gcc 4.6+, clang 3.0+ or MSVC 2013 (Node v4+ requires gcc 4.8+)
* C++11 compatible compiler such as gcc 4.6+ (Node v4+ requires gcc 4.8+), clang 3.0+ or MSVC 2013
* [node-gyp](https://github.com/TooTallNate/node-gyp#installation)
### Linux
[![Ubuntu 12.04 Build Status](https://travis-ci.org/lovell/sharp.png?branch=master)](https://travis-ci.org/lovell/sharp)
[![Centos 6.5 Build Status](https://snap-ci.com/lovell/sharp/branch/master/build_image)](https://snap-ci.com/lovell/sharp/branch/master)
[![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)
For a system-wide installation of the most suitable version of
libvips and its dependencies on the following Operating Systems:
libvips and its dependencies are fetched and stored within `node_modules/sharp` 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.:
* Debian 7, 8
* Ubuntu 12.04, 14.04, 14.10, 15.04, 15.10
* Mint 13, 17
* Elementary 0.3
* RHEL/Centos/Scientific 6, 7
* Fedora 21, 22
* Amazon Linux 2015.03, 2015.09
* OpenSuse 13
* Centos 7
* Fedora 20, 21
* openSUSE 13.2
* Archlinux 2015.06.01
run the following as a user with `sudo` access:
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,
a system-wide installation of the most suitable version of
libvips and its dependencies can be achieved by running
the following command as a user with `sudo` access
(requires `curl` and `pkg-config`):
```sh
curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash -
```
or run the following as `root`:
```sh
curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | bash -
```
The [preinstall.sh](https://github.com/lovell/sharp/blob/master/preinstall.sh) script requires `curl` and `pkg-config`.
Add `--with-openslide` to enable OpenSlide support:
```sh
curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash -s -- --with-openslide
```
#### Ubuntu LTS
libvips v7.40.6 is available via a PPA.
##### 12.04
```sh
sudo add-apt-repository -y ppa:lovell/precise-backport-vips
sudo apt-get update
sudo apt-get install -y libvips-dev libgsf-1-dev
```
##### 14.04
```sh
sudo add-apt-repository -y ppa:lovell/trusty-backport-vips
sudo apt-get update
sudo apt-get install -y libvips-dev libgsf-1-dev
```
### 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)
Install libvips via homebrew:
libvips must be installed before `npm install` is run.
This can be achieved via homebrew:
```sh
brew install homebrew/science/vips --with-webp --with-graphicsmagick
brew install homebrew/science/vips
```
For GIF input and WebP output suppport use:
```sh
brew install homebrew/science/vips --with-imagemagick --with-webp
```
A missing or incorrectly configured _Xcode Command Line Tools_ installation
@@ -89,18 +69,13 @@ If so, please try `brew link gettext --force`.
### Windows
[![Windows Server 2012 Build Status](https://ci.appveyor.com/api/projects/status/pgtul704nkhhg6sg)](https://ci.appveyor.com/project/lovell/sharp)
[![Windows x64 Build Status](https://ci.appveyor.com/api/projects/status/pgtul704nkhhg6sg)](https://ci.appveyor.com/project/lovell/sharp)
Requires x86 32-bit Node.js or io.js (use `iojs.exe` rather than `node.exe`).
libvips and its dependencies are fetched and stored within `node_modules\sharp` during `npm install`.
This involves an automated HTTPS download of approximately 9MB.
The WebP format is currently unsupported.
1. Ensure the [node-gyp prerequisites](https://github.com/TooTallNate/node-gyp#installation) are met.
2. [Download](http://www.vips.ecs.soton.ac.uk/supported/current/win32/) and unzip `vips-dev.x.y.z.zip`.
3. Set the `VIPS_HOME` environment variable to the full path of the `vips-dev-x.y.z` directory.
4. Add `vips-dev-x.y.z\bin` to `PATH`.
Versions of MSVC more recent than 2013 may require the use of `npm install --arch=ia32 --msvs_version=2013`.
Only 64-bit (x64) `node.exe` is supported.
The WebP format is currently unavailable on Windows.
### Heroku
@@ -110,13 +85,20 @@ and its dependencies.
### Docker
[Marc Bachmann](https://github.com/marcbachmann) maintains a
[Dockerfile for libvips](https://github.com/marcbachmann/dockerfile-libvips).
[Marc Bachmann](https://github.com/marcbachmann) maintains an
[Ubuntu-based Dockerfile for libvips](https://github.com/marcbachmann/dockerfile-libvips).
```sh
docker pull marcbachmann/libvips
```
[Will Jordan](https://github.com/wjordan) maintains an
[Alpine-based Dockerfile for libvips](https://github.com/wjordan/dockerfile-libvips).
```sh
docker pull wjordan/libvips
```
### Build tools
* [gulp-responsive](https://www.npmjs.com/package/gulp-responsive)

View File

@@ -2,39 +2,40 @@
### Test environment
* AWS EC2 [c4.xlarge](http://aws.amazon.com/ec2/instance-types/#c4)
* Amazon Linux AMI 2015.09
* Node.js v4.1.2
* 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
### The contenders
* [jimp](https://www.npmjs.com/package/jimp) v0.2.8 - Image processing in pure JavaScript.
* [jimp](https://www.npmjs.com/package/jimp) v0.2.19 - 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)~~ - Wrapper around libmagick++, supports Buffers only. Does not currently work with Node.js v4.
* [imagemagick-native](https://www.npmjs.com/package/imagemagick-native) @5ab570e - 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.20.0 - Fully featured wrapper around GraphicsMagick's `convert` command line utility.
* sharp v0.11.3 / libvips v8.1.0 - Caching within libvips disabled to ensure a fair comparison.
* [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.
### The task
Decompress a 2725x2225 JPEG image, resize to 720x480 using bilinear interpolation, then compress to JPEG.
Decompress a 2725x2225 JPEG image, resize to 720x480 using bicubic interpolation (where available), then compress to JPEG.
### Results
| Module | Input | Output | Ops/sec | Speed-up |
| :---------- | :----- | :----- | ------: | -------: |
| jimp | file | file | 0.94 | 1.0 |
| jimp | buffer | buffer | 1.12 | 1.2 |
| lwip | file | file | 1.12 | 1.2 |
| lwip | buffer | buffer | 1.13 | 1.2 |
| gm | buffer | buffer | 5.50 | 5.9 |
| gm | file | file | 5.55 | 5.9 |
| imagemagick | file | file | 8.66 | 9.2 |
| sharp | stream | stream | 16.33 | 17.4 |
| sharp | file | file | 16.88 | 18.0 |
| sharp | file | buffer | 16.90 | 18.1 |
| sharp | buffer | file | 16.99 | 18.1 |
| sharp | buffer | buffer | 17.06 | 18.1 |
| Module | Input | Output | Ops/sec | Speed-up |
| :----------------- | :----- | :----- | ------: | -------: |
| jimp | file | file | 0.99 | 1.0 |
| jimp | buffer | buffer | 1.05 | 1.1 |
| 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 |
Greater performance can be expected with caching enabled (default) and using 8+ core machines.
@@ -50,7 +51,11 @@ brew install graphicsmagick
```
```sh
sudo apt-get install imagemagick graphicsmagick libmagick++-dev
sudo apt-get install imagemagick libmagick++-dev graphicsmagick
```
```sh
sudo yum install ImageMagick-devel ImageMagick-c++-devel GraphicsMagick
```
### Running the benchmark test

143
index.js Executable file → Normal file
View File

@@ -10,14 +10,31 @@ var color = require('color');
var BluebirdPromise = require('bluebird');
var sharp = require('./build/Release/sharp');
var libvipsVersion = sharp.libvipsVersion();
// Versioning
var versions = {
vips: sharp.libvipsVersion()
};
(function() {
// Does libvips meet minimum requirement?
var libvipsVersionMin = require('./package.json').config.libvips;
if (semver.lt(versions.vips, libvipsVersionMin)) {
throw new Error('Found libvips ' + versions.vips + ' but require at least ' + libvipsVersionMin);
}
// Include versions of dependencies, if present
try {
versions = require('./lib/versions.json');
} catch (err) {}
})();
// Limits
var maximum = {
width: 0x3FFF,
height: 0x3FFF,
pixels: Math.pow(0x3FFF, 2)
};
// Constructor-factory
var Sharp = function(input) {
if (!(this instanceof Sharp)) {
return new Sharp(input);
@@ -49,14 +66,16 @@ var Sharp = function(input) {
flip: false,
flop: false,
withoutEnlargement: false,
interpolator: 'bilinear',
interpolator: 'bicubic',
// operations
background: [0, 0, 0, 255],
flatten: false,
negate: false,
blurSigma: 0,
sharpenRadius: 0,
sharpenFlat: 1,
sharpenJagged: 2,
threshold: 0,
gamma: 0,
greyscale: false,
normalize: 0,
@@ -109,6 +128,11 @@ module.exports.queue = new events.EventEmitter();
*/
module.exports.format = sharp.format();
/*
Version numbers of libvips and its dependencies
*/
module.exports.versions = versions;
/*
Handle incoming chunk on Writable Stream
*/
@@ -137,7 +161,18 @@ Sharp.prototype._write = function(chunk, encoding, callback) {
};
// Crop this part of the resized image (Center/Centre, North, East, South, West)
module.exports.gravity = {'center': 0, 'centre': 0, 'north': 1, 'east': 2, 'south': 3, 'west': 4, 'northeast': 5, 'southeast': 6, 'southwest': 7, 'northwest': 8};
module.exports.gravity = {
'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) {
this.options.canvas = 'crop';
@@ -151,17 +186,25 @@ Sharp.prototype.crop = function(gravity) {
return this;
};
Sharp.prototype.extract = function(topOffset, leftOffset, width, height) {
/*jslint unused: false */
Sharp.prototype.extract = function(options) {
if (!options || typeof options !== 'object') {
// Legacy extract(top,left,width,height) syntax
options = {
left: arguments[1],
top: arguments[0],
width: arguments[2],
height: arguments[3]
};
}
var suffix = this.options.width === -1 && this.options.height === -1 ? 'Pre' : 'Post';
var values = arguments;
['topOffset', 'leftOffset', 'width', 'height'].forEach(function(name, index) {
if (typeof values[index] === 'number' && !Number.isNaN(values[index]) && (values[index] % 1 === 0) && values[index] >= 0) {
this.options[name + suffix] = values[index];
['left', 'top', 'width', 'height'].forEach(function (name) {
var value = options[name];
if (typeof value === 'number' && !Number.isNaN(value) && value % 1 === 0 && value >= 0) {
this.options[name + (name === 'left' || name === 'top' ? 'Offset' : '') + suffix] = value;
} else {
throw new Error('Non-integer value for ' + name + ' of ' + values[index]);
throw new Error('Non-integer value for ' + name + ' of ' + value);
}
}.bind(this));
}, this);
// Ensure existing rotation occurs before pre-resize extraction
if (suffix === 'Pre' && this.options.angle !== 0) {
this.options.rotateBeforePreExtract = true;
@@ -210,6 +253,11 @@ Sharp.prototype.flatten = function(flatten) {
return this;
};
Sharp.prototype.negate = function(negate) {
this.options.negate = (typeof negate === 'boolean') ? negate : true;
return this;
};
Sharp.prototype.overlayWith = function(overlayPath) {
if (typeof overlayPath !== 'string') {
throw new Error('The overlay path must be a string');
@@ -323,6 +371,19 @@ Sharp.prototype.sharpen = function(radius, flat, jagged) {
return this;
};
Sharp.prototype.threshold = function(threshold) {
if (typeof threshold === 'undefined') {
this.options.threshold = 128;
} else if (typeof threshold === 'boolean') {
this.options.threshold = threshold ? 128 : 0;
} else if (typeof threshold === 'number' && !Number.isNaN(threshold) && (threshold % 1 === 0) && threshold >= 0 && threshold <= 255) {
this.options.threshold = threshold;
} else {
throw new Error('Invalid threshold (0 to 255) ' + threshold);
}
return this;
};
/*
Set the interpolator to use for the affine transformation
*/
@@ -370,11 +431,7 @@ Sharp.prototype.gamma = function(gamma) {
Enhance output image contrast by stretching its luminance to cover the full dynamic range
*/
Sharp.prototype.normalize = function(normalize) {
if (process.platform !== 'win32') {
this.options.normalize = (typeof normalize === 'boolean') ? normalize : true;
} else {
console.error('normalize unavailable on win32 platform');
}
this.options.normalize = (typeof normalize === 'boolean') ? normalize : true;
return this;
};
Sharp.prototype.normalise = Sharp.prototype.normalize;
@@ -420,14 +477,10 @@ Sharp.prototype.compressionLevel = function(compressionLevel) {
};
/*
Disable the use of adaptive row filtering for PNG output - requires libvips 7.42.0+
Disable the use of adaptive row filtering for PNG output
*/
Sharp.prototype.withoutAdaptiveFiltering = function(withoutAdaptiveFiltering) {
if (semver.gte(libvipsVersion, '7.42.0')) {
this.options.withoutAdaptiveFiltering = (typeof withoutAdaptiveFiltering === 'boolean') ? withoutAdaptiveFiltering : true;
} else {
console.error('withoutAdaptiveFiltering requires libvips 7.41.0+');
}
this.options.withoutAdaptiveFiltering = (typeof withoutAdaptiveFiltering === 'boolean') ? withoutAdaptiveFiltering : true;
return this;
};
@@ -440,41 +493,29 @@ Sharp.prototype.withoutChromaSubsampling = function(withoutChromaSubsampling) {
};
/*
Apply trellis quantisation to JPEG output - requires libvips 8.0.0+ compiled against mozjpeg 3.0+
Apply trellis quantisation to JPEG output - requires mozjpeg 3.0+
*/
Sharp.prototype.trellisQuantisation = function(trellisQuantisation) {
if (semver.gte(libvipsVersion, '8.0.0')) {
this.options.trellisQuantisation = (typeof trellisQuantisation === 'boolean') ? trellisQuantisation : true;
} else {
console.error('trellisQuantisation requires libvips 8.0.0+');
}
this.options.trellisQuantisation = (typeof trellisQuantisation === 'boolean') ? trellisQuantisation : true;
return this;
};
Sharp.prototype.trellisQuantization = Sharp.prototype.trellisQuantisation;
/*
Apply overshoot deringing to JPEG output - requires libvips 8.0.0+ compiled against mozjpeg 3.0+
Apply overshoot deringing to JPEG output - requires mozjpeg 3.0+
*/
Sharp.prototype.overshootDeringing = function(overshootDeringing) {
if (semver.gte(libvipsVersion, '8.0.0')) {
this.options.overshootDeringing = (typeof overshootDeringing === 'boolean') ? overshootDeringing : true;
} else {
console.error('overshootDeringing requires libvips 8.0.0+');
}
this.options.overshootDeringing = (typeof overshootDeringing === 'boolean') ? overshootDeringing : true;
return this;
};
/*
Optimise scans in progressive JPEG output - requires libvips 8.0.0+ compiled against mozjpeg 3.0+
Optimise scans in progressive JPEG output - requires mozjpeg 3.0+
*/
Sharp.prototype.optimiseScans = function(optimiseScans) {
if (semver.gte(libvipsVersion, '8.0.0')) {
this.options.optimiseScans = (typeof optimiseScans === 'boolean') ? optimiseScans : true;
if (this.options.optimiseScans) {
this.progressive();
}
} else {
console.error('optimiseScans requires libvips 8.0.0+');
this.options.optimiseScans = (typeof optimiseScans === 'boolean') ? optimiseScans : true;
if (this.options.optimiseScans) {
this.progressive();
}
return this;
};
@@ -493,7 +534,7 @@ Sharp.prototype.withMetadata = function(withMetadata) {
typeof withMetadata.orientation === 'number' &&
!Number.isNaN(withMetadata.orientation) &&
withMetadata.orientation % 1 === 0 &&
withMetadata.orientation >=0 &&
withMetadata.orientation >= 0 &&
withMetadata.orientation <= 7
) {
this.options.withMetadataOrientation = withMetadata.orientation;
@@ -519,7 +560,7 @@ Sharp.prototype.tile = function(size, overlap) {
}
// Overlap of tiles, in pixels
if (typeof overlap !== 'undefined' && overlap !== null) {
if (!Number.isNaN(overlap) && overlap % 1 === 0 && overlap >=0 && overlap <= 8192) {
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);
}
@@ -628,12 +669,7 @@ Sharp.prototype.webp = function() {
Force raw, uint8 output
*/
Sharp.prototype.raw = function() {
var supportsRawOutput = module.exports.format.raw.output;
if (supportsRawOutput.file || supportsRawOutput.buffer || supportsRawOutput.stream) {
this.options.output = '__raw';
} else {
console.error('Raw output requires libvips 7.42.0+');
}
this.options.output = '__raw';
return this;
};
@@ -827,10 +863,3 @@ module.exports.concurrency = function(concurrency) {
module.exports.counters = function() {
return sharp.counters();
};
/*
Get the version of the libvips library
*/
module.exports.libvipsVersion = function() {
return libvipsVersion;
};

View File

@@ -1,6 +1,6 @@
{
"name": "sharp",
"version": "0.11.4",
"version": "0.12.0",
"author": "Lovell Fuller <npm@lovell.info>",
"contributors": [
"Pierre Inglebert <pierre.inglebert@gmail.com>",
@@ -17,16 +17,17 @@
"Victor Mateevitsi <mvictoras@gmail.com>",
"Alaric Holloway <alaric.holloway@gmail.com>",
"Bernhard K. Weisshuhn <bkw@codingforce.com>",
"Chris Riley <criley@primedia.com>"
"Chris Riley <criley@primedia.com>",
"David Carley <dacarley@gmail.com>"
],
"description": "High performance Node.js module to resize JPEG, PNG, WebP and TIFF images using the libvips library",
"scripts": {
"clean": "rm -rf test/fixtures/output.*",
"test": "VIPS_WARNING=0 node ./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha -- --slow=5000 --timeout=20000 ./test/unit/*.js",
"test-clean": "npm run clean && npm install && npm test",
"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-leak": "./test/leak/leak.sh",
"test-win32-node": "node ./node_modules/mocha/bin/mocha --slow=5000 --timeout=30000 ./test/unit/*.js",
"test-win32-iojs": "iojs ./node_modules/mocha/bin/mocha --slow=5000 --timeout=30000 ./test/unit/*.js"
"test-packaging": "./packaging/test.sh",
"test-clean": "rm -rf coverage/ test/fixtures/output.* && npm install && npm test"
},
"main": "index.js",
"repository": {
@@ -49,21 +50,26 @@
"bluebird": "^3.0.5",
"color": "^0.10.1",
"nan": "^2.1.0",
"semver": "^5.0.3"
"semver": "^5.1.0",
"request": "^2.67.0",
"tar": "^2.2.1"
},
"devDependencies": {
"async": "^1.5.0",
"coveralls": "^2.11.4",
"exif-reader": "1.0.0",
"exif-reader": "^1.0.0",
"icc": "^0.0.2",
"istanbul": "^0.4.0",
"mocha": "^2.3.3",
"mocha": "^2.3.4",
"mocha-jshint": "^2.2.5",
"node-cpplint": "^0.4.0",
"rimraf": "^2.4.3",
"rimraf": "^2.4.4",
"bufferutil": "^1.2.1"
},
"license": "Apache-2.0",
"config": {
"libvips": "8.1.1"
},
"engines": {
"node": ">=0.10"
}

28
packaging/build.sh Executable file
View File

@@ -0,0 +1,28 @@
#!/bin/sh
# Is docker available?
if ! type docker >/dev/null; then
echo "Please install docker"
exit 1
fi
# TODO: docker v1.9.0 will allow build-time args - https://github.com/docker/docker/pull/15182
# Windows
docker build -t vips-dev-win win
WIN_CONTAINER_ID=$(docker run -d vips-dev-win)
docker cp $WIN_CONTAINER_ID:/libvips-8.1.1-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
# Checksums
sha256sum *.tar.gz

137
packaging/lin/Dockerfile Normal file
View File

@@ -0,0 +1,137 @@
FROM debian:wheezy
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
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
# 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
RUN mkdir ${DEPS}/zlib
RUN curl -Ls http://zlib.net/zlib-${VERSION_ZLIB}.tar.xz | tar xJC ${DEPS}/zlib --strip-components=1
WORKDIR ${DEPS}/zlib
RUN ./configure --prefix=${TARGET} && make install
RUN rm ${TARGET}/lib/libz.a
RUN mkdir ${DEPS}/ffi
RUN curl -Ls ftp://sourceware.org/pub/libffi/libffi-${VERSION_FFI}.tar.gz | tar xzC ${DEPS}/ffi --strip-components=1
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
WORKDIR ${DEPS}/glib
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
RUN mkdir ${DEPS}/xml2
RUN curl -Ls http://xmlsoft.org/sources/libxml2-${VERSION_XML2}.tar.gz | tar xzC ${DEPS}/xml2 --strip-components=1
WORKDIR ${DEPS}/xml2
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --without-python --with-zlib=${TARGET} && make install-strip
RUN 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
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
WORKDIR ${DEPS}/exif
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && 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
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
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
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
WORKDIR ${DEPS}/vips
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--disable-debug --disable-introspection --without-python --without-fftw \
--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
WORKDIR ${TARGET}/include
RUN rm -rf vips/vipsc++.h vips/vipscpp.h vips/V*.h
WORKDIR ${TARGET}/lib
RUN rm -rf pkgconfig .libs *.la libvipsCC* libvips-cpp.*
# Create JSON file of version numbers
WORKDIR ${TARGET}
RUN echo "{\n\
\"zlib\": \"${VERSION_ZLIB}\",\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\
\"orc\": \"${VERSION_ORC}\",\n\
\"vips\": \"${VERSION_VIPS}\"\n\
}" >lib/versions.json
# Create .tar.gz
WORKDIR ${TARGET}
RUN GZIP=-9 tar czf /libvips-${VERSION_VIPS}-lin.tar.gz include lib

50
packaging/test.sh Executable file
View File

@@ -0,0 +1,50 @@
#!/bin/sh
# Verify docker is available
if ! type docker >/dev/null; then
echo "Please install docker"
exit 1
fi
test="npm run clean; NODE_ENV=development 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";
then echo "$dist OK"
else echo "$dist fail" && cat packaging/$dist.log
fi
done
# Centos 6
echo "Testing centos6..."
if docker run -i -t --rm -v $PWD:/v nodesource/centos6:0.12 >packaging/centos6.log 2>&1 sh -c "cd /v; source ./packaging/test/centos6.sh; ./preinstall.sh; $test";
then echo "centos6 OK"
else echo "centos6 fail" && cat packaging/centos6.log
fi
# Centos 7
# Fedora 20, 21
for dist in centos7 fedora20 fedora21; do
echo "Testing $dist..."
if docker run -i -t --rm -v $PWD:/v nodesource/$dist:0.12 >packaging/$dist.log 2>&1 sh -c "cd /v; $test";
then echo "$dist OK"
else echo "$dist fail" && cat packaging/$dist.log
fi
done
# openSUSE 13.2
echo "Testing opensuse..."
if docker run -i -t --rm -v $PWD:/v opensuse:13.2 >packaging/opensuse.log 2>&1 /bin/sh -c "cd /v; ./packaging/test/opensuse.sh; $test";
then echo "opensuse OK"
else echo "opensuse fail" && cat packaging/opensuse.log
fi
# Archlinux 2015.06.01
echo "Testing archlinux..."
if docker run -i -t --rm -v $PWD:/v base/archlinux:2015.06.01 >packaging/archlinux.log 2>&1 sh -c "cd /v; ./packaging/test/archlinux.sh; $test";
then echo "archlinux OK"
else echo "archlinux fail" && cat packaging/archlinux.log
fi

5
packaging/test/archlinux.sh Executable file
View File

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

8
packaging/test/centos6.sh Executable file
View File

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

5
packaging/test/debian.sh Executable file
View File

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

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

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

16
packaging/win/Dockerfile Normal file
View File

@@ -0,0 +1,16 @@
FROM ubuntu:precise
MAINTAINER Lovell Fuller <npm@lovell.info>
RUN apt-get update && apt-get install -y curl zip
# 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
# Clean and zip
WORKDIR /vips/vips-dev-8.1.1
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

View File

@@ -33,7 +33,7 @@ install_libvips_from_source() {
rm -rf vips-$vips_version_latest_major_minor.$vips_version_latest_patch
rm vips-$vips_version_latest_major_minor.$vips_version_latest_patch.tar.gz
ldconfig
echo "Installed libvips $vips_version_latest_major_minor.$vips_version_latest_patch"
echo "Installed libvips $(PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig pkg-config --modversion vips)"
}
install_libopenslide_from_source() {
@@ -249,14 +249,14 @@ elif [ -f /etc/redhat-release ]; then
# RHEL/CentOS 7
echo "Installing libvips dependencies via yum"
yum groupinstall -y "Development Tools"
yum install -y gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel libgsf-devel lcms2-devel ImageMagick-devel gobject-introspection-devel libwebp-devel curl
yum install -y tar curl gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel libgsf-devel lcms2-devel ImageMagick-devel gobject-introspection-devel libwebp-devel
install_libvips_from_source "--prefix=/usr"
;;
"Red Hat Enterprise Linux release 6."*|"CentOS release 6."*|"Scientific Linux release 6."*)
# RHEL/CentOS 6
echo "Installing libvips dependencies via yum"
yum groupinstall -y "Development Tools"
yum install -y gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel libgsf-devel lcms-devel ImageMagick-devel curl
yum install -y tar curl gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel libgsf-devel lcms-devel ImageMagick-devel
yum install -y http://li.nux.ro/download/nux/dextop/el6/x86_64/nux-dextop-release-0-2.el6.nux.noarch.rpm
yum install -y --enablerepo=nux-dextop gobject-introspection-devel
yum install -y http://rpms.famillecollet.com/enterprise/remi-release-6.rpm

25
src/common.cc Executable file → Normal file
View File

@@ -7,12 +7,8 @@
// Verify platform and compiler compatibility
#if (VIPS_MAJOR_VERSION < 7 || (VIPS_MAJOR_VERSION == 7 && VIPS_MINOR_VERSION < 40))
#error libvips version 7.40.0+ required - see http://sharp.dimens.io/page/install
#endif
#ifdef _WIN64
#error Windows 64-bit is currently unsupported - see http://sharp.dimens.io/page/install#windows
#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
#endif
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
@@ -82,7 +78,7 @@ namespace sharp {
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, NULL, "access", access, NULL);
return vips_image_new_from_buffer(buffer, length, nullptr, "access", access, nullptr);
}
/*
@@ -91,7 +87,7 @@ namespace sharp {
ImageType DetermineImageType(char const *file) {
ImageType imageType = ImageType::UNKNOWN;
char const *load = vips_foreign_find_load(file);
if (load != NULL) {
if (load != nullptr) {
std::string loader = load;
if (EndsWith(loader, "JpegFile")) {
imageType = ImageType::JPEG;
@@ -114,7 +110,7 @@ namespace sharp {
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, NULL);
return vips_image_new_from_file(file, "access", access, nullptr);
}
/*
@@ -173,7 +169,7 @@ namespace sharp {
*/
int InterpolatorWindowSize(char const *name) {
VipsInterpolate *interpolator = vips_interpolate_new(name);
if (interpolator == NULL) {
if (interpolator == nullptr) {
return -1;
}
int window_size = vips_interpolate_get_window_size(interpolator);
@@ -181,4 +177,13 @@ namespace sharp {
return window_size;
}
/*
Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows
*/
void FreeCallback(char* data, void* hint) {
if (data != nullptr) {
g_free(data);
}
}
} // namespace sharp

5
src/common.h Executable file → Normal file
View File

@@ -80,6 +80,11 @@ namespace sharp {
*/
int InterpolatorWindowSize(char const *name);
/*
Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows
*/
void FreeCallback(char* data, void* hint);
} // namespace sharp
#endif // SRC_COMMON_H_

67
src/metadata.cc Executable file → Normal file
View File

@@ -35,6 +35,7 @@ using sharp::InitImage;
using sharp::HasProfile;
using sharp::HasAlpha;
using sharp::ExifOrientation;
using sharp::FreeCallback;
using sharp::counterQueue;
struct MetadataBaton {
@@ -64,20 +65,15 @@ struct MetadataBaton {
iccLength(0) {}
};
/*
Delete input char[] buffer and notify V8 of memory deallocation
Used as the callback function for the "postclose" signal
*/
static void DeleteBuffer(VipsObject *object, char *buffer) {
if (buffer != NULL) {
delete[] buffer;
}
}
class MetadataWorker : public AsyncWorker {
public:
MetadataWorker(Callback *callback, MetadataBaton *baton) : AsyncWorker(callback), baton(baton) {}
MetadataWorker(Callback *callback, MetadataBaton *baton, const Local<Object> &bufferIn) :
AsyncWorker(callback), baton(baton) {
if (baton->bufferInLength > 0) {
SaveToPersistent("bufferIn", bufferIn);
}
}
~MetadataWorker() {}
void Execute() {
@@ -85,30 +81,25 @@ class MetadataWorker : public AsyncWorker {
g_atomic_int_dec_and_test(&counterQueue);
ImageType imageType = ImageType::UNKNOWN;
VipsImage *image = NULL;
VipsImage *image = nullptr;
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 != NULL) {
// Listen for "postclose" signal to delete input buffer
g_signal_connect(image, "postclose", G_CALLBACK(DeleteBuffer), baton->bufferIn);
} else {
if (image == nullptr) {
(baton->err).append("Input buffer has corrupt header");
imageType = ImageType::UNKNOWN;
DeleteBuffer(NULL, baton->bufferIn);
}
} else {
(baton->err).append("Input buffer contains unsupported image format");
DeleteBuffer(NULL, baton->bufferIn);
}
} else {
// From file
imageType = DetermineImageType(baton->fileIn.c_str());
imageType = DetermineImageType(baton->fileIn.data());
if (imageType != ImageType::UNKNOWN) {
image = InitImage(baton->fileIn.c_str(), VIPS_ACCESS_RANDOM);
if (image == NULL) {
image = InitImage(baton->fileIn.data(), VIPS_ACCESS_RANDOM);
if (image == nullptr) {
(baton->err).append("Input file has corrupt header");
imageType = ImageType::UNKNOWN;
}
@@ -116,7 +107,7 @@ class MetadataWorker : public AsyncWorker {
(baton->err).append("Input file is of an unsupported image format");
}
}
if (image != NULL && imageType != ImageType::UNKNOWN) {
if (image != nullptr && imageType != ImageType::UNKNOWN) {
// Image type
switch (imageType) {
case ImageType::JPEG: baton->format = "jpeg"; break;
@@ -142,7 +133,7 @@ class MetadataWorker : public AsyncWorker {
size_t exifLength;
if (!vips_image_get_blob(image, VIPS_META_EXIF_NAME, &exif, &exifLength)) {
baton->exifLength = exifLength;
baton->exif = static_cast<char*>(malloc(exifLength));
baton->exif = static_cast<char*>(g_malloc(exifLength));
memcpy(baton->exif, exif, exifLength);
}
}
@@ -152,7 +143,7 @@ class MetadataWorker : public AsyncWorker {
size_t iccLength;
if (!vips_image_get_blob(image, VIPS_META_ICC_NAME, &icc, &iccLength)) {
baton->iccLength = iccLength;
baton->icc = static_cast<char*>(malloc(iccLength));
baton->icc = static_cast<char*>(g_malloc(iccLength));
memcpy(baton->icc, icc, iccLength);
}
}
@@ -170,7 +161,7 @@ class MetadataWorker : public AsyncWorker {
Local<Value> argv[2] = { Null(), Null() };
if (!baton->err.empty()) {
// Error
argv[0] = Error(baton->err.c_str());
argv[0] = Error(baton->err.data());
} else {
// Metadata Object
Local<Object> info = New<Object>();
@@ -185,13 +176,24 @@ class MetadataWorker : public AsyncWorker {
Set(info, New("orientation").ToLocalChecked(), New<Number>(baton->orientation));
}
if (baton->exifLength > 0) {
Set(info, New("exif").ToLocalChecked(), NewBuffer(baton->exif, baton->exifLength).ToLocalChecked());
Set(info,
New("exif").ToLocalChecked(),
NewBuffer(baton->exif, baton->exifLength, FreeCallback, nullptr).ToLocalChecked()
);
}
if (baton->iccLength > 0) {
Set(info, New("icc").ToLocalChecked(), NewBuffer(baton->icc, baton->iccLength).ToLocalChecked());
Set(info,
New("icc").ToLocalChecked(),
NewBuffer(baton->icc, baton->iccLength, FreeCallback, nullptr).ToLocalChecked()
);
}
argv[1] = info;
}
// Dispose of Persistent wrapper around input Buffer so it can be garbage collected
if (baton->bufferInLength > 0) {
GetFromPersistent("bufferIn");
}
delete baton;
// Return to JavaScript
@@ -215,17 +217,16 @@ NAN_METHOD(metadata) {
// Input filename
baton->fileIn = *Utf8String(Get(options, New("fileIn").ToLocalChecked()).ToLocalChecked());
// Input Buffer object
Local<Object> bufferIn;
if (node::Buffer::HasInstance(Get(options, New("bufferIn").ToLocalChecked()).ToLocalChecked())) {
Local<Object> buffer = Get(options, New("bufferIn").ToLocalChecked()).ToLocalChecked().As<Object>();
// Take a copy of the input Buffer to avoid problems with V8 heap compaction
baton->bufferInLength = node::Buffer::Length(buffer);
baton->bufferIn = new char[baton->bufferInLength];
memcpy(baton->bufferIn, node::Buffer::Data(buffer), baton->bufferInLength);
bufferIn = Get(options, New("bufferIn").ToLocalChecked()).ToLocalChecked().As<Object>();
baton->bufferInLength = node::Buffer::Length(bufferIn);
baton->bufferIn = node::Buffer::Data(bufferIn);
}
// Join queue for worker thread
Callback *callback = new Callback(info[1].As<Function>());
AsyncQueueWorker(new MetadataWorker(callback, baton));
AsyncQueueWorker(new MetadataWorker(callback, baton, bufferIn));
// Increment queued task counter
g_atomic_int_inc(&counterQueue);

143
src/operations.cc Executable file → Normal file
View File

@@ -14,11 +14,11 @@ namespace sharp {
// Split src into non-alpha and alpha
VipsImage *srcWithoutAlpha;
if (vips_extract_band(src, &srcWithoutAlpha, 0, "n", src->Bands - 1, NULL))
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, NULL))
if (vips_extract_band(src, &srcAlpha, src->Bands - 1, "n", 1, nullptr))
return -1;
vips_object_local(context, srcAlpha);
@@ -27,12 +27,12 @@ namespace sharp {
VipsImage *dstAlpha;
if (HasAlpha(dst)) {
// Non-alpha: extract all-but-last channel
if (vips_extract_band(dst, &dstWithoutAlpha, 0, "n", dst->Bands - 1, NULL)) {
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, NULL)) {
if (vips_extract_band(dst, &dstAlpha, dst->Bands - 1, "n", 1, nullptr)) {
return -1;
}
vips_object_local(context, dstAlpha);
@@ -41,11 +41,11 @@ namespace sharp {
dstWithoutAlpha = dst;
// Alpha: Use blank, opaque (0xFF) image
VipsImage *black;
if (vips_black(&black, dst->Xsize, dst->Ysize, NULL)) {
if (vips_black(&black, dst->Xsize, dst->Ysize, nullptr)) {
return -1;
}
vips_object_local(context, black);
if (vips_invert(black, &dstAlpha, NULL)) {
if (vips_invert(black, &dstAlpha, nullptr)) {
return -1;
}
vips_object_local(context, dstAlpha);
@@ -53,12 +53,12 @@ namespace sharp {
// Compute normalized input alpha channels:
VipsImage *srcAlphaNormalized;
if (vips_linear1(srcAlpha, &srcAlphaNormalized, 1.0 / 255.0, 0.0, NULL))
if (vips_linear1(srcAlpha, &srcAlphaNormalized, 1.0 / 255.0, 0.0, nullptr))
return -1;
vips_object_local(context, srcAlphaNormalized);
VipsImage *dstAlphaNormalized;
if (vips_linear1(dstAlpha, &dstAlphaNormalized, 1.0 / 255.0, 0.0, NULL))
if (vips_linear1(dstAlpha, &dstAlphaNormalized, 1.0 / 255.0, 0.0, nullptr))
return -1;
vips_object_local(context, dstAlphaNormalized);
@@ -75,17 +75,17 @@ namespace sharp {
// ^^^^^^^^^^^^^^^^^^^
// t1
VipsImage *t0;
if (vips_linear1(srcAlphaNormalized, &t0, -1.0, 1.0, NULL))
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, NULL))
if (vips_multiply(dstAlphaNormalized, t0, &t1, nullptr))
return -1;
vips_object_local(context, t1);
VipsImage *outAlphaNormalized;
if (vips_add(srcAlphaNormalized, t1, &outAlphaNormalized, NULL))
if (vips_add(srcAlphaNormalized, t1, &outAlphaNormalized, nullptr))
return -1;
vips_object_local(context, outAlphaNormalized);
@@ -102,92 +102,29 @@ namespace sharp {
// externally.
//
VipsImage *t2;
if (vips_multiply(dstWithoutAlpha, t0, &t2, NULL))
if (vips_multiply(dstWithoutAlpha, t0, &t2, nullptr))
return -1;
vips_object_local(context, t2);
VipsImage *outRGBPremultiplied;
if (vips_add(srcWithoutAlpha, t2, &outRGBPremultiplied, NULL))
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, NULL))
if (vips_linear1(outAlphaNormalized, &outAlpha, 255.0, 0.0, nullptr))
return -1;
vips_object_local(context, outAlpha);
// Combine RGB and alpha channel into output image:
return vips_bandjoin2(outRGBPremultiplied, outAlpha, out, NULL);
}
/*
* Premultiply alpha channel of `image`.
*/
int Premultiply(VipsObject *context, VipsImage *image, VipsImage **out) {
#if (VIPS_MAJOR_VERSION >= 9 || (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION >= 1))
return vips_premultiply(image, out, NULL);
#else
VipsImage *imageRGB;
if (vips_extract_band(image, &imageRGB, 0, "n", image->Bands - 1, NULL))
return -1;
vips_object_local(context, imageRGB);
VipsImage *imageAlpha;
if (vips_extract_band(image, &imageAlpha, image->Bands - 1, "n", 1, NULL))
return -1;
vips_object_local(context, imageAlpha);
VipsImage *imageAlphaNormalized;
if (vips_linear1(imageAlpha, &imageAlphaNormalized, 1.0 / 255.0, 0.0, NULL))
return -1;
vips_object_local(context, imageAlphaNormalized);
VipsImage *imageRGBPremultiplied;
if (vips_multiply(imageRGB, imageAlphaNormalized, &imageRGBPremultiplied, NULL))
return -1;
vips_object_local(context, imageRGBPremultiplied);
return vips_bandjoin2(imageRGBPremultiplied, imageAlpha, out, NULL);
#endif
}
/*
* Unpremultiply alpha channel of `image`.
*/
int Unpremultiply(VipsObject *context, VipsImage *image, VipsImage **out) {
#if (VIPS_MAJOR_VERSION >= 9 || (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION >= 1))
return vips_unpremultiply(image, out, NULL);
#else
VipsImage *imageRGBPremultipliedTransformed;
if (vips_extract_band(image, &imageRGBPremultipliedTransformed, 0, "n", image->Bands - 1, NULL))
return -1;
vips_object_local(context, imageRGBPremultipliedTransformed);
VipsImage *imageAlphaTransformed;
if (vips_extract_band(image, &imageAlphaTransformed, image->Bands - 1, "n", 1, NULL))
return -1;
vips_object_local(context, imageAlphaTransformed);
VipsImage *imageAlphaNormalizedTransformed;
if (vips_linear1(imageAlphaTransformed, &imageAlphaNormalizedTransformed, 1.0 / 255.0, 0.0, NULL))
return -1;
vips_object_local(context, imageAlphaNormalizedTransformed);
VipsImage *imageRGBUnpremultipliedTransformed;
if (vips_divide(imageRGBPremultipliedTransformed, imageAlphaNormalizedTransformed, &imageRGBUnpremultipliedTransformed, NULL))
return -1;
vips_object_local(context, imageRGBUnpremultipliedTransformed);
return vips_bandjoin2(imageRGBUnpremultipliedTransformed, imageAlphaTransformed, out, NULL);
#endif
return vips_bandjoin2(outRGBPremultiplied, outAlpha, out, nullptr);
}
/*
* Stretch luminance to cover full dynamic range.
*/
int Normalize(VipsObject *context, VipsImage *image, VipsImage **out) {
#ifndef _WIN32
// Get original colourspace
VipsInterpretation typeBeforeNormalize = image->Type;
if (typeBeforeNormalize == VIPS_INTERPRETATION_RGB) {
@@ -195,25 +132,25 @@ namespace sharp {
}
// Convert to LAB colourspace
VipsImage *lab;
if (vips_colourspace(image, &lab, VIPS_INTERPRETATION_LAB, NULL)) {
if (vips_colourspace(image, &lab, VIPS_INTERPRETATION_LAB, nullptr)) {
return -1;
}
vips_object_local(context, lab);
// Extract luminance
VipsImage *luminance;
if (vips_extract_band(lab, &luminance, 0, "n", 1, NULL)) {
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, NULL)) {
if (vips_extract_band(lab, &chroma, 1, "n", 2, nullptr)) {
return -1;
}
vips_object_local(context, chroma);
// Find luminance range
VipsImage *stats;
if (vips_stats(luminance, &stats, NULL)) {
if (vips_stats(luminance, &stats, nullptr)) {
return -1;
}
vips_object_local(context, stats);
@@ -224,19 +161,19 @@ namespace sharp {
double a = -(min * f);
// Scale luminance
VipsImage *luminance100;
if (vips_linear1(luminance, &luminance100, f, a, NULL)) {
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, NULL)) {
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, NULL)) {
if (vips_colourspace(normalizedLab, &normalized, typeBeforeNormalize, nullptr)) {
return -1;
}
vips_object_local(context, normalized);
@@ -244,13 +181,13 @@ namespace sharp {
if (HasAlpha(image)) {
// Extract original alpha channel
VipsImage *alpha;
if (vips_extract_band(image, &alpha, image->Bands - 1, "n", 1, NULL)) {
if (vips_extract_band(image, &alpha, image->Bands - 1, "n", 1, nullptr)) {
return -1;
}
vips_object_local(context, alpha);
// Join alpha channel to normalised image
VipsImage *normalizedAlpha;
if (vips_bandjoin2(normalized, alpha, &normalizedAlpha, NULL)) {
if (vips_bandjoin2(normalized, alpha, &normalizedAlpha, nullptr)) {
return -1;
}
vips_object_local(context, normalizedAlpha);
@@ -262,11 +199,6 @@ namespace sharp {
// Cannot normalise zero-range image
*out = image;
}
#else
// The normalize operation is currently unsupported on Windows
// See https://github.com/lovell/sharp/issues/152
*out = image;
#endif
return 0;
}
@@ -283,19 +215,19 @@ namespace sharp {
1.0, 1.0, 1.0);
vips_image_set_double(blur, "scale", 9);
vips_object_local(context, blur);
if (vips_conv(image, &blurred, blur, NULL)) {
if (vips_conv(image, &blurred, blur, nullptr)) {
return -1;
}
} else {
// Slower, accurate Gaussian blur
// Create Gaussian function for standard deviation
VipsImage *gaussian;
if (vips_gaussmat(&gaussian, sigma, 0.2, "separable", TRUE, "integer", TRUE, NULL)) {
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, NULL)) {
if (vips_convsep(image, &blurred, gaussian, "precision", VIPS_PRECISION_INTEGER, nullptr)) {
return -1;
}
}
@@ -317,12 +249,12 @@ namespace sharp {
-1.0, -1.0, -1.0);
vips_image_set_double(sharpen, "scale", 24);
vips_object_local(context, sharpen);
if (vips_conv(image, &sharpened, sharpen, NULL)) {
if (vips_conv(image, &sharpened, sharpen, nullptr)) {
return -1;
}
} 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, NULL)) {
if (vips_sharpen(image, &sharpened, "radius", radius, "m1", flat, "m2", jagged, nullptr)) {
return -1;
}
}
@@ -330,4 +262,21 @@ namespace sharp {
*out = sharpened;
return 0;
}
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;
}
} // namespace sharp

View File

@@ -9,16 +9,6 @@ namespace sharp {
*/
int Composite(VipsObject *context, VipsImage *src, VipsImage *dst, VipsImage **out);
/*
* Premultiply alpha channel of `image`.
*/
int Premultiply(VipsObject *context, VipsImage *image, VipsImage **out);
/*
* Unpremultiply alpha channel of `image`.
*/
int Unpremultiply(VipsObject *context, VipsImage *image, VipsImage **out);
/*
* Stretch luminance to cover full dynamic range.
*/
@@ -34,6 +24,11 @@ namespace sharp {
*/
int Sharpen(VipsObject *context, VipsImage *image, VipsImage **out, int radius, double flat, double 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);
} // namespace sharp
#endif // SRC_OPERATIONS_H_

352
src/pipeline.cc Executable file → Normal file
View File

@@ -32,16 +32,15 @@ using Nan::Get;
using Nan::Set;
using Nan::To;
using Nan::New;
using Nan::CopyBuffer;
using Nan::NewBuffer;
using Nan::Null;
using Nan::Equals;
using sharp::Composite;
using sharp::Premultiply;
using sharp::Unpremultiply;
using sharp::Normalize;
using sharp::Blur;
using sharp::Sharpen;
using sharp::Threshold;
using sharp::ImageType;
using sharp::DetermineImageType;
@@ -57,6 +56,7 @@ using sharp::IsPng;
using sharp::IsWebp;
using sharp::IsTiff;
using sharp::IsDz;
using sharp::FreeCallback;
using sharp::counterProcess;
using sharp::counterQueue;
@@ -101,10 +101,12 @@ struct PipelineBaton {
std::string interpolator;
double background[4];
bool flatten;
bool negate;
double blurSigma;
int sharpenRadius;
double sharpenFlat;
double sharpenJagged;
int threshold;
std::string overlayPath;
double gamma;
bool greyscale;
@@ -139,10 +141,12 @@ struct PipelineBaton {
canvas(Canvas::CROP),
gravity(0),
flatten(false),
negate(false),
blurSigma(0.0),
sharpenRadius(0),
sharpenFlat(1.0),
sharpenJagged(2.0),
threshold(0),
gamma(0.0),
greyscale(false),
normalize(false),
@@ -169,21 +173,15 @@ struct PipelineBaton {
}
};
/*
Delete input char[] buffer and notify V8 of memory deallocation
Used as the callback function for the "postclose" signal
*/
static void DeleteBuffer(VipsObject *object, char *buffer) {
if (buffer != NULL) {
delete[] buffer;
}
}
class PipelineWorker : public AsyncWorker {
public:
PipelineWorker(Callback *callback, PipelineBaton *baton, Callback *queueListener) :
AsyncWorker(callback), baton(baton), queueListener(queueListener) {}
PipelineWorker(Callback *callback, PipelineBaton *baton, Callback *queueListener, const Local<Object> &bufferIn) :
AsyncWorker(callback), baton(baton), queueListener(queueListener) {
if (baton->bufferInLength > 0) {
SaveToPersistent("bufferIn", bufferIn);
}
}
~PipelineWorker() {}
/*
@@ -204,31 +202,26 @@ class PipelineWorker : public AsyncWorker {
// Input
ImageType inputImageType = ImageType::UNKNOWN;
VipsImage *image = NULL;
VipsImage *image = nullptr;
if (baton->bufferInLength > 0) {
// From buffer
inputImageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
if (inputImageType != ImageType::UNKNOWN) {
image = InitImage(baton->bufferIn, baton->bufferInLength, baton->accessMethod);
if (image != NULL) {
// Listen for "postclose" signal to delete input buffer
g_signal_connect(image, "postclose", G_CALLBACK(DeleteBuffer), baton->bufferIn);
} else {
if (image == nullptr) {
// Could not read header data
(baton->err).append("Input buffer has corrupt header");
inputImageType = ImageType::UNKNOWN;
DeleteBuffer(NULL, baton->bufferIn);
}
} else {
(baton->err).append("Input buffer contains unsupported image format");
DeleteBuffer(NULL, baton->bufferIn);
}
} else {
// From file
inputImageType = DetermineImageType(baton->fileIn.c_str());
inputImageType = DetermineImageType(baton->fileIn.data());
if (inputImageType != ImageType::UNKNOWN) {
image = InitImage(baton->fileIn.c_str(), baton->accessMethod);
if (image == NULL) {
image = InitImage(baton->fileIn.data(), baton->accessMethod);
if (image == nullptr) {
(baton->err).append("Input file has corrupt header");
inputImageType = ImageType::UNKNOWN;
}
@@ -236,7 +229,7 @@ class PipelineWorker : public AsyncWorker {
(baton->err).append("Input file is of an unsupported image format");
}
}
if (image == NULL || inputImageType == ImageType::UNKNOWN) {
if (image == nullptr || inputImageType == ImageType::UNKNOWN) {
return Error();
}
vips_object_local(hook, image);
@@ -264,7 +257,7 @@ class PipelineWorker : public AsyncWorker {
// Rotate pre-extract
if (baton->rotateBeforePreExtract && rotation != Angle::D0) {
VipsImage *rotated;
if (vips_rot(image, &rotated, static_cast<VipsAngle>(rotation), NULL)) {
if (vips_rot(image, &rotated, static_cast<VipsAngle>(rotation), nullptr)) {
return Error();
}
vips_object_local(hook, rotated);
@@ -275,7 +268,7 @@ class PipelineWorker : public AsyncWorker {
// Pre extraction
if (baton->topOffsetPre != -1) {
VipsImage *extractedPre;
if (vips_extract_area(image, &extractedPre, baton->leftOffsetPre, baton->topOffsetPre, baton->widthPre, baton->heightPre, NULL)) {
if (vips_extract_area(image, &extractedPre, baton->leftOffsetPre, baton->topOffsetPre, baton->widthPre, baton->heightPre, nullptr)) {
return Error();
}
vips_object_local(hook, extractedPre);
@@ -285,7 +278,7 @@ class PipelineWorker : public AsyncWorker {
// Get pre-resize image width and height
int inputWidth = image->Xsize;
int inputHeight = image->Ysize;
if (rotation == Angle::D90 || rotation == Angle::D270) {
if (!baton->rotateBeforePreExtract && (rotation == Angle::D90 || rotation == Angle::D270)) {
// Swap input output width and height when rotating by 90 or 270 degrees
int swap = inputWidth;
inputWidth = inputHeight;
@@ -293,7 +286,7 @@ class PipelineWorker : public AsyncWorker {
}
// Get window size of interpolator, used for determining shrink vs affine
int interpolatorWindowSize = InterpolatorWindowSize(baton->interpolator.c_str());
int interpolatorWindowSize = InterpolatorWindowSize(baton->interpolator.data());
if (interpolatorWindowSize < 0) {
return Error();
}
@@ -412,11 +405,11 @@ class PipelineWorker : public AsyncWorker {
// Reload input using shrink-on-load
VipsImage *shrunkOnLoad;
if (baton->bufferInLength > 1) {
if (vips_jpegload_buffer(baton->bufferIn, baton->bufferInLength, &shrunkOnLoad, "shrink", shrink_on_load, NULL)) {
if (vips_jpegload_buffer(baton->bufferIn, baton->bufferInLength, &shrunkOnLoad, "shrink", shrink_on_load, nullptr)) {
return Error();
}
} else {
if (vips_jpegload((baton->fileIn).c_str(), &shrunkOnLoad, "shrink", shrink_on_load, NULL)) {
if (vips_jpegload((baton->fileIn).data(), &shrunkOnLoad, "shrink", shrink_on_load, nullptr)) {
return Error();
}
}
@@ -428,7 +421,7 @@ class PipelineWorker : public AsyncWorker {
if (HasProfile(image)) {
// Convert to sRGB using embedded profile
VipsImage *transformed;
if (!vips_icc_transform(image, &transformed, srgbProfile.c_str(), "embedded", TRUE, NULL)) {
if (!vips_icc_transform(image, &transformed, srgbProfile.data(), "embedded", TRUE, nullptr)) {
// Embedded profile can fail, so only update references on success
vips_object_local(hook, transformed);
image = transformed;
@@ -437,24 +430,29 @@ class PipelineWorker : public AsyncWorker {
// Convert to sRGB using default "USWebCoatedSWOP" CMYK profile
std::string cmykProfile = baton->iccProfilePath + "USWebCoatedSWOP.icc";
VipsImage *transformed;
if (vips_icc_transform(image, &transformed, srgbProfile.c_str(), "input_profile", cmykProfile.c_str(), NULL)) {
if (vips_icc_transform(image, &transformed, srgbProfile.data(), "input_profile", cmykProfile.data(), nullptr)) {
return Error();
}
vips_object_local(hook, transformed);
image = transformed;
}
// Calculate maximum alpha value based on input image pixel depth
double maxAlpha = (image->BandFmt == VIPS_FORMAT_USHORT) ? 65535.0 : 255.0;
// Flatten image to remove alpha channel
if (baton->flatten && HasAlpha(image)) {
// Scale up 8-bit values to match 16-bit input image
double multiplier = (image->Type == VIPS_INTERPRETATION_RGB16) ? 256.0 : 1.0;
// Background colour
VipsArrayDouble *background = vips_array_double_newv(
3, // Ignore alpha channel as we're about to remove it
baton->background[0],
baton->background[1],
baton->background[2]
baton->background[0] * multiplier,
baton->background[1] * multiplier,
baton->background[2] * multiplier
);
VipsImage *flattened;
if (vips_flatten(image, &flattened, "background", background, NULL)) {
if (vips_flatten(image, &flattened, "background", background, "max_alpha", maxAlpha, nullptr)) {
vips_area_unref(reinterpret_cast<VipsArea*>(background));
return Error();
}
@@ -463,10 +461,20 @@ class PipelineWorker : public AsyncWorker {
image = flattened;
}
// Negate the colors in the image.
if (baton->negate) {
VipsImage *negated;
if (vips_invert(image, &negated, nullptr)) {
return Error();
}
vips_object_local(hook, negated);
image = negated;
}
// Gamma encoding (darken)
if (baton->gamma >= 1 && baton->gamma <= 3 && !HasAlpha(image)) {
VipsImage *gammaEncoded;
if (vips_gamma(image, &gammaEncoded, "exponent", 1.0 / baton->gamma, NULL)) {
if (vips_gamma(image, &gammaEncoded, "exponent", 1.0 / baton->gamma, nullptr)) {
return Error();
}
vips_object_local(hook, gammaEncoded);
@@ -476,7 +484,7 @@ class PipelineWorker : public AsyncWorker {
// Convert to greyscale (linear, therefore after gamma encoding, if any)
if (baton->greyscale) {
VipsImage *greyscale;
if (vips_colourspace(image, &greyscale, VIPS_INTERPRETATION_B_W, NULL)) {
if (vips_colourspace(image, &greyscale, VIPS_INTERPRETATION_B_W, nullptr)) {
return Error();
}
vips_object_local(hook, greyscale);
@@ -486,7 +494,7 @@ class PipelineWorker : public AsyncWorker {
if (xshrink > 1 || yshrink > 1) {
VipsImage *shrunk;
// Use vips_shrink with the integral reduction
if (vips_shrink(image, &shrunk, xshrink, yshrink, NULL)) {
if (vips_shrink(image, &shrunk, xshrink, yshrink, nullptr)) {
return Error();
}
vips_object_local(hook, shrunk);
@@ -514,6 +522,7 @@ class PipelineWorker : public AsyncWorker {
bool shouldAffineTransform = xresidual != 0.0 || yresidual != 0.0;
bool shouldBlur = baton->blurSigma != 0.0;
bool shouldSharpen = baton->sharpenRadius != 0;
bool shouldThreshold = baton->threshold != 0;
bool hasOverlay = !baton->overlayPath.empty();
bool shouldPremultiplyAlpha = HasAlpha(image) && (shouldAffineTransform || shouldBlur || shouldSharpen || hasOverlay);
@@ -522,7 +531,7 @@ class PipelineWorker : public AsyncWorker {
// See: http://entropymine.com/imageworsener/resizealpha/
if (shouldPremultiplyAlpha) {
VipsImage *imagePremultiplied;
if (Premultiply(hook, image, &imagePremultiplied)) {
if (vips_premultiply(image, &imagePremultiplied, "max_alpha", maxAlpha, nullptr)) {
(baton->err).append("Failed to premultiply alpha channel.");
return Error();
}
@@ -532,6 +541,12 @@ class PipelineWorker : public AsyncWorker {
// Use vips_affine with the remaining float part
if (shouldAffineTransform) {
// Create interpolator
VipsInterpolate *interpolator = vips_interpolate_new(baton->interpolator.data());
if (interpolator == nullptr) {
return Error();
}
vips_object_local(hook, interpolator);
// Use average of x and y residuals to compute sigma for Gaussian blur
double residual = (xresidual + yresidual) / 2.0;
// Apply Gaussian blur before large affine reductions
@@ -539,39 +554,31 @@ class PipelineWorker : public AsyncWorker {
// Calculate standard deviation
double sigma = ((1.0 / residual) - 0.4) / 3.0;
if (sigma >= 0.3) {
// Create Gaussian function for standard deviation
VipsImage *gaussian;
if (vips_gaussmat(&gaussian, sigma, 0.2, "separable", TRUE, "integer", TRUE, NULL)) {
return Error();
}
vips_object_local(hook, gaussian);
// Sequential input requires a small linecache before use of convolution
if (baton->accessMethod == VIPS_ACCESS_SEQUENTIAL) {
VipsImage *lineCached;
if (vips_linecache(image, &lineCached, "access", VIPS_ACCESS_SEQUENTIAL, "tile_height", 1, "threaded", TRUE, NULL)) {
if (vips_linecache(image, &lineCached, "access", VIPS_ACCESS_SEQUENTIAL,
"tile_height", 1, "threaded", TRUE, nullptr)
) {
return Error();
}
vips_object_local(hook, lineCached);
image = lineCached;
}
// Apply Gaussian function
// Apply Gaussian blur
VipsImage *blurred;
if (vips_convsep(image, &blurred, gaussian, "precision", VIPS_PRECISION_INTEGER, NULL)) {
if (vips_gaussblur(image, &blurred, sigma, nullptr)) {
return Error();
}
vips_object_local(hook, blurred);
image = blurred;
}
}
// Create interpolator - "bilinear" (default), "bicubic" or "nohalo"
VipsInterpolate *interpolator = vips_interpolate_new(baton->interpolator.c_str());
if (interpolator == NULL) {
return Error();
}
vips_object_local(hook, interpolator);
// Perform affine transformation
VipsImage *affined;
if (vips_affine(image, &affined, xresidual, 0.0, 0.0, yresidual, "interpolate", interpolator, NULL)) {
if (vips_affine(image, &affined, xresidual, 0.0, 0.0, yresidual,
"interpolate", interpolator, nullptr)
) {
return Error();
}
vips_object_local(hook, affined);
@@ -581,7 +588,7 @@ class PipelineWorker : public AsyncWorker {
// Rotate
if (!baton->rotateBeforePreExtract && rotation != Angle::D0) {
VipsImage *rotated;
if (vips_rot(image, &rotated, static_cast<VipsAngle>(rotation), NULL)) {
if (vips_rot(image, &rotated, static_cast<VipsAngle>(rotation), nullptr)) {
return Error();
}
vips_object_local(hook, rotated);
@@ -592,7 +599,7 @@ class PipelineWorker : public AsyncWorker {
// Flip (mirror about Y axis)
if (baton->flip) {
VipsImage *flipped;
if (vips_flip(image, &flipped, VIPS_DIRECTION_VERTICAL, NULL)) {
if (vips_flip(image, &flipped, VIPS_DIRECTION_VERTICAL, nullptr)) {
return Error();
}
vips_object_local(hook, flipped);
@@ -603,7 +610,7 @@ class PipelineWorker : public AsyncWorker {
// Flop (mirror about X axis)
if (baton->flop) {
VipsImage *flopped;
if (vips_flip(image, &flopped, VIPS_DIRECTION_HORIZONTAL, NULL)) {
if (vips_flip(image, &flopped, VIPS_DIRECTION_HORIZONTAL, nullptr)) {
return Error();
}
vips_object_local(hook, flopped);
@@ -618,7 +625,7 @@ class PipelineWorker : public AsyncWorker {
if (image->Type != VIPS_INTERPRETATION_sRGB) {
// Convert to sRGB colour space
VipsImage *colourspaced;
if (vips_colourspace(image, &colourspaced, VIPS_INTERPRETATION_sRGB, NULL)) {
if (vips_colourspace(image, &colourspaced, VIPS_INTERPRETATION_sRGB, nullptr)) {
return Error();
}
vips_object_local(hook, colourspaced);
@@ -628,19 +635,19 @@ class PipelineWorker : public AsyncWorker {
if (baton->background[3] < 255.0 && !HasAlpha(image)) {
// Create single-channel transparency
VipsImage *black;
if (vips_black(&black, image->Xsize, image->Ysize, "bands", 1, NULL)) {
if (vips_black(&black, image->Xsize, image->Ysize, "bands", 1, nullptr)) {
return Error();
}
vips_object_local(hook, black);
// Invert to become non-transparent
VipsImage *alpha;
if (vips_invert(black, &alpha, NULL)) {
if (vips_invert(black, &alpha, nullptr)) {
return Error();
}
vips_object_local(hook, alpha);
// Append alpha channel to existing image
VipsImage *joined;
if (vips_bandjoin2(image, alpha, &joined, NULL)) {
if (vips_bandjoin2(image, alpha, &joined, nullptr)) {
return Error();
}
vips_object_local(hook, joined);
@@ -662,7 +669,7 @@ class PipelineWorker : public AsyncWorker {
int top = (baton->height - image->Ysize) / 2;
VipsImage *embedded;
if (vips_embed(image, &embedded, left, top, baton->width, baton->height,
"extend", VIPS_EXTEND_BACKGROUND, "background", background, NULL
"extend", VIPS_EXTEND_BACKGROUND, "background", background, nullptr
)) {
vips_area_unref(reinterpret_cast<VipsArea*>(background));
return Error();
@@ -678,7 +685,7 @@ class PipelineWorker : public AsyncWorker {
int width = std::min(image->Xsize, baton->width);
int height = std::min(image->Ysize, baton->height);
VipsImage *extracted;
if (vips_extract_area(image, &extracted, left, top, width, height, NULL)) {
if (vips_extract_area(image, &extracted, left, top, width, height, nullptr)) {
return Error();
}
vips_object_local(hook, extracted);
@@ -690,7 +697,7 @@ class PipelineWorker : public AsyncWorker {
if (baton->topOffsetPost != -1) {
VipsImage *extractedPost;
if (vips_extract_area(image, &extractedPost,
baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost, NULL
baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost, nullptr
)) {
return Error();
}
@@ -698,6 +705,15 @@ class PipelineWorker : public AsyncWorker {
image = extractedPost;
}
// Threshold - must happen before blurring, due to the utility of blurring after thresholding
if (shouldThreshold) {
VipsImage *thresholded;
if (Threshold(hook, image, &thresholded, baton->threshold)) {
return Error();
}
image = thresholded;
}
// Blur
if (shouldBlur) {
VipsImage *blurred;
@@ -718,12 +734,12 @@ class PipelineWorker : public AsyncWorker {
// Composite with overlay, if present
if (hasOverlay) {
VipsImage *overlayImage = NULL;
VipsImage *overlayImage = nullptr;
ImageType overlayImageType = ImageType::UNKNOWN;
overlayImageType = DetermineImageType(baton->overlayPath.c_str());
overlayImageType = DetermineImageType(baton->overlayPath.data());
if (overlayImageType != ImageType::UNKNOWN) {
overlayImage = InitImage(baton->overlayPath.c_str(), baton->accessMethod);
if (overlayImage == NULL) {
overlayImage = InitImage(baton->overlayPath.data(), baton->accessMethod);
if (overlayImage == nullptr) {
(baton->err).append("Overlay image has corrupt header");
return Error();
} else {
@@ -754,15 +770,15 @@ class PipelineWorker : public AsyncWorker {
// Ensure overlay is sRGB
VipsImage *overlayImageRGB;
if (vips_colourspace(overlayImage, &overlayImageRGB, VIPS_INTERPRETATION_sRGB, NULL)) {
if (vips_colourspace(overlayImage, &overlayImageRGB, VIPS_INTERPRETATION_sRGB, nullptr)) {
return Error();
}
vips_object_local(hook, overlayImageRGB);
// Premultiply overlay
VipsImage *overlayImagePremultiplied;
if (Premultiply(hook, overlayImageRGB, &overlayImagePremultiplied)) {
(baton->err).append("Failed to premultiply alpha channel of overlay image.");
if (vips_premultiply(overlayImageRGB, &overlayImagePremultiplied, nullptr)) {
(baton->err).append("Failed to premultiply alpha channel of overlay image");
return Error();
}
vips_object_local(hook, overlayImagePremultiplied);
@@ -779,11 +795,10 @@ class PipelineWorker : public AsyncWorker {
// Reverse premultiplication after all transformations:
if (shouldPremultiplyAlpha) {
VipsImage *imageUnpremultiplied;
if (Unpremultiply(hook, image, &imageUnpremultiplied)) {
(baton->err).append("Failed to unpremultiply alpha channel.");
if (vips_unpremultiply(image, &imageUnpremultiplied, "max_alpha", maxAlpha, nullptr)) {
(baton->err).append("Failed to unpremultiply alpha channel");
return Error();
}
vips_object_local(hook, imageUnpremultiplied);
image = imageUnpremultiplied;
}
@@ -791,7 +806,7 @@ class PipelineWorker : public AsyncWorker {
// Gamma decoding (brighten)
if (baton->gamma >= 1 && baton->gamma <= 3 && !HasAlpha(image)) {
VipsImage *gammaDecoded;
if (vips_gamma(image, &gammaDecoded, "exponent", baton->gamma, NULL)) {
if (vips_gamma(image, &gammaDecoded, "exponent", baton->gamma, nullptr)) {
return Error();
}
vips_object_local(hook, gammaDecoded);
@@ -808,10 +823,27 @@ class PipelineWorker : public AsyncWorker {
}
// Convert image to sRGB, if not already
if (image->Type != VIPS_INTERPRETATION_sRGB) {
if (image->Type == VIPS_INTERPRETATION_RGB16) {
// Ensure 16-bit integer
VipsImage *ushort;
if (vips_cast_ushort(image, &ushort, nullptr)) {
return Error();
}
vips_object_local(hook, ushort);
image = ushort;
// Fast conversion to 8-bit integer by discarding least-significant byte
VipsImage *msb;
if (vips_msb(image, &msb, nullptr)) {
return Error();
}
vips_object_local(hook, msb);
image = msb;
// Explicitly set to sRGB
image->Type = VIPS_INTERPRETATION_sRGB;
} else if (image->Type != VIPS_INTERPRETATION_sRGB) {
// Switch interpretation to sRGB
VipsImage *rgb;
if (vips_colourspace(image, &rgb, VIPS_INTERPRETATION_sRGB, NULL)) {
if (vips_colourspace(image, &rgb, VIPS_INTERPRETATION_sRGB, nullptr)) {
return Error();
}
vips_object_local(hook, rgb);
@@ -819,7 +851,7 @@ class PipelineWorker : public AsyncWorker {
// Transform colours from embedded profile to sRGB profile
if (baton->withMetadata && HasProfile(image)) {
VipsImage *profiled;
if (vips_icc_transform(image, &profiled, srgbProfile.c_str(), "embedded", TRUE, NULL)) {
if (vips_icc_transform(image, &profiled, srgbProfile.data(), "embedded", TRUE, nullptr)) {
return Error();
}
vips_object_local(hook, profiled);
@@ -832,63 +864,54 @@ class PipelineWorker : public AsyncWorker {
SetExifOrientation(image, baton->withMetadataOrientation);
}
#if !(VIPS_MAJOR_VERSION >= 8 || (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 40 && VIPS_MINOR_VERSION >= 5))
// Generate image tile cache when interlace output is required - no longer required as of libvips 7.40.5+
if (baton->progressive) {
VipsImage *cached;
if (vips_tilecache(image, &cached, "threaded", TRUE, "persistent", TRUE, "max_tiles", -1, NULL)) {
return Error();
}
vips_object_local(hook, cached);
image = cached;
}
#endif
// Output
if (baton->output == "__jpeg" || (baton->output == "__input" && inputImageType == ImageType::JPEG)) {
// Write JPEG to buffer
if (vips_jpegsave_buffer(image, &baton->bufferOut, &baton->bufferOutLength, "strip", !baton->withMetadata,
"Q", baton->quality, "optimize_coding", TRUE, "no_subsample", baton->withoutChromaSubsampling,
#if (VIPS_MAJOR_VERSION >= 8)
if (vips_jpegsave_buffer(
image, &baton->bufferOut, &baton->bufferOutLength,
"strip", !baton->withMetadata,
"Q", baton->quality,
"optimize_coding", TRUE,
"no_subsample", baton->withoutChromaSubsampling,
"trellis_quant", baton->trellisQuantisation,
"overshoot_deringing", baton->overshootDeringing,
"optimize_scans", baton->optimiseScans,
#endif
"interlace", baton->progressive, NULL)) {
"interlace", baton->progressive,
nullptr
)) {
return Error();
}
baton->outputFormat = "jpeg";
} else if (baton->output == "__png" || (baton->output == "__input" && inputImageType == ImageType::PNG)) {
#if (VIPS_MAJOR_VERSION >= 8 || (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 42))
// Select PNG row filter
int filter = baton->withoutAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_NONE : VIPS_FOREIGN_PNG_FILTER_ALL;
// Write PNG to buffer
if (vips_pngsave_buffer(image, &baton->bufferOut, &baton->bufferOutLength, "strip", !baton->withMetadata,
"compression", baton->compressionLevel, "interlace", baton->progressive, "filter", filter, NULL)) {
if (vips_pngsave_buffer(
image, &baton->bufferOut, &baton->bufferOutLength,
"strip", !baton->withMetadata,
"compression", baton->compressionLevel,
"interlace", baton->progressive,
"filter", baton->withoutAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_NONE : VIPS_FOREIGN_PNG_FILTER_ALL,
nullptr
)) {
return Error();
}
#else
// Write PNG to buffer
if (vips_pngsave_buffer(image, &baton->bufferOut, &baton->bufferOutLength, "strip", !baton->withMetadata,
"compression", baton->compressionLevel, "interlace", baton->progressive, NULL)) {
return Error();
}
#endif
baton->outputFormat = "png";
} else if (baton->output == "__webp" || (baton->output == "__input" && inputImageType == ImageType::WEBP)) {
// Write WEBP to buffer
if (vips_webpsave_buffer(image, &baton->bufferOut, &baton->bufferOutLength, "strip", !baton->withMetadata,
"Q", baton->quality, NULL)) {
if (vips_webpsave_buffer(
image, &baton->bufferOut, &baton->bufferOutLength,
"strip", !baton->withMetadata,
"Q", baton->quality,
nullptr
)) {
return Error();
}
baton->outputFormat = "webp";
#if (VIPS_MAJOR_VERSION >= 8 || (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 42))
} else if (baton->output == "__raw") {
// Write raw, uncompressed image data to buffer
if (baton->greyscale || image->Type == VIPS_INTERPRETATION_B_W) {
// Extract first band for greyscale image
VipsImage *grey;
if (vips_extract_band(image, &grey, 0, NULL)) {
if (vips_extract_band(image, &grey, 0, nullptr)) {
return Error();
}
vips_object_local(hook, grey);
@@ -897,7 +920,7 @@ class PipelineWorker : public AsyncWorker {
if (image->BandFmt != VIPS_FORMAT_UCHAR) {
// Cast pixels to uint8 (unsigned char)
VipsImage *uchar;
if (vips_cast(image, &uchar, VIPS_FORMAT_UCHAR, NULL)) {
if (vips_cast(image, &uchar, VIPS_FORMAT_UCHAR, nullptr)) {
return Error();
}
vips_object_local(hook, uchar);
@@ -905,12 +928,11 @@ class PipelineWorker : public AsyncWorker {
}
// Get raw image data
baton->bufferOut = vips_image_write_to_memory(image, &baton->bufferOutLength);
if (baton->bufferOut == NULL) {
if (baton->bufferOut == nullptr) {
(baton->err).append("Could not allocate enough memory for raw output");
return Error();
}
baton->outputFormat = "raw";
#endif
} else {
bool outputJpeg = IsJpeg(baton->output);
bool outputPng = IsPng(baton->output);
@@ -920,52 +942,66 @@ class PipelineWorker : public AsyncWorker {
bool matchInput = !(outputJpeg || outputPng || outputWebp || outputTiff || outputDz);
if (outputJpeg || (matchInput && inputImageType == ImageType::JPEG)) {
// Write JPEG to file
if (vips_jpegsave(image, baton->output.c_str(), "strip", !baton->withMetadata,
"Q", baton->quality, "optimize_coding", TRUE, "no_subsample", baton->withoutChromaSubsampling,
#if (VIPS_MAJOR_VERSION >= 8)
if (vips_jpegsave(
image, baton->output.data(),
"strip", !baton->withMetadata,
"Q", baton->quality,
"optimize_coding", TRUE,
"no_subsample", baton->withoutChromaSubsampling,
"trellis_quant", baton->trellisQuantisation,
"overshoot_deringing", baton->overshootDeringing,
"optimize_scans", baton->optimiseScans,
#endif
"interlace", baton->progressive, NULL)) {
"interlace", baton->progressive,
nullptr
)) {
return Error();
}
baton->outputFormat = "jpeg";
} else if (outputPng || (matchInput && inputImageType == ImageType::PNG)) {
#if (VIPS_MAJOR_VERSION >= 8 || (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 42))
// Select PNG row filter
int filter = baton->withoutAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_NONE : VIPS_FOREIGN_PNG_FILTER_ALL;
// Write PNG to file
if (vips_pngsave(image, baton->output.c_str(), "strip", !baton->withMetadata,
"compression", baton->compressionLevel, "interlace", baton->progressive, "filter", filter, NULL)) {
if (vips_pngsave(
image, baton->output.data(),
"strip", !baton->withMetadata,
"compression", baton->compressionLevel,
"interlace", baton->progressive,
"filter", baton->withoutAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_NONE : VIPS_FOREIGN_PNG_FILTER_ALL,
nullptr
)) {
return Error();
}
#else
// Write PNG to file
if (vips_pngsave(image, baton->output.c_str(), "strip", !baton->withMetadata,
"compression", baton->compressionLevel, "interlace", baton->progressive, NULL)) {
return Error();
}
#endif
baton->outputFormat = "png";
} else if (outputWebp || (matchInput && inputImageType == ImageType::WEBP)) {
// Write WEBP to file
if (vips_webpsave(image, baton->output.c_str(), "strip", !baton->withMetadata,
"Q", baton->quality, NULL)) {
if (vips_webpsave(
image, baton->output.data(),
"strip", !baton->withMetadata,
"Q", baton->quality,
nullptr
)) {
return Error();
}
baton->outputFormat = "webp";
} else if (outputTiff || (matchInput && inputImageType == ImageType::TIFF)) {
// Write TIFF to file
if (vips_tiffsave(image, baton->output.c_str(), "strip", !baton->withMetadata,
"compression", VIPS_FOREIGN_TIFF_COMPRESSION_JPEG, "Q", baton->quality, NULL)) {
if (vips_tiffsave(
image, baton->output.data(),
"strip", !baton->withMetadata,
"compression", VIPS_FOREIGN_TIFF_COMPRESSION_JPEG,
"Q", baton->quality,
nullptr
)) {
return Error();
}
baton->outputFormat = "tiff";
} else if (outputDz) {
// Write DZ to file
if (vips_dzsave(image, baton->output.c_str(), "strip", !baton->withMetadata,
"tile_size", baton->tileSize, "overlap", baton->tileOverlap, NULL)) {
if (vips_dzsave(
image, baton->output.data(),
"strip", !baton->withMetadata,
"tile_size", baton->tileSize,
"overlap", baton->tileOverlap,
nullptr
)) {
return Error();
}
baton->outputFormat = "dz";
@@ -984,10 +1020,10 @@ class PipelineWorker : public AsyncWorker {
void HandleOKCallback () {
HandleScope();
Local<Value> argv[3] = { Null(), Null(), Null() };
Local<Value> argv[3] = { Null(), Null(), Null() };
if (!baton->err.empty()) {
// Error
argv[0] = Nan::Error(baton->err.c_str());
argv[0] = Nan::Error(baton->err.data());
} else {
int width = baton->width;
int height = baton->height;
@@ -1006,23 +1042,27 @@ class PipelineWorker : public AsyncWorker {
Set(info, New("height").ToLocalChecked(), New<Uint32>(static_cast<uint32_t>(height)));
if (baton->bufferOutLength > 0) {
// Copy data to new Buffer
argv[1] = CopyBuffer(static_cast<char*>(baton->bufferOut), baton->bufferOutLength).ToLocalChecked();
// bufferOut was allocated via g_malloc
g_free(baton->bufferOut);
// Pass ownership of output data to Buffer instance
argv[1] = NewBuffer(
static_cast<char*>(baton->bufferOut), baton->bufferOutLength, FreeCallback, nullptr
).ToLocalChecked();
// Add buffer size to info
Set(info, New("size").ToLocalChecked(), New<Uint32>(static_cast<uint32_t>(baton->bufferOutLength)));
argv[2] = info;
} else {
// Add file size to info
GStatBuf st;
g_stat(baton->output.c_str(), &st);
g_stat(baton->output.data(), &st);
Set(info, New("size").ToLocalChecked(), New<Uint32>(static_cast<uint32_t>(st.st_size)));
argv[1] = info;
}
}
// Dispose of Persistent wrapper around input Buffer so it can be garbage collected
if (baton->bufferInLength > 0) {
GetFromPersistent("bufferIn");
}
delete baton;
// to here
// Decrement processing task counter
g_atomic_int_dec_and_test(&counterProcess);
@@ -1169,12 +1209,11 @@ NAN_METHOD(pipeline) {
To<bool>(Get(options, New("sequentialRead").ToLocalChecked()).ToLocalChecked()).FromJust() ?
VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
// Input Buffer object
Local<Object> bufferIn;
if (node::Buffer::HasInstance(Get(options, New("bufferIn").ToLocalChecked()).ToLocalChecked())) {
Local<Object> buffer = Get(options, New("bufferIn").ToLocalChecked()).ToLocalChecked().As<Object>();
// Take a copy of the input Buffer to avoid problems with V8 heap compaction
baton->bufferInLength = node::Buffer::Length(buffer);
baton->bufferIn = new char[baton->bufferInLength];
memcpy(baton->bufferIn, node::Buffer::Data(buffer), baton->bufferInLength);
bufferIn = Get(options, New("bufferIn").ToLocalChecked()).ToLocalChecked().As<Object>();
baton->bufferInLength = node::Buffer::Length(bufferIn);
baton->bufferIn = node::Buffer::Data(bufferIn);
}
// ICC profile to use when input CMYK image has no embedded profile
baton->iccProfilePath = *Utf8String(Get(options, New("iccProfilePath").ToLocalChecked()).ToLocalChecked());
@@ -1218,10 +1257,12 @@ NAN_METHOD(pipeline) {
baton->interpolator = *Utf8String(Get(options, New("interpolator").ToLocalChecked()).ToLocalChecked());
// Operators
baton->flatten = To<bool>(Get(options, New("flatten").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->negate = To<bool>(Get(options, New("negate").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->blurSigma = To<double>(Get(options, New("blurSigma").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->sharpenRadius = To<int32_t>(Get(options, New("sharpenRadius").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->sharpenFlat = To<double>(Get(options, New("sharpenFlat").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->sharpenJagged = To<double>(Get(options, New("sharpenJagged").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->threshold = To<int32_t>(Get(options, New("threshold").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->gamma = To<int32_t>(Get(options, New("gamma").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->greyscale = To<bool>(Get(options, New("greyscale").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->normalize = To<bool>(Get(options, New("normalize").ToLocalChecked()).ToLocalChecked()).FromJust();
@@ -1249,11 +1290,10 @@ NAN_METHOD(pipeline) {
// Join queue for worker thread
Callback *callback = new Callback(info[1].As<Function>());
AsyncQueueWorker(new PipelineWorker(callback, baton, queueListener));
AsyncQueueWorker(new PipelineWorker(callback, baton, queueListener, bufferIn));
// Increment queued task counter
g_atomic_int_inc(&counterQueue);
Local<Value> queueLength[1] = { New<Uint32>(counterQueue) };
queueListener->Call(1, queueLength);
}

51
src/utilities.cc Executable file → Normal file
View File

@@ -1,3 +1,4 @@
#include <cmath>
#include <node.h>
#include <vips/vips.h>
@@ -7,11 +8,12 @@
#include "operations.h"
#include "utilities.h"
using v8::Local;
using v8::Object;
using v8::Number;
using v8::String;
using v8::Boolean;
using v8::Integer;
using v8::Local;
using v8::Number;
using v8::Object;
using v8::String;
using Nan::HandleScope;
using Nan::New;
@@ -38,10 +40,16 @@ NAN_METHOD(cache) {
// Get cache statistics
Local<Object> cache = New<Object>();
Set(cache, New("current").ToLocalChecked(), New<Number>(vips_tracked_get_mem() / 1048576));
Set(cache, New("high").ToLocalChecked(), New<Number>(vips_tracked_get_mem_highwater() / 1048576));
Set(cache, New("memory").ToLocalChecked(), New<Number>(vips_cache_get_max_mem() / 1048576));
Set(cache, New("items").ToLocalChecked(), New<Number>(vips_cache_get_max()));
Set(cache, New("current").ToLocalChecked(),
New<Integer>(static_cast<int>(round(vips_tracked_get_mem() / 1048576)))
);
Set(cache, New("high").ToLocalChecked(),
New<Integer>(static_cast<int>(round(vips_tracked_get_mem_highwater() / 1048576)))
);
Set(cache, New("memory").ToLocalChecked(),
New<Integer>(static_cast<int>(round(vips_cache_get_max_mem() / 1048576)))
);
Set(cache, New("items").ToLocalChecked(), New<Integer>(vips_cache_get_max()));
info.GetReturnValue().Set(cache);
}
@@ -56,7 +64,7 @@ NAN_METHOD(concurrency) {
vips_concurrency_set(To<int32_t>(info[0]).FromJust());
}
// Get concurrency
info.GetReturnValue().Set(New<Number>(vips_concurrency_get()));
info.GetReturnValue().Set(New<Integer>(vips_concurrency_get()));
}
/*
@@ -68,8 +76,8 @@ NAN_METHOD(counters) {
HandleScope();
Local<Object> counters = New<Object>();
Set(counters, New("queue").ToLocalChecked(), New<Number>(counterQueue));
Set(counters, New("process").ToLocalChecked(), New<Number>(counterProcess));
Set(counters, New("queue").ToLocalChecked(), New<Integer>(counterQueue));
Set(counters, New("process").ToLocalChecked(), New<Integer>(counterProcess));
info.GetReturnValue().Set(counters);
}
@@ -157,7 +165,6 @@ NAN_METHOD(format) {
between two images of the same dimensions and number of channels.
*/
NAN_METHOD(_maxColourDistance) {
using sharp::Premultiply;
using sharp::DetermineImageType;
using sharp::ImageType;
using sharp::InitImage;
@@ -169,11 +176,11 @@ NAN_METHOD(_maxColourDistance) {
VipsObject *hook = reinterpret_cast<VipsObject*>(vips_image_new());
// Open input files
VipsImage *image1 = NULL;
VipsImage *image1 = nullptr;
ImageType imageType1 = DetermineImageType(*Utf8String(info[0]));
if (imageType1 != ImageType::UNKNOWN) {
image1 = InitImage(*Utf8String(info[0]), VIPS_ACCESS_SEQUENTIAL);
if (image1 == NULL) {
if (image1 == nullptr) {
g_object_unref(hook);
return ThrowError("Input file 1 has corrupt header");
} else {
@@ -183,11 +190,11 @@ NAN_METHOD(_maxColourDistance) {
g_object_unref(hook);
return ThrowError("Input file 1 is of an unsupported image format");
}
VipsImage *image2 = NULL;
VipsImage *image2 = nullptr;
ImageType imageType2 = DetermineImageType(*Utf8String(info[1]));
if (imageType2 != ImageType::UNKNOWN) {
image2 = InitImage(*Utf8String(info[1]), VIPS_ACCESS_SEQUENTIAL);
if (image2 == NULL) {
if (image2 == nullptr) {
g_object_unref(hook);
return ThrowError("Input file 2 has corrupt header");
} else {
@@ -212,13 +219,13 @@ NAN_METHOD(_maxColourDistance) {
// Premultiply and remove alpha
if (HasAlpha(image1)) {
VipsImage *imagePremultiplied1;
if (Premultiply(hook, image1, &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, NULL)) {
if (vips_extract_band(image1, &imagePremultipliedNoAlpha1, 1, "n", image1->Bands - 1, nullptr)) {
g_object_unref(hook);
return ThrowError(vips_error_buffer());
}
@@ -227,13 +234,13 @@ NAN_METHOD(_maxColourDistance) {
}
if (HasAlpha(image2)) {
VipsImage *imagePremultiplied2;
if (Premultiply(hook, image2, &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, NULL)) {
if (vips_extract_band(image2, &imagePremultipliedNoAlpha2, 1, "n", image2->Bands - 1, nullptr)) {
g_object_unref(hook);
return ThrowError(vips_error_buffer());
}
@@ -242,14 +249,14 @@ NAN_METHOD(_maxColourDistance) {
}
// Calculate colour distance
VipsImage *difference;
if (vips_dE00(image1, image2, &difference, NULL)) {
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, NULL)) {
if (vips_max(difference, &maxColourDistance, nullptr)) {
g_object_unref(hook);
return ThrowError(vips_error_buffer());
}

View File

@@ -8,14 +8,14 @@
"test": "node perf && node random && node parallel"
},
"devDependencies": {
"async": "^1.4.2",
"async": "^1.5.0",
"benchmark": "^1.0.0",
"gm": "^1.20.0",
"gm": "^1.21.0",
"imagemagick": "^0.1.3",
"imagemagick-native": "^1.8.0",
"jimp": "^0.2.8",
"imagemagick-native": "elad/node-imagemagick-native",
"jimp": "^0.2.19",
"lwip": "^0.0.8",
"semver": "^5.0.3"
"semver": "^5.1.0"
},
"license": "Apache-2.0",
"engines": {

4
test/bench/parallel.js Executable file → Normal file
View File

@@ -1,5 +1,7 @@
'use strict';
process.env.UV_THREADPOOL_SIZE = 64;
var assert = require('assert');
var async = require('async');
@@ -15,7 +17,7 @@ var timer = setInterval(function() {
console.dir(sharp.counters());
}, 100);
async.mapSeries([1, 1, 2, 4, 8, 16, 32, 64, 128], function(parallelism, next) {
async.mapSeries([1, 1, 2, 4, 8, 16, 32, 64], function(parallelism, next) {
var start = new Date().getTime();
async.times(parallelism,
function(id, callback) {

979
test/bench/perf.js Executable file → Normal file

File diff suppressed because it is too large Load Diff

0
test/bench/random.js Executable file → Normal file
View File

0
test/fixtures/Landscape_1.jpg vendored Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 136 KiB

0
test/fixtures/Landscape_2.jpg vendored Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 134 KiB

0
test/fixtures/Landscape_3.jpg vendored Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 138 KiB

0
test/fixtures/Landscape_4.jpg vendored Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 137 KiB

0
test/fixtures/Landscape_5.jpg vendored Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 134 KiB

0
test/fixtures/Landscape_6.jpg vendored Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 134 KiB

0
test/fixtures/Landscape_7.jpg vendored Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 137 KiB

0
test/fixtures/Landscape_8.jpg vendored Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 138 KiB

0
test/fixtures/Portrait_1.jpg vendored Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 126 KiB

0
test/fixtures/Portrait_2.jpg vendored Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 133 KiB

0
test/fixtures/Portrait_3.jpg vendored Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 133 KiB

0
test/fixtures/Portrait_4.jpg vendored Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 128 KiB

0
test/fixtures/Portrait_5.jpg vendored Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 131 KiB

0
test/fixtures/Portrait_6.jpg vendored Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 133 KiB

0
test/fixtures/Portrait_7.jpg vendored Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 132 KiB

0
test/fixtures/Portrait_8.jpg vendored Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 506 B

After

Width:  |  Height:  |  Size: 519 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 840 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 377 B

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
test/fixtures/expected/negate-alpha.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
test/fixtures/expected/negate-trans.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

BIN
test/fixtures/expected/negate-trans.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
test/fixtures/expected/negate.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

BIN
test/fixtures/expected/threshold-1.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 882 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
test/fixtures/expected/threshold-128.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
test/fixtures/expected/threshold-40.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

1
test/fixtures/index.js vendored Executable file → Normal file
View File

@@ -71,6 +71,7 @@ module.exports = {
inputPngWithTransparency: getPath('blackbug.png'), // public domain
inputPngWithGreyAlpha: getPath('grey-8bit-alpha.png'),
inputPngWithOneColor: getPath('2x2_fdcce6.png'),
inputPngWithTransparency16bit: getPath('tbgn2c16.png'), // http://www.schaik.com/pngsuite/tbgn2c16.png
inputPngOverlayLayer0: getPath('alpha-layer-0-background.png'),
inputPngOverlayLayer1: getPath('alpha-layer-1-fill.png'),
inputPngOverlayLayer2: getPath('alpha-layer-2-ink.png'),

BIN
test/fixtures/tbgn2c16.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

View File

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

BIN
test/interpolators/gm.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

BIN
test/interpolators/lbb.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View File

@@ -278,11 +278,25 @@
...
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
}
# glib g_file_read_link
# https://github.com/GNOME/glib/commit/49a5d0f6f2aed99cd78f25655f137f4448e47d92
{
leak_magick
leak_g_file_read_link
Memcheck:Leak
match-leak-kinds: definite,indirect,possible
...

13
test/unit/alpha.js Executable file → Normal file
View File

@@ -46,6 +46,19 @@ describe('Alpha transparency', function() {
});
});
it('Flatten 16-bit PNG with transparency to orange', function(done) {
sharp(fixtures.inputPngWithTransparency16bit)
.flatten()
.background({r: 255, g: 102, b: 0})
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(true, info.size > 0);
assert.strictEqual(32, info.width);
assert.strictEqual(32, info.height);
fixtures.assertSimilar(fixtures.expected('flatten-rgb16-orange.jpg'), data, done);
});
});
it('Do not flatten', function(done) {
sharp(fixtures.inputPngWithTransparency)
.flatten(false)

0
test/unit/blur.js Executable file → Normal file
View File

0
test/unit/clone.js Executable file → Normal file
View File

0
test/unit/colourspace.js Executable file → Normal file
View File

0
test/unit/cpplint.js Executable file → Normal file
View File

0
test/unit/crop.js Executable file → Normal file
View File

0
test/unit/embed.js Executable file → Normal file
View File

89
test/unit/extract.js Executable file → Normal file
View File

@@ -8,10 +8,33 @@ 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)
.extract(2, 2, 20, 20)
.extract({ left: 2, top: 2, width: 20, height: 20 })
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(20, info.width);
@@ -22,7 +45,7 @@ describe('Partial image extraction', function() {
it('PNG', function(done) {
sharp(fixtures.inputPng)
.extract(300, 200, 400, 200)
.extract({ left: 200, top: 300, width: 400, height: 200 })
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(400, info.width);
@@ -34,7 +57,7 @@ describe('Partial image extraction', function() {
if (sharp.format.webp.output.file) {
it('WebP', function(done) {
sharp(fixtures.inputWebP)
.extract(50, 100, 125, 200)
.extract({ left: 100, top: 50, width: 125, height: 200 })
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(125, info.width);
@@ -46,7 +69,7 @@ describe('Partial image extraction', function() {
it('TIFF', function(done) {
sharp(fixtures.inputTiff)
.extract(63, 34, 341, 529)
.extract({ left: 34, top: 63, width: 341, height: 529 })
.jpeg()
.toBuffer(function(err, data, info) {
if (err) throw err;
@@ -58,7 +81,7 @@ describe('Partial image extraction', function() {
it('Before resize', function(done) {
sharp(fixtures.inputJpg)
.extract(10, 10, 10, 500, 500)
.extract({ left: 10, top: 10, width: 10, height: 500 })
.resize(100, 100)
.toBuffer(function(err, data, info) {
if (err) throw err;
@@ -72,7 +95,7 @@ describe('Partial image extraction', function() {
sharp(fixtures.inputJpg)
.resize(500, 500)
.crop(sharp.gravity.north)
.extract(10, 10, 100, 100)
.extract({ left: 10, top: 10, width: 100, height: 100 })
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(100, info.width);
@@ -83,10 +106,10 @@ describe('Partial image extraction', function() {
it('Before and after resize and crop', function(done) {
sharp(fixtures.inputJpg)
.extract(0, 0, 700, 700)
.extract({ left: 0, top: 0, width: 700, height: 700 })
.resize(500, 500)
.crop(sharp.gravity.north)
.extract(10, 10, 100, 100)
.extract({ left: 10, top: 10, width: 100, height: 100 })
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(100, info.width);
@@ -96,30 +119,55 @@ describe('Partial image extraction', function() {
});
it('Extract then rotate', function(done) {
sharp(fixtures.inputJpg)
.extract(10, 10, 100, 100)
sharp(fixtures.inputPngWithGreyAlpha)
.extract({ left: 20, top: 10, width: 380, height: 280 })
.rotate(90)
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(100, info.width);
assert.strictEqual(100, info.height);
assert.strictEqual(280, info.width);
assert.strictEqual(380, info.height);
fixtures.assertSimilar(fixtures.expected('extract-rotate.jpg'), data, done);
});
});
it('Rotate then extract', function(done) {
sharp(fixtures.inputJpg)
sharp(fixtures.inputPngWithGreyAlpha)
.rotate(90)
.extract(10, 10, 100, 100)
.extract({ left: 20, top: 10, width: 280, height: 380 })
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(100, info.width);
assert.strictEqual(100, info.height);
assert.strictEqual(280, info.width);
assert.strictEqual(380, info.height);
fixtures.assertSimilar(fixtures.expected('rotate-extract.jpg'), data, done);
});
});
describe('Invalid parameters', function() {
describe('using the legacy extract(top,left,width,height) syntax', function () {
it('String top', function() {
assert.throws(function() {
sharp(fixtures.inputJpg).extract('spoons', 10, 10, 10);
});
});
it('Non-integral left', function() {
assert.throws(function() {
sharp(fixtures.inputJpg).extract(10, 10.2, 10, 10);
});
});
it('Negative width - negative', function() {
assert.throws(function() {
sharp(fixtures.inputJpg).extract(10, 10, -10, 10);
});
});
it('Null height', function() {
assert.throws(function() {
sharp(fixtures.inputJpg).extract(10, 10, 10, null);
});
});
});
it('Undefined', function() {
assert.throws(function() {
@@ -129,27 +177,26 @@ describe('Partial image extraction', function() {
it('String top', function() {
assert.throws(function() {
sharp(fixtures.inputJpg).extract('spoons', 10, 10, 10);
sharp(fixtures.inputJpg).extract({ left: 10, top: 'spoons', width: 10, height: 10 });
});
});
it('Non-integral left', function() {
assert.throws(function() {
sharp(fixtures.inputJpg).extract(10, 10.2, 10, 10);
sharp(fixtures.inputJpg).extract({ left: 10.2, top: 10, width: 10, height: 10 });
});
});
it('Negative width - negative', function() {
assert.throws(function() {
sharp(fixtures.inputJpg).extract(10, 10, -10, 10);
sharp(fixtures.inputJpg).extract({ left: 10, top: 10, width: -10, height: 10 });
});
});
it('Null height', function() {
assert.throws(function() {
sharp(fixtures.inputJpg).extract(10, 10, 10, null);
sharp(fixtures.inputJpg).extract({ left: 10, top: 10, width: 10, height: null });
});
});
});
});

2
test/unit/gamma.js Executable file → Normal file
View File

@@ -16,7 +16,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, {threshold: 12}, done);
fixtures.assertSimilar(fixtures.expected('gamma-0.0.jpg'), data, done);
});
});

0
test/unit/interpolation.js Executable file → Normal file
View File

240
test/unit/io.js Executable file → Normal file
View File

@@ -3,8 +3,6 @@
var fs = require('fs');
var assert = require('assert');
var semver = require('semver');
var sharp = require('../../index');
var fixtures = require('../fixtures');
@@ -483,37 +481,34 @@ describe('Input/output', function() {
done();
});
if (semver.gte(sharp.libvipsVersion(), '7.42.0')) {
it('withoutAdaptiveFiltering generates smaller file [libvips ' + sharp.libvipsVersion() + '>=7.42.0]', function(done) {
// First generate with adaptive filtering
sharp(fixtures.inputPng)
.resize(320, 240)
.withoutAdaptiveFiltering(false)
.toBuffer(function(err, adaptiveData, adaptiveInfo) {
if (err) throw err;
assert.strictEqual(true, adaptiveData.length > 0);
assert.strictEqual(adaptiveData.length, adaptiveInfo.size);
assert.strictEqual('png', adaptiveInfo.format);
assert.strictEqual(320, adaptiveInfo.width);
assert.strictEqual(240, adaptiveInfo.height);
// Then generate without
sharp(fixtures.inputPng)
.resize(320, 240)
.withoutAdaptiveFiltering()
.toBuffer(function(err, withoutAdaptiveData, withoutAdaptiveInfo) {
if (err) throw err;
assert.strictEqual(true, withoutAdaptiveData.length > 0);
assert.strictEqual(withoutAdaptiveData.length, withoutAdaptiveInfo.size);
assert.strictEqual('png', withoutAdaptiveInfo.format);
assert.strictEqual(320, withoutAdaptiveInfo.width);
assert.strictEqual(240, withoutAdaptiveInfo.height);
assert.strictEqual(true, withoutAdaptiveData.length < adaptiveData.length);
done();
});
});
});
}
it('withoutAdaptiveFiltering generates smaller file', function(done) {
// First generate with adaptive filtering
sharp(fixtures.inputPng)
.resize(320, 240)
.withoutAdaptiveFiltering(false)
.toBuffer(function(err, adaptiveData, adaptiveInfo) {
if (err) throw err;
assert.strictEqual(true, adaptiveData.length > 0);
assert.strictEqual(adaptiveData.length, adaptiveInfo.size);
assert.strictEqual('png', adaptiveInfo.format);
assert.strictEqual(320, adaptiveInfo.width);
assert.strictEqual(240, adaptiveInfo.height);
// Then generate without
sharp(fixtures.inputPng)
.resize(320, 240)
.withoutAdaptiveFiltering()
.toBuffer(function(err, withoutAdaptiveData, withoutAdaptiveInfo) {
if (err) throw err;
assert.strictEqual(true, withoutAdaptiveData.length > 0);
assert.strictEqual(withoutAdaptiveData.length, withoutAdaptiveInfo.size);
assert.strictEqual('png', withoutAdaptiveInfo.format);
assert.strictEqual(320, withoutAdaptiveInfo.width);
assert.strictEqual(240, withoutAdaptiveInfo.height);
assert.strictEqual(true, withoutAdaptiveData.length < adaptiveData.length);
done();
});
});
});
});
it('Without chroma subsampling generates larger file', function(done) {
@@ -545,109 +540,110 @@ describe('Input/output', function() {
});
});
if (semver.gte(sharp.libvipsVersion(), '8.0.0')) {
it('Trellis quantisation [libvips ' + sharp.libvipsVersion() + '>=8.0.0]', function(done) {
// First generate without
sharp(fixtures.inputJpg)
.resize(320, 240)
.trellisQuantisation(false)
.toBuffer(function(err, withoutData, withoutInfo) {
if (err) throw err;
assert.strictEqual(true, withoutData.length > 0);
assert.strictEqual(withoutData.length, withoutInfo.size);
assert.strictEqual('jpeg', withoutInfo.format);
assert.strictEqual(320, withoutInfo.width);
assert.strictEqual(240, withoutInfo.height);
// Then generate with
sharp(fixtures.inputJpg)
.resize(320, 240)
.trellisQuantization()
.toBuffer(function(err, withData, withInfo) {
if (err) throw err;
assert.strictEqual(true, withData.length > 0);
assert.strictEqual(withData.length, withInfo.size);
assert.strictEqual('jpeg', withInfo.format);
assert.strictEqual(320, withInfo.width);
assert.strictEqual(240, withInfo.height);
// Verify image is same (as mozjpeg may not be present) size or less
assert.strictEqual(true, withData.length <= withoutData.length);
done();
});
});
});
it('Overshoot deringing [libvips ' + sharp.libvipsVersion() + '>=8.0.0]', function(done) {
// First generate without
sharp(fixtures.inputJpg)
.resize(320, 240)
.overshootDeringing(false)
.toBuffer(function(err, withoutData, withoutInfo) {
if (err) throw err;
assert.strictEqual(true, withoutData.length > 0);
assert.strictEqual(withoutData.length, withoutInfo.size);
assert.strictEqual('jpeg', withoutInfo.format);
assert.strictEqual(320, withoutInfo.width);
assert.strictEqual(240, withoutInfo.height);
// Then generate with
sharp(fixtures.inputJpg)
.resize(320, 240)
.overshootDeringing()
.toBuffer(function(err, withData, withInfo) {
if (err) throw err;
assert.strictEqual(true, withData.length > 0);
assert.strictEqual(withData.length, withInfo.size);
assert.strictEqual('jpeg', withInfo.format);
assert.strictEqual(320, withInfo.width);
assert.strictEqual(240, withInfo.height);
done();
});
});
});
it('Optimise scans [libvips ' + sharp.libvipsVersion() + '>=8.0.0]', function(done) {
// First generate without
sharp(fixtures.inputJpg)
.resize(320, 240)
.optimiseScans(false)
.toBuffer(function(err, withoutData, withoutInfo) {
if (err) throw err;
assert.strictEqual(true, withoutData.length > 0);
assert.strictEqual(withoutData.length, withoutInfo.size);
assert.strictEqual('jpeg', withoutInfo.format);
assert.strictEqual(320, withoutInfo.width);
assert.strictEqual(240, withoutInfo.height);
// Then generate with
sharp(fixtures.inputJpg)
.resize(320, 240)
.optimizeScans()
.toBuffer(function(err, withData, withInfo) {
if (err) throw err;
assert.strictEqual(true, withData.length > 0);
assert.strictEqual(withData.length, withInfo.size);
assert.strictEqual('jpeg', withInfo.format);
assert.strictEqual(320, withInfo.width);
assert.strictEqual(240, withInfo.height);
// Verify image is of a different size (progressive output even without mozjpeg)
assert.strictEqual(true, withData.length != withoutData.length);
done();
});
});
});
}
it('Trellis quantisation', function(done) {
// First generate without
sharp(fixtures.inputJpg)
.resize(320, 240)
.trellisQuantisation(false)
.toBuffer(function(err, withoutData, withoutInfo) {
if (err) throw err;
assert.strictEqual(true, withoutData.length > 0);
assert.strictEqual(withoutData.length, withoutInfo.size);
assert.strictEqual('jpeg', withoutInfo.format);
assert.strictEqual(320, withoutInfo.width);
assert.strictEqual(240, withoutInfo.height);
// Then generate with
sharp(fixtures.inputJpg)
.resize(320, 240)
.trellisQuantization()
.toBuffer(function(err, withData, withInfo) {
if (err) throw err;
assert.strictEqual(true, withData.length > 0);
assert.strictEqual(withData.length, withInfo.size);
assert.strictEqual('jpeg', withInfo.format);
assert.strictEqual(320, withInfo.width);
assert.strictEqual(240, withInfo.height);
// Verify image is same (as mozjpeg may not be present) size or less
assert.strictEqual(true, withData.length <= withoutData.length);
done();
});
});
});
it('Overshoot deringing', function(done) {
// First generate without
sharp(fixtures.inputJpg)
.resize(320, 240)
.overshootDeringing(false)
.toBuffer(function(err, withoutData, withoutInfo) {
if (err) throw err;
assert.strictEqual(true, withoutData.length > 0);
assert.strictEqual(withoutData.length, withoutInfo.size);
assert.strictEqual('jpeg', withoutInfo.format);
assert.strictEqual(320, withoutInfo.width);
assert.strictEqual(240, withoutInfo.height);
// Then generate with
sharp(fixtures.inputJpg)
.resize(320, 240)
.overshootDeringing()
.toBuffer(function(err, withData, withInfo) {
if (err) throw err;
assert.strictEqual(true, withData.length > 0);
assert.strictEqual(withData.length, withInfo.size);
assert.strictEqual('jpeg', withInfo.format);
assert.strictEqual(320, withInfo.width);
assert.strictEqual(240, withInfo.height);
done();
});
});
});
it('Optimise scans', function(done) {
// First generate without
sharp(fixtures.inputJpg)
.resize(320, 240)
.optimiseScans(false)
.toBuffer(function(err, withoutData, withoutInfo) {
if (err) throw err;
assert.strictEqual(true, withoutData.length > 0);
assert.strictEqual(withoutData.length, withoutInfo.size);
assert.strictEqual('jpeg', withoutInfo.format);
assert.strictEqual(320, withoutInfo.width);
assert.strictEqual(240, withoutInfo.height);
// Then generate with
sharp(fixtures.inputJpg)
.resize(320, 240)
.optimizeScans()
.toBuffer(function(err, withData, withInfo) {
if (err) throw err;
assert.strictEqual(true, withData.length > 0);
assert.strictEqual(withData.length, withInfo.size);
assert.strictEqual('jpeg', withInfo.format);
assert.strictEqual(320, withInfo.width);
assert.strictEqual(240, withInfo.height);
// Verify image is of a different size (progressive output even without mozjpeg)
assert.strictEqual(true, withData.length != withoutData.length);
done();
});
});
});
if (sharp.format.magick.input.file) {
it('Convert SVG, if supported, to PNG', function(done) {
sharp(fixtures.inputSvg)
.resize(100, 100)
.toFormat('png')
.toFile(fixtures.path('output.svg.png'), function(err, info) {
.toBuffer(function(err, data, info) {
if (err) {
assert.strictEqual(0, err.message.indexOf('Input file is 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);
}
done();
});
});
}

0
test/unit/jshint.js Executable file → Normal file
View File

0
test/unit/metadata.js Executable file → Normal file
View File

107
test/unit/negate.js Normal file
View File

@@ -0,0 +1,107 @@
'use strict';
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)
.resize(320, 240)
.negate()
.toBuffer(function(err, data, info) {
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
fixtures.assertSimilar(fixtures.expected('negate.jpg'), data, done);
});
});
it('negate (png)', function(done) {
sharp(fixtures.inputPng)
.resize(320, 240)
.negate()
.toBuffer(function(err, data, info) {
assert.strictEqual('png', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
fixtures.assertSimilar(fixtures.expected('negate.png'), data, done);
});
});
it('negate (png, trans)', function(done) {
sharp(fixtures.inputPngWithTransparency)
.resize(320, 240)
.negate()
.toBuffer(function(err, data, info) {
assert.strictEqual('png', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
fixtures.assertSimilar(fixtures.expected('negate-trans.png'), data, done);
});
});
it('negate (png, alpha)', function(done) {
sharp(fixtures.inputPngWithGreyAlpha)
.resize(320, 240)
.negate()
.toBuffer(function(err, data, info) {
assert.strictEqual('png', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
fixtures.assertSimilar(fixtures.expected('negate-alpha.png'), data, done);
});
});
if (sharp.format.webp.output.file) {
it('negate (webp)', function(done) {
sharp(fixtures.inputWebP)
.resize(320, 240)
.negate()
.toBuffer(function(err, data, info) {
assert.strictEqual('webp', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
fixtures.assertSimilar(fixtures.expected('negate.webp'), data, done);
});
});
it('negate (webp, trans)', function(done) {
sharp(fixtures.inputWebPWithTransparency)
.resize(320, 240)
.negate()
.toBuffer(function(err, data, info) {
assert.strictEqual('webp', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
fixtures.assertSimilar(fixtures.expected('negate-trans.webp'), data, done);
});
});
}
it('negate (true)', function(done) {
sharp(fixtures.inputJpg)
.resize(320, 240)
.negate(true)
.toBuffer(function(err, data, info) {
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
fixtures.assertSimilar(fixtures.expected('negate.jpg'), data, done);
});
});
it('negate (false)', function(done) {
var output = fixtures.path('output.unmodified-by-negate.png');
sharp(fixtures.inputJpgWithLowContrast)
.negate(false)
.toFile(output, function(err, info) {
if (err) done(err);
fixtures.assertMaxColourDistance(output, fixtures.inputJpgWithLowContrast, 0);
done();
});
});
});

0
test/unit/normalize.js Executable file → Normal file
View File

0
test/unit/overlay.js Executable file → Normal file
View File

0
test/unit/resize.js Executable file → Normal file
View File

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