Compare commits

...

32 Commits

Author SHA1 Message Date
Lovell Fuller
114ce370ed Provide a default lib location when detecting C++11 ABI
Useful for pkgconfig without -L, e.g. Alpine Linux
See also commit 07d66da
2016-10-13 12:38:53 +01:00
Lovell Fuller
207dcbeaa4 Release v0.16.1 2016-10-13 10:53:38 +01:00
Lovell Fuller
d4a1722863 The long-awaited return of code examples to README 2016-10-12 19:41:49 +01:00
Lovell Fuller
18b9991fe7 Add experimental 'attention' crop strategy 2016-10-12 11:18:58 +01:00
Lovell Fuller
739178dd74 Include '.node' ext for Meteor's require() implementation #537 2016-10-05 10:50:13 +01:00
Taka Kojima
dcd1392a85 Allow platform, arch and arm_version to be overridden (#581)
Aids cross-compilation
2016-10-01 12:54:14 +01:00
Lovell Fuller
07d66da57b Auto-detect C++11 ABI version, remove --sharp-cxx11 flag 2016-09-28 21:40:30 +01:00
Lovell Fuller
28ce33feb3 Fix y-axis calc when overlaying at fixed point #566 2016-09-16 11:20:08 +01:00
Brandon Aaron
86039a3f2b Bumping png16 to 1.6.25 (#570) 2016-09-12 19:43:16 +01:00
Lovell Fuller
af9d09f8ae Ensure conv kernel scale is clamped to min val of 1 #561 2016-09-03 20:06:49 +01:00
Lovell Fuller
7c06a48ec0 Release v0.16.0 2016-08-18 09:00:04 +01:00
Lovell Fuller
7ada9dbd0d Changelog update, fix for small leak introduced in 5c5d74a 2016-08-17 20:56:53 +01:00
Matt Hirsch
5c5d74a903 Add joinChannel and toColourspace/toColorspace operations (#513) 2016-08-17 15:42:05 +01:00
Lovell Fuller
72354d55a8 Doc and changelog updates #519 #540 2016-08-13 17:24:06 +01:00
cmtt
fc2002fbd0 Add alpha channels, if missing, to overlayWith images (#540) 2016-08-13 17:19:15 +01:00
Matt Hirsch
82ec2715f1 Prevent bandbool creating a single channel sRGB image (#519) 2016-08-13 14:55:15 +01:00
Lovell Fuller
ef6e90fb3c Correct dist name logging in packaging test script 2016-08-13 11:43:13 +01:00
Lovell Fuller
475f0bf120 Refactor packaging scripts, add ARMv7/v8 binaries 2016-08-12 13:40:44 +01:00
Lovell Fuller
e68a14c94c Dependency version bumps 2016-08-01 20:23:45 +01:00
Lovell Fuller
da0dc28bc4 Remove unescaped module_root_dir as it can contain spaces 2016-08-01 13:44:46 +01:00
Lovell Fuller
e6bfa52b0b Add raw pixel data support to boolean and withOverlay ops
The previously-scattered image opening logic has been refactored to a
single ImageDescriptor struct/Object available to both JS and C++ code

This removed about 150 LOC but more importantly reduces the complexity
of adding/exposing new operations that require an input image.
2016-07-26 23:07:25 +01:00
Lovell Fuller
36bfbdee0d Add support for using pre-compiled binaries with OSX 2016-07-25 16:32:42 +01:00
Lovell Fuller
7a9a4127a0 Remove deprecated interpolateWith method
Version bump dependencies
2016-07-25 16:11:53 +01:00
Lovell Fuller
4f1472d4ff Upgrade to libvips v8.3.2 2016-07-25 15:30:14 +01:00
Lovell Fuller
032bb7e96b Ensure ICC profiles are removed from PNG output #521 2016-07-21 16:49:27 +01:00
Lovell Fuller
9ddc817a09 Add WebP availability check to test added in a5bd68e 2016-07-21 15:55:34 +01:00
Lovell Fuller
a5bd68ef8c Recalc after WebP shrink-on-load to avoid rounding errors #508 2016-07-21 15:18:14 +01:00
Lovell Fuller
a2ec3642bf Alpine now provides vips in its testing repo
Resize+sharpen+alpha seems to stack-smash, ignore for now
2016-07-20 20:05:43 +01:00
Lovell Fuller
9647fe1b9f Reduce size of pre-built binaries by ~5% 2016-07-20 20:03:41 +01:00
Lovell Fuller
762cda75a9 Update libxml2 dependency CVE-2016-4448 #515
Also updates:
* libpng as previous version is now unavailable (?)
* libjpeg as previous version was pre-release
2016-07-18 12:07:28 +01:00
Matt Hirsch
c39a9b8de9 Prevent boolean errors during extract operation (#509) (#511) 2016-07-16 10:56:15 +01:00
Matt Hirsch
15a577863a Ensure boolean, bandbool, extractChannel ops occur before sRGB conversion (#504) 2016-07-13 19:20:50 +01:00
78 changed files with 2161 additions and 1405 deletions

5
.gitignore vendored
View File

@@ -4,8 +4,13 @@ coverage
test/bench/node_modules test/bench/node_modules
test/fixtures/output* test/fixtures/output*
test/leak/libvips.supp test/leak/libvips.supp
test/saliency/report.json
test/saliency/Image*
test/saliency/[Uu]serData*
!test/saliency/userData.js
lib lib
include include
packaging/libvips* packaging/libvips*
packaging/*.log packaging/*.log
!packaging/build
.DS_Store .DS_Store

View File

@@ -1,3 +1,4 @@
node_modules node_modules
test/bench/node_modules test/bench/node_modules
test/saliency/humanae/node_modules
coverage coverage

View File

@@ -14,9 +14,8 @@ addons:
- ubuntu-toolchain-r-test - ubuntu-toolchain-r-test
packages: packages:
- g++-4.8 - g++-4.8
osx_image: xcode7.3 osx_image: xcode8
before_install: before_install:
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export CXX=g++-4.8; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export CXX=g++-4.8; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install homebrew/science/vips; fi
after_success: after_success:
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js

View File

@@ -43,6 +43,7 @@ Any change that modifies the existing public API should be added to the relevant
| ------: | :--------- | | ------: | :--------- |
| v0.16.0 | pencil | | v0.16.0 | pencil |
| v0.17.0 | quill | | v0.17.0 | quill |
| v0.18.0 | ridge |
Please squash your changes into a single commit using a command like `git rebase -i upstream/<wip-branch>`. Please squash your changes into a single commit using a command like `git rebase -i upstream/<wip-branch>`.

View File

@@ -1,5 +1,9 @@
# sharp # sharp
```sh
npm install sharp
```
The typical use case for this high speed Node.js module The typical use case for this high speed Node.js module
is to convert large images in common formats to is to convert large images in common formats to
smaller, web-friendly JPEG, PNG and WebP images of varying dimensions. smaller, web-friendly JPEG, PNG and WebP images of varying dimensions.
@@ -13,11 +17,45 @@ Lanczos resampling ensures quality is not sacrificed for speed.
As well as image resizing, operations such as As well as image resizing, operations such as
rotation, extraction, compositing and gamma correction are available. rotation, extraction, compositing and gamma correction are available.
Most Windows (x64), Linux and ARMv6+ systems do not require OS X, Windows (x64), Linux (x64, ARM) systems do not require
the installation of any external runtime dependencies. the installation of any external runtime dependencies.
Use with OS X is as simple as running `brew install homebrew/science/vips` ## Examples
to install the libvips dependency.
```javascript
import sharp from 'sharp';
```
```javascript
sharp(inputBuffer)
.resize(320, 240)
.toFile('output.webp', (err, info) => ... );
```
```javascript
sharp('input.jpg')
.rotate()
.resize(200)
.toBuffer()
.then( data => ... )
.catch( err => ... );
```
```javascript
const roundedCorners = new Buffer(
'<svg><rect x="0" y="0" width="200" height="200" rx="50" ry="50"/></svg>'
);
const roundedCornerResizer =
sharp()
.resize(200, 200)
.overlayWith(roundedCorners, { cutout: true })
.png();
readableStream
.pipe(roundedCornerResizer)
.pipe(writableStream);
```
[![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.png?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master) [![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.png?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master)

View File

@@ -1,4 +1,4 @@
os: Previous Visual Studio 2015 os: Visual Studio 2015
version: "{build}" version: "{build}"
build: off build: off
platform: x64 platform: x64
@@ -10,6 +10,7 @@ environment:
- nodejs_version: "6" - nodejs_version: "6"
install: install:
- ps: Install-Product node $env:nodejs_version x64 - ps: Install-Product node $env:nodejs_version x64
- npm install -g npm@latest
- npm install - npm install
test_script: test_script:
- npm run-script test-win - npm run-script test-win

View File

@@ -18,14 +18,14 @@
'src/libvips/cplusplus/VImage.cpp' 'src/libvips/cplusplus/VImage.cpp'
], ],
'include_dirs': [ 'include_dirs': [
'<(module_root_dir)/include', 'include',
'<(module_root_dir)/include/glib-2.0', 'include/glib-2.0',
'<(module_root_dir)/lib/glib-2.0/include' 'lib/glib-2.0/include'
], ],
'libraries': [ 'libraries': [
'<(module_root_dir)/lib/libvips.lib', '../lib/libvips.lib',
'<(module_root_dir)/lib/libglib-2.0.lib', '../lib/libglib-2.0.lib',
'<(module_root_dir)/lib/libgobject-2.0.lib' '../lib/libgobject-2.0.lib'
], ],
'configurations': { 'configurations': {
'Release': { 'Release': {
@@ -51,7 +51,6 @@
], ],
# Nested variables "pattern" borrowed from http://src.chromium.org/viewvc/chrome/trunk/src/build/common.gypi # Nested variables "pattern" borrowed from http://src.chromium.org/viewvc/chrome/trunk/src/build/common.gypi
'variables': { 'variables': {
'sharp_cxx11%': '0',
'variables': { 'variables': {
'variables': { 'variables': {
'conditions': [ 'conditions': [
@@ -92,10 +91,6 @@
'src/sharp.cc', 'src/sharp.cc',
'src/utilities.cc' 'src/utilities.cc'
], ],
'defines': [
'_GLIBCXX_USE_CXX11_ABI=<(sharp_cxx11)',
'_ALLOW_KEYWORD_MACROS'
],
'include_dirs': [ 'include_dirs': [
'<!(node -e "require(\'nan\')")' '<!(node -e "require(\'nan\')")'
], ],
@@ -109,61 +104,83 @@
}, { }, {
'libraries': ['<!@(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --libs vips-cpp)'] 'libraries': ['<!@(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --libs vips-cpp)']
}] }]
],
'defines': [
# Inspect libvips-cpp.so to determine which C++11 ABI version was used and set _GLIBCXX_USE_CXX11_ABI accordingly. This is quite horrible.
'_GLIBCXX_USE_CXX11_ABI=<!(if readelf -Ws "$(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --libs-only-L vips-cpp | cut -c 3- | sed -e "s/^$/\/usr\/lib/")/libvips-cpp.so" | c++filt | grep -qF __cxx11;then echo "1";else echo "0";fi)'
] ]
}, { }, {
# Attempt to download pre-built libvips and install locally within node_modules # Attempt to download pre-built libvips and install locally within node_modules
'include_dirs': [ 'include_dirs': [
'<(module_root_dir)/include', 'include',
'<(module_root_dir)/include/glib-2.0', 'include/glib-2.0',
'<(module_root_dir)/lib/glib-2.0/include' 'lib/glib-2.0/include'
], ],
'conditions': [ 'conditions': [
['OS == "win"', { ['OS == "win"', {
'defines': [
'_ALLOW_KEYWORD_MACROS'
],
'libraries': [ 'libraries': [
'<(module_root_dir)/lib/libvips.lib', '../lib/libvips.lib',
'<(module_root_dir)/lib/libglib-2.0.lib', '../lib/libglib-2.0.lib',
'<(module_root_dir)/lib/libgobject-2.0.lib' '../lib/libgobject-2.0.lib'
]
}],
['OS == "mac"', {
'variables': {
'download_vips': '<!(node -e "require(\'./binding\').download_vips()")'
},
'libraries': [
'../lib/libvips-cpp.42.dylib',
'../lib/libvips.42.dylib',
'../lib/libglib-2.0.0.dylib',
'../lib/libgobject-2.0.0.dylib',
# Ensure runtime linking is relative to sharp.node
'-rpath \'@loader_path/../../lib\''
] ]
}], }],
['OS == "linux"', { ['OS == "linux"', {
'variables': { 'variables': {
'download_vips': '<!(LDD_VERSION="<!(ldd --version 2>&1 || true)" node -e "require(\'./binding\').download_vips()")' 'download_vips': '<!(LDD_VERSION="<!(ldd --version 2>&1 || true)" node -e "require(\'./binding\').download_vips()")'
}, },
'defines': [
'_GLIBCXX_USE_CXX11_ABI=0'
],
'libraries': [ 'libraries': [
'<(module_root_dir)/lib/libvips-cpp.so', '../lib/libvips-cpp.so',
'<(module_root_dir)/lib/libvips.so', '../lib/libvips.so',
'<(module_root_dir)/lib/libglib-2.0.so', '../lib/libglib-2.0.so',
'<(module_root_dir)/lib/libgobject-2.0.so', '../lib/libgobject-2.0.so',
# Dependencies of dependencies, included for openSUSE support # Dependencies of dependencies, included for openSUSE support
'<(module_root_dir)/lib/libcairo.so', '../lib/libcairo.so',
'<(module_root_dir)/lib/libcroco-0.6.so', '../lib/libcroco-0.6.so',
'<(module_root_dir)/lib/libexif.so', '../lib/libexif.so',
'<(module_root_dir)/lib/libffi.so', '../lib/libffi.so',
'<(module_root_dir)/lib/libfontconfig.so', '../lib/libfontconfig.so',
'<(module_root_dir)/lib/libfreetype.so', '../lib/libfreetype.so',
'<(module_root_dir)/lib/libgdk_pixbuf-2.0.so', '../lib/libgdk_pixbuf-2.0.so',
'<(module_root_dir)/lib/libgif.so', '../lib/libgif.so',
'<(module_root_dir)/lib/libgio-2.0.so', '../lib/libgio-2.0.so',
'<(module_root_dir)/lib/libgmodule-2.0.so', '../lib/libgmodule-2.0.so',
'<(module_root_dir)/lib/libgsf-1.so', '../lib/libgsf-1.so',
'<(module_root_dir)/lib/libgthread-2.0.so', '../lib/libgthread-2.0.so',
'<(module_root_dir)/lib/libharfbuzz.so', '../lib/libharfbuzz.so',
'<(module_root_dir)/lib/libjpeg.so', '../lib/libjpeg.so',
'<(module_root_dir)/lib/liblcms2.so', '../lib/liblcms2.so',
'<(module_root_dir)/lib/liborc-0.4.so', '../lib/liborc-0.4.so',
'<(module_root_dir)/lib/libpango-1.0.so', '../lib/libpango-1.0.so',
'<(module_root_dir)/lib/libpangocairo-1.0.so', '../lib/libpangocairo-1.0.so',
'<(module_root_dir)/lib/libpangoft2-1.0.so', '../lib/libpangoft2-1.0.so',
'<(module_root_dir)/lib/libpixman-1.so', '../lib/libpixman-1.so',
'<(module_root_dir)/lib/libpng.so', '../lib/libpng.so',
'<(module_root_dir)/lib/libpng16.so', '../lib/librsvg-2.so',
'<(module_root_dir)/lib/librsvg-2.so', '../lib/libtiff.so',
'<(module_root_dir)/lib/libtiff.so', '../lib/libwebp.so',
'<(module_root_dir)/lib/libwebp.so', '../lib/libxml2.so',
'<(module_root_dir)/lib/libxml2.so', '../lib/libz.so',
'<(module_root_dir)/lib/libz.so',
# Ensure runtime linking is relative to sharp.node # Ensure runtime linking is relative to sharp.node
'-Wl,-rpath=\'$${ORIGIN}/../../lib\'' '-Wl,--disable-new-dtags -Wl,-rpath=\'$${ORIGIN}/../../lib\''
] ]
}] }]
] ]
@@ -209,46 +226,48 @@
['OS == "win"', { ['OS == "win"', {
# Windows lacks support for rpath # Windows lacks support for rpath
'copies': [{ 'copies': [{
'destination': '<(module_root_dir)/build/Release', 'destination': 'build/Release',
'files': [ 'files': [
'<(module_root_dir)/lib/GNU.Gettext.dll', 'lib/GNU.Gettext.dll',
'<(module_root_dir)/lib/libasprintf-0.dll', 'lib/libasprintf-0.dll',
'<(module_root_dir)/lib/libcairo-2.dll', 'lib/libcairo-2.dll',
'<(module_root_dir)/lib/libcairo-gobject-2.dll', 'lib/libcairo-gobject-2.dll',
'<(module_root_dir)/lib/libcairo-script-interpreter-2.dll', 'lib/libcairo-script-interpreter-2.dll',
'<(module_root_dir)/lib/libcroco-0.6-3.dll', 'lib/libcharset-1.dll',
'<(module_root_dir)/lib/libexif-12.dll', 'lib/libcroco-0.6-3.dll',
'<(module_root_dir)/lib/libexpat-1.dll', 'lib/libexif-12.dll',
'<(module_root_dir)/lib/libffi-6.dll', 'lib/libexpat-1.dll',
'<(module_root_dir)/lib/libfftw3-3.dll', 'lib/libffi-6.dll',
'<(module_root_dir)/lib/libfontconfig-1.dll', 'lib/libfftw3-3.dll',
'<(module_root_dir)/lib/libfreetype-6.dll', 'lib/libfontconfig-1.dll',
'<(module_root_dir)/lib/libgcc_s_seh-1.dll', 'lib/libfreetype-6.dll',
'<(module_root_dir)/lib/libgdk_pixbuf-2.0-0.dll', 'lib/libgcc_s_seh-1.dll',
'<(module_root_dir)/lib/libgif-4.dll', 'lib/libgdk_pixbuf-2.0-0.dll',
'<(module_root_dir)/lib/libgio-2.0-0.dll', 'lib/libgif-7.dll',
'<(module_root_dir)/lib/libglib-2.0-0.dll', 'lib/libgio-2.0-0.dll',
'<(module_root_dir)/lib/libgmodule-2.0-0.dll', 'lib/libglib-2.0-0.dll',
'<(module_root_dir)/lib/libgobject-2.0-0.dll', 'lib/libgmodule-2.0-0.dll',
'<(module_root_dir)/lib/libgsf-1-114.dll', 'lib/libgobject-2.0-0.dll',
'<(module_root_dir)/lib/libgthread-2.0-0.dll', 'lib/libgsf-1-114.dll',
'<(module_root_dir)/lib/libintl-8.dll', 'lib/libgthread-2.0-0.dll',
'<(module_root_dir)/lib/libjpeg-62.dll', 'lib/libiconv-2.dll',
'<(module_root_dir)/lib/liblcms2-2.dll', 'lib/libintl-8.dll',
'<(module_root_dir)/lib/libpango-1.0-0.dll', 'lib/libjpeg-62.dll',
'<(module_root_dir)/lib/libpangocairo-1.0-0.dll', 'lib/liblcms2-2.dll',
'<(module_root_dir)/lib/libpangowin32-1.0-0.dll', 'lib/libpango-1.0-0.dll',
'<(module_root_dir)/lib/libpixman-1-0.dll', 'lib/libpangocairo-1.0-0.dll',
'<(module_root_dir)/lib/libpng16-16.dll', 'lib/libpangowin32-1.0-0.dll',
'<(module_root_dir)/lib/libquadmath-0.dll', 'lib/libpixman-1-0.dll',
'<(module_root_dir)/lib/librsvg-2-2.dll', 'lib/libpng16-16.dll',
'<(module_root_dir)/lib/libssp-0.dll', 'lib/libquadmath-0.dll',
'<(module_root_dir)/lib/libstdc++-6.dll', 'lib/librsvg-2-2.dll',
'<(module_root_dir)/lib/libtiff-5.dll', 'lib/libssp-0.dll',
'<(module_root_dir)/lib/libvips-42.dll', 'lib/libstdc++-6.dll',
'<(module_root_dir)/lib/libwebp-6.dll', 'lib/libtiff-5.dll',
'<(module_root_dir)/lib/libxml2-2.dll', 'lib/libvips-42.dll',
'<(module_root_dir)/lib/zlib1.dll' 'lib/libwebp-6.dll',
'lib/libxml2-2.dll',
'lib/zlib1.dll'
] ]
}] }]
}] }]

View File

@@ -17,6 +17,16 @@ var minimumLibvipsVersion = process.env.npm_package_config_libvips || require('.
var vipsHeaderPath = path.join(__dirname, 'include', 'vips', 'vips.h'); var vipsHeaderPath = path.join(__dirname, 'include', 'vips', 'vips.h');
var platform = process.env.npm_config_platform || process.platform;
var arch = process.env.npm_config_arch || process.arch;
var arm_version = process.env.npm_config_armv || process.config.variables.arm_version;
if (arch === 'arch64' || arch === 'armhf') {
arch = 'arm';
if (arch === 'arch64') arm_version = '8';
}
// -- Helpers // -- Helpers
// Does this file exist? // Does this file exist?
@@ -46,6 +56,24 @@ var unpack = function(tarPath, done) {
.pipe(extractor); .pipe(extractor);
}; };
var platformId = function() {
var id = [platform, arch].join('-');
if (arch === 'arm') {
switch(arm_version) {
case '8':
id = id + 'v8';
break;
case '7':
id = id + 'v7';
break;
default:
id = id + 'v6';
break;
}
}
return id;
};
// Error // Error
var error = function(msg) { var error = function(msg) {
if (msg instanceof Error) { if (msg instanceof Error) {
@@ -61,7 +89,7 @@ module.exports.download_vips = function() {
// Has vips been installed locally? // Has vips been installed locally?
if (!isFile(vipsHeaderPath)) { if (!isFile(vipsHeaderPath)) {
// Ensure Intel 64-bit or ARM // Ensure Intel 64-bit or ARM
if (process.arch === 'ia32') { if (arch === 'ia32') {
error('Intel Architecture 32-bit systems require manual installation - please see http://sharp.dimens.io/en/stable/install/'); error('Intel Architecture 32-bit systems require manual installation - please see http://sharp.dimens.io/en/stable/install/');
} }
// Ensure glibc >= 2.15 // Ensure glibc >= 2.15
@@ -77,8 +105,7 @@ module.exports.download_vips = function() {
} }
} }
// Arch/platform-specific .tar.gz // Arch/platform-specific .tar.gz
var platform = (process.arch === 'arm') ? 'arm' : process.platform.substr(0, 3); var tarFilename = ['libvips', minimumLibvipsVersion, platformId()].join('-') + '.tar.gz';
var tarFilename = ['libvips', minimumLibvipsVersion, platform].join('-') + '.tar.gz';
var tarPath = path.join(__dirname, 'packaging', tarFilename); var tarPath = path.join(__dirname, 'packaging', tarFilename);
if (isFile(tarPath)) { if (isFile(tarPath)) {
unpack(tarPath); unpack(tarPath);
@@ -120,15 +147,5 @@ module.exports.use_global_vips = function() {
minimumLibvipsVersion minimumLibvipsVersion
); );
} }
if (process.platform === 'darwin' && !useGlobalVips) {
if (globalVipsVersion) {
error(
'Found libvips ' + globalVipsVersion + ' but require ' + minimumLibvipsVersion +
'\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'); process.stdout.write(useGlobalVips ? 'true' : 'false');
}; };

View File

@@ -3,4 +3,4 @@ machine:
- docker - docker
test: test:
override: override:
- ./packaging/test.sh - ./packaging/test-linux-x64.sh

View File

@@ -60,7 +60,7 @@ Fast access to image metadata without decoding any compressed image data.
* `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg` * `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
* `width`: Number of pixels wide * `width`: Number of pixels wide
* `height`: Number of pixels high * `height`: Number of pixels high
* `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `scrgb`, `cmyk`, `lab`, `xyz`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L522) * `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `scrgb`, `cmyk`, `lab`, `xyz`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568)
* `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK * `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
* `density`: Number of pixels per inch (DPI), if present * `density`: Number of pixels per inch (DPI), if present
* `hasProfile`: Boolean indicating the presence of an embedded ICC profile * `hasProfile`: Boolean indicating the presence of an embedded ICC profile
@@ -175,12 +175,11 @@ Possible attributes of `sharp.gravity` are
`north`, `northeast`, `east`, `southeast`, `south`, `north`, `northeast`, `east`, `southeast`, `south`,
`southwest`, `west`, `northwest`, `center` and `centre`. `southwest`, `west`, `northwest`, `center` and `centre`.
Possible attributes of the experimental `sharp.strategy` are: The experimental strategy-based approach resizes so one dimension is at its target length
then repeatedly ranks edge regions, discarding the edge with the lowest score based on the selected strategy.
* `entropy`: resize so one dimension is at its target size * `entropy`: focus on the region with the highest [Shannon entropy](https://en.wikipedia.org/wiki/Entropy_%28information_theory%29).
then repeatedly remove pixels from the edge with the lowest * `attention`: focus on the region with the highest luminance frequency, colour saturation and presence of skin tones.
[Shannon entropy](https://en.wikipedia.org/wiki/Entropy_%28information_theory%29)
until it too reaches the target size.
The default crop option is a `center`/`centre` gravity. The default crop option is a `center`/`centre` gravity.
@@ -445,7 +444,7 @@ Convert to 8-bit greyscale; 256 shades of grey.
This is a linear operation. If the input image is in a non-linear colour space such as sRGB, use `gamma()` with `greyscale()` for the best results. This is a linear operation. If the input image is in a non-linear colour space such as sRGB, use `gamma()` with `greyscale()` for the best results.
The output image will still be web-friendly sRGB and contain three (identical) channels. By default the output image will be web-friendly sRGB and contain three (identical) color channels. This may be overridden by other sharp operations such as `toColourspace('b-w')`, which will produce an output image containing one color channel. An alpha channel may be present, and will be unchanged by the operation.
#### normalize() / normalise() #### normalize() / normalise()
@@ -453,12 +452,12 @@ Enhance output image contrast by stretching its luminance to cover the full dyna
#### overlayWith(image, [options]) #### overlayWith(image, [options])
Overlay (composite) a image containing an alpha channel over the processed (resized, extracted etc.) image. Overlay (composite) a image over the processed (resized, extracted etc.) image.
`image` is one of the following, and must be the same size or smaller than the processed image: `image` is one of the following, and must be the same size or smaller than the processed image:
* Buffer containing PNG, WebP, GIF or SVG image data, or * Buffer containing image data, or
* String containing the path to an image file, with most major transparency formats supported. * String containing the path to an image file
`options`, if present, is an Object with the following optional attributes: `options`, if present, is an Object with the following optional attributes:
@@ -467,6 +466,7 @@ Overlay (composite) a image containing an alpha channel over the processed (resi
* `left` is an integral Number representing the pixel offset from the left edge. * `left` is an integral Number representing the pixel offset from the left edge.
* `tile` is a Boolean, defaulting to `false`. When set to `true` repeats the overlay image across the entire image with the given `gravity`. * `tile` is a Boolean, defaulting to `false`. When set to `true` repeats the overlay image across the entire image with the given `gravity`.
* `cutout` is a Boolean, defaulting to `false`. When set to `true` applies only the alpha channel of the overlay image to the image to be overlaid, giving the appearance of one image being cut out of another. * `cutout` is a Boolean, defaulting to `false`. When set to `true` applies only the alpha channel of the overlay image to the image to be overlaid, giving the appearance of one image being cut out of another.
* `raw` an Object containing `width`, `height` and `channels` when providing uncompressed data.
If both `top` and `left` are provided, they take precedence over `gravity`. If both `top` and `left` are provided, they take precedence over `gravity`.
@@ -489,11 +489,17 @@ sharp('input.png')
}); });
``` ```
#### toColourspace(colourspace) / toColorspace(colorspace)
Set the output colourspace. By default output image will be web-friendly sRGB, with additional channels interpreted as alpha channels.
`colourspace` is a string or `sharp.colourspace` enum that identifies an output colourspace. String arguments comprise vips colour space interpretation names e.g. `srgb`, `rgb`, `scrgb`, `cmyk`, `lab`, `xyz`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568)
#### extractChannel(channel) #### extractChannel(channel)
Extract a single channel from a multi-channel image. Extract a single channel from a multi-channel image.
* `channel` is a zero-indexed integral Number representing the band number to extract. `red`, `green` or `blue` are also accepted as an alternative to `0`, `1` or `2` respectively. `channel` is a zero-indexed integral Number representing the band number to extract. `red`, `green` or `blue` are also accepted as an alternative to `0`, `1` or `2` respectively.
```javascript ```javascript
sharp(input) sharp(input)
@@ -504,6 +510,22 @@ sharp(input)
}); });
``` ```
#### joinChannel(channels, [options])
Join a data channel to the image. The meaning of the added channels depends on the output colourspace, set with `toColourspace()`. By default the output image will be web-friendly sRGB, with additional channels interpreted as alpha channels.
`channels` is one of
* a single file path
* an array of file paths
* a single buffer
* an array of buffers
Note that channel ordering follows vips convention:
* sRGB: 0: Red, 1: Green, 2: Blue, 3: Alpha
* CMYK: 0: Magenta, 1: Cyan, 2: Yellow, 3: Black, 4: Alpha
Buffers may be any of the image formats supported by sharp: JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data. In the case of a RAW buffer, the `options` object should contain a `raw` attribute, which follows the format of the attribute of the same name in the `sharp()` constructor. See `sharp()` for details. See `raw()` for pixel ordering.
#### bandbool(operation) #### bandbool(operation)
Perform a bitwise boolean operation on all input image channels (bands) to produce a single channel output image. Perform a bitwise boolean operation on all input image channels (bands) to produce a single channel output image.
@@ -523,11 +545,11 @@ sharp('input.png')
In the above example if `input.png` is a 3 channel RGB image, `output.png` will be a 1 channel grayscale image where each pixel `P = R & G & B`. In the above example if `input.png` is a 3 channel RGB image, `output.png` will be a 1 channel grayscale image where each pixel `P = R & G & B`.
For example, if `I(1,1) = [247, 170, 14] = [0b11110111, 0b10101010, 0b00001111]` then `O(1,1) = 0b11110111 & 0b10101010 & 0b00001111 = 0b00000010 = 2`. For example, if `I(1,1) = [247, 170, 14] = [0b11110111, 0b10101010, 0b00001111]` then `O(1,1) = 0b11110111 & 0b10101010 & 0b00001111 = 0b00000010 = 2`.
#### boolean(image, operation) #### boolean(image, operation, [options])
Perform a bitwise boolean operation with `image`, where `image` is one of the following: Perform a bitwise boolean operation with `image`, where `image` is one of the following:
* Buffer containing PNG, WebP, GIF or SVG image data, or * Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
* String containing the path to an image file * String containing the path to an image file
This operation creates an output image where each pixel is the result of the selected bitwise boolean `operation` between the corresponding pixels of the input images. This operation creates an output image where each pixel is the result of the selected bitwise boolean `operation` between the corresponding pixels of the input images.
@@ -537,6 +559,10 @@ The boolean operation can be one of the following:
* `or` performs a bitwise or operation, like the c-operator `|`. * `or` performs a bitwise or operation, like the c-operator `|`.
* `eor` performs a bitwise exclusive or operation, like the c-operator `^`. * `eor` performs a bitwise exclusive or operation, like the c-operator `^`.
`options`, if present, is an Object with the following optional attributes:
* `raw` an Object containing `width`, `height` and `channels` when providing uncompressed data.
### Output ### Output
#### toFile(path, [callback]) #### toFile(path, [callback])

View File

@@ -1,5 +1,68 @@
# Changelog # Changelog
### v0.16 - "*pencil*"
Requires libvips v8.3.3
#### v0.16.1 - 13<sup>th</sup> October 2016
* C++11 ABI version is now auto-detected, remove sharp-cxx11 installation flag.
* Add experimental 'attention' crop strategy.
[#295](https://github.com/lovell/sharp/issues/295)
* Include .node extension for Meteor's require() implementation.
[#537](https://github.com/lovell/sharp/issues/537)
[@isjackwild](https://github.com/isjackwild)
* Ensure convolution kernel scale is clamped to a minimum value of 1.
[#561](https://github.com/lovell/sharp/issues/561)
[@abagshaw](https://github.com/abagshaw)
* Correct calculation of y-axis placement when overlaying image at a fixed point.
[#566](https://github.com/lovell/sharp/issues/566)
[@Nateowami](https://github.com/Nateowami)
#### v0.16.0 - 18<sup>th</sup> August 2016
* Add pre-compiled libvips for OS X, ARMv7 and ARMv8.
[#312](https://github.com/lovell/sharp/issues/312)
* Ensure boolean, bandbool, extractChannel ops occur before sRGB conversion.
[#504](https://github.com/lovell/sharp/pull/504)
[@mhirsch](https://github.com/mhirsch)
* Recalculate factors after WebP shrink-on-load to avoid round-to-zero errors.
[#508](https://github.com/lovell/sharp/issues/508)
[@asilvas](https://github.com/asilvas)
* Prevent boolean errors during extract operation.
[#511](https://github.com/lovell/sharp/pull/511)
[@mhirsch](https://github.com/mhirsch)
* Add joinChannel and toColourspace/toColorspace operations.
[#513](https://github.com/lovell/sharp/pull/513)
[@mhirsch](https://github.com/mhirsch)
* Add support for raw pixel data with boolean and withOverlay operations.
[#516](https://github.com/lovell/sharp/pull/516)
[@mhirsch](https://github.com/mhirsch)
* Prevent bandbool creating a single channel sRGB image.
[#519](https://github.com/lovell/sharp/pull/519)
[@mhirsch](https://github.com/mhirsch)
* Ensure ICC profiles are removed from PNG output unless withMetadata used.
[#521](https://github.com/lovell/sharp/issues/521)
[@ChrisPinewood](https://github.com/ChrisPinewood)
* Add alpha channels, if missing, to overlayWith images.
[#540](https://github.com/lovell/sharp/pull/540)
[@cmtt](https://github.com/cmtt)
* Remove deprecated interpolateWith method - use resize(w, h, { interpolator: ... })
[#310](https://github.com/lovell/sharp/issues/310)
### v0.15 - "*outfit*" ### v0.15 - "*outfit*"
Requires libvips v8.3.1 Requires libvips v8.3.1

View File

@@ -13,12 +13,9 @@ Lanczos resampling ensures quality is not sacrificed for speed.
As well as image resizing, operations such as As well as image resizing, operations such as
rotation, extraction, compositing and gamma correction are available. rotation, extraction, compositing and gamma correction are available.
Most Windows (x64), Linux and ARMv6+ systems do not require OS X, Windows (x64), Linux (x64, ARM) systems do not require
the installation of any external runtime dependencies. 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) [![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.png?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master)
### Formats ### Formats

View File

@@ -7,7 +7,7 @@ npm install sharp
### Prerequisites ### Prerequisites
* C++11 compatible compiler such as gcc 4.8+, clang 3.0+ or MSVC 2013+ * C++11 compatible compiler such as gcc 4.8+, clang 3.0+ or MSVC 2013+
* [node-gyp](https://github.com/TooTallNate/node-gyp#installation) * [node-gyp](https://github.com/TooTallNate/node-gyp#installation) and its dependencies
### Linux ### Linux
@@ -15,31 +15,22 @@ npm install sharp
[![Linux Build Status](https://circleci.com/gh/lovell/sharp.svg?style=svg&circle-token=6cb6d1d287a51af83722b19ed8885377fbc85e5c)](https://circleci.com/gh/lovell/sharp) [![Linux Build Status](https://circleci.com/gh/lovell/sharp.svg?style=svg&circle-token=6cb6d1d287a51af83722b19ed8885377fbc85e5c)](https://circleci.com/gh/lovell/sharp)
libvips and its dependencies are fetched and stored within `node_modules/sharp/lib` during `npm install`. libvips and its dependencies are fetched and stored within `node_modules/sharp/lib` during `npm install`.
This involves an automated HTTPS download of approximately 6.7MB. This involves an automated HTTPS download of approximately 6.5MB.
Most recent Linux-based operating systems with glibc running on x64 and ARMv6+ CPUs should "just work", e.g.: Most recent Linux-based operating systems with glibc running on x64 and ARMv6+ CPUs should "just work", e.g.:
* Debian 7, 8 * Debian 7, 8
* Ubuntu 12.04, 14.04, 15.10, 16.04 * Ubuntu 12.04, 14.04, 16.04
* Centos 7 * Centos 7
* Fedora 22, 23 * Fedora 23, 24
* openSUSE 13.2 * openSUSE 13.2
* Archlinux 2015.06.01 * Archlinux
* Raspbian Jessie * Raspbian Jessie
* Amazon Linux 2015.03, 2015.09 * Amazon Linux 2016.03, 2016.09
To use your own version of libvips instead of the provided binaries, make sure it is To use a globally-installed version of libvips instead of the provided binaries,
at least the version listed under `config.libvips` in the `package.json` file, make sure it is at least the version listed under `config.libvips` in the `package.json` file
that it can be located using `pkg-config --modversion vips-cpp`. and that it can be located using `pkg-config --modversion vips-cpp`.
There are [changes in the C++11 ABI](https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_dual_abi.html)
when using v5.1+ of the `g++` compiler.
If you have installed `libvips-dev` via package manager on an OS such as Debian testing/unstable,
you can pass the required value of the `_GLIBCXX_USE_CXX11_ABI` macro using the `--sharp-cxx11` flag.
```sh
npm install --sharp-cxx11=1
```
If you are using non-stadard paths (anything other than `/usr` or `/usr/local`), If you are using non-stadard paths (anything other than `/usr` or `/usr/local`),
you might need to set `PKG_CONFIG_PATH` during `npm install` you might need to set `PKG_CONFIG_PATH` during `npm install`
@@ -66,30 +57,14 @@ via `sharp.cache(false)` to avoid a stack overflow.
[![OS X 10.9.5 Build Status](https://travis-ci.org/lovell/sharp.png?branch=master)](https://travis-ci.org/lovell/sharp) [![OS X 10.9.5 Build Status](https://travis-ci.org/lovell/sharp.png?branch=master)](https://travis-ci.org/lovell/sharp)
libvips must be installed before `npm install` is run. libvips and its dependencies are fetched and stored within `node_modules/sharp/lib` during `npm install`.
This can be achieved via homebrew: This involves an automated HTTPS download of approximately 6.5MB.
```sh To use your own version of libvips instead of the provided binaries, make sure it is
brew install homebrew/science/vips at least the version listed under `config.libvips` in the `package.json` file and
``` that it can be located using `pkg-config --modversion vips-cpp`.
For WebP suppport use: ### Windows x64
```sh
brew install homebrew/science/vips --with-webp
```
A missing or incorrectly configured _Xcode Command Line Tools_ installation
[can lead](https://github.com/lovell/sharp/issues/80) to a
`library not found for -ljpeg` error.
If so, please try: `xcode-select --install`.
The _gettext_ dependency of _libvips_
[can lead](https://github.com/lovell/sharp/issues/9)
to a `library not found for -lintl` error.
If so, please try `brew link gettext --force`.
### Windows
[![Windows x64 Build Status](https://ci.appveyor.com/api/projects/status/pgtul704nkhhg6sg)](https://ci.appveyor.com/project/lovell/sharp) [![Windows x64 Build Status](https://ci.appveyor.com/api/projects/status/pgtul704nkhhg6sg)](https://ci.appveyor.com/project/lovell/sharp)

194
index.js
View File

@@ -9,7 +9,7 @@ var semver = require('semver');
var color = require('color'); var color = require('color');
var BluebirdPromise = require('bluebird'); var BluebirdPromise = require('bluebird');
var sharp = require('./build/Release/sharp'); var sharp = require('./build/Release/sharp.node');
// Versioning // Versioning
var versions = { var versions = {
@@ -42,14 +42,8 @@ var Sharp = function(input, options) {
stream.Duplex.call(this); stream.Duplex.call(this);
this.options = { this.options = {
// input options // input options
bufferIn: [],
streamIn: false,
sequentialRead: false, sequentialRead: false,
limitInputPixels: maximum.pixels, limitInputPixels: maximum.pixels,
density: 72,
rawWidth: 0,
rawHeight: 0,
rawChannels: 0,
// ICC profiles // ICC profiles
iccProfilePath: path.join(__dirname, 'icc') + path.sep, iccProfilePath: path.join(__dirname, 'icc') + path.sep,
// resize options // resize options
@@ -90,13 +84,10 @@ var Sharp = function(input, options) {
gamma: 0, gamma: 0,
greyscale: false, greyscale: false,
normalize: 0, normalize: 0,
bandBoolOp: null,
booleanOp: null,
booleanBufferIn: null, booleanBufferIn: null,
booleanFileIn: '', booleanFileIn: '',
joinChannelIn: [],
// overlay // overlay
overlayFileIn: '',
overlayBufferIn: null,
overlayGravity: 0, overlayGravity: 0,
overlayXOffset : -1, overlayXOffset : -1,
overlayYOffset : -1, overlayYOffset : -1,
@@ -119,24 +110,13 @@ var Sharp = function(input, options) {
tileSize: 256, tileSize: 256,
tileOverlap: 0, tileOverlap: 0,
extractChannel: -1, extractChannel: -1,
colourspace: 'srgb',
// Function to notify of queue length changes // Function to notify of queue length changes
queueListener: function(queueLength) { queueListener: function(queueLength) {
module.exports.queue.emit('change', queueLength); module.exports.queue.emit('change', queueLength);
} }
}; };
if (isString(input)) { this.options.input = this._createInputDescriptor(input, options, { allowStream: true });
// input=file
this.options.fileIn = input;
} else if (isBuffer(input)) {
// input=buffer
this.options.bufferIn = input;
} else if (!isDefined(input)) {
// input=stream
this.options.streamIn = true;
} else {
throw new Error('Unsupported input ' + typeof input);
}
this._inputOptions(options);
return this; return this;
}; };
module.exports = Sharp; module.exports = Sharp;
@@ -189,37 +169,50 @@ var contains = function(val, list) {
}; };
/* /*
Set input-related options Create Object containing input and input-related options
density: DPI at which to load vector images via libmagick
*/ */
Sharp.prototype._inputOptions = function(options) { Sharp.prototype._createInputDescriptor = function(input, inputOptions, containerOptions) {
if (isObject(options)) { var inputDescriptor = {};
// Density if (isString(input)) {
if (isDefined(options.density)) { // filesystem
if (isInteger(options.density) && inRange(options.density, 1, 2400)) { inputDescriptor.file = input;
this.options.density = options.density; } else if (isBuffer(input)) {
// Buffer
inputDescriptor.buffer = input;
} else if (!isDefined(input) && isObject(containerOptions) && containerOptions.allowStream) {
// Stream
inputDescriptor.buffer = [];
} else { } else {
throw new Error('Invalid density (1 to 2400) ' + options.density); throw new Error('Unsupported input ' + typeof input);
}
if (isObject(inputOptions)) {
// Density
if (isDefined(inputOptions.density)) {
if (isInteger(inputOptions.density) && inRange(inputOptions.density, 1, 2400)) {
inputDescriptor.density = inputOptions.density;
} else {
throw new Error('Invalid density (1 to 2400) ' + inputOptions.density);
} }
} }
// Raw pixel input // Raw pixel input
if (isDefined(options.raw)) { if (isDefined(inputOptions.raw)) {
if ( if (
isObject(options.raw) && isObject(inputOptions.raw) &&
isInteger(options.raw.width) && inRange(options.raw.width, 1, maximum.width) && isInteger(inputOptions.raw.width) && inRange(inputOptions.raw.width, 1, maximum.width) &&
isInteger(options.raw.height) && inRange(options.raw.height, 1, maximum.height) && isInteger(inputOptions.raw.height) && inRange(inputOptions.raw.height, 1, maximum.height) &&
isInteger(options.raw.channels) && inRange(options.raw.channels, 1, 4) isInteger(inputOptions.raw.channels) && inRange(inputOptions.raw.channels, 1, 4)
) { ) {
this.options.rawWidth = options.raw.width; inputDescriptor.rawWidth = inputOptions.raw.width;
this.options.rawHeight = options.raw.height; inputDescriptor.rawHeight = inputOptions.raw.height;
this.options.rawChannels = options.raw.channels; inputDescriptor.rawChannels = inputOptions.raw.channels;
} else { } else {
throw new Error('Expected width, height and channels for raw pixel input'); throw new Error('Expected width, height and channels for raw pixel input');
} }
} }
} else if (isDefined(options)) { } else if (isDefined(inputOptions)) {
throw new Error('Invalid input options ' + options); throw new Error('Invalid input options ' + inputOptions);
} }
return inputDescriptor;
}; };
/* /*
@@ -227,9 +220,9 @@ Sharp.prototype._inputOptions = function(options) {
*/ */
Sharp.prototype._write = function(chunk, encoding, callback) { Sharp.prototype._write = function(chunk, encoding, callback) {
/*jslint unused: false */ /*jslint unused: false */
if (this.options.streamIn) { if (Array.isArray(this.options.input.buffer)) {
if (isBuffer(chunk)) { if (isBuffer(chunk)) {
this.options.bufferIn.push(chunk); this.options.input.buffer.push(chunk);
callback(); callback();
} else { } else {
callback(new Error('Non-Buffer data on Writable Stream')); callback(new Error('Non-Buffer data on Writable Stream'));
@@ -240,13 +233,16 @@ Sharp.prototype._write = function(chunk, encoding, callback) {
}; };
/* /*
Flattens the array of chunks in bufferIn Flattens the array of chunks accumulated in input.buffer
*/ */
Sharp.prototype._flattenBufferIn = function() { Sharp.prototype._flattenBufferIn = function() {
if (Array.isArray(this.options.bufferIn)) { if (this._isStreamInput()) {
this.options.bufferIn = Buffer.concat(this.options.bufferIn); this.options.input.buffer = Buffer.concat(this.options.input.buffer);
} }
}; };
Sharp.prototype._isStreamInput = function() {
return Array.isArray(this.options.input.buffer);
};
// Weighting to apply to image crop // Weighting to apply to image crop
module.exports.gravity = { module.exports.gravity = {
@@ -264,7 +260,8 @@ module.exports.gravity = {
// Strategies for automagic behaviour // Strategies for automagic behaviour
module.exports.strategy = { module.exports.strategy = {
entropy: 16 entropy: 16,
attention: 17
}; };
/* /*
@@ -281,7 +278,7 @@ Sharp.prototype.crop = function(crop) {
} else if (isString(crop) && isInteger(module.exports.gravity[crop])) { } else if (isString(crop) && isInteger(module.exports.gravity[crop])) {
// Gravity (string) // Gravity (string)
this.options.crop = module.exports.gravity[crop]; this.options.crop = module.exports.gravity[crop];
} else if (isInteger(crop) && crop === module.exports.strategy.entropy) { } else if (isInteger(crop) && crop >= module.exports.strategy.entropy) {
// Strategy // Strategy
this.options.crop = crop; this.options.crop = crop;
} else { } else {
@@ -371,14 +368,8 @@ Sharp.prototype.negate = function(negate) {
/* /*
Bitwise boolean operations between images Bitwise boolean operations between images
*/ */
Sharp.prototype.boolean = function(operand, operator) { Sharp.prototype.boolean = function(operand, operator, options) {
if (isString(operand)) { this.options.boolean = this._createInputDescriptor(operand, options);
this.options.booleanFileIn = operand;
} else if (isBuffer(operand)) {
this.options.booleanBufferIn = operand;
} else {
throw new Error('Unsupported boolean operand ' + typeof operand);
}
if (isString(operator) && contains(operator, ['and', 'or', 'eor'])) { if (isString(operator) && contains(operator, ['and', 'or', 'eor'])) {
this.options.booleanOp = operator; this.options.booleanOp = operator;
} else { } else {
@@ -391,13 +382,9 @@ Sharp.prototype.boolean = function(operand, operator) {
Overlay with another image, using an optional gravity Overlay with another image, using an optional gravity
*/ */
Sharp.prototype.overlayWith = function(overlay, options) { Sharp.prototype.overlayWith = function(overlay, options) {
if (isString(overlay)) { this.options.overlay = this._createInputDescriptor(overlay, options, {
this.options.overlayFileIn = overlay; allowStream: false
} else if (isBuffer(overlay)) { });
this.options.overlayBufferIn = overlay;
} else {
throw new Error('Unsupported overlay ' + typeof overlay);
}
if (isObject(options)) { if (isObject(options)) {
if (isDefined(options.tile)) { if (isDefined(options.tile)) {
if (isBoolean(options.tile)) { if (isBoolean(options.tile)) {
@@ -437,6 +424,20 @@ Sharp.prototype.overlayWith = function(overlay, options) {
return this; return this;
}; };
/*
Add another color channel to the image
*/
Sharp.prototype.joinChannel = function(images, options) {
if (Array.isArray(images)) {
images.forEach(function(image) {
this.options.joinChannelIn.push(this._createInputDescriptor(image, options));
}, this);
} else {
this.options.joinChannelIn.push(this._createInputDescriptor(images, options));
}
return this;
};
/* /*
Rotate output image by 0, 90, 180 or 270 degrees Rotate output image by 0, 90, 180 or 270 degrees
Auto-rotation based on the EXIF Orientation tag is represented by an angle of -1 Auto-rotation based on the EXIF Orientation tag is represented by an angle of -1
@@ -503,22 +504,25 @@ Sharp.prototype.blur = function(sigma) {
Convolve the image with a kernel. Convolve the image with a kernel.
*/ */
Sharp.prototype.convolve = function(kernel) { Sharp.prototype.convolve = function(kernel) {
if (!isDefined(kernel) || !isDefined(kernel.kernel) || if (!isObject(kernel) || !Array.isArray(kernel.kernel) ||
!isDefined(kernel.width) || !isDefined(kernel.height) || !isInteger(kernel.width) || !isInteger(kernel.height) ||
!inRange(kernel.width,3,1001) || !inRange(kernel.height,3,1001) || !inRange(kernel.width, 3, 1001) || !inRange(kernel.height, 3, 1001) ||
kernel.height * kernel.width != kernel.kernel.length kernel.height * kernel.width != kernel.kernel.length
) { ) {
// must pass in a kernel // must pass in a kernel
throw new Error('Invalid convolution kernel'); throw new Error('Invalid convolution kernel');
} }
if(!isDefined(kernel.scale)) { // Default scale is sum of kernel values
var sum = 0; if (!isInteger(kernel.scale)) {
kernel.kernel.forEach(function(e) { kernel.scale = kernel.kernel.reduce(function(a, b) {
sum += e; return a + b;
}); }, 0);
kernel.scale = sum;
} }
if(!isDefined(kernel.offset)) { // Clamp scale to a minimum value of 1
if (kernel.scale < 1) {
kernel.scale = 1;
}
if (!isInteger(kernel.offset)) {
kernel.offset = 0; kernel.offset = 0;
} }
this.options.convKernel = kernel; this.options.convKernel = kernel;
@@ -645,6 +649,18 @@ Sharp.prototype.greyscale = function(greyscale) {
}; };
Sharp.prototype.grayscale = Sharp.prototype.greyscale; Sharp.prototype.grayscale = Sharp.prototype.greyscale;
/*
Set output colourspace
*/
Sharp.prototype.toColourspace = function(colourspace) {
if (!isString(colourspace) ) {
throw new Error('Invalid output colourspace ' + colourspace);
}
this.options.colourspace = colourspace;
return this;
};
Sharp.prototype.toColorspace = Sharp.prototype.toColourspace;
Sharp.prototype.progressive = function(progressive) { Sharp.prototype.progressive = function(progressive) {
this.options.progressive = isBoolean(progressive) ? progressive : true; this.options.progressive = isBoolean(progressive) ? progressive : true;
return this; return this;
@@ -833,6 +849,15 @@ module.exports.bool = {
or: 'or', or: 'or',
eor: 'eor' eor: 'eor'
}; };
// Colourspaces
module.exports.colourspace = {
multiband: 'multiband',
'b-w': 'b-w',
bw: 'b-w',
cmyk: 'cmyk',
srgb: 'srgb'
};
module.exports.colorspace = module.exports.colourspace;
/* /*
Resize image to width x height pixels Resize image to width x height pixels
@@ -878,13 +903,6 @@ Sharp.prototype.resize = function(width, height, options) {
} }
return this; return this;
}; };
Sharp.prototype.interpolateWith = util.deprecate(function(interpolator) {
return this.resize(
this.options.width > 0 ? this.options.width : null,
this.options.height > 0 ? this.options.height : null,
{ interpolator: interpolator }
);
}, 'interpolateWith: Please use resize(w, h, { interpolator: ... }) instead');
/* /*
Limit the total number of pixels for input images Limit the total number of pixels for input images
@@ -918,7 +936,7 @@ Sharp.prototype.toFile = function(fileOut, callback) {
return BluebirdPromise.reject(errOutputInvalid); return BluebirdPromise.reject(errOutputInvalid);
} }
} else { } else {
if (this.options.fileIn === fileOut) { if (this.options.input.file === fileOut) {
var errOutputIsInput = new Error('Cannot use same file for input and output'); var errOutputIsInput = new Error('Cannot use same file for input and output');
if (typeof callback === 'function') { if (typeof callback === 'function') {
callback(errOutputIsInput); callback(errOutputIsInput);
@@ -1008,7 +1026,7 @@ Sharp.prototype._pipeline = function(callback) {
var that = this; var that = this;
if (typeof callback === 'function') { if (typeof callback === 'function') {
// output=file/buffer // output=file/buffer
if (this.options.streamIn) { if (this._isStreamInput()) {
// output=file/buffer, input=stream // output=file/buffer, input=stream
this.on('finish', function() { this.on('finish', function() {
that._flattenBufferIn(); that._flattenBufferIn();
@@ -1021,7 +1039,7 @@ Sharp.prototype._pipeline = function(callback) {
return this; return this;
} else if (this.options.streamOut) { } else if (this.options.streamOut) {
// output=stream // output=stream
if (this.options.streamIn) { if (this._isStreamInput()) {
// output=stream, input=stream // output=stream, input=stream
this.on('finish', function() { this.on('finish', function() {
that._flattenBufferIn(); that._flattenBufferIn();
@@ -1050,7 +1068,7 @@ Sharp.prototype._pipeline = function(callback) {
return this; return this;
} else { } else {
// output=promise // output=promise
if (this.options.streamIn) { if (this._isStreamInput()) {
// output=promise, input=stream // output=promise, input=stream
return new BluebirdPromise(function(resolve, reject) { return new BluebirdPromise(function(resolve, reject) {
that.on('finish', function() { that.on('finish', function() {
@@ -1086,7 +1104,7 @@ Sharp.prototype._pipeline = function(callback) {
Sharp.prototype.metadata = function(callback) { Sharp.prototype.metadata = function(callback) {
var that = this; var that = this;
if (typeof callback === 'function') { if (typeof callback === 'function') {
if (this.options.streamIn) { if (this._isStreamInput()) {
this.on('finish', function() { this.on('finish', function() {
that._flattenBufferIn(); that._flattenBufferIn();
sharp.metadata(that.options, callback); sharp.metadata(that.options, callback);
@@ -1096,7 +1114,7 @@ Sharp.prototype.metadata = function(callback) {
} }
return this; return this;
} else { } else {
if (this.options.streamIn) { if (this._isStreamInput()) {
return new BluebirdPromise(function(resolve, reject) { return new BluebirdPromise(function(resolve, reject) {
that.on('finish', function() { that.on('finish', function() {
that._flattenBufferIn(); that._flattenBufferIn();

View File

@@ -1,6 +1,6 @@
{ {
"name": "sharp", "name": "sharp",
"version": "0.15.1", "version": "0.16.1",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"contributors": [ "contributors": [
"Pierre Inglebert <pierre.inglebert@gmail.com>", "Pierre Inglebert <pierre.inglebert@gmail.com>",
@@ -26,7 +26,8 @@
"Chintan Thakkar <lemnisk8@gmail.com>", "Chintan Thakkar <lemnisk8@gmail.com>",
"F. Orlando Galashan <frulo@gmx.de>", "F. Orlando Galashan <frulo@gmx.de>",
"Kleis Auke Wolthuizen <info@kleisauke.nl>", "Kleis Auke Wolthuizen <info@kleisauke.nl>",
"Matt Hirsch <mhirsch@media.mit.edu>" "Matt Hirsch <mhirsch@media.mit.edu>",
"Matthias Thoemmes <thoemmes@gmail.com>"
], ],
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images", "description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images",
"scripts": { "scripts": {
@@ -34,7 +35,7 @@
"test": "VIPS_WARNING=0 node ./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha -- --slow=5000 --timeout=30000 ./test/unit/*.js", "test": "VIPS_WARNING=0 node ./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha -- --slow=5000 --timeout=30000 ./test/unit/*.js",
"test-win": "node ./node_modules/mocha/bin/mocha --slow=5000 --timeout=60000 ./test/unit/*.js", "test-win": "node ./node_modules/mocha/bin/mocha --slow=5000 --timeout=60000 ./test/unit/*.js",
"test-leak": "./test/leak/leak.sh", "test-leak": "./test/leak/leak.sh",
"test-packaging": "./packaging/test.sh", "test-packaging": "./packaging/test-linux-x64.sh",
"test-clean": "rm -rf coverage/ test/fixtures/output.* && npm install && npm test" "test-clean": "rm -rf coverage/ test/fixtures/output.* && npm install && npm test"
}, },
"main": "index.js", "main": "index.js",
@@ -58,29 +59,29 @@
"vips" "vips"
], ],
"dependencies": { "dependencies": {
"bluebird": "^3.4.1", "bluebird": "^3.4.6",
"color": "^0.11.3", "color": "^0.11.3",
"nan": "^2.4.0", "nan": "^2.4.0",
"semver": "^5.2.0", "semver": "^5.3.0",
"request": "^2.73.0", "request": "^2.75.0",
"tar": "^2.2.1" "tar": "^2.2.1"
}, },
"devDependencies": { "devDependencies": {
"async": "^1.5.2", "async": "^2.1.0",
"bufferutil": "^1.2.1", "bufferutil": "^1.2.1",
"coveralls": "^2.11.9", "coveralls": "^2.11.14",
"exif-reader": "^1.0.0", "exif-reader": "^1.0.1",
"icc": "^0.0.2", "icc": "^0.0.2",
"istanbul": "^0.4.4", "istanbul": "^0.4.5",
"mocha": "^2.5.3", "mocha": "^3.1.2",
"mocha-jshint": "^2.3.1", "mocha-jshint": "^2.3.1",
"node-cpplint": "^0.4.0", "node-cpplint": "^0.4.0",
"rimraf": "^2.5.3", "rimraf": "^2.5.4",
"unzip": "^0.1.11" "unzip": "^0.1.11"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"config": { "config": {
"libvips": "8.3.1" "libvips": "8.3.3"
}, },
"engines": { "engines": {
"node": ">=0.10" "node": ">=0.10"

57
packaging/README.md Normal file
View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
#!/bin/sh #!/bin/sh
set -e
# To be run inside a Raspbian container
# Working directories # Working directories
DEPS=/deps DEPS=/deps
@@ -13,33 +12,32 @@ export PKG_CONFIG_PATH="${PKG_CONFIG_PATH}:${TARGET}/lib/pkgconfig"
export PATH="${PATH}:${TARGET}/bin" export PATH="${PATH}:${TARGET}/bin"
export CPPFLAGS="-I${TARGET}/include" export CPPFLAGS="-I${TARGET}/include"
export LDFLAGS="-L${TARGET}/lib" export LDFLAGS="-L${TARGET}/lib"
export CFLAGS="-O3" export CFLAGS="${FLAGS}"
export CXXFLAGS="-O3" export CXXFLAGS="${FLAGS}"
# Dependency version numbers # Dependency version numbers
VERSION_ZLIB=1.2.8 VERSION_ZLIB=1.2.8
VERSION_FFI=3.2.1 VERSION_FFI=3.2.1
VERSION_GLIB=2.48.0 VERSION_GLIB=2.49.4
VERSION_XML2=2.9.3 VERSION_XML2=2.9.4
VERSION_GSF=1.14.36 VERSION_GSF=1.14.39
VERSION_EXIF=0.6.21 VERSION_EXIF=0.6.21
VERSION_LCMS2=2.7 VERSION_LCMS2=2.8
VERSION_JPEG=1.4.90 VERSION_JPEG=1.5.0
VERSION_PNG16=1.6.21 VERSION_PNG16=1.6.25
VERSION_WEBP=0.5.0 VERSION_WEBP=0.5.1
VERSION_TIFF=4.0.6 VERSION_TIFF=4.0.6
VERSION_ORC=0.4.25 VERSION_ORC=0.4.25
VERSION_GDKPIXBUF=2.34.0 VERSION_GDKPIXBUF=2.35.2
VERSION_FREETYPE=2.6.3 VERSION_FREETYPE=2.6.5
VERSION_FONTCONFIG=2.11.95 VERSION_FONTCONFIG=2.12.0
VERSION_HARFBUZZ=1.2.6 VERSION_HARFBUZZ=1.3.0
VERSION_PIXMAN=0.34.0 VERSION_PIXMAN=0.34.0
VERSION_CAIRO=1.14.6 VERSION_CAIRO=1.14.6
VERSION_PANGO=1.40.1 VERSION_PANGO=1.40.1
VERSION_CROCO=0.6.11 VERSION_CROCO=0.6.11
VERSION_SVG=2.40.15 VERSION_SVG=2.40.16
VERSION_GIF=5.1.4 VERSION_GIF=5.1.4
VERSION_VIPS=8.3.1
# Least out-of-sync Sourceforge mirror # Least out-of-sync Sourceforge mirror
SOURCEFORGE_MIRROR=netix SOURCEFORGE_MIRROR=netix
@@ -47,131 +45,157 @@ SOURCEFORGE_MIRROR=netix
mkdir ${DEPS}/zlib mkdir ${DEPS}/zlib
curl -Ls http://zlib.net/zlib-${VERSION_ZLIB}.tar.xz | tar xJC ${DEPS}/zlib --strip-components=1 curl -Ls http://zlib.net/zlib-${VERSION_ZLIB}.tar.xz | tar xJC ${DEPS}/zlib --strip-components=1
cd ${DEPS}/zlib cd ${DEPS}/zlib
./configure --prefix=${TARGET} && make install ./configure --prefix=${TARGET} --uname=linux
make install
rm ${TARGET}/lib/libz.a rm ${TARGET}/lib/libz.a
mkdir ${DEPS}/ffi mkdir ${DEPS}/ffi
curl -Ls ftp://sourceware.org/pub/libffi/libffi-${VERSION_FFI}.tar.gz | tar xzC ${DEPS}/ffi --strip-components=1 curl -Ls ftp://sourceware.org/pub/libffi/libffi-${VERSION_FFI}.tar.gz | tar xzC ${DEPS}/ffi --strip-components=1
cd ${DEPS}/ffi cd ${DEPS}/ffi
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-builddir && make install-strip ./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-builddir
make install-strip
mkdir ${DEPS}/glib mkdir ${DEPS}/glib
curl -Ls https://download.gnome.org/sources/glib/2.48/glib-${VERSION_GLIB}.tar.xz | tar xJC ${DEPS}/glib --strip-components=1 curl -Ls https://download.gnome.org/sources/glib/2.49/glib-${VERSION_GLIB}.tar.xz | tar xJC ${DEPS}/glib --strip-components=1
cd ${DEPS}/glib cd ${DEPS}/glib
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --with-pcre=internal && make install-strip echo glib_cv_stack_grows=no >>glib.cache
echo glib_cv_uscore=no >>glib.cache
./configure --cache-file=glib.cache --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --with-pcre=internal
make install-strip
mkdir ${DEPS}/xml2 mkdir ${DEPS}/xml2
curl -Ls http://xmlsoft.org/sources/libxml2-${VERSION_XML2}.tar.gz | tar xzC ${DEPS}/xml2 --strip-components=1 curl -Ls http://xmlsoft.org/sources/libxml2-${VERSION_XML2}.tar.gz | tar xzC ${DEPS}/xml2 --strip-components=1
cd ${DEPS}/xml2 cd ${DEPS}/xml2
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --without-python --with-zlib=${TARGET} && make install-strip ./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--without-python --without-debug --without-docbook --without-ftp --without-html --without-legacy \
--without-pattern --without-push --without-regexps --without-schemas --without-schematron --with-zlib=${TARGET}
make install-strip
mkdir ${DEPS}/gsf mkdir ${DEPS}/gsf
curl -Ls https://download.gnome.org/sources/libgsf/1.14/libgsf-${VERSION_GSF}.tar.xz | tar xJC ${DEPS}/gsf --strip-components=1 curl -Ls https://download.gnome.org/sources/libgsf/1.14/libgsf-${VERSION_GSF}.tar.xz | tar xJC ${DEPS}/gsf --strip-components=1
cd ${DEPS}/gsf cd ${DEPS}/gsf
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip ./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking
make install-strip
mkdir ${DEPS}/exif mkdir ${DEPS}/exif
curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/libexif/libexif/${VERSION_EXIF}/libexif-${VERSION_EXIF}.tar.bz2 | tar xjC ${DEPS}/exif --strip-components=1 curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/libexif/libexif/${VERSION_EXIF}/libexif-${VERSION_EXIF}.tar.bz2 | tar xjC ${DEPS}/exif --strip-components=1
cd ${DEPS}/exif cd ${DEPS}/exif
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip autoreconf -fiv
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking
make install-strip
mkdir ${DEPS}/lcms2 mkdir ${DEPS}/lcms2
curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/lcms/lcms/${VERSION_LCMS2}/lcms2-${VERSION_LCMS2}.tar.gz | tar xzC ${DEPS}/lcms2 --strip-components=1 curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/lcms/lcms/${VERSION_LCMS2}/lcms2-${VERSION_LCMS2}.tar.gz | tar xzC ${DEPS}/lcms2 --strip-components=1
cd ${DEPS}/lcms2 cd ${DEPS}/lcms2
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip ./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking
make install-strip
mkdir ${DEPS}/jpeg mkdir ${DEPS}/jpeg
curl -Ls https://github.com/libjpeg-turbo/libjpeg-turbo/archive/${VERSION_JPEG}.tar.gz | tar xzC ${DEPS}/jpeg --strip-components=1 curl -Ls https://github.com/libjpeg-turbo/libjpeg-turbo/archive/${VERSION_JPEG}.tar.gz | tar xzC ${DEPS}/jpeg --strip-components=1
cd ${DEPS}/jpeg cd ${DEPS}/jpeg
autoreconf -fiv && ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --with-jpeg8 --without-turbojpeg && make install-strip autoreconf -fiv
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --with-jpeg8 --without-turbojpeg
make install-strip
mkdir ${DEPS}/png16 mkdir ${DEPS}/png16
curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/libpng/libpng16/${VERSION_PNG16}/libpng-${VERSION_PNG16}.tar.xz | tar xJC ${DEPS}/png16 --strip-components=1 curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/libpng/libpng16/${VERSION_PNG16}/libpng-${VERSION_PNG16}.tar.xz | tar xJC ${DEPS}/png16 --strip-components=1
cd ${DEPS}/png16 cd ${DEPS}/png16
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip ./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking
make install-strip
mkdir ${DEPS}/webp mkdir ${DEPS}/webp
curl -Ls http://downloads.webmproject.org/releases/webp/libwebp-${VERSION_WEBP}.tar.gz | tar xzC ${DEPS}/webp --strip-components=1 curl -Ls http://downloads.webmproject.org/releases/webp/libwebp-${VERSION_WEBP}.tar.gz | tar xzC ${DEPS}/webp --strip-components=1
cd ${DEPS}/webp cd ${DEPS}/webp
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip ./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-neon
make install-strip
mkdir ${DEPS}/tiff mkdir ${DEPS}/tiff
curl -Ls http://download.osgeo.org/libtiff/tiff-${VERSION_TIFF}.tar.gz | tar xzC ${DEPS}/tiff --strip-components=1 curl -Ls http://download.osgeo.org/libtiff/tiff-${VERSION_TIFF}.tar.gz | tar xzC ${DEPS}/tiff --strip-components=1
cd ${DEPS}/tiff cd ${DEPS}/tiff
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip if [ -n "${CHOST}" ]; then autoreconf -fiv; fi
rm ${TARGET}/lib/libtiffxx* ./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-mdi --disable-cxx
make install-strip
mkdir ${DEPS}/orc mkdir ${DEPS}/orc
curl -Ls http://gstreamer.freedesktop.org/data/src/orc/orc-${VERSION_ORC}.tar.xz | tar xJC ${DEPS}/orc --strip-components=1 curl -Ls http://gstreamer.freedesktop.org/data/src/orc/orc-${VERSION_ORC}.tar.xz | tar xJC ${DEPS}/orc --strip-components=1
cd ${DEPS}/orc cd ${DEPS}/orc
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip ./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking
make install-strip
cd ${TARGET}/lib
rm -rf liborc-test-*
mkdir ${DEPS}/gdkpixbuf mkdir ${DEPS}/gdkpixbuf
curl -Ls https://download.gnome.org/sources/gdk-pixbuf/2.34/gdk-pixbuf-${VERSION_GDKPIXBUF}.tar.xz | tar xJC ${DEPS}/gdkpixbuf --strip-components=1 curl -Ls https://download.gnome.org/sources/gdk-pixbuf/2.35/gdk-pixbuf-${VERSION_GDKPIXBUF}.tar.xz | tar xJC ${DEPS}/gdkpixbuf --strip-components=1
cd ${DEPS}/gdkpixbuf cd ${DEPS}/gdkpixbuf
LD_LIBRARY_PATH=${TARGET}/lib ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \ LD_LIBRARY_PATH=${TARGET}/lib \
--disable-introspection --disable-modules --without-libpng --without-libjpeg --without-libtiff --without-gdiplus --with-included-loaders= \ ./configure --cache-file=gdkpixbuf.cache --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-introspection --disable-modules --disable-gio-sniffing --without-libpng --without-libjpeg --without-libtiff --without-gdiplus --with-included-loaders=
&& make install-strip make install-strip
mkdir ${DEPS}/freetype mkdir ${DEPS}/freetype
curl -Ls http://download.savannah.gnu.org/releases/freetype/freetype-${VERSION_FREETYPE}.tar.gz | tar xzC ${DEPS}/freetype --strip-components=1 curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/freetype/freetype2/${VERSION_FREETYPE}/freetype-${VERSION_FREETYPE}.tar.gz | tar xzC ${DEPS}/freetype --strip-components=1
cd ${DEPS}/freetype cd ${DEPS}/freetype
./configure --prefix=${TARGET} --enable-shared --disable-static && make install ./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static
make install
mkdir ${DEPS}/fontconfig mkdir ${DEPS}/fontconfig
curl -Ls https://www.freedesktop.org/software/fontconfig/release/fontconfig-${VERSION_FONTCONFIG}.tar.bz2 | tar xjC ${DEPS}/fontconfig --strip-components=1 curl -Ls https://www.freedesktop.org/software/fontconfig/release/fontconfig-${VERSION_FONTCONFIG}.tar.bz2 | tar xjC ${DEPS}/fontconfig --strip-components=1
cd ${DEPS}/fontconfig cd ${DEPS}/fontconfig
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --enable-libxml2 && make install-strip ./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --enable-libxml2
make install-strip
mkdir ${DEPS}/harfbuzz mkdir ${DEPS}/harfbuzz
curl -Ls https://www.freedesktop.org/software/harfbuzz/release/harfbuzz-${VERSION_HARFBUZZ}.tar.bz2 | tar xjC ${DEPS}/harfbuzz --strip-components=1 curl -Ls https://www.freedesktop.org/software/harfbuzz/release/harfbuzz-${VERSION_HARFBUZZ}.tar.bz2 | tar xjC ${DEPS}/harfbuzz --strip-components=1
cd ${DEPS}/harfbuzz cd ${DEPS}/harfbuzz
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip ./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking
make install-strip
mkdir ${DEPS}/pixman mkdir ${DEPS}/pixman
curl -Ls http://cairographics.org/releases/pixman-${VERSION_PIXMAN}.tar.gz | tar xzC ${DEPS}/pixman --strip-components=1 curl -Ls http://cairographics.org/releases/pixman-${VERSION_PIXMAN}.tar.gz | tar xzC ${DEPS}/pixman --strip-components=1
cd ${DEPS}/pixman cd ${DEPS}/pixman
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-libpng --disable-arm-iwmmxt && make install-strip ./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-libpng --disable-arm-iwmmxt
make install-strip
mkdir ${DEPS}/cairo mkdir ${DEPS}/cairo
curl -Ls http://cairographics.org/releases/cairo-${VERSION_CAIRO}.tar.xz | tar xJC ${DEPS}/cairo --strip-components=1 curl -Ls http://cairographics.org/releases/cairo-${VERSION_CAIRO}.tar.xz | tar xJC ${DEPS}/cairo --strip-components=1
cd ${DEPS}/cairo cd ${DEPS}/cairo
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \ ./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--disable-xlib --disable-xcb --disable-quartz --disable-win32 --disable-egl --disable-glx --disable-wgl \ --disable-xlib --disable-xcb --disable-quartz --disable-win32 --disable-egl --disable-glx --disable-wgl \
--disable-script --disable-ps --disable-gobject --disable-trace --disable-interpreter \ --disable-script --disable-ps --disable-gobject --disable-trace --disable-interpreter
&& make install-strip make install-strip
mkdir ${DEPS}/pango mkdir ${DEPS}/pango
curl -Ls https://download.gnome.org/sources/pango/1.40/pango-${VERSION_PANGO}.tar.xz | tar xJC ${DEPS}/pango --strip-components=1 curl -Ls https://download.gnome.org/sources/pango/1.40/pango-${VERSION_PANGO}.tar.xz | tar xJC ${DEPS}/pango --strip-components=1
cd ${DEPS}/pango cd ${DEPS}/pango
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip ./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking
make install-strip
mkdir ${DEPS}/croco mkdir ${DEPS}/croco
curl -Ls https://download.gnome.org/sources/libcroco/0.6/libcroco-${VERSION_CROCO}.tar.xz | tar xJC ${DEPS}/croco --strip-components=1 curl -Ls https://download.gnome.org/sources/libcroco/0.6/libcroco-${VERSION_CROCO}.tar.xz | tar xJC ${DEPS}/croco --strip-components=1
cd ${DEPS}/croco cd ${DEPS}/croco
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip ./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking
make install-strip
mkdir ${DEPS}/svg mkdir ${DEPS}/svg
curl -Ls https://download.gnome.org/sources/librsvg/2.40/librsvg-${VERSION_SVG}.tar.xz | tar xJC ${DEPS}/svg --strip-components=1 curl -Ls https://download.gnome.org/sources/librsvg/2.40/librsvg-${VERSION_SVG}.tar.xz | tar xJC ${DEPS}/svg --strip-components=1
cd ${DEPS}/svg cd ${DEPS}/svg
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \ ./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-introspection --disable-tools --disable-pixbuf-loader
--disable-introspection --disable-tools \ make install-strip
&& make install-strip
mkdir ${DEPS}/gif mkdir ${DEPS}/gif
curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/giflib/giflib-${VERSION_GIF}.tar.gz | tar xzC ${DEPS}/gif --strip-components=1 curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/giflib/giflib-${VERSION_GIF}.tar.gz | tar xzC ${DEPS}/gif --strip-components=1
cd ${DEPS}/gif cd ${DEPS}/gif
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip ./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking
make install-strip
mkdir ${DEPS}/vips mkdir ${DEPS}/vips
curl -Ls http://www.vips.ecs.soton.ac.uk/supported/8.3/vips-${VERSION_VIPS}.tar.gz | tar xzC ${DEPS}/vips --strip-components=1 curl -Ls http://www.vips.ecs.soton.ac.uk/supported/8.3/vips-${VERSION_VIPS}.tar.gz | tar xzC ${DEPS}/vips --strip-components=1
cd ${DEPS}/vips cd ${DEPS}/vips
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \ ./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--disable-debug --disable-introspection --without-python --without-fftw \ --disable-debug --disable-introspection --without-python --without-fftw \
--without-magick --without-pangoft2 --without-ppm --without-analyze --without-radiance \ --without-magick --without-pangoft2 --without-ppm --without-analyze --without-radiance \
--with-zip-includes=${TARGET}/include --with-zip-libraries=${TARGET}/lib \ --with-zip-includes=${TARGET}/include --with-zip-libraries=${TARGET}/lib \
--with-jpeg-includes=${TARGET}/include --with-jpeg-libraries=${TARGET}/lib \ --with-jpeg-includes=${TARGET}/include --with-jpeg-libraries=${TARGET}/lib
&& make install-strip make install-strip
# Remove the old C++ bindings # Remove the old C++ bindings
cd ${TARGET}/include cd ${TARGET}/include
@@ -208,4 +232,5 @@ echo "{\n\
}" >lib/versions.json }" >lib/versions.json
# Create .tar.gz # Create .tar.gz
GZIP=-9 tar czf /arm/libvips-${VERSION_VIPS}-arm.tar.gz include lib tar czf /packaging/libvips-${VERSION_VIPS}-${PLATFORM}.tar.gz include lib
advdef --recompress --shrink-insane /packaging/libvips-${VERSION_VIPS}-${PLATFORM}.tar.gz

19
packaging/build/win.sh Executable file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,14 @@
FROM debian:wheezy
MAINTAINER Lovell Fuller <npm@lovell.info>
# Create Debian-based container suitable for building Linux x64 binaries
# Build dependencies
RUN \
apt-get update && \
apt-get install -y build-essential autoconf libtool nasm gtk-doc-tools texinfo advancecomp
# Compiler settings
ENV \
PLATFORM="linux-x64" \
FLAGS="-O3"

View File

@@ -22,7 +22,7 @@ fi
export SSHPASS=hypriot export SSHPASS=hypriot
echo "Copying sharp source to device" echo "Copying sharp source to device"
sshpass -e scp -o PreferredAuthentications=password -r ../../sharp root@${IP}:/root/sharp sshpass -e scp -o PreferredAuthentications=password -r ../../sharp pirate@${IP}:/home/pirate/sharp
echo "Compile and test within container" echo "Compile and test within container"
sshpass -e ssh -o PreferredAuthentications=password -t root@${IP} "docker run -i -t --rm -v \${PWD}/sharp:/s hypriot/rpi-node:5 sh -c 'cd /s && npm install --unsafe-perm && npm test'" sshpass -e ssh -o PreferredAuthentications=password -t pirate@${IP} "docker run --rm -v \${PWD}/sharp:/s hypriot/rpi-node:6 sh -c 'cd /s && npm install --unsafe-perm && npm test'"

View File

@@ -6,7 +6,7 @@ if ! type docker >/dev/null; then
exit 1 exit 1
fi fi
version_node=4.4.2 version_node=6.3.0
test="npm run clean; npm install --unsafe-perm; npm test" test="npm run clean; npm install --unsafe-perm; npm test"
@@ -23,15 +23,15 @@ done
# Centos 7 # Centos 7
echo "Testing centos7..." echo "Testing centos7..."
if docker run -i -t --rm -v $PWD:/v -e "NODE_ENV=development" nodesource/centos7:$version_node >packaging/$dist.log 2>&1 sh -c "cd /v; $test"; if docker run -i -t --rm -v $PWD:/v -e "NODE_ENV=development" nodesource/centos7:$version_node >packaging/$dist.log 2>&1 sh -c "cd /v; $test";
then echo "$dist OK" then echo "centos7 OK"
else echo "$dist fail" && cat packaging/$dist.log else echo "centos7 fail" && cat packaging/$dist.log
fi fi
# Fedora 22 # Fedora 22
echo "Testing fedora22..." echo "Testing fedora22..."
if docker run -i -t --rm -v $PWD:/v -e "NODE_ENV=development" nodesource/fedora22:$version_node >packaging/$dist.log 2>&1 sh -c "cd /v; $test"; if docker run -i -t --rm -v $PWD:/v -e "NODE_ENV=development" nodesource/fedora22:$version_node >packaging/$dist.log 2>&1 sh -c "cd /v; $test";
then echo "$dist OK" then echo "fedora22 OK"
else echo "$dist fail" && cat packaging/$dist.log else echo "fedora22 fail" && cat packaging/$dist.log
fi fi
# openSUSE 13.2 # openSUSE 13.2
@@ -43,7 +43,7 @@ fi
# Archlinux 2015.06.01 # Archlinux 2015.06.01
echo "Testing archlinux..." 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"; if docker run -i -t --rm -v $PWD:/v pritunl/archlinux:latest >packaging/archlinux.log 2>&1 sh -c "cd /v; ./packaging/test/archlinux.sh; $test";
then echo "archlinux OK" then echo "archlinux OK"
else echo "archlinux fail" && cat packaging/archlinux.log else echo "archlinux fail" && cat packaging/archlinux.log
fi fi

View File

@@ -3,5 +3,5 @@
# Install build dependencies # Install build dependencies
apk add --update make gcc g++ python nodejs apk add --update make gcc g++ python nodejs
# Install libvips with build headers and dependencies # Install libvips from aports/testing
apk add libvips-dev --update --repository https://s3.amazonaws.com/wjordan-apk --allow-untrusted apk add --update --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing vips-dev

View File

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

View File

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

View File

@@ -2,6 +2,8 @@
# Use of this script is deprecated # Use of this script is deprecated
echo
echo "WARNING: This script will stop working at the end of 2016"
echo echo
echo "WARNING: This script is no longer required on most 64-bit Linux systems when using sharp v0.12.0+" echo "WARNING: This script is no longer required on most 64-bit Linux systems when using sharp v0.12.0+"
echo echo
@@ -10,10 +12,11 @@ echo
echo "If you really, really need this script, it will attempt" echo "If you really, really need this script, it will attempt"
echo "to globally install libvips if not already available." echo "to globally install libvips if not already available."
echo echo
sleep 5
vips_version_minimum=8.3.1 vips_version_minimum=8.3.3
vips_version_latest_major_minor=8.3 vips_version_latest_major_minor=8.3
vips_version_latest_patch=1 vips_version_latest_patch=3
openslide_version_minimum=3.4.0 openslide_version_minimum=3.4.0
openslide_version_latest_major_minor=3.4 openslide_version_latest_major_minor=3.4

View File

@@ -1,32 +1,53 @@
#include <cstdlib> #include <cstdlib>
#include <string> #include <string>
#include <string.h> #include <string.h>
#include <node.h>
#include <node_buffer.h>
#include <vips/vips8> #include <vips/vips8>
#include "nan.h"
#include "common.h" #include "common.h"
// Verify platform and compiler compatibility
#if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 2))
#error libvips version 8.2.0+ required - see sharp.dimens.io/page/install
#endif
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
#error GCC version 4.6+ is required for C++11 features - see sharp.dimens.io/page/install#prerequisites
#endif
#if (defined(__clang__) && defined(__has_feature))
#if (!__has_feature(cxx_range_for))
#error clang version 3.0+ is required for C++11 features - see sharp.dimens.io/page/install#prerequisites
#endif
#endif
#define EXIF_IFD0_ORIENTATION "exif-ifd0-Orientation"
using vips::VImage; using vips::VImage;
namespace sharp { namespace sharp {
// Convenience methods to access the attributes of a v8::Object
bool HasAttr(v8::Handle<v8::Object> obj, std::string attr) {
return Nan::Has(obj, Nan::New(attr).ToLocalChecked()).FromJust();
}
std::string AttrAsStr(v8::Handle<v8::Object> obj, std::string attr) {
return *Nan::Utf8String(Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked());
}
// Create an InputDescriptor instance from a v8::Object describing an input image
InputDescriptor* CreateInputDescriptor(
v8::Handle<v8::Object> input, std::vector<v8::Local<v8::Object>> buffersToPersist
) {
Nan::HandleScope();
InputDescriptor *descriptor = new InputDescriptor;
if (HasAttr(input, "file")) {
descriptor->file = AttrAsStr(input, "file");
} else {
v8::Local<v8::Object> buffer = AttrAs<v8::Object>(input, "buffer");
descriptor->bufferLength = node::Buffer::Length(buffer);
descriptor->buffer = node::Buffer::Data(buffer);
buffersToPersist.push_back(buffer);
}
// Density for vector-based input
if (HasAttr(input, "density")) {
descriptor->density = AttrTo<uint32_t>(input, "density");
}
// Raw pixel input
if (HasAttr(input, "rawChannels")) {
descriptor->rawChannels = AttrTo<uint32_t>(input, "rawChannels");
descriptor->rawWidth = AttrTo<uint32_t>(input, "rawWidth");
descriptor->rawHeight = AttrTo<uint32_t>(input, "rawHeight");
}
return descriptor;
}
// How many tasks are in the queue? // How many tasks are in the queue?
volatile int counterQueue = 0; volatile int counterQueue = 0;
@@ -149,6 +170,73 @@ namespace sharp {
return imageType; return imageType;
} }
/*
Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data)
*/
std::tuple<VImage, ImageType> OpenInput(InputDescriptor *descriptor, VipsAccess accessMethod) {
VImage image;
ImageType imageType;
if (descriptor->buffer != nullptr) {
// From buffer
if (descriptor->rawChannels > 0) {
// Raw, uncompressed pixel data
image = VImage::new_from_memory(descriptor->buffer, descriptor->bufferLength,
descriptor->rawWidth, descriptor->rawHeight, descriptor->rawChannels, VIPS_FORMAT_UCHAR);
if (descriptor->rawChannels < 3) {
image.get_image()->Type = VIPS_INTERPRETATION_B_W;
} else {
image.get_image()->Type = VIPS_INTERPRETATION_sRGB;
}
imageType = ImageType::RAW;
} else {
// Compressed data
imageType = DetermineImageType(descriptor->buffer, descriptor->bufferLength);
if (imageType != ImageType::UNKNOWN) {
try {
vips::VOption *option = VImage::option()->set("access", accessMethod);
if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
option->set("dpi", static_cast<double>(descriptor->density));
}
if (imageType == ImageType::MAGICK) {
option->set("density", std::to_string(descriptor->density).data());
}
image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option);
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
SetDensity(image, descriptor->density);
}
} catch (...) {
throw vips::VError("Input buffer has corrupt header");
}
} else {
throw vips::VError("Input buffer contains unsupported image format");
}
}
} else {
// From filesystem
imageType = DetermineImageType(descriptor->file.data());
if (imageType != ImageType::UNKNOWN) {
try {
vips::VOption *option = VImage::option()->set("access", accessMethod);
if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
option->set("dpi", static_cast<double>(descriptor->density));
}
if (imageType == ImageType::MAGICK) {
option->set("density", std::to_string(descriptor->density).data());
}
image = VImage::new_from_file(descriptor->file.data(), option);
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
SetDensity(image, descriptor->density);
}
} catch (...) {
throw vips::VError("Input file has corrupt header");
}
} else {
throw vips::VError("Input file is missing or of an unsupported image format");
}
}
return std::make_tuple(image, imageType);
}
/* /*
Does this image have an embedded profile? Does this image have an embedded profile?
*/ */
@@ -303,7 +391,7 @@ namespace sharp {
if(y >= 0 && y < (inHeight - outHeight)) { if(y >= 0 && y < (inHeight - outHeight)) {
top = y; top = y;
} else if(x >= (inHeight - outHeight)) { } else if(y >= (inHeight - outHeight)) {
top = inHeight - outHeight; top = inHeight - outHeight;
} }
@@ -342,4 +430,13 @@ namespace sharp {
); );
} }
/*
Get interpretation type from string
*/
VipsInterpretation GetInterpretation(std::string const typeStr) {
return static_cast<VipsInterpretation>(
vips_enum_from_nick(nullptr, VIPS_TYPE_INTERPRETATION, typeStr.data())
);
}
} // namespace sharp } // namespace sharp

View File

@@ -4,12 +4,70 @@
#include <string> #include <string>
#include <tuple> #include <tuple>
#include <node.h>
#include <vips/vips8> #include <vips/vips8>
#include "nan.h"
// Verify platform and compiler compatibility
#if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 3))
#error libvips version 8.3.x required - see sharp.dimens.io/page/install
#endif
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
#error GCC version 4.6+ is required for C++11 features - see sharp.dimens.io/page/install#prerequisites
#endif
#if (defined(__clang__) && defined(__has_feature))
#if (!__has_feature(cxx_range_for))
#error clang version 3.0+ is required for C++11 features - see sharp.dimens.io/page/install#prerequisites
#endif
#endif
#define EXIF_IFD0_ORIENTATION "exif-ifd0-Orientation"
using vips::VImage; using vips::VImage;
namespace sharp { namespace sharp {
struct InputDescriptor {
std::string name;
std::string file;
char *buffer;
size_t bufferLength;
int density;
int rawChannels;
int rawWidth;
int rawHeight;
InputDescriptor():
buffer(nullptr),
bufferLength(0),
density(72),
rawChannels(0),
rawWidth(0),
rawHeight(0) {}
};
// Convenience methods to access the attributes of a v8::Object
bool HasAttr(v8::Handle<v8::Object> obj, std::string attr);
std::string AttrAsStr(v8::Handle<v8::Object> obj, std::string attr);
template<typename T> v8::Local<T> AttrAs(v8::Handle<v8::Object> obj, std::string attr) {
return Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked().As<T>();
}
template<typename T> T AttrTo(v8::Handle<v8::Object> obj, std::string attr) {
return Nan::To<T>(Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked()).FromJust();
}
template<typename T> T AttrTo(v8::Handle<v8::Object> obj, int attr) {
return Nan::To<T>(Nan::Get(obj, attr).ToLocalChecked()).FromJust();
}
// Create an InputDescriptor instance from a v8::Object describing an input image
InputDescriptor* CreateInputDescriptor(
v8::Handle<v8::Object> input, std::vector<v8::Local<v8::Object>> buffersToPersist
);
enum class ImageType { enum class ImageType {
JPEG, JPEG,
PNG, PNG,
@@ -57,6 +115,11 @@ namespace sharp {
*/ */
ImageType DetermineImageType(char const *file); ImageType DetermineImageType(char const *file);
/*
Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data)
*/
std::tuple<VImage, ImageType> OpenInput(InputDescriptor *descriptor, VipsAccess accessMethod);
/* /*
Does this image have an embedded profile? Does this image have an embedded profile?
*/ */
@@ -133,6 +196,11 @@ namespace sharp {
*/ */
VipsOperationBoolean GetBooleanOperation(std::string const opStr); VipsOperationBoolean GetBooleanOperation(std::string const opStr);
/*
Get interpretation type from string
*/
VipsInterpretation GetInterpretation(std::string const typeStr);
} // namespace sharp } // namespace sharp
#endif // SRC_COMMON_H_ #endif // SRC_COMMON_H_

View File

@@ -1,135 +1,54 @@
#include <numeric>
#include <node.h> #include <node.h>
#include <vips/vips8> #include <vips/vips8>
#include "nan.h" #include "nan.h"
#include "common.h" #include "common.h"
#include "metadata.h" #include "metadata.h"
using v8::Handle; class MetadataWorker : public Nan::AsyncWorker {
using v8::Local;
using v8::Value;
using v8::Object;
using v8::Number;
using v8::String;
using v8::Boolean;
using v8::Function;
using v8::Exception;
using Nan::AsyncQueueWorker;
using Nan::AsyncWorker;
using Nan::Callback;
using Nan::HandleScope;
using Nan::Utf8String;
using Nan::Has;
using Nan::Get;
using Nan::Set;
using Nan::New;
using Nan::NewBuffer;
using Nan::Null;
using Nan::Error;
using vips::VImage;
using vips::VError;
using sharp::ImageType;
using sharp::ImageTypeId;
using sharp::DetermineImageType;
using sharp::HasProfile;
using sharp::HasAlpha;
using sharp::ExifOrientation;
using sharp::HasDensity;
using sharp::GetDensity;
using sharp::FreeCallback;
using sharp::counterQueue;
struct MetadataBaton {
// Input
std::string fileIn;
char *bufferIn;
size_t bufferInLength;
// Output
std::string format;
int width;
int height;
std::string space;
int channels;
int density;
bool hasProfile;
bool hasAlpha;
int orientation;
char *exif;
size_t exifLength;
char *icc;
size_t iccLength;
std::string err;
MetadataBaton():
bufferInLength(0),
density(0),
orientation(0),
exifLength(0),
iccLength(0) {}
};
class MetadataWorker : public AsyncWorker {
public: public:
MetadataWorker(Callback *callback, MetadataBaton *baton, const Local<Object> &bufferIn) : MetadataWorker(
AsyncWorker(callback), baton(baton) { Nan::Callback *callback, MetadataBaton *baton,
if (baton->bufferInLength > 0) { std::vector<v8::Local<v8::Object>> const buffersToPersist
SaveToPersistent("bufferIn", bufferIn); ) : Nan::AsyncWorker(callback), baton(baton), buffersToPersist(buffersToPersist) {
// Protect Buffer objects from GC, keyed on index
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
SaveToPersistent(index, buffer);
return index + 1;
} }
);
} }
~MetadataWorker() {} ~MetadataWorker() {}
void Execute() { void Execute() {
// Decrement queued task counter // Decrement queued task counter
g_atomic_int_dec_and_test(&counterQueue); g_atomic_int_dec_and_test(&sharp::counterQueue);
ImageType imageType = ImageType::UNKNOWN; vips::VImage image;
VImage image; sharp::ImageType imageType = sharp::ImageType::UNKNOWN;
if (baton->bufferInLength > 0) {
// From buffer
imageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
if (imageType != ImageType::UNKNOWN) {
try { try {
image = VImage::new_from_buffer(baton->bufferIn, baton->bufferInLength, nullptr); std::tie(image, imageType) = OpenInput(baton->input, VIPS_ACCESS_SEQUENTIAL);
} catch (...) { } catch (vips::VError const &err) {
(baton->err).append("Input buffer has corrupt header"); (baton->err).append(err.what());
imageType = ImageType::UNKNOWN;
} }
} else { if (imageType != sharp::ImageType::UNKNOWN) {
(baton->err).append("Input buffer contains unsupported image format");
}
} else {
// From file
imageType = DetermineImageType(baton->fileIn.data());
if (imageType != ImageType::UNKNOWN) {
try {
image = VImage::new_from_file(baton->fileIn.data());
} catch (...) {
(baton->err).append("Input file has corrupt header");
imageType = ImageType::UNKNOWN;
}
} else {
(baton->err).append("Input file is missing or of an unsupported image format");
}
}
if (imageType != ImageType::UNKNOWN) {
// Image type // Image type
baton->format = ImageTypeId(imageType); baton->format = sharp::ImageTypeId(imageType);
// VipsImage attributes // VipsImage attributes
baton->width = image.width(); baton->width = image.width();
baton->height = image.height(); baton->height = image.height();
baton->space = vips_enum_nick(VIPS_TYPE_INTERPRETATION, image.interpretation()); baton->space = vips_enum_nick(VIPS_TYPE_INTERPRETATION, image.interpretation());
baton->channels = image.bands(); baton->channels = image.bands();
if (HasDensity(image)) { if (sharp::HasDensity(image)) {
baton->density = GetDensity(image); baton->density = sharp::GetDensity(image);
} }
baton->hasProfile = HasProfile(image); baton->hasProfile = sharp::HasProfile(image);
// Derived attributes // Derived attributes
baton->hasAlpha = HasAlpha(image); baton->hasAlpha = sharp::HasAlpha(image);
baton->orientation = ExifOrientation(image); baton->orientation = sharp::ExifOrientation(image);
// EXIF // EXIF
if (image.get_typeof(VIPS_META_EXIF_NAME) == VIPS_TYPE_BLOB) { if (image.get_typeof(VIPS_META_EXIF_NAME) == VIPS_TYPE_BLOB) {
size_t exifLength; size_t exifLength;
@@ -147,53 +66,59 @@ class MetadataWorker : public AsyncWorker {
baton->iccLength = iccLength; baton->iccLength = iccLength;
} }
} }
// Clean up // Clean up
vips_error_clear(); vips_error_clear();
vips_thread_shutdown(); vips_thread_shutdown();
} }
void HandleOKCallback () { void HandleOKCallback () {
HandleScope(); using Nan::New;
using Nan::Set;
Nan::HandleScope();
Local<Value> argv[2] = { Null(), Null() }; v8::Local<v8::Value> argv[2] = { Nan::Null(), Nan::Null() };
if (!baton->err.empty()) { if (!baton->err.empty()) {
// Error argv[0] = Nan::Error(baton->err.data());
argv[0] = Error(baton->err.data());
} else { } else {
// Metadata Object // Metadata Object
Local<Object> info = New<Object>(); v8::Local<v8::Object> info = New<v8::Object>();
Set(info, New("format").ToLocalChecked(), New<String>(baton->format).ToLocalChecked()); Set(info, New("format").ToLocalChecked(), New<v8::String>(baton->format).ToLocalChecked());
Set(info, New("width").ToLocalChecked(), New<Number>(baton->width)); Set(info, New("width").ToLocalChecked(), New<v8::Uint32>(baton->width));
Set(info, New("height").ToLocalChecked(), New<Number>(baton->height)); Set(info, New("height").ToLocalChecked(), New<v8::Uint32>(baton->height));
Set(info, New("space").ToLocalChecked(), New<String>(baton->space).ToLocalChecked()); Set(info, New("space").ToLocalChecked(), New<v8::String>(baton->space).ToLocalChecked());
Set(info, New("channels").ToLocalChecked(), New<Number>(baton->channels)); Set(info, New("channels").ToLocalChecked(), New<v8::Uint32>(baton->channels));
if (baton->density > 0) { if (baton->density > 0) {
Set(info, New("density").ToLocalChecked(), New<Number>(baton->density)); Set(info, New("density").ToLocalChecked(), New<v8::Uint32>(baton->density));
} }
Set(info, New("hasProfile").ToLocalChecked(), New<Boolean>(baton->hasProfile)); Set(info, New("hasProfile").ToLocalChecked(), New<v8::Boolean>(baton->hasProfile));
Set(info, New("hasAlpha").ToLocalChecked(), New<Boolean>(baton->hasAlpha)); Set(info, New("hasAlpha").ToLocalChecked(), New<v8::Boolean>(baton->hasAlpha));
if (baton->orientation > 0) { if (baton->orientation > 0) {
Set(info, New("orientation").ToLocalChecked(), New<Number>(baton->orientation)); Set(info, New("orientation").ToLocalChecked(), New<v8::Uint32>(baton->orientation));
} }
if (baton->exifLength > 0) { if (baton->exifLength > 0) {
Set(info, Set(info,
New("exif").ToLocalChecked(), New("exif").ToLocalChecked(),
NewBuffer(baton->exif, baton->exifLength, FreeCallback, nullptr).ToLocalChecked() Nan::NewBuffer(baton->exif, baton->exifLength, sharp::FreeCallback, nullptr).ToLocalChecked()
); );
} }
if (baton->iccLength > 0) { if (baton->iccLength > 0) {
Set(info, Set(info,
New("icc").ToLocalChecked(), New("icc").ToLocalChecked(),
NewBuffer(baton->icc, baton->iccLength, FreeCallback, nullptr).ToLocalChecked() Nan::NewBuffer(baton->icc, baton->iccLength, sharp::FreeCallback, nullptr).ToLocalChecked()
); );
} }
argv[1] = info; argv[1] = info;
} }
// Dispose of Persistent wrapper around input Buffer so it can be garbage collected // Dispose of Persistent wrapper around input Buffers so they can be garbage collected
if (baton->bufferInLength > 0) { std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
GetFromPersistent("bufferIn"); [this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
GetFromPersistent(index);
return index + 1;
} }
);
delete baton->input;
delete baton; delete baton;
// Return to JavaScript // Return to JavaScript
@@ -202,32 +127,27 @@ class MetadataWorker : public AsyncWorker {
private: private:
MetadataBaton* baton; MetadataBaton* baton;
std::vector<v8::Local<v8::Object>> buffersToPersist;
}; };
/* /*
metadata(options, callback) metadata(options, callback)
*/ */
NAN_METHOD(metadata) { NAN_METHOD(metadata) {
HandleScope(); // Input Buffers must not undergo GC compaction during processing
std::vector<v8::Local<v8::Object>> buffersToPersist;
// V8 objects are converted to non-V8 types held in the baton struct // V8 objects are converted to non-V8 types held in the baton struct
MetadataBaton *baton = new MetadataBaton; MetadataBaton *baton = new MetadataBaton;
Local<Object> options = info[0].As<Object>(); v8::Local<v8::Object> options = info[0].As<v8::Object>();
// Input filename // Input
baton->fileIn = *Utf8String(Get(options, New("fileIn").ToLocalChecked()).ToLocalChecked()); baton->input = sharp::CreateInputDescriptor(sharp::AttrAs<v8::Object>(options, "input"), buffersToPersist);
// Input Buffer object
Local<Object> bufferIn;
if (node::Buffer::HasInstance(Get(options, New("bufferIn").ToLocalChecked()).ToLocalChecked())) {
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 // Join queue for worker thread
Callback *callback = new Callback(info[1].As<Function>()); Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>());
AsyncQueueWorker(new MetadataWorker(callback, baton, bufferIn)); Nan::AsyncQueueWorker(new MetadataWorker(callback, baton, buffersToPersist));
// Increment queued task counter // Increment queued task counter
g_atomic_int_inc(&counterQueue); g_atomic_int_inc(&sharp::counterQueue);
} }

View File

@@ -2,6 +2,41 @@
#define SRC_METADATA_H_ #define SRC_METADATA_H_
#include "nan.h" #include "nan.h"
#include "common.h"
struct MetadataBaton {
// Input
sharp::InputDescriptor *input;
// Output
std::string format;
int width;
int height;
std::string space;
int channels;
int density;
bool hasProfile;
bool hasAlpha;
int orientation;
char *exif;
size_t exifLength;
char *icc;
size_t iccLength;
std::string err;
MetadataBaton():
input(nullptr),
width(0),
height(0),
channels(0),
density(0),
hasProfile(false),
hasAlpha(false),
orientation(0),
exif(nullptr),
exifLength(0),
icc(nullptr),
iccLength(0) {}
};
NAN_METHOD(metadata); NAN_METHOD(metadata);

View File

@@ -1,6 +1,7 @@
#include <algorithm> #include <algorithm>
#include <tuple> #include <functional>
#include <memory> #include <memory>
#include <tuple>
#include <vips/vips8> #include <vips/vips8>
#include "common.h" #include "common.h"
@@ -289,69 +290,104 @@ namespace sharp {
} }
} }
/*
Calculate the Shannon entropy
*/
double EntropyStrategy::operator()(VImage image) {
return image.hist_find().hist_entropy();
}
/*
Calculate the intensity of edges, skin tone and saturation
*/
double AttentionStrategy::operator()(VImage image) {
// Convert to LAB colourspace
VImage lab = image.colourspace(VIPS_INTERPRETATION_LAB);
VImage l = lab[0];
VImage a = lab[1];
VImage b = lab[2];
// Edge detect luminosity with the Sobel operator
VImage sobel = vips::VImage::new_matrixv(3, 3,
-1.0, 0.0, 1.0,
-2.0, 0.0, 2.0,
-1.0, 0.0, 1.0);
VImage edges = l.conv(sobel).abs() + l.conv(sobel.rot90()).abs();
// Skin tone chroma thresholds trained with http://humanae.tumblr.com/
VImage skin = (a >= 3) & (a <= 22) & (b >= 4) & (b <= 31);
// Chroma >~50% saturation
VImage lch = lab.colourspace(VIPS_INTERPRETATION_LCH);
VImage c = lch[1];
VImage saturation = c > 60;
// Find maximum in combined saliency mask
VImage mask = edges + skin + saturation;
return mask.max();
}
/* /*
Calculate crop area based on image entropy Calculate crop area based on image entropy
*/ */
std::tuple<int, int> EntropyCrop(VImage image, int const outWidth, int const outHeight) { std::tuple<int, int> Crop(
VImage image, int const outWidth, int const outHeight, std::function<double(VImage)> strategy
) {
int left = 0; int left = 0;
int top = 0; int top = 0;
int const inWidth = image.width(); int const inWidth = image.width();
int const inHeight = image.height(); int const inHeight = image.height();
if (inWidth > outWidth) { if (inWidth > outWidth) {
// Reduce width by repeated removing slices from edge with lowest entropy // Reduce width by repeated removing slices from edge with lowest score
int width = inWidth; int width = inWidth;
double leftEntropy = 0.0; double leftScore = 0.0;
double rightEntropy = 0.0; double rightScore = 0.0;
// Max width of each slice // Max width of each slice
int const maxSliceWidth = static_cast<int>(ceil((inWidth - outWidth) / 8.0)); int const maxSliceWidth = static_cast<int>(ceil((inWidth - outWidth) / 8.0));
while (width > outWidth) { while (width > outWidth) {
// Width of current slice // Width of current slice
int const slice = std::min(width - outWidth, maxSliceWidth); int const slice = std::min(width - outWidth, maxSliceWidth);
if (leftEntropy == 0.0) { if (leftScore == 0.0) {
// Update entropy of left slice // Update score of left slice
leftEntropy = Entropy(image.extract_area(left, 0, slice, inHeight)); leftScore = strategy(image.extract_area(left, 0, slice, inHeight));
} }
if (rightEntropy == 0.0) { if (rightScore == 0.0) {
// Update entropy of right slice // Update score of right slice
rightEntropy = Entropy(image.extract_area(width - slice - 1, 0, slice, inHeight)); rightScore = strategy(image.extract_area(width - slice - 1, 0, slice, inHeight));
} }
// Keep slice with highest entropy // Keep slice with highest score
if (leftEntropy >= rightEntropy) { if (leftScore >= rightScore) {
// Discard right slice // Discard right slice
rightEntropy = 0.0; rightScore = 0.0;
} else { } else {
// Discard left slice // Discard left slice
leftEntropy = 0.0; leftScore = 0.0;
left = left + slice; left = left + slice;
} }
width = width - slice; width = width - slice;
} }
} }
if (inHeight > outHeight) { if (inHeight > outHeight) {
// Reduce height by repeated removing slices from edge with lowest entropy // Reduce height by repeated removing slices from edge with lowest score
int height = inHeight; int height = inHeight;
double topEntropy = 0.0; double topScore = 0.0;
double bottomEntropy = 0.0; double bottomScore = 0.0;
// Max height of each slice // Max height of each slice
int const maxSliceHeight = static_cast<int>(ceil((inHeight - outHeight) / 8.0)); int const maxSliceHeight = static_cast<int>(ceil((inHeight - outHeight) / 8.0));
while (height > outHeight) { while (height > outHeight) {
// Height of current slice // Height of current slice
int const slice = std::min(height - outHeight, maxSliceHeight); int const slice = std::min(height - outHeight, maxSliceHeight);
if (topEntropy == 0.0) { if (topScore == 0.0) {
// Update entropy of top slice // Update score of top slice
topEntropy = Entropy(image.extract_area(0, top, inWidth, slice)); topScore = strategy(image.extract_area(0, top, inWidth, slice));
} }
if (bottomEntropy == 0.0) { if (bottomScore == 0.0) {
// Update entropy of bottom slice // Update score of bottom slice
bottomEntropy = Entropy(image.extract_area(0, height - slice - 1, inWidth, slice)); bottomScore = strategy(image.extract_area(0, height - slice - 1, inWidth, slice));
} }
// Keep slice with highest entropy // Keep slice with highest score
if (topEntropy >= bottomEntropy) { if (topScore >= bottomScore) {
// Discard bottom slice // Discard bottom slice
bottomEntropy = 0.0; bottomScore = 0.0;
} else { } else {
// Discard top slice // Discard top slice
topEntropy = 0.0; topScore = 0.0;
top = top + slice; top = top + slice;
} }
height = height - slice; height = height - slice;
@@ -360,13 +396,6 @@ namespace sharp {
return std::make_tuple(left, top); return std::make_tuple(left, top);
} }
/*
Calculate the Shannon entropy for an image
*/
double Entropy(VImage image) {
return image.hist_find().hist_entropy();
}
/* /*
Insert a tile cache to prevent over-computation of any previous operations in the pipeline Insert a tile cache to prevent over-computation of any previous operations in the pipeline
*/ */
@@ -396,7 +425,8 @@ namespace sharp {
Perform boolean/bitwise operation on image color channels - results in one channel image Perform boolean/bitwise operation on image color channels - results in one channel image
*/ */
VImage Bandbool(VImage image, VipsOperationBoolean const boolean) { VImage Bandbool(VImage image, VipsOperationBoolean const boolean) {
return image.bandbool(boolean); image = image.bandbool(boolean);
return image.copy(VImage::option()->set("interpretation", VIPS_INTERPRETATION_B_W));
} }
/* /*

View File

@@ -1,8 +1,10 @@
#ifndef SRC_OPERATIONS_H_ #ifndef SRC_OPERATIONS_H_
#define SRC_OPERATIONS_H_ #define SRC_OPERATIONS_H_
#include <tuple> #include <algorithm>
#include <functional>
#include <memory> #include <memory>
#include <tuple>
#include <vips/vips8> #include <vips/vips8>
using vips::VImage; using vips::VImage;
@@ -63,14 +65,21 @@ namespace sharp {
VImage Sharpen(VImage image, double const sigma, double const flat, double const jagged); VImage Sharpen(VImage image, double const sigma, double const flat, double const jagged);
/* /*
Calculate crop area based on image entropy Crop strategy functors
*/ */
std::tuple<int, int> EntropyCrop(VImage image, int const outWidth, int const outHeight); struct EntropyStrategy {
double operator()(VImage image);
};
struct AttentionStrategy {
double operator()(VImage image);
};
/* /*
Calculate the Shannon entropy for an image Calculate crop area based on given strategy (Entropy, Attention)
*/ */
double Entropy(VImage image); std::tuple<int, int> Crop(
VImage image, int const outWidth, int const outHeight, std::function<double(VImage)> strategy
);
/* /*
Insert a tile cache to prevent over-computation of any previous operations in the pipeline Insert a tile cache to prevent over-computation of any previous operations in the pipeline

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,7 @@
#include <vips/vips8> #include <vips/vips8>
#include "nan.h" #include "nan.h"
#include "common.h"
NAN_METHOD(pipeline); NAN_METHOD(pipeline);
@@ -18,30 +19,20 @@ enum class Canvas {
}; };
struct PipelineBaton { struct PipelineBaton {
std::string fileIn; sharp::InputDescriptor *input;
char *bufferIn;
size_t bufferInLength;
std::string iccProfilePath; std::string iccProfilePath;
int limitInputPixels; int limitInputPixels;
int density;
int rawWidth;
int rawHeight;
int rawChannels;
std::string formatOut; std::string formatOut;
std::string fileOut; std::string fileOut;
void *bufferOut; void *bufferOut;
size_t bufferOutLength; size_t bufferOutLength;
std::string overlayFileIn; sharp::InputDescriptor *overlay;
char *overlayBufferIn;
size_t overlayBufferInLength;
int overlayGravity; int overlayGravity;
int overlayXOffset; int overlayXOffset;
int overlayYOffset; int overlayYOffset;
bool overlayTile; bool overlayTile;
bool overlayCutout; bool overlayCutout;
std::string booleanFileIn; std::vector<sharp::InputDescriptor *> joinChannelIn;
char *booleanBufferIn;
size_t booleanBufferInLength;
int topOffsetPre; int topOffsetPre;
int leftOffsetPre; int leftOffsetPre;
int widthPre; int widthPre;
@@ -55,6 +46,8 @@ struct PipelineBaton {
int channels; int channels;
Canvas canvas; Canvas canvas;
int crop; int crop;
int cropCalcLeft;
int cropCalcTop;
std::string kernel; std::string kernel;
std::string interpolator; std::string interpolator;
double background[4]; double background[4];
@@ -96,36 +89,33 @@ struct PipelineBaton {
int convKernelHeight; int convKernelHeight;
double convKernelScale; double convKernelScale;
double convKernelOffset; double convKernelOffset;
VipsOperationBoolean bandBoolOp; sharp::InputDescriptor *boolean;
VipsOperationBoolean booleanOp; VipsOperationBoolean booleanOp;
VipsOperationBoolean bandBoolOp;
int extractChannel; int extractChannel;
VipsInterpretation colourspace;
int tileSize; int tileSize;
int tileOverlap; int tileOverlap;
VipsForeignDzContainer tileContainer; VipsForeignDzContainer tileContainer;
VipsForeignDzLayout tileLayout; VipsForeignDzLayout tileLayout;
PipelineBaton(): PipelineBaton():
bufferInLength(0), input(nullptr),
limitInputPixels(0), limitInputPixels(0),
density(72),
rawWidth(0),
rawHeight(0),
rawChannels(0),
formatOut(""),
fileOut(""),
bufferOutLength(0), bufferOutLength(0),
overlayBufferInLength(0), overlay(nullptr),
overlayGravity(0), overlayGravity(0),
overlayXOffset(-1), overlayXOffset(-1),
overlayYOffset(-1), overlayYOffset(-1),
overlayTile(false), overlayTile(false),
overlayCutout(false), overlayCutout(false),
booleanBufferInLength(0),
topOffsetPre(-1), topOffsetPre(-1),
topOffsetPost(-1), topOffsetPost(-1),
channels(0), channels(0),
canvas(Canvas::CROP), canvas(Canvas::CROP),
crop(0), crop(0),
cropCalcLeft(-1),
cropCalcTop(-1),
flatten(false), flatten(false),
negate(false), negate(false),
blurSigma(0.0), blurSigma(0.0),
@@ -160,9 +150,11 @@ struct PipelineBaton {
convKernelHeight(0), convKernelHeight(0),
convKernelScale(0.0), convKernelScale(0.0),
convKernelOffset(0.0), convKernelOffset(0.0),
bandBoolOp(VIPS_OPERATION_BOOLEAN_LAST), boolean(nullptr),
booleanOp(VIPS_OPERATION_BOOLEAN_LAST), booleanOp(VIPS_OPERATION_BOOLEAN_LAST),
bandBoolOp(VIPS_OPERATION_BOOLEAN_LAST),
extractChannel(-1), extractChannel(-1),
colourspace(VIPS_INTERPRETATION_LAST),
tileSize(256), tileSize(256),
tileOverlap(0), tileOverlap(0),
tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS), tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS),

View File

@@ -8,14 +8,14 @@
"test": "VIPS_WARNING=0 node perf && node random && node parallel" "test": "VIPS_WARNING=0 node perf && node random && node parallel"
}, },
"devDependencies": { "devDependencies": {
"async": "^1.5.2", "async": "^2.1.1",
"benchmark": "^2.1.0", "benchmark": "^2.1.1",
"gm": "^1.22.0", "gm": "^1.23.0",
"imagemagick": "^0.1.3", "imagemagick": "^0.1.3",
"imagemagick-native": "^1.9.2", "imagemagick-native": "^1.9.3",
"jimp": "^0.2.24", "jimp": "^0.2.27",
"lwip": "^0.0.9", "lwip": "^0.0.9",
"semver": "^5.1.0" "semver": "^5.3.0"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {

View File

@@ -519,6 +519,36 @@ async.series({
} }
}); });
} }
}).add('sharp-crop-entropy', {
defer: true,
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.crop(sharp.strategy.entropy)
.toBuffer(function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add('sharp-crop-attention', {
defer: true,
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.crop(sharp.strategy.attention)
.toBuffer(function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).on('cycle', function(event) { }).on('cycle', function(event) {
console.log('operations ' + String(event.target)); console.log('operations ' + String(event.target));
}).on('complete', function() { }).on('complete', function() {

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -80,6 +80,7 @@ module.exports = {
inputPngAlphaPremultiplicationSmall: getPath('alpha-premultiply-1024x768-paper.png'), inputPngAlphaPremultiplicationSmall: getPath('alpha-premultiply-1024x768-paper.png'),
inputPngAlphaPremultiplicationLarge: getPath('alpha-premultiply-2048x1536-paper.png'), inputPngAlphaPremultiplicationLarge: getPath('alpha-premultiply-2048x1536-paper.png'),
inputPngBooleanNoAlpha: getPath('bandbool.png'), inputPngBooleanNoAlpha: getPath('bandbool.png'),
inputPngTestJoinChannel: getPath('testJoinChannel.png'),
inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp
inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp

Binary file not shown.

Before

Width:  |  Height:  |  Size: 502 B

After

Width:  |  Height:  |  Size: 338 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 624 B

After

Width:  |  Height:  |  Size: 418 B

BIN
test/fixtures/testJoinChannel.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 756 B

View File

@@ -29,7 +29,16 @@
... ...
fun:jpeg_finish_compress fun:jpeg_finish_compress
} }
{
value_jpeg_obj
Memcheck:Value8
obj:*/libjpeg.so*
}
{
cond_jpeg_obj
Memcheck:Cond
obj:*/libjpeg.so*
}
# libpng # libpng
{ {
cond_libpng_png_read_row cond_libpng_png_read_row

16
test/saliency/README.md Normal file
View File

@@ -0,0 +1,16 @@
# Crop strategy accuracy
1. Download the [MSRA Salient Object Database](http://research.microsoft.com/en-us/um/people/jiansun/SalientObject/salient_object.htm) (101MB).
2. Extract each image and its median human-labelled salient region.
3. Generate a test report of percentage deviance of top and left edges for each crop strategy, plus a naive centre gravity crop as "control".
```sh
git clone https://github.com/lovell/sharp.git
cd sharp/test/saliency
./download.sh
node report.js
python -m SimpleHTTPServer
```
The test report will then be available at
http://localhost:8000/report.html

25
test/saliency/download.sh Executable file
View File

@@ -0,0 +1,25 @@
#!/bin/sh
# Fetch and parse the MSRA Salient Object Database 'Image set B'
# http://research.microsoft.com/en-us/um/people/jiansun/salientobject/salient_object.htm
if [ ! -d Image ]; then
if [ ! -f ImageB.zip ]; then
echo "Downloading 5000 images (101MB)"
curl -O http://research.microsoft.com/en-us/um/people/jiansun/salientobject/ImageSetB/ImageB.zip
fi
unzip ImageB.zip
fi
if [ ! -d UserData ]; then
if [ ! -f UserDataB.zip ]; then
echo "Downloading human-labelled regions"
curl -O http://research.microsoft.com/en-us/um/people/jiansun/salientobject/ImageSetB/UserDataB.zip
fi
unzip UserDataB.zip
fi
if [ ! -f userData.json ]; then
echo "Processing human-labelled regions"
node userData.js
fi

View File

@@ -0,0 +1,39 @@
'use strict';
/*jshint esversion: 6 */
const fs = require('fs');
const request = require('request');
const tumblr = require('tumblr.js');
const client = tumblr.createClient({
consumer_key: '***',
consumer_secret: '***'
});
const fetchImages = function(offset) {
console.log(`Fetching offset ${offset}`);
client.posts('humanae', {
type: 'photo',
offset: offset
}, function (err, response) {
if (err) throw err;
if (response.posts.length > 0) {
response.posts.forEach((post) => {
const url = post.photos[0].alt_sizes
.filter((image) => image.width === 100)
.map((image) => image.url)
[0];
const filename = `./images/${post.id}.jpg`;
try {
fs.statSync(filename);
} catch (err) {
if (err.code === 'ENOENT') {
request(url).pipe(fs.createWriteStream(filename));
}
}
});
fetchImages(offset + 20);
}
});
};
fetchImages(0);

View File

@@ -0,0 +1,9 @@
{
"name": "sharp-crop-strategy-attention-model-humanae",
"version": "0.0.1",
"private": true,
"dependencies": {
"request": "^2.75.0",
"tumblr.js": "^1.1.1"
}
}

View File

@@ -0,0 +1,34 @@
'use strict';
/*jshint esversion: 6 */
const fs = require('fs');
const child_process = require('child_process');
const a = [];
const b = [];
fs.readdirSync('./images')
.filter((file) => file.endsWith('.jpg'))
.forEach((file) => {
// Extract one pixel, avoiding first DCT block, and return value of A and B channels
const command = `convert ./images/${file}[1x1+8+8] -colorspace lab -format "%[fx:u.g] %[fx:u.b]" info:`;
const result = child_process.execSync(command, { encoding: 'utf8' });
const ab = result.split(' ');
a.push(ab[0]);
b.push(ab[1]);
});
a.sort((v1, v2) => v1 - v2);
b.sort((v1, v2) => v1 - v2);
// Convert from 0..1 to -128..128
const convert = function(v) {
return Math.round(256 * (v - 0.5));
};
const threshold = Math.round(a.length / 100);
console.log(`Trimming lowest/highest ${threshold} for 98th percentile`);
// Ignore ~2% outliers
console.log(`a ${convert(a[threshold])} - ${convert(a[a.length - threshold])}`);
console.log(`b ${convert(b[threshold])} - ${convert(b[b.length - threshold])}`);

25
test/saliency/report.html Normal file
View File

@@ -0,0 +1,25 @@
<html>
<head>
<link href="https://cdnjs.cloudflare.com/ajax/libs/metrics-graphics/2.10.1/metricsgraphics.min.css" rel="stylesheet" type="text/css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.6/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/metrics-graphics/2.10.1/metricsgraphics.min.js"></script>
</head>
<body>
<div id="accuracy"></div>
<script>
d3.json('report.json', function(err, data) {
MG.data_graphic({
title: 'Crop accuracy',
data: data,
target: '#accuracy',
width: 960,
height: 600,
x_accessor: 'accuracy',
x_label: '% Accuracy',
y_accessor: ['centre', 'entropy', 'attention'],
legend: ['Centre', 'Entropy', 'Attention']
});
});
</script>
</body>
</html>

69
test/saliency/report.js Normal file
View File

@@ -0,0 +1,69 @@
'use strict';
/*jshint esversion: 6 */
const os = require('os');
const fs = require('fs');
const path = require('path');
const async = require('async');
const sharp = require('../../');
const crops = {
centre: sharp.gravity.centre,
entropy: sharp.strategy.entropy,
attention: sharp.strategy.attention
};
const concurrency = os.cpus().length;
const scores = {};
const incrementScore = function(accuracy, crop) {
if (typeof scores[accuracy] === 'undefined') {
scores[accuracy] = {};
}
if (typeof scores[accuracy][crop] === 'undefined') {
scores[accuracy][crop] = 0;
}
scores[accuracy][crop]++;
};
const userData = require('./userData.json');
const files = Object.keys(userData);
async.eachLimit(files, concurrency, function(file, done) {
const filename = path.join(__dirname, 'Image', file);
const salientWidth = userData[file].right - userData[file].left;
const salientHeight = userData[file].bottom - userData[file].top;
sharp(filename).metadata(function(err, metadata) {
if (err) console.log(err);
async.each(Object.keys(crops), function(crop, done) {
async.parallel([
// Left edge accuracy
function(done) {
sharp(filename).resize(salientWidth, metadata.height).crop(crops[crop]).toBuffer(function(err, data, info) {
const accuracy = Math.round(Math.abs(userData[file].left - info.cropCalcLeft) / (metadata.width - salientWidth) * 100);
incrementScore(accuracy, crop);
done();
});
},
// Top edge accuracy
function(done) {
sharp(filename).resize(metadata.width, salientHeight).crop(crops[crop]).toBuffer(function(err, data, info) {
const accuracy = Math.round(Math.abs(userData[file].top - info.cropCalcTop) / (metadata.height - salientHeight) * 100);
incrementScore(accuracy, crop);
done();
});
}
], done);
}, done);
});
}, function() {
const report = [];
Object.keys(scores).forEach(function(accuracy) {
report.push(
Object.assign({
accuracy: parseInt(accuracy, 10)
}, scores[accuracy])
);
});
fs.writeFileSync('report.json', JSON.stringify(report, null, 2));
});

69
test/saliency/userData.js Normal file
View File

@@ -0,0 +1,69 @@
'use strict';
/*jshint esversion: 6, loopfunc: true */
const fs = require('fs');
const path = require('path');
const userDataDir = 'UserData';
const images = {};
const median = function(values) {
values.sort(function(a,b) {
return a - b;
});
const half = Math.floor(values.length / 2);
if (values.length % 2) {
return values[half];
} else {
return Math.floor((values[half - 1] + values[half]) / 2);
}
};
// List of files
fs.readdirSync(userDataDir).forEach(function(file) {
// Contents of file
const lines = fs.readFileSync(path.join(userDataDir, file), {encoding: 'utf-8'}).split(/\r\n/);
// First line = number of entries
const entries = parseInt(lines[0], 10);
// Verify number of entries
if (entries !== 500) {
throw new Error('Expecting 500 images in ' + file + ', found ' + entries);
}
// Keep track of which line we're on
let linePos = 2;
for (let i = 0; i < entries; i++) {
// Get data for current image
const filename = lines[linePos].replace(/\\/, path.sep);
linePos = linePos + 2;
const regions = lines[linePos].split('; ');
linePos = linePos + 2;
// Parse human-labelled regions for min/max coords
const lefts = [], tops = [], rights = [], bottoms = [];
regions.forEach(function(region) {
if (region.indexOf(' ') !== -1) {
const coords = region.split(' ');
lefts.push(parseInt(coords[0], 10));
tops.push(parseInt(coords[1], 10));
rights.push(parseInt(coords[2], 10));
bottoms.push(parseInt(coords[3], 10));
}
});
// Add image
images[filename] = {
left: median(lefts),
top: median(tops),
right: median(rights),
bottom: median(bottoms)
};
}
});
// Verify number of images found
const imageCount = Object.keys(images).length;
if (imageCount === 5000) {
// Write output
fs.writeFileSync('userData.json', JSON.stringify(images, null, 2));
} else {
throw new Error('Expecting 5000 images, found ' + imageCount);
}

View File

@@ -15,6 +15,7 @@ describe('Bandbool per-channel boolean operations', function() {
it(op + ' operation', function(done) { it(op + ' operation', function(done) {
sharp(fixtures.inputPngBooleanNoAlpha) sharp(fixtures.inputPngBooleanNoAlpha)
.bandbool(op) .bandbool(op)
.toColourspace('b-w')
.toBuffer(function(err, data, info) { .toBuffer(function(err, data, info) {
if (err) throw err; if (err) throw err;
assert.strictEqual(200, info.width); assert.strictEqual(200, info.width);
@@ -25,6 +26,15 @@ describe('Bandbool per-channel boolean operations', function() {
}); });
}); });
it('sRGB image retains 3 channels', function(done) {
sharp(fixtures.inputJpg)
.bandbool('and')
.toBuffer(function(err, data, info) {
assert.strictEqual(3, info.channels);
done();
});
});
it('Invalid operation', function() { it('Invalid operation', function() {
assert.throws(function() { assert.throws(function() {
sharp().bandbool('fail'); sharp().bandbool('fail');

View File

@@ -40,6 +40,23 @@ describe('Boolean operation between two images', function() {
}); });
}); });
it(op + ' operation, raw', function(done) {
sharp(fixtures.inputJpgBooleanTest)
.raw()
.toBuffer(function(err, data, info) {
if (err) throw err;
sharp(fixtures.inputJpg)
.resize(320, 240)
.boolean(data, op, { raw: info })
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
fixtures.assertSimilar(fixtures.expected('boolean_' + op + '_result.jpg'), data, done);
});
});
});
}); });
it('Invalid operation', function() { it('Invalid operation', function() {

View File

@@ -29,6 +29,20 @@ describe('Colour space conversion', function() {
.toFile(fixtures.path('output.greyscale-not.jpg'), done); .toFile(fixtures.path('output.greyscale-not.jpg'), done);
}); });
it('Greyscale with single channel output', function(done) {
sharp(fixtures.inputJpg)
.resize(320, 240)
.greyscale()
.toColourspace('b-w')
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(1, info.channels);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
fixtures.assertSimilar(fixtures.expected('output.greyscale-single.jpg'), data, done);
});
});
if (sharp.format.tiff.input.file && sharp.format.webp.output.buffer) { if (sharp.format.tiff.input.file && sharp.format.webp.output.buffer) {
it('From 1-bit TIFF to sRGB WebP [slow]', function(done) { it('From 1-bit TIFF to sRGB WebP [slow]', function(done) {
sharp(fixtures.inputTiff) sharp(fixtures.inputTiff)
@@ -79,4 +93,10 @@ describe('Colour space conversion', function() {
}); });
}); });
it('Invalid input', function() {
assert.throws(function() {
sharp(fixtures.inputJpg)
.toColourspace(null);
});
});
}); });

View File

@@ -9,18 +9,17 @@ describe('Convolve', function() {
it('specific convolution kernel 1', function(done) { it('specific convolution kernel 1', function(done) {
sharp(fixtures.inputPngStripesV) sharp(fixtures.inputPngStripesV)
.resize(320, 240) .convolve({
.convolve( width: 3,
{ height: 3,
'width': 3, scale: 50,
'height': 3, offset: 0,
'scale': 50, kernel: [ 10, 20, 10,
'offset': 0,
'kernel': [ 10, 20, 10,
0, 0, 0, 0, 0, 0,
10, 20, 10 ] 10, 20, 10 ]
}) })
.toBuffer(function(err, data, info) { .toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual('png', info.format); assert.strictEqual('png', info.format);
assert.strictEqual(320, info.width); assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height); assert.strictEqual(240, info.height);
@@ -30,16 +29,15 @@ describe('Convolve', function() {
it('specific convolution kernel 2', function(done) { it('specific convolution kernel 2', function(done) {
sharp(fixtures.inputPngStripesH) sharp(fixtures.inputPngStripesH)
.resize(320, 240) .convolve({
.convolve( width: 3,
{ height: 3,
'width': 3, kernel: [ 1, 0, 1,
'height': 3,
'kernel': [ 1, 0, 1,
2, 0, 2, 2, 0, 2,
1, 0, 1 ] 1, 0, 1 ]
}) })
.toBuffer(function(err, data, info) { .toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual('png', info.format); assert.strictEqual('png', info.format);
assert.strictEqual(320, info.width); assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height); assert.strictEqual(240, info.height);
@@ -47,35 +45,47 @@ describe('Convolve', function() {
}); });
}); });
it('invalid kernel specification: no data', function() { it('horizontal Sobel operator', function(done) {
assert.throws(function() { sharp(fixtures.inputJpg)
sharp(fixtures.inputJpg).convolve( .resize(320, 240)
{ .convolve({
'width': 3, width: 3,
'height': 3, height: 3,
'kernel': [] kernel: [ -1, 0, 1,
}); -2, 0, 2,
-1, 0, 1 ]
})
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
fixtures.assertSimilar(fixtures.expected('conv-sobel-horizontal.jpg'), data, done);
}); });
}); });
it('invalid kernel specification: bad data format', function() { describe('invalid kernel specification', function() {
it('missing', function() {
assert.throws(function() { assert.throws(function() {
sharp(fixtures.inputJpg).convolve( sharp(fixtures.inputJpg).convolve({});
{
'width': 3,
'height': 3,
'kernel': [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
}); });
}); });
}); it('incorrect data format', function() {
it('invalid kernel specification: wrong width', function() {
assert.throws(function() { assert.throws(function() {
sharp(fixtures.inputJpg).convolve( sharp(fixtures.inputJpg).convolve({
{ width: 3,
'width': 3, height: 3,
'height': 4, kernel: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
'kernel': [1, 2, 3, 4, 5, 6, 7, 8, 9] });
});
});
it('incorrect dimensions', function() {
assert.throws(function() {
sharp(fixtures.inputJpg).convolve({
width: 3,
height: 4,
kernel: [1, 2, 3, 4, 5, 6, 7, 8, 9]
});
}); });
}); });
}); });

View File

@@ -29,6 +29,9 @@ describe('cpplint', function() {
}, },
whitespace: { whitespace: {
parens: false parens: false
},
runtime: {
indentation_namespace: false
} }
} }
}, function(err, report) { }, function(err, report) {

View File

@@ -172,7 +172,9 @@ describe('Crop', function() {
assert.strictEqual(3, info.channels); assert.strictEqual(3, info.channels);
assert.strictEqual(80, info.width); assert.strictEqual(80, info.width);
assert.strictEqual(320, info.height); assert.strictEqual(320, info.height);
fixtures.assertSimilar(fixtures.expected('crop-entropy.jpg'), data, done); assert.strictEqual(250, info.cropCalcLeft);
assert.strictEqual(0, info.cropCalcTop);
fixtures.assertSimilar(fixtures.expected('crop-strategy.jpg'), data, done);
}); });
}); });
@@ -186,10 +188,47 @@ describe('Crop', function() {
assert.strictEqual(4, info.channels); assert.strictEqual(4, info.channels);
assert.strictEqual(320, info.width); assert.strictEqual(320, info.width);
assert.strictEqual(80, info.height); assert.strictEqual(80, info.height);
fixtures.assertSimilar(fixtures.expected('crop-entropy.png'), data, done); assert.strictEqual(0, info.cropCalcLeft);
assert.strictEqual(80, info.cropCalcTop);
fixtures.assertSimilar(fixtures.expected('crop-strategy.png'), data, done);
}); });
}); });
}); });
describe('Attention strategy', function() {
it('JPEG', function(done) {
sharp(fixtures.inputJpgWithCmykProfile)
.resize(80, 320)
.crop(sharp.strategy.attention)
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
assert.strictEqual(3, info.channels);
assert.strictEqual(80, info.width);
assert.strictEqual(320, info.height);
assert.strictEqual(250, info.cropCalcLeft);
assert.strictEqual(0, info.cropCalcTop);
fixtures.assertSimilar(fixtures.expected('crop-strategy.jpg'), data, done);
});
});
it('PNG', function(done) {
sharp(fixtures.inputPngWithTransparency)
.resize(320, 80)
.crop(sharp.strategy.attention)
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual('png', info.format);
assert.strictEqual(4, info.channels);
assert.strictEqual(320, info.width);
assert.strictEqual(80, info.height);
assert.strictEqual(0, info.cropCalcLeft);
assert.strictEqual(80, info.cropCalcTop);
fixtures.assertSimilar(fixtures.expected('crop-strategy.png'), data, done);
});
});
});
}); });

View File

@@ -181,6 +181,7 @@ describe('Partial image extraction', function() {
.extract({ left: 3000, top: 10, width: 10, height: 10 }) .extract({ left: 3000, top: 10, width: 10, height: 10 })
.toBuffer(function(err) { .toBuffer(function(err) {
assert(err instanceof Error); assert(err instanceof Error);
assert.strictEqual(err.message, "extract_area: bad extract area\n");
done(); done();
}); });
}); });

View File

@@ -69,4 +69,13 @@ describe('Image channel extraction', function() {
}); });
}); });
it('Non-existant channel', function(done) {
sharp(fixtures.inputPng)
.extractChannel(1)
.toBuffer(function(err) {
assert(err instanceof Error);
done();
});
});
}); });

View File

@@ -60,12 +60,4 @@ describe('Interpolators and kernels', function() {
}); });
}); });
describe('deprecated interpolateWith method still works', function() {
it('resize then interpolateWith', function() {
sharp().resize(1, 1).interpolateWith('bicubic');
});
it('interpolateWith then resize', function() {
sharp().interpolateWith('bicubic').resize(1, 1);
});
});
}); });

151
test/unit/joinChannel.js Normal file
View File

@@ -0,0 +1,151 @@
'use strict';
var assert = require('assert');
var fs = require('fs');
var sharp = require('../../index');
var fixtures = require('../fixtures');
var BluebirdPromise = require('bluebird');
describe('Image channel insertion', function() {
it('Grayscale to RGB, buffer', function(done) {
sharp(fixtures.inputPng) // gray -> red
.resize(320, 240)
.joinChannel(fixtures.inputPngTestJoinChannel) // new green channel
.joinChannel(fixtures.inputPngStripesH) // new blue channel
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
assert.strictEqual(3, info.channels);
fixtures.assertSimilar(fixtures.expected('joinChannel-rgb.jpg'), data, done);
});
});
it('Grayscale to RGB, file', function(done) {
sharp(fixtures.inputPng) // gray -> red
.resize(320, 240)
.joinChannel(fs.readFileSync(fixtures.inputPngTestJoinChannel)) // new green channel
.joinChannel(fs.readFileSync(fixtures.inputPngStripesH)) // new blue channel
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
assert.strictEqual(3, info.channels);
fixtures.assertSimilar(fixtures.expected('joinChannel-rgb.jpg'), data, done);
});
});
it('Grayscale to RGBA, buffer', function(done) {
sharp(fixtures.inputPng) // gray -> red
.resize(320, 240)
.joinChannel([fixtures.inputPngTestJoinChannel,
fixtures.inputPngStripesH,
fixtures.inputPngStripesV]) // new green + blue + alpha channel
.toColourspace(sharp.colourspace.srgb)
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
assert.strictEqual(4, info.channels);
fixtures.assertSimilar(fixtures.expected('joinChannel-rgba.png'), data, done);
});
});
it('Grayscale to RGBA, file', function(done) {
sharp(fixtures.inputPng) // gray -> red
.resize(320, 240)
.joinChannel([fs.readFileSync(fixtures.inputPngTestJoinChannel), // new green channel
fs.readFileSync(fixtures.inputPngStripesH), // new blue channel
fs.readFileSync(fixtures.inputPngStripesV)]) // new alpha channel
.toColourspace('srgb')
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
assert.strictEqual(4, info.channels);
fixtures.assertSimilar(fixtures.expected('joinChannel-rgba.png'), data, done);
});
});
it('Grayscale to CMYK, buffers', function(done) {
sharp(fixtures.inputPng) // gray -> magenta
.resize(320, 240)
.joinChannel([fs.readFileSync(fixtures.inputPngTestJoinChannel), // new cyan channel
fs.readFileSync(fixtures.inputPngStripesH), // new yellow channel
fs.readFileSync(fixtures.inputPngStripesV)]) // new black channel
.toColorspace('cmyk')
.toFormat('jpeg')
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
assert.strictEqual(4, info.channels);
fixtures.assertSimilar(fixtures.expected('joinChannel-cmyk.jpg'), data, done);
});
});
it('Join raw buffers to RGB', function(done) {
BluebirdPromise.all([
sharp(fixtures.inputPngTestJoinChannel).toColourspace('b-w').raw().toBuffer(),
sharp(fixtures.inputPngStripesH).toColourspace('b-w').raw().toBuffer()
])
.then(function(buffers) {
sharp(fixtures.inputPng)
.resize(320, 240)
.joinChannel(buffers,
{ raw: {
width: 320,
height: 240,
channels: 1
}})
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
assert.strictEqual(3, info.channels);
fixtures.assertSimilar(fixtures.expected('joinChannel-rgb.jpg'), data, done);
});
})
.catch(function(err) {
throw err;
});
});
it('Grayscale to RGBA, files, two arrays', function(done) {
sharp(fixtures.inputPng) // gray -> red
.resize(320, 240)
.joinChannel([fs.readFileSync(fixtures.inputPngTestJoinChannel)]) // new green channel
.joinChannel([fs.readFileSync(fixtures.inputPngStripesH), // new blue channel
fs.readFileSync(fixtures.inputPngStripesV)]) // new alpha channel
.toColourspace('srgb')
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
assert.strictEqual(4, info.channels);
fixtures.assertSimilar(fixtures.expected('joinChannel-rgba.png'), data, done);
});
});
it('Invalid raw buffer description', function() {
assert.throws(function() {
sharp().joinChannel(fs.readFileSync(fixtures.inputPng),{raw:{}});
});
});
it('Invalid input', function() {
assert.throws(function() {
sharp(fixtures.inputJpg)
.joinChannel(1);
});
});
it('No arguments', function() {
assert.throws(function() {
sharp(fixtures.inputJpg)
.joinChannel();
});
});
});

View File

@@ -328,6 +328,22 @@ describe('Image metadata', function() {
}); });
}); });
it('Remove metadata from PNG output', function(done) {
sharp(fixtures.inputJpgWithExif)
.png()
.toBuffer(function(err, buffer) {
if (err) throw err;
sharp(buffer).metadata(function(err, metadata) {
if (err) throw err;
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual('undefined', typeof metadata.orientation);
assert.strictEqual('undefined', typeof metadata.exif);
assert.strictEqual('undefined', typeof metadata.icc);
done();
});
});
});
it('File input with corrupt header fails gracefully', function(done) { it('File input with corrupt header fails gracefully', function(done) {
sharp(fixtures.inputJpgWithCorruptHeader) sharp(fixtures.inputJpgWithCorruptHeader)
.metadata(function(err) { .metadata(function(err) {

View File

@@ -154,11 +154,20 @@ describe('Overlays', function() {
}); });
} }
it('Fail when overlay does not contain alpha channel', function(done) { it('Composite JPEG onto PNG', function(done) {
sharp(fixtures.inputPngOverlayLayer1) sharp(fixtures.inputPngOverlayLayer1)
.overlayWith(fixtures.inputJpg) .overlayWith(fixtures.inputJpgWithLandscapeExif1)
.toBuffer(function(error) { .toBuffer(function(error) {
assert.strictEqual(true, error instanceof Error); if (error) return done(error);
done();
});
});
it('Composite opaque JPEG onto JPEG', function(done) {
sharp(fixtures.inputJpg)
.overlayWith(fixtures.inputJpgWithLandscapeExif1)
.toBuffer(function(error) {
if (error) return done(error);
done(); done();
}); });
}); });
@@ -428,7 +437,27 @@ describe('Overlays', function() {
assert.strictEqual(3, info.channels); assert.strictEqual(3, info.channels);
fixtures.assertSimilar(expected, data, done); fixtures.assertSimilar(expected, data, done);
}); });
});
it('Overlay 100x100 with 50x50 so bottom edges meet', function(done) {
sharp(fixtures.inputJpg)
.resize(50, 50)
.toBuffer(function(err, overlay) {
if (err) throw err;
sharp(fixtures.inputJpgWithLandscapeExif1)
.resize(100, 100)
.overlayWith(overlay, {
top: 50,
left: 40
})
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
assert.strictEqual(100, info.width);
assert.strictEqual(100, info.height);
fixtures.assertSimilar(fixtures.expected('overlay-bottom-edges-meet.jpg'), data, done);
});
});
}); });
}); });
@@ -528,4 +557,28 @@ describe('Overlays', function() {
}); });
}); });
it('Composite RGBA raw buffer onto JPEG', function(done) {
sharp(fixtures.inputPngOverlayLayer1)
.raw()
.toBuffer(function(err, data, info) {
if (err) throw err;
sharp(fixtures.inputJpg)
.resize(2048, 1536)
.overlayWith(data, { raw: info })
.toBuffer(function(err, data) {
if (err) throw err;
fixtures.assertSimilar(fixtures.expected('overlay-jpeg-with-rgb.jpg'), data, done);
});
});
});
it('Throws an error when called with an invalid file', function(done) {
sharp(fixtures.inputJpg)
.overlayWith('notfound.png')
.toBuffer(function(err) {
assert(err instanceof Error);
done();
});
});
}); });

View File

@@ -128,6 +128,29 @@ describe('Resize dimensions', function() {
done(); done();
}); });
if (sharp.format.webp.output.buffer) {
it('WebP shrink-on-load rounds to zero, ensure recalculation is correct', function(done) {
sharp(fixtures.inputJpg)
.resize(1080, 607)
.webp()
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual('webp', info.format);
assert.strictEqual(1080, info.width);
assert.strictEqual(607, info.height);
sharp(data)
.resize(233, 131)
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual('webp', info.format);
assert.strictEqual(233, info.width);
assert.strictEqual(131, info.height);
done();
});
});
});
}
if (sharp.format.tiff.input.file) { if (sharp.format.tiff.input.file) {
it('TIFF embed known to cause rounding errors', function(done) { it('TIFF embed known to cause rounding errors', function(done) {
sharp(fixtures.inputTiff) sharp(fixtures.inputTiff)

View File

@@ -46,6 +46,7 @@ describe('Sharpen', function() {
}); });
}); });
if (!process.env.SHARP_TEST_WITHOUT_CACHE) {
it('specific radius/levels with alpha channel', function(done) { it('specific radius/levels with alpha channel', function(done) {
sharp(fixtures.inputPngWithTransparency) sharp(fixtures.inputPngWithTransparency)
.resize(320, 240) .resize(320, 240)
@@ -59,6 +60,7 @@ describe('Sharpen', function() {
fixtures.assertSimilar(fixtures.expected('sharpen-rgba.png'), data, done); fixtures.assertSimilar(fixtures.expected('sharpen-rgba.png'), data, done);
}); });
}); });
}
it('mild sharpen', function(done) { it('mild sharpen', function(done) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)

View File

@@ -38,7 +38,7 @@ describe('Trim borders', function() {
describe('Invalid thresholds', function() { describe('Invalid thresholds', function() {
[-1, 100, 'fail', {}].forEach(function(threshold) { [-1, 100, 'fail', {}].forEach(function(threshold) {
it(threshold, function() { it(JSON.stringify(threshold), function() {
assert.throws(function() { assert.throws(function() {
sharp().trim(threshold); sharp().trim(threshold);
}); });